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='books'):
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())
328 other = other.only('name', 'slug', 'category')
335 'category_choices': category_choices,
338 'list_type': list_type,
342 @register.inclusion_tag('catalogue/inline_tag_list.html')
343 def inline_tag_list(tags, choices=None, category=None, list_type='books'):
344 return tag_list(tags, choices, category, list_type)
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, list_type='books',
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_unicode() if by_author else initial, []))
383 names[-1][1].append(obj)
387 'initial_blocks': initial_blocks,
389 'list_type': list_type,
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)