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.
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 _
16 from catalogue.utils import split_tags
17 from catalogue.models import Book, BookMedia, Fragment, Tag
18 from catalogue.constants import LICENSES
20 register = template.Library()
23 class RegistrationForm(UserCreationForm):
25 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
26 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)
29 class LoginForm(AuthenticationForm):
31 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
32 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)
45 return '%s%s' % (text[0].upper(), text[1:])
51 def html_title_from_tags(tags):
53 return title_from_tags(tags)
54 template = Template("{{ category }}: <a href='{{ tag.get_absolute_url }}'>{{ tag.name }}</a>")
55 return capfirst(",<br/>".join(
56 template.render(Context({'tag': tag, 'category': _(tag.category)})) for tag in tags))
59 def simple_title(tags):
62 title.append("%s: %s" % (_(tag.category), tag.name))
63 return capfirst(', '.join(title))
67 def book_title(book, html_links=False):
68 return book.pretty_title(html_links)
72 def book_title_html(book):
73 return book_title(book, html_links=True)
77 def title_from_tags(tags):
81 result[tag.category] = tag
84 # TODO: Remove this after adding flection mechanism
85 return simple_title(tags)
87 class Flection(object):
88 def get_case(self, name, flection):
92 self = split_tags(tags)
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']
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)
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)
113 # Przypadki ogólniejsze
115 title += u'Motyw %s' % unicode(self['theme'])
119 title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
121 title += unicode(self['genre'])
123 if 'kind' in self or 'author' in self or 'epoch' in self:
124 if 'genre' in self or 'theme' in self:
126 title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
128 title += u' w twórczości '
130 title += u'%s ' % unicode(self.get('kind', u'twórczość'))
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')
137 return capfirst(title)
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)
147 return "<ol>%s</ol>" % text
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)
158 return "<ol>%s</ol>" % text
163 def book_tree_texml(book_list, books_by_parent, depth=1):
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>
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)
180 def book_tree_csv(author, book_list, books_by_parent, depth=1, max_depth=3, delimeter="\t"):
181 def quote_if_necessary(s):
184 s.replace('"', '\\"')
189 return "".join("""%(author)s%(d)s%(preindent)s%(title)s%(d)s%(postindent)s%(audiences)s%(d)s%(audiobook)s
192 "preindent": delimeter * (depth - 1),
193 "postindent": delimeter * (max_depth - depth),
195 "author": quote_if_necessary(author.name),
196 "title": quote_if_necessary(book.title),
197 "audiences": ", ".join(book.audiences_pl()),
198 "audiobook": "audiobook" if book.has_media('mp3') else "",
199 "children": book_tree_csv(author, books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
200 } for book in book_list)
203 def all_editors(extra_info):
205 if 'editors' in extra_info:
206 editors += extra_info['editors']
207 if 'technical_editors' in extra_info:
208 editors += extra_info['technical_editors']
209 # support for extra_info-s from librarian<1.2
210 if 'editor' in extra_info:
211 editors.append(extra_info['editor'])
212 if 'technical_editor' in extra_info:
213 editors.append(extra_info['technical_editor'])
215 ' '.join(p.strip() for p in person.rsplit(',', 1)[::-1])
216 for person in sorted(set(editors)))
220 def user_creation_form():
221 return RegistrationForm(prefix='registration').as_ul()
225 def authentication_form():
226 return LoginForm(prefix='login').as_ul()
230 def catalogue_url(parser, token):
231 bits = token.split_contents()
237 tags_to_remove.append(bit[1:])
239 tags_to_add.append(bit)
241 return CatalogueURLNode(tags_to_add, tags_to_remove)
244 class CatalogueURLNode(Node):
245 def __init__(self, tags_to_add, tags_to_remove):
246 self.tags_to_add = [Variable(tag) for tag in tags_to_add]
247 self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
249 def render(self, context):
253 for tag_variable in self.tags_to_add:
254 tag = tag_variable.resolve(context)
255 if isinstance(tag, (list, dict)):
256 tags_to_add += [t for t in tag]
258 tags_to_add.append(tag)
260 for tag_variable in self.tags_to_remove:
261 tag = tag_variable.resolve(context)
263 tags_to_remove += [t for t in tag]
265 tags_to_remove.append(tag)
267 tag_slugs = [tag.url_chunk for tag in tags_to_add]
268 for tag in tags_to_remove:
270 tag_slugs.remove(tag.url_chunk)
274 if len(tag_slugs) > 0:
275 return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
277 return reverse('main_page')
280 @register.inclusion_tag('catalogue/latest_blog_posts.html')
281 def latest_blog_posts(feed_url, posts_to_show=5):
283 feed = feedparser.parse(str(feed_url))
285 for i in range(posts_to_show):
286 pub_date = feed['entries'][i].updated_parsed
287 published = datetime.date(pub_date[0], pub_date[1], pub_date[2] )
289 'title': feed['entries'][i].title,
290 'summary': feed['entries'][i].summary,
291 'link': feed['entries'][i].link,
294 return {'posts': posts}
299 @register.inclusion_tag('catalogue/tag_list.html')
300 def tag_list(tags, choices=None):
303 if len(tags) == 1 and tags[0].category not in [t.category for t in choices]:
308 @register.inclusion_tag('catalogue/inline_tag_list.html')
309 def inline_tag_list(tags, choices=None):
310 return tag_list(tags, choices)
313 @register.inclusion_tag('catalogue/book_info.html')
318 @register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
319 def book_wide(context, book):
320 book_themes = book.related_themes()
321 extra_info = book.extra_info
322 hide_about = extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl')
326 'main_link': reverse('book_text', args=[book.slug]) if book.html_file else None,
327 'related': book.related_info(),
328 'extra_info': extra_info,
329 'hide_about': hide_about,
330 'themes': book_themes,
331 'request': context.get('request'),
335 @register.inclusion_tag('catalogue/book_short.html', takes_context=True)
336 def book_short(context, book):
339 'main_link': book.get_absolute_url(),
340 'related': book.related_info(),
341 'request': context.get('request'),
345 @register.inclusion_tag('catalogue/book_mini_box.html')
349 'related': book.related_info(),
353 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
354 def work_list(context, object_list):
355 request = context.get('request')
357 object_type = type(object_list[0]).__name__
361 @register.inclusion_tag('catalogue/fragment_promo.html')
362 def fragment_promo(arg=None):
364 fragments = Fragment.objects.all().order_by('?')
365 fragment = fragments[0] if fragments.exists() else None
366 elif isinstance(arg, Book):
367 fragment = arg.choose_fragment()
369 fragments = Fragment.tagged.with_all(arg).order_by('?')
370 fragment = fragments[0] if fragments.exists() else None
373 'fragment': fragment,
377 @register.inclusion_tag('catalogue/related_books.html')
378 def related_books(book, limit=6, random=1):
379 cache_key = "catalogue.related_books.%d.%d" % (book.id, limit - random)
380 related = cache.get(cache_key)
382 related = list(Book.objects.filter(
383 common_slug=book.common_slug).exclude(pk=book.pk)[:limit])
384 limit -= len(related)
386 related += Book.tagged.related_to(book,
387 Book.objects.exclude(common_slug=book.common_slug),
388 ignore_by_tag=book.book_tag())[:limit-random]
389 cache.set(cache_key, related, 1800)
391 related += list(Book.objects.exclude(
392 pk__in=[b.pk for b in related] + [book.pk]
393 ).order_by('?')[:random])
399 @register.inclusion_tag('catalogue/menu.html')
400 def catalogue_menu():
401 tags = Tag.objects.filter(
402 category__in=('author', 'epoch', 'genre', 'kind', 'theme')
403 ).exclude(book_count=0)
404 return split_tags(tags)
409 def tag_url(category, slug):
410 return reverse('catalogue.views.tagged_object_list', args=[
411 '/'.join((Tag.categories_dict[category], slug))
416 def download_audio(book, daisy=True):
417 related = book.related_info()
419 if related['media'].get('mp3'):
420 links.append("<a href='%s'>%s</a>" %
421 (reverse('download_zip_mp3', args=[book.slug]),
422 BookMedia.formats['mp3'].name))
423 if related['media'].get('ogg'):
424 links.append("<a href='%s'>%s</a>" %
425 (reverse('download_zip_ogg', args=[book.slug]),
426 BookMedia.formats['ogg'].name))
427 if daisy and related['media'].get('daisy'):
428 for dsy in book.get_media('daisy'):
429 links.append("<a href='%s'>%s</a>" %
430 (dsy.file.url, BookMedia.formats['daisy'].name))
431 return ", ".join(links)
434 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
435 def custom_pdf_link_li(book):
438 'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
442 @register.inclusion_tag("catalogue/snippets/license_icon.html")
443 def license_icon(license_url):
444 """Creates a license icon, if the license_url is known."""
445 known = LICENSES.get(license_url)
449 "license_url": license_url,
450 "icon": "img/licenses/%s.png" % known['icon'],
451 "license_description": known['description'],