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
19 from catalogue.helpers import get_audiobook_tags
20 from catalogue.models import Book, BookMedia, Fragment, Tag, Source
21 from catalogue.constants import LICENSES
22 from picture.models import Picture
24 register = template.Library()
27 class RegistrationForm(UserCreationForm):
29 """Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."""
30 return self._html_output(
31 u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>',
32 '</li>', u' %s', False)
35 class LoginForm(AuthenticationForm):
37 """Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."""
38 return self._html_output(
39 u'<li>%(errors)s%(label)s %(field)s<span class="help-text">%(help_text)s</span></li>', u'<li>%s</li>',
40 '</li>', u' %s', False)
53 return '%s%s' % (text[0].upper(), text[1:])
59 def html_title_from_tags(tags):
61 return title_from_tags(tags)
62 template = Template("{{ category }}: <a href='{{ tag.get_absolute_url }}'>{{ tag.name }}</a>")
63 return capfirst(",<br/>".join(
64 template.render(Context({'tag': tag, 'category': _(tag.category)})) for tag in tags))
67 def simple_title(tags):
70 title.append("%s: %s" % (_(tag.category), tag.name))
71 return capfirst(', '.join(title))
75 def book_title(book, html_links=False):
76 return book.pretty_title(html_links)
80 def book_title_html(book):
81 return book_title(book, html_links=True)
85 def title_from_tags(tags):
89 result[tag.category] = tag
92 # TODO: Remove this after adding flection mechanism
93 return simple_title(tags)
95 class Flection(object):
96 def get_case(self, name, flection):
100 self = split_tags(tags)
104 # Specjalny przypadek oglądania wszystkich lektur na danej półce
105 if len(self) == 1 and 'set' in self:
106 return u'Półka %s' % self['set']
108 # Specjalny przypadek "Twórczość w pozytywizmie", wtedy gdy tylko epoka
109 # jest wybrana przez użytkownika
110 if 'epoch' in self and len(self) == 1:
111 text = u'Twórczość w %s' % flection.get_case(unicode(self['epoch']), u'miejscownik')
112 return capfirst(text)
114 # Specjalny przypadek "Dramat w twórczości Sofoklesa", wtedy gdy podane
115 # są tylko rodzaj literacki i autor
116 if 'kind' in self and 'author' in self and len(self) == 2:
117 text = u'%s w twórczości %s' % (
118 unicode(self['kind']), flection.get_case(unicode(self['author']), u'dopełniacz'))
119 return capfirst(text)
121 # Przypadki ogólniejsze
123 title += u'Motyw %s' % unicode(self['theme'])
127 title += u' w %s' % flection.get_case(unicode(self['genre']), u'miejscownik')
129 title += unicode(self['genre'])
131 if 'kind' in self or 'author' in self or 'epoch' in self:
132 if 'genre' in self or 'theme' in self:
134 title += u' w %s ' % flection.get_case(unicode(self['kind']), u'miejscownik')
136 title += u' w twórczości '
138 title += u'%s ' % unicode(self.get('kind', u'twórczość'))
141 title += flection.get_case(unicode(self['author']), u'dopełniacz')
142 elif 'epoch' in self:
143 title += flection.get_case(unicode(self['epoch']), u'dopełniacz')
145 return capfirst(title)
149 def book_tree(book_list, books_by_parent):
150 text = "".join("<li><a href='%s'>%s</a>%s</li>" % (
151 book.get_absolute_url(), book.title, book_tree(books_by_parent.get(book, ()), books_by_parent)
152 ) for book in book_list)
155 return "<ol>%s</ol>" % text
161 def audiobook_tree(book_list, books_by_parent):
162 text = "".join("<li><a class='open-player' href='%s'>%s</a>%s</li>" % (
163 reverse("book_player", args=[book.slug]), book.title,
164 audiobook_tree(books_by_parent.get(book, ()), books_by_parent)
165 ) for book in book_list)
168 return "<ol>%s</ol>" % text
174 def book_tree_texml(book_list, books_by_parent, depth=1):
176 <cmd name='hspace'><parm>%(depth)dem</parm></cmd>%(title)s
177 <spec cat='align' /><cmd name="note"><parm>%(audiences)s</parm></cmd>
178 <spec cat='align' /><cmd name="note"><parm>%(audiobook)s</parm></cmd>
184 "audiences": ", ".join(book.audiences_pl()),
185 "audiobook": "audiobook" if book.has_media('mp3') else "",
186 "children": book_tree_texml(books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
187 } for book in book_list)
191 def book_tree_csv(author, book_list, books_by_parent, depth=1, max_depth=3, delimeter="\t"):
192 def quote_if_necessary(s):
195 s.replace('"', '\\"')
200 return "".join("""%(author)s%(d)s%(preindent)s%(title)s%(d)s%(postindent)s%(audiences)s%(d)s%(audiobook)s
203 "preindent": delimeter * (depth - 1),
204 "postindent": delimeter * (max_depth - depth),
206 "author": quote_if_necessary(author.name),
207 "title": quote_if_necessary(book.title),
208 "audiences": ", ".join(book.audiences_pl()),
209 "audiobook": "audiobook" if book.has_media('mp3') else "",
210 "children": book_tree_csv(author, books_by_parent.get(book.id, ()), books_by_parent, depth + 1)
211 } for book in book_list)
215 def all_editors(extra_info):
217 if 'editors' in extra_info:
218 editors += extra_info['editors']
219 if 'technical_editors' in extra_info:
220 editors += extra_info['technical_editors']
221 # support for extra_info-s from librarian<1.2
222 if 'editor' in extra_info:
223 editors.append(extra_info['editor'])
224 if 'technical_editor' in extra_info:
225 editors.append(extra_info['technical_editor'])
227 ' '.join(p.strip() for p in person.rsplit(',', 1)[::-1])
228 for person in sorted(set(editors)))
232 def user_creation_form():
233 return RegistrationForm(prefix='registration').as_ul()
237 def authentication_form():
238 return LoginForm(prefix='login').as_ul()
242 def catalogue_url(parser, token):
243 bits = token.split_contents()
249 tags_to_remove.append(bit[1:])
251 tags_to_add.append(bit)
253 return CatalogueURLNode(bits[1], tags_to_add, tags_to_remove)
256 class CatalogueURLNode(Node):
257 def __init__(self, list_type, tags_to_add, tags_to_remove):
258 self.tags_to_add = [Variable(tag) for tag in tags_to_add]
259 self.tags_to_remove = [Variable(tag) for tag in tags_to_remove]
260 self.list_type_var = Variable(list_type)
262 def render(self, context):
263 list_type = self.list_type_var.resolve(context)
267 for tag_variable in self.tags_to_add:
268 tag = tag_variable.resolve(context)
269 if isinstance(tag, (list, dict)):
270 tags_to_add += [t for t in tag]
272 tags_to_add.append(tag)
274 for tag_variable in self.tags_to_remove:
275 tag = tag_variable.resolve(context)
277 tags_to_remove += [t for t in tag]
279 tags_to_remove.append(tag)
281 tag_slugs = [tag.url_chunk for tag in tags_to_add]
282 for tag in tags_to_remove:
284 tag_slugs.remove(tag.url_chunk)
288 if len(tag_slugs) > 0:
289 if list_type == 'gallery':
290 return reverse('tagged_object_list_gallery', kwargs={'tags': '/'.join(tag_slugs)})
291 elif list_type == 'audiobooks':
292 return reverse('tagged_object_list_audiobooks', kwargs={'tags': '/'.join(tag_slugs)})
294 return reverse('tagged_object_list', kwargs={'tags': '/'.join(tag_slugs)})
296 if list_type == 'gallery':
297 return reverse('gallery')
298 elif list_type == 'audiobooks':
299 return reverse('audiobook_list')
301 return reverse('book_list')
304 # @register.inclusion_tag('catalogue/tag_list.html')
305 def tag_list(tags, choices=None, category=None, list_type='default'):
306 # print(tags, choices, category)
310 if category is None and tags:
311 category = tags[0].category
313 category_choices = [tag for tag in choices if tag.category == category]
315 if len(tags) == 1 and category not in [t.category for t in choices]:
320 if category is not None:
321 other = Tag.objects.filter(category=category).exclude(pk__in=[t.pk for t in tags])\
322 .exclude(pk__in=[t.pk for t in category_choices])
323 # Filter out empty tags.
324 ct = ContentType.objects.get_for_model(Picture if list_type == 'gallery' else Book)
325 other = other.filter(items__content_type=ct).distinct()
326 if list_type == 'audiobooks':
327 other = other.filter(id__in=get_audiobook_tags())
334 'category_choices': category_choices,
337 'list_type': list_type,
341 @register.inclusion_tag('catalogue/inline_tag_list.html')
342 def inline_tag_list(tags, choices=None, category=None, list_type='default'):
343 return tag_list(tags, choices, category, list_type)
346 @register.inclusion_tag('catalogue/collection_list.html')
347 def collection_list(collections):
348 return {'collections': collections}
351 @register.inclusion_tag('catalogue/book_info.html')
354 'is_picture': isinstance(book, Picture),
359 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
360 def work_list(context, object_list):
361 request = context.get('request')
362 return {'object_list': object_list, 'request': request}
365 @register.inclusion_tag('catalogue/plain_list.html', takes_context=True)
366 def plain_list(context, object_list, with_initials=True, by_author=False, choice=None, book=None, list_type='default',
367 paged=True, initial_blocks=False):
370 if len(object_list) < settings.CATALOGUE_MIN_INITIALS and not by_author:
371 with_initials = False
372 initial_blocks = False
373 for obj in object_list:
376 initial = obj.sort_key_author
378 initial = obj.get_initial().upper()
379 if initial != last_initial:
380 last_initial = initial
381 names.append((obj.author_str() if by_author else initial, []))
382 names[-1][1].append(obj)
386 'initial_blocks': initial_blocks,
388 'list_type': list_type,
393 # TODO: These are no longer just books.
394 @register.inclusion_tag('catalogue/related_books.html', takes_context=True)
395 def related_books(context, instance, limit=6, random=1, taken=0):
397 max_books = limit - random
398 is_picture = isinstance(instance, Picture)
400 pics_qs = Picture.objects.all()
402 pics_qs = pics_qs.exclude(pk=instance.pk)
403 pics = Picture.tagged.related_to(instance, pics_qs)
405 # Reserve one spot for an image.
408 books_qs = Book.objects.all()
410 books_qs = books_qs.exclude(common_slug=instance.common_slug).exclude(ancestor=instance)
411 books = Book.tagged.related_to(instance, books_qs)[:max_books]
413 pics = pics[:1 + max_books - books.count()]
415 random_excluded_books = [b.pk for b in books]
416 random_excluded_pics = [p.pk for p in pics]
417 (random_excluded_pics if is_picture else random_excluded_books).append(instance.pk)
420 'request': context['request'],
424 'random_excluded_books': random_excluded_books,
425 'random_excluded_pics': random_excluded_pics,
430 def download_audio(book, daisy=True):
432 if book.has_media('mp3'):
433 links.append("<a href='%s'>%s</a>" % (
434 reverse('download_zip_mp3', args=[book.slug]), BookMedia.formats['mp3'].name))
435 if book.has_media('ogg'):
436 links.append("<a href='%s'>%s</a>" % (
437 reverse('download_zip_ogg', args=[book.slug]), BookMedia.formats['ogg'].name))
438 if daisy and book.has_media('daisy'):
439 for dsy in book.get_media('daisy'):
440 links.append("<a href='%s'>%s</a>" % (dsy.file.url, BookMedia.formats['daisy'].name))
441 return "".join(links)
444 @register.inclusion_tag("catalogue/snippets/custom_pdf_link_li.html")
445 def custom_pdf_link_li(book):
448 'NO_CUSTOM_PDF': settings.NO_CUSTOM_PDF,
452 @register.inclusion_tag("catalogue/snippets/license_icon.html")
453 def license_icon(license_url):
454 """Creates a license icon, if the license_url is known."""
455 known = LICENSES.get(license_url)
459 "license_url": license_url,
460 "icon": "img/licenses/%s.png" % known['icon'],
461 "license_description": known['description'],
467 return obj.__class__.__name__
471 def source_name(url):
473 netloc = urlparse(url).netloc
475 netloc = urlparse('http://' + url).netloc
478 source, created = Source.objects.get_or_create(netloc=netloc)
479 return source.name or netloc
482 @ssi_variable(register, patch_response=[add_never_cache_headers])
483 def catalogue_random_book(request, exclude_ids):
484 from .. import app_settings
485 if random() < app_settings.RELATED_RANDOM_PICTURE_CHANCE:
487 queryset = Book.objects.exclude(pk__in=exclude_ids)
488 count = queryset.count()
490 return queryset[randint(0, count - 1)].pk
495 @ssi_variable(register, patch_response=[add_never_cache_headers])
496 def choose_fragment(request, book_id=None, tag_ids=None, unless=False):
500 if book_id is not None:
501 fragment = Book.objects.get(pk=book_id).choose_fragment()
503 if tag_ids is not None:
504 tags = Tag.objects.filter(pk__in=tag_ids)
505 fragments = Fragment.tagged.with_all(tags).order_by().only('id')
507 fragments = Fragment.objects.all().order_by().only('id')
508 fragment_count = fragments.count()
509 fragment = fragments[randint(0, fragment_count - 1)] if fragment_count else None
510 return fragment.pk if fragment is not None else None
514 def strip_tag(html, tag_name):
515 # docelowo może być warto zainstalować BeautifulSoup do takich rzeczy
517 return re.sub(r"<.?%s\b[^>]*>" % tag_name, "", html)