Added migration to add extra info to book.
[wolnelektury.git] / apps / catalogue / views.py
1 # -*- coding: utf-8 -*-
2 import tempfile
3 import zipfile
4
5 from django.template import RequestContext
6 from django.shortcuts import render_to_response, get_object_or_404
7 from django.http import HttpResponse, HttpResponseRedirect, Http404
8 from django.core.urlresolvers import reverse
9 from django.db.models import Q
10 from django.contrib.auth.decorators import login_required
11 from django.utils.datastructures import SortedDict
12 from django.views.decorators.http import require_POST
13 from django.contrib import auth
14 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
15 from django.utils import simplejson
16 from django.utils.functional import Promise
17 from django.utils.encoding import force_unicode
18 from django.views.decorators import cache
19
20 from catalogue import models
21 from catalogue import forms
22 from catalogue.utils import split_tags
23 from newtagging import views as newtagging_views
24
25
26 class LazyEncoder(simplejson.JSONEncoder):
27     def default(self, obj):
28         if isinstance(obj, Promise):
29             return force_unicode(obj)
30         return obj
31
32
33 def main_page(request):    
34     if request.user.is_authenticated():
35         shelves = models.Tag.objects.filter(category='set', user=request.user)
36         new_set_form = forms.NewSetForm()
37     extra_where = 'NOT catalogue_tag.category = "set"'
38     tags = models.Tag.objects.usage_for_model(models.Book, counts=True, extra={'where': [extra_where]})
39     fragment_tags = models.Tag.objects.usage_for_model(models.Fragment, counts=True,
40         extra={'where': ['catalogue_tag.category = "theme"'] + [extra_where]})
41     categories = split_tags(tags)
42     
43     form = forms.SearchForm()
44     return render_to_response('catalogue/main_page.html', locals(),
45         context_instance=RequestContext(request))
46
47
48 def book_list(request):
49     books = models.Book.objects.all()
50     form = forms.SearchForm()
51     
52     books_by_first_letter = SortedDict()
53     for book in books:
54         books_by_first_letter.setdefault(book.title[0], []).append(book)
55     
56     return render_to_response('catalogue/book_list.html', locals(),
57         context_instance=RequestContext(request))
58
59
60 def tagged_object_list(request, tags=''):
61     # Prevent DoS attacks on our database
62     if len(tags.split('/')) > 6:
63         raise Http404
64         
65     try:
66         tags = models.Tag.get_tag_list(tags)
67     except models.Tag.DoesNotExist:
68         raise Http404
69     
70     if len([tag for tag in tags if tag.category == 'book']):
71         raise Http404
72     
73     model = models.Book
74     shelf = [tag for tag in tags if tag.category == 'set']
75     shelf_is_set = (len(tags) == 1 and tags[0].category == 'set')
76     theme_is_set = len([tag for tag in tags if tag.category == 'theme']) > 0
77     if theme_is_set:
78         model = models.Fragment
79
80     user_is_owner = (len(shelf) and request.user.is_authenticated() and request.user == shelf[0].user)
81     
82     extra_where = 'catalogue_tag.category NOT IN ("set", "book")'
83     related_tags = models.Tag.objects.related_for_model(tags, model, counts=True, extra={'where': [extra_where]})
84     categories = split_tags(related_tags)
85
86     if not (theme_is_set or shelf_is_set):
87         model=models.Book.objects.filter(parent=None)
88     
89     return newtagging_views.tagged_object_list(
90         request,
91         tag_model=models.Tag,
92         queryset_or_model=model,
93         tags=tags,
94         template_name='catalogue/tagged_object_list.html',
95         extra_context = {'categories': categories, 'shelf_is_set': shelf_is_set, 'user_is_owner': user_is_owner },
96     )
97
98
99 def book_fragments(request, book_slug, theme_slug):
100     book = get_object_or_404(models.Book, slug=book_slug)
101     book_tag = get_object_or_404(models.Tag, slug='l-' + book_slug)
102     theme = get_object_or_404(models.Tag, slug=theme_slug)
103     fragments = models.Fragment.tagged.with_all([book_tag, theme])
104     
105     form = forms.SearchForm()
106     return render_to_response('catalogue/book_fragments.html', locals(),
107         context_instance=RequestContext(request))
108
109
110 def book_detail(request, slug):
111     book = get_object_or_404(models.Book, slug=slug)
112     book_tag = get_object_or_404(models.Tag, slug = 'l-' + slug)
113     tags = list(book.tags.filter(~Q(category='set')))
114     categories = split_tags(tags)
115     book_children = book.children.all().order_by('parent_number')
116     extra_where = 'catalogue_tag.category = "theme"'
117     book_themes = models.Tag.objects.related_for_model(book_tag, models.Fragment, counts=True, extra={'where': [extra_where]})
118     
119     form = forms.SearchForm()
120     return render_to_response('catalogue/book_detail.html', locals(),
121         context_instance=RequestContext(request))
122
123
124 def book_text(request, slug):
125     book = get_object_or_404(models.Book, slug=slug)
126     book_themes = {}
127     for fragment in book.fragments.all():
128         for theme in fragment.tags.filter(category='theme'):
129             book_themes.setdefault(theme, []).append(fragment)
130     
131     book_themes = book_themes.items()
132     book_themes.sort(key=lambda s: s[0].sort_key)
133     return render_to_response('catalogue/book_text.html', locals(),
134         context_instance=RequestContext(request))
135
136
137 # ==========
138 # = Search =
139 # ==========
140 def _tags_starting_with(prefix, user):
141     books = models.Book.objects.filter(title__icontains=prefix)
142     tags = models.Tag.objects.filter(name__icontains=prefix)
143     if user.is_authenticated():
144         tags = tags.filter(~Q(category='book') & (~Q(category='set') | Q(user=user)))
145     else:
146         tags = tags.filter(~Q(category='book') & ~Q(category='set'))
147
148     return list(books) + list(tags)
149         
150
151 def search(request):
152     tags = request.GET.get('tags', '')
153     prefix = request.GET.get('q', '')
154     # Prefix must have at least 2 characters
155     if len(prefix) < 2:
156         return HttpResponse('')
157     
158     try:
159         tag_list = models.Tag.get_tag_list(tags)
160     except:
161         tag_list = []
162     
163     result = _tags_starting_with(prefix, request.user)
164     if len(result) > 0:
165         tag = result[0]
166         if isinstance(tag, models.Book):
167             return HttpResponseRedirect(tag.get_absolute_url())
168         else:
169             tag_list.append(tag)
170         
171     return HttpResponseRedirect(reverse('catalogue.views.tagged_object_list', 
172         kwargs={'tags': '/'.join(tag.slug for tag in tag_list)}
173     ))
174
175
176 def tags_starting_with(request):
177     prefix = request.GET.get('q', '')
178     # Prefix must have at least 2 characters
179     if len(prefix) < 2:
180         return HttpResponse('')
181     
182     return HttpResponse('\n'.join(tag.name for tag in _tags_starting_with(prefix, request.user)))
183
184
185 # ====================
186 # = Shelf management =
187 # ====================
188 @login_required
189 @cache.never_cache
190 def user_shelves(request):
191     shelves = models.Tag.objects.filter(category='set', user=request.user)
192     new_set_form = forms.NewSetForm()
193     return render_to_response('catalogue/user_shelves.html', locals(),
194             context_instance=RequestContext(request))
195
196 @cache.never_cache
197 def book_sets(request, slug):
198     book = get_object_or_404(models.Book, slug=slug)
199     user_sets = models.Tag.objects.filter(category='set', user=request.user)
200     book_sets = book.tags.filter(category='set', user=request.user)
201     
202     if not request.user.is_authenticated():
203         return HttpResponse('<p>Aby zarządzać swoimi półkami, musisz się zalogować.</p>')
204     
205     if request.method == 'POST':
206         form = forms.ObjectSetsForm(book, request.user, request.POST)
207         if form.is_valid():
208             old_shelves = list(book.tags.filter(category='set'))
209             new_shelves = [models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']]
210             
211             for shelf in [shelf for shelf in old_shelves if shelf not in new_shelves]:
212                 shelf.book_count -= 1
213                 shelf.save()
214                 
215             for shelf in [shelf for shelf in new_shelves if shelf not in old_shelves]:
216                 shelf.book_count += 1
217                 shelf.save()
218             
219             book.tags = new_shelves + list(book.tags.filter(~Q(category='set') | ~Q(user=request.user)))
220             if request.is_ajax():
221                 return HttpResponse('<p>Półki zostały zapisane.</p>')
222             else:
223                 return HttpResponseRedirect('/')
224     else:
225         form = forms.ObjectSetsForm(book, request.user)
226         new_set_form = forms.NewSetForm()
227     
228     return render_to_response('catalogue/book_sets.html', locals(),
229         context_instance=RequestContext(request))
230
231
232 @login_required
233 @require_POST
234 @cache.never_cache
235 def remove_from_shelf(request, shelf, book):
236     book = get_object_or_404(models.Book, slug=book)
237     shelf = get_object_or_404(models.Tag, slug=shelf, category='set', user=request.user)
238     
239     models.Tag.objects.remove_tag(book, shelf)
240     
241     shelf.book_count -= 1
242     shelf.save()
243     
244     return HttpResponse('Usunieto')
245
246
247 @cache.never_cache
248 def download_shelf(request, slug):
249     """"
250     Create a ZIP archive on disk and transmit it in chunks of 8KB,
251     without loading the whole file into memory. A similar approach can
252     be used for large dynamic PDF files.                                        
253     """
254     shelf = get_object_or_404(models.Tag, slug=slug, category='set')
255             
256     # Create a ZIP archive
257     temp = temp = tempfile.TemporaryFile()
258     archive = zipfile.ZipFile(temp, 'w')
259     
260     # Collect all books to include in ZIP archive
261     def collect_books(books):
262         result = []
263         for book in books:
264             if len(book.children.all()) == 0:
265                 result.append(book)
266             else:
267                 result += collect_books(book.children.all())
268         return result
269     
270     for book in collect_books(models.Book.tagged.with_all(shelf)):
271         if book.pdf_file:
272             filename = book.pdf_file.path
273             archive.write(filename, str('%s.pdf' % book.slug))
274         if book.odt_file:
275             filename = book.odt_file.path
276             archive.write(filename, str('%s.odt' % book.slug))
277         if book.txt_file:
278             filename = book.txt_file.path
279             archive.write(filename, str('%s.txt' % book.slug))
280     archive.close()
281     
282     response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed')
283     response['Content-Disposition'] = 'attachment; filename=%s.zip' % shelf.sort_key
284     response['Content-Length'] = temp.tell()
285     
286     temp.seek(0)
287     response.write(temp.read())
288     return response
289
290
291 @login_required
292 @require_POST
293 @cache.never_cache
294 def new_set(request):
295     new_set_form = forms.NewSetForm(request.POST)
296     if new_set_form.is_valid():
297         new_set = new_set_form.save(request.user)
298
299         if request.is_ajax():
300             return HttpResponse(u'<p>Półka <strong>%s</strong> została utworzona</p>' % new_set)
301         else:
302             return HttpResponseRedirect('/')
303
304     return HttpResponseRedirect('/')
305
306
307 @login_required
308 @require_POST
309 @cache.never_cache
310 def delete_shelf(request, slug):
311     user_set = get_object_or_404(models.Tag, slug=slug, category='set', user=request.user)
312     user_set.delete()
313
314     if request.is_ajax():
315         return HttpResponse(u'<p>Półka <strong>%s</strong> została usunięta</p>' % user_set.name)
316     else:
317         return HttpResponseRedirect('/')
318
319
320 # ==================
321 # = Authentication =
322 # ==================
323 @require_POST
324 @cache.never_cache
325 def login(request):
326     form = AuthenticationForm(data=request.POST, prefix='login')
327     if form.is_valid():
328         auth.login(request, form.get_user())
329         response_data = {'success': True, 'errors': {}}
330     else:
331         response_data = {'success': False, 'errors': form.errors}
332     return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
333
334
335 @require_POST
336 @cache.never_cache
337 def register(request):
338     registration_form = UserCreationForm(request.POST, prefix='registration')
339     if registration_form.is_valid():
340         user = registration_form.save()
341         user = auth.authenticate(
342             username=registration_form.cleaned_data['username'], 
343             password=registration_form.cleaned_data['password1']
344         )
345         auth.login(request, user)
346         response_data = {'success': True, 'errors': {}}
347     else:
348         response_data = {'success': False, 'errors': registration_form.errors}
349     return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data))
350
351
352 @cache.never_cache
353 def logout_then_redirect(request):
354     auth.logout(request)
355     return HttpResponseRedirect(request.GET.get('next', '/'))
356