3 logger = logging.getLogger("fnp.wiki")
5 from django.conf import settings
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 from django.http import Http404
16 from wiki.models import Book, Chunk, Theme
17 from wiki.forms import DocumentTextSaveForm, DocumentTextRevertForm, DocumentTagForm, DocumentCreateForm, DocumentsUploadForm
18 from datetime import datetime
19 from django.utils.encoding import smart_unicode
20 from django.utils.translation import ugettext_lazy as _
21 from django.utils.decorators import decorator_from_middleware
22 from django.middleware.gzip import GZipMiddleware
26 # Quick hack around caching problems, TODO: use ETags
28 from django.views.decorators.cache import never_cache
37 def document_list(request):
38 return direct_to_template(request, 'wiki/document_list.html', extra_context={
39 'books': Book.objects.all(),
40 'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
41 key=lambda x: x[1]['time'], reverse=True),
46 def editor(request, slug, chunk=None, template_name='wiki/document_details.html'):
48 chunk = Chunk.get(slug, chunk)
49 except Chunk.MultipleObjectsReturned:
52 except Chunk.DoesNotExist:
55 book = Book.objects.get(slug=slug)
56 except Book.DoesNotExist:
57 return http.HttpResponseRedirect(reverse("wiki_create_missing", args=[slug]))
61 access_time = datetime.now()
62 last_books = request.session.get("wiki_last_books", {})
63 last_books[slug, chunk.slug] = {
65 'title': chunk.pretty_name(),
68 if len(last_books) > MAX_LAST_DOCS:
69 oldest_key = min(last_books, key=lambda x: last_books[x]['time'])
70 del last_books[oldest_key]
71 request.session['wiki_last_books'] = last_books
73 return direct_to_template(request, template_name, extra_context={
76 "text_save": DocumentTextSaveForm(prefix="textsave"),
77 "text_revert": DocumentTextRevertForm(prefix="textrevert"),
78 "add_tag": DocumentTagForm(prefix="addtag"),
80 'REDMINE_URL': settings.REDMINE_URL,
85 def editor_readonly(request, slug, chunk=None, template_name='wiki/document_details_readonly.html'):
87 chunk = Chunk.get(slug, chunk)
88 revision = request.GET['revision']
89 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist, KeyError):
92 access_time = datetime.now()
93 last_books = request.session.get("wiki_last_books", {})
94 last_books[slug, chunk.slug] = {
96 'title': chunk.book.title,
99 if len(last_books) > MAX_LAST_DOCS:
100 oldest_key = min(last_books, key=lambda x: last_books[x]['time'])
101 del last_books[oldest_key]
102 request.session['wiki_last_books'] = last_books
104 return direct_to_template(request, template_name, extra_context={
106 'revision': revision,
108 'REDMINE_URL': settings.REDMINE_URL,
112 def create_missing(request, slug):
113 slug = slug.replace(' ', '-')
115 if request.method == "POST":
116 form = DocumentCreateForm(request.POST, request.FILES)
119 if request.user.is_authenticated():
120 creator = request.user
123 book = Book.create(creator=creator,
124 slug=form.cleaned_data['slug'],
125 title=form.cleaned_data['title'],
126 text=form.cleaned_data['text'],
129 return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
131 form = DocumentCreateForm(initial={
133 "title": slug.replace('-', ' ').title(),
136 return direct_to_template(request, "wiki/document_create_missing.html", extra_context={
143 if request.method == "POST":
144 form = DocumentsUploadForm(request.POST, request.FILES)
148 if request.user.is_authenticated():
149 creator = request.user
153 zip = form.cleaned_data['zip']
158 existing = [book.slug for book in Book.objects.all()]
159 for filename in zip.namelist():
160 if filename[-1] == '/':
162 title = os.path.basename(filename)[:-4]
163 slug = slughifi(title)
164 if not (slug and filename.endswith('.xml')):
165 skipped_list.append(filename)
167 error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
168 elif slug in existing:
169 error_list.append((filename, slug, _('Slug already used in repository.')))
172 zip.read(filename).decode('utf-8') # test read
173 ok_list.append((filename, slug, title))
174 except UnicodeDecodeError:
175 error_list.append((filename, title, _('File should be UTF-8 encoded.')))
176 slugs[slug] = filename
179 for filename, slug, title in ok_list:
180 Book.create(creator=creator,
183 text=zip.read(filename).decode('utf-8'),
186 return direct_to_template(request, "wiki/document_upload.html", extra_context={
189 "skipped_list": skipped_list,
190 "error_list": error_list,
193 form = DocumentsUploadForm()
195 return direct_to_template(request, "wiki/document_upload.html", extra_context={
201 @decorator_from_middleware(GZipMiddleware)
202 def text(request, slug, chunk=None):
204 doc = Chunk.get(slug, chunk).doc
205 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
208 if request.method == 'POST':
209 form = DocumentTextSaveForm(request.POST, prefix="textsave")
212 # - stage completion should be stored (as a relation)
214 if request.user.is_authenticated():
215 author = request.user
218 text = form.cleaned_data['text']
219 parent_revision = form.cleaned_data['parent_revision']
220 parent = doc.at_revision(parent_revision)
221 doc.commit(author=author,
224 description=form.cleaned_data['comment'],
226 revision = doc.revision()
227 return JSONResponse({
228 'text': doc.materialize() if parent_revision != revision else None,
230 'revision': revision,
233 return JSONFormInvalid(form)
235 revision = request.GET.get("revision", None)
238 revision = int(revision)
239 except (ValueError, TypeError):
242 return JSONResponse({
243 'text': doc.at_revision(revision).materialize(),
245 'revision': revision if revision else doc.revision(),
250 def compiled(request, slug):
251 text = get_object_or_404(Book, slug=slug).materialize()
253 response = http.HttpResponse(text, content_type='application/xml', mimetype='application/wl+xml')
254 response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
260 def revert(request, slug, chunk=None):
261 form = DocumentTextRevertForm(request.POST, prefix="textrevert")
264 doc = Chunk.get(slug, chunk).doc
265 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
268 revision = form.cleaned_data['revision']
270 comment = form.cleaned_data['comment']
271 comment += "\n#revert to %s" % revision
273 if request.user.is_authenticated():
274 author = request.user
278 before = doc.revision()
279 logger.info("Reverting %s to %s", slug, revision)
280 doc.at_revision(revision).revert(author=author, description=comment)
282 return JSONResponse({
283 'text': doc.materialize() if before != doc.revision() else None,
285 'revision': doc.revision(),
288 return JSONFormInvalid(form)
292 def gallery(request, directory):
295 smart_unicode(settings.MEDIA_URL),
296 smart_unicode(settings.FILEBROWSER_DIRECTORY),
297 smart_unicode(directory)))
299 base_dir = os.path.join(
300 smart_unicode(settings.MEDIA_ROOT),
301 smart_unicode(settings.FILEBROWSER_DIRECTORY),
302 smart_unicode(directory))
304 def map_to_url(filename):
305 return "%s/%s" % (base_url, smart_unicode(filename))
307 def is_image(filename):
308 return os.path.splitext(f)[1].lower() in (u'.jpg', u'.jpeg', u'.png')
310 images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)]
312 return JSONResponse(images)
313 except (IndexError, OSError):
314 logger.exception("Unable to fetch gallery")
319 def diff(request, slug, chunk=None):
320 revA = int(request.GET.get('from', 0))
321 revB = int(request.GET.get('to', 0))
324 revA, revB = revB, revA
330 doc = Chunk.get(slug, chunk).doc
331 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
333 docA = doc.at_revision(revA).materialize()
334 docB = doc.at_revision(revB).materialize()
336 return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(),
337 docB.splitlines(), context=3))
341 def revision(request, slug, chunk=None):
343 doc = Chunk.get(slug, chunk).doc
344 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
346 return http.HttpResponse(str(doc.revision()))
350 def history(request, slug, chunk=None):
353 doc = Chunk.get(slug, chunk).doc
354 except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
358 for change in doc.history().order_by('-created_at'):
360 author = "%s %s <%s>" % (
361 change.author.first_name,
362 change.author.last_name,
367 "version": change.revision,
368 "description": change.description,
370 "date": change.created_at,
373 return JSONResponse(changes)
382 @ajax_require_permission('wiki.can_change_tags')
383 def add_tag(request, name):
384 name = normalize_name(name)
385 storage = getstorage()
387 form = DocumentTagForm(request.POST, prefix="addtag")
389 doc = storage.get_or_404(form.cleaned_data['id'])
390 doc.add_tag(tag=form.cleaned_data['tag'],
391 revision=form.cleaned_data['revision'],
392 author=request.user.username)
393 return JSONResponse({"message": _("Tag added")})
395 return JSONFormInvalid(form)
399 @ajax_require_permission('wiki.can_publish')
400 def publish(request, name):
401 name = normalize_name(name)
403 storage = getstorage()
404 document = storage.get_by_tag(name, "ready_to_publish")
406 api = wlapi.WLAPI(**settings.WL_API_CONFIG)
409 return JSONResponse({"result": api.publish_book(document)})
410 except wlapi.APICallException, e:
411 return JSONServerError({"message": str(e)})
415 prefix = request.GET.get('q', '')
416 return http.HttpResponse('\n'.join([str(t) for t in Theme.objects.filter(name__istartswith=prefix)]))