2 from StringIO import StringIO
4 logger = logging.getLogger("fnp.wiki")
8 from django.conf import settings
10 from django.views.generic.simple import direct_to_template
11 from django.views.decorators.http import require_POST, require_GET
12 from django.core.urlresolvers import reverse
13 from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
14 ajax_require_permission, recursive_groupby)
15 from django import http
16 from django.shortcuts import get_object_or_404, redirect
17 from django.http import Http404
19 from wiki.models import Book, Chunk, Theme
20 from wiki.forms import (DocumentTextSaveForm, DocumentTextRevertForm, DocumentTagForm, DocumentCreateForm, DocumentsUploadForm,
22 from datetime import datetime
23 from django.utils.encoding import smart_unicode
24 from django.utils.translation import ugettext_lazy as _
25 from django.utils.decorators import decorator_from_middleware
26 from django.middleware.gzip import GZipMiddleware
32 # Quick hack around caching problems, TODO: use ETags
34 from django.views.decorators.cache import never_cache
43 def document_list(request):
44 return direct_to_template(request, 'wiki/document_list.html', extra_context={
45 'books': Book.objects.all(),
46 'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
47 key=lambda x: x[1]['time'], reverse=True),
52 def editor(request, slug, chunk=None, template_name='wiki/document_details.html'):
54 chunk = Chunk.get(slug, chunk)
55 except Chunk.MultipleObjectsReturned:
58 except Chunk.DoesNotExist:
61 book = Book.objects.get(slug=slug)
62 except Book.DoesNotExist:
63 return http.HttpResponseRedirect(reverse("wiki_create_missing", args=[slug]))
67 access_time = datetime.now()
68 last_books = request.session.get("wiki_last_books", {})
69 last_books[slug, chunk.slug] = {
71 'title': chunk.pretty_name(),
74 if len(last_books) > MAX_LAST_DOCS:
75 oldest_key = min(last_books, key=lambda x: last_books[x]['time'])
76 del last_books[oldest_key]
77 request.session['wiki_last_books'] = last_books
79 return direct_to_template(request, template_name, extra_context={
82 "text_save": DocumentTextSaveForm(prefix="textsave"),
83 "text_revert": DocumentTextRevertForm(prefix="textrevert"),
84 "add_tag": DocumentTagForm(prefix="addtag"),
86 'REDMINE_URL': settings.REDMINE_URL,
91 def editor_readonly(request, slug, chunk=None, template_name='wiki/document_details_readonly.html'):
93 chunk = Chunk.get(slug, chunk)
94 revision = request.GET['revision']
95 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist, KeyError):
98 access_time = datetime.now()
99 last_books = request.session.get("wiki_last_books", {})
100 last_books[slug, chunk.slug] = {
102 'title': chunk.book.title,
105 if len(last_books) > MAX_LAST_DOCS:
106 oldest_key = min(last_books, key=lambda x: last_books[x]['time'])
107 del last_books[oldest_key]
108 request.session['wiki_last_books'] = last_books
110 return direct_to_template(request, template_name, extra_context={
112 'revision': revision,
114 'REDMINE_URL': settings.REDMINE_URL,
118 def create_missing(request, slug):
119 slug = slug.replace(' ', '-')
121 if request.method == "POST":
122 form = DocumentCreateForm(request.POST, request.FILES)
125 if request.user.is_authenticated():
126 creator = request.user
129 book = Book.create(creator=creator,
130 slug=form.cleaned_data['slug'],
131 title=form.cleaned_data['title'],
132 text=form.cleaned_data['text'],
135 return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
137 form = DocumentCreateForm(initial={
139 "title": slug.replace('-', ' ').title(),
142 return direct_to_template(request, "wiki/document_create_missing.html", extra_context={
149 if request.method == "POST":
150 form = DocumentsUploadForm(request.POST, request.FILES)
154 if request.user.is_authenticated():
155 creator = request.user
159 zip = form.cleaned_data['zip']
164 existing = [book.slug for book in Book.objects.all()]
165 for filename in zip.namelist():
166 if filename[-1] == '/':
168 title = os.path.basename(filename)[:-4]
169 slug = slughifi(title)
170 if not (slug and filename.endswith('.xml')):
171 skipped_list.append(filename)
173 error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
174 elif slug in existing:
175 error_list.append((filename, slug, _('Slug already used in repository.')))
178 zip.read(filename).decode('utf-8') # test read
179 ok_list.append((filename, slug, title))
180 except UnicodeDecodeError:
181 error_list.append((filename, title, _('File should be UTF-8 encoded.')))
182 slugs[slug] = filename
185 for filename, slug, title in ok_list:
186 Book.create(creator=creator,
189 text=zip.read(filename).decode('utf-8'),
192 return direct_to_template(request, "wiki/document_upload.html", extra_context={
195 "skipped_list": skipped_list,
196 "error_list": error_list,
199 form = DocumentsUploadForm()
201 return direct_to_template(request, "wiki/document_upload.html", extra_context={
207 @decorator_from_middleware(GZipMiddleware)
208 def text(request, slug, chunk=None):
210 doc = Chunk.get(slug, chunk)
211 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
214 if request.method == 'POST':
215 form = DocumentTextSaveForm(request.POST, prefix="textsave")
217 if request.user.is_authenticated():
218 author = request.user
221 text = form.cleaned_data['text']
222 parent_revision = form.cleaned_data['parent_revision']
223 parent = doc.at_revision(parent_revision)
224 stage = form.cleaned_data['stage_completed']
225 tags = [stage] if stage else []
226 doc.commit(author=author,
229 description=form.cleaned_data['comment'],
232 revision = doc.revision()
233 return JSONResponse({
234 'text': doc.materialize() if parent_revision != revision else None,
236 'revision': revision,
239 return JSONFormInvalid(form)
241 revision = request.GET.get("revision", None)
244 revision = int(revision)
245 except (ValueError, TypeError):
248 return JSONResponse({
249 'text': doc.at_revision(revision).materialize(),
251 'revision': revision if revision else doc.revision(),
256 def book_xml(request, slug):
257 xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
259 response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
260 response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
265 def book_txt(request, slug):
266 xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
269 librarian.text.transform(StringIO(xml), output)
270 text = output.getvalue()
271 response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
272 response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
277 def book_html(request, slug):
278 xml = get_object_or_404(Book, slug=slug).materialize(Book.publish_tag())
281 librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
283 html = output.getvalue()
284 response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
290 def revert(request, slug, chunk=None):
291 form = DocumentTextRevertForm(request.POST, prefix="textrevert")
294 doc = Chunk.get(slug, chunk)
295 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
298 revision = form.cleaned_data['revision']
300 comment = form.cleaned_data['comment']
301 comment += "\n#revert to %s" % revision
303 if request.user.is_authenticated():
304 author = request.user
308 before = doc.revision()
309 logger.info("Reverting %s to %s", slug, revision)
310 doc.at_revision(revision).revert(author=author, description=comment)
312 return JSONResponse({
313 'text': doc.materialize() if before != doc.revision() else None,
315 'revision': doc.revision(),
318 return JSONFormInvalid(form)
322 def gallery(request, directory):
325 smart_unicode(settings.MEDIA_URL),
326 smart_unicode(settings.FILEBROWSER_DIRECTORY),
327 smart_unicode(directory)))
329 base_dir = os.path.join(
330 smart_unicode(settings.MEDIA_ROOT),
331 smart_unicode(settings.FILEBROWSER_DIRECTORY),
332 smart_unicode(directory))
334 def map_to_url(filename):
335 return "%s/%s" % (base_url, smart_unicode(filename))
337 def is_image(filename):
338 return os.path.splitext(f)[1].lower() in (u'.jpg', u'.jpeg', u'.png')
340 images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)]
342 return JSONResponse(images)
343 except (IndexError, OSError):
344 logger.exception("Unable to fetch gallery")
349 def diff(request, slug, chunk=None):
350 revA = int(request.GET.get('from', 0))
351 revB = int(request.GET.get('to', 0))
354 revA, revB = revB, revA
360 doc = Chunk.get(slug, chunk)
361 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
363 docA = doc.at_revision(revA).materialize()
364 docB = doc.at_revision(revB).materialize()
366 return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(),
367 docB.splitlines(), context=3))
371 def revision(request, slug, chunk=None):
373 doc = Chunk.get(slug, chunk)
374 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
376 return http.HttpResponse(str(doc.revision()))
380 def history(request, slug, chunk=None):
383 doc = Chunk.get(slug, chunk)
384 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
388 for change in doc.history().order_by('-created_at'):
390 "version": change.revision,
391 "description": change.description,
392 "author": change.author_str(),
393 "date": change.created_at,
394 "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()),
396 return JSONResponse(changes)
399 def book(request, slug):
400 book = get_object_or_404(Book, slug=slug)
402 return direct_to_template(request, "wiki/book_detail.html", extra_context={
408 @ajax_require_permission('wiki.can_change_tags')
409 def add_tag(request, slug, chunk=None):
410 form = DocumentTagForm(request.POST, prefix="addtag")
413 doc = Chunk.get(slug, chunk)
414 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
417 tag = form.cleaned_data['tag']
418 revision = revision=form.cleaned_data['revision']
419 doc.at_revision(revision).tags.add(tag)
420 return JSONResponse({"message": _("Tag added")})
422 return JSONFormInvalid(form)
430 @ajax_require_permission('wiki.can_publish')
431 def publish(request, name):
432 name = normalize_name(name)
434 storage = getstorage()
435 document = storage.get_by_tag(name, "ready_to_publish")
437 api = wlapi.WLAPI(**settings.WL_API_CONFIG)
440 return JSONResponse({"result": api.publish_book(document)})
441 except wlapi.APICallException, e:
442 return JSONServerError({"message": str(e)})
446 prefix = request.GET.get('q', '')
447 return http.HttpResponse('\n'.join([str(t) for t in Theme.objects.filter(name__istartswith=prefix)]))