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.
7 from random import randint
9 from django.conf import settings
10 from django import template
11 from django.template import Node, Variable, Template, Context
12 from django.core.cache import cache
13 from django.core.urlresolvers import reverse
14 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
15 from django.utils.translation import ugettext as _
17 from catalogue.utils import split_tags, related_tag_name as _related_tag_name
18 from catalogue.models import Book, BookMedia, Fragment, Tag
19 from catalogue.constants import LICENSES, LANGUAGES_3TO2
21 register = template.Library()
24 class RegistrationForm(UserCreationForm):
26 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
27 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)
30 class LoginForm(AuthenticationForm):
32 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
33 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)
46 return '%s%s' % (text[0].upper(), text[1:])
52 def html_title_from_tags(tags):
54 return title_from_tags(tags)
55 template = Template("{{ category }}: <a href='{{ tag.get_absolute_url }}'>{{ tag.name }}</a>")
56 return capfirst(",<br/>".join(
57 template.render(Context({'tag': tag, 'category': _(tag.category)})) for tag in tags))
60 def simple_title(tags):
63 title.append("%s: %s" % (_(tag.category), tag.name))
64 return capfirst(', '.join(title))
68 def book_title(book, html_links=False):
69 return book.pretty_title(html_links)
73 def book_title_html(book):
74 return book_title(book, html_links=True)
78 def title_from_tags(tags):
82 result[tag.category] = tag
85 # TODO: Remove this after adding flection mechanism
86 return simple_title(tags)
88 class Flection(object):
89 def get_case(self, name, flection):
93 self = split_tags(tags)
97 # Specjalny przypadek oglądania wszystkich lektur na danej półce
98 if len(self) == 1 and 'set' in self:
99 return u'Półka %s' % self['set']
101 # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
102 # jest wybrana przez użytkownika
103 if 'epoch' in self and len(self) == 1:
104 text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik')
105 return capfirst(text)
107 # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
108 # są tylko rodzaj literacki i autor
109 if 'kind' in self and 'author' in self and len(self) == 2:
110 text = u'%s w twórczości %s' % (unicode(self['kind']),
111 flection.get_case(unicode(self['author']), u'dopełniacz'))
112 return capfirst(text)
114 # Przypadki ogólniejsze
116 title += u'Motyw %s' % unicode(self['theme'])
120 title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
122 title += unicode(self['genre'])
124 if 'kind' in self or 'author' in self or 'epoch' in self:
125 if 'genre' in self or 'theme' in self:
127 title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
129 title += u' w twórczości '
131 title += u'%s ' % unicode(self.get('kind', u'twórczość'))
134 title += flection.get_case(unicode(self['author']), u'dopełniacz')
135 elif 'epoch' in self:
136 title += flection.get_case(unicode(self['epoch']), u'dopełniacz')
138 return capfirst(title)
142 def book_tree(book_list, books_by_parent):
143 text = "".join("<li><a href='%s'>%s</a>%s</li>" % (
144 book.get_absolute_url(), book.title, book_tree(books_by_parent.get(book, ()), books_by_parent)
145 ) for book in book_list)
148 return "<ol>%s</ol>" % text
153 def audiobook_tree(book_list, books_by_parent):
154 text = "".join("<li><a class='open-player' href='%s'>%s</a>%s</li>" % (
155 reverse("book_player", args=[book.slug]), book.title, audiobook_tree(books_by_parent.get(book, ()), books_by_parent)
156 ) for book in book_list)
159 return "<ol>%s</ol>" % text
164 def book_tree_texml(book_list, books_by_parent, depth=1):
166 <cmd name='hspace'><parm>%(depth)dem</parm></cmd>%(title)s
167 <spec cat='align' /><cmd name="note"><parm>%(audiences)s</parm></cmd>
168 <spec cat='align' /><cmd name="note"><parm>%(audiobook)s</parm></cmd>
174 "audiences": ", ".join(book.audiences_pl()),
175 "audiobook": "audiobook" if book.has_media('mp3') else "",
176 "children": book_tree_texml(books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
177 } for book in book_list)
181 def book_tree_csv(author, book_list, books_by_parent, depth=1, max_depth=3, delimeter="\t"):
182 def quote_if_necessary(s):
185 s.replace('"', '\\"')
190 return "".join("""%(author)s%(d)s%(preindent)s%(title)s%(d)s%(postindent)s%(audiences)s%(d)s%(audiobook)s
193 "preindent": delimeter * (depth - 1),
194 "postindent": delimeter * (max_depth - depth),
196 "author": quote_if_necessary(author.name),
197 "title": quote_if_necessary(book.title),
198 "audiences": ", ".join(book.audiences_pl()),
199 "audiobook": "audiobook" if book.has_media('mp3') else "",
200 "children": book_tree_csv(author, books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
201 } for book in book_list)
204 def all_editors(extra_info):
206 if 'editors' in extra_info:
207 editors += extra_info['editors']
208 if 'technical_editors' in extra_info:
209 editors += extra_info['technical_editors']
210 # support for extra_info-s from librarian<1.2
211 if 'editor' in extra_info:
212 editors.append(extra_info['editor'])
213 if 'technical_editor' in extra_info:
214 editors.append(extra_info['technical_editor'])
216 ' '.join(p.strip() for p in person.rsplit(',', 1)[::-1])
217 for person in sorted(set(editors)))
221 def user_creation_form():
222 return RegistrationForm(prefix='registration').as_ul()
226 def authentication_form():
227 return LoginForm(prefix='login').as_ul()
231 def catalogue_url(parser, token):
232 bits = token.split_contents()
238 tags_to_remove.append(bit[1:])
240 tags_to_add.append(bit)
242 return CatalogueURLNode(tags_to_add, tags_to_remove)
245 class CatalogueURLNode(Node):
246 def __init__(self, tags_to_add, tags_to_remove):
247 self.tags_to_add = [Variable(tag) for tag in tags_to_add]
248 self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
250 def render(self, context):
254 for tag_variable in self.tags_to_add:
255 tag = tag_variable.resolve(context)
256 if isinstance(tag, (list, dict)):
257 tags_to_add += [t for t in tag]
259 tags_to_add.append(tag)
261 for tag_variable in self.tags_to_remove:
262 tag = tag_variable.resolve(context)
264 tags_to_remove += [t for t in tag]
266 tags_to_remove.append(tag)
268 tag_slugs = [tag.url_chunk for tag in tags_to_add]
269 for tag in tags_to_remove:
271 tag_slugs.remove(tag.url_chunk)
275 if len(tag_slugs) > 0:
276 return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
278 return reverse('main_page')
281 @register.inclusion_tag('catalogue/latest_blog_posts.html')
282 def latest_blog_posts(feed_url, posts_to_show=5):
284 feed = feedparser.parse(str(feed_url))
286 for i in range(posts_to_show):
287 pub_date = feed['entries'][i].updated_parsed
288 published = datetime.date(pub_date[0], pub_date[1], pub_date[2] )
290 'title': feed['entries'][i].title,
291 'summary': feed['entries'][i].summary,
292 'link': feed['entries'][i].link,
295 return {'posts': posts}
300 @register.inclusion_tag('catalogue/tag_list.html')
301 def tag_list(tags, choices=None):
304 if len(tags) == 1 and tags[0].category not in [t.category for t in choices]:
309 @register.inclusion_tag('catalogue/inline_tag_list.html')
310 def inline_tag_list(tags, choices=None):
311 return tag_list(tags, choices)
314 @register.inclusion_tag('catalogue/collection_list.html')
315 def collection_list(collections):
319 @register.inclusion_tag('catalogue/book_info.html')
324 @register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
325 def book_wide(context, book):
326 book_themes = book.related_themes()
327 extra_info = book.extra_info
328 hide_about = extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl')
332 'main_link': reverse('book_text', args=[book.slug]) if book.html_file else None,
333 'related': book.related_info(),
334 'extra_info': extra_info,
335 'hide_about': hide_about,
336 'themes': book_themes,
337 'request': context.get('request'),
338 'show_lang': book.language_code() != settings.LANGUAGE_CODE,
342 @register.inclusion_tag('catalogue/book_short.html', takes_context=True)
343 def book_short(context, book):
346 'main_link': book.get_absolute_url(),
347 'related': book.related_info(),
348 'request': context.get('request'),
349 'show_lang': book.language_code() != settings.LANGUAGE_CODE,
353 @register.inclusion_tag('catalogue/book_mini_box.html')
354 def book_mini(book, with_link=True):
355 author_str = ", ".join(related_tag_name(tag)
356 for tag in book.related_info()['tags'].get('author', ()))
359 'author_str': author_str,
360 'with_link': with_link,
361 'show_lang': book.language_code() != settings.LANGUAGE_CODE,
365 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
366 def work_list(context, object_list):
367 request = context.get('request')
368 for obj in object_list:
369 obj.object_type = type(obj).__name__
374 @register.inclusion_tag('catalogue/fragment_promo.html')
375 def fragment_promo(arg=None):
377 fragments = Fragment.objects.all().order_by('?')
378 fragment = fragments[0] if fragments.exists() else None
379 elif isinstance(arg, Book):
380 fragment = arg.choose_fragment()
382 fragments = Fragment.tagged.with_all(arg).order_by('?')
383 fragment = fragments[0] if fragments.exists() else None
386 'fragment': fragment,
390 @register.inclusion_tag('catalogue/related_books.html')
391 def related_books(book, limit=6, random=1, taken=0):
392 limit = limit - taken
393 cache_key = "catalogue.related_books.%d.%d" % (book.id, limit - random)
394 related = cache.get(cache_key)
396 related = Book.tagged.related_to(book,
397 Book.objects.exclude(common_slug=book.common_slug),
398 ignore_by_tag=book.book_tag())[:limit-random]
399 cache.set(cache_key, related, 1800)
401 random_books = Book.objects.exclude(
402 pk__in=[b.pk for b in related] + [book.pk])
404 count = random_books.count()
406 related.append(random_books[randint(0, count - 1)])
408 related += list(random_books.order_by('?')[:random])
414 @register.inclusion_tag('catalogue/menu.html')
415 def catalogue_menu():
416 return {'categories': [
417 ('author', _('Authors'), 'autorzy'),
418 ('genre', _('Genres'), 'gatunki'),
419 ('kind', _('Kinds'), 'rodzaje'),
420 ('epoch', _('Epochs'), 'epoki'),
421 ('theme', _('Themes'), 'autorzy'),
426 def tag_url(category, slug):
427 return Tag.create_url(category, slug)
431 def download_audio(book, daisy=True):
432 related = book.related_info()
434 if related['media'].get('mp3'):
435 links.append("<a href='%s'>%s</a>" %
436 (reverse('download_zip_mp3', args=[book.slug]),
437 BookMedia.formats['mp3'].name))
438 if related['media'].get('ogg'):
439 links.append("<a href='%s'>%s</a>" %
440 (reverse('download_zip_ogg', args=[book.slug]),
441 BookMedia.formats['ogg'].name))
442 if daisy and related['media'].get('daisy'):
443 for dsy in book.get_media('daisy'):
444 links.append("<a href='%s'>%s</a>" %
445 (dsy.file.url, BookMedia.formats['daisy'].name))
446 return ", ".join(links)
449 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
450 def custom_pdf_link_li(book):
453 'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
457 @register.inclusion_tag("catalogue/snippets/license_icon.html")
458 def license_icon(license_url):
459 """Creates a license icon, if the license_url is known."""
460 known = LICENSES.get(license_url)
464 "license_url": license_url,
465 "icon": "img/licenses/%s.png" % known['icon'],
466 "license_description": known['description'],
471 def related_tag_name(tag, lang=None):
472 return _related_tag_name(tag, lang)
477 return obj.__class__.__name__