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.
5 from random import randint, random
6 from urlparse import urlparse
7 from django.contrib.contenttypes.models import ContentType
9 from django.conf import settings
10 from django import template
11 from django.template import Node, Variable, Template, Context
12 from django.core.urlresolvers import reverse
13 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
14 from django.utils.cache import add_never_cache_headers
15 from django.utils.translation import ugettext as _
17 from ssify import ssi_variable
18 from catalogue.models import Book, BookMedia, Fragment, Tag, Source
19 from catalogue.constants import LICENSES
20 from picture.models import Picture
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(
29 u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>',
30 '</li>', u' %s', False)
33 class LoginForm(AuthenticationForm):
35 """Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."""
36 return self._html_output(
37 u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>',
38 '</li>', u' %s', False)
51 return '%s%s' % (text[0].upper(), text[1:])
57 def html_title_from_tags(tags):
59 return title_from_tags(tags)
60 template = Template("{{ category }}: <a href='{{ tag.get_absolute_url }}'>{{ tag.name }}</a>")
61 return capfirst(",<br/>".join(
62 template.render(Context({'tag': tag, 'category': _(tag.category)})) for tag in tags))
65 def simple_title(tags):
68 title.append("%s: %s" % (_(tag.category), tag.name))
69 return capfirst(', '.join(title))
73 def book_title(book, html_links=False):
74 return book.pretty_title(html_links)
78 def book_title_html(book):
79 return book_title(book, html_links=True)
83 def title_from_tags(tags):
87 result[tag.category] = tag
90 # TODO: Remove this after adding flection mechanism
91 return simple_title(tags)
93 class Flection(object):
94 def get_case(self, name, flection):
98 self = split_tags(tags)
102 # Specjalny przypadek oglądania wszystkich lektur na danej półce
103 if len(self) == 1 and 'set' in self:
104 return u'Półka %s' % self['set']
106 # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
107 # jest wybrana przez użytkownika
108 if 'epoch' in self and len(self) == 1:
109 text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik')
110 return capfirst(text)
112 # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
113 # są tylko rodzaj literacki i autor
114 if 'kind' in self and 'author' in self and len(self) == 2:
115 text = u'%s w twórczości %s' % (
116 unicode(self['kind']), flection.get_case(unicode(self['author']), u'dopełniacz'))
117 return capfirst(text)
119 # Przypadki ogólniejsze
121 title += u'Motyw %s' % unicode(self['theme'])
125 title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
127 title += unicode(self['genre'])
129 if 'kind' in self or 'author' in self or 'epoch' in self:
130 if 'genre' in self or 'theme' in self:
132 title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
134 title += u' w twórczości '
136 title += u'%s ' % unicode(self.get('kind', u'twórczość'))
139 title += flection.get_case(unicode(self['author']), u'dopełniacz')
140 elif 'epoch' in self:
141 title += flection.get_case(unicode(self['epoch']), u'dopełniacz')
143 return capfirst(title)
147 def book_tree(book_list, books_by_parent):
148 text = "".join("<li><a href='%s'>%s</a>%s</li>" % (
149 book.get_absolute_url(), book.title, book_tree(books_by_parent.get(book, ()), books_by_parent)
150 ) for book in book_list)
153 return "<ol>%s</ol>" % text
159 def audiobook_tree(book_list, books_by_parent):
160 text = "".join("<li><a class='open-player' href='%s'>%s</a>%s</li>" % (
161 reverse("book_player", args=[book.slug]), book.title,
162 audiobook_tree(books_by_parent.get(book, ()), books_by_parent)
163 ) for book in book_list)
166 return "<ol>%s</ol>" % text
172 def book_tree_texml(book_list, books_by_parent, depth=1):
174 <cmd name='hspace'><parm>%(depth)dem</parm></cmd>%(title)s
175 <spec cat='align' /><cmd name="note"><parm>%(audiences)s</parm></cmd>
176 <spec cat='align' /><cmd name="note"><parm>%(audiobook)s</parm></cmd>
182 "audiences": ", ".join(book.audiences_pl()),
183 "audiobook": "audiobook" if book.has_media('mp3') else "",
184 "children": book_tree_texml(books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
185 } for book in book_list)
189 def book_tree_csv(author, book_list, books_by_parent, depth=1, max_depth=3, delimeter="\t"):
190 def quote_if_necessary(s):
193 s.replace('"', '\\"')
198 return "".join("""%(author)s%(d)s%(preindent)s%(title)s%(d)s%(postindent)s%(audiences)s%(d)s%(audiobook)s
201 "preindent": delimeter * (depth - 1),
202 "postindent": delimeter * (max_depth - depth),
204 "author": quote_if_necessary(author.name),
205 "title": quote_if_necessary(book.title),
206 "audiences": ", ".join(book.audiences_pl()),
207 "audiobook": "audiobook" if book.has_media('mp3') else "",
208 "children": book_tree_csv(author, books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
209 } for book in book_list)
213 def all_editors(extra_info):
215 if 'editors' in extra_info:
216 editors += extra_info['editors']
217 if 'technical_editors' in extra_info:
218 editors += extra_info['technical_editors']
219 # support for extra_info-s from librarian<1.2
220 if 'editor' in extra_info:
221 editors.append(extra_info['editor'])
222 if 'technical_editor' in extra_info:
223 editors.append(extra_info['technical_editor'])
225 ' '.join(p.strip() for p in person.rsplit(',', 1)[::-1])
226 for person in sorted(set(editors)))
230 def user_creation_form():
231 return RegistrationForm(prefix='registration').as_ul()
235 def authentication_form():
236 return LoginForm(prefix='login').as_ul()
240 def catalogue_url(parser, token):
241 bits = token.split_contents()
247 tags_to_remove.append(bit[1:])
249 tags_to_add.append(bit)
251 return CatalogueURLNode(tags_to_add, tags_to_remove)
255 def catalogue_url_gallery(parser, token):
256 bits = token.split_contents()
262 tags_to_remove.append(bit[1:])
264 tags_to_add.append(bit)
266 return CatalogueURLNode(tags_to_add, tags_to_remove, gallery=True)
269 class CatalogueURLNode(Node):
270 def __init__(self, tags_to_add, tags_to_remove, gallery=False):
271 self.tags_to_add = [Variable(tag) for tag in tags_to_add]
272 self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
273 self.gallery = gallery
275 def render(self, context):
279 for tag_variable in self.tags_to_add:
280 tag = tag_variable.resolve(context)
281 if isinstance(tag, (list, dict)):
282 tags_to_add += [t for t in tag]
284 tags_to_add.append(tag)
286 for tag_variable in self.tags_to_remove:
287 tag = tag_variable.resolve(context)
289 tags_to_remove += [t for t in tag]
291 tags_to_remove.append(tag)
293 tag_slugs = [tag.url_chunk for tag in tags_to_add]
294 for tag in tags_to_remove:
296 tag_slugs.remove(tag.url_chunk)
300 if len(tag_slugs) > 0:
302 return reverse('tagged_object_list_gallery', kwargs={'tags': '/'.join(tag_slugs)})
304 return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
306 return reverse('book_list')
309 @register.inclusion_tag('catalogue/tag_list.html')
310 def tag_list(tags, choices=None, category=None, gallery=False):
311 print(tags, choices, category)
315 if category is None and tags:
316 category = tags[0].category
318 category_choices = [tag for tag in choices if tag.category == category]
320 if len(tags) == 1 and category not in [t.category for t in choices]:
323 if category is not None:
324 other = Tag.objects.filter(category=category).exclude(pk__in=[t.pk for t in tags])\
325 .exclude(pk__in=[t.pk for t in category_choices])
326 # Filter out empty tags.
327 ct = ContentType.objects.get_for_model(Picture if gallery else Book)
328 other = other.filter(items__content_type=ct).distinct()
333 @register.inclusion_tag('catalogue/inline_tag_list.html')
334 def inline_tag_list(tags, choices=None, category=None, gallery=False):
335 return tag_list(tags, choices, category, gallery)
338 @register.inclusion_tag('catalogue/collection_list.html')
339 def collection_list(collections):
343 @register.inclusion_tag('catalogue/book_info.html')
346 'is_picture': isinstance(book, Picture),
351 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
352 def work_list(context, object_list):
353 request = context.get('request')
357 @register.inclusion_tag('catalogue/plain_list.html', takes_context=True)
358 def plain_list(context, object_list, with_initials=True, by_author=False, choice=None, book=None, gallery=False,
362 for obj in object_list:
365 initial = obj.sort_key_author
367 initial = obj.get_initial().upper()
368 if initial != last_initial:
369 last_initial = initial
370 names.append((obj.author_str() if by_author else initial, []))
371 names[-1][1].append(obj)
375 # TODO: These are no longer just books.
376 @register.inclusion_tag('catalogue/related_books.html', takes_context=True)
377 def related_books(context, instance, limit=6, random=1, taken=0):
379 max_books = limit - random
380 is_picture = isinstance(instance, Picture)
382 pics_qs = Picture.objects.all()
384 pics_qs = pics_qs.exclude(pk=instance.pk)
385 pics = Picture.tagged.related_to(instance, pics_qs)
387 # Reserve one spot for an image.
390 books_qs = Book.objects.all()
392 books_qs = books_qs.exclude(common_slug=instance.common_slug).exclude(ancestor=instance)
393 books = Book.tagged.related_to(instance, books_qs)[:max_books]
395 pics = pics[:1 + max_books - books.count()]
397 random_excluded_books = [b.pk for b in books]
398 random_excluded_pics = [p.pk for p in pics]
399 (random_excluded_pics if is_picture else random_excluded_books).append(instance.pk)
402 'request': context['request'],
406 'random_excluded_books': random_excluded_books,
407 'random_excluded_pics': random_excluded_pics,
412 def download_audio(book, daisy=True):
414 if book.has_media('mp3'):
415 links.append("<a href='%s'>%s</a>" % (
416 reverse('download_zip_mp3', args=[book.slug]), BookMedia.formats['mp3'].name))
417 if book.has_media('ogg'):
418 links.append("<a href='%s'>%s</a>" % (
419 reverse('download_zip_ogg', args=[book.slug]), BookMedia.formats['ogg'].name))
420 if daisy and book.has_media('daisy'):
421 for dsy in book.get_media('daisy'):
422 links.append("<a href='%s'>%s</a>" % (dsy.file.url, BookMedia.formats['daisy'].name))
423 return "".join(links)
426 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
427 def custom_pdf_link_li(book):
430 'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
434 @register.inclusion_tag("catalogue/snippets/license_icon.html")
435 def license_icon(license_url):
436 """Creates a license icon, if the license_url is known."""
437 known = LICENSES.get(license_url)
441 "license_url": license_url,
442 "icon": "img/licenses/%s.png" % known['icon'],
443 "license_description": known['description'],
449 return obj.__class__.__name__
453 def source_name(url):
455 netloc = urlparse(url).netloc
457 netloc = urlparse('http://' + url).netloc
460 source, created = Source.objects.get_or_create(netloc=netloc)
461 return source.name or netloc
464 @ssi_variable(register, patch_response=[add_never_cache_headers])
465 def catalogue_random_book(request, exclude_ids):
466 from .. import app_settings
467 if random() < app_settings.RELATED_RANDOM_PICTURE_CHANCE:
469 queryset = Book.objects.exclude(pk__in=exclude_ids)
470 count = queryset.count()
472 return queryset[randint(0, count - 1)].pk
477 @ssi_variable(register, patch_response=[add_never_cache_headers])
478 def choose_fragment(request, book_id=None, tag_ids=None, unless=False):
482 if book_id is not None:
483 fragment = Book.objects.get(pk=book_id).choose_fragment()
485 if tag_ids is not None:
486 tags = Tag.objects.filter(pk__in=tag_ids)
487 fragments = Fragment.tagged.with_all(tags).order_by().only('id')
489 fragments = Fragment.objects.all().order_by().only('id')
490 fragment_count = fragments.count()
491 fragment = fragments[randint(0, fragment_count - 1)] if fragment_count else None
492 return fragment.pk if fragment is not None else None