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(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)
247 def catalogue_url_gallery(parser, token):
248 bits = token.split_contents()
254 tags_to_remove.append(bit[1:])
256 tags_to_add.append(bit)
258 return CatalogueURLNode(tags_to_add, tags_to_remove, gallery=True)
261 class CatalogueURLNode(Node):
262 def __init__(self, tags_to_add, tags_to_remove, gallery=False):
263 self.tags_to_add = [Variable(tag) for tag in tags_to_add]
264 self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
265 self.gallery = gallery
267 def render(self, context):
271 for tag_variable in self.tags_to_add:
272 tag = tag_variable.resolve(context)
273 if isinstance(tag, (list, dict)):
274 tags_to_add += [t for t in tag]
276 tags_to_add.append(tag)
278 for tag_variable in self.tags_to_remove:
279 tag = tag_variable.resolve(context)
281 tags_to_remove += [t for t in tag]
283 tags_to_remove.append(tag)
285 tag_slugs = [tag.url_chunk for tag in tags_to_add]
286 for tag in tags_to_remove:
288 tag_slugs.remove(tag.url_chunk)
292 if len(tag_slugs) > 0:
294 return reverse('tagged_object_list_gallery', kwargs={'tags': '/'.join(tag_slugs)})
296 return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
298 return reverse('book_list')
301 @register.inclusion_tag('catalogue/tag_list.html')
302 def tag_list(tags, choices=None, category=None, gallery=False):
303 print(tags, choices, category)
307 if category is None and tags:
308 category = tags[0].category
310 category_choices = [tag for tag in choices if tag.category == category]
312 if len(tags) == 1 and category not in [t.category for t in choices]:
315 if category is not None:
316 other = Tag.objects.filter(category=category).exclude(pk__in=[t.pk for t in tags]).exclude(pk__in=[t.pk for t in category_choices])
317 # Filter out empty tags.
318 ct = ContentType.objects.get_for_model(Picture if gallery else Book)
319 other = other.filter(items__content_type=ct).distinct()
324 @register.inclusion_tag('catalogue/inline_tag_list.html')
325 def inline_tag_list(tags, choices=None, category=None, gallery=False):
326 return tag_list(tags, choices, category, gallery)
329 @register.inclusion_tag('catalogue/collection_list.html')
330 def collection_list(collections):
334 @register.inclusion_tag('catalogue/book_info.html')
337 'is_picture': isinstance(book, Picture),
342 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
343 def work_list(context, object_list):
344 request = context.get('request')
349 @register.inclusion_tag('catalogue/plain_list.html', takes_context=True)
350 def plain_list(context, object_list, with_initials=True, by_author=False, choice=None, book=None, gallery=False, paged=True):
353 for obj in object_list:
356 initial = obj.sort_key_author
358 initial = obj.get_initial().upper()
359 if initial != last_initial:
360 last_initial = initial
361 names.append((obj.author_str() if by_author else initial, []))
362 names[-1][1].append(obj)
367 # TODO: These are no longer just books.
368 @register.inclusion_tag('catalogue/related_books.html', takes_context=True)
369 def related_books(context, instance, limit=6, random=1, taken=0):
370 limit = limit - taken
371 max_books = limit - random
372 is_picture = isinstance(instance, Picture)
374 pics_qs = Picture.objects.all()
376 pics_qs = pics_qs.exclude(pk=instance.pk)
377 pics = Picture.tagged.related_to(instance, pics_qs)
379 # Reserve one spot for an image.
382 books_qs = Book.objects.all()
384 books_qs = books_qs.exclude(common_slug=instance.common_slug).exclude(ancestor=instance)
385 books = Book.tagged.related_to(instance, books_qs)[:max_books]
387 pics = pics[:1 + max_books - books.count()]
389 random_excluded_books = [b.pk for b in books]
390 random_excluded_pics = [p.pk for p in pics]
391 (random_excluded_pics if is_picture else random_excluded_books).append(instance.pk)
394 'request': context['request'],
398 'random_excluded_books': random_excluded_books,
399 'random_excluded_pics': random_excluded_pics,
404 def download_audio(book, daisy=True):
406 if book.has_media('mp3'):
407 links.append("<a href='%s'>%s</a>" %
408 (reverse('download_zip_mp3', args=[book.slug]),
409 BookMedia.formats['mp3'].name))
410 if book.has_media('ogg'):
411 links.append("<a href='%s'>%s</a>" %
412 (reverse('download_zip_ogg', args=[book.slug]),
413 BookMedia.formats['ogg'].name))
414 if daisy and book.has_media('daisy'):
415 for dsy in book.get_media('daisy'):
416 links.append("<a href='%s'>%s</a>" %
417 (dsy.file.url, BookMedia.formats['daisy'].name))
418 return "".join(links)
421 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
422 def custom_pdf_link_li(book):
425 'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
429 @register.inclusion_tag("catalogue/snippets/license_icon.html")
430 def license_icon(license_url):
431 """Creates a license icon, if the license_url is known."""
432 known = LICENSES.get(license_url)
436 "license_url": license_url,
437 "icon": "img/licenses/%s.png" % known['icon'],
438 "license_description": known['description'],
444 return obj.__class__.__name__
448 def source_name(url):
450 netloc = urlparse(url).netloc
452 netloc = urlparse('http://' + url).netloc
455 source, created = Source.objects.get_or_create(netloc=netloc)
456 return source.name or netloc
459 @ssi_variable(register, patch_response=[add_never_cache_headers])
460 def catalogue_random_book(request, exclude_ids):
461 from .. import app_settings
462 if random() < app_settings.RELATED_RANDOM_PICTURE_CHANCE:
464 queryset = Book.objects.exclude(pk__in=exclude_ids)
465 count = queryset.count()
467 return queryset[randint(0, count - 1)].pk
472 @ssi_variable(register, patch_response=[add_never_cache_headers])
473 def choose_fragment(request, book_id=None, tag_ids=None, unless=False):
477 if book_id is not None:
478 fragment = Book.objects.get(pk=book_id).choose_fragment()
480 if tag_ids is not None:
481 tags = Tag.objects.filter(pk__in=tag_ids)
482 fragments = Fragment.tagged.with_all(tags).order_by().only('id')
484 fragments = Fragment.objects.all().order_by().only('id')
485 fragment_count = fragments.count()
486 fragment = fragments[randint(0, fragment_count - 1)] if fragment_count else None
487 return fragment.pk if fragment is not None else None