6f09ddd696ee603a2c2db0502e5bb61b89477df3
[redakcja.git] / apps / wiki / views.py
1 import os
2 import logging
3 logger = logging.getLogger("fnp.wiki")
4
5 from django.conf import settings
6
7 from django.views.generic.simple import direct_to_template
8 from django.views.decorators.http import require_POST, require_GET
9 from django.core.urlresolvers import reverse
10 from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
11                 ajax_require_permission, recursive_groupby)
12 from django import http
13 from django.shortcuts import get_object_or_404, redirect
14
15 from wiki.models import Book, Theme
16 from wiki.forms import DocumentTextSaveForm, DocumentTextRevertForm, DocumentTagForm, DocumentCreateForm, DocumentsUploadForm
17 from datetime import datetime
18 from django.utils.encoding import smart_unicode
19 from django.utils.translation import ugettext_lazy as _
20 from django.utils.decorators import decorator_from_middleware
21 from django.middleware.gzip import GZipMiddleware
22
23
24 #
25 # Quick hack around caching problems, TODO: use ETags
26 #
27 from django.views.decorators.cache import never_cache
28
29 import nice_diff
30 import operator
31
32 MAX_LAST_DOCS = 10
33
34
35 @never_cache
36 def document_list(request):
37     return direct_to_template(request, 'wiki/document_list.html', extra_context={
38         'books': Book.objects.all(),
39         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
40                         key=lambda x: x[1]['time'], reverse=True),
41     })
42
43
44 @never_cache
45 def editor(request, slug, template_name='wiki/document_details.html'):
46     try:
47         book = Book.objects.get(slug=slug)
48     except Book.DoesNotExist:
49         return http.HttpResponseRedirect(reverse("wiki_create_missing", args=[slug]))
50
51     access_time = datetime.now()
52     last_books = request.session.get("wiki_last_books", {})
53     last_books[slug] = {
54         'time': access_time,
55         'title': book.title,
56         }
57
58     if len(last_books) > MAX_LAST_DOCS:
59         oldest_key = min(last_books, key=operator.itemgetter('time'))
60         del last_books[oldest_key]
61     request.session['wiki_last_books'] = last_books
62
63     return direct_to_template(request, template_name, extra_context={
64         'book': book,
65         'forms': {
66             "text_save": DocumentTextSaveForm(prefix="textsave"),
67             "text_revert": DocumentTextRevertForm(prefix="textrevert"),
68             "add_tag": DocumentTagForm(prefix="addtag"),
69         },
70         'REDMINE_URL': settings.REDMINE_URL,
71     })
72
73
74 @require_GET
75 def editor_readonly(request, slug, template_name='wiki/document_details_readonly.html'):
76     try:
77         book = Book.objects.get(slug=slug)
78         revision = request.GET['revision']
79     except KeyError:
80         raise http.Http404
81
82     access_time = datetime.now()
83     last_books = request.session.get("wiki_last_books", {})
84     last_books[slug] = {
85         'time': access_time,
86         'title': book.title,
87         }
88
89     if len(last_books) > MAX_LAST_DOCS:
90         oldest_key = min(last_books, key=operator.itemgetter('time'))
91         del last_books[oldest_key]
92     request.session['wiki_last_books'] = last_books
93
94     return direct_to_template(request, template_name, extra_context={
95         'book': book,
96         'revision': revision,
97         'readonly': True,
98         'REDMINE_URL': settings.REDMINE_URL,
99     })
100
101
102 def create_missing(request, slug):
103     slug = slug.replace(' ', '-')
104
105     if request.method == "POST":
106         form = DocumentCreateForm(request.POST, request.FILES)
107         if form.is_valid():
108             
109             if request.user.is_authenticated():
110                 creator = request.user
111             else:
112                 creator = None
113             book = Book.create(creator=creator,
114                 slug=form.cleaned_data['slug'],
115                 title=form.cleaned_data['title'],
116                 text=form.cleaned_data['text'],
117             )
118
119             return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
120     else:
121         form = DocumentCreateForm(initial={
122                 "slug": slug,
123                 "title": slug.replace('-', ' ').title(),
124         })
125
126     return direct_to_template(request, "wiki/document_create_missing.html", extra_context={
127         "slug": slug,
128         "form": form,
129     })
130
131
132 def upload(request):
133     if request.method == "POST":
134         form = DocumentsUploadForm(request.POST, request.FILES)
135         if form.is_valid():
136             import slughifi
137
138             if request.user.is_authenticated():
139                 creator = request.user
140             else:
141                 creator = None
142
143             zip = form.cleaned_data['zip']
144             skipped_list = []
145             ok_list = []
146             error_list = []
147             slugs = {}
148             existing = [book.slug for book in Book.objects.all()]
149             for filename in zip.namelist():
150                 if filename[-1] == '/':
151                     continue
152                 title = os.path.basename(filename)[:-4]
153                 slug = slughifi(title)
154                 if not (slug and filename.endswith('.xml')):
155                     skipped_list.append(filename)
156                 elif slug in slugs:
157                     error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
158                 elif slug in existing:
159                     error_list.append((filename, slug, _('Slug already used in repository.')))
160                 else:
161                     try:
162                         zip.read(filename).decode('utf-8') # test read
163                         ok_list.append((filename, slug, title))
164                     except UnicodeDecodeError:
165                         error_list.append((filename, title, _('File should be UTF-8 encoded.')))
166                     slugs[slug] = filename
167
168             if not error_list:
169                 for filename, slug, title in ok_list:
170                     Book.create(creator=creator,
171                         slug=slug,
172                         title=title,
173                         text=zip.read(filename).decode('utf-8'),
174                     )
175
176             return direct_to_template(request, "wiki/document_upload.html", extra_context={
177                 "form": form,
178                 "ok_list": ok_list,
179                 "skipped_list": skipped_list,
180                 "error_list": error_list,
181             })
182     else:
183         form = DocumentsUploadForm()
184
185     return direct_to_template(request, "wiki/document_upload.html", extra_context={
186         "form": form,
187     })
188
189
190 @never_cache
191 @decorator_from_middleware(GZipMiddleware)
192 def text(request, slug):
193     doc = get_object_or_404(Book, slug=slug).doc
194
195     if request.method == 'POST':
196         form = DocumentTextSaveForm(request.POST, prefix="textsave")
197         if form.is_valid():
198             # TODO:
199             # - stage completion should be stored (as a relation)
200
201             if request.user.is_authenticated():
202                 author = request.user
203             else:
204                 author = None
205             text = form.cleaned_data['text']
206             parent_revision = form.cleaned_data['parent_revision']
207             parent = doc.at_revision(parent_revision)
208             doc.commit(author=author,
209                        text=text,
210                        parent=parent,
211                        description=form.cleaned_data['comment'],
212                        )
213             revision = doc.revision()
214             return JSONResponse({
215                 'text': doc.materialize() if parent_revision != revision else None,
216                 'meta': {},
217                 'revision': revision,
218             })
219         else:
220             return JSONFormInvalid(form)
221     else:
222         revision = request.GET.get("revision", None)
223         
224         try:
225             revision = int(revision)
226         except ValueError:
227             revision = None
228
229         return JSONResponse({
230             'text': doc.at_revision(revision).materialize(),
231             'meta': {},
232             'revision': revision if revision else doc.revision(),
233         })
234
235
236 @never_cache
237 @require_POST
238 def revert(request, slug):
239     form = DocumentTextRevertForm(request.POST, prefix="textrevert")
240     if form.is_valid():
241         doc = get_object_or_404(Book, slug=slug).doc
242         revision = form.cleaned_data['revision']
243
244         comment = form.cleaned_data['comment']
245         comment += "\n#revert to %s" % revision
246
247         if request.user.is_authenticated():
248             author = request.user
249         else:
250             author = None
251
252         before = doc.revision()
253         logger.info("Reverting %s to %s", slug, revision)
254         doc.at_revision(revision).revert(author=author, description=comment)
255
256         return JSONResponse({
257             'text': doc.materialize() if before != doc.revision() else None,
258             'meta': {},
259             'revision': doc.revision(),
260         })
261     else:
262         return JSONFormInvalid(form)
263
264
265 @never_cache
266 def gallery(request, directory):
267     try:
268         base_url = ''.join((
269                         smart_unicode(settings.MEDIA_URL),
270                         smart_unicode(settings.FILEBROWSER_DIRECTORY),
271                         smart_unicode(directory)))
272
273         base_dir = os.path.join(
274                     smart_unicode(settings.MEDIA_ROOT),
275                     smart_unicode(settings.FILEBROWSER_DIRECTORY),
276                     smart_unicode(directory))
277
278         def map_to_url(filename):
279             return "%s/%s" % (base_url, smart_unicode(filename))
280
281         def is_image(filename):
282             return os.path.splitext(f)[1].lower() in (u'.jpg', u'.jpeg', u'.png')
283
284         images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)]
285         images.sort()
286         return JSONResponse(images)
287     except (IndexError, OSError):
288         logger.exception("Unable to fetch gallery")
289         raise http.Http404
290
291
292 @never_cache
293 def diff(request, slug):
294     revA = int(request.GET.get('from', 0))
295     revB = int(request.GET.get('to', 0))
296
297     if revA > revB:
298         revA, revB = revB, revA
299
300     if revB == 0:
301         revB = None
302
303     doc = get_object_or_404(Book, slug=slug).doc
304     docA = doc.at_revision(revA).materialize()
305     docB = doc.at_revision(revB).materialize()
306
307     return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(),
308                                          docB.splitlines(), context=3))
309
310
311 @never_cache
312 def revision(request, slug):
313     book = get_object_or_404(Book, slug=slug)
314     return http.HttpResponse(str(book.doc.revision()))
315
316
317 @never_cache
318 def history(request, slug):
319     # TODO: pagination
320     book = get_object_or_404(Book, slug=slug)
321     rev = book.doc.revision()
322     changes = []
323     for change in book.doc.history().order_by('-created_at'):
324         if change.author:
325             author = "%s %s <%s>" % (
326                 change.author.first_name,
327                 change.author.last_name, 
328                 change.author.email)
329         else:
330             author = None
331         changes.append({
332                 "version": rev,
333                 "description": change.description,
334                 "author": author,
335                 "date": change.created_at,
336                 "tag": [],
337             })
338         rev -= 1
339     return JSONResponse(changes)
340
341
342
343 """
344 import wlapi
345
346
347 @require_POST
348 @ajax_require_permission('wiki.can_change_tags')
349 def add_tag(request, name):
350     name = normalize_name(name)
351     storage = getstorage()
352
353     form = DocumentTagForm(request.POST, prefix="addtag")
354     if form.is_valid():
355         doc = storage.get_or_404(form.cleaned_data['id'])
356         doc.add_tag(tag=form.cleaned_data['tag'],
357                     revision=form.cleaned_data['revision'],
358                     author=request.user.username)
359         return JSONResponse({"message": _("Tag added")})
360     else:
361         return JSONFormInvalid(form)
362
363
364 @require_POST
365 @ajax_require_permission('wiki.can_publish')
366 def publish(request, name):
367     name = normalize_name(name)
368
369     storage = getstorage()
370     document = storage.get_by_tag(name, "ready_to_publish")
371
372     api = wlapi.WLAPI(**settings.WL_API_CONFIG)
373
374     try:
375         return JSONResponse({"result": api.publish_book(document)})
376     except wlapi.APICallException, e:
377         return JSONServerError({"message": str(e)})
378 """
379
380 def themes(request):
381     prefix = request.GET.get('q', '')
382     return http.HttpResponse('\n'.join([str(t) for t in Theme.objects.filter(name__istartswith=prefix)]))