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
8 from urlparse import urlparse
10 from django.conf import settings
11 from django import template
12 from django.template import Node, Variable, Template, Context
13 from django.core.cache import cache
14 from django.core.urlresolvers import reverse
15 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
16 from django.utils.translation import ugettext as _
18 from catalogue.utils import split_tags
19 from catalogue.models import Book, BookMedia, Fragment, Tag, Source
20 from catalogue.constants import LICENSES
22 register = template.Library()
25 class RegistrationForm(UserCreationForm):
27 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
28 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)
31 class LoginForm(AuthenticationForm):
33 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
34 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)
47 return '%s%s' % (text[0].upper(), text[1:])
53 def html_title_from_tags(tags):
55 return title_from_tags(tags)
56 template = Template("{{ category }}: <a href='{{ tag.get_absolute_url }}'>{{ tag.name }}</a>")
57 return capfirst(",<br/>".join(
58 template.render(Context({'tag': tag, 'category': _(tag.category)})) for tag in tags))
61 def simple_title(tags):
64 title.append("%s: %s" % (_(tag.category), tag.name))
65 return capfirst(', '.join(title))
69 def book_title(book, html_links=False):
70 return book.pretty_title(html_links)
74 def book_title_html(book):
75 return book_title(book, html_links=True)
79 def title_from_tags(tags):
83 result[tag.category] = tag
86 # TODO: Remove this after adding flection mechanism
87 return simple_title(tags)
89 class Flection(object):
90 def get_case(self, name, flection):
94 self = split_tags(tags)
98 # Specjalny przypadek oglądania wszystkich lektur na danej półce
99 if len(self) == 1 and 'set' in self:
100 return u'Półka %s' % self['set']
102 # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
103 # jest wybrana przez użytkownika
104 if 'epoch' in self and len(self) == 1:
105 text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik')
106 return capfirst(text)
108 # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
109 # są tylko rodzaj literacki i autor
110 if 'kind' in self and 'author' in self and len(self) == 2:
111 text = u'%s w twórczości %s' % (unicode(self['kind']),
112 flection.get_case(unicode(self['author']), u'dopełniacz'))
113 return capfirst(text)
115 # Przypadki ogólniejsze
117 title += u'Motyw %s' % unicode(self['theme'])
121 title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
123 title += unicode(self['genre'])
125 if 'kind' in self or 'author' in self or 'epoch' in self:
126 if 'genre' in self or 'theme' in self:
128 title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
130 title += u' w twórczości '
132 title += u'%s ' % unicode(self.get('kind', u'twórczość'))
135 title += flection.get_case(unicode(self['author']), u'dopełniacz')
136 elif 'epoch' in self:
137 title += flection.get_case(unicode(self['epoch']), u'dopełniacz')
139 return capfirst(title)
143 def book_tree(book_list, books_by_parent):
144 text = "".join("<li><a href='%s'>%s</a>%s</li>" % (
145 book.get_absolute_url(), book.title, book_tree(books_by_parent.get(book, ()), books_by_parent)
146 ) for book in book_list)
149 return "<ol>%s</ol>" % text
154 def audiobook_tree(book_list, books_by_parent):
155 text = "".join("<li><a class='open-player' href='%s'>%s</a>%s</li>" % (
156 reverse("book_player", args=[book.slug]), book.title, audiobook_tree(books_by_parent.get(book, ()), books_by_parent)
157 ) for book in book_list)
160 return "<ol>%s</ol>" % text
165 def book_tree_texml(book_list, books_by_parent, depth=1):
167 <cmd name='hspace'><parm>%(depth)dem</parm></cmd>%(title)s
168 <spec cat='align' /><cmd name="note"><parm>%(audiences)s</parm></cmd>
169 <spec cat='align' /><cmd name="note"><parm>%(audiobook)s</parm></cmd>
175 "audiences": ", ".join(book.audiences_pl()),
176 "audiobook": "audiobook" if book.has_media('mp3') else "",
177 "children": book_tree_texml(books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
178 } for book in book_list)
182 def book_tree_csv(author, book_list, books_by_parent, depth=1, max_depth=3, delimeter="\t"):
183 def quote_if_necessary(s):
186 s.replace('"', '\\"')
191 return "".join("""%(author)s%(d)s%(preindent)s%(title)s%(d)s%(postindent)s%(audiences)s%(d)s%(audiobook)s
194 "preindent": delimeter * (depth - 1),
195 "postindent": delimeter * (max_depth - depth),
197 "author": quote_if_necessary(author.name),
198 "title": quote_if_necessary(book.title),
199 "audiences": ", ".join(book.audiences_pl()),
200 "audiobook": "audiobook" if book.has_media('mp3') else "",
201 "children": book_tree_csv(author, books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
202 } for book in book_list)
205 def all_editors(extra_info):
207 if 'editors' in extra_info:
208 editors += extra_info['editors']
209 if 'technical_editors' in extra_info:
210 editors += extra_info['technical_editors']
211 # support for extra_info-s from librarian<1.2
212 if 'editor' in extra_info:
213 editors.append(extra_info['editor'])
214 if 'technical_editor' in extra_info:
215 editors.append(extra_info['technical_editor'])
217 ' '.join(p.strip() for p in person.rsplit(',', 1)[::-1])
218 for person in sorted(set(editors)))
222 def user_creation_form():
223 return RegistrationForm(prefix='registration').as_ul()
227 def authentication_form():
228 return LoginForm(prefix='login').as_ul()
232 def catalogue_url(parser, token):
233 bits = token.split_contents()
239 tags_to_remove.append(bit[1:])
241 tags_to_add.append(bit)
243 return CatalogueURLNode(tags_to_add, tags_to_remove)
246 class CatalogueURLNode(Node):
247 def __init__(self, tags_to_add, tags_to_remove):
248 self.tags_to_add = [Variable(tag) for tag in tags_to_add]
249 self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
251 def render(self, context):
255 for tag_variable in self.tags_to_add:
256 tag = tag_variable.resolve(context)
257 if isinstance(tag, (list, dict)):
258 tags_to_add += [t for t in tag]
260 tags_to_add.append(tag)
262 for tag_variable in self.tags_to_remove:
263 tag = tag_variable.resolve(context)
265 tags_to_remove += [t for t in tag]
267 tags_to_remove.append(tag)
269 tag_slugs = [tag.url_chunk for tag in tags_to_add]
270 for tag in tags_to_remove:
272 tag_slugs.remove(tag.url_chunk)
276 if len(tag_slugs) > 0:
277 return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
279 return reverse('main_page')
282 @register.inclusion_tag('catalogue/latest_blog_posts.html')
283 def latest_blog_posts(feed_url, posts_to_show=5):
285 feed = feedparser.parse(str(feed_url))
287 for i in range(posts_to_show):
288 pub_date = feed['entries'][i].published_parsed
289 published = datetime.date(pub_date[0], pub_date[1], pub_date[2])
291 'title': feed['entries'][i].title,
292 'summary': feed['entries'][i].summary,
293 'link': feed['entries'][i].link,
296 return {'posts': posts}
301 @register.inclusion_tag('catalogue/tag_list.html')
302 def tag_list(tags, choices=None):
305 if len(tags) == 1 and tags[0].category not in [t.category for t in choices]:
310 @register.inclusion_tag('catalogue/inline_tag_list.html')
311 def inline_tag_list(tags, choices=None):
312 return tag_list(tags, choices)
315 @register.inclusion_tag('catalogue/collection_list.html')
316 def collection_list(collections):
320 @register.inclusion_tag('catalogue/book_info.html')
325 @register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
326 def book_wide(context, book):
327 ctx = book_short(context, book)
328 ctx['extra_info'] = book.extra_info
329 ctx['hide_about'] = ctx['extra_info'].get('about', '').startswith('http://wiki.wolnepodreczniki.pl')
330 ctx['themes'] = book.related_themes()
331 ctx['main_link'] = reverse('book_text', args=[book.slug]) if book.html_file else None
335 @register.inclusion_tag('catalogue/book_short.html', takes_context=True)
336 def book_short(context, book):
337 stage_note, stage_note_url = book.stage_note()
341 'has_audio': book.has_media('mp3'),
342 'main_link': book.get_absolute_url(),
343 'parents': book.parents(),
344 'tags': split_tags(book.tags.exclude(category__in=('set', 'theme'))),
345 'request': context.get('request'),
346 'show_lang': book.language_code() != settings.LANGUAGE_CODE,
347 'stage_note': stage_note,
348 'stage_note_url': stage_note_url,
352 @register.inclusion_tag('catalogue/book_mini_box.html')
353 def book_mini(book, with_link=True):
354 author_str = ", ".join(tag.name
355 for tag in book.tags.filter(category='author'))
358 'author_str': author_str,
359 'with_link': with_link,
360 'show_lang': book.language_code() != settings.LANGUAGE_CODE,
364 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
365 def work_list(context, object_list):
366 request = context.get('request')
370 @register.inclusion_tag('catalogue/fragment_promo.html')
371 def fragment_promo(arg=None):
372 if isinstance(arg, Book):
373 fragment = arg.choose_fragment()
376 fragments = Fragment.objects.all()
378 fragments = Fragment.tagged.with_all(arg)
379 fragments = fragments.order_by().only('id')
380 fragments_count = fragments.count()
382 fragment = fragments.order_by()[randint(0, fragments_count - 1)]
387 'fragment': fragment,
391 @register.inclusion_tag('catalogue/related_books.html')
392 def related_books(book, limit=6, random=1, taken=0):
393 limit = limit - taken
394 cache_key = "catalogue.related_books.%d.%d" % (book.id, limit - random)
395 related = cache.get(cache_key)
397 related = Book.tagged.related_to(book,
398 Book.objects.exclude(common_slug=book.common_slug)
399 ).exclude(ancestor=book)[:limit-random]
400 cache.set(cache_key, related, 1800)
402 random_books = Book.objects.exclude(
403 pk__in=[b.pk for b in related] + [book.pk])
405 count = random_books.count()
407 random_related = [random_books[randint(0, count - 1)]]
409 random_related = list(random_books.order_by('?')[:random])
414 'random_related': random_related,
418 @register.inclusion_tag('catalogue/menu.html')
419 def catalogue_menu():
420 return {'categories': [
421 ('author', _('Authors'), 'autorzy'),
422 ('genre', _('Genres'), 'gatunki'),
423 ('kind', _('Kinds'), 'rodzaje'),
424 ('epoch', _('Epochs'), 'epoki'),
425 ('theme', _('Themes'), 'motywy'),
430 def tag_url(category, slug):
431 return Tag.create_url(category, slug)
435 def download_audio(book, daisy=True):
437 if related['media'].get('mp3'):
438 links.append("<a href='%s'>%s</a>" %
439 (reverse('download_zip_mp3', args=[book.slug]),
440 BookMedia.formats['mp3'].name))
441 if related['media'].get('ogg'):
442 links.append("<a href='%s'>%s</a>" %
443 (reverse('download_zip_ogg', args=[book.slug]),
444 BookMedia.formats['ogg'].name))
445 if daisy and related['media'].get('daisy'):
446 for dsy in book.get_media('daisy'):
447 links.append("<a href='%s'>%s</a>" %
448 (dsy.file.url, BookMedia.formats['daisy'].name))
449 return ", ".join(links)
452 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
453 def custom_pdf_link_li(book):
456 'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
460 @register.inclusion_tag("catalogue/snippets/license_icon.html")
461 def license_icon(license_url):
462 """Creates a license icon, if the license_url is known."""
463 known = LICENSES.get(license_url)
467 "license_url": license_url,
468 "icon": "img/licenses/%s.png" % known['icon'],
469 "license_description": known['description'],
475 return obj.__class__.__name__
479 def source_name(url):
481 netloc = urlparse(url).netloc
483 netloc = urlparse('http://' + url).netloc
486 source, created = Source.objects.get_or_create(netloc=netloc)
487 return source.name or netloc