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