Optimization.
[redakcja.git] / src / wiki_img / views.py
1 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 import os
5 import functools
6 import logging
7 logger = logging.getLogger("fnp.wiki_img")
8
9 from django.urls import reverse
10 from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
11                 ajax_require_permission)
12
13 from django.http import Http404, HttpResponse, HttpResponseForbidden
14 from django.shortcuts import get_object_or_404, render
15 from django.views.decorators.http import require_GET, require_POST
16 from django.conf import settings
17 from django.utils.formats import localize
18 from django.utils.translation import gettext as _
19
20 from documents.models import Image
21 from wiki import forms
22 from wiki import nice_diff
23 from wiki_img.forms import ImageSaveForm
24
25 #
26 # Quick hack around caching problems, TODO: use ETags
27 #
28 from django.views.decorators.cache import never_cache
29
30
31 @never_cache
32 def editor(request, slug, template_name='wiki_img/document_details.html'):
33     doc = get_object_or_404(Image, slug=slug)
34
35     return render(request, template_name, {
36         'document': doc,
37         'forms': {
38             "text_save": ImageSaveForm(user=request.user, prefix="textsave"),
39             "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"),
40             "pubmark": forms.DocumentPubmarkForm(prefix="pubmark"),
41         },
42         'can_pubmark': request.user.has_perm('documents.can_pubmark_image'),
43         'REDMINE_URL': settings.REDMINE_URL,
44     })
45
46
47 @require_GET
48 def editor_readonly(request, slug, template_name='wiki_img/document_details_readonly.html'):
49     doc = get_object_or_404(Image, slug=slug)
50     try:
51         revision = request.GET['revision']
52     except (KeyError):
53         raise Http404
54
55     return render(request, template_name, {
56         'document': doc,
57         'revision': revision,
58         'readonly': True,
59         'REDMINE_URL': settings.REDMINE_URL,
60     })
61
62
63 @never_cache
64 def text(request, image_id):
65     doc = get_object_or_404(Image, pk=image_id)
66     if request.method == 'POST':
67         form = ImageSaveForm(request.POST, user=request.user, prefix="textsave")
68         if form.is_valid():
69             if request.user.is_authenticated:
70                 author = request.user
71             else:
72                 author = None
73             text = form.cleaned_data['text']
74             parent_revision = form.cleaned_data['parent_revision']
75             if parent_revision is not None:
76                 parent = doc.at_revision(parent_revision)
77             else:
78                 parent = None
79             stage = form.cleaned_data['stage_completed']
80             tags = [stage] if stage else []
81             publishable = (form.cleaned_data['publishable'] and
82                     request.user.has_perm('documents.can_pubmark_image'))
83             doc.commit(author=author,
84                    text=text,
85                    parent=parent,
86                    description=form.cleaned_data['comment'],
87                    tags=tags,
88                    author_name=form.cleaned_data['author_name'],
89                    author_email=form.cleaned_data['author_email'],
90                    publishable=publishable,
91                 )
92             revision = doc.revision()
93             return JSONResponse({
94                 'text': doc.materialize() if parent_revision != revision else None,
95                 'meta': {},
96                 'revision': revision,
97             })
98         else:
99             return JSONFormInvalid(form)
100     else:
101         revision = request.GET.get("revision", None)
102         
103         try:
104             revision = int(revision)
105         except (ValueError, TypeError):
106             revision = doc.revision()
107
108         if revision is not None:
109             text = doc.at_revision(revision).materialize()
110         else:
111             text = ''
112
113         return JSONResponse({
114             'text': text,
115             'meta': {},
116             'revision': revision,
117         })
118
119
120 @never_cache
121 def history(request, object_id):
122     # TODO: pagination
123     doc = get_object_or_404(Image, pk=object_id)
124     if not doc.accessible(request):
125         return HttpResponseForbidden("Not authorized.")
126
127     changes = []
128     for change in doc.history().reverse():
129         changes.append({
130                 "version": change.revision,
131                 "description": change.description,
132                 "author": change.author_str(),
133                 "date": localize(change.created_at),
134                 "publishable": _("Publishable") + "\n" if change.publishable else "",
135                 "tag": ',\n'.join(str(tag) for tag in change.tags.all()),
136             })
137     return JSONResponse(changes)
138
139
140 @never_cache
141 @require_POST
142 def revert(request, object_id):
143     form = forms.DocumentTextRevertForm(request.POST, prefix="textrevert")
144     if form.is_valid():
145         doc = get_object_or_404(Image, pk=object_id)
146         if not doc.accessible(request):
147             return HttpResponseForbidden("Not authorized.")
148
149         revision = form.cleaned_data['revision']
150
151         comment = form.cleaned_data['comment']
152         comment += "\n#revert to %s" % revision
153
154         if request.user.is_authenticated:
155             author = request.user
156         else:
157             author = None
158
159         before = doc.revision()
160         logger.info("Reverting %s to %s", object_id, revision)
161         doc.at_revision(revision).revert(author=author, description=comment)
162
163         return JSONResponse({
164             'text': doc.materialize() if before != doc.revision() else None,
165             'meta': {},
166             'revision': doc.revision(),
167         })
168     else:
169         return JSONFormInvalid(form)
170
171
172 @never_cache
173 def diff(request, object_id):
174     revA = int(request.GET.get('from', 0))
175     revB = int(request.GET.get('to', 0))
176
177     if revA > revB:
178         revA, revB = revB, revA
179
180     if revB == 0:
181         revB = None
182
183     doc = get_object_or_404(Image, pk=object_id)
184     if not doc.accessible(request):
185         return HttpResponseForbidden("Not authorized.")
186
187     # allow diff from the beginning
188     if revA:
189         docA = doc.at_revision(revA).materialize()
190     else:
191         docA = ""
192     docB = doc.at_revision(revB).materialize()
193
194     return HttpResponse(nice_diff.html_diff_table(docA.splitlines(),
195                                          docB.splitlines(), context=3))
196
197
198 @require_POST
199 @ajax_require_permission('documents.can_pubmark_image')
200 def pubmark(request, object_id):
201     form = forms.DocumentPubmarkForm(request.POST, prefix="pubmark")
202     if form.is_valid():
203         doc = get_object_or_404(Image, pk=object_id)
204         if not doc.accessible(request):
205             return HttpResponseForbidden("Not authorized.")
206
207         revision = form.cleaned_data['revision']
208         publishable = form.cleaned_data['publishable']
209         change = doc.at_revision(revision)
210         if publishable != change.publishable:
211             change.set_publishable(publishable)
212             return JSONResponse({"message": _("Revision marked")})
213         else:
214             return JSONResponse({"message": _("Nothing changed")})
215     else:
216         return JSONFormInvalid(form)