tags in editor metadata window
[redakcja.git] / apps / wiki / views.py
1 # -*- coding: utf-8 -*-
2 #
3 # This file is part of MIL/PEER, licensed under GNU Affero GPLv3 or later.
4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 #
6 import json
7 import os
8 import logging
9 import urllib
10
11 from django.conf import settings
12 from django import http
13 from django.http import HttpResponseForbidden
14 from django.middleware.gzip import GZipMiddleware
15 from django.utils.decorators import decorator_from_middleware
16 from django.utils.encoding import smart_unicode
17 from django.utils.formats import localize
18 from django.utils.html import escape
19 from django.utils.translation import ugettext as _
20 from django.views.decorators.http import require_POST
21 from django.shortcuts import get_object_or_404, render
22
23 from catalogue.models import Document, Template, Category
24 from dvcs.models import Revision
25 import nice_diff
26 from wiki import forms
27 from wiki.helpers import JSONResponse, JSONFormInvalid
28
29 #
30 # Quick hack around caching problems, TODO: use ETags
31 #
32 from django.views.decorators.cache import never_cache
33
34 logger = logging.getLogger("fnp.wiki")
35
36 MAX_LAST_DOCS = 10
37
38
39 def get_history(document):
40     revisions = []
41     for i, revision in enumerate(document.history()):
42         revisions.append({
43             "version": i + 1,
44             "description": revision.description,
45             "author": escape(revision.author_str()),
46             "date": localize(revision.created_at),
47             "revision": revision.pk,
48             "published": _("Published") + ": " +
49             localize(revision.publish_log.order_by('-timestamp')[0].timestamp)
50             if revision.publish_log.exists() else "",
51         })
52     return revisions
53
54
55 @never_cache
56 def editor(request, pk, template_name='wiki/bootstrap.html'):
57     doc = get_object_or_404(Document, pk=pk, deleted=False)
58     if not doc.can_edit(request.user):
59         return HttpResponseForbidden("Not authorized.")
60
61     save_form = forms.DocumentTextSaveForm(user=request.user, prefix="textsave")
62     text = doc.materialize()
63     revision = doc.revision
64     history = get_history(doc)
65     return render(request, template_name, {
66         'serialized_document_data': json.dumps({
67             'document': text,
68             'document_id': doc.pk,
69             'title': doc.meta().get('title', ''),
70             'history': history,
71             'version': len(history),
72             'revision': revision.pk,
73             'stage': doc.stage,
74             'stage_name': doc.stage_name(),
75             'assignment': doc.assigned_to.username if doc.assigned_to else None,
76         }),
77         'serialized_templates': json.dumps([
78             {'id': t.id, 'name': t.name, 'content': t.content} for t in Template.objects.filter(is_partial=True)
79         ]),
80         'forms': {
81             "text_save": save_form,
82             "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"),
83             "text_publish": forms.DocumentTextPublishForm(prefix="textpublish"),
84         },
85         'tag_categories': Category.objects.all(),
86         'pk': doc.pk,
87     })
88
89
90 @never_cache
91 @decorator_from_middleware(GZipMiddleware)
92 def text(request, doc_id):
93     doc = get_object_or_404(Document, pk=doc_id, deleted=False)
94
95     if request.method == 'POST':
96         if not doc.can_edit(request.user):
97             return HttpResponseForbidden("Not authorized.")
98         form = forms.DocumentTextSaveForm(request.POST, user=request.user, prefix="textsave")
99         if form.is_valid():
100             if request.user.is_authenticated():
101                 author = request.user
102             else:
103                 author = None
104             text = form.cleaned_data['text']
105             # parent_revision = form.cleaned_data['parent_revision']
106             # if parent_revision is not None:
107             #     parent = doc.at_revision(parent_revision)
108             # else:
109             #     parent = None
110             stage = form.cleaned_data['stage']
111             # tags = [stage] if stage else []
112             # publishable = (form.cleaned_data['publishable'] and
113             #                request.user.has_perm('catalogue.can_pubmark'))
114             try:
115                 doc.commit(
116                     author=author,
117                     text=text,
118                     description=form.cleaned_data['comment'],
119                     author_name=form.cleaned_data['author_name'],
120                     author_email=form.cleaned_data['author_email'],
121                 )
122                 doc.set_stage(stage)
123             except:
124                 from traceback import print_exc
125                 print_exc()
126                 raise
127             return JSONResponse({
128                 'text': None,  # doc.materialize() if parent_revision != revision else None,
129                 'version': len(get_history(doc)),
130                 'stage': doc.stage,
131                 'stage_name': doc.stage_name(),
132                 'assignment': doc.assigned_to.username if doc.assigned_to else None
133             })
134         else:
135             return JSONFormInvalid(form)
136     else:
137         revision = request.GET.get("revision", None)
138         
139         try:
140             revision = int(revision)
141         except (ValueError, TypeError):
142             revision = doc.revision()
143
144         if revision is not None:
145             text = doc.at_revision(revision).materialize()
146         else:
147             text = ''
148
149         return JSONResponse({
150             'text': text,
151             'meta': {},
152             'revision': revision,
153         })
154
155
156 @never_cache
157 @require_POST
158 def revert(request, doc_id):
159     form = forms.DocumentTextRevertForm(request.POST, prefix="textrevert")
160     if form.is_valid():
161         doc = get_object_or_404(Document, pk=doc_id, deleted=False)
162         if not doc.can_edit(request.user):
163             return HttpResponseForbidden("Not authorized.")
164         rev = get_object_or_404(Revision, pk=form.cleaned_data['revision'])
165
166         comment = form.cleaned_data['comment']
167         comment += "\n#revert to %s" % rev.pk
168
169         if request.user.is_authenticated():
170             author = request.user
171         else:
172             author = None
173
174         # before = doc.revision
175         logger.info("Reverting %s to %s", doc_id, rev.pk)
176
177         doc.commit(
178             author=author,
179             text=rev.materialize(),
180             description=comment,
181             # author_name=form.cleaned_data['author_name'], #?
182             # author_email=form.cleaned_data['author_email'], #?
183         )
184
185         return JSONResponse({
186             'document': doc.materialize(),
187             'version': len(get_history(doc)),
188             'stage': doc.stage,
189             'stage_name': doc.stage_name(),
190             'assignment': doc.assigned_to.username if doc.assigned_to else None,
191         })
192     else:
193         return JSONFormInvalid(form)
194
195
196 @never_cache
197 def gallery(request, directory):
198     if not request.user.is_authenticated():
199         return HttpResponseForbidden("Not authorized.")
200
201     try:
202         base_url = ''.join((
203                         smart_unicode(settings.MEDIA_URL),
204                         smart_unicode(settings.IMAGE_DIR),
205                         smart_unicode(directory)))
206
207         base_dir = os.path.join(
208                     smart_unicode(settings.MEDIA_ROOT),
209                     smart_unicode(settings.IMAGE_DIR),
210                     smart_unicode(directory))
211
212         def map_to_url(filename):
213             return urllib.quote("%s/%s" % (base_url, smart_unicode(filename)))
214
215         def is_image(filename):
216             return os.path.splitext(f)[1].lower() in (u'.jpg', u'.jpeg', u'.png')
217
218         images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)]
219         images.sort()
220
221         return JSONResponse(images)
222     except (IndexError, OSError):
223         logger.exception("Unable to fetch gallery")
224         raise http.Http404
225
226
227 @never_cache
228 def diff(request, doc_id):
229     revA = int(request.GET.get('from', 0))
230     revB = int(request.GET.get('to', 0))
231
232     if revA > revB:
233         revA, revB = revB, revA
234
235     if revB == 0:
236         revB = None
237
238     # TODO: check if revisions in line.
239
240     doc = get_object_or_404(Document, pk=doc_id, deleted=False)
241
242     # allow diff from the beginning
243     if revA:
244         docA = Revision.objects.get(pk=revA).materialize()
245     else:
246         docA = ""
247     docB = Revision.objects.get(pk=revB).materialize()
248
249     return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(), docB.splitlines(), context=3))
250
251
252 @never_cache
253 def history(request, doc_id):
254     # TODO: pagination
255     doc = get_object_or_404(Document, pk=doc_id, deleted=False)
256
257     return JSONResponse(get_history(doc))