more api changes, preparing for Android app
[wolnelektury.git] / apps / catalogue / views.py
1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 #
5 import tempfile
6 import zipfile
7 import tarfile
8 import sys
9 import pprint
10 import traceback
11 import re
12 import itertools
13 from datetime import datetime
14
15 from django.conf import settings
16 from django.template import RequestContext
17 from django.shortcuts import render_to_response, get_object_or_404
18 from django.http import HttpResponse, HttpResponseRedirect, Http404
19 from django.core.urlresolvers import reverse
20 from django.db.models import Q
21 from django.contrib.auth.decorators import login_required, user_passes_test
22 from django.utils.datastructures import SortedDict
23 from django.views.decorators.http import require_POST
24 from django.contrib import auth
25 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
26 from django.utils import simplejson
27 from django.utils.functional import Promise
28 from django.utils.encoding import force_unicode
29 from django.utils.http import urlquote_plus
30 from django.views.decorators import cache
31 from django.utils import translation
32 from django.utils.translation import ugettext as _
33 from django.views.generic.list_detail import object_list
34
35 from catalogue import models
36 from catalogue import forms
37 from catalogue.utils import split_tags
38 from newtagging import views as newtagging_views
39 from pdcounter import models as pdcounter_models
40 from pdcounter import views as pdcounter_views
41 from slughifi import slughifi
42
43
44 staff_required = user_passes_test(lambda user: user.is_staff)
45
46
47 class LazyEncoder(simplejson.JSONEncoder):
48     def default(self, obj):
49         if isinstance(obj, Promise):
50             return force_unicode(obj)
51         return obj
52
53 # shortcut for JSON reponses
54 class JSONResponse(HttpResponse):
55     def __init__(self, data={}, callback=None, **kwargs):
56         # get rid of mimetype
57         kwargs.pop('mimetype', None)
58         data = simplejson.dumps(data)
59         if callback:
60             data = callback + "(" + data + ");" 
61         super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs)
62
63
64 def main_page(request):
65     if request.user.is_authenticated():
66         shelves = models.Tag.objects.filter(category='set', user=request.user)
67         new_set_form = forms.NewSetForm()
68
69     tags = models.Tag.objects.exclude(category__in=('set', 'book'))
70     for tag in tags:
71         tag.count = tag.get_count()
72     categories = split_tags(tags)
73     fragment_tags = categories.get('theme', [])
74
75     form = forms.SearchForm()
76     return render_to_response('catalogue/main_page.html', locals(),
77         context_instance=RequestContext(request))
78
79
80 def book_list(request, filter=None, template_name='catalogue/book_list.html'):
81     """ generates a listing of all books, optionally filtered with a test function """
82
83     form = forms.SearchForm()
84
85     books_by_parent = {}
86     books = models.Book.objects.all().order_by('parent_number', 'sort_key').only('title', 'parent', 'slug')
87     if filter:
88         books = books.filter(filter).distinct()
89         book_ids = set((book.pk for book in books))
90         for book in books:
91             parent = book.parent_id
92             if parent not in book_ids:
93                 parent = None
94             books_by_parent.setdefault(parent, []).append(book)
95     else:
96         for book in books:
97             books_by_parent.setdefault(book.parent_id, []).append(book)
98
99     orphans = []
100     books_by_author = SortedDict()
101     books_nav = SortedDict()
102     for tag in models.Tag.objects.filter(category='author'):
103         books_by_author[tag] = []
104
105     for book in books_by_parent.get(None,()):
106         authors = list(book.tags.filter(category='author'))
107         if authors:
108             for author in authors:
109                 books_by_author[author].append(book)
110         else:
111             orphans.append(book)
112
113     for tag in books_by_author:
114         if books_by_author[tag]:
115             books_nav.setdefault(tag.sort_key[0], []).append(tag)
116
117     return render_to_response(template_name, locals(),
118         context_instance=RequestContext(request))
119
120
121 def audiobook_list(request):
122     return book_list(request, Q(medias__type='mp3') | Q(medias__type='ogg'),
123                      template_name='catalogue/audiobook_list.html')
124
125
126 def daisy_list(request):
127     return book_list(request, Q(medias__type='daisy'),
128                      template_name='catalogue/daisy_list.html')
129
130
131 def differentiate_tags(request, tags, ambiguous_slugs):
132     beginning = '/'.join(tag.url_chunk for tag in tags)
133     unparsed = '/'.join(ambiguous_slugs[1:])
134     options = []
135     for tag in models.Tag.objects.exclude(category='book').filter(slug=ambiguous_slugs[0]):
136         options.append({
137             'url_args': '/'.join((beginning, tag.url_chunk, unparsed)).strip('/'),
138             'tags': [tag]
139         })
140     return render_to_response('catalogue/differentiate_tags.html',
141                 {'tags': tags, 'options': options, 'unparsed': ambiguous_slugs[1:]},
142                 context_instance=RequestContext(request))
143
144
145 def tagged_object_list(request, tags=''):
146     try:
147         tags = models.Tag.get_tag_list(tags)
148     except models.Tag.DoesNotExist:
149         chunks = tags.split('/')
150         if len(chunks) == 2 and chunks[0] == 'autor':
151             return pdcounter_views.author_detail(request, chunks[1])
152         else:
153             raise Http404
154     except models.Tag.MultipleObjectsReturned, e:
155         return differentiate_tags(request, e.tags, e.ambiguous_slugs)
156
157     try:
158         if len(tags) > settings.MAX_TAG_LIST:
159             raise Http404
160     except AttributeError:
161         pass
162
163     if len([tag for tag in tags if tag.category == 'book']):
164         raise Http404
165
166     theme_is_set = [tag for tag in tags if tag.category == 'theme']
167     shelf_is_set = [tag for tag in tags if tag.category == 'set']
168     only_shelf = shelf_is_set and len(tags) == 1
169     only_my_shelf = only_shelf and request.user.is_authenticated() and request.user == tags[0].user
170
171     objects = only_author = None
172     categories = {}
173
174     if theme_is_set:
175         shelf_tags = [tag for tag in tags if tag.category == 'set']
176         fragment_tags = [tag for tag in tags if tag.category != 'set']
177         fragments = models.Fragment.tagged.with_all(fragment_tags)
178
179         if shelf_tags:
180             books = models.Book.tagged.with_all(shelf_tags).order_by()
181             l_tags = models.Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
182             fragments = models.Fragment.tagged.with_any(l_tags, fragments)
183
184         # newtagging goes crazy if we just try:
185         #related_tags = models.Tag.objects.usage_for_queryset(fragments, counts=True,
186         #                    extra={'where': ["catalogue_tag.category != 'book'"]})
187         fragment_keys = [fragment.pk for fragment in fragments]
188         if fragment_keys:
189             related_tags = models.Fragment.tags.usage(counts=True,
190                                 filters={'pk__in': fragment_keys},
191                                 extra={'where': ["catalogue_tag.category != 'book'"]})
192             related_tags = (tag for tag in related_tags if tag not in fragment_tags)
193             categories = split_tags(related_tags)
194
195             objects = fragments
196     else:
197         if shelf_is_set:
198             objects = models.Book.tagged.with_all(tags)
199         else:
200             objects = models.Book.tagged_top_level(tags)
201
202         # get related tags from `tag_counter` and `theme_counter`
203         related_counts = {}
204         tags_pks = [tag.pk for tag in tags]
205         for book in objects:
206             for tag_pk, value in itertools.chain(book.tag_counter.iteritems(), book.theme_counter.iteritems()):
207                 if tag_pk in tags_pks:
208                     continue
209                 related_counts[tag_pk] = related_counts.get(tag_pk, 0) + value
210         related_tags = models.Tag.objects.filter(pk__in=related_counts.keys())
211         related_tags = [tag for tag in related_tags if tag not in tags]
212         for tag in related_tags:
213             tag.count = related_counts[tag.pk]
214
215         categories = split_tags(related_tags)
216         del related_tags
217
218     if not objects:
219         only_author = len(tags) == 1 and tags[0].category == 'author'
220         objects = models.Book.objects.none()
221
222     return object_list(
223         request,
224         objects,
225         template_name='catalogue/tagged_object_list.html',
226         extra_context={
227             'categories': categories,
228             'only_shelf': only_shelf,
229             'only_author': only_author,
230             'only_my_shelf': only_my_shelf,
231             'formats_form': forms.DownloadFormatsForm(),
232             'tags': tags,
233         }
234     )
235
236
237 def book_fragments(request, book_slug, theme_slug):
238     book = get_object_or_404(models.Book, slug=book_slug)
239     book_tag = get_object_or_404(models.Tag, slug='l-' + book_slug, category='book')
240     theme = get_object_or_404(models.Tag, slug=theme_slug, category='theme')
241     fragments = models.Fragment.tagged.with_all([book_tag, theme])
242
243     form = forms.SearchForm()
244     return render_to_response('catalogue/book_fragments.html', locals(),
245         context_instance=RequestContext(request))
246
247
248 def book_detail(request, slug):
249     try:
250         book = models.Book.objects.get(slug=slug)
251     except models.Book.DoesNotExist:
252         return pdcounter_views.book_stub_detail(request, slug)
253
254     book_tag = book.book_tag()
255     tags = list(book.tags.filter(~Q(category='set')))
256     categories = split_tags(tags)
257     book_children = book.children.all().order_by('parent_number', 'sort_key')
258     
259     _book = book
260     parents = []
261     while _book.parent:
262         parents.append(_book.parent)
263         _book = _book.parent
264     parents = reversed(parents)
265
266     theme_counter = book.theme_counter
267     book_themes = models.Tag.objects.filter(pk__in=theme_counter.keys())
268     for tag in book_themes:
269         tag.count = theme_counter[tag.pk]
270
271     extra_info = book.get_extra_info_value()
272
273     form = forms.SearchForm()
274     return render_to_response('catalogue/book_detail.html', locals(),
275         context_instance=RequestContext(request))
276
277
278 def book_text(request, slug):
279     book = get_object_or_404(models.Book, slug=slug)
280     if not book.has_html_file():
281         raise Http404
282     book_themes = {}
283     for fragment in book.fragments.all():
284         for theme in fragment.tags.filter(category='theme'):
285             book_themes.setdefault(theme, []).append(fragment)
286
287     book_themes = book_themes.items()
288     book_themes.sort(key=lambda s: s[0].sort_key)
289     return render_to_response('catalogue/book_text.html', locals(),
290         context_instance=RequestContext(request))
291
292
293 # ==========
294 # = Search =
295 # ==========
296
297 def _no_diacritics_regexp(query):
298     """ returns a regexp for searching for a query without diacritics
299
300     should be locale-aware """
301     names = {
302         u'a':u'aąĄ', u'c':u'cćĆ', u'e':u'eęĘ', u'l': u'lłŁ', u'n':u'nńŃ', u'o':u'oóÓ', u's':u'sśŚ', u'z':u'zźżŹŻ',
303         u'ą':u'ąĄ', u'ć':u'ćĆ', u'ę':u'ęĘ', u'ł': u'łŁ', u'ń':u'ńŃ', u'ó':u'óÓ', u'ś':u'śŚ', u'ź':u'źŹ', u'ż':u'żŻ'
304         }
305     def repl(m):
306         l = m.group()
307         return u"(%s)" % '|'.join(names[l])
308     return re.sub(u'[%s]' % (u''.join(names.keys())), repl, query)
309
310 def unicode_re_escape(query):
311     """ Unicode-friendly version of re.escape """
312     return re.sub('(?u)(\W)', r'\\\1', query)
313
314 def _word_starts_with(name, prefix):
315     """returns a Q object getting models having `name` contain a word
316     starting with `prefix`
317
318     We define word characters as alphanumeric and underscore, like in JS.
319
320     Works for MySQL, PostgreSQL, Oracle.
321     For SQLite, _sqlite* version is substituted for this.
322     """
323     kwargs = {}
324
325     prefix = _no_diacritics_regexp(unicode_re_escape(prefix))
326     # can't use [[:<:]] (word start),
327     # but we want both `xy` and `(xy` to catch `(xyz)`
328     kwargs['%s__iregex' % name] = u"(^|[^[:alnum:]_])%s" % prefix
329
330     return Q(**kwargs)
331
332
333 def _word_starts_with_regexp(prefix):
334     prefix = _no_diacritics_regexp(unicode_re_escape(prefix))
335     return ur"(^|(?<=[^\wąćęłńóśźżĄĆĘŁŃÓŚŹŻ]))%s" % prefix
336
337
338 def _sqlite_word_starts_with(name, prefix):
339     """ version of _word_starts_with for SQLite
340
341     SQLite in Django uses Python re module
342     """
343     kwargs = {}
344     kwargs['%s__iregex' % name] = _word_starts_with_regexp(prefix)
345     return Q(**kwargs)
346
347
348 if hasattr(settings, 'DATABASES'):
349     if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
350         _word_starts_with = _sqlite_word_starts_with
351 elif settings.DATABASE_ENGINE == 'sqlite3':
352     _word_starts_with = _sqlite_word_starts_with
353
354
355 class App():
356     def __init__(self, name, view):
357         self.name = name
358         self._view = view
359         self.lower = name.lower()
360         self.category = 'application'
361     def view(self):
362         return reverse(*self._view)
363
364 _apps = (
365     App(u'Leśmianator', (u'lesmianator', )),
366     )
367
368
369 def _tags_starting_with(prefix, user=None):
370     prefix = prefix.lower()
371     # PD counter
372     book_stubs = pdcounter_models.BookStub.objects.filter(_word_starts_with('title', prefix))
373     authors = pdcounter_models.Author.objects.filter(_word_starts_with('name', prefix))
374
375     books = models.Book.objects.filter(_word_starts_with('title', prefix))
376     tags = models.Tag.objects.filter(_word_starts_with('name', prefix))
377     if user and user.is_authenticated():
378         tags = tags.filter(~Q(category='book') & (~Q(category='set') | Q(user=user)))
379     else:
380         tags = tags.filter(~Q(category='book') & ~Q(category='set'))
381
382     prefix_regexp = re.compile(_word_starts_with_regexp(prefix))
383     return list(books) + list(tags) + [app for app in _apps if prefix_regexp.search(app.lower)] + list(book_stubs) + list(authors)
384
385
386 def _get_result_link(match, tag_list):
387     if isinstance(match, models.Tag):
388         return reverse('catalogue.views.tagged_object_list',
389             kwargs={'tags': '/'.join(tag.url_chunk for tag in tag_list + [match])}
390         )
391     elif isinstance(match, App):
392         return match.view()
393     else:
394         return match.get_absolute_url()
395
396
397 def _get_result_type(match):
398     if isinstance(match, models.Book) or isinstance(match, pdcounter_models.BookStub):
399         type = 'book'
400     else:
401         type = match.category
402     return type
403
404
405 def books_starting_with(prefix):
406     prefix = prefix.lower()
407     return models.Book.objects.filter(_word_starts_with('title', prefix))
408
409
410 def find_best_matches(query, user=None):
411     """ Finds a Book, Tag, BookStub or Author best matching a query.
412
413     Returns a with:
414       - zero elements when nothing is found,
415       - one element when a best result is found,
416       - more then one element on multiple exact matches
417
418     Raises a ValueError on too short a query.
419     """
420
421     query = query.lower()
422     if len(query) < 2:
423         raise ValueError("query must have at least two characters")
424
425     result = tuple(_tags_starting_with(query, user))
426     # remove pdcounter stuff
427     book_titles = set(match.pretty_title().lower() for match in result
428                       if isinstance(match, models.Book))
429     authors = set(match.name.lower() for match in result
430                   if isinstance(match, models.Tag) and match.category=='author')
431     result = tuple(res for res in result if not (
432                  (isinstance(res, pdcounter_models.BookStub) and res.pretty_title().lower() in book_titles)
433                  or (isinstance(res, pdcounter_models.Author) and res.name.lower() in authors)
434              ))
435
436     exact_matches = tuple(res for res in result if res.name.lower() == query)
437     if exact_matches:
438         return exact_matches
439     else:
440         return tuple(result)[:1]
441
442
443 def search(request):
444     tags = request.GET.get('tags', '')
445     prefix = request.GET.get('q', '')
446
447     try:
448         tag_list = models.Tag.get_tag_list(tags)
449     except:
450         tag_list = []
451
452     try:
453         result = find_best_matches(prefix, request.user)
454     except ValueError:
455         return render_to_response('catalogue/search_too_short.html', {'tags':tag_list, 'prefix':prefix},
456             context_instance=RequestContext(request))
457
458     if len(result) == 1:
459         return HttpResponseRedirect(_get_result_link(result[0], tag_list))
460     elif len(result) > 1:
461         return render_to_response('catalogue/search_multiple_hits.html',
462             {'tags':tag_list, 'prefix':prefix, 'results':((x, _get_result_link(x, tag_list), _get_result_type(x)) for x in result)},
463             context_instance=RequestContext(request))
464     else:
465         return render_to_response('catalogue/search_no_hits.html', {'tags':tag_list, 'prefix':prefix},
466             context_instance=RequestContext(request))
467
468
469 def tags_starting_with(request):
470     prefix = request.GET.get('q', '')
471     # Prefix must have at least 2 characters
472     if len(prefix) < 2:
473         return HttpResponse('')
474     tags_list = []
475     result = ""   
476     for tag in _tags_starting_with(prefix, request.user):
477         if not tag.name in tags_list:
478             result += "\n" + tag.name
479             tags_list.append(tag.name)
480     return HttpResponse(result)
481
482 def json_tags_starting_with(request, callback=None):
483     # Callback for JSONP
484     prefix = request.GET.get('q', '')
485     callback = request.GET.get('callback', '')
486     # Prefix must have at least 2 characters
487     if len(prefix) < 2:
488         return HttpResponse('')
489     tags_list = []
490     for tag in _tags_starting_with(prefix, request.user):
491         if not tag.name in tags_list:
492             tags_list.append(tag.name)
493     if request.GET.get('mozhint', ''):
494         result = [prefix, tags_list]
495     else:
496         result = {"matches": tags_list}
497     return JSONResponse(result, callback)
498
499 # ====================
500 # = Shelf management =
501 # ====================
502 @login_required
503 @cache.never_cache
504 def user_shelves(request):
505     shelves = models.Tag.objects.filter(category='set', user=request.user)
506     new_set_form = forms.NewSetForm()
507     return render_to_response('catalogue/user_shelves.html', locals(),
508             context_instance=RequestContext(request))
509
510 @cache.never_cache
511 def book_sets(request, slug):
512     if not request.user.is_authenticated():
513         return HttpResponse(_('<p>To maintain your shelves you need to be logged in.</p>'))
514
515     book = get_object_or_404(models.Book, slug=slug)
516     user_sets = models.Tag.objects.filter(category='set', user=request.user)
517     book_sets = book.tags.filter(category='set', user=request.user)
518
519     if request.method == 'POST':
520         form = forms.ObjectSetsForm(book, request.user, request.POST)
521         if form.is_valid():
522             old_shelves = list(book.tags.filter(category='set'))
523             new_shelves = [models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']]
524
525             for shelf in [shelf for shelf in old_shelves if shelf not in new_shelves]:
526                 shelf.book_count = None
527                 shelf.save()
528
529             for shelf in [shelf for shelf in new_shelves if shelf not in old_shelves]:
530                 shelf.book_count = None
531                 shelf.save()
532
533             book.tags = new_shelves + list(book.tags.filter(~Q(category='set') | ~Q(user=request.user)))
534             if request.is_ajax():
535                 return JSONResponse('{"msg":"'+_("<p>Shelves were sucessfully saved.</p>")+'", "after":"close"}')
536             else:
537                 return HttpResponseRedirect('/')
538     else:
539         form = forms.ObjectSetsForm(book, request.user)
540         new_set_form = forms.NewSetForm()
541
542     return render_to_response('catalogue/book_sets.html', locals(),
543         context_instance=RequestContext(request))
544
545
546 @login_required
547 @require_POST
548 @cache.never_cache
549 def remove_from_shelf(request, shelf, book):
550     book = get_object_or_404(models.Book, slug=book)
551     shelf = get_object_or_404(models.Tag, slug=shelf, category='set', user=request.user)
552
553     if shelf in book.tags:
554         models.Tag.objects.remove_tag(book, shelf)
555
556         shelf.book_count = None
557         shelf.save()
558
559         return HttpResponse(_('Book was successfully removed from the shelf'))
560     else:
561         return HttpResponse(_('This book is not on the shelf'))
562
563
564 def collect_books(books):
565     """
566     Returns all real books in collection.
567     """
568     result = []
569     for book in books:
570         if len(book.children.all()) == 0:
571             result.append(book)
572         else:
573             result += collect_books(book.children.all())
574     return result
575
576
577 @cache.never_cache
578 def download_shelf(request, slug):
579     """"
580     Create a ZIP archive on disk and transmit it in chunks of 8KB,
581     without loading the whole file into memory. A similar approach can
582     be used for large dynamic PDF files.
583     """
584     shelf = get_object_or_404(models.Tag, slug=slug, category='set')
585
586     formats = []
587     form = forms.DownloadFormatsForm(request.GET)
588     if form.is_valid():
589         formats = form.cleaned_data['formats']
590     if len(formats) == 0:
591         formats = ['pdf', 'epub', 'odt', 'txt']
592
593     # Create a ZIP archive
594     temp = tempfile.TemporaryFile()
595     archive = zipfile.ZipFile(temp, 'w')
596
597     already = set()
598     for book in collect_books(models.Book.tagged.with_all(shelf)):
599         if 'pdf' in formats and book.pdf_file:
600             filename = book.pdf_file.path
601             archive.write(filename, str('%s.pdf' % book.slug))
602         if book.root_ancestor not in already and 'epub' in formats and book.root_ancestor.epub_file:
603             filename = book.root_ancestor.epub_file.path
604             archive.write(filename, str('%s.epub' % book.root_ancestor.slug))
605             already.add(book.root_ancestor)
606         if 'odt' in formats and book.has_media("odt"):
607             for file in book.get_media("odt"):
608                 filename = file.file.path
609                 archive.write(filename, str('%s.odt' % slughifi(file.name)))
610         if 'txt' in formats and book.txt_file:
611             filename = book.txt_file.path
612             archive.write(filename, str('%s.txt' % book.slug))
613     archive.close()
614
615     response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed')
616     response['Content-Disposition'] = 'attachment; filename=%s.zip' % slughifi(shelf.name)
617     response['Content-Length'] = temp.tell()
618
619     temp.seek(0)
620     response.write(temp.read())
621     return response
622
623
624 @cache.never_cache
625 def shelf_book_formats(request, shelf):
626     """"
627     Returns a list of formats of books in shelf.
628     """
629     shelf = get_object_or_404(models.Tag, slug=shelf, category='set')
630
631     formats = {'pdf': False, 'epub': False, 'odt': False, 'txt': False}
632
633     for book in collect_books(models.Book.tagged.with_all(shelf)):
634         if book.pdf_file:
635             formats['pdf'] = True
636         if book.root_ancestor.epub_file:
637             formats['epub'] = True
638         if book.txt_file:
639             formats['txt'] = True
640         for format in ('odt',):
641             if book.has_media(format):
642                 formats[format] = True
643
644     return HttpResponse(LazyEncoder().encode(formats))
645
646
647 @login_required
648 @require_POST
649 @cache.never_cache
650 def new_set(request):
651     new_set_form = forms.NewSetForm(request.POST)
652     if new_set_form.is_valid():
653         new_set = new_set_form.save(request.user)
654
655         if request.is_ajax():
656             return JSONResponse('{"id":"%d", "name":"%s", "msg":"<p>Shelf <strong>%s</strong> was successfully created</p>"}' % (new_set.id, new_set.name, new_set))
657         else:
658             return HttpResponseRedirect('/')
659
660     return HttpResponseRedirect('/')
661
662
663 @login_required
664 @require_POST
665 @cache.never_cache
666 def delete_shelf(request, slug):
667     user_set = get_object_or_404(models.Tag, slug=slug, category='set', user=request.user)
668     user_set.delete()
669
670     if request.is_ajax():
671         return HttpResponse(_('<p>Shelf <strong>%s</strong> was successfully removed</p>') % user_set.name)
672     else:
673         return HttpResponseRedirect('/')
674
675
676 # ==================
677 # = Authentication =
678 # ==================
679
680 @cache.never_cache
681 def simple_login(request):
682     if request.method == "GET":
683         #next = request.REQUEST.get('next', '')
684         #if next == '':
685         form = AuthenticationForm(prefix='login')
686         return render_to_response('auth/login.html', locals(),
687                 context_instance=RequestContext(request))
688         #else:
689         #    return HttpResponseRedirect("/"+next)
690             
691     elif request.method == "POST":
692         form = AuthenticationForm(data=request.POST, prefix='login')
693         if form.is_valid():
694             auth.login(request, form.get_user())   
695         url = request.META['HTTP_REFERER'].split("next=")[1]
696         url = url.replace("%3F","?").replace("%3D","=")
697         return HttpResponseRedirect(url)    
698
699
700 @require_POST
701 @cache.never_cache
702 def login(request):
703     form = AuthenticationForm(data=request.POST, prefix='login')
704     if form.is_valid():
705         auth.login(request, form.get_user())
706         response_data = {'success': True, 'errors': {}}
707     else:
708         response_data = {'success': False, 'errors': form.errors}
709     return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
710
711
712 @require_POST
713 @cache.never_cache
714 def register(request):
715     registration_form = UserCreationForm(request.POST, prefix='registration')
716     if registration_form.is_valid():
717         user = registration_form.save()
718         user = auth.authenticate(
719             username=registration_form.cleaned_data['username'],
720             password=registration_form.cleaned_data['password1']
721         )
722         auth.login(request, user)
723         response_data = {'success': True, 'errors': {}}
724     else:
725         response_data = {'success': False, 'errors': registration_form.errors}
726     return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
727
728
729 @cache.never_cache
730 def logout_then_redirect(request):
731     auth.logout(request)
732     return HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
733
734
735
736 # =========
737 # = Admin =
738 # =========
739 @login_required
740 @staff_required
741 def import_book(request):
742     """docstring for import_book"""
743     book_import_form = forms.BookImportForm(request.POST, request.FILES)
744     if book_import_form.is_valid():
745         try:
746             book_import_form.save()
747         except:
748             info = sys.exc_info()
749             exception = pprint.pformat(info[1])
750             tb = '\n'.join(traceback.format_tb(info[2]))
751             return HttpResponse(_("An error occurred: %(exception)s\n\n%(tb)s") % {'exception':exception, 'tb':tb}, mimetype='text/plain')
752         return HttpResponse(_("Book imported successfully"))
753     else:
754         return HttpResponse(_("Error importing file: %r") % book_import_form.errors)
755
756
757
758 def clock(request):
759     """ Provides server time for jquery.countdown,
760     in a format suitable for Date.parse()
761     """
762     return HttpResponse(datetime.now().strftime('%Y/%m/%d %H:%M:%S'))
763
764
765 @cache.never_cache
766 def xmls(request):
767     """"
768     Create a zip archive with all XML files.
769     This should be removed when we have real API.
770     """
771     temp = tempfile.TemporaryFile()
772     archive = zipfile.ZipFile(temp, 'w')
773
774     for book in models.Book.objects.all():
775         archive.write(book.xml_file.path, str('%s.xml' % book.slug))
776     archive.close()
777
778     response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed')
779     response['Content-Disposition'] = 'attachment; filename=xmls.zip'
780     response['Content-Length'] = temp.tell()
781
782     temp.seek(0)
783     response.write(temp.read())
784     return response
785
786
787
788 # info views for API
789
790 def book_info(request, id, lang='pl'):
791     book = get_object_or_404(models.Book, id=id)
792     # set language by hand
793     translation.activate(lang)
794     return render_to_response('catalogue/book_info.html', locals(),
795         context_instance=RequestContext(request))
796
797 def tag_info(request, id):
798     tag = get_object_or_404(models.Tag, id=id)
799     return HttpResponse(tag.description)