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 import forms
21 from datetime import datetime
22 from django.utils.encoding import smart_unicode
23 from django.utils.translation import ugettext_lazy as _
24 from django.utils.decorators import decorator_from_middleware
25 from django.middleware.gzip import GZipMiddleware
29 from wiki.xml_tools import GradedText
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": forms.DocumentTextSaveForm(prefix="textsave"),
83 "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"),
84 "add_tag": forms.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 = forms.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 = forms.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 = forms.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 = forms.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 = forms.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 = forms.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 # do we need some automation?
408 graded = GradedText(chunk.materialize())
415 master = graded.master()
416 if first_master is None:
417 first_master = master
418 elif master != first_master:
419 chunk_dict['bad_master'] = master
420 chunks.append(chunk_dict)
422 return direct_to_template(request, "wiki/book_detail.html", extra_context={
426 "first_master": first_master,
430 def chunk_add(request, slug, chunk):
432 doc = Chunk.get(slug, chunk)
433 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
436 if request.method == "POST":
437 form = forms.ChunkAddForm(request.POST, instance=doc)
439 if request.user.is_authenticated():
440 creator = request.user
443 doc.split(creator=creator,
444 slug=form.cleaned_data['slug'],
445 comment=form.cleaned_data['comment'],
448 return http.HttpResponseRedirect(doc.book.get_absolute_url())
450 form = forms.ChunkAddForm(initial={
451 "slug": str(doc.number + 1),
452 "comment": "cz. %d" % (doc.number + 1, ),
455 return direct_to_template(request, "wiki/chunk_add.html", extra_context={
461 def chunk_edit(request, slug, chunk):
463 doc = Chunk.get(slug, chunk)
464 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
466 if request.method == "POST":
467 form = forms.ChunkForm(request.POST, instance=doc)
470 return http.HttpResponseRedirect(doc.book.get_absolute_url())
472 form = forms.ChunkForm(instance=doc)
473 return direct_to_template(request, "wiki/chunk_edit.html", extra_context={
479 def book_append(request, slug):
480 book = get_object_or_404(Book, slug=slug)
481 if request.method == "POST":
482 form = forms.BookAppendForm(request.POST)
484 append_to = form.cleaned_data['append_to']
485 append_to.append(book)
486 return http.HttpResponseRedirect(append_to.get_absolute_url())
488 form = forms.BookAppendForm()
489 return direct_to_template(request, "wiki/book_append_to.html", extra_context={
495 def book_edit(request, slug):
496 book = get_object_or_404(Book, slug=slug)
497 if request.method == "POST":
498 form = forms.BookForm(request.POST, instance=book)
501 return http.HttpResponseRedirect(book.get_absolute_url())
503 form = forms.BookForm(instance=book)
504 return direct_to_template(request, "wiki/book_edit.html", extra_context={
511 @ajax_require_permission('wiki.can_change_tags')
512 def add_tag(request, slug, chunk=None):
513 form = forms.DocumentTagForm(request.POST, prefix="addtag")
516 doc = Chunk.get(slug, chunk)
517 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
520 tag = form.cleaned_data['tag']
521 revision = revision=form.cleaned_data['revision']
522 doc.at_revision(revision).tags.add(tag)
523 return JSONResponse({"message": _("Tag added")})
525 return JSONFormInvalid(form)
533 @ajax_require_permission('wiki.can_publish')
534 def publish(request, name):
535 name = normalize_name(name)
537 storage = getstorage()
538 document = storage.get_by_tag(name, "ready_to_publish")
540 api = wlapi.WLAPI(**settings.WL_API_CONFIG)
543 return JSONResponse({"result": api.publish_book(document)})
544 except wlapi.APICallException, e:
545 return JSONServerError({"message": str(e)})
549 prefix = request.GET.get('q', '')
550 return http.HttpResponse('\n'.join([str(t) for t in Theme.objects.filter(name__istartswith=prefix)]))