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]:
325 if category is not None:
326 other = Tag.objects.filter(category=category).exclude(pk__in=[t.pk for t in tags])\
327 .exclude(pk__in=[t.pk for t in category_choices])
328 # Filter out empty tags.
329 ct = ContentType.objects.get_for_model(Picture if gallery else Book)
330 other = other.filter(items__content_type=ct).distinct()
342 @register.inclusion_tag('catalogue/inline_tag_list.html')
343 def inline_tag_list(tags, choices=None, category=None, gallery=False):
344 return tag_list(tags, choices, category, gallery)
347 @register.inclusion_tag('catalogue/collection_list.html')
348 def collection_list(collections):
349 return {'collections': collections}
352 @register.inclusion_tag('catalogue/book_info.html')
355 'is_picture': isinstance(book, Picture),
360 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
361 def work_list(context, object_list):
362 request = context.get('request')
363 return {'object_list': object_list, 'request': request}
366 @register.inclusion_tag('catalogue/plain_list.html', takes_context=True)
367 def plain_list(context, object_list, with_initials=True, by_author=False, choice=None, book=None, gallery=False,
368 paged=True, initial_blocks=False):
371 if len(object_list) < settings.CATALOGUE_MIN_INITIALS and not by_author:
372 with_initials = False
373 initial_blocks = False
374 for obj in object_list:
377 initial = obj.sort_key_author
379 initial = obj.get_initial().upper()
380 if initial != last_initial:
381 last_initial = initial
382 names.append((obj.author_str() if by_author else initial, []))
383 names[-1][1].append(obj)
387 'initial_blocks': initial_blocks,
394 # TODO: These are no longer just books.
395 @register.inclusion_tag('catalogue/related_books.html', takes_context=True)
396 def related_books(context, instance, limit=6, random=1, taken=0):
398 max_books = limit - random
399 is_picture = isinstance(instance, Picture)
401 pics_qs = Picture.objects.all()
403 pics_qs = pics_qs.exclude(pk=instance.pk)
404 pics = Picture.tagged.related_to(instance, pics_qs)
406 # Reserve one spot for an image.
409 books_qs = Book.objects.all()
411 books_qs = books_qs.exclude(common_slug=instance.common_slug).exclude(ancestor=instance)
412 books = Book.tagged.related_to(instance, books_qs)[:max_books]
414 pics = pics[:1 + max_books - books.count()]
416 random_excluded_books = [b.pk for b in books]
417 random_excluded_pics = [p.pk for p in pics]
418 (random_excluded_pics if is_picture else random_excluded_books).append(instance.pk)
421 'request': context['request'],
425 'random_excluded_books': random_excluded_books,
426 'random_excluded_pics': random_excluded_pics,
431 def download_audio(book, daisy=True):
433 if book.has_media('mp3'):
434 links.append("<a href='%s'>%s</a>" % (
435 reverse('download_zip_mp3', args=[book.slug]), BookMedia.formats['mp3'].name))
436 if book.has_media('ogg'):
437 links.append("<a href='%s'>%s</a>" % (
438 reverse('download_zip_ogg', args=[book.slug]), BookMedia.formats['ogg'].name))
439 if daisy and book.has_media('daisy'):
440 for dsy in book.get_media('daisy'):
441 links.append("<a href='%s'>%s</a>" % (dsy.file.url, BookMedia.formats['daisy'].name))
442 return "".join(links)
445 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
446 def custom_pdf_link_li(book):
449 'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
453 @register.inclusion_tag("catalogue/snippets/license_icon.html")
454 def license_icon(license_url):
455 """Creates a license icon, if the license_url is known."""
456 known = LICENSES.get(license_url)
460 "license_url": license_url,
461 "icon": "img/licenses/%s.png" % known['icon'],
462 "license_description": known['description'],
468 return obj.__class__.__name__
472 def source_name(url):
474 netloc = urlparse(url).netloc
476 netloc = urlparse('http://' + url).netloc
479 source, created = Source.objects.get_or_create(netloc=netloc)
480 return source.name or netloc
483 @ssi_variable(register, patch_response=[add_never_cache_headers])
484 def catalogue_random_book(request, exclude_ids):
485 from .. import app_settings
486 if random() < app_settings.RELATED_RANDOM_PICTURE_CHANCE:
488 queryset = Book.objects.exclude(pk__in=exclude_ids)
489 count = queryset.count()
491 return queryset[randint(0, count - 1)].pk
496 @ssi_variable(register, patch_response=[add_never_cache_headers])
497 def choose_fragment(request, book_id=None, tag_ids=None, unless=False):
501 if book_id is not None:
502 fragment = Book.objects.get(pk=book_id).choose_fragment()
504 if tag_ids is not None:
505 tags = Tag.objects.filter(pk__in=tag_ids)
506 fragments = Fragment.tagged.with_all(tags).order_by().only('id')
508 fragments = Fragment.objects.all().order_by().only('id')
509 fragment_count = fragments.count()
510 fragment = fragments[randint(0, fragment_count - 1)] if fragment_count else None
511 return fragment.pk if fragment is not None else None
515 def strip_tag(html, tag_name):
516 # docelowo może być warto zainstalować BeautifulSoup do takich rzeczy
518 return re.sub(r"<.?%s\b[^>]*>" % tag_name, "", html)