refactor catalogue to separate app,
[redakcja.git] / apps / catalogue / views.py
1 from datetime import datetime
2 import logging
3 import os
4 from StringIO import StringIO
5
6 from django.contrib import auth
7 from django.contrib.auth.models import User
8 from django.contrib.auth.decorators import login_required
9 from django.core.urlresolvers import reverse
10 from django.db.models import Count
11 from django import http
12 from django.http import Http404
13 from django.shortcuts import get_object_or_404
14 from django.utils.http import urlquote_plus
15 from django.utils.translation import ugettext_lazy as _
16 from django.views.decorators.http import require_POST
17 from django.views.generic.simple import direct_to_template
18
19 import librarian.html
20 import librarian.text
21
22 from apiclient import api_call
23 from catalogue import forms
24 from catalogue import helpers
25 from catalogue.helpers import active_tab
26 from catalogue.models import Book, Chunk
27 from catalogue import xml_tools
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.catalogue")
35
36
37 @active_tab('all')
38 @never_cache
39 def document_list(request):
40     chunks_list = helpers.ChunksList(Chunk.objects.order_by(
41         'book__title', 'book', 'number'))
42
43     return direct_to_template(request, 'catalogue/document_list.html', extra_context={
44         'books': chunks_list,
45         #'books': [helpers.BookChunks(b) for b in Book.objects.all().select_related()],
46         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
47                         key=lambda x: x[1]['time'], reverse=True),
48     })
49
50
51 @active_tab('unassigned')
52 @never_cache
53 def unassigned(request):
54     chunks_list = helpers.ChunksList(Chunk.objects.filter(
55         user=None).order_by('book__title', 'book__id', 'number'))
56
57     return direct_to_template(request, 'catalogue/document_list.html', extra_context={
58         'books': chunks_list,
59         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
60                         key=lambda x: x[1]['time'], reverse=True),
61     })
62
63
64 @never_cache
65 def user(request, username=None):
66     if username is None:
67         if request.user.is_authenticated():
68             user = request.user
69         else:
70             raise Http404
71     else:
72         user = get_object_or_404(User, username=username)
73
74     chunks_list = helpers.ChunksList(Chunk.objects.filter(
75         user=user).order_by('book__title', 'book', 'number'))
76
77     return direct_to_template(request, 'catalogue/document_list.html', extra_context={
78         'books': chunks_list,
79         'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
80                         key=lambda x: x[1]['time'], reverse=True),
81     })
82 my = login_required(active_tab('my')(user))
83
84
85 @active_tab('users')
86 def users(request):
87     return direct_to_template(request, 'catalogue/user_list.html', extra_context={
88         'users': User.objects.all().annotate(count=Count('chunk')).order_by(
89             '-count', 'last_name', 'first_name'),
90     })
91
92
93 @never_cache
94 def logout_then_redirect(request):
95     auth.logout(request)
96     return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
97
98
99 @active_tab('create')
100 def create_missing(request, slug=None):
101     if slug is None:
102         slug = ''
103     slug = slug.replace(' ', '-')
104
105     if request.method == "POST":
106         form = forms.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 = forms.DocumentCreateForm(initial={
122                 "slug": slug,
123                 "title": slug.replace('-', ' ').title(),
124         })
125
126     return direct_to_template(request, "catalogue/document_create_missing.html", extra_context={
127         "slug": slug,
128         "form": form,
129     })
130
131
132 @active_tab('upload')
133 def upload(request):
134     if request.method == "POST":
135         form = forms.DocumentsUploadForm(request.POST, request.FILES)
136         if form.is_valid():
137             import slughifi
138
139             if request.user.is_authenticated():
140                 creator = request.user
141             else:
142                 creator = None
143
144             zip = form.cleaned_data['zip']
145             skipped_list = []
146             ok_list = []
147             error_list = []
148             slugs = {}
149             existing = [book.slug for book in Book.objects.all()]
150             for filename in zip.namelist():
151                 if filename[-1] == '/':
152                     continue
153                 title = os.path.basename(filename)[:-4]
154                 slug = slughifi(title)
155                 if not (slug and filename.endswith('.xml')):
156                     skipped_list.append(filename)
157                 elif slug in slugs:
158                     error_list.append((filename, slug, _('Slug already used for %s' % slugs[slug])))
159                 elif slug in existing:
160                     error_list.append((filename, slug, _('Slug already used in repository.')))
161                 else:
162                     try:
163                         zip.read(filename).decode('utf-8') # test read
164                         ok_list.append((filename, slug, title))
165                     except UnicodeDecodeError:
166                         error_list.append((filename, title, _('File should be UTF-8 encoded.')))
167                     slugs[slug] = filename
168
169             if not error_list:
170                 for filename, slug, title in ok_list:
171                     Book.create(creator=creator,
172                         slug=slug,
173                         title=title,
174                         text=zip.read(filename).decode('utf-8'),
175                     )
176
177             return direct_to_template(request, "catalogue/document_upload.html", extra_context={
178                 "form": form,
179                 "ok_list": ok_list,
180                 "skipped_list": skipped_list,
181                 "error_list": error_list,
182             })
183     else:
184         form = forms.DocumentsUploadForm()
185
186     return direct_to_template(request, "catalogue/document_upload.html", extra_context={
187         "form": form,
188     })
189
190
191 @never_cache
192 def book_xml(request, slug):
193     xml = get_object_or_404(Book, slug=slug).materialize()
194
195     response = http.HttpResponse(xml, content_type='application/xml', mimetype='application/wl+xml')
196     response['Content-Disposition'] = 'attachment; filename=%s.xml' % slug
197     return response
198
199
200 @never_cache
201 def book_txt(request, slug):
202     xml = get_object_or_404(Book, slug=slug).materialize()
203     output = StringIO()
204     # errors?
205     librarian.text.transform(StringIO(xml), output)
206     text = output.getvalue()
207     response = http.HttpResponse(text, content_type='text/plain', mimetype='text/plain')
208     response['Content-Disposition'] = 'attachment; filename=%s.txt' % slug
209     return response
210
211
212 @never_cache
213 def book_html(request, slug):
214     xml = get_object_or_404(Book, slug=slug).materialize()
215     output = StringIO()
216     # errors?
217     librarian.html.transform(StringIO(xml), output, parse_dublincore=False,
218                              flags=['full-page'])
219     html = output.getvalue()
220     response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
221     return response
222
223
224 @never_cache
225 def revision(request, slug, chunk=None):
226     try:
227         doc = Chunk.get(slug, chunk)
228     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
229         raise Http404
230     return http.HttpResponse(str(doc.revision()))
231
232
233 def book(request, slug):
234     book = get_object_or_404(Book, slug=slug)
235
236     # TODO: most of this should go somewhere else
237
238     # do we need some automation?
239     first_master = None
240     chunks = []
241     need_fixing = False
242     choose_master = False
243
244     length = book.chunk_set.count()
245     for i, chunk in enumerate(book):
246         chunk_dict = {
247             "chunk": chunk,
248             "fix": [],
249             "grade": ""
250             }
251         graded = xml_tools.GradedText(chunk.materialize())
252         if graded.is_wl():
253             master = graded.master()
254             if first_master is None:
255                 first_master = master
256             elif master != first_master:
257                 chunk_dict['fix'].append('bad-master')
258
259             if i > 0 and not graded.has_trim_begin():
260                 chunk_dict['fix'].append('trim-begin')
261             if i < length - 1 and not graded.has_trim_end():
262                 chunk_dict['fix'].append('trim-end')
263
264             if chunk_dict['fix']:
265                 chunk_dict['grade'] = 'wl-fix'
266             else:
267                 chunk_dict['grade'] = 'wl'
268
269         elif graded.is_broken_wl():
270             chunk_dict['grade'] = 'wl-broken'
271         elif graded.is_xml():
272             chunk_dict['grade'] = 'xml'
273         else:
274             chunk_dict['grade'] = 'plain'
275             chunk_dict['fix'].append('wl')
276             choose_master = True
277
278         if chunk_dict['fix']:
279             need_fixing = True
280         chunks.append(chunk_dict)
281
282     if first_master or not need_fixing:
283         choose_master = False
284
285     if request.method == "POST":
286         form = forms.ChooseMasterForm(request.POST)
287         if not choose_master or form.is_valid():
288             if choose_master:
289                 first_master = form.cleaned_data['master']
290
291             # do the actual fixing
292             for c in chunks:
293                 if not c['fix']:
294                     continue
295
296                 text = c['chunk'].materialize()
297                 for fix in c['fix']:
298                     if fix == 'bad-master':
299                         text = xml_tools.change_master(text, first_master)
300                     elif fix == 'trim-begin':
301                         text = xml_tools.add_trim_begin(text)
302                     elif fix == 'trim-end':
303                         text = xml_tools.add_trim_end(text)
304                     elif fix == 'wl':
305                         text = xml_tools.basic_structure(text, first_master)
306                 author = request.user if request.user.is_authenticated() else None
307                 description = "auto-fix: " + ", ".join(c['fix'])
308                 c['chunk'].commit(text=text, author=author, 
309                     description=description)
310
311             return http.HttpResponseRedirect(book.get_absolute_url())
312     elif choose_master:
313         form = forms.ChooseMasterForm()
314     else:
315         form = None
316
317     return direct_to_template(request, "catalogue/book_detail.html", extra_context={
318         "book": book,
319         "chunks": chunks,
320         "need_fixing": need_fixing,
321         "choose_master": choose_master,
322         "first_master": first_master,
323         "form": form,
324     })
325
326
327 def chunk_add(request, slug, chunk):
328     try:
329         doc = Chunk.get(slug, chunk)
330     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
331         raise Http404
332
333     if request.method == "POST":
334         form = forms.ChunkAddForm(request.POST, instance=doc)
335         if form.is_valid():
336             if request.user.is_authenticated():
337                 creator = request.user
338             else:
339                 creator = None
340             doc.split(creator=creator,
341                 slug=form.cleaned_data['slug'],
342                 comment=form.cleaned_data['comment'],
343             )
344
345             return http.HttpResponseRedirect(doc.book.get_absolute_url())
346     else:
347         form = forms.ChunkAddForm(initial={
348                 "slug": str(doc.number + 1),
349                 "comment": "cz. %d" % (doc.number + 1, ),
350         })
351
352     return direct_to_template(request, "catalogue/chunk_add.html", extra_context={
353         "chunk": doc,
354         "form": form,
355     })
356
357
358 def chunk_edit(request, slug, chunk):
359     try:
360         doc = Chunk.get(slug, chunk)
361     except (Chunk.MultipleObjectsReturned, Chunk.DoesNotExist):
362         raise Http404
363     if request.method == "POST":
364         form = forms.ChunkForm(request.POST, instance=doc)
365         if form.is_valid():
366             form.save()
367             return http.HttpResponseRedirect(doc.book.get_absolute_url())
368     else:
369         form = forms.ChunkForm(instance=doc)
370     return direct_to_template(request, "catalogue/chunk_edit.html", extra_context={
371         "chunk": doc,
372         "form": form,
373     })
374
375
376 def book_append(request, slug):
377     book = get_object_or_404(Book, slug=slug)
378     if request.method == "POST":
379         form = forms.BookAppendForm(request.POST)
380         if form.is_valid():
381             append_to = form.cleaned_data['append_to']
382             append_to.append(book)
383             return http.HttpResponseRedirect(append_to.get_absolute_url())
384     else:
385         form = forms.BookAppendForm()
386     return direct_to_template(request, "catalogue/book_append_to.html", extra_context={
387         "book": book,
388         "form": form,
389     })
390
391
392 def book_edit(request, slug):
393     book = get_object_or_404(Book, slug=slug)
394     if request.method == "POST":
395         form = forms.BookForm(request.POST, instance=book)
396         if form.is_valid():
397             form.save()
398             return http.HttpResponseRedirect(book.get_absolute_url())
399     else:
400         form = forms.BookForm(instance=book)
401     return direct_to_template(request, "catalogue/book_edit.html", extra_context={
402         "book": book,
403         "form": form,
404     })
405
406
407 @require_POST
408 @login_required
409 def publish(request, slug):
410     book = get_object_or_404(Book, slug=slug)
411     try:
412         ret = api_call(request.user, "books", {"book_xml": book.materialize()})
413     except BaseException, e:
414         return http.HttpResponse(e)
415     else:
416         book.last_published = datetime.now()
417         book.save()
418         return http.HttpResponseRedirect(book.get_absolute_url())