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
18 from catalogue.models import Book, BookMedia, Fragment, Tag
19 from catalogue.constants import LICENSES
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/book_info.html')
319 @register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
320 def book_wide(context, book):
321 book_themes = book.related_themes()
322 extra_info = book.extra_info
323 hide_about = extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl')
327 'main_link': reverse('book_text', args=[book.slug]) if book.html_file else None,
328 'related': book.related_info(),
329 'extra_info': extra_info,
330 'hide_about': hide_about,
331 'themes': book_themes,
332 'request': context.get('request'),
336 @register.inclusion_tag('catalogue/book_short.html', takes_context=True)
337 def book_short(context, book):
340 'main_link': book.get_absolute_url(),
341 'related': book.related_info(),
342 'request': context.get('request'),
346 @register.inclusion_tag('catalogue/book_mini_box.html')
348 author_str = ", ".join(name
349 for name, url in book.related_info()['tags']['author'])
352 'author_str': author_str,
356 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
357 def work_list(context, object_list):
358 request = context.get('request')
360 object_type = type(object_list[0]).__name__
364 @register.inclusion_tag('catalogue/fragment_promo.html')
365 def fragment_promo(arg=None):
367 fragments = Fragment.objects.all().order_by('?')
368 fragment = fragments[0] if fragments.exists() else None
369 elif isinstance(arg, Book):
370 fragment = arg.choose_fragment()
372 fragments = Fragment.tagged.with_all(arg).order_by('?')
373 fragment = fragments[0] if fragments.exists() else None
376 'fragment': fragment,
380 @register.inclusion_tag('catalogue/related_books.html')
381 def related_books(book, limit=6, random=1):
382 cache_key = "catalogue.related_books.%d.%d" % (book.id, limit - random)
383 related = cache.get(cache_key)
385 related = list(Book.objects.filter(
386 common_slug=book.common_slug).exclude(pk=book.pk)[:limit])
387 limit -= len(related)
389 related += Book.tagged.related_to(book,
390 Book.objects.exclude(common_slug=book.common_slug),
391 ignore_by_tag=book.book_tag())[:limit-random]
392 cache.set(cache_key, related, 1800)
394 random_books = Book.objects.exclude(
395 pk__in=[b.pk for b in related] + [book.pk])
397 count = random_books.count()
399 related.append(random_books[randint(0, count - 1)])
401 related += list(random_books.order_by('?')[:random])
407 @register.inclusion_tag('catalogue/menu.html')
408 def catalogue_menu():
409 return {'categories': [
410 ('author', _('Authors'), 'autorzy'),
411 ('genre', _('Genres'), 'gatunki'),
412 ('kind', _('Kinds'), 'rodzaje'),
413 ('epoch', _('Epochs'), 'epoki'),
414 ('theme', _('Themes'), 'autorzy'),
419 def tag_url(category, slug):
420 return Tag.create_url(category, slug)
424 def download_audio(book, daisy=True):
425 related = book.related_info()
427 if related['media'].get('mp3'):
428 links.append("<a href='%s'>%s</a>" %
429 (reverse('download_zip_mp3', args=[book.slug]),
430 BookMedia.formats['mp3'].name))
431 if related['media'].get('ogg'):
432 links.append("<a href='%s'>%s</a>" %
433 (reverse('download_zip_ogg', args=[book.slug]),
434 BookMedia.formats['ogg'].name))
435 if daisy and related['media'].get('daisy'):
436 for dsy in book.get_media('daisy'):
437 links.append("<a href='%s'>%s</a>" %
438 (dsy.file.url, BookMedia.formats['daisy'].name))
439 return ", ".join(links)
442 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
443 def custom_pdf_link_li(book):
446 'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
450 @register.inclusion_tag("catalogue/snippets/license_icon.html")
451 def license_icon(license_url):
452 """Creates a license icon, if the license_url is known."""
453 known = LICENSES.get(license_url)
457 "license_url": license_url,
458 "icon": "img/licenses/%s.png" % known['icon'],
459 "license_description": known['description'],