make custom pdf optional
[wolnelektury.git] / apps / catalogue / templatetags / catalogue_tags.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 datetime
6 import feedparser
7
8 from django.conf import settings
9 from django import template
10 from django.template import Node, Variable, Template, Context
11 from django.core.cache import cache
12 from django.core.urlresolvers import reverse
13 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
14 from django.utils.translation import ugettext as _
15
16 from catalogue.utils import split_tags
17 from catalogue.models import Book, BookMedia, Fragment, Tag
18
19 register = template.Library()
20
21
22 class RegistrationForm(UserCreationForm):
23     def as_ul(self):
24         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
25         return self._html_output(u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>', '</li>', u' %s', False)
26
27
28 class LoginForm(AuthenticationForm):
29     def as_ul(self):
30         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
31         return self._html_output(u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>', '</li>', u' %s', False)
32
33
34 def iterable(obj):
35     try:
36         iter(obj)
37         return True
38     except TypeError:
39         return False
40
41
42 def capfirst(text):
43     try:
44         return '%s%s' % (text[0].upper(), text[1:])
45     except IndexError:
46         return ''
47
48
49 @register.simple_tag
50 def html_title_from_tags(tags):
51     if len(tags) < 2:
52         return title_from_tags(tags)
53     template = Template("{{ category }}: <a href='{{ tag.get_absolute_url }}'>{{ tag.name }}</a>")
54     return capfirst(",<br/>".join(
55         template.render(Context({'tag': tag, 'category': _(tag.category)})) for tag in tags))
56     
57
58
59 def simple_title(tags):
60     title = []
61     for tag in tags:
62         title.append("%s: %s" % (_(tag.category), tag.name))
63     return capfirst(', '.join(title))
64
65
66 @register.simple_tag
67 def book_title(book, html_links=False):
68     return book.pretty_title(html_links)
69
70
71 @register.simple_tag
72 def book_title_html(book):
73     return book_title(book, html_links=True)
74
75
76 @register.simple_tag
77 def title_from_tags(tags):
78     def split_tags(tags):
79         result = {}
80         for tag in tags:
81             result[tag.category] = tag
82         return result
83
84     # TODO: Remove this after adding flection mechanism
85     return simple_title(tags)
86
87     class Flection(object):
88         def get_case(self, name, flection):
89             return name
90     flection = Flection()
91
92     self = split_tags(tags)
93
94     title = u''
95
96     # Specjalny przypadek oglądania wszystkich lektur na danej półce
97     if len(self) == 1 and 'set' in self:
98         return u'Półka %s' % self['set']
99
100     # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
101     # jest wybrana przez użytkownika
102     if 'epoch' in self and len(self) == 1:
103         text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik')
104         return capfirst(text)
105
106     # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
107     # są tylko rodzaj literacki i autor
108     if 'kind' in self and 'author' in self and len(self) == 2:
109         text = u'%s w twórczości %s' % (unicode(self['kind']),
110             flection.get_case(unicode(self['author']), u'dopełniacz'))
111         return capfirst(text)
112
113     # Przypadki ogólniejsze
114     if 'theme' in self:
115         title += u'Motyw %s' % unicode(self['theme'])
116
117     if 'genre' in self:
118         if 'theme' in self:
119             title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
120         else:
121             title += unicode(self['genre'])
122
123     if 'kind' in self or 'author' in self or 'epoch' in self:
124         if 'genre' in self or 'theme' in self:
125             if 'kind' in self:
126                 title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
127             else:
128                 title += u' w twórczości '
129         else:
130             title += u'%s ' % unicode(self.get('kind', u'twórczość'))
131
132     if 'author' in self:
133         title += flection.get_case(unicode(self['author']), u'dopełniacz')
134     elif 'epoch' in self:
135         title += flection.get_case(unicode(self['epoch']), u'dopełniacz')
136
137     return capfirst(title)
138
139
140 @register.simple_tag
141 def book_tree(book_list, books_by_parent):
142     text = "".join("<li><a href='%s'>%s</a>%s</li>" % (
143         book.get_absolute_url(), book.title, book_tree(books_by_parent.get(book, ()), books_by_parent)
144         ) for book in book_list)
145
146     if text:
147         return "<ol>%s</ol>" % text
148     else:
149         return ''
150
151 @register.simple_tag
152 def audiobook_tree(book_list, books_by_parent):
153     text = "".join("<li><a class='open-player' href='%s'>%s</a>%s</li>" % (
154         reverse("book_player", args=[book.slug]), book.title, audiobook_tree(books_by_parent.get(book, ()), books_by_parent)
155         ) for book in book_list)
156
157     if text:
158         return "<ol>%s</ol>" % text
159     else:
160         return ''
161
162 @register.simple_tag
163 def book_tree_texml(book_list, books_by_parent, depth=1):
164     return "".join("""
165             <cmd name='hspace'><parm>%(depth)dem</parm></cmd>%(title)s
166             <spec cat='align' /><cmd name="note"><parm>%(audiences)s</parm></cmd>
167             <spec cat='align' /><cmd name="note"><parm>%(audiobook)s</parm></cmd>
168             <ctrl ch='\\' />
169             %(children)s
170             """ % {
171                 "depth": depth,
172                 "title": book.title, 
173                 "audiences": ", ".join(book.audiences_pl()),
174                 "audiobook": "audiobook" if book.has_media('mp3') else "",
175                 "children": book_tree_texml(books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
176             } for book in book_list)
177
178
179 @register.simple_tag
180 def all_editors(extra_info):
181     editors = []
182     if 'editors' in extra_info:
183         editors += extra_info['editors']
184     if 'technical_editors' in extra_info:
185         editors += extra_info['technical_editors']
186     # support for extra_info-s from librarian<1.2
187     if 'editor' in extra_info:
188         editors.append(extra_info['editor'])
189     if 'technical_editor' in extra_info:
190         editors.append(extra_info['technical_editor'])
191     return ', '.join(
192                      ' '.join(p.strip() for p in person.rsplit(',', 1)[::-1])
193                      for person in sorted(set(editors)))
194
195
196 @register.simple_tag
197 def user_creation_form():
198     return RegistrationForm(prefix='registration').as_ul()
199
200
201 @register.simple_tag
202 def authentication_form():
203     return LoginForm(prefix='login').as_ul()
204
205
206 @register.tag
207 def catalogue_url(parser, token):
208     bits = token.split_contents()
209
210     tags_to_add = []
211     tags_to_remove = []
212     for bit in bits[1:]:
213         if bit[0] == '-':
214             tags_to_remove.append(bit[1:])
215         else:
216             tags_to_add.append(bit)
217
218     return CatalogueURLNode(tags_to_add, tags_to_remove)
219
220
221 class CatalogueURLNode(Node):
222     def __init__(self, tags_to_add, tags_to_remove):
223         self.tags_to_add = [Variable(tag) for tag in tags_to_add]
224         self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
225
226     def render(self, context):
227         tags_to_add = []
228         tags_to_remove = []
229
230         for tag_variable in self.tags_to_add:
231             tag = tag_variable.resolve(context)
232             if isinstance(tag, (list, dict)):
233                 tags_to_add += [t for t in tag]
234             else:
235                 tags_to_add.append(tag)
236
237         for tag_variable in self.tags_to_remove:
238             tag = tag_variable.resolve(context)
239             if iterable(tag):
240                 tags_to_remove += [t for t in tag]
241             else:
242                 tags_to_remove.append(tag)
243
244         tag_slugs = [tag.url_chunk for tag in tags_to_add]
245         for tag in tags_to_remove:
246             try:
247                 tag_slugs.remove(tag.url_chunk)
248             except KeyError:
249                 pass
250
251         if len(tag_slugs) > 0:
252             return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
253         else:
254             return reverse('main_page')
255
256
257 @register.inclusion_tag('catalogue/latest_blog_posts.html')
258 def latest_blog_posts(feed_url, posts_to_show=5):
259     try:
260         feed = feedparser.parse(str(feed_url))
261         posts = []
262         for i in range(posts_to_show):
263             pub_date = feed['entries'][i].updated_parsed
264             published = datetime.date(pub_date[0], pub_date[1], pub_date[2] )
265             posts.append({
266                 'title': feed['entries'][i].title,
267                 'summary': feed['entries'][i].summary,
268                 'link': feed['entries'][i].link,
269                 'date': published,
270                 })
271         return {'posts': posts}
272     except:
273         return {'posts': []}
274
275
276 @register.inclusion_tag('catalogue/tag_list.html')
277 def tag_list(tags, choices=None):
278     if choices is None:
279         choices = []
280     if len(tags) == 1 and tags[0].category not in [t.category for t in choices]:
281         one_tag = tags[0]
282     return locals()
283
284
285 @register.inclusion_tag('catalogue/inline_tag_list.html')
286 def inline_tag_list(tags, choices=None):
287     return tag_list(tags, choices)
288
289
290 @register.inclusion_tag('catalogue/book_info.html')
291 def book_info(book):
292     return locals()
293
294
295 @register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
296 def book_wide(context, book):
297     book_themes = book.related_themes()
298     extra_info = book.extra_info
299     hide_about = extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl')
300
301     return {
302         'book': book,
303         'main_link': reverse('book_text', args=[book.slug]) if book.html_file else None,
304         'related': book.related_info(),
305         'extra_info': extra_info,
306         'hide_about': hide_about,
307         'themes': book_themes,
308         'request': context.get('request'),
309     }
310
311
312 @register.inclusion_tag('catalogue/book_short.html', takes_context=True)
313 def book_short(context, book):
314     return {
315         'book': book,
316         'main_link': book.get_absolute_url(),
317         'related': book.related_info(),
318         'request': context.get('request'),
319     }
320
321
322 @register.inclusion_tag('catalogue/book_mini_box.html')
323 def book_mini(book):
324     return {
325         'book': book,
326         'related': book.related_info(),
327     }
328
329
330 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
331 def work_list(context, object_list):
332     request = context.get('request')
333     if object_list:
334         object_type = type(object_list[0]).__name__
335     return locals()
336
337
338 @register.inclusion_tag('catalogue/fragment_promo.html')
339 def fragment_promo(arg=None):
340     if arg is None:
341         fragments = Fragment.objects.all().order_by('?')
342         fragment = fragments[0] if fragments.exists() else None
343     elif isinstance(arg, Book):
344         fragment = arg.choose_fragment()
345     else:
346         fragments = Fragment.tagged.with_all(arg).order_by('?')
347         fragment = fragments[0] if fragments.exists() else None
348
349     return {
350         'fragment': fragment,
351     }
352
353
354 @register.inclusion_tag('catalogue/related_books.html')
355 def related_books(book, limit=6, random=1):
356     cache_key = "catalogue.related_books.%d.%d" % (book.id, limit - random)
357     related = cache.get(cache_key)
358     if related is None:
359         related = list(Book.objects.filter(
360             common_slug=book.common_slug).exclude(pk=book.pk)[:limit])
361         limit -= len(related)
362         if limit > random:
363             related += Book.tagged.related_to(book,
364                     Book.objects.exclude(common_slug=book.common_slug),
365                     ignore_by_tag=book.book_tag())[:limit-random]
366         cache.set(cache_key, related, 1800)
367     if random:
368         related += list(Book.objects.exclude(
369                         pk__in=[b.pk for b in related] + [book.pk]
370                     ).order_by('?')[:random])
371     return {
372         'books': related,
373     }
374
375
376 @register.inclusion_tag('catalogue/menu.html')
377 def catalogue_menu():
378     tags = Tag.objects.filter(
379             category__in=('author', 'epoch', 'genre', 'kind', 'theme')
380         ).exclude(book_count=0)
381     return split_tags(tags)
382     
383
384
385 @register.simple_tag
386 def tag_url(category, slug):
387     return reverse('catalogue.views.tagged_object_list', args=[
388         '/'.join((Tag.categories_dict[category], slug))
389     ])
390
391
392 @register.simple_tag
393 def download_audio(book, daisy=True):
394     related = book.related_info()
395     links = []
396     if related['media'].get('mp3'):
397         links.append("<a href='%s'>%s</a>" %
398             (reverse('download_zip_mp3', args=[book.slug]),
399                 BookMedia.formats['mp3'].name))
400     if related['media'].get('ogg'):
401         links.append("<a href='%s'>%s</a>" %
402             (reverse('download_zip_ogg', args=[book.slug]),
403                 BookMedia.formats['ogg'].name))
404     if daisy and related['media'].get('daisy'):
405         for dsy in book.get_media('daisy'):
406             links.append("<a href='%s'>%s</a>" %
407                 (dsy.file.url, BookMedia.formats['daisy'].name))
408     return ", ".join(links)
409
410
411 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
412 def custom_pdf_link_li(book):
413     return {
414         'book': book,
415         'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
416     }