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
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
31 # Quick hack around caching problems, TODO: use ETags
33 from django.views.decorators.cache import never_cache
42 def document_list(request):
43 return direct_to_template(request, 'wiki/document_list.html', extra_context={
44 'books': Book.objects.all(),
45 'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
46 key=lambda x: x[1]['time'], reverse=True),
51 def editor(request, slug, chunk=None, template_name='wiki/document_details.html'):
53 chunk = Chunk.get(slug, chunk)
54 except Chunk.MultipleObjectsReturned:
57 except Chunk.DoesNotExist:
60 book = Book.objects.get(slug=slug)
61 except Book.DoesNotExist:
62 return http.HttpResponseRedirect(reverse("wiki_create_missing", args=[slug]))
66 access_time = datetime.now()
67 last_books = request.session.get("wiki_last_books", {})
68 last_books[slug, chunk.slug] = {
70 'title': chunk.pretty_name(),
73 if len(last_books) > MAX_LAST_DOCS:
74 oldest_key = min(last_books, key=lambda x: last_books[x]['time'])
75 del last_books[oldest_key]
76 request.session['wiki_last_books'] = last_books
78 return direct_to_template(request, template_name, extra_context={
81 "text_save": DocumentTextSaveForm(prefix="textsave"),
82 "text_revert": DocumentTextRevertForm(prefix="textrevert"),
83 "add_tag": DocumentTagForm(prefix="addtag"),
85 'REDMINE_URL': settings.REDMINE_URL,
90 def editor_readonly(request, slug, chunk=None, template_name='wiki/document_details_readonly.html'):
92 chunk = Chunk.get(slug, chunk)
93 revision = request.GET['revision']
94 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist, KeyError):
97 access_time = datetime.now()
98 last_books = request.session.get("wiki_last_books", {})
99 last_books[slug, chunk.slug] = {
101 'title': chunk.book.title,
104 if len(last_books) > MAX_LAST_DOCS:
105 oldest_key = min(last_books, key=lambda x: last_books[x]['time'])
106 del last_books[oldest_key]
107 request.session['wiki_last_books'] = last_books
109 return direct_to_template(request, template_name, extra_context={
111 'revision': revision,
113 'REDMINE_URL': settings.REDMINE_URL,
117 def create_missing(request, slug):
118 slug = slug.replace(' ', '-')
120 if request.method == "POST":
121 form = DocumentCreateForm(request.POST, request.FILES)
124 if request.user.is_authenticated():
125 creator = request.user
128 book = Book.create(creator=creator,
129 slug=form.cleaned_data['slug'],
130 title=form.cleaned_data['title'],
131 text=form.cleaned_data['text'],
134 return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
136 form = DocumentCreateForm(initial={
138 "title": slug.replace('-', ' ').title(),
141 return direct_to_template(request, "wiki/document_create_missing.html", extra_context={
148 if request.method == "POST":
149 form = DocumentsUploadForm(request.POST, request.FILES)
153 if request.user.is_authenticated():
154 creator = request.user
158 zip = form.cleaned_data['zip']
163 existing = [book.slug for book in Book.objects.all()]
164 for filename in zip.namelist():
165 if filename[-1] == '/':
167 title = os.path.basename(filename)[:-4]
168 slug = slughifi(title)
169 if not (slug and filename.endswith('.xml')):
170 skipped_list.append(filename)
172 error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
173 elif slug in existing:
174 error_list.append((filename, slug, _('Slug already used in repository.')))
177 zip.read(filename).decode('utf-8') # test read
178 ok_list.append((filename, slug, title))
179 except UnicodeDecodeError:
180 error_list.append((filename, title, _('File should be UTF-8 encoded.')))
181 slugs[slug] = filename
184 for filename, slug, title in ok_list:
185 Book.create(creator=creator,
188 text=zip.read(filename).decode('utf-8'),
191 return direct_to_template(request, "wiki/document_upload.html", extra_context={
194 "skipped_list": skipped_list,
195 "error_list": error_list,
198 form = DocumentsUploadForm()
200 return direct_to_template(request, "wiki/document_upload.html", extra_context={
206 @decorator_from_middleware(GZipMiddleware)
207 def text(request, slug, chunk=None):
209 doc = Chunk.get(slug, chunk)
210 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
213 if request.method == 'POST':
214 form = DocumentTextSaveForm(request.POST, prefix="textsave")
217 # - stage completion should be stored (as a relation)
219 if request.user.is_authenticated():
220 author = request.user
223 text = form.cleaned_data['text']
224 parent_revision = form.cleaned_data['parent_revision']
225 parent = doc.at_revision(parent_revision)
226 doc.commit(author=author,
229 description=form.cleaned_data['comment'],
231 revision = doc.revision()
232 return JSONResponse({
233 'text': doc.materialize() if parent_revision != revision else None,
235 'revision': revision,
238 return JSONFormInvalid(form)
240 revision = request.GET.get("revision", None)
243 revision = int(revision)
244 except (ValueError, TypeError):
247 return JSONResponse({
248 'text': doc.at_revision(revision).materialize(),
250 'revision': revision if revision else doc.revision(),
255 def book_xml(request, slug):
256 xml = get_object_or_404(Book, slug=slug).materialize()
258 response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
259 response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
264 def book_txt(request, slug):
265 xml = get_object_or_404(Book, slug=slug).materialize()
268 librarian.text.transform(StringIO(xml), output)
269 text = output.getvalue()
270 response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
271 response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
276 def book_html(request, slug):
277 xml = get_object_or_404(Book, slug=slug).materialize()
280 librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
282 html = output.getvalue()
283 response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
289 def revert(request, slug, chunk=None):
290 form = DocumentTextRevertForm(request.POST, prefix="textrevert")
293 doc = Chunk.get(slug, chunk)
294 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
297 revision = form.cleaned_data['revision']
299 comment = form.cleaned_data['comment']
300 comment += "\n#revert to %s" % revision
302 if request.user.is_authenticated():
303 author = request.user
307 before = doc.revision()
308 logger.info("Reverting %s to %s", slug, revision)
309 doc.at_revision(revision).revert(author=author, description=comment)
311 return JSONResponse({
312 'text': doc.materialize() if before != doc.revision() else None,
314 'revision': doc.revision(),
317 return JSONFormInvalid(form)
321 def gallery(request, directory):
324 smart_unicode(settings.MEDIA_URL),
325 smart_unicode(settings.FILEBROWSER_DIRECTORY),
326 smart_unicode(directory)))
328 base_dir = os.path.join(
329 smart_unicode(settings.MEDIA_ROOT),
330 smart_unicode(settings.FILEBROWSER_DIRECTORY),
331 smart_unicode(directory))
333 def map_to_url(filename):
334 return "%s/%s" % (base_url, smart_unicode(filename))
336 def is_image(filename):
337 return os.path.splitext(f)[1].lower() in (u'.jpg', u'.jpeg', u'.png')
339 images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)]
341 return JSONResponse(images)
342 except (IndexError, OSError):
343 logger.exception("Unable to fetch gallery")
348 def diff(request, slug, chunk=None):
349 revA = int(request.GET.get('from', 0))
350 revB = int(request.GET.get('to', 0))
353 revA, revB = revB, revA
359 doc = Chunk.get(slug, chunk)
360 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
362 docA = doc.at_revision(revA).materialize()
363 docB = doc.at_revision(revB).materialize()
365 return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(),
366 docB.splitlines(), context=3))
370 def revision(request, slug, chunk=None):
372 doc = Chunk.get(slug, chunk)
373 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
375 return http.HttpResponse(str(doc.revision()))
379 def history(request, slug, chunk=None):
382 doc = Chunk.get(slug, chunk)
383 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
387 for change in doc.history().order_by('-created_at'):
389 author = "%s %s <%s>" % (
390 change.author.first_name,
391 change.author.last_name,
396 "version": change.revision,
397 "description": change.description,
399 "date": change.created_at,
402 return JSONResponse(changes)
405 def book(request, slug):
406 book = get_object_or_404(Book, slug=slug)
408 return direct_to_template(request, "wiki/book_detail.html", extra_context={
419 @ajax_require_permission('wiki.can_change_tags')
420 def add_tag(request, name):
421 name = normalize_name(name)
422 storage = getstorage()
424 form = DocumentTagForm(request.POST, prefix="addtag")
426 doc = storage.get_or_404(form.cleaned_data['id'])
427 doc.add_tag(tag=form.cleaned_data['tag'],
428 revision=form.cleaned_data['revision'],
429 author=request.user.username)
430 return JSONResponse({"message": _("Tag added")})
432 return JSONFormInvalid(form)
436 @ajax_require_permission('wiki.can_publish')
437 def publish(request, name):
438 name = normalize_name(name)
440 storage = getstorage()
441 document = storage.get_by_tag(name, "ready_to_publish")
443 api = wlapi.WLAPI(**settings.WL_API_CONFIG)
446 return JSONResponse({"result": api.publish_book(document)})
447 except wlapi.APICallException, e:
448 return JSONServerError({"message": str(e)})
452 prefix = request.GET.get('q', '')
453 return http.HttpResponse('\n'.join([str(t) for t in Theme.objects.filter(name__istartswith=prefix)]))