From: Radek Czajka Date: Fri, 13 Jan 2012 16:05:36 +0000 (+0100) Subject: Merge branch 'production' into pretty X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/73ce961f14509aabfa26536f847afd28111029c6?hp=764fcc72600a23d5093511228dee596b55c45c72 Merge branch 'production' into pretty Conflicts: apps/catalogue/models.py wolnelektury/settings.py wolnelektury/templates/catalogue/main_page.html wolnelektury/urls.py --- diff --git a/.gitignore b/.gitignore index c37544128..230a87670 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ thumbs.db # Tags file TAGS + +media diff --git a/apps/ajaxable/__init__.py b/apps/ajaxable/__init__.py new file mode 100644 index 000000000..a0105436b --- /dev/null +++ b/apps/ajaxable/__init__.py @@ -0,0 +1,4 @@ +""" +Provides a way to create forms behaving correctly as AJAX forms +as well as standalone forms without any Javascript. +""" diff --git a/apps/ajaxable/models.py b/apps/ajaxable/models.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ajaxable/templates/ajaxable/form.html b/apps/ajaxable/templates/ajaxable/form.html new file mode 100755 index 000000000..ba79e4b69 --- /dev/null +++ b/apps/ajaxable/templates/ajaxable/form.html @@ -0,0 +1,10 @@ +{% load i18n %} +

{{ title }}

+ +
+
    +
    + {{ form.as_ul }} +
  1. +
+
\ No newline at end of file diff --git a/apps/ajaxable/templates/ajaxable/form_on_page.html b/apps/ajaxable/templates/ajaxable/form_on_page.html new file mode 100755 index 000000000..61175d507 --- /dev/null +++ b/apps/ajaxable/templates/ajaxable/form_on_page.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block titleextra %}{{ title }}{% endblock %} + +{% block body %} + + {% include ajax_template %} + + {% if response_data.message %} +

{{ response_data.message }}

+ {% endif %} + +{% endblock %} diff --git a/apps/ajaxable/utils.py b/apps/ajaxable/utils.py new file mode 100755 index 000000000..d6f70501d --- /dev/null +++ b/apps/ajaxable/utils.py @@ -0,0 +1,82 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.utils.encoding import force_unicode +from django.utils.functional import Promise +from django.utils.http import urlquote_plus +from django.utils import simplejson +from django.utils.translation import ugettext_lazy as _ + + +class LazyEncoder(simplejson.JSONEncoder): + def default(self, obj): + if isinstance(obj, Promise): + return force_unicode(obj) + return obj + +# shortcut for JSON reponses +class JSONResponse(HttpResponse): + def __init__(self, data={}, callback=None, **kwargs): + # get rid of mimetype + kwargs.pop('mimetype', None) + data = simplejson.dumps(data) + if callback: + data = callback + "(" + data + ");" + super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs) + + + +class AjaxableFormView(object): + """Subclass this to create an ajaxable view for any form. + + In the subclass, provide at least form_class. + + """ + form_class = None + # override to customize form look + template = "ajaxable/form.html" + submit = _('Send') + + title = '' + success_message = '' + formname = "form" + full_template = "ajaxable/form_on_page.html" + + def __call__(self, request): + """A view displaying a form, or JSON if `ajax' GET param is set.""" + ajax = request.GET.get('ajax', False) + if request.method == "POST": + form = self.form_class(data=request.POST) + if form.is_valid(): + self.success(form, request) + redirect = request.GET.get('next') + if not ajax and redirect: + return HttpResponseRedirect(urlquote_plus( + redirect, safe='/?=&')) + response_data = {'success': True, + 'message': self.success_message, 'redirect': redirect} + else: + response_data = {'success': False, 'errors': form.errors} + if ajax: + return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) + else: + form = self.form_class() + response_data = None + + template = self.template if ajax else self.full_template + return render_to_response(template, { + self.formname: form, + "title": self.title, + "submit": self.submit, + "response_data": response_data, + "ajax_template": self.template, + }, + context_instance=RequestContext(request)) + + def success(self, form, request): + """What to do when the form is valid. + + By default, just save the form. + + """ + return form.save(request) diff --git a/apps/api/handlers.py b/apps/api/handlers.py index eb15a4edd..fddff7407 100644 --- a/apps/api/handlers.py +++ b/apps/api/handlers.py @@ -16,6 +16,8 @@ from api.helpers import timestamp from api.models import Deleted from catalogue.forms import BookImportForm from catalogue.models import Book, Tag, BookMedia, Fragment +from picture.models import Picture +from picture.forms import PictureImportForm from stats.utils import piwik_track @@ -94,13 +96,12 @@ class BookDetailHandler(BaseHandler): """ allowed_methods = ['GET'] - fields = ['title', 'parent'] + Book.file_types + [ + fields = ['title', 'parent'] + Book.formats + [ 'media', 'url'] + category_singular.keys() @piwik_track def read(self, request, slug): - """ Returns details of a book, identified by a slug. """ - + """ Returns details of a book, identified by a slug and lang. """ try: return Book.objects.get(slug=slug) except Book.DoesNotExist: @@ -203,7 +204,7 @@ def _file_getter(format): else: return '' return get_file -for format in Book.file_types: +for format in Book.formats: setattr(BooksHandler, format, _file_getter(format)) @@ -247,9 +248,8 @@ class TagsHandler(BaseHandler): except KeyError, e: return rc.NOT_FOUND - tags = Tag.objects.filter(category=category_sng) - tags = [t for t in tags if t.get_count() > 0] - if tags: + tags = Tag.objects.filter(category=category_sng).exclude(book_count=0) + if tags.exists(): return tags else: return rc.NOT_FOUND @@ -268,7 +268,6 @@ class FragmentDetailHandler(BaseHandler): @piwik_track def read(self, request, slug, anchor): """ Returns details of a fragment, identified by book slug and anchor. """ - try: return Fragment.objects.get(book__slug=slug, anchor=anchor) except Fragment.DoesNotExist: @@ -307,7 +306,8 @@ class FragmentsHandler(BaseHandler): def href(cls, fragment): """ Returns URI in the API for the fragment. """ - return API_BASE + reverse("api_fragment", args=[fragment.book.slug, fragment.anchor]) + return API_BASE + reverse("api_fragment", + args=[fragment.book.slug, fragment.anchor]) @classmethod def url(cls, fragment): @@ -355,8 +355,7 @@ class CatalogueHandler(BaseHandler): def book_dict(book, fields=None): all_fields = ['url', 'title', 'description', 'gazeta_link', 'wiki_link', - ] + Book.file_types + [ - 'mp3', 'ogg', 'daisy', + ] + Book.formats + BookMedia.formats + [ 'parent', 'parent_number', 'tags', 'license', 'license_description', 'source_name', @@ -373,7 +372,7 @@ class CatalogueHandler(BaseHandler): obj = {} for field in fields: - if field in Book.file_types: + if field in Book.formats: f = getattr(book, field+'_file') if f: obj[field] = { @@ -381,7 +380,7 @@ class CatalogueHandler(BaseHandler): 'size': f.size, } - elif field in ('mp3', 'ogg', 'daisy'): + elif field in BookMedia.formats: media = [] for m in book.media.filter(type=field): media.append({ @@ -510,7 +509,7 @@ class CatalogueHandler(BaseHandler): changed_at__gte=since, changed_at__lt=until): # only serve non-empty tags - if tag.get_count(): + if tag.book_count: tag_d = cls.tag_dict(tag, fields) updated.append(tag_d) elif tag.created_at < since: @@ -587,3 +586,21 @@ class ChangesHandler(CatalogueHandler): @piwik_track def read(self, request, since): return self.changes(request, since) + + +class PictureHandler(BaseHandler): + model = Picture + fields = ('slug', 'title') + allowed_methods = ('POST',) + + def create(self, request): + if not request.user.has_perm('picture.add_picture'): + return rc.FORBIDDEN + + data = json.loads(request.POST.get('data')) + form = PictureImportForm(data) + if form.is_valid(): + form.save() + return rc.CREATED + else: + return rc.NOT_FOUND diff --git a/apps/api/management/commands/mobileinit.py b/apps/api/management/commands/mobileinit.py index 658b1770d..2abbfb3d3 100755 --- a/apps/api/management/commands/mobileinit.py +++ b/apps/api/management/commands/mobileinit.py @@ -23,10 +23,10 @@ class Command(BaseCommand): db = init_db(last_checked) for b in Book.objects.all(): add_book(db, b) - for t in Tag.objects.exclude(category__in=('book', 'set', 'theme')): + for t in Tag.objects.exclude( + category__in=('book', 'set', 'theme')).exclude(book_count=0): # only add non-empty tags - if t.get_count(): - add_tag(db, t) + add_tag(db, t) db.commit() db.close() current(last_checked) diff --git a/apps/api/tests.py b/apps/api/tests.py index 2c2e51ce8..5a981a26a 100644 --- a/apps/api/tests.py +++ b/apps/api/tests.py @@ -8,6 +8,14 @@ from django.conf import settings from api.helpers import timestamp from catalogue.models import Book, Tag +from picture.tests.utils import RequestFactory +from picture.forms import PictureImportForm +from picture.models import Picture, picture_storage +import picture.tests +from django.core.files.uploadedfile import SimpleUploadedFile + + +from os import path class ApiTest(TestCase): @@ -135,3 +143,22 @@ class TagTests(TestCase): tag = json.loads(self.client.get('/api/authors/joe/').content) self.assertEqual(tag['name'], self.tag.name, 'Wrong tag details.') + + +class PictureTests(ApiTest): + def test_publish(self): + slug = "kandinsky-composition-viii" + xml = SimpleUploadedFile('composition8.xml', open(path.join(picture.tests.__path__[0], "files", slug + ".xml")).read()) + img = SimpleUploadedFile('kompozycja-8.png', open(path.join(picture.tests.__path__[0], "files", slug + ".png")).read()) + + import_form = PictureImportForm({}, { + 'picture_xml_file': xml, + 'picture_image_file': img + }) + + assert import_form.is_valid() + if import_form.is_valid(): + import_form.save() + + pic = Picture.objects.get(slug=slug) + diff --git a/apps/api/urls.py b/apps/api/urls.py index 2d92eba46..fd97d63d9 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -4,7 +4,7 @@ from piston.authentication import OAuthAuthentication from piston.resource import Resource from api import handlers - +from catalogue.models import Book auth = OAuthAuthentication(realm="Wolne Lektury") @@ -22,6 +22,7 @@ tag_resource = Resource(handler=handlers.TagDetailHandler) fragment_resource = Resource(handler=handlers.FragmentDetailHandler) fragment_list_resource = Resource(handler=handlers.FragmentsHandler) +picture_resource = Resource(handler=handlers.PictureHandler, authentication=auth) urlpatterns = patterns( 'piston.authentication', @@ -46,16 +47,18 @@ urlpatterns = patterns( # objects details - url(r'^books/(?P[a-z0-9-]+)/$', book_resource, name="api_book"), + url(r'^books/(?P[a-z0-9-]+)/$', book_resource, name="api_book"), url(r'^(?P[a-z0-9-]+)/(?P[a-z0-9-]+)/$', tag_resource, name="api_tag"), - url(r'^books/(?P[a-z0-9-]+)/fragments/(?P[a-z0-9-]+)/$', + url(r'^books/(?P[a-z0-9-]+)/fragments/(?P[a-z0-9-]+)/$', fragment_resource, name="api_fragment"), # books by tags url(r'^(?P(?:(?:[a-z0-9-]+/){2}){0,6})books/$', book_list_resource), url(r'^(?P(?:(?:[a-z0-9-]+/){2}){0,6})parent_books/$', book_list_resource, {"top_level": True}), + url(r'^pictures/$', picture_resource), + # fragments by book, tags, themes # this should be paged url(r'^(?P(?:(?:[a-z0-9-]+/){2}){1,6})fragments/$', fragment_list_resource), diff --git a/apps/catalogue/admin.py b/apps/catalogue/admin.py index 61db42fe6..87ab727e1 100644 --- a/apps/catalogue/admin.py +++ b/apps/catalogue/admin.py @@ -10,7 +10,7 @@ from catalogue.models import Tag, Book, Fragment, BookMedia, Collection class TagAdmin(admin.ModelAdmin): - list_display = ('name', 'slug', 'sort_key', 'category', 'has_description', 'main_page',) + list_display = ('name', 'slug', 'sort_key', 'category', 'has_description',) list_filter = ('category',) search_fields = ('name',) ordering = ('name',) diff --git a/apps/catalogue/fields.py b/apps/catalogue/fields.py index 048824498..e19df9d37 100644 --- a/apps/catalogue/fields.py +++ b/apps/catalogue/fields.py @@ -73,20 +73,12 @@ class JSONField(models.TextField): class JQueryAutoCompleteWidget(forms.TextInput): - def __init__(self, source, options=None, *args, **kwargs): - self.source = source - self.options = None - if options: - self.options = dumps(options) + def __init__(self, options, *args, **kwargs): + self.options = dumps(options) super(JQueryAutoCompleteWidget, self).__init__(*args, **kwargs) - def render_js(self, field_id): - source = "'%s'" % escape(self.source) - options = '' - if self.options: - options += ', %s' % self.options - - return u'$(\'#%s\').autocomplete(%s%s).result(autocomplete_result_handler);' % (field_id, source, options) + def render_js(self, field_id, options): + return u'$(\'#%s\').autocomplete(%s).result(autocomplete_result_handler);' % (field_id, options) def render(self, name, value=None, attrs=None): final_attrs = self.build_attrs(attrs, name=name) @@ -100,21 +92,38 @@ class JQueryAutoCompleteWidget(forms.TextInput): ''' % { - 'attrs' : flatatt(final_attrs), - 'js' : self.render_js(final_attrs['id']), + 'attrs': flatatt(final_attrs), + 'js' : self.render_js(final_attrs['id'], self.options), } return mark_safe(html) +class JQueryAutoCompleteSearchWidget(JQueryAutoCompleteWidget): + def __init__(self, *args, **kwargs): + super(JQueryAutoCompleteSearchWidget, self).__init__(*args, **kwargs) + + def render_js(self, field_id, options): + return u"" + + class JQueryAutoCompleteField(forms.CharField): - def __init__(self, source, options=None, *args, **kwargs): + def __init__(self, source, options={}, *args, **kwargs): if 'widget' not in kwargs: - kwargs['widget'] = JQueryAutoCompleteWidget(source, options) + options['source'] = source + kwargs['widget'] = JQueryAutoCompleteWidget(options) super(JQueryAutoCompleteField, self).__init__(*args, **kwargs) +class JQueryAutoCompleteSearchField(forms.CharField): + def __init__(self, options={}, *args, **kwargs): + if 'widget' not in kwargs: + kwargs['widget'] = JQueryAutoCompleteSearchWidget(options) + + super(JQueryAutoCompleteSearchField, self).__init__(*args, **kwargs) + + class OverwritingFieldFile(FieldFile): """ Deletes the old file before saving the new one. diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index 391e3e429..655f1eccd 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _ from slughifi import slughifi from catalogue.models import Tag, Book -from catalogue.fields import JQueryAutoCompleteField +from catalogue.fields import JQueryAutoCompleteSearchField from catalogue import utils @@ -31,15 +31,16 @@ class BookImportForm(forms.Form): class SearchForm(forms.Form): - q = JQueryAutoCompleteField('/katalog/tags/', {'minChars': 2, 'selectFirst': True, 'cacheLength': 50, 'matchContains': "word"}) - tags = forms.CharField(widget=forms.HiddenInput, required=False) + q = JQueryAutoCompleteSearchField() # {'minChars': 2, 'selectFirst': True, 'cacheLength': 50, 'matchContains': "word"}) - def __init__(self, *args, **kwargs): - tags = kwargs.pop('tags', []) + def __init__(self, source, *args, **kwargs): + kwargs['auto_id'] = False super(SearchForm, self).__init__(*args, **kwargs) - self.fields['q'].widget.attrs['title'] = _('title, author, theme/topic, epoch, kind, genre') - #self.fields['q'].widget.attrs['style'] = '' - self.fields['tags'].initial = '/'.join(tag.url_chunk for tag in Tag.get_tag_list(tags)) + self.fields['q'].widget.attrs['id'] = _('search') + self.fields['q'].widget.attrs['autocomplete'] = _('off') + self.fields['q'].widget.attrs['data-source'] = _(source) + if not 'q' in self.data: + self.fields['q'].widget.attrs['title'] = _('title, author, theme/topic, epoch, kind, genre, phrase') class UserSetsForm(forms.Form): @@ -56,7 +57,7 @@ class ObjectSetsForm(forms.Form): self.fields['set_ids'] = forms.MultipleChoiceField( label=_('Shelves'), required=False, - choices=[(tag.id, "%s (%s)" % (tag.name, tag.get_count())) for tag in Tag.objects.filter(category='set', user=user)], + choices=[(tag.id, "%s (%s)" % (tag.name, tag.book_count)) for tag in Tag.objects.filter(category='set', user=user)], initial=[tag.id for tag in obj.tags.filter(category='set', user=user)], widget=forms.CheckboxSelectMultiple ) @@ -78,21 +79,62 @@ class NewSetForm(forms.Form): return new_set -FORMATS = ( - ('mp3', 'MP3'), - ('ogg', 'OGG'), - ('pdf', 'PDF'), - ('odt', 'ODT'), - ('txt', 'TXT'), - ('epub', 'EPUB'), - ('daisy', 'DAISY'), - ('mobi', 'MOBI'), -) +FORMATS = [(f, f.upper()) for f in Book.ebook_formats] class DownloadFormatsForm(forms.Form): - formats = forms.MultipleChoiceField(required=False, choices=FORMATS, widget=forms.CheckboxSelectMultiple) + formats = forms.MultipleChoiceField(required=False, choices=FORMATS, + widget=forms.CheckboxSelectMultiple) def __init__(self, *args, **kwargs): super(DownloadFormatsForm, self).__init__(*args, **kwargs) + +PDF_PAGE_SIZES = ( + ('a4paper', _('A4')), + ('a5paper', _('A5')), +) + + +PDF_LEADINGS = ( + ('', _('Normal leading')), + ('onehalfleading', _('One and a half leading')), + ('doubleleading', _('Double leading')), + ) + +PDF_FONT_SIZES = ( + ('11pt', _('Default')), + ('13pt', _('Big')) + ) + + +class CustomPDFForm(forms.Form): + nofootnotes = forms.BooleanField(required=False, label=_("Don't show footnotes")) + nothemes = forms.BooleanField(required=False, label=_("Don't disply themes")) + nowlfont = forms.BooleanField(required=False, label=_("Don't use our custom font")) + ## pagesize = forms.ChoiceField(PDF_PAGE_SIZES, required=True, label=_("Paper size")) + leading = forms.ChoiceField(PDF_LEADINGS, required=False, label=_("Leading")) + fontsize = forms.ChoiceField(PDF_FONT_SIZES, required=True, label=_("Font size")) + + @property + def customizations(self): + c = [] + if self.cleaned_data['nofootnotes']: + c.append('nofootnotes') + + if self.cleaned_data['nothemes']: + c.append('nothemes') + + if self.cleaned_data['nowlfont']: + c.append('nowlfont') + + ## c.append(self.cleaned_data['pagesize']) + c.append(self.cleaned_data['fontsize']) + + if self.cleaned_data['leading']: + c.append(self.cleaned_data['leading']) + + c.sort() + + return c + diff --git a/apps/catalogue/import_utils.py b/apps/catalogue/import_utils.py new file mode 100755 index 000000000..bf36ea57f --- /dev/null +++ b/apps/catalogue/import_utils.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from librarian import DocProvider + +class ORMDocProvider(DocProvider): + """Used for getting books' children.""" + + def __init__(self, book): + self.book = book + + def by_slug(self, slug): + if slug == self.book.slug: + return open(self.book.xml_file.path) + else: + return type(self.book).objects.get(slug=slug).xml_file \ No newline at end of file diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo index 6a4a5590b..31849200b 100644 Binary files a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo and b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo differ diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.po b/apps/catalogue/locale/pl/LC_MESSAGES/django.po index 648a1ab33..ca4f243f8 100644 --- a/apps/catalogue/locale/pl/LC_MESSAGES/django.po +++ b/apps/catalogue/locale/pl/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-01-10 13:04+0100\n" -"PO-Revision-Date: 2012-01-10 13:05+0100\n" +"POT-Creation-Date: 2011-10-11 15:44+0200\n" +"PO-Revision-Date: 2011-10-11 15:44+0100\n" "Last-Translator: Radek Czajka \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -22,226 +22,218 @@ msgstr "" msgid "Enter a valid JSON value. Error: %s" msgstr "Wprowadź prawidłową wartość JSON. Błąd: %s" -#: forms.py:26 +#: forms.py:25 msgid "Please supply an XML." msgstr "Proszę podać XML." -#: forms.py:40 +#: forms.py:39 msgid "title, author, theme/topic, epoch, kind, genre" msgstr "tytuł, autor, motyw/temat, epoka, rodzaj, gatunek" -#: forms.py:57 +#: forms.py:56 msgid "Shelves" msgstr "Półki" -#: forms.py:70 +#: forms.py:69 msgid "Name of the new shelf" msgstr "nazwa nowej półki" -#: models.py:29 +#: models.py:35 msgid "author" msgstr "autor" -#: models.py:30 +#: models.py:36 msgid "epoch" msgstr "epoka" -#: models.py:31 +#: models.py:37 msgid "kind" msgstr "rodzaj" -#: models.py:32 +#: models.py:38 msgid "genre" msgstr "gatunek" -#: models.py:33 +#: models.py:39 msgid "theme" msgstr "motyw" -#: models.py:34 +#: models.py:40 msgid "set" msgstr "półka" -#: models.py:35 -#: models.py:319 +#: models.py:41 models.py:314 msgid "book" msgstr "książka" -#: models.py:39 +#: models.py:45 msgid "ODT file" msgstr "Plik ODT" -#: models.py:40 +#: models.py:46 msgid "MP3 file" msgstr "Plik MP3" -#: models.py:41 +#: models.py:47 msgid "OGG file" msgstr "Plik OGG" -#: models.py:42 +#: models.py:48 msgid "DAISY file" msgstr "Plik DAISY" -#: models.py:59 -#: models.py:194 +#: models.py:65 models.py:198 msgid "name" msgstr "nazwa" -#: models.py:60 -#: models.py:294 -#: models.py:951 -#: models.py:968 -#: models.py:971 +#: models.py:66 models.py:287 models.py:876 msgid "slug" msgstr "slug" -#: models.py:61 -#: models.py:293 +#: models.py:67 models.py:286 msgid "sort key" msgstr "klucz sortowania" -#: models.py:62 +#: models.py:68 msgid "category" msgstr "kategoria" -#: models.py:64 -#: models.py:106 -#: models.py:295 -#: models.py:444 -#: models.py:969 +#: models.py:70 models.py:112 models.py:288 models.py:469 msgid "description" msgstr "opis" -#: models.py:68 +#: models.py:71 +msgid "main page" +msgstr "strona główna" + +#: models.py:71 +msgid "Show tag on main page" +msgstr "Pokazuj tag na stronie głównej" + +#: models.py:74 msgid "book count" msgstr "liczba książek" -#: models.py:72 -#: models.py:73 -#: models.py:196 -#: models.py:296 -#: models.py:297 +#: models.py:78 models.py:79 models.py:200 models.py:289 models.py:290 msgid "creation date" msgstr "data utworzenia" -#: models.py:90 +#: models.py:96 msgid "tag" msgstr "tag" -#: models.py:91 +#: models.py:97 msgid "tags" msgstr "tagi" -#: models.py:193 -#: models.py:952 +#: models.py:197 models.py:877 msgid "type" msgstr "typ" -#: models.py:195 +#: models.py:199 msgid "file" msgstr "plik" -#: models.py:197 -#: models.py:299 +#: models.py:201 models.py:292 msgid "extra information" msgstr "dodatkowe informacje" -#: models.py:206 -#: models.py:207 +#: models.py:210 models.py:211 msgid "book media" msgstr "media książki" -#: models.py:292 -#: models.py:967 +#: models.py:285 msgid "title" msgstr "tytuł" -#: models.py:298 +#: models.py:291 msgid "parent number" msgstr "numer rodzica" -#: models.py:320 +#: models.py:296 +msgid "XML file" +msgstr "Plik XML" + +#: models.py:297 +msgid "HTML file" +msgstr "Plik HTML" + +#: models.py:298 +msgid "PDF file" +msgstr "Plik PDF" + +#: models.py:299 +msgid "EPUB file" +msgstr "Plik EPUB" + +#: models.py:300 +msgid "TXT file" +msgstr "Plik TXT" + +#: models.py:315 msgid "books" msgstr "książki" -#: models.py:408 +#: models.py:435 msgid "Read online" msgstr "Czytaj online" -#: models.py:672 +#: models.py:656 #, python-format msgid "Book with slug = \"%s\" does not exist." msgstr "Książki ο slug = \"%s\" nie istnieje." -#: models.py:685 +#: models.py:669 #, python-format msgid "Book %s already exists" msgstr "Książka %s już istnieje" -#: models.py:900 -#, python-format -msgid "%s file" -msgstr "plik %s" - -#: models.py:919 +#: models.py:844 msgid "fragment" msgstr "fragment" -#: models.py:920 +#: models.py:845 msgid "fragments" msgstr "fragmenty" -#: models.py:953 +#: models.py:878 msgid "sha-1 hash" msgstr "hash sha-1" -#: models.py:954 +#: models.py:879 msgid "time" msgstr "czas" -#: models.py:958 +#: models.py:883 msgid "file record" msgstr "" -#: models.py:959 +#: models.py:884 msgid "file records" msgstr "" -#: models.py:972 -msgid "book slugs" -msgstr "slugi książek" - -#: models.py:976 -msgid "collection" -msgstr "kolekcja" - -#: models.py:977 -msgid "collections" -msgstr "kolekcje" - -#: views.py:509 +#: views.py:531 msgid "

To maintain your shelves you need to be logged in.

" msgstr "

Aby zarządzać swoimi półkami, musisz się zalogować.

" -#: views.py:531 +#: views.py:553 msgid "

Shelves were sucessfully saved.

" msgstr "

Półki zostały zapisane.

" -#: views.py:555 +#: views.py:577 msgid "Book was successfully removed from the shelf" msgstr "Usunięto" -#: views.py:557 +#: views.py:579 msgid "This book is not on the shelf" msgstr "Książki nie ma na półce" -#: views.py:676 +#: views.py:689 #, python-format msgid "

Shelf %s was successfully removed

" msgstr "

Półka %s została usunięta

" -#: views.py:738 +#: views.py:748 #, python-format msgid "" "An error occurred: %(exception)s\n" @@ -252,36 +244,15 @@ msgstr "" "\n" "%(tb)s" -#: views.py:739 +#: views.py:749 msgid "Book imported successfully" msgstr "Książka zaimportowana" -#: views.py:741 +#: views.py:751 #, python-format msgid "Error importing file: %r" msgstr "Błąd podczas importowania pliku: %r" -#~ msgid "main page" -#~ msgstr "strona główna" - -#~ msgid "Show tag on main page" -#~ msgstr "Pokazuj tag na stronie głównej" - -#~ msgid "XML file" -#~ msgstr "Plik XML" - -#~ msgid "HTML file" -#~ msgstr "Plik HTML" - -#~ msgid "PDF file" -#~ msgstr "Plik PDF" - -#~ msgid "EPUB file" -#~ msgstr "Plik EPUB" - -#~ msgid "TXT file" -#~ msgstr "Plik TXT" - #, fuzzy #~ msgid "sort_key" #~ msgstr "klucz sortowania" @@ -304,5 +275,8 @@ msgstr "Błąd podczas importowania pliku: %r" #~ msgid "book stub" #~ msgstr "zapowiedź książki" +#~ msgid "book stubs" +#~ msgstr "zapowiedzi książek" + #~ msgid "

Shelf %s was successfully created

" #~ msgstr "

Półka %s została utworzona

" diff --git a/apps/catalogue/management/commands/__init__.py b/apps/catalogue/management/commands/__init__.py index e69de29bb..8d1c8b69c 100644 --- a/apps/catalogue/management/commands/__init__.py +++ b/apps/catalogue/management/commands/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/catalogue/management/commands/importbooks.py b/apps/catalogue/management/commands/importbooks.py index ecd3fcc97..5b4d499a4 100644 --- a/apps/catalogue/management/commands/importbooks.py +++ b/apps/catalogue/management/commands/importbooks.py @@ -12,6 +12,7 @@ from django.core.management.color import color_style from django.core.files import File from catalogue.models import Book +from picture.models import Picture class Command(BaseCommand): @@ -28,12 +29,39 @@ class Command(BaseCommand): help='Don\'t build TXT file'), make_option('-P', '--no-build-pdf', action='store_false', dest='build_pdf', default=True, help='Don\'t build PDF file'), + make_option('-S', '--no-search-index', action='store_false', dest='search_index', default=True, + help='Don\'t build PDF file'), make_option('-w', '--wait-until', dest='wait_until', metavar='TIME', help='Wait until specified time (Y-M-D h:m:s)'), + make_option('-p', '--picture', action='store_true', dest='import_picture', default=False, + help='Import pictures'), ) help = 'Imports books from the specified directories.' args = 'directory [directory ...]' + def import_book(self, file_path, options): + verbose = options.get('verbose') + file_base, ext = os.path.splitext(file_path) + book = Book.from_xml_file(file_path, overwrite=options.get('force'), + build_epub=options.get('build_epub'), + build_txt=options.get('build_txt'), + build_pdf=options.get('build_pdf'), + build_mobi=options.get('build_mobi'), + search_index=options.get('search_index')) + for ebook_format in Book.ebook_formats: + if os.path.isfile(file_base + '.' + ebook_format): + getattr(book, '%s_file' % ebook_format).save( + '%s.%s' % (book.slug, ebook_format), + File(file(file_base + '.' + ebook_format))) + if verbose: + print "Importing %s.%s" % (file_base, ebook_format) + + book.save() + + def import_picture(self, file_path, options): + picture = Picture.from_xml_file(file_path, overwrite=options.get('force')) + return picture + def handle(self, *directories, **options): from django.db import transaction @@ -42,13 +70,14 @@ class Command(BaseCommand): verbose = options.get('verbose') force = options.get('force') show_traceback = options.get('traceback', False) + import_picture = options.get('import_picture') wait_until = None if options.get('wait_until'): wait_until = time.mktime(time.strptime(options.get('wait_until'), '%Y-%m-%d %H:%M:%S')) if verbose > 0: print "Will wait until %s; it's %f seconds from now" % ( - time.strftime('%Y-%m-%d %H:%M:%S', + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(wait_until)), wait_until - time.time()) # Start transaction management. @@ -83,34 +112,14 @@ class Command(BaseCommand): # Import book files try: - book = Book.from_xml_file(file_path, overwrite=force, - build_epub=options.get('build_epub'), - build_txt=options.get('build_txt'), - build_pdf=options.get('build_pdf'), - build_mobi=options.get('build_mobi')) + if import_picture: + self.import_picture(file_path, options) + else: + self.import_book(file_path, options) files_imported += 1 - if os.path.isfile(file_base + '.pdf'): - book.pdf_file.save('%s.pdf' % book.slug, File(file(file_base + '.pdf'))) - if verbose: - print "Importing %s.pdf" % file_base - if os.path.isfile(file_base + '.mobi'): - book.mobi_file.save('%s.mobi' % book.slug, File(file(file_base + '.mobi'))) - if verbose: - print "Importing %s.mobi" % file_base - if os.path.isfile(file_base + '.epub'): - book.epub_file.save('%s.epub' % book.slug, File(file(file_base + '.epub'))) - if verbose: - print "Importing %s.epub" % file_base - if os.path.isfile(file_base + '.txt'): - book.txt_file.save('%s.txt' % book.slug, File(file(file_base + '.txt'))) - if verbose: - print "Importing %s.txt" % file_base - - book.save() - - except Book.AlreadyExists, msg: - print self.style.ERROR('%s: Book already imported. Skipping. To overwrite use --force.' % + except (Book.AlreadyExists, Picture.AlreadyExists): + print self.style.ERROR('%s: Book or Picture already imported. Skipping. To overwrite use --force.' % file_path) files_skipped += 1 diff --git a/apps/catalogue/management/commands/pack.py b/apps/catalogue/management/commands/pack.py index c75f092a9..280c0f6ad 100755 --- a/apps/catalogue/management/commands/pack.py +++ b/apps/catalogue/management/commands/pack.py @@ -23,7 +23,7 @@ class Command(BaseCommand): make_option('-e', '--exclude', dest='exclude', metavar='SLUG,...', help='Exclude specific books by slug') ) - help = 'Prepare data for Lesmianator.' + help = 'Prepare ZIP package with files of given type.' args = '[%s] output_path.zip' % '|'.join(ftypes) def handle(self, ftype, path, **options): @@ -33,7 +33,7 @@ class Command(BaseCommand): include = options.get('include') exclude = options.get('exclude') - if ftype in Book.file_types: + if ftype in Book.formats: field = "%s_file" % ftype else: print self.style.ERROR('Unknown file type.') diff --git a/apps/catalogue/migrations/0017_auto__add_collection.py b/apps/catalogue/migrations/0017_auto__add_collection.py deleted file mode 100644 index 2e9ca4185..000000000 --- a/apps/catalogue/migrations/0017_auto__add_collection.py +++ /dev/null @@ -1,147 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding model 'Collection' - db.create_table('catalogue_collection', ( - ('title', self.gf('django.db.models.fields.CharField')(max_length=120, db_index=True)), - ('slug', self.gf('django.db.models.fields.SlugField')(max_length=120, primary_key=True, db_index=True)), - ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), - ('book_slugs', self.gf('django.db.models.fields.TextField')()), - )) - db.send_create_signal('catalogue', ['Collection']) - - if not db.dry_run: - from django.core.management import call_command - call_command("loaddata", "collection-boy.json") - - def backwards(self, orm): - - # Deleting model 'Collection' - db.delete_table('catalogue_collection') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'catalogue.book': { - 'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'}, - 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), - 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), - 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), - 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), - 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), - 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), - 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}), - 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), - 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), - 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), - 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) - }, - 'catalogue.bookmedia': { - 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), - 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), - 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), - 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), - 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), - 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) - }, - 'catalogue.collection': { - 'Meta': {'ordering': "('title',)", 'object_name': 'Collection'}, - 'book_slugs': ('django.db.models.fields.TextField', [], {}), - 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'primary_key': 'True', 'db_index': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}) - }, - 'catalogue.filerecord': { - 'Meta': {'ordering': "('-time', '-slug', '-type')", 'object_name': 'FileRecord'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'sha1': ('django.db.models.fields.CharField', [], {'max_length': '40'}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), - 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'type': ('django.db.models.fields.CharField', [], {'max_length': '20', 'db_index': 'True'}) - }, - 'catalogue.fragment': { - 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, - 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), - 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'short_text': ('django.db.models.fields.TextField', [], {}), - 'text': ('django.db.models.fields.TextField', [], {}) - }, - 'catalogue.tag': { - 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, - 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), - 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), - 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), - 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) - }, - 'catalogue.tagrelation': { - 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0017_auto__add_field_book_language__del_unique_book_slug__add_unique_book_s.py b/apps/catalogue/migrations/0017_auto__add_field_book_language__del_unique_book_slug__add_unique_book_s.py new file mode 100644 index 000000000..6d1edcfa1 --- /dev/null +++ b/apps/catalogue/migrations/0017_auto__add_field_book_language__del_unique_book_slug__add_unique_book_s.py @@ -0,0 +1,144 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Removing unique constraint on 'Book', fields ['slug'] + db.delete_unique('catalogue_book', ['slug']) + + # Adding field 'Book.language' + db.add_column('catalogue_book', 'language', self.gf('django.db.models.fields.CharField')(default='pol', max_length=3, db_index=True), keep_default=False) + + # Adding unique constraint on 'Book', fields ['slug', 'language'] + db.create_unique('catalogue_book', ['slug', 'language']) + + + def backwards(self, orm): + + # Removing unique constraint on 'Book', fields ['slug', 'language'] + db.delete_unique('catalogue_book', ['slug', 'language']) + + # Deleting field 'Book.language' + db.delete_column('catalogue_book', 'language') + + # Adding unique constraint on 'Book', fields ['slug'] + db.create_unique('catalogue_book', ['slug']) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'}, + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) + }, + 'catalogue.bookmedia': { + 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'catalogue.filerecord': { + 'Meta': {'ordering': "('-time', '-slug', '-type')", 'object_name': 'FileRecord'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'sha1': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '20', 'db_index': 'True'}) + }, + 'catalogue.fragment': { + 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, + 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'short_text': ('django.db.models.fields.TextField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'catalogue.tag': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, + 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) + }, + 'catalogue.tagrelation': { + 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0018_auto__del_filerecord.py b/apps/catalogue/migrations/0018_auto__del_filerecord.py new file mode 100644 index 000000000..66a6542a3 --- /dev/null +++ b/apps/catalogue/migrations/0018_auto__del_filerecord.py @@ -0,0 +1,131 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting model 'FileRecord' + db.delete_table('catalogue_filerecord') + + + def backwards(self, orm): + + # Adding model 'FileRecord' + db.create_table('catalogue_filerecord', ( + ('sha1', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('time', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('type', self.gf('django.db.models.fields.CharField')(max_length=20, db_index=True)), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=120, db_index=True)), + )) + db.send_create_signal('catalogue', ['FileRecord']) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'}, + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) + }, + 'catalogue.bookmedia': { + 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'catalogue.fragment': { + 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, + 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'short_text': ('django.db.models.fields.TextField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'catalogue.tag': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, + 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) + }, + 'catalogue.tagrelation': { + 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0019_auto__add_field_book_cover.py b/apps/catalogue/migrations/0019_auto__add_field_book_cover.py new file mode 100644 index 000000000..259d935ba --- /dev/null +++ b/apps/catalogue/migrations/0019_auto__add_field_book_cover.py @@ -0,0 +1,125 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'Book.cover' + db.add_column('catalogue_book', 'cover', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Book.cover' + db.delete_column('catalogue_book', 'cover') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'}, + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) + }, + 'catalogue.bookmedia': { + 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'catalogue.fragment': { + 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, + 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'short_text': ('django.db.models.fields.TextField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'catalogue.tag': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, + 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'main_page': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) + }, + 'catalogue.tagrelation': { + 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0020_auto__del_field_tag_main_page.py b/apps/catalogue/migrations/0020_auto__del_field_tag_main_page.py new file mode 100644 index 000000000..e9f77946e --- /dev/null +++ b/apps/catalogue/migrations/0020_auto__del_field_tag_main_page.py @@ -0,0 +1,124 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting field 'Tag.main_page' + db.delete_column('catalogue_tag', 'main_page') + + + def backwards(self, orm): + + # Adding field 'Tag.main_page' + db.add_column('catalogue_tag', 'main_page', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True), keep_default=False) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'}, + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) + }, + 'catalogue.bookmedia': { + 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'catalogue.fragment': { + 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, + 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'short_text': ('django.db.models.fields.TextField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'catalogue.tag': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, + 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) + }, + 'catalogue.tagrelation': { + 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0021_build_covers.py b/apps/catalogue/migrations/0021_build_covers.py new file mode 100644 index 000000000..319decb18 --- /dev/null +++ b/apps/catalogue/migrations/0021_build_covers.py @@ -0,0 +1,137 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + from StringIO import StringIO + from django.core.files.base import ContentFile + from librarian import ValidationError + from librarian.cover import WLCover + from librarian.dcparser import BookInfo + + for book in orm.Book.objects.filter(cover=None): + try: + book_info = BookInfo.from_file(book.xml_file.path) + except ValidationError: + pass + else: + cover = WLCover(book_info).image() + imgstr = StringIO() + cover.save(imgstr, 'png') + book.cover.save('book/png/%s.png' % book.slug, + ContentFile(imgstr.getvalue())) + + + def backwards(self, orm): + "Write your backwards methods here." + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "[['slug', 'language']]", 'object_name': 'Book'}, + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) + }, + 'catalogue.bookmedia': { + 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'catalogue.fragment': { + 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, + 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'short_text': ('django.db.models.fields.TextField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'catalogue.tag': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, + 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) + }, + 'catalogue.tagrelation': { + 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py b/apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py new file mode 100644 index 000000000..75a1c99f4 --- /dev/null +++ b/apps/catalogue/migrations/0022_auto__add_field_book_common_slug__add_unique_book_slug__del_unique_boo.py @@ -0,0 +1,137 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Removing unique constraint on 'Book', fields ['slug', 'language'] + db.delete_unique('catalogue_book', ['slug', 'language']) + + # Adding field 'Book.common_slug' + db.add_column('catalogue_book', 'common_slug', self.gf('django.db.models.fields.SlugField')(default='-', max_length=120, db_index=True), keep_default=False) + + # Adding unique constraint on 'Book', fields ['slug'] + db.create_unique('catalogue_book', ['slug']) + + + def backwards(self, orm): + + # Removing unique constraint on 'Book', fields ['slug'] + db.delete_unique('catalogue_book', ['slug']) + + # Deleting field 'Book.common_slug' + db.delete_column('catalogue_book', 'common_slug') + + # Adding unique constraint on 'Book', fields ['slug', 'language'] + db.create_unique('catalogue_book', ['slug', 'language']) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'}, + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) + }, + 'catalogue.bookmedia': { + 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'catalogue.fragment': { + 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, + 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'short_text': ('django.db.models.fields.TextField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'catalogue.tag': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, + 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) + }, + 'catalogue.tagrelation': { + 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0023_common_slug.py b/apps/catalogue/migrations/0023_common_slug.py new file mode 100644 index 000000000..386314915 --- /dev/null +++ b/apps/catalogue/migrations/0023_common_slug.py @@ -0,0 +1,123 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + orm.Book.objects.all().update(common_slug=models.F('slug')) + + + def backwards(self, orm): + "Write your backwards methods here." + pass + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'}, + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) + }, + 'catalogue.bookmedia': { + 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'catalogue.fragment': { + 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, + 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'short_text': ('django.db.models.fields.TextField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'catalogue.tag': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, + 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) + }, + 'catalogue.tagrelation': { + 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0024_auto__add_collection.py b/apps/catalogue/migrations/0024_auto__add_collection.py new file mode 100644 index 000000000..e2e21007d --- /dev/null +++ b/apps/catalogue/migrations/0024_auto__add_collection.py @@ -0,0 +1,138 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Collection' + db.create_table('catalogue_collection', ( + ('title', self.gf('django.db.models.fields.CharField')(max_length=120, db_index=True)), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=120, primary_key=True, db_index=True)), + ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('book_slugs', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('catalogue', ['Collection']) + + + def backwards(self, orm): + + # Deleting model 'Collection' + db.delete_table('catalogue_collection') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'}, + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'cover': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) + }, + 'catalogue.bookmedia': { + 'Meta': {'ordering': "('type', 'name')", 'object_name': 'BookMedia'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'media'", 'to': "orm['catalogue.Book']"}), + 'extra_info': ('catalogue.fields.JSONField', [], {'default': "'{}'"}), + 'file': ('catalogue.fields.OverwritingFileField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'source_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': "'100'"}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'catalogue.collection': { + 'Meta': {'ordering': "('title',)", 'object_name': 'Collection'}, + 'book_slugs': ('django.db.models.fields.TextField', [], {}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'primary_key': 'True', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}) + }, + 'catalogue.fragment': { + 'Meta': {'ordering': "('book', 'anchor')", 'object_name': 'Fragment'}, + 'anchor': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fragments'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'short_text': ('django.db.models.fields.TextField', [], {}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + 'catalogue.tag': { + 'Meta': {'ordering': "('sort_key',)", 'unique_together': "(('slug', 'category'),)", 'object_name': 'Tag'}, + 'book_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '120', 'db_index': 'True'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}) + }, + 'catalogue.tagrelation': { + 'Meta': {'unique_together': "(('tag', 'content_type', 'object_id'),)", 'object_name': 'TagRelation', 'db_table': "'catalogue_tag_relation'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'to': "orm['catalogue.Tag']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index 1417519f7..9a1e71ad0 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -2,12 +2,13 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -from datetime import datetime +from collections import namedtuple from django.db import models from django.db.models import permalink, Q import django.dispatch from django.core.cache import cache +from django.core.files.storage import DefaultStorage from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.template.loader import render_to_string @@ -22,9 +23,17 @@ from django.conf import settings from newtagging.models import TagBase, tags_updated from newtagging import managers from catalogue.fields import JSONField, OverwritingFileField -from catalogue.utils import create_zip +from catalogue.utils import create_zip, split_tags +from catalogue.tasks import touch_tag, index_book +from shutil import copy +from glob import glob +import re +from os import path +import search + +# Those are hard-coded here so that makemessages sees them. TAG_CATEGORIES = ( ('author', _('author')), ('epoch', _('epoch')), @@ -35,13 +44,6 @@ TAG_CATEGORIES = ( ('book', _('book')), ) -MEDIA_FORMATS = ( - ('odt', _('ODT file')), - ('mp3', _('MP3 file')), - ('ogg', _('OGG file')), - ('daisy', _('DAISY file')), -) - # not quite, but Django wants you to set a timeout CACHE_FOREVER = 2419200 # 28 days @@ -62,7 +64,6 @@ class Tag(TagBase): category = models.CharField(_('category'), max_length=50, blank=False, null=False, db_index=True, choices=TAG_CATEGORIES) description = models.TextField(_('description'), blank=True) - main_page = models.BooleanField(_('main page'), default=False, db_index=True, help_text=_('Show tag on main page')) user = models.ForeignKey(User, blank=True, null=True) book_count = models.IntegerField(_('book count'), blank=True, null=True) @@ -107,25 +108,22 @@ class Tag(TagBase): has_description.boolean = True def get_count(self): - """ returns global book count for book tags, fragment count for themes """ - - if self.book_count is None: - if self.category == 'book': - # never used - objects = Book.objects.none() - elif self.category == 'theme': - objects = Fragment.tagged.with_all((self,)) - else: - objects = Book.tagged.with_all((self,)).order_by() - if self.category != 'set': - # eliminate descendants - l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects]) - descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)] - if descendants_keys: - objects = objects.exclude(pk__in=descendants_keys) - self.book_count = objects.count() - self.save() - return self.book_count + """Returns global book count for book tags, fragment count for themes.""" + + if self.category == 'book': + # never used + objects = Book.objects.none() + elif self.category == 'theme': + objects = Fragment.tagged.with_all((self,)) + else: + objects = Book.tagged.with_all((self,)).order_by() + if self.category != 'set': + # eliminate descendants + l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects]) + descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)] + if descendants_keys: + objects = objects.exclude(pk__in=descendants_keys) + return objects.count() @staticmethod def get_tag_list(tags): @@ -169,28 +167,89 @@ class Tag(TagBase): def url_chunk(self): return '/'.join((Tag.categories_dict[self.category], self.slug)) + @staticmethod + def tags_from_info(info): + from slughifi import slughifi + from sortify import sortify + meta_tags = [] + categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch')) + for field_name, category in categories: + try: + tag_names = getattr(info, field_name) + except: + try: + tag_names = [getattr(info, category)] + except: + # For instance, Pictures do not have 'genre' field. + continue + for tag_name in tag_names: + tag_sort_key = tag_name + if category == 'author': + tag_sort_key = tag_name.last_name + tag_name = tag_name.readable() + tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category) + if created: + tag.name = tag_name + tag.sort_key = sortify(tag_sort_key.lower()) + tag.save() + meta_tags.append(tag) + return meta_tags + + + +def get_dynamic_path(media, filename, ext=None, maxlen=100): + from slughifi import slughifi + + # how to put related book's slug here? + if not ext: + # BookMedia case + ext = media.formats[media.type].ext + if media is None or not media.name: + name = slughifi(filename.split(".")[0]) + else: + name = slughifi(media.name) + return 'book/%s/%s.%s' % (ext, name[:maxlen-len('book/%s/.%s' % (ext, ext))-4], ext) + # TODO: why is this hard-coded ? def book_upload_path(ext=None, maxlen=100): - def get_dynamic_path(media, filename, ext=ext): - from slughifi import slughifi + return lambda *args: get_dynamic_path(*args, ext=ext, maxlen=maxlen) - # how to put related book's slug here? - if not ext: - if media.type == 'daisy': - ext = 'daisy.zip' - else: - ext = media.type - if not media.name: - name = slughifi(filename.split(".")[0]) - else: - name = slughifi(media.name) - return 'book/%s/%s.%s' % (ext, name[:maxlen-len('book/%s/.%s' % (ext, ext))-4], ext) - return get_dynamic_path + +def get_customized_pdf_path(book, customizations): + """ + Returns a MEDIA_ROOT relative path for a customized pdf. The name will contain a hash of customization options. + """ + customizations.sort() + h = hash(tuple(customizations)) + + pdf_name = '%s-custom-%s' % (book.slug, h) + pdf_file = get_dynamic_path(None, pdf_name, ext='pdf') + + return pdf_file + + +def get_existing_customized_pdf(book): + """ + Returns a list of paths to generated customized pdf of a book + """ + pdf_glob = '%s-custom-' % (book.slug,) + pdf_glob = get_dynamic_path(None, pdf_glob, ext='pdf') + pdf_glob = re.sub(r"[.]([a-z0-9]+)$", "*.\\1", pdf_glob) + return glob(path.join(settings.MEDIA_ROOT, pdf_glob)) class BookMedia(models.Model): - type = models.CharField(_('type'), choices=MEDIA_FORMATS, max_length="100") + FileFormat = namedtuple("FileFormat", "name ext") + formats = SortedDict([ + ('mp3', FileFormat(name='MP3', ext='mp3')), + ('ogg', FileFormat(name='Ogg Vorbis', ext='ogg')), + ('daisy', FileFormat(name='DAISY', ext='daisy.zip')), + ]) + format_choices = [(k, _('%s file') % t.name) + for k, t in formats.items()] + + type = models.CharField(_('type'), choices=format_choices, max_length="100") name = models.CharField(_('name'), max_length="100") file = OverwritingFileField(_('file'), upload_to=book_upload_path()) uploaded_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False) @@ -213,7 +272,7 @@ class BookMedia(models.Model): try: old = BookMedia.objects.get(pk=self.pk) except BookMedia.DoesNotExist, e: - pass + old = None else: # if name changed, change the file name, too if slughifi(self.name) != slughifi(old.name): @@ -222,7 +281,9 @@ class BookMedia(models.Model): super(BookMedia, self).save(*args, **kwargs) # remove the zip package for book with modified media - remove_zip(self.book.slug) + if old: + remove_zip("%s_%s" % (old.book.slug, old.type)) + remove_zip("%s_%s" % (self.book.slug, self.type)) extra_info = self.get_extra_info_value() extra_info.update(self.read_meta()) @@ -291,7 +352,11 @@ class BookMedia(models.Model): class Book(models.Model): title = models.CharField(_('title'), max_length=120) sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False) - slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True) + slug = models.SlugField(_('slug'), max_length=120, db_index=True, + unique=True) + common_slug = models.SlugField(_('slug'), max_length=120, db_index=True) + language = models.CharField(_('language code'), max_length=3, db_index=True, + default=settings.CATALOGUE_DEFAULT_LANGUAGE) description = models.TextField(_('description'), blank=True) created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True) changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True) @@ -301,8 +366,11 @@ class Book(models.Model): wiki_link = models.CharField(blank=True, max_length=240) # files generated during publication - file_types = ['epub', 'html', 'mobi', 'pdf', 'txt', 'xml'] - + cover = models.FileField(_('cover'), upload_to=book_upload_path('png'), + null=True, blank=True) + ebook_formats = ['pdf', 'epub', 'mobi', 'txt'] + formats = ebook_formats + ['html', 'xml'] + parent = models.ForeignKey('self', blank=True, null=True, related_name='children') objects = models.Manager() tagged = managers.ModelTaggedItemManager(Tag) @@ -355,14 +423,14 @@ class Book(models.Model): return book_tag def has_media(self, type): - if type in Book.file_types: + if type in Book.formats: return bool(getattr(self, "%s_file" % type)) else: return self.media.filter(type=type).exists() def get_media(self, type): if self.has_media(type): - if type in Book.file_types: + if type in Book.formats: return getattr(self, "%s_file" % type) else: return self.media.filter(type=type) @@ -385,6 +453,7 @@ class Book(models.Model): cache_key = "Book.short_html/%d/%s" for lang, langname in settings.LANGUAGES: cache.delete(cache_key % (self.id, lang)) + cache.delete("Book.mini_box/%d" % (self.id, )) # Fragment.short_html relies on book's tags, so reset it here too for fragm in self.fragments.all(): fragm.reset_short_html() @@ -399,26 +468,15 @@ class Book(models.Model): if short_html is not None: return mark_safe(short_html) else: - tags = self.tags.filter(~Q(category__in=('set', 'theme', 'book'))) - tags = [mark_safe(u'%s' % (tag.get_absolute_url(), tag.name)) for tag in tags] + tags = self.tags.filter(category__in=('author', 'kind', 'genre', 'epoch')) + tags = split_tags(tags) - formats = [] + formats = {} # files generated during publication - if self.has_media("html"): - formats.append(u'%s' % (reverse('book_text', kwargs={'slug': self.slug}), _('Read online'))) - if self.has_media("pdf"): - formats.append(u'PDF' % self.get_media('pdf').url) - if self.has_media("mobi"): - formats.append(u'MOBI' % self.get_media('mobi').url) - if self.root_ancestor.has_media("epub"): - formats.append(u'EPUB' % self.root_ancestor.get_media('epub').url) - if self.has_media("txt"): - formats.append(u'TXT' % self.get_media('txt').url) - # other files - for m in self.media.order_by('type'): - formats.append(u'%s' % (m.file.url, m.type.upper())) - - formats = [mark_safe(format) for format in formats] + for ebook_format in self.ebook_formats: + if self.has_media(ebook_format): + formats[ebook_format] = self.get_media(ebook_format) + short_html = unicode(render_to_string('catalogue/book_short.html', {'book': self, 'tags': tags, 'formats': formats})) @@ -427,17 +485,22 @@ class Book(models.Model): cache.set(cache_key, short_html, CACHE_FOREVER) return mark_safe(short_html) - @property - def root_ancestor(self): - """ returns the oldest ancestor """ + def mini_box(self): + if self.id: + cache_key = "Book.mini_box/%d" % (self.id, ) + short_html = cache.get(cache_key) + else: + short_html = None - if not hasattr(self, '_root_ancestor'): - book = self - while book.parent: - book = book.parent - self._root_ancestor = book - return self._root_ancestor + if short_html is None: + authors = self.tags.filter(category='author') + short_html = unicode(render_to_string('catalogue/book_mini_box.html', + {'book': self, 'authors': authors, 'STATIC_URL': settings.STATIC_URL})) + + if self.id: + cache.set(cache_key, short_html, CACHE_FOREVER) + return mark_safe(short_html) def has_description(self): return len(self.description) > 0 @@ -445,11 +508,6 @@ class Book(models.Model): has_description.boolean = True # ugly ugly ugly - def has_odt_file(self): - return bool(self.has_media("odt")) - has_odt_file.short_description = 'ODT' - has_odt_file.boolean = True - def has_mp3_file(self): return bool(self.has_media("mp3")) has_mp3_file.short_description = 'MP3' @@ -465,103 +523,94 @@ class Book(models.Model): has_daisy_file.short_description = 'DAISY' has_daisy_file.boolean = True - def build_pdf(self): - """ (Re)builds the pdf file. + def wldocument(self, parse_dublincore=True): + from catalogue.import_utils import ORMDocProvider + from librarian.parser import WLDocument + + return WLDocument.from_file(self.xml_file.path, + provider=ORMDocProvider(self), + parse_dublincore=parse_dublincore) + + def build_cover(self, book_info=None): + """(Re)builds the cover image.""" + from StringIO import StringIO + from django.core.files.base import ContentFile + from librarian.cover import WLCover + + if book_info is None: + book_info = self.wldocument().book_info + cover = WLCover(book_info).image() + imgstr = StringIO() + cover.save(imgstr, 'png') + self.cover.save(None, ContentFile(imgstr.getvalue())) + + def build_pdf(self, customizations=None, file_name=None): + """ (Re)builds the pdf file. + customizations - customizations which are passed to LaTeX class file. + file_name - save the pdf file under a different name and DO NOT save it in db. """ - from tempfile import NamedTemporaryFile from os import unlink from django.core.files import File - from librarian import pdf - from catalogue.utils import ORMDocProvider, remove_zip + from catalogue.utils import remove_zip - try: - pdf_file = NamedTemporaryFile(delete=False) - pdf.transform(ORMDocProvider(self), - file_path=str(self.xml_file.path), - output_file=pdf_file, - ) + pdf = self.wldocument().as_pdf(customizations=customizations) - self.pdf_file.save('%s.pdf' % self.slug, File(open(pdf_file.name))) - finally: - unlink(pdf_file.name) + if file_name is None: + # we'd like to be sure not to overwrite changes happening while + # (timely) pdf generation is taking place (async celery scenario) + current_self = Book.objects.get(id=self.id) + current_self.pdf_file.save('%s.pdf' % self.slug, + File(open(pdf.get_filename()))) + self.pdf_file = current_self.pdf_file - # remove zip with all pdf files - remove_zip(settings.ALL_PDF_ZIP) + # remove cached downloadables + remove_zip(settings.ALL_PDF_ZIP) + + for customized_pdf in get_existing_customized_pdf(self): + unlink(customized_pdf) + else: + print "saving %s" % file_name + print "to: %s" % DefaultStorage().path(file_name) + DefaultStorage().save(file_name, File(open(pdf.get_filename()))) def build_mobi(self): """ (Re)builds the MOBI file. """ - from tempfile import NamedTemporaryFile - from os import unlink from django.core.files import File - from librarian import mobi - from catalogue.utils import ORMDocProvider, remove_zip + from catalogue.utils import remove_zip - try: - mobi_file = NamedTemporaryFile(suffix='.mobi', delete=False) - mobi.transform(ORMDocProvider(self), verbose=1, - file_path=str(self.xml_file.path), - output_file=mobi_file.name, - ) + mobi = self.wldocument().as_mobi() - self.mobi_file.save('%s.mobi' % self.slug, File(open(mobi_file.name))) - finally: - unlink(mobi_file.name) + self.mobi_file.save('%s.mobi' % self.slug, File(open(mobi.get_filename()))) # remove zip with all mobi files remove_zip(settings.ALL_MOBI_ZIP) - def build_epub(self, remove_descendants=True): - """ (Re)builds the epub file. - If book has a parent, does nothing. - Unless remove_descendants is False, descendants' epubs are removed. - """ - from StringIO import StringIO - from hashlib import sha1 - from django.core.files.base import ContentFile - from librarian import epub, NoDublinCore - from catalogue.utils import ORMDocProvider, remove_zip - - if self.parent: - # don't need an epub - return + def build_epub(self): + """(Re)builds the epub file.""" + from django.core.files import File + from catalogue.utils import remove_zip - epub_file = StringIO() - try: - epub.transform(ORMDocProvider(self), self.slug, output_file=epub_file) - self.epub_file.save('%s.epub' % self.slug, ContentFile(epub_file.getvalue())) - FileRecord(slug=self.slug, type='epub', sha1=sha1(epub_file.getvalue()).hexdigest()).save() - except NoDublinCore: - pass + epub = self.wldocument().as_epub() - book_descendants = list(self.children.all()) - while len(book_descendants) > 0: - child_book = book_descendants.pop(0) - if remove_descendants and child_book.has_epub_file(): - child_book.epub_file.delete() - # save anyway, to refresh short_html - child_book.save() - book_descendants += list(child_book.children.all()) + self.epub_file.save('%s.epub' % self.slug, + File(open(epub.get_filename()))) # remove zip package with all epub files remove_zip(settings.ALL_EPUB_ZIP) def build_txt(self): - from StringIO import StringIO from django.core.files.base import ContentFile - from librarian import text - out = StringIO() - text.transform(open(self.xml_file.path), out) - self.txt_file.save('%s.txt' % self.slug, ContentFile(out.getvalue())) + text = self.wldocument().as_text() + self.txt_file.save('%s.txt' % self.slug, ContentFile(text.get_string())) def build_html(self): - from tempfile import NamedTemporaryFile from markupstring import MarkupString - from django.core.files import File + from django.core.files.base import ContentFile from slughifi import slughifi from librarian import html @@ -569,9 +618,10 @@ class Book(models.Model): category__in=('author', 'epoch', 'genre', 'kind'))) book_tag = self.book_tag() - html_file = NamedTemporaryFile() - if html.transform(self.xml_file.path, html_file, parse_dublincore=False): - self.html_file.save('%s.html' % self.slug, File(html_file)) + html_output = self.wldocument(parse_dublincore=False).as_html() + if html_output: + self.html_file.save('%s.html' % self.slug, + ContentFile(html_output.get_string())) # get ancestor l-tags for adding to new fragments ancestor_tags = [] @@ -632,12 +682,25 @@ class Book(models.Model): getattr(settings, "ALL_%s_ZIP" % format_.upper())) return result.wait() - def zip_audiobooks(self): - bm = BookMedia.objects.filter(book=self, type='mp3') + def zip_audiobooks(self, format_): + bm = BookMedia.objects.filter(book=self, type=format_) paths = map(lambda bm: (None, bm.file.path), bm) - result = create_zip.delay(paths, self.slug) + result = create_zip.delay(paths, "%s_%s" % (self.slug, format_)) return result.wait() + def search_index(self, book_info=None, reuse_index=False): + if reuse_index: + idx = search.ReusableIndex() + else: + idx = search.Index() + + idx.open() + try: + idx.index_book(self, book_info) + idx.index_tags() + finally: + idx.close() + @classmethod def from_xml_file(cls, xml_file, **kwargs): from django.core.files import File @@ -656,25 +719,25 @@ class Book(models.Model): @classmethod def from_text_and_meta(cls, raw_file, book_info, overwrite=False, - build_epub=True, build_txt=True, build_pdf=True, build_mobi=True): + build_epub=True, build_txt=True, build_pdf=True, build_mobi=True, + search_index=True): import re - from slughifi import slughifi from sortify import sortify # check for parts before we do anything children = [] if hasattr(book_info, 'parts'): for part_url in book_info.parts: - base, slug = part_url.rsplit('/', 1) try: - children.append(Book.objects.get(slug=slug)) + children.append(Book.objects.get(slug=part_url.slug)) except Book.DoesNotExist, e: - raise Book.DoesNotExist(_('Book with slug = "%s" does not exist.') % slug) + raise Book.DoesNotExist(_('Book "%s" does not exist.') % + part_url.slug) # Read book metadata - book_base, book_slug = book_info.url.rsplit('/', 1) - if re.search(r'[^a-zA-Z0-9-]', book_slug): + book_slug = book_info.url.slug + if re.search(r'[^a-z0-9-]', book_slug): raise ValueError('Invalid characters in slug') book, created = Book.objects.get_or_create(slug=book_slug) @@ -682,32 +745,21 @@ class Book(models.Model): book_shelves = [] else: if not overwrite: - raise Book.AlreadyExists(_('Book %s already exists') % book_slug) + raise Book.AlreadyExists(_('Book %s already exists') % ( + book_slug)) # Save shelves for this book book_shelves = list(book.tags.filter(category='set')) + book.language = book_info.language book.title = book_info.title + if book_info.variant_of: + book.common_slug = book_info.variant_of.slug + else: + book.common_slug = book.slug book.set_extra_info_value(book_info.to_dict()) book.save() - meta_tags = [] - categories = (('kinds', 'kind'), ('genres', 'genre'), ('authors', 'author'), ('epochs', 'epoch')) - for field_name, category in categories: - try: - tag_names = getattr(book_info, field_name) - except: - tag_names = [getattr(book_info, category)] - for tag_name in tag_names: - tag_sort_key = tag_name - if category == 'author': - tag_sort_key = tag_name.last_name - tag_name = ' '.join(tag_name.first_names) + ' ' + tag_name.last_name - tag, created = Tag.objects.get_or_create(slug=slughifi(tag_name), category=category) - if created: - tag.name = tag_name - tag.sort_key = sortify(tag_sort_key.lower()) - tag.save() - meta_tags.append(tag) + meta_tags = Tag.tags_from_info(book_info) book.tags = set(meta_tags + book_shelves) @@ -728,26 +780,35 @@ class Book(models.Model): if not settings.NO_BUILD_TXT and build_txt: book.build_txt() + book.build_cover(book_info) + if not settings.NO_BUILD_EPUB and build_epub: - book.root_ancestor.build_epub() + book.build_epub() if not settings.NO_BUILD_PDF and build_pdf: - book.root_ancestor.build_pdf() + book.build_pdf() if not settings.NO_BUILD_MOBI and build_mobi: book.build_mobi() + if not settings.NO_SEARCH_INDEX and search_index: + index_book.delay(book.id, book_info) + book_descendants = list(book.children.all()) + descendants_tags = set() # add l-tag to descendants and their fragments - # delete unnecessary EPUB files while len(book_descendants) > 0: child_book = book_descendants.pop(0) + descendants_tags.update(child_book.tags) child_book.tags = list(child_book.tags) + [book_tag] child_book.save() for fragment in child_book.fragments.all(): fragment.tags = set(list(fragment.tags) + [book_tag]) book_descendants += list(child_book.children.all()) + for tag in descendants_tags: + touch_tag.delay(tag) + book.save() # refresh cache @@ -857,7 +918,8 @@ class Book(models.Model): """ books_by_parent = {} - books = cls.objects.all().order_by('parent_number', 'sort_key').only('title', 'parent', 'slug') + books = cls.objects.all().order_by('parent_number', 'sort_key').only( + 'title', 'parent', 'slug') if filter: books = books.filter(filter).distinct() book_ids = set((book.pk for book in books)) @@ -885,6 +947,19 @@ class Book(models.Model): return books_by_author, orphans, books_by_parent + _audiences_pl = { + "SP1": (1, u"szkoła podstawowa"), + "SP2": (1, u"szkoła podstawowa"), + "P": (1, u"szkoła podstawowa"), + "G": (2, u"gimnazjum"), + "L": (3, u"liceum"), + "LP": (3, u"liceum"), + } + def audiences_pl(self): + audiences = self.get_extra_info_value().get('audiences', []) + audiences = sorted(set([self._audiences_pl[a] for a in audiences])) + return [a[1] for a in audiences] + def _has_factory(ftype): has = lambda self: bool(getattr(self, "%s_file" % ftype)) @@ -895,7 +970,7 @@ def _has_factory(ftype): # add the file fields -for t in Book.file_types: +for t in Book.formats: field_name = "%s_file" % t models.FileField(_("%s file" % t.upper()), upload_to=book_upload_path(t), @@ -920,7 +995,7 @@ class Fragment(models.Model): verbose_name_plural = _('fragments') def get_absolute_url(self): - return '%s#m%s' % (reverse('book_text', kwargs={'slug': self.book.slug}), self.anchor) + return '%s#m%s' % (reverse('book_text', args=[self.book.slug]), self.anchor) def reset_short_html(self): if self.id is None: @@ -947,21 +1022,6 @@ class Fragment(models.Model): return mark_safe(short_html) -class FileRecord(models.Model): - slug = models.SlugField(_('slug'), max_length=120, db_index=True) - type = models.CharField(_('type'), max_length=20, db_index=True) - sha1 = models.CharField(_('sha-1 hash'), max_length=40) - time = models.DateTimeField(_('time'), auto_now_add=True) - - class Meta: - ordering = ('-time','-slug', '-type') - verbose_name = _('file record') - verbose_name_plural = _('file records') - - def __unicode__(self): - return "%s %s.%s" % (self.sha1, self.slug, self.type) - - class Collection(models.Model): """A collection of books, which might be defined before publishing them.""" title = models.CharField(_('title'), max_length=120, db_index=True) @@ -990,7 +1050,8 @@ class Collection(models.Model): def _tags_updated_handler(sender, affected_tags, **kwargs): # reset tag global counter # we want Tag.changed_at updated for API to know the tag was touched - Tag.objects.filter(pk__in=[tag.pk for tag in affected_tags]).update(book_count=None, changed_at=datetime.now()) + for tag in affected_tags: + touch_tag.delay(tag) # if book tags changed, reset book tag counter if isinstance(sender, Book) and \ diff --git a/apps/catalogue/tasks.py b/apps/catalogue/tasks.py new file mode 100755 index 000000000..86e84a511 --- /dev/null +++ b/apps/catalogue/tasks.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from datetime import datetime +from celery.task import task +import catalogue.models + +@task +def touch_tag(tag): + update_dict = { + 'book_count': tag.get_count(), + 'changed_at': datetime.now(), + } + + type(tag).objects.filter(pk=tag.pk).update(**update_dict) + + +@task +def index_book(book_id, book_info=None): + return catalogue.models.Book.objects.get(id=book_id).search_index(book_info) diff --git a/apps/catalogue/templatetags/catalogue_tags.py b/apps/catalogue/templatetags/catalogue_tags.py index e6544b038..df938a624 100644 --- a/apps/catalogue/templatetags/catalogue_tags.py +++ b/apps/catalogue/templatetags/catalogue_tags.py @@ -15,6 +15,7 @@ from django.conf import settings from django.utils.translation import ugettext as _ from catalogue.forms import SearchForm +from catalogue.utils import split_tags register = template.Library() @@ -144,12 +145,14 @@ def book_tree(book_list, books_by_parent): def book_tree_texml(book_list, books_by_parent, depth=1): return "".join(""" %(depth)dem%(title)s + %(audiences)s %(audiobook)s %(children)s """ % { "depth": depth, "title": book.title, + "audiences": ", ".join(book.audiences_pl()), "audiobook": "audiobook" if book.has_media('mp3') else "", "children": book_tree_texml(books_by_parent.get(book.id, ()), books_by_parent, depth + 1) } for book in book_list) @@ -182,22 +185,6 @@ def authentication_form(): return LoginForm(prefix='login').as_ul() -@register.inclusion_tag('catalogue/search_form.html') -def search_form(): - return {"form": SearchForm()} - -@register.inclusion_tag('catalogue/breadcrumbs.html') -def breadcrumbs(tags, search_form=True): - context = {'tag_list': tags} - try: - max_tag_list = settings.MAX_TAG_LIST - except AttributeError: - max_tag_list = -1 - if search_form and (max_tag_list == -1 or len(tags) < max_tag_list): - context['search_form'] = SearchForm(tags=tags) - return context - - @register.tag def catalogue_url(parser, token): bits = token.split_contents() @@ -277,24 +264,35 @@ def tag_list(tags, choices=None): one_tag = tags[0] return locals() - -@register.inclusion_tag('catalogue/folded_tag_list.html') -def folded_tag_list(tags, title='', choices=None): - tags = [tag for tag in tags if tag.count] +@register.inclusion_tag('catalogue/inline_tag_list.html') +def inline_tag_list(tags, choices=None): if choices is None: choices = [] - some_tags_hidden = False - tag_count = len(tags) - - if tag_count == 1: + if len(tags) == 1: one_tag = tags[0] - else: - shown_tags = [tag for tag in tags if tag.main_page] - if tag_count > len(shown_tags): - some_tags_hidden = True return locals() @register.inclusion_tag('catalogue/book_info.html') def book_info(book): return locals() + + +@register.inclusion_tag('catalogue/book_wide.html') +def book_wide(book): + tags = book.tags.filter(category__in=('author', 'kind', 'genre', 'epoch')) + tags = split_tags(tags) + + formats = {} + # files generated during publication + for ebook_format in book.ebook_formats: + if book.has_media(ebook_format): + formats[ebook_format] = book.get_media(ebook_format) + + extra_info = book.get_extra_info_value() + + has_media = {} + for media_format in ['mp3', 'ogg']: + has_media[media_format] = book.has_media(media_format) + + return locals() diff --git a/apps/catalogue/test_utils.py b/apps/catalogue/test_utils.py index a5f0b4fef..eeda03f04 100644 --- a/apps/catalogue/test_utils.py +++ b/apps/catalogue/test_utils.py @@ -3,6 +3,7 @@ from django.test import TestCase import shutil import tempfile from slughifi import slughifi +from librarian import WLURI class WLTestCase(TestCase): """ @@ -12,10 +13,12 @@ class WLTestCase(TestCase): self._MEDIA_ROOT, settings.MEDIA_ROOT = settings.MEDIA_ROOT, tempfile.mkdtemp(prefix='djangotest_') settings.NO_BUILD_PDF = settings.NO_BUILD_MOBI = settings.NO_BUILD_EPUB = settings.NO_BUILD_TXT = True settings.CELERY_ALWAYS_EAGER = True + self._CACHE_BACKEND, settings.CACHE_BACKEND = settings.CACHE_BACKEND, 'dummy://' def tearDown(self): shutil.rmtree(settings.MEDIA_ROOT, True) settings.MEDIA_ROOT = self._MEDIA_ROOT + settings.CACHE_BACKEND = self._CACHE_BACKEND class PersonStub(object): @@ -23,8 +26,16 @@ class PersonStub(object): self.first_names = first_names self.last_name = last_name + def readable(self): + return " ".join(self.first_names + (self.last_name,)) + class BookInfoStub(object): + _empty_fields = ['cover_url', 'variant_of'] + # allow single definition for multiple-value fields + _salias = { + 'authors': 'author', + } def __init__(self, **kwargs): self.__dict = kwargs @@ -35,18 +46,28 @@ class BookInfoStub(object): return object.__setattr__(self, key, value) def __getattr__(self, key): - return self.__dict[key] + try: + return self.__dict[key] + except KeyError: + if key in self._empty_fields: + return None + elif key in self._salias: + return [getattr(self, self._salias[key])] + else: + raise def to_dict(self): return dict((key, unicode(value)) for key, value in self.__dict.items()) -def info_args(title): +def info_args(title, language=None): """ generate some keywords for comfortable BookInfoCreation """ slug = unicode(slughifi(title)) + if language is None: + language = u'pol' return { 'title': unicode(title), - 'slug': slug, - 'url': u"http://wolnelektury.pl/example/%s" % slug, + 'url': WLURI.from_slug(slug), 'about': u"http://wolnelektury.pl/example/URI/%s" % slug, + 'language': language, } diff --git a/apps/catalogue/tests/book_import.py b/apps/catalogue/tests/book_import.py index f65d8807a..3af1bb486 100644 --- a/apps/catalogue/tests/book_import.py +++ b/apps/catalogue/tests/book_import.py @@ -4,23 +4,25 @@ from __future__ import with_statement from django.core.files.base import ContentFile, File from catalogue.test_utils import * from catalogue import models +from librarian import WLURI from nose.tools import raises import tempfile -from os import unlink,path +from os import unlink, path, makedirs class BookImportLogicTests(WLTestCase): def setUp(self): WLTestCase.setUp(self) self.book_info = BookInfoStub( - url=u"http://wolnelektury.pl/example/default-book", + url=WLURI.from_slug(u"default-book"), about=u"http://wolnelektury.pl/example/URI/default_book", title=u"Default Book", author=PersonStub(("Jim",), "Lazy"), kind="X-Kind", genre="X-Genre", epoch="X-Epoch", + language=u"pol", ) self.expected_tags = [ @@ -112,7 +114,7 @@ class BookImportLogicTests(WLTestCase): @raises(ValueError) def test_book_with_invalid_slug(self): """ Book with invalid characters in slug shouldn't be imported """ - self.book_info.url = "http://wolnelektury.pl/example/default_book" + self.book_info.url = WLURI.from_slug(u"default_book") BOOK_TEXT = "" book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info) @@ -242,6 +244,42 @@ class ChildImportTests(WLTestCase): 'wrong related theme list') +class MultilingualBookImportTest(WLTestCase): + def setUp(self): + WLTestCase.setUp(self) + common_uri = WLURI.from_slug('common-slug') + + self.pol_info = BookInfoStub( + genre='X-Genre', + epoch='X-Epoch', + kind='X-Kind', + author=PersonStub(("Joe",), "Doe"), + variant_of=common_uri, + **info_args(u"Książka") + ) + + self.eng_info = BookInfoStub( + genre='X-Genre', + epoch='X-Epoch', + kind='X-Kind', + author=PersonStub(("Joe",), "Doe"), + variant_of=common_uri, + **info_args("A book", "eng") + ) + + def test_multilingual_import(self): + BOOK_TEXT = """A""" + + book1 = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.pol_info) + book2 = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.eng_info) + + self.assertEqual( + set([b.language for b in models.Book.objects.all()]), + set(['pol', 'eng']), + 'Books imported in wrong languages.' + ) + + class BookImportGenerateTest(WLTestCase): def setUp(self): WLTestCase.setUp(self) @@ -258,3 +296,13 @@ class BookImportGenerateTest(WLTestCase): parent = models.Book.from_xml_file(input) parent.build_pdf() self.assertTrue(path.exists(parent.pdf_file.path)) + + def test_custom_pdf(self): + out = models.get_dynamic_path(None, 'test-custom', ext='pdf') + absoulute_path = path.join(settings.MEDIA_ROOT, out) + + if not path.exists(path.dirname(absoulute_path)): + makedirs(path.dirname(absoulute_path)) + + self.book.build_pdf(customizations=['nofootnotes', '13pt', 'a4paper'], file_name=out) + self.assertTrue(path.exists(absoulute_path)) diff --git a/apps/catalogue/tests/bookmedia.py b/apps/catalogue/tests/bookmedia.py index b6598b33d..5d2ba66c3 100644 --- a/apps/catalogue/tests/bookmedia.py +++ b/apps/catalogue/tests/bookmedia.py @@ -103,8 +103,8 @@ class BookMediaTests(WLTestCase): bm.file.save(None, self.file) bm.save() - zip_url = self.book.zip_audiobooks() - self.assertEqual('zip/'+self.book.slug+'.zip', zip_url) + zip_url = self.book.zip_audiobooks('ogg') + self.assertEqual('zip/'+self.book.slug+'_ogg.zip', zip_url) self.assertTrue(exists(join(settings.MEDIA_ROOT, zip_url))) bm2 = models.BookMedia(book=self.book, type='ogg', name="Other title") diff --git a/apps/catalogue/tests/files/fraszki.xml b/apps/catalogue/tests/files/fraszki.xml index edb29abbc..90e7c1245 100755 --- a/apps/catalogue/tests/files/fraszki.xml +++ b/apps/catalogue/tests/files/fraszki.xml @@ -12,7 +12,7 @@ Fraszka -http://wolnelektury.pl/lektura/fraszki +http://wolnelektury.pl/katalog/lektura/fraszki Domena publiczna - Jan Kochanowski zm. 1584 1584 diff --git a/apps/catalogue/tests/tags.py b/apps/catalogue/tests/tags.py index 7e6e66716..a47e426a5 100644 --- a/apps/catalogue/tests/tags.py +++ b/apps/catalogue/tests/tags.py @@ -3,9 +3,6 @@ from catalogue import models from catalogue.test_utils import * from django.core.files.base import ContentFile -from nose.tools import raises - - class BooksByTagTests(WLTestCase): """ tests the /katalog/category/tag page for found books """ @@ -66,12 +63,13 @@ class BooksByTagTests(WLTestCase): ['Child']) - +from django.test import Client class TagRelatedTagsTests(WLTestCase): """ tests the /katalog/category/tag/ page for related tags """ def setUp(self): WLTestCase.setUp(self) + self.client = Client() author = PersonStub(("Common",), "Man") gchild_info = BookInfoStub(author=author, genre="GchildGenre", epoch='Epoch', kind="Kind", @@ -132,14 +130,14 @@ class TagRelatedTagsTests(WLTestCase): def test_related_differ(self): """ related tags shouldn't include filtering tags """ - cats = self.client.get('/katalog/rodzaj/kind/').context['categories'] + response = self.client.get('/katalog/rodzaj/kind/') + cats = response.context['categories'] self.assertFalse('Kind' in [tag.name for tag in cats['kind']], 'filtering tag wrongly included in related') cats = self.client.get('/katalog/motyw/theme/').context['categories'] self.assertFalse('Theme' in [tag.name for tag in cats['theme']], 'filtering theme wrongly included in related') - def test_parent_tag_once(self): """ if parent and descendants have a common tag, count it only once """ diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index 0e0da4b4a..db044fc1c 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -4,12 +4,22 @@ # from django.conf.urls.defaults import * from catalogue.feeds import AudiobookFeed +from catalogue.models import Book +from picture.models import Picture +from catalogue.views import CustomPDFFormView -urlpatterns = patterns('catalogue.views', - url(r'^$', 'main_page', name='main_page'), +SLUG = r'[a-z0-9-]*' + +urlpatterns = patterns('picture.views', + # pictures - currently pictures are coupled with catalogue, hence the url is here + url(r'^obraz/?$', 'picture_list'), + url(r'^obraz/(?P%s)/?$' % SLUG, 'picture_detail') + ) + \ + patterns('catalogue.views', + url(r'^$', 'catalogue', name='catalogue'), url(r'^polki/(?P[a-zA-Z0-9-]+)/formaty/$', 'shelf_book_formats', name='shelf_book_formats'), - url(r'^polki/(?P[a-zA-Z0-9-]+)/(?P[a-zA-Z0-9-0-]+)/usun$', 'remove_from_shelf', name='remove_from_shelf'), + url(r'^polki/(?P[a-zA-Z0-9-]+)/(?P%s)/usun$' % SLUG, 'remove_from_shelf', name='remove_from_shelf'), url(r'^polki/$', 'user_shelves', name='user_shelves'), url(r'^polki/(?P[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'), url(r'^polki/(?P[a-zA-Z0-9-]+)\.zip$', 'download_shelf', name='download_shelf'), @@ -17,29 +27,31 @@ urlpatterns = patterns('catalogue.views', url(r'^lektury/(?P[a-zA-Z0-9-]+)/$', 'collection', name='collection'), url(r'^audiobooki/$', 'audiobook_list', name='audiobook_list'), url(r'^daisy/$', 'daisy_list', name='daisy_list'), - url(r'^lektura/(?P[a-zA-Z0-9-]+)/polki/', 'book_sets', name='book_shelves'), + url(r'^lektura/(?P%s)/polki/' % SLUG, 'book_sets', name='book_shelves'), url(r'^polki/nowa/$', 'new_set', name='new_set'), url(r'^tags/$', 'tags_starting_with', name='hint'), url(r'^jtags/$', 'json_tags_starting_with', name='jhint'), - url(r'^szukaj/$', 'search', name='search'), + url(r'^szukaj/$', 'search', name='old_search'), # zip - #url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'), - #url(r'^zip/epub\.zip$', 'download_zip', {'format': 'epub', 'slug': None}, 'download_zip_epub'), - #url(r'^zip/mobi\.zip$', 'download_zip', {'format': 'mobi', 'slug': None}, 'download_zip_mobi'), - #url(r'^zip/audiobook/(?P[a-zA-Z0-9-]+)\.zip', 'download_zip', {'format': 'audiobook'}, 'download_zip_audiobook'), - - # tools - url(r'^zegar/$', 'clock', name='clock'), + url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'), + url(r'^zip/epub\.zip$', 'download_zip', {'format': 'epub', 'slug': None}, 'download_zip_epub'), + url(r'^zip/mobi\.zip$', 'download_zip', {'format': 'mobi', 'slug': None}, 'download_zip_mobi'), + url(r'^zip/mp3/(?P%s)\.zip' % SLUG, 'download_zip', {'format': 'mp3'}, 'download_zip_mp3'), + url(r'^zip/ogg/(?P%s)\.zip' % SLUG, 'download_zip', {'format': 'ogg'}, 'download_zip_ogg'), # Public interface. Do not change this URLs. - url(r'^lektura/(?P[a-zA-Z0-9-]+)\.html$', 'book_text', name='book_text'), - url(r'^lektura/(?P[a-zA-Z0-9-]+)/$', 'book_detail', name='book_detail'), - url(r'^lektura/(?P[a-zA-Z0-9-]+)/motyw/(?P[a-zA-Z0-9-]+)/$', + url(r'^lektura/(?P%s)\.html$' % SLUG, 'book_text', name='book_text'), + url(r'^lektura/(?P%s)/audiobook/$' % SLUG, 'player', name='book_player'), + url(r'^lektura/(?P%s)/$' % SLUG, 'book_detail', name='book_detail'), + url(r'^lektura/(?P%s)/motyw/(?P[a-zA-Z0-9-]+)/$' % SLUG, 'book_fragments', name='book_fragments'), url(r'^(?P[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'), url(r'^audiobooki/(?Pmp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'), -) + url(r'^custompdf$', CustomPDFFormView(), name='custom_pdf_form'), + url(r'^custompdf/(?P%s).pdf' % SLUG, 'download_custom_pdf'), + +) diff --git a/apps/catalogue/utils.py b/apps/catalogue/utils.py index 0134701a6..a48ec0370 100644 --- a/apps/catalogue/utils.py +++ b/apps/catalogue/utils.py @@ -8,7 +8,10 @@ import random import time from base64 import urlsafe_b64encode +from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect from django.core.files.uploadedfile import UploadedFile +from django.core.files.base import File +from django.core.files.storage import DefaultStorage from django.utils.hashcompat import sha_constructor from django.conf import settings from celery.task import task @@ -17,8 +20,9 @@ from errno import EEXIST, ENOENT from fcntl import flock, LOCK_EX from zipfile import ZipFile -from librarian import DocProvider - +from reporting.utils import read_chunks +from celery.task import task +import catalogue.models # Use the system (hardware-based) random number generator if it exists. if hasattr(random, 'SystemRandom'): @@ -55,19 +59,6 @@ class ExistingFile(UploadedFile): pass -class ORMDocProvider(DocProvider): - """Used for getting books' children.""" - - def __init__(self, book): - self.book = book - - def by_slug(self, slug): - if slug == self.book.slug: - return self.book.xml_file - else: - return type(self.book).objects.get(slug=slug).xml_file - - class LockFile(object): """ A file lock monitor class; createas an ${objname}.lock @@ -131,3 +122,64 @@ def remove_zip(zip_slug): except OSError as oe: if oe.errno != ENOENT: raise oe + + +class AttachmentHttpResponse(HttpResponse): + """Response serving a file to be downloaded. + """ + def __init__ (self, file_path, file_name, mimetype): + super(AttachmentHttpResponse, self).__init__(mimetype=mimetype) + self['Content-Disposition'] = 'attachment; filename=%s' % file_name + self.file_path = file_path + self.file_name = file_name + + with open(DefaultStorage().path(self.file_path)) as f: + for chunk in read_chunks(f): + self.write(chunk) + +@task +def async_build_pdf(book_id, customizations, file_name): + """ + A celery task to generate pdf files. + Accepts the same args as Book.build_pdf, but with book id as first parameter + instead of Book instance + """ + book = catalogue.models.Book.objects.get(id=book_id) + print "will gen %s" % DefaultStorage().path(file_name) + if not DefaultStorage().exists(file_name): + book.build_pdf(customizations=customizations, file_name=file_name) + print "done." + + +class MultiQuerySet(object): + def __init__(self, *args, **kwargs): + self.querysets = args + self._count = None + + def count(self): + if not self._count: + self._count = sum(len(qs) for qs in self.querysets) + return self._count + + def __len__(self): + return self.count() + + def __getitem__(self, item): + try: + indices = (offset, stop, step) = item.indices(self.count()) + except AttributeError: + # it's not a slice - make it one + return self[item : item + 1][0] + items = [] + total_len = stop - offset + for qs in self.querysets: + if len(qs) < offset: + offset -= len(qs) + else: + items += list(qs[offset:stop]) + if len(items) >= total_len: + return items + else: + offset = 0 + stop = total_len - len(items) + continue \ No newline at end of file diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index bf0c42f70..f57797da1 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -17,56 +17,39 @@ from django.utils.datastructures import SortedDict from django.views.decorators.http import require_POST from django.contrib import auth from django.contrib.auth.forms import UserCreationForm, AuthenticationForm -from django.utils import simplejson -from django.utils.functional import Promise -from django.utils.encoding import force_unicode from django.utils.http import urlquote_plus from django.views.decorators import cache from django.utils import translation from django.utils.translation import ugettext as _ from django.views.generic.list_detail import object_list +from ajaxable.utils import LazyEncoder, JSONResponse, AjaxableFormView + from catalogue import models from catalogue import forms -from catalogue.utils import split_tags +from catalogue.utils import (split_tags, AttachmentHttpResponse, + async_build_pdf, MultiQuerySet) +from catalogue.tasks import touch_tag from pdcounter import models as pdcounter_models from pdcounter import views as pdcounter_views from suggest.forms import PublishingSuggestForm +from picture.models import Picture +from os import path staff_required = user_passes_test(lambda user: user.is_staff) -class LazyEncoder(simplejson.JSONEncoder): - def default(self, obj): - if isinstance(obj, Promise): - return force_unicode(obj) - return obj - -# shortcut for JSON reponses -class JSONResponse(HttpResponse): - def __init__(self, data={}, callback=None, **kwargs): - # get rid of mimetype - kwargs.pop('mimetype', None) - data = simplejson.dumps(data) - if callback: - data = callback + "(" + data + ");" - super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs) - - -def main_page(request): - if request.user.is_authenticated(): - shelves = models.Tag.objects.filter(category='set', user=request.user) - new_set_form = forms.NewSetForm() - - tags = models.Tag.objects.exclude(category__in=('set', 'book')) +def catalogue(request): + tags = models.Tag.objects.exclude( + category__in=('set', 'book')).exclude(book_count=0) + tags = list(tags) for tag in tags: - tag.count = tag.get_count() + tag.count = tag.book_count categories = split_tags(tags) fragment_tags = categories.get('theme', []) - form = forms.SearchForm() - return render_to_response('catalogue/main_page.html', locals(), + return render_to_response('catalogue/catalogue.html', locals(), context_instance=RequestContext(request)) @@ -74,8 +57,6 @@ def book_list(request, filter=None, template_name='catalogue/book_list.html', context=None): """ generates a listing of all books, optionally filtered with a test function """ - form = forms.SearchForm() - books_by_author, orphans, books_by_parent = models.Book.book_list(filter) books_nav = SortedDict() for tag in books_by_author: @@ -122,6 +103,7 @@ def differentiate_tags(request, tags, ambiguous_slugs): def tagged_object_list(request, tags=''): + # import pdb; pdb.set_trace() try: tags = models.Tag.get_tag_list(tags) except models.Tag.DoesNotExist: @@ -200,28 +182,29 @@ def tagged_object_list(request, tags=''): only_author = len(tags) == 1 and tags[0].category == 'author' objects = models.Book.objects.none() - return object_list( - request, - objects, - template_name='catalogue/tagged_object_list.html', - extra_context={ + # Add pictures + objects = MultiQuerySet(Picture.tagged.with_all(tags), objects) + + return render_to_response('catalogue/tagged_object_list.html', + { + 'object_list': objects, 'categories': categories, 'only_shelf': only_shelf, 'only_author': only_author, 'only_my_shelf': only_my_shelf, 'formats_form': forms.DownloadFormatsForm(), 'tags': tags, - } - ) + }, + context_instance=RequestContext(request)) -def book_fragments(request, book_slug, theme_slug): - book = get_object_or_404(models.Book, slug=book_slug) - book_tag = get_object_or_404(models.Tag, slug='l-' + book_slug, category='book') +def book_fragments(request, slug, theme_slug): + book = get_object_or_404(models.Book, slug=slug) + + book_tag = book.book_tag() theme = get_object_or_404(models.Tag, slug=theme_slug, category='theme') fragments = models.Fragment.tagged.with_all([book_tag, theme]) - form = forms.SearchForm() return render_to_response('catalogue/book_fragments.html', locals(), context_instance=RequestContext(request)) @@ -230,13 +213,13 @@ def book_detail(request, slug): try: book = models.Book.objects.get(slug=slug) except models.Book.DoesNotExist: - return pdcounter_views.book_stub_detail(request, slug) + return pdcounter_views.book_stub_detail(request, kwargs['slug']) book_tag = book.book_tag() tags = list(book.tags.filter(~Q(category='set'))) categories = split_tags(tags) book_children = book.children.all().order_by('parent_number', 'sort_key') - + _book = book parents = [] while _book.parent: @@ -252,25 +235,52 @@ def book_detail(request, slug): extra_info = book.get_extra_info_value() hide_about = extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl') + custom_pdf_form = forms.CustomPDFForm() + return render_to_response('catalogue/book_detail.html', locals(), + context_instance=RequestContext(request)) + + +def player(request, slug): + book = get_object_or_404(models.Book, slug=slug) + if not book.has_media('mp3'): + raise Http404 + + ogg_files = {} + for m in book.media.filter(type='ogg').order_by(): + ogg_files[m.name] = m + + audiobooks = [] + have_oggs = True projects = set() - for m in book.media.filter(type='mp3'): + for mp3 in book.media.filter(type='mp3'): # ogg files are always from the same project - meta = m.get_extra_info_value() + meta = mp3.get_extra_info_value() project = meta.get('project') if not project: # temporary fallback project = u'CzytamySłuchając' projects.add((project, meta.get('funded_by', ''))) + + media = {'mp3': mp3} + + ogg = ogg_files.get(mp3.name) + if ogg: + media['ogg'] = ogg + else: + have_oggs = False + audiobooks.append(media) + print audiobooks + projects = sorted(projects) - form = forms.SearchForm() - return render_to_response('catalogue/book_detail.html', locals(), + return render_to_response('catalogue/player.html', locals(), context_instance=RequestContext(request)) def book_text(request, slug): book = get_object_or_404(models.Book, slug=slug) + if not book.has_html_file(): raise Http404 book_themes = {} @@ -402,7 +412,7 @@ def books_starting_with(prefix): def find_best_matches(query, user=None): - """ Finds a Book, Tag, BookStub or Author best matching a query. + """ Finds a models.Book, Tag, models.BookStub or Author best matching a query. Returns a with: - zero elements when nothing is found, @@ -457,7 +467,7 @@ def search(request): context_instance=RequestContext(request)) else: form = PublishingSuggestForm(initial={"books": prefix + ", "}) - return render_to_response('catalogue/search_no_hits.html', + return render_to_response('catalogue/search_no_hits.html', {'tags':tag_list, 'prefix':prefix, "pubsuggest_form": form}, context_instance=RequestContext(request)) @@ -468,7 +478,7 @@ def tags_starting_with(request): if len(prefix) < 2: return HttpResponse('') tags_list = [] - result = "" + result = "" for tag in _tags_starting_with(prefix, request.user): if not tag.name in tags_list: result += "\n" + tag.name @@ -509,6 +519,7 @@ def book_sets(request, slug): return HttpResponse(_('

To maintain your shelves you need to be logged in.

')) book = get_object_or_404(models.Book, slug=slug) + user_sets = models.Tag.objects.filter(category='set', user=request.user) book_sets = book.tags.filter(category='set', user=request.user) @@ -519,12 +530,10 @@ def book_sets(request, slug): new_shelves = [models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']] for shelf in [shelf for shelf in old_shelves if shelf not in new_shelves]: - shelf.book_count = None - shelf.save() + touch_tag(shelf) for shelf in [shelf for shelf in new_shelves if shelf not in old_shelves]: - shelf.book_count = None - shelf.save() + touch_tag(shelf) book.tags = new_shelves + list(book.tags.filter(~Q(category='set') | ~Q(user=request.user))) if request.is_ajax(): @@ -542,15 +551,14 @@ def book_sets(request, slug): @login_required @require_POST @cache.never_cache -def remove_from_shelf(request, shelf, book): - book = get_object_or_404(models.Book, slug=book) +def remove_from_shelf(request, shelf, slug): + book = get_object_or_404(models.Book, slug=slug) + shelf = get_object_or_404(models.Tag, slug=shelf, category='set', user=request.user) if shelf in book.tags: models.Tag.objects.remove_tag(book, shelf) - - shelf.book_count = None - shelf.save() + touch_tag(shelf) return HttpResponse(_('Book was successfully removed from the shelf')) else: @@ -588,31 +596,17 @@ def download_shelf(request, slug): if form.is_valid(): formats = form.cleaned_data['formats'] if len(formats) == 0: - formats = ['pdf', 'epub', 'mobi', 'odt', 'txt'] + formats = models.Book.ebook_formats # Create a ZIP archive temp = tempfile.TemporaryFile() archive = zipfile.ZipFile(temp, 'w') - already = set() for book in collect_books(models.Book.tagged.with_all(shelf)): - if 'pdf' in formats and book.pdf_file: - filename = book.pdf_file.path - archive.write(filename, str('%s.pdf' % book.slug)) - if 'mobi' in formats and book.mobi_file: - filename = book.mobi_file.path - archive.write(filename, str('%s.mobi' % book.slug)) - if book.root_ancestor not in already and 'epub' in formats and book.root_ancestor.epub_file: - filename = book.root_ancestor.epub_file.path - archive.write(filename, str('%s.epub' % book.root_ancestor.slug)) - already.add(book.root_ancestor) - if 'odt' in formats and book.has_media("odt"): - for file in book.get_media("odt"): - filename = file.file.path - archive.write(filename, str('%s.odt' % slughifi(file.name))) - if 'txt' in formats and book.txt_file: - filename = book.txt_file.path - archive.write(filename, str('%s.txt' % book.slug)) + for ebook_format in models.Book.ebook_formats: + if ebook_format in formats and book.has_media(ebook_format): + filename = book.get_media(ebook_format).path + archive.write(filename, str('%s.%s' % (book.slug, ebook_format))) archive.close() response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed') @@ -631,20 +625,14 @@ def shelf_book_formats(request, shelf): """ shelf = get_object_or_404(models.Tag, slug=shelf, category='set') - formats = {'pdf': False, 'epub': False, 'mobi': False, 'odt': False, 'txt': False} + formats = {} + for ebook_format in models.Book.ebook_formats: + formats[ebook_format] = False for book in collect_books(models.Book.tagged.with_all(shelf)): - if book.pdf_file: - formats['pdf'] = True - if book.root_ancestor.epub_file: - formats['epub'] = True - if book.mobi_file: - formats['mobi'] = True - if book.txt_file: - formats['txt'] = True - for format in ('odt',): - if book.has_media(format): - formats[format] = True + for ebook_format in models.Book.ebook_formats: + if book.has_media(ebook_format): + formats[ebook_format] = True return HttpResponse(LazyEncoder().encode(formats)) @@ -678,45 +666,6 @@ def delete_shelf(request, slug): return HttpResponseRedirect('/') -# ================== -# = Authentication = -# ================== -@require_POST -@cache.never_cache -def login(request): - form = AuthenticationForm(data=request.POST, prefix='login') - if form.is_valid(): - auth.login(request, form.get_user()) - response_data = {'success': True, 'errors': {}} - else: - response_data = {'success': False, 'errors': form.errors} - return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) - - -@require_POST -@cache.never_cache -def register(request): - registration_form = UserCreationForm(request.POST, prefix='registration') - if registration_form.is_valid(): - user = registration_form.save() - user = auth.authenticate( - username=registration_form.cleaned_data['username'], - password=registration_form.cleaned_data['password1'] - ) - auth.login(request, user) - response_data = {'success': True, 'errors': {}} - else: - response_data = {'success': False, 'errors': registration_form.errors} - return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) - - -@cache.never_cache -def logout_then_redirect(request): - auth.logout(request) - return HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?=')) - - - # ========= # = Admin = # ========= @@ -741,14 +690,6 @@ def import_book(request): return HttpResponse(_("Error importing file: %r") % book_import_form.errors) - -def clock(request): - """ Provides server time for jquery.countdown, - in a format suitable for Date.parse() - """ - return HttpResponse(datetime.now().strftime('%Y/%m/%d %H:%M:%S')) - - # info views for API def book_info(request, id, lang='pl'): @@ -764,13 +705,50 @@ def tag_info(request, id): return HttpResponse(tag.description) -def download_zip(request, format, slug): +def download_zip(request, format, slug=None): url = None - if format in ('pdf', 'epub', 'mobi'): + if format in models.Book.ebook_formats: url = models.Book.zip_format(format) - elif format == 'audiobook' and slug is not None: - book = models.Book.objects.get(slug=slug) - url = book.zip_audiobooks() + elif format in ('mp3', 'ogg') and slug is not None: + book = get_object_or_404(models.Book, slug=slug) + url = book.zip_audiobooks(format) else: raise Http404('No format specified for zip package') return HttpResponseRedirect(urlquote_plus(settings.MEDIA_URL + url, safe='/?=')) + + +def download_custom_pdf(request, slug, method='GET'): + book = get_object_or_404(models.Book, slug=slug) + + if request.method == method: + form = forms.CustomPDFForm(method == 'GET' and request.GET or request.POST) + if form.is_valid(): + cust = form.customizations + pdf_file = models.get_customized_pdf_path(book, cust) + + if not path.exists(pdf_file): + result = async_build_pdf.delay(book.id, cust, pdf_file) + result.wait() + return AttachmentHttpResponse(file_name=("%s.pdf" % book.slug), file_path=pdf_file, mimetype="application/pdf") + else: + raise Http404(_('Incorrect customization options for PDF')) + else: + raise Http404(_('Bad method')) + + +class CustomPDFFormView(AjaxableFormView): + form_class = forms.CustomPDFForm + title = _('Download custom PDF') + submit = _('Download') + + def __call__(self, request): + from copy import copy + if request.method == 'POST': + request.GET = copy(request.GET) + request.GET['next'] = "%s?%s" % (reverse('catalogue.views.download_custom_pdf', args=[request.GET['slug']]), + request.POST.urlencode()) + return super(CustomPDFFormView, self).__call__(request) + + + def success(self, *args): + pass diff --git a/apps/dictionary/templates/dictionary/note_list.html b/apps/dictionary/templates/dictionary/note_list.html index e0b10f3c2..6eac93a89 100755 --- a/apps/dictionary/templates/dictionary/note_list.html +++ b/apps/dictionary/templates/dictionary/note_list.html @@ -1,17 +1,16 @@ {% extends "base.html" %} {% load i18n pagination_tags %} -{% load catalogue_tags %} {% block bodyid %}footnotes{% endblock %} -{% block title %}{% trans "Footnotes on WolneLektury.pl" %}{% endblock %} +{% block titleextra %}{% trans "Footnotes" %}{% endblock %} {% block body %}

{% trans "Footnotes" %}

- {% search_form %} +

{% trans "By first letter" %}: @@ -52,4 +51,6 @@ {% endif %} +

+ {% endblock %} diff --git a/apps/dictionary/views.py b/apps/dictionary/views.py index e12006366..7b9cd5313 100755 --- a/apps/dictionary/views.py +++ b/apps/dictionary/views.py @@ -7,7 +7,6 @@ from catalogue.forms import SearchForm from dictionary.models import Note def letter_notes(request, letter=None): - form = SearchForm() letters = ["0-9"] + [chr(a) for a in range(ord('a'), ord('z')+1)] objects = Note.objects.all() if letter == "0-9": diff --git a/apps/infopages/admin.py b/apps/infopages/admin.py index 66f2996c5..e5bc93cc6 100644 --- a/apps/infopages/admin.py +++ b/apps/infopages/admin.py @@ -4,6 +4,6 @@ from modeltranslation.admin import TranslationAdmin from infopages.models import InfoPage class InfoPageAdmin(TranslationAdmin): - list_display = ('title',) + list_display = ('title', 'slug', 'main_page') admin.site.register(InfoPage, InfoPageAdmin) \ No newline at end of file diff --git a/apps/infopages/migrations/0002_auto__del_field_infopage_page_title__del_field_infopage_page_title_en_.py b/apps/infopages/migrations/0002_auto__del_field_infopage_page_title__del_field_infopage_page_title_en_.py new file mode 100644 index 000000000..6ecd60b8f --- /dev/null +++ b/apps/infopages/migrations/0002_auto__del_field_infopage_page_title__del_field_infopage_page_title_en_.py @@ -0,0 +1,111 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting field 'InfoPage.page_title' + db.delete_column('infopages_infopage', 'page_title') + + # Deleting field 'InfoPage.page_title_en' + db.delete_column('infopages_infopage', 'page_title_en') + + # Deleting field 'InfoPage.page_title_es' + db.delete_column('infopages_infopage', 'page_title_es') + + # Deleting field 'InfoPage.page_title_fr' + db.delete_column('infopages_infopage', 'page_title_fr') + + # Deleting field 'InfoPage.page_title_uk' + db.delete_column('infopages_infopage', 'page_title_uk') + + # Deleting field 'InfoPage.page_title_de' + db.delete_column('infopages_infopage', 'page_title_de') + + # Deleting field 'InfoPage.page_title_lt' + db.delete_column('infopages_infopage', 'page_title_lt') + + # Deleting field 'InfoPage.page_title_pl' + db.delete_column('infopages_infopage', 'page_title_pl') + + # Deleting field 'InfoPage.page_title_ru' + db.delete_column('infopages_infopage', 'page_title_ru') + + # Adding field 'InfoPage.main_page' + db.add_column('infopages_infopage', 'main_page', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Adding field 'InfoPage.page_title' + db.add_column('infopages_infopage', 'page_title', self.gf('django.db.models.fields.CharField')(default='', max_length=120, blank=True), keep_default=False) + + # Adding field 'InfoPage.page_title_en' + db.add_column('infopages_infopage', 'page_title_en', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False) + + # Adding field 'InfoPage.page_title_es' + db.add_column('infopages_infopage', 'page_title_es', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False) + + # Adding field 'InfoPage.page_title_fr' + db.add_column('infopages_infopage', 'page_title_fr', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False) + + # Adding field 'InfoPage.page_title_uk' + db.add_column('infopages_infopage', 'page_title_uk', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False) + + # Adding field 'InfoPage.page_title_de' + db.add_column('infopages_infopage', 'page_title_de', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False) + + # Adding field 'InfoPage.page_title_lt' + db.add_column('infopages_infopage', 'page_title_lt', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False) + + # Adding field 'InfoPage.page_title_pl' + db.add_column('infopages_infopage', 'page_title_pl', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False) + + # Adding field 'InfoPage.page_title_ru' + db.add_column('infopages_infopage', 'page_title_ru', self.gf('django.db.models.fields.CharField')(max_length=120, null=True, blank=True), keep_default=False) + + # Deleting field 'InfoPage.main_page' + db.delete_column('infopages_infopage', 'main_page') + + + models = { + 'infopages.infopage': { + 'Meta': {'ordering': "('main_page', 'slug')", 'object_name': 'InfoPage'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'left_column': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'left_column_de': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'left_column_en': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'left_column_es': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'left_column_fr': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'left_column_lt': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'left_column_pl': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'left_column_ru': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'left_column_uk': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'main_page': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'right_column': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'right_column_de': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'right_column_en': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'right_column_es': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'right_column_fr': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'right_column_lt': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'right_column_pl': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'right_column_ru': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'right_column_uk': ('django.db.models.fields.TextField', [], {'null': True, 'blank': True}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120', 'blank': 'True'}), + 'title_de': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}), + 'title_en': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}), + 'title_es': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}), + 'title_fr': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}), + 'title_lt': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}), + 'title_pl': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}), + 'title_ru': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}), + 'title_uk': ('django.db.models.fields.CharField', [], {'max_length': '120', 'null': True, 'blank': True}) + } + } + + complete_apps = ['infopages'] diff --git a/apps/infopages/models.py b/apps/infopages/models.py index 9fe7b3287..cf9e9bf66 100644 --- a/apps/infopages/models.py +++ b/apps/infopages/models.py @@ -6,21 +6,22 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ class InfoPage(models.Model): - """ - An InfoPage is used to display a two-column flatpage - """ + """An InfoPage is used to display a two-column flatpage.""" - page_title = models.CharField(_('page title'), max_length=120, blank=True) + main_page = models.IntegerField(_('main page priority'), null=True, blank=True) slug = models.SlugField(_('slug'), max_length=120, unique=True, db_index=True) title = models.CharField(_('title'), max_length=120, blank=True) left_column = models.TextField(_('left column'), blank=True) right_column = models.TextField(_('right column'), blank=True) class Meta: - ordering = ('slug',) + ordering = ('main_page', 'slug',) verbose_name = _('info page') verbose_name_plural = _('info pages') def __unicode__(self): return self.title + @models.permalink + def get_absolute_url(self): + return ('infopage', [self.slug]) diff --git a/apps/infopages/templates/infopages/infopage.html b/apps/infopages/templates/infopages/infopage.html new file mode 100755 index 000000000..db2d49b29 --- /dev/null +++ b/apps/infopages/templates/infopages/infopage.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% load i18n %} +{% load chunks %} + +{% block titleextra %}{{ page.title }}{% endblock %} + +{% block metadescription %}{{ left_column|striptags|truncatewords:10 }}{% endblock %} + +{% block body %} +

{{ page.title }}

+ + {% autoescape off %} +
+
+ {{ left_column }} +
+
+
+
+ {{ right_column }} +
+
+ {% endautoescape %} +{% endblock %} diff --git a/apps/infopages/templates/infopages/on_main.html b/apps/infopages/templates/infopages/on_main.html new file mode 100755 index 000000000..dc0103c61 --- /dev/null +++ b/apps/infopages/templates/infopages/on_main.html @@ -0,0 +1,5 @@ + diff --git a/apps/infopages/templatetags/__init__.py b/apps/infopages/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/infopages/templatetags/infopages_tags.py b/apps/infopages/templatetags/infopages_tags.py new file mode 100755 index 000000000..d7c93ca8f --- /dev/null +++ b/apps/infopages/templatetags/infopages_tags.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django import template +from infopages.models import InfoPage + +register = template.Library() + + +@register.inclusion_tag('infopages/on_main.html') +def infopages_on_main(): + objects = InfoPage.objects.exclude(main_page=None) + return {"objects": objects} diff --git a/apps/infopages/urls.py b/apps/infopages/urls.py new file mode 100755 index 000000000..081e0ef39 --- /dev/null +++ b/apps/infopages/urls.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.conf.urls.defaults import * + + +urlpatterns = patterns('infopages.views', + url(r'^(?P[a-zA-Z0-9_-]+)/$', 'infopage', name='infopage'), +) + diff --git a/apps/infopages/views.py b/apps/infopages/views.py index 07a416bbf..d5dee7630 100644 --- a/apps/infopages/views.py +++ b/apps/infopages/views.py @@ -2,15 +2,24 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext, Template, TemplateSyntaxError -from catalogue.forms import SearchForm from infopages.models import InfoPage + def infopage(request, slug): - form = SearchForm() - object = InfoPage.objects.get(slug=slug) + page = get_object_or_404(InfoPage, slug=slug) + rc = RequestContext(request) + try: + left_column = Template(page.left_column).render(rc) + except TemplateSyntaxError: + left_column = '' + + try: + right_column = Template(page.right_column).render(rc) + except TemplateSyntaxError: + left_column = '' - return render_to_response('info/base.html', locals(), - context_instance=RequestContext(request)) \ No newline at end of file + return render_to_response('infopages/infopage.html', locals(), + context_instance=RequestContext(request)) diff --git a/apps/lesmianator/urls.py b/apps/lesmianator/urls.py index e7bbb48d4..de48644f1 100644 --- a/apps/lesmianator/urls.py +++ b/apps/lesmianator/urls.py @@ -3,11 +3,12 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.conf.urls.defaults import * +from catalogue.models import Book urlpatterns = patterns('lesmianator.views', url(r'^$', 'main_page', name='lesmianator'), url(r'^wiersz/$', 'new_poem', name='new_poem'), - url(r'^lektura/(?P[a-zA-Z0-9-]+)/$', 'poem_from_book', name='poem_from_book'), + url(r'^lektura/(?P[a-z0-9-]+)/$', 'poem_from_book', name='poem_from_book'), url(r'^polka/(?P[a-zA-Z0-9-]+)/$', 'poem_from_set', name='poem_from_set'), url(r'^wiersz/(?P[a-zA-Z0-9-]+)/$', 'get_poem', name='get_poem'), ) diff --git a/apps/lesmianator/views.py b/apps/lesmianator/views.py index 56acb5763..e86febe91 100644 --- a/apps/lesmianator/views.py +++ b/apps/lesmianator/views.py @@ -1,5 +1,6 @@ # Create your views here. +from django.http import Http404 from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from django.contrib.auth.decorators import login_required @@ -7,17 +8,15 @@ from django.views.decorators import cache from catalogue.utils import get_random_hash from catalogue.models import Book, Tag -from catalogue import forms from lesmianator.models import Poem, Continuations def main_page(request): last = Poem.objects.all().order_by('-created_at')[:10] - form = forms.SearchForm() shelves = Tag.objects.filter(user__username='lesmianator') return render_to_response('lesmianator/lesmianator.html', - {"last": last, "form": form, "shelves": shelves}, + {"last": last, "shelves": shelves}, context_instance=RequestContext(request)) diff --git a/apps/lessons/urls.py b/apps/lessons/urls.py index 1d256374b..4b6a3e10b 100644 --- a/apps/lessons/urls.py +++ b/apps/lessons/urls.py @@ -9,9 +9,6 @@ from catalogue import forms urlpatterns = patterns('', url(r'^$', 'django.views.generic.simple.direct_to_template', { 'template': 'lessons/document_list.html', - 'extra_context': { - 'form': forms.SearchForm(), - }, }, name='lessons_document_list'), url(r'^(?P[a-zA-Z0-9_-]+)/$', 'lessons.views.document_detail', name='lessons_document_detail'), diff --git a/apps/lessons/views.py b/apps/lessons/views.py index 242526d86..9314d1cac 100644 --- a/apps/lessons/views.py +++ b/apps/lessons/views.py @@ -3,7 +3,6 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.views.generic.list_detail import object_detail -from catalogue import forms from lessons import models @@ -17,6 +16,4 @@ def document_detail(request, slug): slug_field='slug', queryset=models.Document.objects.all(), template_name=template_name, - extra_context={ - 'form': forms.SearchForm(), - }) + ) diff --git a/apps/opds/views.py b/apps/opds/views.py index c7d38284b..dc094bba7 100644 --- a/apps/opds/views.py +++ b/apps/opds/views.py @@ -5,6 +5,7 @@ from base64 import b64encode import os.path from urlparse import urljoin +from urllib2 import unquote from django.contrib.syndication.views import Feed from django.core.urlresolvers import reverse @@ -16,7 +17,11 @@ from django.contrib.sites.models import Site from basicauth import logged_in_or_basicauth, factory_decorator from catalogue.models import Book, Tag -from catalogue.views import books_starting_with + +from search import Search, SearchResult, JVM +from lucene import Term, QueryWrapperFilter, TermQuery + +import re from stats.utils import piwik_track @@ -229,7 +234,7 @@ class ByCategoryFeed(Feed): return feed['title'] def items(self, feed): - return (tag for tag in Tag.objects.filter(category=feed['category']) if tag.get_count() > 0) + return Tag.objects.filter(category=feed['category']).exclude(book_count=0) def item_title(self, item): return item.name @@ -280,7 +285,7 @@ class UserFeed(Feed): return u"Półki użytkownika %s" % user.username def items(self, user): - return (tag for tag in Tag.objects.filter(category='set', user=user) if tag.get_count() > 0) + return Tag.objects.filter(category='set', user=user).exclude(book_count=0) def item_title(self, item): return item.name @@ -316,20 +321,124 @@ class UserSetFeed(AcquisitionFeed): # no class decorators in python 2.5 #UserSetFeed = factory_decorator(logged_in_or_basicauth())(UserSetFeed) + @piwik_track class SearchFeed(AcquisitionFeed): description = u"Wyniki wyszukiwania na stronie WolneLektury.pl" title = u"Wyniki wyszukiwania" + + INLINE_QUERY_RE = re.compile(r"(author:(?P[^ ]+)|title:(?P[^ ]+)|categories:(?P<categories>[^ ]+)|description:(?P<description>[^ ]+))") def get_object(self, request): - return request.GET.get('q', '') + """ + For OPDS 1.1 We should handle a query for search terms + and criteria provided either as opensearch or 'inline' query. + OpenSearch defines fields: atom:author, atom:contributor (treated as translator), + atom:title. Inline query provides author, title, categories (treated as book tags), + description (treated as content search terms). + + if search terms are provided, we shall search for books + according to Hint information (from author & contributror & title). + + but if search terms are empty, we should do a different search + (perhaps for is_book=True) + + """ + JVM.attachCurrentThread() + + query = request.GET.get('q', '') + + inline_criteria = re.findall(self.INLINE_QUERY_RE, query) + if inline_criteria: + def get_criteria(criteria, name, position): + e = filter(lambda el: el[0][0:len(name)] == name, criteria) + print e + if not e: + return None + c = e[0][position] + print c + if c[0] == '"' and c[-1] == '"': + c = c[1:-1] + c = c.replace('+', ' ') + return c + + #import pdb; pdb.set_trace() + author = get_criteria(inline_criteria, 'author', 1) + title = get_criteria(inline_criteria, 'title', 2) + translator = None + categories = get_criteria(inline_criteria, 'categories', 3) + query = get_criteria(inline_criteria, 'description', 4) + else: + author = request.GET.get('author', '') + title = request.GET.get('title', '') + translator = request.GET.get('translator', '') + categories = None + fuzzy = False + + + srch = Search() + hint = srch.hint() + + # Scenario 1: full search terms provided. + # Use auxiliarry information to narrow it and make it better. + if query: + filters = [] + + if author: + print "narrow to author %s" % author + hint.tags(srch.search_tags(author, filter=srch.term_filter(Term('tag_category', 'author')))) + + if translator: + print "filter by translator %s" % translator + filters.append(QueryWrapperFilter( + srch.make_phrase(srch.get_tokens(translator, field='translators'), + field='translators'))) + + if categories: + filters.append(QueryWrapperFilter( + srch.make_phrase(srch.get_tokens(categories, field="tag_name_pl"), + field='tag_name_pl'))) + + flt = srch.chain_filters(filters) + if title: + print "hint by book title %s" % title + q = srch.make_phrase(srch.get_tokens(title, field='title'), field='title') + hint.books(*srch.search_books(q, filter=flt)) + + toks = srch.get_tokens(query) + print "tokens: %s" % toks + # import pdb; pdb.set_trace() + results = SearchResult.aggregate(srch.search_perfect_book(toks, fuzzy=fuzzy, hint=hint), + srch.search_perfect_parts(toks, fuzzy=fuzzy, hint=hint), + srch.search_everywhere(toks, fuzzy=fuzzy, hint=hint)) + results.sort(reverse=True) + return [r.book for r in results] + else: + # Scenario 2: since we no longer have to figure out what the query term means to the user, + # we can just use filters and not the Hint class. + filters = [] + + fields = { + 'author': author, + 'translators': translator, + 'title': title + } + + for fld, q in fields.items(): + if q: + filters.append(QueryWrapperFilter( + srch.make_phrase(srch.get_tokens(q, field=fld), field=fld))) + + flt = srch.chain_filters(filters) + books = srch.search_books(TermQuery(Term('is_book', 'true')), filter=flt) + return books def get_link(self, query): - return "%s?q=%s" % (reverse('search'), query) + return "%s?q=%s" % (reverse('search'), query) - def items(self, query): + def items(self, books): try: - return books_starting_with(query) + return books except ValueError: # too short a query return [] diff --git a/apps/pdcounter/urls.py b/apps/pdcounter/urls.py deleted file mode 100644 index 232513754..000000000 --- a/apps/pdcounter/urls.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.conf.urls.defaults import * - - -urlpatterns = patterns('catalogue.views', - url(r'^$', 'main_page', name='main_page'), - url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/formaty/$', 'shelf_book_formats', name='shelf_book_formats'), - url(r'^polki/(?P<shelf>[a-zA-Z0-9-]+)/(?P<book>[a-zA-Z0-9-0-]+)/usun$', 'remove_from_shelf', name='remove_from_shelf'), - url(r'^polki/$', 'user_shelves', name='user_shelves'), - url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)/usun/$', 'delete_shelf', name='delete_shelf'), - url(r'^polki/(?P<slug>[a-zA-Z0-9-]+)\.zip$', 'download_shelf', name='download_shelf'), - url(r'^lektury/', 'book_list', name='book_list'), - url(r'^audiobooki/', 'audiobook_list', name='audiobook_list'), - url(r'^daisy/', 'daisy_list', name='daisy_list'), - url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/polki/', 'book_sets', name='book_shelves'), - url(r'^polki/nowa/$', 'new_set', name='new_set'), - url(r'^tags/$', 'tags_starting_with', name='hint'), - url(r'^jtags/$', 'json_tags_starting_with', name='jhint'), - url(r'^szukaj/$', 'search', name='search'), - - # tools - url(r'^zegar/$', 'clock', name='clock'), - url(r'^xmls.zip$', 'xmls', name='xmls'), - url(r'^epubs.tar$', 'epubs', name='epubs'), - - # Public interface. Do not change this URLs. - url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)\.html$', 'book_text', name='book_text'), - url(r'^lektura/(?P<slug>[a-zA-Z0-9-]+)/$', 'book_detail', name='book_detail'), - url(r'^lektura/(?P<book_slug>[a-zA-Z0-9-]+)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$', - 'book_fragments', name='book_fragments'), - url(r'^(?P<tags>[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'), -) - diff --git a/apps/pdcounter/views.py b/apps/pdcounter/views.py index efcfe95ee..b07ee11e9 100644 --- a/apps/pdcounter/views.py +++ b/apps/pdcounter/views.py @@ -7,16 +7,14 @@ from django.template import RequestContext from django.shortcuts import render_to_response, get_object_or_404 from pdcounter import models -from catalogue import forms from suggest.forms import PublishingSuggestForm def book_stub_detail(request, slug): book = get_object_or_404(models.BookStub, slug=slug) pd_counter = book.pd - form = forms.SearchForm() - pubsuggest_form = PublishingSuggestForm( + form = PublishingSuggestForm( initial={"books": u"%s — %s, \n" % (book.author, book.title)}) return render_to_response('pdcounter/book_stub_detail.html', locals(), @@ -26,9 +24,8 @@ def book_stub_detail(request, slug): def author_detail(request, slug): author = get_object_or_404(models.Author, slug=slug) pd_counter = author.goes_to_pd() - form = forms.SearchForm() - pubsuggest_form = PublishingSuggestForm(initial={"books": author.name + ", \n"}) + form = PublishingSuggestForm(initial={"books": author.name + ", \n"}) return render_to_response('pdcounter/author_detail.html', locals(), context_instance=RequestContext(request)) diff --git a/apps/picture/__init__.py b/apps/picture/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/picture/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/picture/admin.py b/apps/picture/admin.py new file mode 100644 index 000000000..fb6bcf261 --- /dev/null +++ b/apps/picture/admin.py @@ -0,0 +1,9 @@ + +from django.contrib import admin +from picture.models import Picture +from sorl.thumbnail.admin import AdminImageMixin + +class PictureAdmin(AdminImageMixin, admin.ModelAdmin): + pass + +admin.site.register(Picture, PictureAdmin) diff --git a/apps/picture/forms.py b/apps/picture/forms.py new file mode 100644 index 000000000..9d0c5a54b --- /dev/null +++ b/apps/picture/forms.py @@ -0,0 +1,35 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ +from picture.models import Picture + + +class PictureImportForm(forms.Form): + picture_xml_file = forms.FileField(required=False) + picture_xml = forms.CharField(required=False) + picture_image_file = forms.FileField(required=False) + picture_image_data = forms.CharField(required=False) + + def clean(self): + from base64 import b64decode + from django.core.files.base import ContentFile + + if not self.cleaned_data['picture_xml_file']: + if self.cleaned_data['picture_xml']: + self.cleaned_data['picture_xml_file'] = \ + ContentFile(self.cleaned_data['picture_xml'].encode('utf-8')) + else: + raise forms.ValidationError(_("Please supply an XML.")) + + if not self.cleaned_data['picture_image_file']: + if self.cleaned_data['picture_image_data']: + self.cleaned_data['picture_image_file'] = \ + ContentFile(b64decode( + self.cleaned_data['picture_image_data'])) + else: + raise forms.ValidationError(_("Please supply an image.")) + + return super(PictureImportForm, self).clean() + + def save(self, commit=True, **kwargs): + return Picture.from_xml_file(self.cleaned_data['picture_xml_file'], image_file=self.cleaned_data['picture_image_file'], + overwrite=True, **kwargs) diff --git a/apps/picture/models.py b/apps/picture/models.py new file mode 100644 index 000000000..0217d8bad --- /dev/null +++ b/apps/picture/models.py @@ -0,0 +1,195 @@ +from django.db import models +import catalogue.models +from django.db.models import permalink +from sorl.thumbnail import ImageField +from django.conf import settings +from django.core.files.storage import FileSystemStorage +from django.utils.datastructures import SortedDict +from django.template.loader import render_to_string +from django.core.cache import cache +from catalogue.utils import split_tags +from django.utils.safestring import mark_safe +from slughifi import slughifi + +from django.utils.translation import ugettext_lazy as _ +from newtagging import managers +from os import path + + +picture_storage = FileSystemStorage(location=path.join(settings.MEDIA_ROOT, 'pictures'), base_url=settings.MEDIA_URL + "pictures/") + + +class Picture(models.Model): + """ + Picture resource. + + """ + title = models.CharField(_('title'), max_length=120) + slug = models.SlugField(_('slug'), max_length=120, db_index=True, unique=True) + sort_key = models.CharField(_('sort key'), max_length=120, db_index=True, editable=False) + created_at = models.DateTimeField(_('creation date'), auto_now_add=True, db_index=True) + changed_at = models.DateTimeField(_('creation date'), auto_now=True, db_index=True) + xml_file = models.FileField('xml_file', upload_to="xml", storage=picture_storage) + image_file = ImageField(_('image_file'), upload_to="images", storage=picture_storage) + objects = models.Manager() + tagged = managers.ModelTaggedItemManager(catalogue.models.Tag) + tags = managers.TagDescriptor(catalogue.models.Tag) + + class AlreadyExists(Exception): + pass + + class Meta: + ordering = ('sort_key',) + + verbose_name = _('picture') + verbose_name_plural = _('pictures') + + def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs): + from sortify import sortify + + self.sort_key = sortify(self.title) + + ret = super(Picture, self).save(force_insert, force_update) + + if reset_short_html: + self.reset_short_html() + + return ret + + def __unicode__(self): + return self.title + + @permalink + def get_absolute_url(self): + return ('picture.views.picture_detail', [self.slug]) + + @classmethod + def from_xml_file(cls, xml_file, image_file=None, overwrite=False): + """ + Import xml and it's accompanying image file. + If image file is missing, it will be fetched by librarian.picture.ImageStore + which looks for an image file in the same directory the xml is, with extension matching + its mime type. + """ + from sortify import sortify + from django.core.files import File + from librarian.picture import WLPicture, ImageStore + close_xml_file = False + close_image_file = False + # class SimpleImageStore(object): + # def path(self_, slug, mime_type): + # """Returns the image file. Ignores slug ad mime_type.""" + # return image_file + + if image_file is not None and not isinstance(image_file, File): + image_file = File(open(image_file)) + close_image_file = True + + if not isinstance(xml_file, File): + xml_file = File(open(xml_file)) + close_xml_file = True + + try: + # use librarian to parse meta-data + picture_xml = WLPicture.from_file(xml_file, + image_store=ImageStore(picture_storage.path('images'))) + # image_store=SimpleImageStore + + picture, created = Picture.objects.get_or_create(slug=picture_xml.slug) + if not created and not overwrite: + raise Picture.AlreadyExists('Picture %s already exists' % picture_xml.slug) + + picture.title = picture_xml.picture_info.title + + motif_tags = set() + for part in picture_xml.partiter(): + for motif in part['themes']: + tag, created = catalogue.models.Tag.objects.get_or_create(slug=slughifi(motif), category='theme') + if created: + tag.name = motif + tag.sort_key = sortify(tag.name) + tag.save() + motif_tags.add(tag) + + picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info) + \ + list(motif_tags) + + if image_file is not None: + img = image_file + else: + img = picture_xml.image_file() + + # FIXME: hardcoded extension + picture.image_file.save(path.basename(picture_xml.image_path), File(img)) + + picture.xml_file.save("%s.xml" % picture.slug, File(xml_file)) + picture.save() + finally: + if close_xml_file: + xml_file.close() + if close_image_file: + image_file.close() + return picture + + @classmethod + def picture_list(cls, filter=None): + """Generates a hierarchical listing of all pictures + Pictures are optionally filtered with a test function. + """ + + pics = cls.objects.all().order_by('sort_key')\ + .only('title', 'slug', 'image_file') + + if filter: + pics = pics.filter(filter).distinct() + + pics_by_author = SortedDict() + orphans = [] + for tag in catalogue.models.Tag.objects.filter(category='author'): + pics_by_author[tag] = [] + + for pic in pics: + authors = list(pic.tags.filter(category='author')) + if authors: + for author in authors: + pics_by_author[author].append(pic) + else: + orphans.append(pic) + + return pics_by_author, orphans + + @property + def info(self): + if not hasattr(self, '_info'): + from librarian import dcparser + from librarian import picture + info = dcparser.parse(self.xml_file.path, picture.PictureInfo) + self._info = info + return self._info + + def reset_short_html(self): + if self.id is None: + return + + cache_key = "Picture.short_html/%d" % (self.id) + cache.delete(cache_key) + + def short_html(self): + if self.id: + cache_key = "Picture.short_html/%d" % (self.id) + short_html = cache.get(cache_key) + else: + short_html = None + + if short_html is not None: + return mark_safe(short_html) + else: + tags = self.tags.filter(category__in=('author', 'kind', 'epoch')) + tags = split_tags(tags) + + short_html = unicode(render_to_string('picture/picture_short.html', + {'picture': self, 'tags': tags})) + + if self.id: + cache.set(cache_key, short_html, catalogue.models.CACHE_FOREVER) + return mark_safe(short_html) diff --git a/apps/picture/templates/admin/picture/picture/change_list.html b/apps/picture/templates/admin/picture/picture/change_list.html new file mode 100755 index 000000000..e150da51e --- /dev/null +++ b/apps/picture/templates/admin/picture/picture/change_list.html @@ -0,0 +1,11 @@ +{% extends "admin/change_list.html" %} +{% load i18n %} + +{% block content %} + <form action="{% url import_picture %}" method="post" enctype="multipart/form-data"> + <p>XML: <input type="file" id="id_picture_xml_file" name="picture_xml_file" /><br/> + {% trans "Image" %}: <input type="file" id="id_picture_image_file" name="picture_image_file" /><br/> + <input type="submit" value="{% trans "Import picture" %}"/></p> + </form> + {{ block.super }} +{% endblock content %} \ No newline at end of file diff --git a/apps/picture/tests/__init__.py b/apps/picture/tests/__init__.py new file mode 100644 index 000000000..8817a1c18 --- /dev/null +++ b/apps/picture/tests/__init__.py @@ -0,0 +1 @@ +from picture.tests.picture_import import * diff --git a/apps/picture/tests/files/kandinsky-composition-viii.png b/apps/picture/tests/files/kandinsky-composition-viii.png new file mode 100644 index 000000000..a809eb5c4 Binary files /dev/null and b/apps/picture/tests/files/kandinsky-composition-viii.png differ diff --git a/apps/picture/tests/files/kandinsky-composition-viii.xml b/apps/picture/tests/files/kandinsky-composition-viii.xml new file mode 100644 index 000000000..d9e79cbed --- /dev/null +++ b/apps/picture/tests/files/kandinsky-composition-viii.xml @@ -0,0 +1,36 @@ +<picture> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> + <rdf:Description rdf:about="http://wiki.wolnepodreczniki.pl/Lektury:Andersen/Brzydkie_kaczątko"> + <dc:creator xml:lang="pl">Kandinsky, Vasily</dc:creator> + <dc:title xml:lang="la">composition 8</dc:title> + <dc:publisher xml:lang="pl">Fundacja Nowoczesna Polska</dc:publisher> + <dc:contributor.editor xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Sekuła, Aleksandra</dc:contributor.editor> + <dc:contributor.editor xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Kwiatkowska, Katarzyna</dc:contributor.editor> + <dc:contributor.technical_editor xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">Trzeciak, Weronika</dc:contributor.technical_editor> + <dc:subject.period xml:lang="pl">Modernizm</dc:subject.period> + <dc:subject.type xml:lang="pl">Obraz</dc:subject.type> + <dc:description xml:lang="pl">Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). Reprodukcja cyfrowa wykonana przez Bibliotekę Narodową z egzemplarza pochodzącego ze zbiorów BN.</dc:description> + <dc:description.dimensions xml:lang="pl">55 1/8 x 79 1/8 inches</dc:description.dimensions> + <dc:description.medium xml:lang="pl">Olej na płótnie</dc:description.medium> + <dc:identifier.url xml:lang="pl">http://wolnelektury.pl/katalog/obraz/kandinsky-composition-viii</dc:identifier.url> + <dc:source.URL xml:lang="pl">http://www.ibiblio.org/wm/paint/auth/kandinsky/kandinsky.comp-8.jpg</dc:source.URL> + <dc:source xml:lang="pl">Muzeum Narodowe, inw. 00000001.</dc:source> + <dc:rights xml:lang="pl">Domena publiczna - Vasily Kandinsky zm. ?</dc:rights> + <dc:date.pd xml:lang="pl">1940</dc:date.pd> + <dc:type>Image</dc:type> + <dc:format xml:lang="pl">image/png</dc:format> + <dc:format.dimensions xml:lang="pl">1090 x 755 px</dc:format.dimensions> + <dc:format.checksum.sha1 xml:lang="pl">122b590510ce70cc80e617557f82048ce20f1d7b</dc:format.checksum.sha1> + <dc:date xml:lang="pl">1923</dc:date> + <dc:language xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">eng</dc:language> + </rdf:Description> + </rdf:RDF> + + <sem type="object" object="kosmos"> + <div type="area" x1="17" y1="31" x2="275" y2="309"/> + </sem> + <sem type="theme" theme="nieporządek"> + <div type="area" x1="300" y1="138" x2="976" y2="514"/> + </sem> + +</picture> diff --git a/apps/picture/tests/picture_import.py b/apps/picture/tests/picture_import.py new file mode 100644 index 000000000..91fb35f71 --- /dev/null +++ b/apps/picture/tests/picture_import.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import with_statement + +from django.core.files.base import ContentFile, File +from catalogue.test_utils import * +from catalogue import models +from librarian import WLURI +from picture.models import Picture + +from nose.tools import raises +import tempfile +from os import unlink, path, makedirs + + +class PictureTest(TestCase): + + def test_import(self): + picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml")) + + motifs = set([tag.name for tag in picture.tags if tag.category == 'theme']) + assert motifs == set([u'nieporządek']), 'theme tags are wrong. %s' % motifs + + picture.delete() + + def test_import_with_explicit_image(self): + picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"), + path.join(path.dirname(__file__), "files/kandinsky-composition-viii.png")) + + picture.delete() + diff --git a/apps/picture/views.py b/apps/picture/views.py new file mode 100644 index 000000000..24457e297 --- /dev/null +++ b/apps/picture/views.py @@ -0,0 +1,58 @@ +from picture.models import Picture +from django.contrib.auth.decorators import permission_required +from django.utils.datastructures import SortedDict +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext + + +def picture_list(request, filter=None, template_name='catalogue/picture_list.html'): + """ generates a listing of all books, optionally filtered with a test function """ + + pictures_by_author, orphans = Picture.picture_list() + books_nav = SortedDict() + for tag in pictures_by_author: + if pictures_by_author[tag]: + books_nav.setdefault(tag.sort_key[0], []).append(tag) + + # import pdb; pdb.set_trace() + return render_to_response(template_name, locals(), + context_instance=RequestContext(request)) + + +def picture_detail(request, picture): + picture = get_object_or_404(Picture, slug=picture) + + categories = SortedDict() + for tag in picture.tags: + categories.setdefault(tag.category, []).append(tag) + + picture_themes = [] + + return render_to_response("catalogue/picture_detail.html", locals(), + context_instance=RequestContext(request)) + +# ========= +# = Admin = +# ========= +@permission_required('picture.add_picture') +def import_picture(request): + """docstring for import_book""" + from django.http import HttpResponse + from picture.forms import PictureImportForm + from django.utils.translation import ugettext as _ + + import_form = PictureImportForm(request.POST, request.FILES) + if import_form.is_valid(): + try: + import_form.save() + except: + import sys + import pprint + import traceback + info = sys.exc_info() + exception = pprint.pformat(info[1]) + tb = '\n'.join(traceback.format_tb(info[2])) + return HttpResponse(_("An error occurred: %(exception)s\n\n%(tb)s") % {'exception':exception, 'tb':tb}, mimetype='text/plain') + return HttpResponse(_("Picture imported successfully")) + else: + return HttpResponse(_("Error importing file: %r") % import_form.errors) diff --git a/apps/reporting/templates/reporting/catalogue.texml b/apps/reporting/templates/reporting/catalogue.texml index 93a13d0b2..c17ed0754 100755 --- a/apps/reporting/templates/reporting/catalogue.texml +++ b/apps/reporting/templates/reporting/catalogue.texml @@ -47,7 +47,7 @@ LetterSpace=-1.0 \setlength{\leftmargin}{0em} \setlength{\rightmargin}{0em} \setlength{\textheight}{24cm} -\setlength{\textwidth}{17cm} +\setlength{\textwidth}{17.5cm} \pagestyle{fancy} @@ -98,7 +98,7 @@ LetterSpace=-1.0 \end{flushright} \end{minipage} - \begin{longtable}{p{15cm} p{2cm}} + \begin{longtable}{p{9.5cm} p{5.5cm}r p{2cm}} <TeXML escape="1"> {% book_tree_texml orphans books_by_parent %} diff --git a/apps/reporting/templates/reporting/main.html b/apps/reporting/templates/reporting/main.html index bbfca9964..485610dd7 100755 --- a/apps/reporting/templates/reporting/main.html +++ b/apps/reporting/templates/reporting/main.html @@ -2,23 +2,22 @@ {% load i18n %} {% load reporting_stats catalogue_tags %} -{% block title %}Statystyka w WolneLektury.pl{% endblock %} +{% block titleextra %}{% trans "Reports" %}{% endblock %} {% block bodyid %}reports-stats{% endblock %} {% block body %} <h1>Statystyka</h1> - {% search_form %} - <p><a href="{% url reporting_catalogue_pdf %}">Katalog biblioteki w formacie PDF.</a></p> + <div class="normal-text"> <table class="stats"> <tr><th>Utwory</th></tr> - <tr><td>Wszystkie utwory:</td><td>{% count_books_all %}</td></tr> - <tr><td>Utwory z własną treścią:</td><td>{% count_books_nonempty %}</td></tr> - <tr><td>Utwory bez własnej treści:</td><td>{% count_books_empty %}</td></tr> + <tr><td>Utwory:</td><td>{% count_books %}</td></tr> <tr><td>Niezależne książki:</td><td>{% count_books_root %}</td></tr> + <tr><td>Utwory nadrzędne:</td><td>{% count_books_parent %}</td></tr> + <tr><td>Wszystkie utwory:</td><td>{% count_books_all %}</td></tr> <tr><th>Media</th><th>Liczba</th><th>Rozmiar</th><th>Do wymiany</th></tr> {% for mt in media_types %} @@ -27,11 +26,13 @@ <td>{{ mt.size|filesizeformat }}</td> <td>{{ mt.deprecated }} {% for m in mt.deprecated_files %} - <br/><a href="{{ m.book.get_absolute_url }}">{{ m }}</a> + <br/><a href="{{ m.book.get_absolute_url }}">{% book_title m.book %}: {{ m }}</a> {% endfor %} </td> </tr> {% endfor %} </table> + </div> + {% endblock %} diff --git a/apps/reporting/templatetags/reporting_stats.py b/apps/reporting/templatetags/reporting_stats.py index dceee0001..6f20c087c 100755 --- a/apps/reporting/templatetags/reporting_stats.py +++ b/apps/reporting/templatetags/reporting_stats.py @@ -49,11 +49,11 @@ def count_books_all(): return Book.objects.all().count() @register_counter -def count_books_nonempty(): +def count_books(): return Book.objects.exclude(html_file='').count() @register_counter -def count_books_empty(): +def count_books_parent(): return Book.objects.filter(html_file='').count() @register_counter diff --git a/apps/search/__init__.py b/apps/search/__init__.py new file mode 100644 index 000000000..1451fa214 --- /dev/null +++ b/apps/search/__init__.py @@ -0,0 +1,3 @@ +import lucene + +from index import Index, Search, ReusableIndex, SearchResult, JVM, IndexChecker, IndexStore diff --git a/apps/search/context_processors.py b/apps/search/context_processors.py new file mode 100644 index 000000000..c54525aec --- /dev/null +++ b/apps/search/context_processors.py @@ -0,0 +1,7 @@ + +from catalogue.forms import SearchForm +from django.core.urlresolvers import reverse + + +def search_form(request): + return { 'search_form': SearchForm(reverse('search.views.hint'), request.GET) } diff --git a/apps/search/index.py b/apps/search/index.py new file mode 100644 index 000000000..cc478ee53 --- /dev/null +++ b/apps/search/index.py @@ -0,0 +1,1174 @@ +# -*- coding: utf-8 -*- + +from django.conf import settings +from lucene import SimpleFSDirectory, IndexWriter, CheckIndex, \ + File, Field, Integer, \ + NumericField, Version, Document, JavaError, IndexSearcher, \ + QueryParser, PerFieldAnalyzerWrapper, \ + SimpleAnalyzer, PolishAnalyzer, ArrayList, \ + KeywordAnalyzer, NumericRangeQuery, NumericRangeFilter, BooleanQuery, \ + BlockJoinQuery, BlockJoinCollector, Filter, TermsFilter, ChainedFilter, \ + HashSet, BooleanClause, Term, CharTermAttribute, \ + PhraseQuery, MultiPhraseQuery, StringReader, TermQuery, \ + FuzzyQuery, FuzzyTermEnum, PrefixTermEnum, Sort, Integer, \ + SimpleHTMLFormatter, Highlighter, QueryScorer, TokenSources, TextFragment, \ + BooleanFilter, TermsFilter, FilterClause, QueryWrapperFilter, \ + initVM, CLASSPATH, JArray, JavaError + # KeywordAnalyzer + +# Initialize jvm +JVM = initVM(CLASSPATH) + +import sys +import os +import re +import errno +from librarian import dcparser +from librarian.parser import WLDocument +import catalogue.models +from multiprocessing.pool import ThreadPool +from threading import current_thread +import atexit +import traceback + + +class WLAnalyzer(PerFieldAnalyzerWrapper): + def __init__(self): + polish = PolishAnalyzer(Version.LUCENE_34) + # polish_gap.setPositionIncrementGap(999) + + simple = SimpleAnalyzer(Version.LUCENE_34) + # simple_gap.setPositionIncrementGap(999) + + keyword = KeywordAnalyzer(Version.LUCENE_34) + + # not sure if needed: there's NOT_ANALYZED meaning basically the same + + PerFieldAnalyzerWrapper.__init__(self, polish) + + self.addAnalyzer("tags", simple) + self.addAnalyzer("technical_editors", simple) + self.addAnalyzer("editors", simple) + self.addAnalyzer("url", keyword) + self.addAnalyzer("source_url", keyword) + self.addAnalyzer("source_name", simple) + self.addAnalyzer("publisher", simple) + self.addAnalyzer("authors", simple) + self.addAnalyzer("title", simple) + + self.addAnalyzer("is_book", keyword) + # shouldn't the title have two forms? _pl and simple? + + self.addAnalyzer("themes", simple) + self.addAnalyzer("themes_pl", polish) + + self.addAnalyzer("tag_name", simple) + self.addAnalyzer("tag_name_pl", polish) + + self.addAnalyzer("translators", simple) + + self.addAnalyzer("KEYWORD", keyword) + self.addAnalyzer("SIMPLE", simple) + self.addAnalyzer("POLISH", polish) + + +class IndexStore(object): + """ + Provides access to search index. + + self.store - lucene index directory + """ + def __init__(self): + self.make_index_dir() + self.store = SimpleFSDirectory(File(settings.SEARCH_INDEX)) + + def make_index_dir(self): + try: + os.makedirs(settings.SEARCH_INDEX) + except OSError as exc: + if exc.errno == errno.EEXIST: + pass + else: raise + + +class IndexChecker(IndexStore): + def __init__(self): + IndexStore.__init__(self) + + def check(self): + checker = CheckIndex(self.store) + status = checker.checkIndex() + return status + + +class Snippets(object): + """ + This class manages snippet files for indexed object (book) + the snippets are concatenated together, and their positions and + lengths are kept in lucene index fields. + """ + SNIPPET_DIR = "snippets" + + def __init__(self, book_id): + try: + os.makedirs(os.path.join(settings.SEARCH_INDEX, self.SNIPPET_DIR)) + except OSError as exc: + if exc.errno == errno.EEXIST: + pass + else: raise + self.book_id = book_id + self.file = None + + def open(self, mode='r'): + """ + Open the snippet file. Call .close() afterwards. + """ + if not 'b' in mode: + mode += 'b' + self.file = open(os.path.join(settings.SEARCH_INDEX, self.SNIPPET_DIR, str(self.book_id)), mode) + self.position = 0 + return self + + def add(self, snippet): + """ + Append a snippet (unicode) to the snippet file. + Return a (position, length) tuple + """ + txt = snippet.encode('utf-8') + l = len(txt) + self.file.write(txt) + pos = (self.position, l) + self.position += l + return pos + + def get(self, pos): + """ + Given a tuple of (position, length) return an unicode + of the snippet stored there. + """ + self.file.seek(pos[0], 0) + txt = self.file.read(pos[1]).decode('utf-8') + return txt + + def close(self): + """Close snippet file""" + self.file.close() + + +class BaseIndex(IndexStore): + """ + Base index class. + Provides basic operations on index: opening, closing, optimizing. + """ + def __init__(self, analyzer=None): + super(BaseIndex, self).__init__() + self.index = None + if not analyzer: + analyzer = WLAnalyzer() + self.analyzer = analyzer + + def open(self, analyzer=None): + if self.index: + raise Exception("Index is already opened") + self.index = IndexWriter(self.store, self.analyzer,\ + IndexWriter.MaxFieldLength.LIMITED) + return self.index + + def optimize(self): + self.index.optimize() + + def close(self): + try: + self.index.optimize() + except JavaError, je: + print "Error during optimize phase, check index: %s" % je + + self.index.close() + self.index = None + + def __enter__(self): + self.open() + return self + + def __exit__(self, type, value, tb): + self.close() + + +class Index(BaseIndex): + """ + Class indexing books. + """ + def __init__(self, analyzer=None): + super(Index, self).__init__(analyzer) + + def index_tags(self): + """ + Re-index global tag list. + Removes all tags from index, then index them again. + Indexed fields include: id, name (with and without polish stems), category + """ + q = NumericRangeQuery.newIntRange("tag_id", 0, Integer.MAX_VALUE, True, True) + self.index.deleteDocuments(q) + + for tag in catalogue.models.Tag.objects.all(): + doc = Document() + doc.add(NumericField("tag_id", Field.Store.YES, True).setIntValue(tag.id)) + doc.add(Field("tag_name", tag.name, Field.Store.NO, Field.Index.ANALYZED)) + doc.add(Field("tag_name_pl", tag.name, Field.Store.NO, Field.Index.ANALYZED)) + doc.add(Field("tag_category", tag.category, Field.Store.NO, Field.Index.NOT_ANALYZED)) + self.index.addDocument(doc) + + def create_book_doc(self, book): + """ + Create a lucene document referring book id. + """ + doc = Document() + doc.add(NumericField("book_id", Field.Store.YES, True).setIntValue(book.id)) + if book.parent is not None: + doc.add(NumericField("parent_id", Field.Store.YES, True).setIntValue(book.parent.id)) + return doc + + def remove_book(self, book): + """Removes a book from search index. + book - Book instance.""" + q = NumericRangeQuery.newIntRange("book_id", book.id, book.id, True, True) + self.index.deleteDocuments(q) + + def index_book(self, book, book_info=None, overwrite=True): + """ + Indexes the book. + Creates a lucene document for extracted metadata + and calls self.index_content() to index the contents of the book. + """ + if overwrite: + self.remove_book(book) + + book_doc = self.create_book_doc(book) + meta_fields = self.extract_metadata(book, book_info) + for f in meta_fields.values(): + if isinstance(f, list) or isinstance(f, tuple): + for elem in f: + book_doc.add(elem) + else: + book_doc.add(f) + + self.index.addDocument(book_doc) + del book_doc + + self.index_content(book, book_fields=[meta_fields['title'], meta_fields['authors']]) + + master_tags = [ + 'opowiadanie', + 'powiesc', + 'dramat_wierszowany_l', + 'dramat_wierszowany_lp', + 'dramat_wspolczesny', 'liryka_l', 'liryka_lp', + 'wywiad' + ] + + skip_header_tags = ['autor_utworu', 'nazwa_utworu', 'dzielo_nadrzedne'] + + def extract_metadata(self, book, book_info=None): + """ + Extract metadata from book and returns a map of fields keyed by fieldname + """ + fields = {} + + if book_info is None: + book_info = dcparser.parse(open(book.xml_file.path)) + + fields['slug'] = Field("slug", book.slug, Field.Store.NO, Field.Index.ANALYZED_NO_NORMS) + fields['tags'] = self.add_gaps([Field("tags", t.name, Field.Store.NO, Field.Index.ANALYZED) for t in book.tags], 'tags') + fields['is_book'] = Field("is_book", 'true', Field.Store.NO, Field.Index.NOT_ANALYZED) + + # validator, name + for field in dcparser.BookInfo.FIELDS: + if hasattr(book_info, field.name): + if not getattr(book_info, field.name): + continue + # since no type information is available, we use validator + type_indicator = field.validator + if type_indicator == dcparser.as_unicode: + s = getattr(book_info, field.name) + if field.multiple: + s = ', '.join(s) + try: + fields[field.name] = Field(field.name, s, Field.Store.NO, Field.Index.ANALYZED) + except JavaError as je: + raise Exception("failed to add field: %s = '%s', %s(%s)" % (field.name, s, je.message, je.args)) + elif type_indicator == dcparser.as_person: + p = getattr(book_info, field.name) + if isinstance(p, dcparser.Person): + persons = unicode(p) + else: + persons = ', '.join(map(unicode, p)) + fields[field.name] = Field(field.name, persons, Field.Store.NO, Field.Index.ANALYZED) + elif type_indicator == dcparser.as_date: + dt = getattr(book_info, field.name) + fields[field.name] = Field(field.name, "%04d%02d%02d" %\ + (dt.year, dt.month, dt.day), Field.Store.NO, Field.Index.NOT_ANALYZED) + + return fields + + def add_gaps(self, fields, fieldname): + """ + Interposes a list of fields with gap-fields, which are indexed spaces and returns it. + This allows for doing phrase queries which do not overlap the gaps (when slop is 0). + """ + def gap(): + while True: + yield Field(fieldname, ' ', Field.Store.NO, Field.Index.NOT_ANALYZED) + return reduce(lambda a, b: a + b, zip(fields, gap()))[0:-1] + + def get_master(self, root): + """ + Returns the first master tag from an etree. + """ + for master in root.iter(): + if master.tag in self.master_tags: + return master + + def index_content(self, book, book_fields=[]): + """ + Walks the book XML and extract content from it. + Adds parts for each header tag and for each fragment. + """ + wld = WLDocument.from_file(book.xml_file.path, parse_dublincore=False) + root = wld.edoc.getroot() + + master = self.get_master(root) + if master is None: + return [] + + def walker(node): + yield node, None + for child in list(node): + for b, e in walker(child): + yield b, e + yield None, node + return + + def fix_format(text): + return re.sub("(?m)/$", "", text) + + def add_part(snippets, **fields): + doc = self.create_book_doc(book) + for f in book_fields: + doc.add(f) + + doc.add(NumericField('header_index', Field.Store.YES, True).setIntValue(fields["header_index"])) + doc.add(NumericField("header_span", Field.Store.YES, True)\ + .setIntValue('header_span' in fields and fields['header_span'] or 1)) + doc.add(Field('header_type', fields["header_type"], Field.Store.YES, Field.Index.NOT_ANALYZED)) + + doc.add(Field('content', fields["content"], Field.Store.NO, Field.Index.ANALYZED, \ + Field.TermVector.WITH_POSITIONS_OFFSETS)) + + snip_pos = snippets.add(fields["content"]) + doc.add(NumericField("snippets_position", Field.Store.YES, True).setIntValue(snip_pos[0])) + doc.add(NumericField("snippets_length", Field.Store.YES, True).setIntValue(snip_pos[1])) + + if 'fragment_anchor' in fields: + doc.add(Field("fragment_anchor", fields['fragment_anchor'], + Field.Store.YES, Field.Index.NOT_ANALYZED)) + + if 'themes' in fields: + themes, themes_pl = zip(*[ + (Field("themes", theme, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS), + Field("themes_pl", theme, Field.Store.NO, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS)) + for theme in fields['themes']]) + + themes = self.add_gaps(themes, 'themes') + themes_pl = self.add_gaps(themes_pl, 'themes_pl') + + for t in themes: + doc.add(t) + for t in themes_pl: + doc.add(t) + + return doc + + def give_me_utf8(s): + if isinstance(s, unicode): + return s.encode('utf-8') + else: + return s + + fragments = {} + snippets = Snippets(book.id).open('w') + try: + for header, position in zip(list(master), range(len(master))): + + if header.tag in self.skip_header_tags: + continue + + content = u' '.join([t for t in header.itertext()]) + content = fix_format(content) + + doc = add_part(snippets, header_index=position, header_type=header.tag, content=content) + + self.index.addDocument(doc) + + for start, end in walker(header): + if start is not None and start.tag == 'begin': + fid = start.attrib['id'][1:] + fragments[fid] = {'content': [], 'themes': [], 'start_section': position, 'start_header': header.tag} + fragments[fid]['content'].append(start.tail) + elif start is not None and start.tag == 'motyw': + fid = start.attrib['id'][1:] + if start.text is not None: + fragments[fid]['themes'] += map(str.strip, map(give_me_utf8, start.text.split(','))) + fragments[fid]['content'].append(start.tail) + elif start is not None and start.tag == 'end': + fid = start.attrib['id'][1:] + if fid not in fragments: + continue # a broken <end> node, skip it + frag = fragments[fid] + if frag['themes'] == []: + continue # empty themes list. + del fragments[fid] + + def jstr(l): + return u' '.join(map( + lambda x: x == None and u'(none)' or unicode(x), + l)) + + doc = add_part(snippets, + header_type=frag['start_header'], + header_index=frag['start_section'], + header_span=position - frag['start_section'] + 1, + fragment_anchor=fid, + content=u' '.join(filter(lambda s: s is not None, frag['content'])), + themes=frag['themes']) + + self.index.addDocument(doc) + elif start is not None: + for frag in fragments.values(): + frag['content'].append(start.text) + elif end is not None: + for frag in fragments.values(): + frag['content'].append(end.tail) + finally: + snippets.close() + + +def log_exception_wrapper(f): + def _wrap(*a): + try: + f(*a) + except Exception, e: + print("Error in indexing thread: %s" % e) + traceback.print_exc() + raise e + return _wrap + + +class ReusableIndex(Index): + """ + Works like index, but does not close/optimize Lucene index + until program exit (uses atexit hook). + This is usefull for importbooks command. + + if you cannot rely on atexit, use ReusableIndex.close_reusable() yourself. + """ + index = None + + def open(self, analyzer=None, threads=4): + if ReusableIndex.index is not None: + self.index = ReusableIndex.index + else: + print("opening index") + Index.open(self, analyzer) + ReusableIndex.index = self.index + atexit.register(ReusableIndex.close_reusable) + + # def index_book(self, *args, **kw): + # job = ReusableIndex.pool.apply_async(log_exception_wrapper(Index.index_book), (self,) + args, kw) + # ReusableIndex.pool_jobs.append(job) + + @staticmethod + def close_reusable(): + if ReusableIndex.index is not None: + ReusableIndex.index.optimize() + ReusableIndex.index.close() + ReusableIndex.index = None + + def close(self): + pass + + +class JoinSearch(object): + """ + This mixin could be used to handle block join queries. + (currently unused) + """ + def __init__(self, *args, **kw): + super(JoinSearch, self).__init__(*args, **kw) + + def wrapjoins(self, query, fields=[]): + """ + This functions modifies the query in a recursive way, + so Term and Phrase Queries contained, which match + provided fields are wrapped in a BlockJoinQuery, + and so delegated to children documents. + """ + if BooleanQuery.instance_(query): + qs = BooleanQuery.cast_(query) + for clause in qs: + clause = BooleanClause.cast_(clause) + clause.setQuery(self.wrapjoins(clause.getQuery(), fields)) + return qs + else: + termset = HashSet() + query.extractTerms(termset) + for t in termset: + t = Term.cast_(t) + if t.field() not in fields: + return query + return BlockJoinQuery(query, self.parent_filter, + BlockJoinQuery.ScoreMode.Total) + + def bsearch(self, query, max_results=50): + q = self.query(query) + bjq = BlockJoinQuery(q, self.parent_filter, BlockJoinQuery.ScoreMode.Avg) + + tops = self.searcher.search(bjq, max_results) + bks = [] + for found in tops.scoreDocs: + doc = self.searcher.doc(found.doc) + bks.append(catalogue.models.Book.objects.get(id=doc.get("book_id"))) + return (bks, tops.totalHits) + + +class SearchResult(object): + def __init__(self, searcher, scoreDocs, score=None, how_found=None, snippets=None): + if score: + self.score = score + else: + self.score = scoreDocs.score + + self._hits = [] + self.hits = None # processed hits + + stored = searcher.doc(scoreDocs.doc) + self.book_id = int(stored.get("book_id")) + + header_type = stored.get("header_type") + if not header_type: + return + + sec = (header_type, int(stored.get("header_index"))) + header_span = stored.get('header_span') + header_span = header_span is not None and int(header_span) or 1 + + fragment = stored.get("fragment_anchor") + + hit = (sec + (header_span,), fragment, scoreDocs.score, {'how_found': how_found, 'snippets': snippets and [snippets] or []}) + + self._hits.append(hit) + + def merge(self, other): + if self.book_id != other.book_id: + raise ValueError("this search result is or book %d; tried to merge with %d" % (self.book_id, other.book_id)) + self._hits += other._hits + if other.score > self.score: + self.score = other.score + return self + + def get_book(self): + return catalogue.models.Book.objects.get(id=self.book_id) + + book = property(get_book) + + def process_hits(self): + POSITION = 0 + FRAGMENT = 1 + POSITION_INDEX = 1 + POSITION_SPAN = 2 + SCORE = 2 + OTHER = 3 + + # to sections and fragments + frags = filter(lambda r: r[FRAGMENT] is not None, self._hits) + sect = filter(lambda r: r[FRAGMENT] is None, self._hits) + sect = filter(lambda s: 0 == len(filter( + lambda f: s[POSITION][POSITION_INDEX] >= f[POSITION][POSITION_INDEX] + and s[POSITION][POSITION_INDEX] < f[POSITION][POSITION_INDEX] + f[POSITION][POSITION_SPAN], + frags)), sect) + + hits = [] + + # remove duplicate fragments + fragments = {} + for f in frags: + fid = f[FRAGMENT] + if fid in fragments: + if fragments[fid][SCORE] >= f[SCORE]: + continue + fragments[fid] = f + frags = fragments.values() + + # remove duplicate sections + sections = {} + + for s in sect: + si = s[POSITION][POSITION_INDEX] + # skip existing + if si in sections: + if sections[si]['score'] >= s[SCORE]: + continue + + m = {'score': s[SCORE], + 'section_number': s[POSITION][POSITION_INDEX] + 1, + } + m.update(s[OTHER]) + sections[si] = m + + hits = sections.values() + + for f in frags: + frag = catalogue.models.Fragment.objects.get(anchor=f[FRAGMENT]) + m = {'score': f[SCORE], + 'fragment': frag, + 'themes': frag.tags.filter(category='theme') + } + m.update(f[OTHER]) + hits.append(m) + + hits.sort(lambda a, b: cmp(a['score'], b['score']), reverse=True) + + self.hits = hits + + return self + + def __unicode__(self): + return u'SearchResult(book_id=%d, score=%d)' % (self.book_id, self.score) + + @staticmethod + def aggregate(*result_lists): + books = {} + for rl in result_lists: + for r in rl: + if r.book_id in books: + books[r.book_id].merge(r) + #print(u"already have one with score %f, and this one has score %f" % (books[book.id][0], found.score)) + else: + books[r.book_id] = r + return books.values() + + def __cmp__(self, other): + return cmp(self.score, other.score) + + +class Hint(object): + """ + Given some hint information (information we already know about) + our search target - like author, title (specific book), epoch, genre, kind + we can narrow down search using filters. + """ + def __init__(self, search): + """ + Accepts a Searcher instance. + """ + self.search = search + self.book_tags = {} + self.part_tags = [] + self._books = [] + + def books(self, *books): + """ + Give a hint that we search these books. + """ + self._books = books + + def tags(self, tags): + """ + Give a hint that these Tag objects (a list of) + is necessary. + """ + for t in tags: + if t.category in ['author', 'title', 'epoch', 'genre', 'kind']: + lst = self.book_tags.get(t.category, []) + lst.append(t) + self.book_tags[t.category] = lst + if t.category in ['theme', 'theme_pl']: + self.part_tags.append(t) + + def tag_filter(self, tags, field='tags'): + """ + Given a lsit of tags and an optional field (but they are normally in tags field) + returns a filter accepting only books with specific tags. + """ + q = BooleanQuery() + + for tag in tags: + toks = self.search.get_tokens(tag.name, field=field) + tag_phrase = PhraseQuery() + for tok in toks: + tag_phrase.add(Term(field, tok)) + q.add(BooleanClause(tag_phrase, BooleanClause.Occur.MUST)) + + return QueryWrapperFilter(q) + + def book_filter(self): + """ + Filters using book tags (all tag kinds except a theme) + """ + tags = reduce(lambda a, b: a + b, self.book_tags.values(), []) + if tags: + return self.tag_filter(tags) + else: + return None + + def part_filter(self): + """ + This filter can be used to look for book parts. + It filters on book id and/or themes. + """ + fs = [] + if self.part_tags: + fs.append(self.tag_filter(self.part_tags, field='themes')) + + if self._books != []: + bf = BooleanFilter() + for b in self._books: + id_filter = NumericRangeFilter.newIntRange('book_id', b.id, b.id, True, True) + bf.add(FilterClause(id_filter, BooleanClause.Occur.SHOULD)) + fs.append(bf) + + return Search.chain_filters(fs) + + def should_search_for_book(self): + return self._books == [] + + def just_search_in(self, all): + """Holds logic to figure out which indexes should be search, when we have some hinst already""" + some = [] + for field in all: + if field == 'authors' and 'author' in self.book_tags: + continue + if field == 'title' and self._books != []: + continue + if (field == 'themes' or field == 'themes_pl') and self.part_tags: + continue + some.append(field) + return some + + +class Search(IndexStore): + """ + Search facilities. + """ + def __init__(self, default_field="content"): + IndexStore.__init__(self) + self.analyzer = WLAnalyzer() # PolishAnalyzer(Version.LUCENE_34) + # self.analyzer = WLAnalyzer() + self.searcher = IndexSearcher(self.store, True) + self.parser = QueryParser(Version.LUCENE_34, default_field, + self.analyzer) + + self.parent_filter = TermsFilter() + self.parent_filter.addTerm(Term("is_book", "true")) + + def query(self, query): + """Parse query in default Lucene Syntax. (for humans) + """ + return self.parser.parse(query) + + def simple_search(self, query, max_results=50): + """Runs a query for books using lucene syntax. (for humans) + Returns (books, total_hits) + """ + + tops = self.searcher.search(self.query(query), max_results) + bks = [] + for found in tops.scoreDocs: + doc = self.searcher.doc(found.doc) + bks.append(catalogue.models.Book.objects.get(id=doc.get("book_id"))) + return (bks, tops.totalHits) + + def get_tokens(self, searched, field='content'): + """returns tokens analyzed by a proper (for a field) analyzer + argument can be: StringReader, string/unicode, or tokens. In the last case + they will just be returned (so we can reuse tokens, if we don't change the analyzer) + """ + if isinstance(searched, str) or isinstance(searched, unicode): + searched = StringReader(searched) + elif isinstance(searched, list): + return searched + + searched.reset() + tokens = self.analyzer.reusableTokenStream(field, searched) + toks = [] + while tokens.incrementToken(): + cta = tokens.getAttribute(CharTermAttribute.class_) + toks.append(cta.toString()) + return toks + + def fuzziness(self, fuzzy): + """Helper method to sanitize fuzziness""" + if not fuzzy: + return None + if isinstance(fuzzy, float) and fuzzy > 0.0 and fuzzy <= 1.0: + return fuzzy + else: + return 0.5 + + def make_phrase(self, tokens, field='content', slop=2, fuzzy=False): + """ + Return a PhraseQuery with a series of tokens. + """ + if fuzzy: + phrase = MultiPhraseQuery() + for t in tokens: + term = Term(field, t) + fuzzterm = FuzzyTermEnum(self.searcher.getIndexReader(), term, self.fuzziness(fuzzy)) + fuzzterms = [] + + while True: + # print("fuzz %s" % unicode(fuzzterm.term()).encode('utf-8')) + ft = fuzzterm.term() + if ft: + fuzzterms.append(ft) + if not fuzzterm.next(): break + if fuzzterms: + phrase.add(JArray('object')(fuzzterms, Term)) + else: + phrase.add(term) + else: + phrase = PhraseQuery() + phrase.setSlop(slop) + for t in tokens: + term = Term(field, t) + phrase.add(term) + return phrase + + def make_term_query(self, tokens, field='content', modal=BooleanClause.Occur.SHOULD, fuzzy=False): + """ + Returns term queries joined by boolean query. + modal - applies to boolean query + fuzzy - should the query by fuzzy. + """ + q = BooleanQuery() + for t in tokens: + term = Term(field, t) + if fuzzy: + term = FuzzyQuery(term, self.fuzziness(fuzzy)) + else: + term = TermQuery(term) + q.add(BooleanClause(term, modal)) + return q + + # def content_query(self, query): + # return BlockJoinQuery(query, self.parent_filter, + # BlockJoinQuery.ScoreMode.Total) + + def search_perfect_book(self, searched, max_results=20, fuzzy=False, hint=None): + """ + Search for perfect book matches. Just see if the query matches with some author or title, + taking hints into account. + """ + fields_to_search = ['authors', 'title'] + only_in = None + if hint: + if not hint.should_search_for_book(): + return [] + fields_to_search = hint.just_search_in(fields_to_search) + only_in = hint.book_filter() + + qrys = [self.make_phrase(self.get_tokens(searched, field=fld), field=fld, fuzzy=fuzzy) for fld in fields_to_search] + + books = [] + for q in qrys: + top = self.searcher.search(q, + self.chain_filters([only_in, self.term_filter(Term('is_book', 'true'))]), + max_results) + for found in top.scoreDocs: + books.append(SearchResult(self.searcher, found)) + return books + + def search_book(self, searched, max_results=20, fuzzy=False, hint=None): + fields_to_search = ['tags', 'authors', 'title'] + + only_in = None + if hint: + if not hint.should_search_for_book(): + return [] + fields_to_search = hint.just_search_in(fields_to_search) + only_in = hint.book_filter() + + tokens = self.get_tokens(searched, field='SIMPLE') + + q = BooleanQuery() + + for fld in fields_to_search: + q.add(BooleanClause(self.make_term_query(tokens, field=fld, + fuzzy=fuzzy), BooleanClause.Occur.SHOULD)) + + books = [] + top = self.searcher.search(q, + self.chain_filters([only_in, self.term_filter(Term('is_book', 'true'))]), + max_results) + for found in top.scoreDocs: + books.append(SearchResult(self.searcher, found)) + + return books + + def search_perfect_parts(self, searched, max_results=20, fuzzy=False, hint=None): + """ + Search for book parts which containt a phrase perfectly matching (with a slop of 2, default for make_phrase()) + some part/fragment of the book. + """ + qrys = [self.make_phrase(self.get_tokens(searched), field=fld, fuzzy=fuzzy) for fld in ['content']] + + flt = None + if hint: + flt = hint.part_filter() + + books = [] + for q in qrys: + top = self.searcher.search(q, + self.chain_filters([self.term_filter(Term('is_book', 'true'), inverse=True), + flt]), + max_results) + for found in top.scoreDocs: + books.append(SearchResult(self.searcher, found, snippets=self.get_snippets(found, q))) + + return books + + def search_everywhere(self, searched, max_results=20, fuzzy=False, hint=None): + """ + Tries to use search terms to match different fields of book (or its parts). + E.g. one word can be an author survey, another be a part of the title, and the rest + are some words from third chapter. + """ + books = [] + only_in = None + + if hint: + only_in = hint.part_filter() + + # content only query : themes x content + q = BooleanQuery() + + tokens_pl = self.get_tokens(searched, field='content') + tokens = self.get_tokens(searched, field='SIMPLE') + + # only search in themes when we do not already filter by themes + if hint is None or hint.just_search_in(['themes']) != []: + q.add(BooleanClause(self.make_term_query(tokens_pl, field='themes_pl', + fuzzy=fuzzy), BooleanClause.Occur.MUST)) + + q.add(BooleanClause(self.make_term_query(tokens_pl, field='content', + fuzzy=fuzzy), BooleanClause.Occur.SHOULD)) + + topDocs = self.searcher.search(q, only_in, max_results) + for found in topDocs.scoreDocs: + books.append(SearchResult(self.searcher, found)) + print "* %s theme x content: %s" % (searched, books[-1]._hits) + + # query themes/content x author/title/tags + q = BooleanQuery() + in_content = BooleanQuery() + in_meta = BooleanQuery() + + for fld in ['themes_pl', 'content']: + in_content.add(BooleanClause(self.make_term_query(tokens_pl, field=fld, fuzzy=False), BooleanClause.Occur.SHOULD)) + + for fld in ['tags', 'authors', 'title']: + in_meta.add(BooleanClause(self.make_term_query(tokens, field=fld, fuzzy=False), BooleanClause.Occur.SHOULD)) + + q.add(BooleanClause(in_content, BooleanClause.Occur.MUST)) + q.add(BooleanClause(in_meta, BooleanClause.Occur.SHOULD)) + + topDocs = self.searcher.search(q, only_in, max_results) + for found in topDocs.scoreDocs: + books.append(SearchResult(self.searcher, found)) + print "* %s scatter search: %s" % (searched, books[-1]._hits) + + return books + + # def multisearch(self, query, max_results=50): + # """ + # Search strategy: + # - (phrase) OR -> content + # -> title + # -> authors + # - (keywords) -> authors + # -> motyw + # -> tags + # -> content + # """ + # queryreader = StringReader(query) + # tokens = self.get_tokens(queryreader) + + # top_level = BooleanQuery() + # Should = BooleanClause.Occur.SHOULD + + # phrase_level = BooleanQuery() + # phrase_level.setBoost(1.3) + + # p_content = self.make_phrase(tokens, joined=True) + # p_title = self.make_phrase(tokens, 'title') + # p_author = self.make_phrase(tokens, 'author') + + # phrase_level.add(BooleanClause(p_content, Should)) + # phrase_level.add(BooleanClause(p_title, Should)) + # phrase_level.add(BooleanClause(p_author, Should)) + + # kw_level = BooleanQuery() + + # kw_level.add(self.make_term_query(tokens, 'author'), Should) + # j_themes = self.make_term_query(tokens, 'themes', joined=True) + # kw_level.add(j_themes, Should) + # kw_level.add(self.make_term_query(tokens, 'tags'), Should) + # j_con = self.make_term_query(tokens, joined=True) + # kw_level.add(j_con, Should) + + # top_level.add(BooleanClause(phrase_level, Should)) + # top_level.add(BooleanClause(kw_level, Should)) + + # return None + + def get_snippets(self, scoreDoc, query, field='content'): + """ + Returns a snippet for found scoreDoc. + """ + htmlFormatter = SimpleHTMLFormatter() + highlighter = Highlighter(htmlFormatter, QueryScorer(query)) + + stored = self.searcher.doc(scoreDoc.doc) + + # locate content. + snippets = Snippets(stored.get('book_id')).open() + try: + text = snippets.get((int(stored.get('snippets_position')), + int(stored.get('snippets_length')))) + finally: + snippets.close() + + tokenStream = TokenSources.getAnyTokenStream(self.searcher.getIndexReader(), scoreDoc.doc, field, self.analyzer) + # highlighter.getBestTextFragments(tokenStream, text, False, 10) + snip = highlighter.getBestFragments(tokenStream, text, 3, "...") + + return snip + + @staticmethod + def enum_to_array(enum): + """ + Converts a lucene TermEnum to array of Terms, suitable for + addition to queries + """ + terms = [] + + while True: + t = enum.term() + if t: + terms.append(t) + if not enum.next(): break + + if terms: + return JArray('object')(terms, Term) + + def search_tags(self, query, filter=None, max_results=40): + """ + Search for Tag objects using query. + """ + tops = self.searcher.search(query, filter, max_results) + + tags = [] + for found in tops.scoreDocs: + doc = self.searcher.doc(found.doc) + tag = catalogue.models.Tag.objects.get(id=doc.get("tag_id")) + tags.append(tag) + print "%s (%d) -> %f" % (tag, tag.id, found.score) + + return tags + + def search_books(self, query, filter=None, max_results=10): + """ + Searches for Book objects using query + """ + bks = [] + tops = self.searcher.search(query, filter, max_results) + for found in tops.scoreDocs: + doc = self.searcher.doc(found.doc) + bks.append(catalogue.models.Book.objects.get(id=doc.get("book_id"))) + return bks + + def create_prefix_phrase(self, toks, field): + q = MultiPhraseQuery() + for i in range(len(toks)): + t = Term(field, toks[i]) + if i == len(toks) - 1: + pterms = Search.enum_to_array(PrefixTermEnum(self.searcher.getIndexReader(), t)) + if pterms: + q.add(pterms) + else: + q.add(t) + else: + q.add(t) + return q + + @staticmethod + def term_filter(term, inverse=False): + only_term = TermsFilter() + only_term.addTerm(term) + + if inverse: + neg = BooleanFilter() + neg.add(FilterClause(only_term, BooleanClause.Occur.MUST_NOT)) + only_term = neg + + return only_term + + def hint_tags(self, string, max_results=50): + """ + Return auto-complete hints for tags + using prefix search. + """ + toks = self.get_tokens(string, field='SIMPLE') + top = BooleanQuery() + + for field in ['tag_name', 'tag_name_pl']: + q = self.create_prefix_phrase(toks, field) + top.add(BooleanClause(q, BooleanClause.Occur.SHOULD)) + + no_book_cat = self.term_filter(Term("tag_category", "book"), inverse=True) + + return self.search_tags(top, no_book_cat, max_results=max_results) + + def hint_books(self, string, max_results=50): + """ + Returns auto-complete hints for book titles + Because we do not index 'pseudo' title-tags. + Prefix search. + """ + toks = self.get_tokens(string, field='SIMPLE') + + q = self.create_prefix_phrase(toks, 'title') + + return self.search_books(q, self.term_filter(Term("is_book", "true")), max_results=max_results) + + @staticmethod + def chain_filters(filters, op=ChainedFilter.AND): + """ + Chains a filter list together + """ + filters = filter(lambda x: x is not None, filters) + if not filters: + return None + chf = ChainedFilter(JArray('object')(filters, Filter), op) + return chf + + def filtered_categories(self, tags): + """ + Return a list of tag categories, present in tags list. + """ + cats = {} + for t in tags: + cats[t.category] = True + return cats.keys() + + def hint(self): + return Hint(self) diff --git a/apps/search/management/__init__.py b/apps/search/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/search/management/commands/__init__.py b/apps/search/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/search/management/commands/checkindex.py b/apps/search/management/commands/checkindex.py new file mode 100644 index 000000000..b910277de --- /dev/null +++ b/apps/search/management/commands/checkindex.py @@ -0,0 +1,22 @@ + +from django.core.management.base import BaseCommand +from search import IndexChecker + +class Command(BaseCommand): + help = 'Check Lucene search index' + args = '' + + def handle(self, *args, **opts): + checker = IndexChecker() + status = checker.check() + if status.clean: + print "No problems found." + else: + if status.missingSegments: + print "Unable to locate." + print "Number of bad segments: %d / %d (max segment name is %d)" % \ + (status.numBadSegments, status.numSegments, status.maxSegmentName) + print "Total lost documents (due to bad segments) %d" % status.totLoseDocCount + if not status.validCounter: + print "Segment counter is not valid." + diff --git a/apps/search/management/commands/optimizeindex.py b/apps/search/management/commands/optimizeindex.py new file mode 100644 index 000000000..a8a4cf9dd --- /dev/null +++ b/apps/search/management/commands/optimizeindex.py @@ -0,0 +1,15 @@ + +from django.core.management.base import BaseCommand +from search import Index + +class Command(BaseCommand): + help = 'Optimize Lucene search index' + args = '' + + def handle(self, *args, **opts): + index = Index() + index.open() + try: + index.optimize() + finally: + index.close() diff --git a/apps/search/management/commands/reindex.py b/apps/search/management/commands/reindex.py new file mode 100755 index 000000000..bce47080d --- /dev/null +++ b/apps/search/management/commands/reindex.py @@ -0,0 +1,25 @@ +from django.core.management.base import BaseCommand + +class Command(BaseCommand): + help = 'Reindex everything.' + args = '' + + def handle(self, *args, **opts): + from catalogue.models import Book + import search + idx = search.ReusableIndex() + idx.open() + + if args: + books = [] + for a in args: + books += Book.objects.filter(slug=a).all() + else: + books = Book.objects.all() + + for b in books: + print b.title + idx.index_book(b, None) + print 'Reindexing tags.' + idx.index_tags() + idx.close() diff --git a/apps/search/tests/__init__.py b/apps/search/tests/__init__.py new file mode 100644 index 000000000..403c290f0 --- /dev/null +++ b/apps/search/tests/__init__.py @@ -0,0 +1 @@ +from search.tests.index import * diff --git a/apps/search/tests/files/fraszka-do-anusie.xml b/apps/search/tests/files/fraszka-do-anusie.xml new file mode 100755 index 000000000..3bbda155e --- /dev/null +++ b/apps/search/tests/files/fraszka-do-anusie.xml @@ -0,0 +1,49 @@ +<?xml version='1.0' encoding='utf-8'?> +<utwor> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> +<rdf:Description rdf:about="http://wiki.wolnepodreczniki.pl/index.php?title=Lektury:S%C4%99p-Szarzy%C5%84ski/Rytmy/Fraszka_do_Anusie"> +<dc:creator xml:lang="pl">Sęp Szarzyński, Mikołaj</dc:creator> +<dc:title xml:lang="pl">Fraszka do Anusie</dc:title> +<dc:contributor.editor xml:lang="pl">Sekuła, Aleksandra</dc:contributor.editor> +<dc:contributor.technical_editor xml:lang="pl">Sutkowska, Olga</dc:contributor.technical_editor> +<dc:publisher xml:lang="pl">Fundacja Nowoczesna Polska</dc:publisher> +<dc:subject.period xml:lang="pl">Barok</dc:subject.period> +<dc:subject.type xml:lang="pl">Liryka</dc:subject.type> +<dc:subject.genre xml:lang="pl">Fraszka</dc:subject.genre> +<dc:description xml:lang="pl">Publikacja zrealizowana w ramach projektu Wolne Lektury (http://wolnelektury.pl). Reprodukcja cyfrowa wykonana przez Bibliotekę Narodową z egzemplarza pochodzącego ze zbiorów BN.</dc:description> +<dc:identifier.url xml:lang="pl">http://wolnelektury.pl/katalog/lektura/fraszka-do-anusie</dc:identifier.url> +<dc:source.URL xml:lang="pl">http://www.polona.pl/Content/8759</dc:source.URL> +<dc:source xml:lang="pl">Szarzyński Sęp, Mikołaj (ca 1550-1581), Rytmy abo Wiersze polskie w wyborze, E. Wende, Warszawa, 1914</dc:source> +<dc:rights xml:lang="pl">Domena publiczna - Mikołaj Sęp Szarzyński zm. 1581</dc:rights> +<dc:date.pd xml:lang="pl">1581</dc:date.pd> +<dc:format xml:lang="pl">xml</dc:format> +<dc:type xml:lang="pl">text</dc:type> +<dc:type xml:lang="en">text</dc:type> +<dc:date xml:lang="pl">2008-12-29</dc:date> +<dc:audience xml:lang="pl">L</dc:audience> +<dc:audience xml:lang="pl">L</dc:audience> +<dc:language xml:lang="pl">pol</dc:language> +</rdf:Description> +</rdf:RDF> + <liryka_l> + +<autor_utworu>Mikołaj Sęp Szarzyński</autor_utworu> + +<nazwa_utworu>Fraszka do Anusie</nazwa_utworu> + + + +<strofa><begin id="b1230084410751"/><motyw id="m1230084410751">Kochanek, Łzy, Miłość, Oko, Serce, Wzrok</motyw>Jeśli oczu hamować swoich nie umiały/ +Leśnych krynic boginie, aby nie płakały,/ +Gdy baczyły<pe><slowo_obce>baczyły</slowo_obce> --- tu: zobaczyły, patrzyły na.</pe> przy studni Narcyza pięknego,/ +A on umarł prze miłość oblicza swojego;/ +Jeśli nieśmiertelnym stanom żałość rozkazuje,/ +Gdy niebaczna fortuna co niesłusznie psuje:</strofa> + +<strofa>Jakoż ja mam hamować, by na lice moje/ +Z oczu smutnych żałośne nie płynęły zdroje?/ +Jako serce powściągać, aby nie wzdychało/ +I od ciężkiej żałości omdlewać nie miało?<end id="e1230084410751"/></strofa> + +</liryka_l> +</utwor> diff --git a/apps/search/tests/files/fraszki.xml b/apps/search/tests/files/fraszki.xml new file mode 100755 index 000000000..edb29abbc --- /dev/null +++ b/apps/search/tests/files/fraszki.xml @@ -0,0 +1,27 @@ +<?xml version='1.0' encoding='utf-8'?> +<utwor> +<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> +<rdf:Description rdf:about=""> +<dc:creator xml:lang="pl">Kochanowski, Jan</dc:creator> +<dc:title xml:lang="pl">Fraszki</dc:title> +<dc:relation.hasPart xml:lang="pl">http://wolnelektury.pl/katalog/lektura/fraszka-do-anusie</dc:relation.hasPart> + +<dc:publisher xml:lang="pl">Fundacja Nowoczesna Polska</dc:publisher> +<dc:subject.period xml:lang="pl">Renesans</dc:subject.period> +<dc:subject.type xml:lang="pl">Liryka</dc:subject.type> +<dc:subject.genre xml:lang="pl">Fraszka</dc:subject.genre> + +<dc:description xml:lang="pl"></dc:description> +<dc:identifier.url xml:lang="pl">http://wolnelektury.pl/lektura/fraszki</dc:identifier.url> +<dc:source xml:lang="pl"></dc:source> +<dc:rights xml:lang="pl">Domena publiczna - Jan Kochanowski zm. 1584</dc:rights> +<dc:date.pd xml:lang="pl">1584</dc:date.pd> +<dc:format xml:lang="pl">xml</dc:format> +<dc:type xml:lang="pl">text</dc:type> + +<dc:type xml:lang="en">text</dc:type> +<dc:date xml:lang="pl">2008-11-12</dc:date> +<dc:language xml:lang="pl">pol</dc:language> +</rdf:Description> +</rdf:RDF> +</utwor> diff --git a/apps/search/tests/index.py b/apps/search/tests/index.py new file mode 100644 index 000000000..ee376a84b --- /dev/null +++ b/apps/search/tests/index.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +from django.conf import settings +from search import Index, Search, IndexStore, JVM, SearchResult +from catalogue import models +from catalogue.test_utils import WLTestCase +from lucene import PolishAnalyzer, Version +#from nose.tools import raises +from os import path + + +class BookSearchTests(WLTestCase): + def setUp(self): + JVM.attachCurrentThread() + WLTestCase.setUp(self) + settings.SEARCH_INDEX = path.join(settings.MEDIA_ROOT, 'search') + + txt = path.join(path.dirname(__file__), 'files/fraszka-do-anusie.xml') + self.book = models.Book.from_xml_file(txt) + + index = Index() + index.open() + try: + index.index_book(self.book) + except: + index.close() + + self.search = Search() + + def test_search_perfect_book_author(self): + books = self.search.search_perfect_book("sęp szarzyński") + assert len(books) == 1 + assert books[0].book_id == self.book.id + + def test_search_perfect_book_title(self): + books = self.search.search_perfect_book("fraszka anusie") + assert len(books) == 1 + assert books[0].book_id == self.book.id + + def test_search_perfect_parts(self): + books = self.search.search_perfect_parts("Jakoż hamować") + assert len(books) == 2 + for b in books: + b.book_id == self.book.id + a = SearchResult.aggregate(books) + # just one fragment hit. + assert len(filter(lambda x: x[1], a[0].hits)) == 1 + print a[0].process_hits() + + def test_search_perfect_author_title(self): + books = self.search.search_perfect_book("szarzyński anusie") + assert books == [] + + books = self.search.search_book("szarzyński anusie") + assert len(books) == 1 + + books = self.search.search_book("szarzyński fraszka") + assert len(books) == 1 + + def test_search_everywhere(self): + books = self.search.search_everywhere("szarzyński kochanek") + print 'szarzyński kochanek %s' % [b.hits for b in books] + + books = self.search.search_everywhere("szarzyński narcyz") + print 'szarzyński narcyz %s' % [b.hits for b in books] + + books = self.search.search_everywhere("anusie narcyz") + print 'anusie narcyz %s' % [b.hits for b in books] + + # theme content cross + books = self.search.search_everywhere("wzrok boginie") + print 'wzrok boginie %s' % [b.hits for b in books] + + books = self.search.search_everywhere("anusie płynęły zdroje") + print 'anusie płynęły zdroje %s' % [b.hits for b in books] diff --git a/apps/search/urls.py b/apps/search/urls.py new file mode 100644 index 000000000..607f094cd --- /dev/null +++ b/apps/search/urls.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.conf.urls.defaults import * + +urlpatterns = patterns('search.views', + url(r'^$', 'main', name='search'), + url(r'^hint/$', 'hint'), +) + diff --git a/apps/search/views.py b/apps/search/views.py new file mode 100644 index 000000000..d056a1826 --- /dev/null +++ b/apps/search/views.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +from django.conf import settings +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.contrib.auth.decorators import login_required +from django.views.decorators import cache +from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect +from django.utils.translation import ugettext as _ + +from catalogue.utils import get_random_hash +from catalogue.models import Book, Tag, Fragment +from catalogue.fields import dumps +from catalogue.views import JSONResponse +from search import Search, JVM, SearchResult +from lucene import StringReader +from suggest.forms import PublishingSuggestForm + +import enchant + +dictionary = enchant.Dict('pl_PL') + + +def match_word_re(word): + if 'sqlite' in settings.DATABASES['default']['ENGINE']: + return r"\b%s\b" % word + elif 'mysql' in settings.DATABASES['default']['ENGINE']: + return "[[:<:]]%s[[:>:]]" % word + + +def did_you_mean(query, tokens): + change = {} + for t in tokens: + print("%s ok? %s, sug: %s" % (t, dictionary.check(t), dictionary.suggest(t))) + authors = Tag.objects.filter(category='author', name__iregex=match_word_re(t)) + if len(authors) > 0: + continue + + if not dictionary.check(t): + try: + change[t] = dictionary.suggest(t)[0] + except IndexError: + pass + + if change == {}: + return None + + for frm, to in change.items(): + query = query.replace(frm, to) + + return query + + +def hint(request): + prefix = request.GET.get('term', '') + if len(prefix) < 2: + return JSONResponse([]) + JVM.attachCurrentThread() + s = Search() + + hint = s.hint() + try: + tags = request.GET.get('tags', '') + hint.tags(Tag.get_tag_list(tags)) + except: + pass + + # tagi beda ograniczac tutaj + # ale tagi moga byc na ksiazce i na fragmentach + # jezeli tagi dot tylko ksiazki, to wazne zeby te nowe byly w tej samej ksiazce + # jesli zas dotycza themes, to wazne, zeby byly w tym samym fragmencie. + + tags = s.hint_tags(prefix) + books = s.hint_books(prefix) + + # TODO DODAC TU HINTY + + return JSONResponse( + [{'label': t.name, + 'category': _(t.category), + 'id': t.id, + 'url': t.get_absolute_url()} + for t in tags] + \ + [{'label': b.title, + 'category': _('book'), + 'id': b.id, + 'url': b.get_absolute_url()} + for b in books]) + + +def main(request): + results = {} + JVM.attachCurrentThread() # where to put this? + srch = Search() + + results = None + query = None + fuzzy = False + + if 'q' in request.GET: + tags = request.GET.get('tags', '') + query = request.GET['q'] + book_id = request.GET.get('book', None) + book = None + if book_id is not None: + book = get_object_or_404(Book, id=book_id) + + hint = srch.hint() + try: + tag_list = Tag.get_tag_list(tags) + except: + tag_list = [] + + if len(query) < 2: + return render_to_response('catalogue/search_too_short.html', {'tags': tag_list, 'prefix': query}, + context_instance=RequestContext(request)) + + hint.tags(tag_list) + if book: + hint.books(book) + + toks = StringReader(query) + fuzzy = 'fuzzy' in request.GET + if fuzzy: + fuzzy = 0.7 + + results = SearchResult.aggregate(srch.search_perfect_book(toks, fuzzy=fuzzy, hint=hint), + srch.search_book(toks, fuzzy=fuzzy, hint=hint), + srch.search_perfect_parts(toks, fuzzy=fuzzy, hint=hint), + srch.search_everywhere(toks, fuzzy=fuzzy, hint=hint)) + + for r in results: + r.process_hits() + + results.sort(reverse=True) + + for r in results: + print "-----" + for h in r.hits: + print "- %s" % h + + # Did you mean? + suggestion = did_you_mean(query, srch.get_tokens(toks, field="SIMPLE")) + + if len(results) == 1: + if len(results[0].hits) == 0: + return HttpResponseRedirect(results[0].book.get_absolute_url()) + elif len(results[0].hits) == 1 and results[0].hits[0] is not None: + frag = Fragment.objects.get(anchor=results[0].hits[0]) + return HttpResponseRedirect(frag.get_absolute_url()) + elif len(results) == 0: + form = PublishingSuggestForm(initial={"books": query + ", "}) + return render_to_response('catalogue/search_no_hits.html', + {'tags': tag_list, + 'prefix': query, + "form": form, + 'did_you_mean': suggestion}, + context_instance=RequestContext(request)) + + return render_to_response('catalogue/search_multiple_hits.html', + {'tags': tag_list, + 'prefix': query, + 'results': results, + 'did_you_mean': suggestion}, + context_instance=RequestContext(request)) diff --git a/apps/sponsors/models.py b/apps/sponsors/models.py index d91c8f420..1e0d2e52d 100644 --- a/apps/sponsors/models.py +++ b/apps/sponsors/models.py @@ -9,24 +9,17 @@ from django.utils.translation import ugettext_lazy as _ from django.template.loader import render_to_string from PIL import Image -from sorl.thumbnail.fields import ImageWithThumbnailsField from sponsors.fields import JSONField from django.core.files.base import ContentFile -THUMB_WIDTH=120 -THUMB_HEIGHT=120 +THUMB_WIDTH = 120 +THUMB_HEIGHT = 120 + class Sponsor(models.Model): name = models.CharField(_('name'), max_length=120) _description = models.CharField(_('description'), blank=True, max_length=255) - logo = ImageWithThumbnailsField( - _('logo'), - upload_to='sponsorzy/sponsor/logo', - thumbnail={ - 'size': (THUMB_WIDTH, THUMB_HEIGHT), - 'extension': 'png', - 'options': ['pad', 'detail'], - }) + logo = models.ImageField(_('logo'), upload_to='sponsorzy/sponsor/logo') url = models.URLField(_('url'), blank=True, verify_exists=False) def __unicode__(self): @@ -65,10 +58,21 @@ class SponsorPage(models.Model): for column in self.get_sponsors_value(): sponsor_ids.extend(column['sponsors']) sponsors = Sponsor.objects.in_bulk(sponsor_ids) - sprite = Image.new('RGBA', (THUMB_WIDTH, len(sponsors)*THUMB_HEIGHT)) + sprite = Image.new('RGBA', (THUMB_WIDTH, len(sponsors) * THUMB_HEIGHT)) for i, sponsor_id in enumerate(sponsor_ids): - simg = Image.open(sponsors[sponsor_id].logo.thumbnail.dest) - sprite.paste(simg, (0, i*THUMB_HEIGHT)) + simg = Image.open(sponsors[sponsor_id].logo.path) + if simg.size[0] > THUMB_WIDTH or simg.size[1] > THUMB_HEIGHT: + size = ( + min(THUMB_WIDTH, + simg.size[0] * THUMB_HEIGHT / simg.size[1]), + min(THUMB_HEIGHT, + simg.size[1] * THUMB_WIDTH / simg.size[0]) + ) + simg = simg.resize(size, Image.ANTIALIAS) + sprite.paste(simg, ( + (THUMB_WIDTH - simg.size[0]) / 2, + i * THUMB_HEIGHT + (THUMB_HEIGHT - simg.size[1]) / 2, + )) imgstr = StringIO() sprite.save(imgstr, 'png') diff --git a/apps/sponsors/processors.py b/apps/sponsors/processors.py deleted file mode 100644 index 112241d96..000000000 --- a/apps/sponsors/processors.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from PIL import Image, ImageFilter, ImageChops - - -def add_padding(image, requested_size, opts): - if 'pad' in opts: - padded_image = Image.new('RGBA', requested_size, '#fff') - width, height = image.size - requested_width, requested_height = requested_size - print 'whatever' - padded_image.paste(image, (0, (requested_height - height) / 2)) - return padded_image - return image - -add_padding.valid_options = ('pad',) diff --git a/apps/sponsors/static/sponsors/css/footer_admin.css b/apps/sponsors/static/sponsors/css/footer_admin.css index 1277bd2d3..fe19d30c9 100644 --- a/apps/sponsors/static/sponsors/css/footer_admin.css +++ b/apps/sponsors/static/sponsors/css/footer_admin.css @@ -66,3 +66,8 @@ background-color: #EEE; cursor: default; } + +.sponsors-sponsor img { + max-width: 120px; + max-height: 120px; +} diff --git a/apps/sponsors/widgets.py b/apps/sponsors/widgets.py index e4b30bbbc..fc1387323 100644 --- a/apps/sponsors/widgets.py +++ b/apps/sponsors/widgets.py @@ -23,7 +23,7 @@ class SponsorPageWidget(forms.Textarea): def render(self, name, value, attrs=None): output = [super(SponsorPageWidget, self).render(name, value, attrs)] - sponsors = [(unicode(obj), obj.pk, obj.logo.thumbnail) for obj in models.Sponsor.objects.all()] + sponsors = [(unicode(obj), obj.pk, obj.logo.url) for obj in models.Sponsor.objects.all()] sponsors_js = ', '.join('{name: "%s", id: %d, image: "%s"}' % sponsor for sponsor in sponsors) output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {') # TODO: "id_" is hard-coded here. This should instead use the correct diff --git a/apps/suggest/forms.py b/apps/suggest/forms.py index 14cec03f9..b7c614f31 100644 --- a/apps/suggest/forms.py +++ b/apps/suggest/forms.py @@ -9,14 +9,49 @@ from django.core.validators import email_re from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext - -from suggest.models import PublishingSuggestion +from suggest.models import PublishingSuggestion, Suggestion class SuggestForm(forms.Form): contact = forms.CharField(label=_('Contact'), max_length=120, required=False) description = forms.CharField(label=_('Description'), widget=forms.Textarea, required=True) + def save(self, request): + contact = self.cleaned_data['contact'] + description = self.cleaned_data['description'] + + suggestion = Suggestion(contact=contact, + description=description, ip=request.META['REMOTE_ADDR']) + if request.user.is_authenticated(): + suggestion.user = request.user + suggestion.save() + + mail_managers(u'Nowa sugestia na stronie WolneLektury.pl', u'''\ +Zgłoszono nową sugestię w serwisie WolneLektury.pl. +http://%(site)s%(url)s + +Użytkownik: %(user)s +Kontakt: %(contact)s + +%(description)s''' % { + 'site': Site.objects.get_current().domain, + 'url': reverse('admin:suggest_suggestion_change', args=[suggestion.id]), + 'user': str(request.user) if request.user.is_authenticated() else '', + 'contact': contact, + 'description': description, + }, fail_silently=True) + + if email_re.match(contact): + send_mail(u'[WolneLektury] ' + _(u'Thank you for your suggestion.'), + _(u"""\ +Thank you for your comment on WolneLektury.pl. +The suggestion has been referred to the project coordinator.""") + +u""" + +-- +""" + _(u'''Message sent automatically. Please do not reply.'''), + 'no-reply@wolnelektury.pl', [contact], fail_silently=True) + class PublishingSuggestForm(forms.Form): contact = forms.CharField(label=_('Contact'), max_length=120, required=False) diff --git a/apps/suggest/templates/publishing_suggest.html b/apps/suggest/templates/publishing_suggest.html index a6ed283d5..3e710008c 100755 --- a/apps/suggest/templates/publishing_suggest.html +++ b/apps/suggest/templates/publishing_suggest.html @@ -1,14 +1,16 @@ {% load i18n %} -<h2>{% trans "Didn't find a book? Make a suggestion." %}</h2> +<h1>{% trans "Didn't find a book? Make a suggestion." %}</h1> + <form id='suggest-publishing-form' action="{% url suggest_publishing %}" method="post" accept-charset="utf-8" class="cuteform"> +{% csrf_token %} <ol> - <li><span class="error">{{ pubsuggest_form.contact.errors }}</span><label for="id_contact">{{ pubsuggest_form.contact.label }}</label> {{ pubsuggest_form.contact }}</li> + <li><span class="error">{{ form.contact.errors }}</span><label for="id_contact">{{ form.contact.label }}</label> {{ form.contact }}</li> <li>{% trans "I'd like to find in WolneLektury.pl these…" %}</li> - <li><span class="error">{{ pubsuggest_form.books.errors }}</span><label for="id_books">{{ pubsuggest_form.books.label }}:</label> {{ pubsuggest_form.books }}</li> + <li><span class="error">{{ form.books.errors }}</span><label for="id_books">{{ form.books.label }}:</label> {{ form.books }}</li> - <li><span class="error">{{ pubsuggest_form.audiobooks.errors }}</span><label for="id_audiobooks">{{ pubsuggest_form.audiobooks.label }}:</label> {{ pubsuggest_form.audiobooks }}</li> + <li><span class="error">{{ form.audiobooks.errors }}</span><label for="id_audiobooks">{{ form.audiobooks.label }}:</label> {{ form.audiobooks }}</li> <li><input type="submit" value="{% trans "Send report" %}"/></li> </ol> diff --git a/apps/suggest/templates/publishing_suggest_full.html b/apps/suggest/templates/publishing_suggest_full.html deleted file mode 100755 index c5d8c2833..000000000 --- a/apps/suggest/templates/publishing_suggest_full.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block title %}Sugestia do planu wydawniczego w WolneLektury.pl{% endblock %} - -{% block metadescription %}Sugestia do planu wydawniczego.{% endblock %} - -{% block bodyid %}suggest-publishing{% endblock %} - -{% block body %} - <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form"> - <p>{{ form.q }} <input type="submit" value="{% trans "Search" %}" /> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p> - </form> - - <div id="books-list"> - </div> - - <div class="column-right block-form"> - {% include "publishing_suggest.html" %} - {% if response_data.message %} - <p>{{ response_data.message }}</p> - {% endif %} - </div> -{% endblock %} diff --git a/apps/suggest/templates/suggest.html b/apps/suggest/templates/suggest.html deleted file mode 100644 index c7fdd8128..000000000 --- a/apps/suggest/templates/suggest.html +++ /dev/null @@ -1,9 +0,0 @@ -{% load i18n %} -<h2>{% trans "Report a bug or suggestion" %}</h2> -<form id='suggest-form' action="{% url suggest.views.report %}" method="post" accept-charset="utf-8" class="cuteform"> -<ol> - <li><label for="id_contact">{{ form.contact.label }}</label> {{ form.contact }}</li> - <li><label for="id_description">{{ form.description.label }}</label> {{ form.description }}</li> - <li><input type="submit" value="{% trans "Send report" %}"/></li> -</ol> -</form> \ No newline at end of file diff --git a/apps/suggest/urls.py b/apps/suggest/urls.py index b769e491b..ae4ac1573 100644 --- a/apps/suggest/urls.py +++ b/apps/suggest/urls.py @@ -3,21 +3,10 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.conf.urls.defaults import * -from django.views.generic.simple import direct_to_template -from suggest.forms import SuggestForm, PublishingSuggestForm -from suggest.views import PublishingSuggestionFormView +from suggest import views urlpatterns = patterns('', - url(r'^$', 'django.views.generic.simple.direct_to_template', - {'template': 'suggest.html', 'extra_context': {'form': SuggestForm }}, name='suggest'), - url(r'^wyslij/$', 'suggest.views.report', name='report'), - - #url(r'^plan/$', 'suggest.views.publishing', name='suggest_publishing'), - url(r'^plan/$', PublishingSuggestionFormView(), name='suggest_publishing'), - #url(r'^plan_block/$', 'django.views.generic.simple.direct_to_template', - # {'template': 'publishing_suggest.html', - # 'extra_context': {'pubsuggest_form': PublishingSuggestForm }}, - # name='suggest_publishing'), - #url(r'^plan/wyslij/$', 'suggest.views.publishing_commit', name='suggest_publishing_commit'), + url(r'^$', views.SuggestionFormView(), name='suggest'), + url(r'^plan/$', views.PublishingSuggestionFormView(), name='suggest_publishing'), ) diff --git a/apps/suggest/views.py b/apps/suggest/views.py index 24ee12ce4..15b65f24d 100644 --- a/apps/suggest/views.py +++ b/apps/suggest/views.py @@ -2,107 +2,22 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -from django.core.mail import send_mail, mail_managers -from django.core.urlresolvers import reverse -from django.core.validators import email_re -from django.http import HttpResponse, HttpResponseRedirect -from django.utils.translation import ugettext as _ -from django.views.decorators import cache -from django.views.decorators.http import require_POST -from django.contrib.sites.models import Site -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.utils.translation import ugettext_lazy as _ -from catalogue.forms import SearchForm +from ajaxable.utils import AjaxableFormView from suggest import forms from suggest.models import Suggestion, PublishingSuggestion -# FIXME - shouldn't be in catalogue -from catalogue.views import LazyEncoder - - -class AjaxableFormView(object): - formClass = None - template = None - ajax_template = None - formname = None - - def __call__(self, request): - """ - A view displaying a form, or JSON if `ajax' GET param is set. - """ - ajax = request.GET.get('ajax', False) - if request.method == "POST": - form = self.formClass(request.POST) - if form.is_valid(): - form.save(request) - response_data = {'success': True, 'message': _('Report was sent successfully.')} - else: - response_data = {'success': False, 'errors': form.errors} - if ajax: - return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) - else: - form = self.formClass() - response_data = None - - template = self.ajax_template if ajax else self.template - return render_to_response(template, { - self.formname: form, - "form": SearchForm(), - "response_data": response_data, - }, - context_instance=RequestContext(request)) - - class PublishingSuggestionFormView(AjaxableFormView): - formClass = forms.PublishingSuggestForm - ajax_template = "publishing_suggest.html" - template = "publishing_suggest_full.html" - formname = "pubsuggest_form" - - -@require_POST -@cache.never_cache -def report(request): - suggest_form = forms.SuggestForm(request.POST) - if suggest_form.is_valid(): - contact = suggest_form.cleaned_data['contact'] - description = suggest_form.cleaned_data['description'] - - suggestion = Suggestion(contact=contact, - description=description, ip=request.META['REMOTE_ADDR']) - if request.user.is_authenticated(): - suggestion.user = request.user - suggestion.save() - - mail_managers(u'Nowa sugestia na stronie WolneLektury.pl', u'''\ -Zgłoszono nową sugestię w serwisie WolneLektury.pl. -http://%(site)s%(url)s - -Użytkownik: %(user)s -Kontakt: %(contact)s - -%(description)s''' % { - 'site': Site.objects.get_current().domain, - 'url': reverse('admin:suggest_suggestion_change', args=[suggestion.id]), - 'user': str(request.user) if request.user.is_authenticated() else '', - 'contact': contact, - 'description': description, - }, fail_silently=True) - - if email_re.match(contact): - send_mail(u'[WolneLektury] ' + _(u'Thank you for your suggestion.'), - _(u"""\ -Thank you for your comment on WolneLektury.pl. -The suggestion has been referred to the project coordinator.""") + -u""" + form_class = forms.PublishingSuggestForm + title = _('Report a bug or suggestion') + template = "publishing_suggest.html" + success_message = _('Report was sent successfully.') --- -""" + _(u'''Message sent automatically. Please do not reply.'''), - 'no-reply@wolnelektury.pl', [contact], fail_silently=True) - response_data = {'success': True, 'message': _('Report was sent successfully.')} - else: - response_data = {'success': False, 'errors': suggest_form.errors} - return HttpResponse(LazyEncoder(ensure_ascii=False).encode(response_data)) +class SuggestionFormView(AjaxableFormView): + form_class = forms.SuggestForm + title = _('Report a bug or suggestion') + submit = _('Send report') + success_message = _('Report was sent successfully.') diff --git a/lib/librarian b/lib/librarian index 84ec0eba6..e394602de 160000 --- a/lib/librarian +++ b/lib/librarian @@ -1 +1 @@ -Subproject commit 84ec0eba63d0933b3f22a7884c46be6b796ee165 +Subproject commit e394602de9243608d1e99a3de448a75646f1a77f diff --git a/requirements.txt b/requirements.txt index bc8b138d9..81cc3936a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ Feedparser>=4.1 # PIL PIL>=1.1.6 mutagen>=1.17 -sorl-thumbnail>=3.2,<10 +sorl-thumbnail>=11.09,<12 # home-brewed & dependencies lxml>=2.2.2 @@ -28,3 +28,6 @@ lxml>=2.2.2 # celery tasks django-celery django-kombu + +# spell checking +pyenchant diff --git a/scripts/make-tags b/scripts/make-tags index 776d9d97c..754e63598 100755 --- a/scripts/make-tags +++ b/scripts/make-tags @@ -3,9 +3,11 @@ ROOT=$(git rev-parse --show-toplevel) find $ROOT -name '*.py' | xargs etags -o ${ROOT}/TAGS -find $ROOT/../librarian -name '*.py' | xargs etags -a -o ${ROOT}/TAGS if [ -n "$VIRTUAL_ENV" ]; then find ${VIRTUAL_ENV}/lib -name '*.py' |xargs etags -a -o ${ROOT}/TAGS else echo "No Virtual env enabled, will not add it to TAGS" -fi \ No newline at end of file +fi + +find $ROOT/wolnelektury/static/css -name '*.css' |xargs etags -a -o ${ROOT}/TAGS +find $ROOT/wolnelektury/static/js -name '*.js' |xargs etags -a -o ${ROOT}/TAGS diff --git a/scripts/setmainpage.py b/scripts/setmainpage.py deleted file mode 100755 index 324424356..000000000 --- a/scripts/setmainpage.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. -# -from django.core.management import setup_environ -from wolnelektury import settings -import sys - -setup_environ(settings) - -from catalogue.models import Tag - - -MAIN_PAGE_THEMES = [ - u'Obywatel', - u'Car', - u'Błoto', - u'Krew', - u'Danse macabre', - u'Obcy', - u'Matka', - u'Gotycyzm', -] - - -for tag in Tag.objects.all(): - if tag.category in ('epoch', 'genre', 'author', 'kind'): - tag.main_page = True - elif tag.category == 'theme' and tag.name in MAIN_PAGE_THEMES: - tag.main_page = True - else: - tag.main_page = False - - tag.save() - sys.stderr.write('.') - - diff --git a/wolnelektury/locale/pl/LC_MESSAGES/django.mo b/wolnelektury/locale/pl/LC_MESSAGES/django.mo index f40dee295..1326a31b8 100644 Binary files a/wolnelektury/locale/pl/LC_MESSAGES/django.mo and b/wolnelektury/locale/pl/LC_MESSAGES/django.mo differ diff --git a/wolnelektury/locale/pl/LC_MESSAGES/django.po b/wolnelektury/locale/pl/LC_MESSAGES/django.po index c256843ce..15a5dcc26 100644 --- a/wolnelektury/locale/pl/LC_MESSAGES/django.po +++ b/wolnelektury/locale/pl/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: WolneLektury\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-01-10 13:04+0100\n" -"PO-Revision-Date: 2012-01-10 13:04+0100\n" +"POT-Creation-Date: 2011-10-11 15:45+0200\n" +"PO-Revision-Date: 2011-10-11 15:46+0100\n" "Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: pl\n" @@ -129,24 +129,24 @@ msgstr "" #: templates/base.html.py:120 #: templates/base.html:126 #: templates/base.html.py:132 -#: templates/catalogue/book_detail.html:211 +#: templates/catalogue/book_detail.html:208 #: templates/catalogue/book_fragments.html:33 #: templates/catalogue/differentiate_tags.html:23 #: templates/catalogue/search_multiple_hits.html:29 #: templates/catalogue/search_too_short.html:19 -#: templates/catalogue/tagged_object_list.html:143 +#: templates/catalogue/tagged_object_list.html:142 msgid "Close" msgstr "Zamknij" #: templates/base.html:122 #: templates/base.html.py:128 #: templates/base.html:134 -#: templates/catalogue/book_detail.html:213 +#: templates/catalogue/book_detail.html:210 #: templates/catalogue/book_fragments.html:35 #: templates/catalogue/differentiate_tags.html:25 #: templates/catalogue/search_multiple_hits.html:31 #: templates/catalogue/search_too_short.html:21 -#: templates/catalogue/tagged_object_list.html:145 +#: templates/catalogue/tagged_object_list.html:144 msgid "Loading" msgstr "Ładowanie" @@ -197,7 +197,7 @@ msgstr "Szukaj" #: templates/catalogue/book_list.html:13 #: templates/catalogue/main_page.html:31 #: templates/catalogue/search_form.html:3 -#: templates/catalogue/tagged_object_list.html:45 +#: templates/catalogue/tagged_object_list.html:44 #: templates/info/base.html:12 #: templates/lesmianator/lesmianator.html:14 #: templates/lessons/document_list.html:34 @@ -273,12 +273,10 @@ msgstr "Pobierz plik PDF" #: templates/catalogue/book_detail.html:53 #: templates/catalogue/book_detail.html:56 #: templates/catalogue/book_detail.html:59 -#: templates/catalogue/book_detail.html:62 #: templates/catalogue/tagged_object_list.html:37 #: templates/catalogue/tagged_object_list.html:38 #: templates/catalogue/tagged_object_list.html:39 #: templates/catalogue/tagged_object_list.html:40 -#: templates/catalogue/tagged_object_list.html:41 msgid "for reading" msgstr "do czytania" @@ -292,110 +290,104 @@ msgid "Download EPUB" msgstr "Pobierz plik EPUB" #: templates/catalogue/book_detail.html:53 -#: templates/catalogue/book_detail.html:56 #: templates/catalogue/tagged_object_list.html:38 -#: templates/catalogue/tagged_object_list.html:39 msgid "on mobile devices" msgstr "na urządzeniach mobilnych" #: templates/catalogue/book_detail.html:56 -msgid "Download MOBI" -msgstr "Pobierz plik MOBI" - -#: templates/catalogue/book_detail.html:59 msgid "Download TXT" msgstr "Pobierz plik TXT" -#: templates/catalogue/book_detail.html:59 -#: templates/catalogue/tagged_object_list.html:41 +#: templates/catalogue/book_detail.html:56 +#: templates/catalogue/tagged_object_list.html:40 msgid "on small displays, for example mobile phones" msgstr "na małych ekranach, np. na komórce" -#: templates/catalogue/book_detail.html:62 +#: templates/catalogue/book_detail.html:59 msgid "Download ODT" msgstr "Pobierz plik ODT" -#: templates/catalogue/book_detail.html:62 -#: templates/catalogue/tagged_object_list.html:40 +#: templates/catalogue/book_detail.html:59 +#: templates/catalogue/tagged_object_list.html:39 msgid "and editing using" msgstr "i edytowania przy pomocy" -#: templates/catalogue/book_detail.html:67 +#: templates/catalogue/book_detail.html:64 msgid "Audiobooks" msgstr "Audiobooki" -#: templates/catalogue/book_detail.html:81 +#: templates/catalogue/book_detail.html:78 msgid "Artist" msgstr "Czyta" -#: templates/catalogue/book_detail.html:82 +#: templates/catalogue/book_detail.html:79 msgid "Director" msgstr "Reżyseruje" -#: templates/catalogue/book_detail.html:110 +#: templates/catalogue/book_detail.html:107 msgid "Audiobooks were prepared as a part of the projects:" msgstr "Audiobooki przygotowane w ramach projektów:" -#: templates/catalogue/book_detail.html:115 +#: templates/catalogue/book_detail.html:112 #, python-format msgid "%(cs)s, funded by %(fb)s" msgstr "%(cs)s, finansowanego przez %(fb)s" -#: templates/catalogue/book_detail.html:127 +#: templates/catalogue/book_detail.html:124 #, python-format msgid "Audiobooks were prepared as a part of the %(cs)s project funded by %(fb)s." msgstr "Audiobooki przygotowane w ramach projektu %(cs)s finansowanego przez %(fb)s." -#: templates/catalogue/book_detail.html:129 +#: templates/catalogue/book_detail.html:126 #, python-format msgid "Audiobooks were prepared as a part of the %(cs)s project." msgstr "Audiobooki przygotowane w ramach projektu %(cs)s." -#: templates/catalogue/book_detail.html:155 +#: templates/catalogue/book_detail.html:152 msgid "Details" msgstr "O utworze" -#: templates/catalogue/book_detail.html:158 +#: templates/catalogue/book_detail.html:155 msgid "Author" msgstr "Autor" -#: templates/catalogue/book_detail.html:164 +#: templates/catalogue/book_detail.html:161 msgid "Epoch" msgstr "Epoka" -#: templates/catalogue/book_detail.html:170 +#: templates/catalogue/book_detail.html:167 msgid "Kind" msgstr "Rodzaj" -#: templates/catalogue/book_detail.html:176 +#: templates/catalogue/book_detail.html:173 msgid "Genre" msgstr "Gatunek" -#: templates/catalogue/book_detail.html:182 +#: templates/catalogue/book_detail.html:179 msgid "Other resources" msgstr "W innych miejscach" -#: templates/catalogue/book_detail.html:185 +#: templates/catalogue/book_detail.html:182 msgid "Source of the book" msgstr "Źródło lektury" -#: templates/catalogue/book_detail.html:188 +#: templates/catalogue/book_detail.html:185 msgid "Book on the Editor's Platform" msgstr "Utwór na Platformie Redakcyjnej" -#: templates/catalogue/book_detail.html:191 +#: templates/catalogue/book_detail.html:188 msgid "Book description on Lektury.Gazeta.pl" msgstr "Opis lektury w Lektury.Gazeta.pl" -#: templates/catalogue/book_detail.html:194 +#: templates/catalogue/book_detail.html:191 msgid "Book description on Wikipedia" msgstr "Opis lektury w Wikipedii" -#: templates/catalogue/book_detail.html:197 +#: templates/catalogue/book_detail.html:194 msgid "View XML source" msgstr "Źródłowy plik XML" -#: templates/catalogue/book_detail.html:201 +#: templates/catalogue/book_detail.html:198 msgid "Work's themes " msgstr "Motywy w utworze" @@ -516,7 +508,7 @@ msgid "Table of contents" msgstr "Spis treści" #: templates/catalogue/book_text.html:22 -#: templates/catalogue/tagged_object_list.html:134 +#: templates/catalogue/tagged_object_list.html:133 msgid "Themes" msgstr "Motywy" @@ -528,10 +520,6 @@ msgstr "Nota red." msgid "Infobox" msgstr "Informacje" -#: templates/catalogue/collection.html:6 -msgid "in WolneLektury.pl" -msgstr "w WolneLektury.pl" - #: templates/catalogue/daisy_list.html:6 msgid "Listing of all DAISY files on WolneLektury.pl" msgstr "Spis wszystkich plików DAISY w WolneLektury.pl" @@ -563,15 +551,15 @@ msgid "Show full category" msgstr "Zobacz całą kategorię" #: templates/catalogue/folded_tag_list.html:13 -#: templates/catalogue/main_page.html:103 -#: templates/catalogue/main_page.html:108 -#: templates/catalogue/main_page.html:147 -#: templates/catalogue/main_page.html:342 +#: templates/catalogue/main_page.html:111 +#: templates/catalogue/main_page.html:116 +#: templates/catalogue/main_page.html:155 +#: templates/catalogue/main_page.html:350 msgid "See more" msgstr "Zobacz więcej" #: templates/catalogue/folded_tag_list.html:22 -#: templates/catalogue/main_page.html:310 +#: templates/catalogue/main_page.html:318 msgid "Hide" msgstr "Zwiń" @@ -580,7 +568,7 @@ msgid "Shelves containing fragment" msgstr "Półki zawierające fragment" #: templates/catalogue/fragment_sets.html:4 -#: templates/catalogue/main_page.html:76 +#: templates/catalogue/main_page.html:85 msgid "You do not own any shelves. You can create one below, if you want to." msgstr "Nie posiadasz żadnych półek. Jeśli chcesz, możesz utworzyć nową półkę poniżej." @@ -627,143 +615,143 @@ msgstr[2] "" msgid "Browse books by categories" msgstr "Przeglądaj lektury według wybranych kategorii" -#: templates/catalogue/main_page.html:59 +#: templates/catalogue/main_page.html:69 msgid "Books for every school level" msgstr "Lektury na każdy poziom edukacji" -#: templates/catalogue/main_page.html:61 +#: templates/catalogue/main_page.html:71 msgid "primary school" msgstr "szkoła podstawowa" -#: templates/catalogue/main_page.html:62 +#: templates/catalogue/main_page.html:72 msgid "gymnasium" msgstr "gimnazjum" -#: templates/catalogue/main_page.html:63 +#: templates/catalogue/main_page.html:73 msgid "high school" msgstr "szkoła średnia" -#: templates/catalogue/main_page.html:67 +#: templates/catalogue/main_page.html:76 #: templates/catalogue/user_shelves.html:2 msgid "Your shelves with books" msgstr "Twoje półki z lekturami" -#: templates/catalogue/main_page.html:72 +#: templates/catalogue/main_page.html:81 msgid "delete" msgstr "usuń" -#: templates/catalogue/main_page.html:81 +#: templates/catalogue/main_page.html:90 #: templates/catalogue/user_shelves.html:15 msgid "Create shelf" msgstr "Utwórz półkę" -#: templates/catalogue/main_page.html:86 +#: templates/catalogue/main_page.html:94 msgid "Create your own book set. You can share it with friends by sending them link to your shelf." msgstr "Stwórz własny zestaw lektur. Możesz się nim później podzielić z innymi, przesyłając im link do Twojej półki." -#: templates/catalogue/main_page.html:87 +#: templates/catalogue/main_page.html:95 msgid "You need to " msgstr "Aby zarządzać swoimi półkami, musisz się" -#: templates/catalogue/main_page.html:87 +#: templates/catalogue/main_page.html:95 msgid "sign in" msgstr "zalogować" -#: templates/catalogue/main_page.html:87 +#: templates/catalogue/main_page.html:95 msgid "to manage your shelves." msgstr "." -#: templates/catalogue/main_page.html:93 +#: templates/catalogue/main_page.html:101 msgid "Twórzże się!" msgstr "" -#: templates/catalogue/main_page.html:95 #: templates/catalogue/main_page.html:103 +#: templates/catalogue/main_page.html:111 msgid "Wolne Lektury Widget" msgstr "Widżet Wolne Lektury" -#: templates/catalogue/main_page.html:96 +#: templates/catalogue/main_page.html:104 msgid "Place our widget - search engine for Wolne Lektury which gives access to free books and audiobooks - on your homepage! Just copy the HTML code below onto your page:" msgstr "Umieść widżet – wyszukiwarkę Wolnych Lektur umożliwiającą dostęp do darmowych lektur i audiobooków – na swojej stronie WWW! Po prostu skopiuj poniższy kod HTML na swoją stronę:" -#: templates/catalogue/main_page.html:97 +#: templates/catalogue/main_page.html:105 msgid "Insert this element in place where you want display the widget" msgstr "Umieść ten element w miejscu gdzie chcesz wyświetlić widżet" -#: templates/catalogue/main_page.html:100 +#: templates/catalogue/main_page.html:108 msgid "Place this element just before closing body tag: </body>" msgstr "Umieść ten element tuż przed zamknięciem taga body: </body>" -#: templates/catalogue/main_page.html:106 -#: templates/catalogue/main_page.html:108 +#: templates/catalogue/main_page.html:114 +#: templates/catalogue/main_page.html:116 #: templates/lessons/document_list.html:32 msgid "Hand-outs for teachers" msgstr "Materiały pomocnicze dla nauczycieli" -#: templates/catalogue/main_page.html:107 +#: templates/catalogue/main_page.html:115 msgid "Lessons' prospects and other ideas for using Wolnelektury.pl for teaching." msgstr "Scenariusze lekcji i inne pomysły na wykorzytanie serwisu WolneLektury.pl podczas nauczania." -#: templates/catalogue/main_page.html:114 -#: templates/catalogue/tagged_object_list.html:113 +#: templates/catalogue/main_page.html:122 +#: templates/catalogue/tagged_object_list.html:112 msgid "Authors" msgstr "Autorzy" -#: templates/catalogue/main_page.html:118 -#: templates/catalogue/tagged_object_list.html:117 +#: templates/catalogue/main_page.html:126 +#: templates/catalogue/tagged_object_list.html:116 msgid "Kinds" msgstr "Rodzaje" -#: templates/catalogue/main_page.html:122 -#: templates/catalogue/tagged_object_list.html:121 +#: templates/catalogue/main_page.html:130 +#: templates/catalogue/tagged_object_list.html:120 msgid "Genres" msgstr "Gatunki" -#: templates/catalogue/main_page.html:126 -#: templates/catalogue/tagged_object_list.html:125 +#: templates/catalogue/main_page.html:134 +#: templates/catalogue/tagged_object_list.html:124 msgid "Epochs" msgstr "Epoki" -#: templates/catalogue/main_page.html:132 -#: templates/catalogue/main_page.html:147 +#: templates/catalogue/main_page.html:140 +#: templates/catalogue/main_page.html:155 msgid "Themes and topics" msgstr "Motywy i tematy" -#: templates/catalogue/main_page.html:135 +#: templates/catalogue/main_page.html:143 msgid "Themes groups" msgstr "Rodziny motywów" -#: templates/catalogue/main_page.html:320 +#: templates/catalogue/main_page.html:328 msgid "News" msgstr "Aktualności" -#: templates/catalogue/main_page.html:324 +#: templates/catalogue/main_page.html:332 msgid "See our blog" msgstr "Zobacz nasz blog" -#: templates/catalogue/main_page.html:327 -#: templates/catalogue/main_page.html:333 +#: templates/catalogue/main_page.html:335 +#: templates/catalogue/main_page.html:341 msgid "You can help us!" msgstr "Możesz nam pomóc!" -#: templates/catalogue/main_page.html:329 +#: templates/catalogue/main_page.html:337 msgid "Become a volunteer – an editor, developer or translator." msgstr "Zostań naszym redaktorem, programistą lub tłumaczem – wolontariuszem." -#: templates/catalogue/main_page.html:330 +#: templates/catalogue/main_page.html:338 msgid "Gain new skills and experience." msgstr "Zdobądź nowe umiejętności i doświadczenie." -#: templates/catalogue/main_page.html:331 +#: templates/catalogue/main_page.html:339 msgid "Join an open project of creating an innovative online library." msgstr "Weź udział w otwartym projekcie i twórz innowacyjną bibliotekę internetową." -#: templates/catalogue/main_page.html:336 -#: templates/catalogue/main_page.html:342 +#: templates/catalogue/main_page.html:344 +#: templates/catalogue/main_page.html:350 msgid "About us" msgstr "O projekcie" -#: templates/catalogue/main_page.html:338 +#: templates/catalogue/main_page.html:346 msgid "" "\n" "\t\t\tInternet library with school readings “Wolne Lektury” (<a href=\"http://wolnelektury.pl\">www.wolnelektury.pl</a>) is a project made by Modern Poland Foundation. It started in 2007 and shares school readings, which are recommended by Ministry of National Education and are in public domain.\n" @@ -772,7 +760,7 @@ msgstr "" "\n" "Biblioteka internetowa z lekturami szkolnymi „Wolne Lektury” (<a href=\"http://wolnelektury.pl\">www.wolnelektury.pl</a>) to projekt realizowany przez fundację Nowoczesna Polska. Działa od 2007 roku i udostępnia w swoich zbiorach lektury szkolne, które są zalecane do użytku przez Ministerstwo Edukacji Narodowej i które trafiły już do domeny publicznej." -#: templates/catalogue/main_page.html:351 +#: templates/catalogue/main_page.html:359 msgid "" "\n" "Portions of this page are modifications based on work created and <a href=\"http://code.google.com/policies.html\">shared by Google</a> and used\n" @@ -802,7 +790,7 @@ msgid "Search in WolneLektury.pl" msgstr "Wyszukiwanie w WolneLektury.pl" #: templates/catalogue/search_no_hits.html:14 -#: templates/catalogue/tagged_object_list.html:103 +#: templates/catalogue/tagged_object_list.html:101 msgid "Sorry! Search cirteria did not match any resources." msgstr "Przepraszamy! Brak wyników spełniających kryteria podane w zapytaniu." @@ -836,93 +824,93 @@ msgstr "Pobierz wszystkie książki z tej półki" msgid "Choose books' formats which you want to download:" msgstr "Wybierz formaty książek, które chcesz pobrać:" +#: templates/catalogue/tagged_object_list.html:41 #: templates/catalogue/tagged_object_list.html:42 -#: templates/catalogue/tagged_object_list.html:43 msgid "for listening" msgstr "do słuchania" -#: templates/catalogue/tagged_object_list.html:42 +#: templates/catalogue/tagged_object_list.html:41 msgid "on favourite MP3 player" msgstr "w ulubionym odtwarzaczu MP3" -#: templates/catalogue/tagged_object_list.html:43 +#: templates/catalogue/tagged_object_list.html:42 msgid "open format" msgstr "otwarty format" -#: templates/catalogue/tagged_object_list.html:43 +#: templates/catalogue/tagged_object_list.html:42 msgid "Xiph.org Foundation" msgstr "fundacji Xiph.Org" -#: templates/catalogue/tagged_object_list.html:45 +#: templates/catalogue/tagged_object_list.html:44 #: templates/lessons/ajax_document_detail.html:3 msgid "Download" msgstr "Pobierz" -#: templates/catalogue/tagged_object_list.html:45 +#: templates/catalogue/tagged_object_list.html:44 msgid "Updating list of books' formats on the shelf" msgstr "Uaktualnianie listy formatów książek na półce." -#: templates/catalogue/tagged_object_list.html:45 +#: templates/catalogue/tagged_object_list.html:44 msgid "cancel" msgstr "anuluj" -#: templates/catalogue/tagged_object_list.html:50 +#: templates/catalogue/tagged_object_list.html:49 msgid "Share this shelf" msgstr "Podziel się tą półką" -#: templates/catalogue/tagged_object_list.html:52 +#: templates/catalogue/tagged_object_list.html:51 msgid "Copy this link and share it with other people to let them see your shelf." msgstr "Skopiuj ten link i przekaż go osobom, z którymi chcesz się podzielić tą półką." -#: templates/catalogue/tagged_object_list.html:62 +#: templates/catalogue/tagged_object_list.html:61 #: templates/pdcounter/author_detail.html:27 msgid "Read work's study of this author on Lektury.Gazeta.pl" msgstr "Przeczytaj omówienia utworów autora w serwisie Lektury.Gazeta.pl" -#: templates/catalogue/tagged_object_list.html:64 +#: templates/catalogue/tagged_object_list.html:63 #, python-format msgid "Read study of epoch %(last_tag)s on Lektury.Gazeta.pl" msgstr "Przeczytaj omówienia z epoki %(last_tag)s w serwisie Lektury.Gazeta.pl" -#: templates/catalogue/tagged_object_list.html:66 +#: templates/catalogue/tagged_object_list.html:65 #, python-format msgid "Read study of kind %(last_tag)s on Lektury.Gazeta.pl" msgstr "Przeczytaj omówienia z rodzaju %(last_tag)s w serwisie Lektury.Gazeta.pl" -#: templates/catalogue/tagged_object_list.html:68 +#: templates/catalogue/tagged_object_list.html:67 #, python-format msgid "Read study of genre %(last_tag)s on Lektury.Gazeta.pl" msgstr "Przeczytaj omówienia z gatunku %(last_tag)s w serwisie Lektury.Gazeta.pl" -#: templates/catalogue/tagged_object_list.html:70 +#: templates/catalogue/tagged_object_list.html:69 msgid "Read related study on Lektury.Gazeta.pl" msgstr "Przeczytaj powiązane omówienia w serwisie Lektury.Gazeta.pl" -#: templates/catalogue/tagged_object_list.html:78 +#: templates/catalogue/tagged_object_list.html:77 #: templates/pdcounter/author_detail.html:32 msgid "Read article about this author on Wikipedia" msgstr "Przeczytaj artykuł o autorze w Wikipedii" -#: templates/catalogue/tagged_object_list.html:80 +#: templates/catalogue/tagged_object_list.html:79 #, python-format msgid "Read article about epoch %(last_tag)s on Wikipedia" msgstr "Przeczytaj artykuł o epoce %(last_tag)s w Wikipedii" -#: templates/catalogue/tagged_object_list.html:82 +#: templates/catalogue/tagged_object_list.html:81 #, python-format msgid "Read article about kind %(last_tag)s on Wikipedia" msgstr "Przeczytaj artykuł o rodzaju %(last_tag)s w Wikipedii" -#: templates/catalogue/tagged_object_list.html:84 +#: templates/catalogue/tagged_object_list.html:83 #, python-format msgid "Read article about genre %(last_tag)s on Wikipedia" msgstr "Przeczytaj artykuł o gatunku %(last_tag)s w Wikipedii" -#: templates/catalogue/tagged_object_list.html:86 +#: templates/catalogue/tagged_object_list.html:85 msgid "Read related article on Wikipedia" msgstr "Przeczytaj powiązany artykuł w Wikipedii" -#: templates/catalogue/tagged_object_list.html:96 +#: templates/catalogue/tagged_object_list.html:95 msgid "Delete" msgstr "Usuń" @@ -1076,6 +1064,9 @@ msgstr "Zaloguj się" #~ "to profesjonalne nagrania tekstów literackich z naszego zbioru dostępne " #~ "na wolnej licencji w formatach MP3, Ogg Vorbis oraz w systemie DAISY." +#~ msgid "Download MP3" +#~ msgstr "Pobierz plik MP3" + #~ msgid "Download Ogg Vorbis" #~ msgstr "Pobierz plik Ogg Vorbis" diff --git a/wolnelektury/settings.py b/wolnelektury/settings.py index e34b9052b..a69b0508e 100644 --- a/wolnelektury/settings.py +++ b/wolnelektury/settings.py @@ -60,6 +60,7 @@ USE_I18N = True # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = path.join(PROJECT_DIR, '../media/') STATIC_ROOT = path.join(PROJECT_DIR, 'static/') +SEARCH_INDEX = path.join(MEDIA_ROOT, 'search/') # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). @@ -88,6 +89,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.media', 'django.core.context_processors.request', 'wolnelektury.context_processors.extra_settings', + 'search.context_processors.search_form', ) MIDDLEWARE_CLASSES = [ @@ -110,7 +112,7 @@ TEMPLATE_DIRS = [ path.join(PROJECT_DIR, 'templates'), ] -LOGIN_URL = '/uzytkownicy/login/' +LOGIN_URL = '/uzytkownicy/zaloguj/' LOGIN_REDIRECT_URL = '/' @@ -137,6 +139,7 @@ INSTALLED_APPS = [ 'modeltranslation', # our + 'ajaxable', 'api', 'catalogue', 'chunks', @@ -151,6 +154,8 @@ INSTALLED_APPS = [ 'sponsors', 'stats', 'suggest', + 'picture', + 'search', ] CACHES = { @@ -172,13 +177,33 @@ CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True # CSS and JavaScript file groups COMPRESS_CSS = { 'all': { - 'source_filenames': ('css/master.css', 'css/jquery.autocomplete.css', 'css/jquery.countdown.css', 'css/master.plain.css', 'css/sponsors.css', 'css/facelist_2-0.css',), + #'source_filenames': ('css/master.css', 'css/jquery.autocomplete.css', 'css/master.plain.css', 'css/facelist_2-0.css',), + 'source_filenames': [ + 'css/jquery.countdown.css', + + 'css/base.css', + 'css/header.css', + 'css/main_page.css', + 'css/dialogs.css', + 'css/picture_box.css', + 'css/book_box.css', + 'css/catalogue.css', + 'css/sponsors.css', + + 'css/ui-lightness/jquery-ui-1.8.16.custom.css', + ], 'output_filename': 'css/all.min?.css', }, 'book': { 'source_filenames': ('css/master.book.css',), 'output_filename': 'css/book.min?.css', }, + 'player': { + 'source_filenames': [ + 'jplayer/jplayer.blue.monday.css', + ], + 'output_filename': 'css/player.min?.css', + }, 'simple': { 'source_filenames': ('css/simple.css',), 'output_filename': 'css/simple.min?.css', @@ -186,20 +211,37 @@ COMPRESS_CSS = { } COMPRESS_JS = { - 'jquery': { - 'source_filenames': ('js/jquery.js',), - 'output_filename': 'js/jquery.min.js', - }, - 'all': { - 'source_filenames': ('js/jquery.autocomplete.js', 'js/jquery.form.js', + 'base': { + 'source_filenames': ( + 'js/jquery.cycle.min.js', + 'js/jquery.jqmodal.js', + 'js/jquery.form.js', 'js/jquery.countdown.js', 'js/jquery.countdown-pl.js', 'js/jquery.countdown-de.js', 'js/jquery.countdown-uk.js', 'js/jquery.countdown-es.js', 'js/jquery.countdown-lt.js', 'js/jquery.countdown-ru.js', 'js/jquery.countdown-fr.js', - 'js/jquery.cycle.min.js', - 'js/jquery.jqmodal.js', 'js/jquery.labelify.js', 'js/catalogue.js', + + 'js/jquery-ui-1.8.16.custom.min.js', + + 'js/locale.js', + 'js/dialogs.js', + 'js/sponsors.js', + 'js/base.js', + 'js/pdcounter.js', + + 'js/search.js', + + 'js/jquery.labelify.js', ), - 'output_filename': 'js/all?.min.js', + 'output_filename': 'js/base?.min.js', + }, + 'player': { + 'source_filenames': [ + 'jplayer/jquery.jplayer.min.js', + 'jplayer/jplayer.playlist.min.js', + 'js/player.js', + ], + 'output_filename': 'js/player.min?.js', }, 'book': { 'source_filenames': ('js/jquery.eventdelegation.js', 'js/jquery.scrollto.js', 'js/jquery.highlightfade.js', 'js/book.js',), @@ -240,12 +282,16 @@ MAX_TAG_LIST = 6 NO_BUILD_EPUB = False NO_BUILD_TXT = False NO_BUILD_PDF = False -NO_BUILD_MOBI = False +NO_BUILD_MOBI = True +NO_SEARCH_INDEX = False ALL_EPUB_ZIP = 'wolnelektury_pl_epub' ALL_PDF_ZIP = 'wolnelektury_pl_pdf' ALL_MOBI_ZIP = 'wolnelektury_pl_mobi' +CATALOGUE_DEFAULT_LANGUAGE = 'pol' +PUBLISH_PLAN_FEED = 'http://redakcja.wolnelektury.pl/documents/track/editor-proofreading/' + PAGINATION_INVALID_PAGE_RAISES_404 = True import djcelery @@ -259,6 +305,7 @@ BROKER_PASSWORD = "guest" BROKER_VHOST = "/" + # Load localsettings, if they exist try: from localsettings import * diff --git a/wolnelektury/static/css/base.css b/wolnelektury/static/css/base.css new file mode 100755 index 000000000..5ce2f5a78 --- /dev/null +++ b/wolnelektury/static/css/base.css @@ -0,0 +1,186 @@ +/* Logo font */ +@font-face { + /* IE version */ + font-family: WL-Logo; + src: url(/static/fonts/WL.eot); +} +@font-face { + font-family: WL-Nav; + src: url(/static/fonts/WL-Nav.ttf) format("truetype"); +} + + +html { + margin: 0; + padding: 0; +} + +body { + margin: 0; + background: #f7f7f7; + font-size: .625em; + font-family: Georgia; + /*line-height: 1.4em;*/ +} + + +a { + color: #1199a2; /* #01adba; */ + text-decoration: none; +} + +h1 { + font-size: 3.5em; + font-weight: normal; + margin-top: .4em +} + +.left-column { + width: 47em; + float: left; +} +.right-column { + float:right; + width: 47em; +} +.normal-text { + font-size: 1.3em; + line-height: 1.3em; +} + +h2 { + margin: 0; + font-size: 1em; + font-weight: normal; +} + + +.mono { + font-family: "Andale Mono", "Lucida Sans Typewriter", "Courier New"; + font-weight: bold; +} + +.accent1 { + color: #191919; +} + +.accent2 { + color: #242424; +} + +.accent3 { + color: #575c63; +} + + +.accent4 { + color: #707b7a; +} + +.contrast { + #1199a2; +} + +/* #281d1c */ + +.clearboth { + clear: both; +} + +#header-content, div#main-content, div#half-header-content, #footer-content { + width: 97.5em; + margin: auto; +} + + +.page-desc { + margin-left: 1.5em; +} + +.inline-tag-lists p span:first-child { + color: #281d1c; +} + +.inline-tag-lists { + font-size: 1.1em; +} + +#themes-list-toggle:after { + padding-left: 1em; + content: "↓"; + font-family: WL-Nav; + vertical-align: middle; +} +#themes-list-wrapper { + position: relative; + font-size: 1.1em; +} +#themes-list { + position: absolute; + display: none; + border: 1px solid #ddd; + padding: 1em; + background: #fff; + -moz-box-shadow: 2px 2px 2px #ddd; + -webkit-box-shadow: 2px 2px 2px #ddd; + box-shadow: 2px 2px 2px #ddd; + z-index: 500; +} +#themes-list ul { + list-style: none; + padding: 0; + margin: 0; + font-size: 1.1em; + -moz-column-width: 12em; + width: 48em; +} + + +a.cite { + display: block; + color: black; + background: white; + padding: 3em 2em .1em 8em; +} +.cite-body { + font-size: 1.8em; + line-height: 1.3em; +} +.cite p { + color: #444; + font-size: 1.1em; + margin-top: 1.6em; +} + +.see-also { + margin-left: 8em; + float: left; + width: 14.3em; +} +.download { + margin-left: 2em; + float: left; +} + +.see-also, .download { + margin-top: 2em; + margin-bottom: 2em; +} +.see-also h2, .download h2 { + font-size: 1.1em; +} +.see-also ul, .download ul { + list-style: none; + padding: 0; + margin: 0; + font-size: 1.1em; +} + + +#footer { + color: #777; + eborder-top: 1px solid #ddd; + margin-top: 5em; + padding-top:3em; + background: #fff; +} diff --git a/wolnelektury/static/css/book_box.css b/wolnelektury/static/css/book_box.css new file mode 100755 index 000000000..12adda1d9 --- /dev/null +++ b/wolnelektury/static/css/book_box.css @@ -0,0 +1,225 @@ +.book-wide-box, .book-mini-box, .book-box { + display: inline-block; + margin: 0; + vertical-align: top; +} + + +.book-box { + width: 48.75em; +} + +.book-mini-box { + width: 16.15em; +} + +.book-wide-box { + width: 98.5em; + margin-left: -0.1em; +} + +.book-mini-box a, .book-box-inner { + display: block; + color: black; + border: 1px solid #ddd; + height: 20em; + padding: .8em 1em; + margin: .1em; + background: #fff; + -moz-box-shadow: 2px 2px 2px #ddd; + -webkit-box-shadow: 2px 2px 2px #ddd; + box-shadow: 2px 2px 2px #ddd; +} + +.book-mini-box a { + height: 27.1em; + margin: .1em; + overflow: hidden; +} +.book-box-inner { + height: 19.75em; + margin: .5em; +} + +.book-wide-box .book-box-inner { + height: 24.4em; +} + +.book-mini-box img, .book-box img, .book-wide-box img { + width: 13.9em; + height: 19.3em; +} +.book-mini-box img { + margin-bottom: 1.8em; +} +.book-box img, .book-wide-box img { + float: left; + margin-right: 1.5em; +} + +.book-mini-box .desc { + margin-left:0em; +} +.book-mini-box .author { + font-size: 1.1em; + color: #707b7a; + display: block; +} +.book-mini-box .title { + font-size: 1.4em; + color: #242424; +} + + +.book-box-body { + height: 17em; + overflow: hidden; +} + +.book-wide-box .book-box-body { + height: 21.8em; +} + +.book-box-head { + min-height: 7em; + margin-top: 1.4em; + margin-bottom: 1em; +} +.book-box-head .author { + font-size: 1.1em; +} +.book-box-head .title { + font-size: 2.4em; + margin-top: .3em; +} +.book-box-body .tags { + font-size: 1.1em; +} +.book-box-tag { + margin-right: .5em; + margin-left: .4em; +} +.book-box-download { + position: relative; +} + +.book-box-download a { + position: relative; + z-index: 1; +} + +.book-box-formats { + display: none; + position: absolute; + + width: 16.363em; + border: 1px solid #ddd; + padding: 3.454em 1.727em .818em 1.727em; + background: #fff; + -moz-box-shadow: 2px 2px 2px #ddd; + -webkit-box-shadow: 2px 2px 2px #ddd; + box-shadow: 2px 2px 2px #ddd; + + z-index: 0; + top: -1.454em; + left: -1.727em; +} +.book-box-formats span { + display: block; +} + + +.book-box-download:hover .book-box-formats span:first-child { + margin-top: 1.454em; +} + +.book-box-download:hover .book-box-formats { + display: block; +} + +.book-box-tools { + font-size: 1.1em; +} + +.book-wide-box .book-box-tools { + margin-left: 14em; +} + +.book-box-tools a.downarrow:before { + content: "\2609"; + font-family: WL-Nav; + font-size: 2.25em; + margin-right: .15em; + vertical-align: middle; +} + +.book-box-audiobook a:before { + content: "\266B"; + font-family: WL-Nav; + font-size: 2.25em; + margin-right: .15em; + vertical-align: middle; +} + +ul.book-box-tools { + margin: 0; + padding: 0; +} + +.book-box-tools li { + display: inline-block; +} + +.book-box-read { + width: 11.5em; +} +.book-box-download { + width: 8.5em; +} +.book-box-audiobook { + width: 7em; +} + +.book-wide-box .right-column { + float: right; + width: 41.5em; +} + +.book-wide-box blockquote.cite-body { + /* @ 18pt */ + width: 100%; /*23.055em;*/ + height: 7.222em; + background-color: #f7f7f7; + margin: 0; + position: relative; + top: -0.444em; + right: -0.555em; + vertical-align: center; +} + +.book-wide-box blockquote div { + padding: 0.888em; +} + +ul.inline-items, ul.inline-items li { + margin: 0; + padding: 0; +} + +ul.inline-items li { + display: inline-block; +} + +.book-wide-box #other-tools { + float: left; + width: 14.5em; + margin: 6em 0 0 1.5em; + +} + +.book-wide-box #other-download { + float: left; + width: 22.5em; + margin: 6em 1.5em 0em 1.5em +} + diff --git a/wolnelektury/static/css/catalogue.css b/wolnelektury/static/css/catalogue.css new file mode 100755 index 000000000..ff7ea4b9c --- /dev/null +++ b/wolnelektury/static/css/catalogue.css @@ -0,0 +1,69 @@ +.work-list { + margin: 0; + padding: 0; + list-style: none; +} + +.work-item { + margin: 0; + padding: 0; +} + +.books .work-item { + display: inline-block; +} + + +#tagged-object-list .left-column, #tagged-object-list .right-column { + width: 48em; +} + + +/* listing of all books */ +#book-list { + padding-left: 50px; +} +#book-list-nav { + position: absolute; + right: 50px; + width: 200px; + border-left: 1px #cfcfcf solid; + padding: 10px; + font-size: 1.2em; +} + +#book-list-nav ul { + list-style-type: none; + margin: 5px; + padding: 0; +} + +.book-list-show-index { + display: none; + margin: 0; + padding: 0; +} + + +#book-a-list #book-list ol { + padding-left: 1em; + margin: 0.3em 0 1.2em 0; + list-style: none; +} + +#book-a-list #book-list h2 a { + color: black; +} + +#book-list-up { + position: fixed; + bottom: 50px; + right: 50px; + border-left: 1px #cfcfcf solid; + padding: 10px; + background-color: white; +} + + + +fragment \ No newline at end of file diff --git a/wolnelektury/static/css/dialogs.css b/wolnelektury/static/css/dialogs.css new file mode 100755 index 000000000..35136e0ca --- /dev/null +++ b/wolnelektury/static/css/dialogs.css @@ -0,0 +1,104 @@ +.cuteform { + font-size: 1.1em; +} +.cuteform ol, .cuteform ul { + padding: 0; + margin: 0; + list-style: none; + font-style: 1.1em; +} + +.cuteform ol li, .cuteform ul li { + margin-top: 0.7em; +} + +.cuteform label { + display: block; +} + +.cuteform span.help-text { + display: block; + font-size: 0.8em; + color: #999; +} + +.cuteform .error { + color: #BF3024; + display: block; +} +.cuteform .errorlist { + color: #BF3024; +} + + +.jqmOverlay { background-color: #000; } + + +.dialog-window { + position: absolute; + display: none; + background-color: transparent; + margin-top: -0.5em; + margin-left: 1em; +} + +.dialog-window div.header { + font-size: 1.1em; + width: 4em; + background-color: #FFF; + border-right: 0.3em solid #DDD; + padding: 0.5em 1em 0.5em 1em; + right: 0; + left: auto; + float: right; + text-align: center; +} + + +.dialog-window div.target { + background-color: #FFF; + color: black; + border-right: 0.3em solid #DDD; + border-bottom: 0.3em solid #DDD; + padding: 1em; + clear: both; +} + +.dialog-window h1 { + font-size: 1.2em; +} + +.dialog-window textarea, .dialog-window input { + width: 100%; +} + +#login-window { + width: 26em; +} +#register-window { + width: 26em; +} + +#suggest-window { + width: 26em; +} + +#suggest-window textarea { + height: 6em; +} + +#suggest-publishing-window { + width: 29em; +} + +#suggest-publishing-window textarea { + height: 3em; +} + +#custom-pdf-window { + width: 24em; +} + +#custom-pdf-window label { + display: inline; +} diff --git a/wolnelektury/static/css/header.css b/wolnelektury/static/css/header.css new file mode 100755 index 000000000..9435ad7c0 --- /dev/null +++ b/wolnelektury/static/css/header.css @@ -0,0 +1,207 @@ +/* Logo font */ +@font-face { + /* IE version */ + font-family: WL-Logo; + src: url(/static/fonts/WL.eot); +} +@font-face { + font-family: WL-Logo; + src: url(/static/fonts/WL.ttf) format("truetype"); +} + + +#header { + height: 3em; + padding-top: 1.9em; + padding-bottom: 0; + color: #989898; + background: #191919; +} + +#half-header { + padding-bottom: 0; + background: url('/static/img/bg-header.png'); + background-position: center; + background-size: 100%; +} + +#half-header-content { + background: #191919; +} + + +#user-info { + float: right; + margin: 0; +} + +#logo { + position: absolute; + top: 1.9em; + margin-left: 1.5em; +} + +#logo a { + font-family: WL-Logo; + font-size: 2.05em; + color:#f7f7f7; +} + +#tagline { + display: inline-block; + margin-left: 25.5em; +} +#tagline span { + font-size: 1.1em; +} + +#search-area { + margin: 0; + background: #444; + margin-left: 24em; + width: 73.5em; +} + +#search-field { + display: inline-block; + width: 63.1em; + padding-left: .5em; + padding-right: 0; + padding-top: 0.6em; + padding-bottom: 0; +} + +#search { + font-size: 1.3em; + padding: 0; + /*height: 3.3em; + width: 62.6em; + padding-left: .5em; + -webkit-border-radius: .5em; + -moz-border-radius: .5em; + border: none; + border-radius: .5em; + -webkit-box-shadow:0 0 .5em #444 inset; + -moz-box-shadow:0 0 .5em #444 inset; + box-shadow: 0 0 .5em #444 inset;*/ + height: 2.54em; + width: 47.47em; + padding-left: 1em; + -webkit-border-radius: .38em; + -moz-border-radius: .38em; + border: none; + border-radius: .38em; + -webkit-box-shadow:0 0 .38em #444 inset; + -moz-box-shadow:0 0 .38em #444 inset; + box-shadow: 0 0 .5em #444 inset; + + font-family: Georgia; + background-color: #fff; + color: #000; +} +#search.blur { + font-family: Georgia; + font-style: italic; + color: #888; +} + +#search-button { + display: inline-block; + background: #02adb7; + padding: 0; + margin: 0; + width: 9.4em; + float: right; +} +#search-button button { + font-size: 1em; + height: 4.5em; + border: none; + background: #02adb7; + color: white; + width: 100%; + padding: 0; +} + +#search-button button span { + font-size: 1.1em; + position:relative; +} + + +#nav-line { + background-color: #e2e2e2; + height: 4.9em; +} + +ul#catalogue { + list-style: none; + padding: 0; + margin: 0 0 0 .6em; +} +ul#catalogue li { + background-color: #e2e2e2; + float: left; +} +ul#catalogue a { + display: block; + padding-left: 1.4em; + padding-right: 1.4em; + /* must match grid-line */ + height: 3.1em; + padding-top: 1.8em; +} +ul#catalogue span { + font-size: 1.1em; +} + + +#lang-button { + color: #aaa; +} +#lang-button:after { + padding-left: 1em; + content: "↓"; + font-family: WL-Nav; + vertical-align: middle; +} +#lang-menu { + position: relative; + float: right; + display: block; + padding-left: 2.5em; + padding-right: 2em; + /* must match grid-line */ + height: 3em; + padding-top: 1.9em; + background: #f7f7f7; +} + +#lang-menu-items button { + display: none; + background: #f7f7f7; + color: #777; + cursor: pointer; + width: 100%; + border: solid #ddd; + border-width: 0 0 1px 0; + padding: .5em 0; + margin: 0; +} + +#lang-menu:hover button { + display: block; +} + +#lang-menu:hover #lang-menu-items { + position: absolute; + width: 100%; + padding: 0; + left: 0; + /* must match grid-line height */ + top: 3.9em; +} + +#lang-menu .active { + color: #000; +} diff --git a/wolnelektury/static/css/main_page.css b/wolnelektury/static/css/main_page.css new file mode 100755 index 000000000..9124ee049 --- /dev/null +++ b/wolnelektury/static/css/main_page.css @@ -0,0 +1,116 @@ +#big-cite { + background-color: white; + padding: 10.75em 10em 8.5em 18.2em; + margin: 0; +} + +#big-cite a { + color: black; + display: block; +} + +#big-cite h2 { + margin: 0; + font-size: 1.1em; + color: #575c63; +} + + +#big-cite-text { + margin: .05em; + font-size: 2.8em; + line-height: 1.2em; + color: #191919; +} + + +#big-cite-source { + color: #00a1ac; + margin: 0; + font-size: 1.1em; + margin: 1.1em 0.2em; +} + + +#promo-box { + float: right; + width: 32em; + margin-top: -5.1em; +} +#promo-box-header { + padding-top: 2em; + height: 3.1em; + padding-bottom: 0; + padding-left: 2.5em; + padding-right: 2.5em; + background: #191919; + color: white; +} +#promo-box-header h2 { + font-size: 1.1em; + padding-top: .1em; +} +#promo-box-body { + border-bottom: 2px solid #efefef; + padding: 2em 2.8em; + height: 30em; + background: #efefef; +} +#promo-box-title { + color: #02ADB7; + height: 2.75em; + margin: 0; +} +#promo-box-title span { + font-size: 1.1em; +} +#promo-box-body p { + margin-top: 0; +} +#promo-box-content { + font-size: 1.2em; + line-height: 1.55em; + color: #989898; +} + +.main-last { + padding-top: 1.9em; + height: 3.2em; + padding-left: 1.9em; +} +.main-last span { + font-size: 1.1em; +} + + +.infopages-box { + width: 20.6em; + display: inline-block; + margin: .5em 0 0 0; + padding: 0 1.7em; + vertical-align: top; + color: #989898; +} +.infopages-box h2 { + color: #02ADB7; + height: 2.8em; + padding-top: 2.5em; +} +.infopages-box h2 span { + font-size: 1.1em; +} +.infopages-box a { + color: black; +} + +.infopages-box ol, .infopages-box ul { + font-size: 1.1em; + list-style: none; + padding: 0; + margin: 0; + line-height: 1.45em; +} + +.social-links { + margin-top: 1em; +} \ No newline at end of file diff --git a/wolnelektury/static/css/master.css b/wolnelektury/static/css/master.css index e788e5562..85f1a40d7 100644 --- a/wolnelektury/static/css/master.css +++ b/wolnelektury/static/css/master.css @@ -413,6 +413,11 @@ p .ac_input { padding: 0 10px 0 10px; } +#formats .wrap div.download .custom-pdf { + text-align: left; +} + + #czytamysluchajac { margin-top: 2.5em; } @@ -1190,4 +1195,22 @@ div.shown-tags p, div.all-tags p { /* report */ .stats td { vertical-align: top; -} \ No newline at end of file +} + +/* ============ */ +/* = Pictures = */ +/* ============ */ + + +#picture-list .picture .title { + font-weight: bold; +} + +#picture-list .picture { + background-color: white; + padding: 0.8em; + margin: 0.8em; + border: black 1px solid; + width: 600px; +} + diff --git a/wolnelektury/static/css/picture_box.css b/wolnelektury/static/css/picture_box.css new file mode 100755 index 000000000..06f3e62c8 --- /dev/null +++ b/wolnelektury/static/css/picture_box.css @@ -0,0 +1,90 @@ +.picture-mini-box, .picture-box { + display: inline-block; + margin: 0; + vertical-align: top; +} + + +.picture-box { + width: 37.5em; +} + +.picture-mini-box { + width: 12.5em; +} + +.picture-mini-box a, .picture-box-inner { + display: block; + color: black; + border: 1px solid #ddd; + height: 20em; + padding: .75em; + margin: .1em; + background: #fff; + -moz-box-shadow: 2px 2px 2px #ddd; + -webkit-box-shadow: 2px 2px 2px #ddd; + box-shadow: 2px 2px 2px #ddd; + overflow: hidden; +} + +.picture-mini-box a { + height: 20em; + margin: .1em; +} +.picture-box-inner { + height: 14.4em; + margin: .5em; +} + +.picture-mini-box img, .picture-box img { + width: 10.8em; + height: 14.4em; +} +.picture-mini-box img { + margin-bottom: .3em; +} +.picture-box img { + float: left; + margin-right: 1.5em; +} + +.picture-mini-box .author { + color: #777; +} + + +.picture-box-body { + height: 13em; + overflow: hidden; +} +.picture-box-head { + min-height: 7em; +} +.picture-box-tag { + font-size: .8em; + margin-right: .5em; +} +.picture-box-download { + position: relative; +} +.picture-box-formats { + display: none; + top: -2em; + position: absolute; + height: 2em; + width: 100em; +} +.picture-box-formats a { + margin-right: 1em; +} +.picture-box-download:hover .picture-box-formats { + display: block; +} + +.picture-box-tools a:before { + content: "⇩"; + font-family: WL-Nav; + font-size: 2em; + margin-right: .25em; + vertical-align: middle; +} diff --git a/wolnelektury/static/css/sponsors.css b/wolnelektury/static/css/sponsors.css index a1798b138..1624f5f95 100644 --- a/wolnelektury/static/css/sponsors.css +++ b/wolnelektury/static/css/sponsors.css @@ -1,4 +1,5 @@ .sponsors-page { + background: white; margin-top: 6px; } @@ -7,6 +8,10 @@ width: 150px; } +.sponsor-logos { + height: 130px; +} + .sponsors-page img { float: left; } diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png new file mode 100644 index 000000000..954e22dbd Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png new file mode 100644 index 000000000..64ece5707 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png new file mode 100644 index 000000000..abdc01082 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png new file mode 100644 index 000000000..9b383f4d2 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png new file mode 100644 index 000000000..a23baad25 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 000000000..42ccba269 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png new file mode 100644 index 000000000..1b1972b56 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png new file mode 100644 index 000000000..f1273672d Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png b/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png new file mode 100644 index 000000000..359397acf Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_222222_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_222222_256x240.png new file mode 100644 index 000000000..b273ff111 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_222222_256x240.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png new file mode 100644 index 000000000..a641a371a Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png new file mode 100644 index 000000000..85e63e9f6 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png new file mode 100644 index 000000000..e117effa3 Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png differ diff --git a/wolnelektury/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png b/wolnelektury/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png new file mode 100644 index 000000000..42f8f992c Binary files /dev/null and b/wolnelektury/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png differ diff --git a/wolnelektury/static/css/ui-lightness/jquery-ui-1.8.16.custom.css b/wolnelektury/static/css/ui-lightness/jquery-ui-1.8.16.custom.css new file mode 100644 index 000000000..da10fff71 --- /dev/null +++ b/wolnelektury/static/css/ui-lightness/jquery-ui-1.8.16.custom.css @@ -0,0 +1,342 @@ +/* + * jQuery UI CSS Framework 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; } +.ui-widget-content a { color: #333333; } +.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } +.ui-widget-header a { color: #ffffff; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; } +.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; } +.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } + +/* Overlays */ +.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); } +.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/* + * jQuery UI Autocomplete 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu 1.8.16 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} diff --git a/wolnelektury/static/fonts/WL-Nav.ttf b/wolnelektury/static/fonts/WL-Nav.ttf new file mode 100644 index 000000000..e73dedf6f Binary files /dev/null and b/wolnelektury/static/fonts/WL-Nav.ttf differ diff --git a/wolnelektury/static/fonts/WL.eot b/wolnelektury/static/fonts/WL.eot new file mode 100644 index 000000000..53fedbda0 Binary files /dev/null and b/wolnelektury/static/fonts/WL.eot differ diff --git a/wolnelektury/static/fonts/WL.ttf b/wolnelektury/static/fonts/WL.ttf new file mode 100644 index 000000000..6a8934a1b Binary files /dev/null and b/wolnelektury/static/fonts/WL.ttf differ diff --git a/wolnelektury/static/img/bg-header.png b/wolnelektury/static/img/bg-header.png new file mode 100644 index 000000000..f7e572edc Binary files /dev/null and b/wolnelektury/static/img/bg-header.png differ diff --git a/wolnelektury/static/jplayer/Jplayer.swf b/wolnelektury/static/jplayer/Jplayer.swf new file mode 100644 index 000000000..4d50c86ae Binary files /dev/null and b/wolnelektury/static/jplayer/Jplayer.swf differ diff --git a/wolnelektury/static/jplayer/jplayer.blue.monday.css b/wolnelektury/static/jplayer/jplayer.blue.monday.css new file mode 100644 index 000000000..0d90a22b1 --- /dev/null +++ b/wolnelektury/static/jplayer/jplayer.blue.monday.css @@ -0,0 +1,623 @@ +/* + * Skin for jPlayer Plugin (jQuery JavaScript Library) + * http://www.happyworm.com/jquery/jplayer + * + * Skin Name: Blue Monday + * + * Copyright (c) 2010-2011 Happyworm Ltd + * Dual licensed under the MIT and GPL licenses. + * - http://www.opensource.org/licenses/mit-license.php + * - http://www.gnu.org/copyleft/gpl.html + * + * Author: Silvia Benvenuti + * Skin Version: 4.0 (jPlayer 2.1.0) + * Date: 1st September 2011 + */ + +div.jp-audio, +div.jp-video { + + /* Edit the font-size to counteract inherited font sizing. + * Eg. 1.25em = 1 / 0.8em + */ + + font-size:1.25em; /* 1.25em for testing in site pages */ /* No parent CSS that can effect the size in the demos ZIP */ + + font-family:Verdana, Arial, sans-serif; + line-height:1.6; + color: #666; + border:1px solid #009be3; + background-color:#eee; + position:relative; +} +div.jp-audio { + width:420px; +} +div.jp-video-270p { + width:480px; +} +div.jp-video-360p { + width:640px; +} +div.jp-video-full { + /* Rules for IE6 (full-screen) */ + width:480px; + height:270px; + /* Rules for IE7 (full-screen) - Otherwise the relative container causes other page items that are not position:static (default) to appear over the video/gui. */ + position:static !important; position:relative +} + +div.jp-video-full div.jp-jplayer { + top: 0; + left: 0; + position: fixed !important; position: relative; /* Rules for IE6 (full-screen) */ + overflow: hidden; + z-index:1000; +} + +div.jp-video-full div.jp-gui { + position: fixed !important; position: static; /* Rules for IE6 (full-screen) */ + top: 0; + left: 0; + width:100%; + height:100%; + z-index:1000; +} + +div.jp-video-full div.jp-interface { + position: absolute !important; position: relative; /* Rules for IE6 (full-screen) */ + bottom: 0; + left: 0; + z-index:1000; +} + +div.jp-interface { + position: relative; + background-color:#eee; + width:100%; +} + +div.jp-audio div.jp-type-single div.jp-interface { + height:80px; +} +div.jp-audio div.jp-type-playlist div.jp-interface { + height:80px; +} + +div.jp-video div.jp-interface { + border-top:1px solid #009be3; +} + +/* @group CONTROLS */ + +div.jp-controls-holder { + clear: both; + width:440px; + margin:0 auto; + position: relative; + overflow:hidden; + top:-8px; /* This negative value depends on the size of the text in jp-currentTime and jp-duration */ +} + +div.jp-interface ul.jp-controls { + list-style-type:none; + margin:0; + padding: 0; + overflow:hidden; +} + +div.jp-audio ul.jp-controls { + width: 380px; + padding:20px 20px 0 20px; +} + +div.jp-video div.jp-type-single ul.jp-controls { + width: 78px; + margin-left: 200px; +} + +div.jp-video div.jp-type-playlist ul.jp-controls { + width: 134px; + margin-left: 172px; +} +div.jp-video ul.jp-controls, +div.jp-interface ul.jp-controls li { + display:inline; + float: left; +} + +div.jp-interface ul.jp-controls a { + display:block; + overflow:hidden; + text-indent:-9999px; +} +a.jp-play, +a.jp-pause { + width:40px; + height:40px; +} + +a.jp-play { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 0 no-repeat; +} +a.jp-play:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -41px 0 no-repeat; +} +a.jp-pause { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -42px no-repeat; + display: none; +} +a.jp-pause:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -41px -42px no-repeat; +} + +a.jp-stop, a.jp-previous, a.jp-next { + width:28px; + height:28px; + margin-top:6px; +} + +a.jp-stop { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -83px no-repeat; + margin-left:10px; +} + +a.jp-stop:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -29px -83px no-repeat; +} + +a.jp-previous { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -112px no-repeat; +} +a.jp-previous:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -29px -112px no-repeat; +} + +a.jp-next { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -141px no-repeat; +} +a.jp-next:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -29px -141px no-repeat; +} + +/* @end */ + +/* @group progress bar */ + +div.jp-progress { + overflow:hidden; + background-color: #ddd; +} +div.jp-audio div.jp-progress { + position: absolute; + top:32px; + height:15px; +} +div.jp-audio div.jp-type-single div.jp-progress { + left:110px; + width:186px; +} +div.jp-audio div.jp-type-playlist div.jp-progress { + left:166px; + width:130px; +} +div.jp-video div.jp-progress { + top:0px; + left:0px; + width:100%; + height:10px; +} +div.jp-seek-bar { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -202px repeat-x; + width:0px; + height:100%; + cursor: pointer; +} +div.jp-play-bar { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -218px repeat-x ; + width:0px; + height:100%; +} + +/* The seeking class is added/removed inside jPlayer */ +div.jp-seeking-bg { + background: url("/static/jplayer/jplayer.blue.monday.seeking.gif"); +} + +/* @end */ + +/* @group volume controls */ + + +a.jp-mute, +a.jp-unmute, +a.jp-volume-max { + width:18px; + height:15px; + margin-top:12px; +} + +div.jp-audio div.jp-type-single a.jp-mute, +div.jp-audio div.jp-type-single a.jp-unmute { + margin-left: 210px; +} + +div.jp-audio div.jp-type-playlist a.jp-mute, +div.jp-audio div.jp-type-playlist a.jp-unmute { + margin-left: 154px; +} + +div.jp-audio a.jp-volume-max { + margin-left: 56px; +} + +div.jp-video a.jp-mute, +div.jp-video a.jp-unmute, +div.jp-video a.jp-volume-max { + position: absolute; + top:12px; + margin-top:0; +} + +div.jp-video a.jp-mute, +div.jp-video a.jp-unmute { + left: 50px; +} + +div.jp-video a.jp-volume-max { + left: 134px; +} + +a.jp-mute { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -170px no-repeat; +} +a.jp-mute:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -19px -170px no-repeat; +} +a.jp-unmute { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -60px -170px no-repeat; + display: none; +} +a.jp-unmute:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -79px -170px no-repeat; +} + a.jp-volume-max { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -186px no-repeat; +} +a.jp-volume-max:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -19px -186px no-repeat; +} + +div.jp-volume-bar { + position: absolute; + overflow:hidden; + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -250px repeat-x; + width:46px; + height:5px; + cursor: pointer; +} +div.jp-audio div.jp-volume-bar { + top:37px; + left:330px; +} +div.jp-video div.jp-volume-bar { + top:17px; + left:72px; +} +div.jp-volume-bar-value { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -256px repeat-x; + width:0px; + height:5px; +} + +/* @end */ + +/* @group current time and duration */ + +div.jp-audio div.jp-time-holder { + position:absolute; + top:50px; +} +div.jp-audio div.jp-type-single div.jp-time-holder { + left:110px; + width:186px; +} +div.jp-audio div.jp-type-playlist div.jp-time-holder { + left:166px; + width:130px; +} + +div.jp-current-time, +div.jp-duration { + width:60px; + font-size:.64em; + font-style:oblique; +} +div.jp-current-time { + float: left; + display:inline; +} +div.jp-duration { + float: right; + display:inline; + text-align: right; +} + +div.jp-video div.jp-current-time { + margin-left:20px; +} +div.jp-video div.jp-duration { + margin-right:20px; +} + +/* @end */ + +/* @group playlist */ + +div.jp-title { + font-weight:bold; + text-align:center; +} + +div.jp-title, +div.jp-playlist { + width:100%; + background-color:#ccc; + border-top:1px solid #009be3; +} +div.jp-type-single div.jp-title, +div.jp-type-playlist div.jp-title, +div.jp-type-single div.jp-playlist { + border-top:none; +} +div.jp-title ul, +div.jp-playlist ul { + list-style-type:none; + margin:0; + padding:0 20px; + font-size:.72em; +} + +div.jp-title li { + padding:5px 0; + font-weight:bold; +} +div.jp-playlist li { + padding:5px 0 4px 20px; + border-bottom:1px solid #eee; +} + +div.jp-playlist li div { + display:inline; +} + +/* Note that the first-child (IE6) and last-child (IE6/7/8) selectors do not work on IE */ + +div.jp-type-playlist div.jp-playlist li:last-child { + padding:5px 0 5px 20px; + border-bottom:none; +} +div.jp-type-playlist div.jp-playlist li.jp-playlist-current { + list-style-type:square; + list-style-position:inside; + padding-left:7px; +} +div.jp-type-playlist div.jp-playlist a { + color: #333; + text-decoration: none; +} +div.jp-type-playlist div.jp-playlist a:hover { + color:#0d88c1; +} +div.jp-type-playlist div.jp-playlist a.jp-playlist-current { + color:#0d88c1; +} + +div.jp-type-playlist div.jp-playlist a.jp-playlist-item-remove { + float:right; + display:inline; + text-align:right; + margin-right:10px; + font-weight:bold; + color:#666; +} +div.jp-type-playlist div.jp-playlist a.jp-playlist-item-remove:hover { + color:#0d88c1; +} +div.jp-type-playlist div.jp-playlist span.jp-free-media { + float:right; + display:inline; + text-align:right; + margin-right:10px; +} +div.jp-type-playlist div.jp-playlist span.jp-free-media a{ + color:#666; +} +div.jp-type-playlist div.jp-playlist span.jp-free-media a:hover{ + color:#0d88c1; +} +span.jp-artist { + font-size:.8em; + color:#666; +} + +/* @end */ + +div.jp-video-play { + position:absolute; + top:0; + left:0; + width:100%; + cursor:pointer; + background-color:rgba(0,0,0,0); /* Makes IE9 work with the active area over the whole video area. IE6/7/8 only have the button as active area. */ +} +div.jp-video-270p div.jp-video-play { + height:270px; +} +div.jp-video-360p div.jp-video-play { + height:360px; +} +div.jp-video-full div.jp-video-play { + height:100%; + z-index:1000; +} +a.jp-video-play-icon { + position:relative; + display:block; + width: 112px; + height: 100px; + + margin-left:-56px; + margin-top:-50px; + left:50%; + top:50%; + + background: url("/static/jplayer/jplayer.blue.monday.video.play.png") 0 0 no-repeat; + text-indent:-9999px; +} +div.jp-video-play:hover a.jp-video-play-icon { + background: url("/static/jplayer/jplayer.blue.monday.video.play.png") 0 -100px no-repeat; +} + + + + + +div.jp-jplayer audio, +div.jp-jplayer { + width:0px; + height:0px; +} + +div.jp-jplayer { + background-color: #000000; +} + + + + + +/* @group TOGGLES */ + +/* The audio toggles are nested inside jp-time-holder */ + +ul.jp-toggles { + list-style-type:none; + padding:0; + margin:0 auto; + overflow:hidden; +} + +div.jp-audio .jp-type-single ul.jp-toggles { + width:25px; +} +div.jp-audio .jp-type-playlist ul.jp-toggles { + width:55px; + margin: 0; + position: absolute; + left: 325px; + top: 50px; +} + +div.jp-video ul.jp-toggles { + margin-top:10px; + width:100px; +} + +ul.jp-toggles li { + display:block; + float:right; +} + +ul.jp-toggles li a { + display:block; + width:25px; + height:18px; + text-indent:-9999px; + line-height:100%; /* need this for IE6 */ +} + +a.jp-full-screen { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -310px no-repeat; + margin-left: 20px; +} + +a.jp-full-screen:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -30px -310px no-repeat; +} + +a.jp-restore-screen { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -60px -310px no-repeat; + margin-left: 20px; +} + +a.jp-restore-screen:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -90px -310px no-repeat; +} + +a.jp-repeat { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -290px no-repeat; +} + +a.jp-repeat:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -30px -290px no-repeat; +} + +a.jp-repeat-off { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -60px -290px no-repeat; +} + +a.jp-repeat-off:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -90px -290px no-repeat; +} + +a.jp-shuffle { + background: url("/static/jplayer/jplayer.blue.monday.jpg") 0 -270px no-repeat; + margin-left: 5px; +} + +a.jp-shuffle:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -30px -270px no-repeat; +} + +a.jp-shuffle-off { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -60px -270px no-repeat; + margin-left: 5px; +} + +a.jp-shuffle-off:hover { + background: url("/static/jplayer/jplayer.blue.monday.jpg") -90px -270px no-repeat; +} + + +/* @end */ + +/* @group NO SOLUTION error feedback */ + +.jp-no-solution { + position:absolute; + width:390px; + margin-left:-202px; + left:50%; + top: 10px; + + padding:5px; + font-size:.8em; + background-color:#eee; + border:2px solid #009be3; + color:#000; + display:none; +} + +.jp-no-solution a { + color:#000; +} + +.jp-no-solution span { + font-size:1em; + display:block; + text-align:center; + font-weight:bold; +} + +/* @end */ diff --git a/wolnelektury/static/jplayer/jplayer.blue.monday.jpg b/wolnelektury/static/jplayer/jplayer.blue.monday.jpg new file mode 100644 index 000000000..adab53ff7 Binary files /dev/null and b/wolnelektury/static/jplayer/jplayer.blue.monday.jpg differ diff --git a/wolnelektury/static/jplayer/jplayer.blue.monday.seeking.gif b/wolnelektury/static/jplayer/jplayer.blue.monday.seeking.gif new file mode 100644 index 000000000..dbd2105ab Binary files /dev/null and b/wolnelektury/static/jplayer/jplayer.blue.monday.seeking.gif differ diff --git a/wolnelektury/static/jplayer/jplayer.blue.monday.video.play.png b/wolnelektury/static/jplayer/jplayer.blue.monday.video.play.png new file mode 100644 index 000000000..8e97df011 Binary files /dev/null and b/wolnelektury/static/jplayer/jplayer.blue.monday.video.play.png differ diff --git a/wolnelektury/static/jplayer/jplayer.playlist.min.js b/wolnelektury/static/jplayer/jplayer.playlist.min.js new file mode 100644 index 000000000..42c0e22a1 --- /dev/null +++ b/wolnelektury/static/jplayer/jplayer.playlist.min.js @@ -0,0 +1,30 @@ +/* + * Playlist Object for the jPlayer Plugin + * http://www.jplayer.org + * + * Copyright (c) 2009 - 2011 Happyworm Ltd + * Dual licensed under the MIT and GPL licenses. + * - http://www.opensource.org/licenses/mit-license.php + * - http://www.gnu.org/copyleft/gpl.html + * + * Author: Mark J Panaghiston + * Version: 2.1.0 (jPlayer 2.1.0) + * Date: 1st September 2011 + */ + +(function(b,f){jPlayerPlaylist=function(a,c,d){var e=this;this.current=0;this.removing=this.shuffled=this.loop=!1;this.cssSelector=b.extend({},this._cssSelector,a);this.options=b.extend(!0,{},this._options,d);this.playlist=[];this.original=[];this._initPlaylist(c);this.cssSelector.title=this.cssSelector.cssSelectorAncestor+" .jp-title";this.cssSelector.playlist=this.cssSelector.cssSelectorAncestor+" .jp-playlist";this.cssSelector.next=this.cssSelector.cssSelectorAncestor+" .jp-next";this.cssSelector.previous= +this.cssSelector.cssSelectorAncestor+" .jp-previous";this.cssSelector.shuffle=this.cssSelector.cssSelectorAncestor+" .jp-shuffle";this.cssSelector.shuffleOff=this.cssSelector.cssSelectorAncestor+" .jp-shuffle-off";this.options.cssSelectorAncestor=this.cssSelector.cssSelectorAncestor;this.options.repeat=function(a){e.loop=a.jPlayer.options.loop};b(this.cssSelector.jPlayer).bind(b.jPlayer.event.ready,function(){e._init()});b(this.cssSelector.jPlayer).bind(b.jPlayer.event.ended,function(){e.next()}); +b(this.cssSelector.jPlayer).bind(b.jPlayer.event.play,function(){b(this).jPlayer("pauseOthers")});b(this.cssSelector.jPlayer).bind(b.jPlayer.event.resize,function(a){a.jPlayer.options.fullScreen?b(e.cssSelector.title).show():b(e.cssSelector.title).hide()});b(this.cssSelector.previous).click(function(){e.previous();b(this).blur();return!1});b(this.cssSelector.next).click(function(){e.next();b(this).blur();return!1});b(this.cssSelector.shuffle).click(function(){e.shuffle(!0);return!1});b(this.cssSelector.shuffleOff).click(function(){e.shuffle(!1); +return!1}).hide();this.options.fullScreen||b(this.cssSelector.title).hide();b(this.cssSelector.playlist+" ul").empty();this._createItemHandlers();b(this.cssSelector.jPlayer).jPlayer(this.options)};jPlayerPlaylist.prototype={_cssSelector:{jPlayer:"#jquery_jplayer_1",cssSelectorAncestor:"#jp_container_1"},_options:{playlistOptions:{autoPlay:!1,loopOnPrevious:!1,shuffleOnLoop:!0,enableRemoveControls:!1,displayTime:"slow",addTime:"fast",removeTime:"fast",shuffleTime:"slow",itemClass:"jp-playlist-item", +freeGroupClass:"jp-free-media",freeItemClass:"jp-playlist-item-free",removeItemClass:"jp-playlist-item-remove"}},option:function(a,b){if(b===f)return this.options.playlistOptions[a];this.options.playlistOptions[a]=b;switch(a){case "enableRemoveControls":this._updateControls();break;case "itemClass":case "freeGroupClass":case "freeItemClass":case "removeItemClass":this._refresh(!0),this._createItemHandlers()}return this},_init:function(){var a=this;this._refresh(function(){a.options.playlistOptions.autoPlay? +a.play(a.current):a.select(a.current)})},_initPlaylist:function(a){this.current=0;this.removing=this.shuffled=!1;this.original=b.extend(!0,[],a);this._originalPlaylist()},_originalPlaylist:function(){var a=this;this.playlist=[];b.each(this.original,function(b){a.playlist[b]=a.original[b]})},_refresh:function(a){var c=this;if(a&&!b.isFunction(a))b(this.cssSelector.playlist+" ul").empty(),b.each(this.playlist,function(a){b(c.cssSelector.playlist+" ul").append(c._createListItem(c.playlist[a]))}),this._updateControls(); +else{var d=b(this.cssSelector.playlist+" ul").children().length?this.options.playlistOptions.displayTime:0;b(this.cssSelector.playlist+" ul").slideUp(d,function(){var d=b(this);b(this).empty();b.each(c.playlist,function(a){d.append(c._createListItem(c.playlist[a]))});c._updateControls();b.isFunction(a)&&a();c.playlist.length?b(this).slideDown(c.options.playlistOptions.displayTime):b(this).show()})}},_createListItem:function(a){var c=this,d="<li><div>";d+="<a href='javascript:;' class='"+this.options.playlistOptions.removeItemClass+ +"'>×</a>";if(a.free){var e=!0;d+="<span class='"+this.options.playlistOptions.freeGroupClass+"'>(";b.each(a,function(a,f){b.jPlayer.prototype.format[a]&&(e?e=!1:d+=" | ",d+="<a class='"+c.options.playlistOptions.freeItemClass+"' href='"+f+"' tabindex='1'>"+a+"</a>")});d+=")</span>"}d+="<a href='javascript:;' class='"+this.options.playlistOptions.itemClass+"' tabindex='1'>"+a.title+(a.artist?" <span class='jp-artist'>by "+a.artist+"</span>":"")+"</a>";d+="</div></li>";return d},_createItemHandlers:function(){var a= +this;b(this.cssSelector.playlist+" a."+this.options.playlistOptions.itemClass).die("click").live("click",function(){var c=b(this).parent().parent().index();a.current!==c?a.play(c):b(a.cssSelector.jPlayer).jPlayer("play");b(this).blur();return!1});b(a.cssSelector.playlist+" a."+this.options.playlistOptions.freeItemClass).die("click").live("click",function(){b(this).parent().parent().find("."+a.options.playlistOptions.itemClass).click();b(this).blur();return!1});b(a.cssSelector.playlist+" a."+this.options.playlistOptions.removeItemClass).die("click").live("click", +function(){var c=b(this).parent().parent().index();a.remove(c);b(this).blur();return!1})},_updateControls:function(){this.options.playlistOptions.enableRemoveControls?b(this.cssSelector.playlist+" ."+this.options.playlistOptions.removeItemClass).show():b(this.cssSelector.playlist+" ."+this.options.playlistOptions.removeItemClass).hide();this.shuffled?(b(this.cssSelector.shuffleOff).show(),b(this.cssSelector.shuffle).hide()):(b(this.cssSelector.shuffleOff).hide(),b(this.cssSelector.shuffle).show())}, +_highlight:function(a){this.playlist.length&&a!==f&&(b(this.cssSelector.playlist+" .jp-playlist-current").removeClass("jp-playlist-current"),b(this.cssSelector.playlist+" li:nth-child("+(a+1)+")").addClass("jp-playlist-current").find(".jp-playlist-item").addClass("jp-playlist-current"),b(this.cssSelector.title+" li").html(this.playlist[a].title+(this.playlist[a].artist?" <span class='jp-artist'>by "+this.playlist[a].artist+"</span>":"")))},setPlaylist:function(a){this._initPlaylist(a);this._init()}, +add:function(a,c){b(this.cssSelector.playlist+" ul").append(this._createListItem(a)).find("li:last-child").hide().slideDown(this.options.playlistOptions.addTime);this._updateControls();this.original.push(a);this.playlist.push(a);c?this.play(this.playlist.length-1):this.original.length===1&&this.select(0)},remove:function(a){var c=this;if(a===f)return this._initPlaylist([]),this._refresh(function(){b(c.cssSelector.jPlayer).jPlayer("clearMedia")}),!0;else if(this.removing)return!1;else{a=a<0?c.original.length+ +a:a;if(0<=a&&a<this.playlist.length)this.removing=!0,b(this.cssSelector.playlist+" li:nth-child("+(a+1)+")").slideUp(this.options.playlistOptions.removeTime,function(){b(this).remove();if(c.shuffled){var d=c.playlist[a];b.each(c.original,function(a){if(c.original[a]===d)return c.original.splice(a,1),!1})}else c.original.splice(a,1);c.playlist.splice(a,1);c.original.length?a===c.current?(c.current=a<c.original.length?c.current:c.original.length-1,c.select(c.current)):a<c.current&&c.current--:(b(c.cssSelector.jPlayer).jPlayer("clearMedia"), +c.current=0,c.shuffled=!1,c._updateControls());c.removing=!1});return!0}},select:function(a){a=a<0?this.original.length+a:a;0<=a&&a<this.playlist.length?(this.current=a,this._highlight(a),b(this.cssSelector.jPlayer).jPlayer("setMedia",this.playlist[this.current])):this.current=0},play:function(a){a=a<0?this.original.length+a:a;0<=a&&a<this.playlist.length?this.playlist.length&&(this.select(a),b(this.cssSelector.jPlayer).jPlayer("play")):a===f&&b(this.cssSelector.jPlayer).jPlayer("play")},pause:function(){b(this.cssSelector.jPlayer).jPlayer("pause")}, +next:function(){var a=this.current+1<this.playlist.length?this.current+1:0;this.loop?a===0&&this.shuffled&&this.options.playlistOptions.shuffleOnLoop&&this.playlist.length>1?this.shuffle(!0,!0):this.play(a):a>0&&this.play(a)},previous:function(){var a=this.current-1>=0?this.current-1:this.playlist.length-1;(this.loop&&this.options.playlistOptions.loopOnPrevious||a<this.playlist.length-1)&&this.play(a)},shuffle:function(a,c){var d=this;a===f&&(a=!this.shuffled);(a||a!==this.shuffled)&&b(this.cssSelector.playlist+ +" ul").slideUp(this.options.playlistOptions.shuffleTime,function(){(d.shuffled=a)?d.playlist.sort(function(){return 0.5-Math.random()}):d._originalPlaylist();d._refresh(!0);c||!b(d.cssSelector.jPlayer).data("jPlayer").status.paused?d.play(0):d.select(0);b(this).slideDown(d.options.playlistOptions.shuffleTime)})}}})(jQuery); \ No newline at end of file diff --git a/wolnelektury/static/jplayer/jquery.jplayer.min.js b/wolnelektury/static/jplayer/jquery.jplayer.min.js new file mode 100644 index 000000000..bcf7901a0 --- /dev/null +++ b/wolnelektury/static/jplayer/jquery.jplayer.min.js @@ -0,0 +1,97 @@ +/* + * jPlayer Plugin for jQuery JavaScript Library + * http://www.jplayer.org + * + * Copyright (c) 2009 - 2011 Happyworm Ltd + * Dual licensed under the MIT and GPL licenses. + * - http://www.opensource.org/licenses/mit-license.php + * - http://www.gnu.org/copyleft/gpl.html + * + * Author: Mark J Panaghiston + * Version: 2.1.0 + * Date: 1st September 2011 + */ + +(function(b,f){b.fn.jPlayer=function(a){var c=typeof a==="string",d=Array.prototype.slice.call(arguments,1),e=this,a=!c&&d.length?b.extend.apply(null,[!0,a].concat(d)):a;if(c&&a.charAt(0)==="_")return e;c?this.each(function(){var c=b.data(this,"jPlayer"),h=c&&b.isFunction(c[a])?c[a].apply(c,d):c;if(h!==c&&h!==f)return e=h,!1}):this.each(function(){var c=b.data(this,"jPlayer");c?c.option(a||{}):b.data(this,"jPlayer",new b.jPlayer(a,this))});return e};b.jPlayer=function(a,c){if(arguments.length){this.element= +b(c);this.options=b.extend(!0,{},this.options,a);var d=this;this.element.bind("remove.jPlayer",function(){d.destroy()});this._init()}};b.jPlayer.emulateMethods="load play pause";b.jPlayer.emulateStatus="src readyState networkState currentTime duration paused ended playbackRate";b.jPlayer.emulateOptions="muted volume";b.jPlayer.reservedEvent="ready flashreset resize repeat error warning";b.jPlayer.event={ready:"jPlayer_ready",flashreset:"jPlayer_flashreset",resize:"jPlayer_resize",repeat:"jPlayer_repeat", +click:"jPlayer_click",error:"jPlayer_error",warning:"jPlayer_warning",loadstart:"jPlayer_loadstart",progress:"jPlayer_progress",suspend:"jPlayer_suspend",abort:"jPlayer_abort",emptied:"jPlayer_emptied",stalled:"jPlayer_stalled",play:"jPlayer_play",pause:"jPlayer_pause",loadedmetadata:"jPlayer_loadedmetadata",loadeddata:"jPlayer_loadeddata",waiting:"jPlayer_waiting",playing:"jPlayer_playing",canplay:"jPlayer_canplay",canplaythrough:"jPlayer_canplaythrough",seeking:"jPlayer_seeking",seeked:"jPlayer_seeked", +timeupdate:"jPlayer_timeupdate",ended:"jPlayer_ended",ratechange:"jPlayer_ratechange",durationchange:"jPlayer_durationchange",volumechange:"jPlayer_volumechange"};b.jPlayer.htmlEvent="loadstart,abort,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,ratechange".split(",");b.jPlayer.pause=function(){b.each(b.jPlayer.prototype.instances,function(a,b){b.data("jPlayer").status.srcSet&&b.jPlayer("pause")})};b.jPlayer.timeFormat={showHour:!1,showMin:!0,showSec:!0,padHour:!1,padMin:!0,padSec:!0, +sepHour:":",sepMin:":",sepSec:""};b.jPlayer.convertTime=function(a){var c=new Date(a*1E3),d=c.getUTCHours(),a=c.getUTCMinutes(),c=c.getUTCSeconds(),d=b.jPlayer.timeFormat.padHour&&d<10?"0"+d:d,a=b.jPlayer.timeFormat.padMin&&a<10?"0"+a:a,c=b.jPlayer.timeFormat.padSec&&c<10?"0"+c:c;return(b.jPlayer.timeFormat.showHour?d+b.jPlayer.timeFormat.sepHour:"")+(b.jPlayer.timeFormat.showMin?a+b.jPlayer.timeFormat.sepMin:"")+(b.jPlayer.timeFormat.showSec?c+b.jPlayer.timeFormat.sepSec:"")};b.jPlayer.uaBrowser= +function(a){var a=a.toLowerCase(),b=/(opera)(?:.*version)?[ \/]([\w.]+)/,d=/(msie) ([\w.]+)/,e=/(mozilla)(?:.*? rv:([\w.]+))?/,a=/(webkit)[ \/]([\w.]+)/.exec(a)||b.exec(a)||d.exec(a)||a.indexOf("compatible")<0&&e.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}};b.jPlayer.uaPlatform=function(a){var b=a.toLowerCase(),d=/(android)/,e=/(mobile)/,a=/(ipad|iphone|ipod|android|blackberry|playbook|windows ce|webos)/.exec(b)||[],b=/(ipad|playbook)/.exec(b)||!e.exec(b)&&d.exec(b)||[];a[1]&&(a[1]=a[1].replace(/\s/g, +"_"));return{platform:a[1]||"",tablet:b[1]||""}};b.jPlayer.browser={};b.jPlayer.platform={};var i=b.jPlayer.uaBrowser(navigator.userAgent);if(i.browser)b.jPlayer.browser[i.browser]=!0,b.jPlayer.browser.version=i.version;i=b.jPlayer.uaPlatform(navigator.userAgent);if(i.platform)b.jPlayer.platform[i.platform]=!0,b.jPlayer.platform.mobile=!i.tablet,b.jPlayer.platform.tablet=!!i.tablet;b.jPlayer.prototype={count:0,version:{script:"2.1.0",needFlash:"2.1.0",flash:"unknown"},options:{swfPath:"js",solution:"html, flash", +supplied:"mp3",preload:"metadata",volume:0.8,muted:!1,wmode:"opaque",backgroundColor:"#000000",cssSelectorAncestor:"#jp_container_1",cssSelector:{videoPlay:".jp-video-play",play:".jp-play",pause:".jp-pause",stop:".jp-stop",seekBar:".jp-seek-bar",playBar:".jp-play-bar",mute:".jp-mute",unmute:".jp-unmute",volumeBar:".jp-volume-bar",volumeBarValue:".jp-volume-bar-value",volumeMax:".jp-volume-max",currentTime:".jp-current-time",duration:".jp-duration",fullScreen:".jp-full-screen",restoreScreen:".jp-restore-screen", +repeat:".jp-repeat",repeatOff:".jp-repeat-off",gui:".jp-gui",noSolution:".jp-no-solution"},fullScreen:!1,autohide:{restored:!1,full:!0,fadeIn:200,fadeOut:600,hold:1E3},loop:!1,repeat:function(a){a.jPlayer.options.loop?b(this).unbind(".jPlayerRepeat").bind(b.jPlayer.event.ended+".jPlayer.jPlayerRepeat",function(){b(this).jPlayer("play")}):b(this).unbind(".jPlayerRepeat")},nativeVideoControls:{},noFullScreen:{msie:/msie [0-6]/,ipad:/ipad.*?os [0-4]/,iphone:/iphone/,ipod:/ipod/,android_pad:/android [0-3](?!.*?mobile)/, +android_phone:/android.*?mobile/,blackberry:/blackberry/,windows_ce:/windows ce/,webos:/webos/},noVolume:{ipad:/ipad/,iphone:/iphone/,ipod:/ipod/,android_pad:/android(?!.*?mobile)/,android_phone:/android.*?mobile/,blackberry:/blackberry/,windows_ce:/windows ce/,webos:/webos/,playbook:/playbook/},verticalVolume:!1,idPrefix:"jp",noConflict:"jQuery",emulateHtml:!1,errorAlerts:!1,warningAlerts:!1},optionsAudio:{size:{width:"0px",height:"0px",cssClass:""},sizeFull:{width:"0px",height:"0px",cssClass:""}}, +optionsVideo:{size:{width:"480px",height:"270px",cssClass:"jp-video-270p"},sizeFull:{width:"100%",height:"100%",cssClass:"jp-video-full"}},instances:{},status:{src:"",media:{},paused:!0,format:{},formatType:"",waitForPlay:!0,waitForLoad:!0,srcSet:!1,video:!1,seekPercent:0,currentPercentRelative:0,currentPercentAbsolute:0,currentTime:0,duration:0,readyState:0,networkState:0,playbackRate:1,ended:0},internal:{ready:!1},solution:{html:!0,flash:!0},format:{mp3:{codec:'audio/mpeg; codecs="mp3"',flashCanPlay:!0, +media:"audio"},m4a:{codec:'audio/mp4; codecs="mp4a.40.2"',flashCanPlay:!0,media:"audio"},oga:{codec:'audio/ogg; codecs="vorbis"',flashCanPlay:!1,media:"audio"},wav:{codec:'audio/wav; codecs="1"',flashCanPlay:!1,media:"audio"},webma:{codec:'audio/webm; codecs="vorbis"',flashCanPlay:!1,media:"audio"},fla:{codec:"audio/x-flv",flashCanPlay:!0,media:"audio"},m4v:{codec:'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',flashCanPlay:!0,media:"video"},ogv:{codec:'video/ogg; codecs="theora, vorbis"',flashCanPlay:!1, +media:"video"},webmv:{codec:'video/webm; codecs="vorbis, vp8"',flashCanPlay:!1,media:"video"},flv:{codec:"video/x-flv",flashCanPlay:!0,media:"video"}},_init:function(){var a=this;this.element.empty();this.status=b.extend({},this.status);this.internal=b.extend({},this.internal);this.internal.domNode=this.element.get(0);this.formats=[];this.solutions=[];this.require={};this.htmlElement={};this.html={};this.html.audio={};this.html.video={};this.flash={};this.css={};this.css.cs={};this.css.jq={};this.ancestorJq= +[];this.options.volume=this._limitValue(this.options.volume,0,1);b.each(this.options.supplied.toLowerCase().split(","),function(c,d){var e=d.replace(/^\s+|\s+$/g,"");if(a.format[e]){var f=!1;b.each(a.formats,function(a,b){if(e===b)return f=!0,!1});f||a.formats.push(e)}});b.each(this.options.solution.toLowerCase().split(","),function(c,d){var e=d.replace(/^\s+|\s+$/g,"");if(a.solution[e]){var f=!1;b.each(a.solutions,function(a,b){if(e===b)return f=!0,!1});f||a.solutions.push(e)}});this.internal.instance= +"jp_"+this.count;this.instances[this.internal.instance]=this.element;this.element.attr("id")||this.element.attr("id",this.options.idPrefix+"_jplayer_"+this.count);this.internal.self=b.extend({},{id:this.element.attr("id"),jq:this.element});this.internal.audio=b.extend({},{id:this.options.idPrefix+"_audio_"+this.count,jq:f});this.internal.video=b.extend({},{id:this.options.idPrefix+"_video_"+this.count,jq:f});this.internal.flash=b.extend({},{id:this.options.idPrefix+"_flash_"+this.count,jq:f,swf:this.options.swfPath+ +(this.options.swfPath.toLowerCase().slice(-4)!==".swf"?(this.options.swfPath&&this.options.swfPath.slice(-1)!=="/"?"/":"")+"Jplayer.swf":"")});this.internal.poster=b.extend({},{id:this.options.idPrefix+"_poster_"+this.count,jq:f});b.each(b.jPlayer.event,function(b,c){a.options[b]!==f&&(a.element.bind(c+".jPlayer",a.options[b]),a.options[b]=f)});this.require.audio=!1;this.require.video=!1;b.each(this.formats,function(b,c){a.require[a.format[c].media]=!0});this.options=this.require.video?b.extend(!0, +{},this.optionsVideo,this.options):b.extend(!0,{},this.optionsAudio,this.options);this._setSize();this.status.nativeVideoControls=this._uaBlocklist(this.options.nativeVideoControls);this.status.noFullScreen=this._uaBlocklist(this.options.noFullScreen);this.status.noVolume=this._uaBlocklist(this.options.noVolume);this._restrictNativeVideoControls();this.htmlElement.poster=document.createElement("img");this.htmlElement.poster.id=this.internal.poster.id;this.htmlElement.poster.onload=function(){(!a.status.video|| +a.status.waitForPlay)&&a.internal.poster.jq.show()};this.element.append(this.htmlElement.poster);this.internal.poster.jq=b("#"+this.internal.poster.id);this.internal.poster.jq.css({width:this.status.width,height:this.status.height});this.internal.poster.jq.hide();this.internal.poster.jq.bind("click.jPlayer",function(){a._trigger(b.jPlayer.event.click)});this.html.audio.available=!1;if(this.require.audio)this.htmlElement.audio=document.createElement("audio"),this.htmlElement.audio.id=this.internal.audio.id, +this.html.audio.available=!!this.htmlElement.audio.canPlayType&&this._testCanPlayType(this.htmlElement.audio);this.html.video.available=!1;if(this.require.video)this.htmlElement.video=document.createElement("video"),this.htmlElement.video.id=this.internal.video.id,this.html.video.available=!!this.htmlElement.video.canPlayType&&this._testCanPlayType(this.htmlElement.video);this.flash.available=this._checkForFlash(10);this.html.canPlay={};this.flash.canPlay={};b.each(this.formats,function(b,c){a.html.canPlay[c]= +a.html[a.format[c].media].available&&""!==a.htmlElement[a.format[c].media].canPlayType(a.format[c].codec);a.flash.canPlay[c]=a.format[c].flashCanPlay&&a.flash.available});this.html.desired=!1;this.flash.desired=!1;b.each(this.solutions,function(c,d){if(c===0)a[d].desired=!0;else{var e=!1,f=!1;b.each(a.formats,function(b,c){a[a.solutions[0]].canPlay[c]&&(a.format[c].media==="video"?f=!0:e=!0)});a[d].desired=a.require.audio&&!e||a.require.video&&!f}});this.html.support={};this.flash.support={};b.each(this.formats, +function(b,c){a.html.support[c]=a.html.canPlay[c]&&a.html.desired;a.flash.support[c]=a.flash.canPlay[c]&&a.flash.desired});this.html.used=!1;this.flash.used=!1;b.each(this.solutions,function(c,d){b.each(a.formats,function(b,c){if(a[d].support[c])return a[d].used=!0,!1})});this._resetActive();this._resetGate();this._cssSelectorAncestor(this.options.cssSelectorAncestor);!this.html.used&&!this.flash.used?(this._error({type:b.jPlayer.error.NO_SOLUTION,context:"{solution:'"+this.options.solution+"', supplied:'"+ +this.options.supplied+"'}",message:b.jPlayer.errorMsg.NO_SOLUTION,hint:b.jPlayer.errorHint.NO_SOLUTION}),this.css.jq.noSolution.length&&this.css.jq.noSolution.show()):this.css.jq.noSolution.length&&this.css.jq.noSolution.hide();if(this.flash.used){var c,d="jQuery="+encodeURI(this.options.noConflict)+"&id="+encodeURI(this.internal.self.id)+"&vol="+this.options.volume+"&muted="+this.options.muted;if(b.browser.msie&&Number(b.browser.version)<=8){d=['<param name="movie" value="'+this.internal.flash.swf+ +'" />','<param name="FlashVars" value="'+d+'" />','<param name="allowScriptAccess" value="always" />','<param name="bgcolor" value="'+this.options.backgroundColor+'" />','<param name="wmode" value="'+this.options.wmode+'" />'];c=document.createElement('<object id="'+this.internal.flash.id+'" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="0" height="0"></object>');for(var e=0;e<d.length;e++)c.appendChild(document.createElement(d[e]))}else e=function(a,b,c){var d=document.createElement("param"); +d.setAttribute("name",b);d.setAttribute("value",c);a.appendChild(d)},c=document.createElement("object"),c.setAttribute("id",this.internal.flash.id),c.setAttribute("data",this.internal.flash.swf),c.setAttribute("type","application/x-shockwave-flash"),c.setAttribute("width","1"),c.setAttribute("height","1"),e(c,"flashvars",d),e(c,"allowscriptaccess","always"),e(c,"bgcolor",this.options.backgroundColor),e(c,"wmode",this.options.wmode);this.element.append(c);this.internal.flash.jq=b(c)}if(this.html.used){if(this.html.audio.available)this._addHtmlEventListeners(this.htmlElement.audio, +this.html.audio),this.element.append(this.htmlElement.audio),this.internal.audio.jq=b("#"+this.internal.audio.id);if(this.html.video.available)this._addHtmlEventListeners(this.htmlElement.video,this.html.video),this.element.append(this.htmlElement.video),this.internal.video.jq=b("#"+this.internal.video.id),this.status.nativeVideoControls?this.internal.video.jq.css({width:this.status.width,height:this.status.height}):this.internal.video.jq.css({width:"0px",height:"0px"}),this.internal.video.jq.bind("click.jPlayer", +function(){a._trigger(b.jPlayer.event.click)})}this.options.emulateHtml&&this._emulateHtmlBridge();this.html.used&&!this.flash.used&&setTimeout(function(){a.internal.ready=!0;a.version.flash="n/a";a._trigger(b.jPlayer.event.repeat);a._trigger(b.jPlayer.event.ready)},100);this._updateNativeVideoControls();this._updateInterface();this._updateButtons(!1);this._updateAutohide();this._updateVolume(this.options.volume);this._updateMute(this.options.muted);this.css.jq.videoPlay.length&&this.css.jq.videoPlay.hide(); +b.jPlayer.prototype.count++},destroy:function(){this.clearMedia();this._removeUiClass();this.css.jq.currentTime.length&&this.css.jq.currentTime.text("");this.css.jq.duration.length&&this.css.jq.duration.text("");b.each(this.css.jq,function(a,b){b.length&&b.unbind(".jPlayer")});this.internal.poster.jq.unbind(".jPlayer");this.internal.video.jq&&this.internal.video.jq.unbind(".jPlayer");this.options.emulateHtml&&this._destroyHtmlBridge();this.element.removeData("jPlayer");this.element.unbind(".jPlayer"); +this.element.empty();delete this.instances[this.internal.instance]},enable:function(){},disable:function(){},_testCanPlayType:function(a){try{return a.canPlayType(this.format.mp3.codec),!0}catch(b){return!1}},_uaBlocklist:function(a){var c=navigator.userAgent.toLowerCase(),d=!1;b.each(a,function(a,b){if(b&&b.test(c))return d=!0,!1});return d},_restrictNativeVideoControls:function(){if(this.require.audio&&this.status.nativeVideoControls)this.status.nativeVideoControls=!1,this.status.noFullScreen=!0}, +_updateNativeVideoControls:function(){if(this.html.video.available&&this.html.used)this.htmlElement.video.controls=this.status.nativeVideoControls,this._updateAutohide(),this.status.nativeVideoControls&&this.require.video?(this.internal.poster.jq.hide(),this.internal.video.jq.css({width:this.status.width,height:this.status.height})):this.status.waitForPlay&&this.status.video&&(this.internal.poster.jq.show(),this.internal.video.jq.css({width:"0px",height:"0px"}))},_addHtmlEventListeners:function(a, +c){var d=this;a.preload=this.options.preload;a.muted=this.options.muted;a.volume=this.options.volume;a.addEventListener("progress",function(){c.gate&&(d._getHtmlStatus(a),d._updateInterface(),d._trigger(b.jPlayer.event.progress))},!1);a.addEventListener("timeupdate",function(){c.gate&&(d._getHtmlStatus(a),d._updateInterface(),d._trigger(b.jPlayer.event.timeupdate))},!1);a.addEventListener("durationchange",function(){if(c.gate)d.status.duration=this.duration,d._getHtmlStatus(a),d._updateInterface(), +d._trigger(b.jPlayer.event.durationchange)},!1);a.addEventListener("play",function(){c.gate&&(d._updateButtons(!0),d._html_checkWaitForPlay(),d._trigger(b.jPlayer.event.play))},!1);a.addEventListener("playing",function(){c.gate&&(d._updateButtons(!0),d._seeked(),d._trigger(b.jPlayer.event.playing))},!1);a.addEventListener("pause",function(){c.gate&&(d._updateButtons(!1),d._trigger(b.jPlayer.event.pause))},!1);a.addEventListener("waiting",function(){c.gate&&(d._seeking(),d._trigger(b.jPlayer.event.waiting))}, +!1);a.addEventListener("seeking",function(){c.gate&&(d._seeking(),d._trigger(b.jPlayer.event.seeking))},!1);a.addEventListener("seeked",function(){c.gate&&(d._seeked(),d._trigger(b.jPlayer.event.seeked))},!1);a.addEventListener("volumechange",function(){if(c.gate)d.options.volume=a.volume,d.options.muted=a.muted,d._updateMute(),d._updateVolume(),d._trigger(b.jPlayer.event.volumechange)},!1);a.addEventListener("suspend",function(){c.gate&&(d._seeked(),d._trigger(b.jPlayer.event.suspend))},!1);a.addEventListener("ended", +function(){if(c.gate){if(!b.jPlayer.browser.webkit)d.htmlElement.media.currentTime=0;d.htmlElement.media.pause();d._updateButtons(!1);d._getHtmlStatus(a,!0);d._updateInterface();d._trigger(b.jPlayer.event.ended)}},!1);a.addEventListener("error",function(){if(c.gate&&(d._updateButtons(!1),d._seeked(),d.status.srcSet))clearTimeout(d.internal.htmlDlyCmdId),d.status.waitForLoad=!0,d.status.waitForPlay=!0,d.status.video&&!d.status.nativeVideoControls&&d.internal.video.jq.css({width:"0px",height:"0px"}), +d._validString(d.status.media.poster)&&!d.status.nativeVideoControls&&d.internal.poster.jq.show(),d.css.jq.videoPlay.length&&d.css.jq.videoPlay.show(),d._error({type:b.jPlayer.error.URL,context:d.status.src,message:b.jPlayer.errorMsg.URL,hint:b.jPlayer.errorHint.URL})},!1);b.each(b.jPlayer.htmlEvent,function(e,g){a.addEventListener(this,function(){c.gate&&d._trigger(b.jPlayer.event[g])},!1)})},_getHtmlStatus:function(a,b){var d=0,e=0,g=0,f=0;if(a.duration)this.status.duration=a.duration;d=a.currentTime; +e=this.status.duration>0?100*d/this.status.duration:0;typeof a.seekable==="object"&&a.seekable.length>0?(g=this.status.duration>0?100*a.seekable.end(a.seekable.length-1)/this.status.duration:100,f=100*a.currentTime/a.seekable.end(a.seekable.length-1)):(g=100,f=e);b&&(e=f=d=0);this.status.seekPercent=g;this.status.currentPercentRelative=f;this.status.currentPercentAbsolute=e;this.status.currentTime=d;this.status.readyState=a.readyState;this.status.networkState=a.networkState;this.status.playbackRate= +a.playbackRate;this.status.ended=a.ended},_resetStatus:function(){this.status=b.extend({},this.status,b.jPlayer.prototype.status)},_trigger:function(a,c,d){a=b.Event(a);a.jPlayer={};a.jPlayer.version=b.extend({},this.version);a.jPlayer.options=b.extend(!0,{},this.options);a.jPlayer.status=b.extend(!0,{},this.status);a.jPlayer.html=b.extend(!0,{},this.html);a.jPlayer.flash=b.extend(!0,{},this.flash);if(c)a.jPlayer.error=b.extend({},c);if(d)a.jPlayer.warning=b.extend({},d);this.element.trigger(a)}, +jPlayerFlashEvent:function(a,c){if(a===b.jPlayer.event.ready)if(this.internal.ready){if(this.flash.gate){if(this.status.srcSet){var d=this.status.currentTime,e=this.status.paused;this.setMedia(this.status.media);d>0&&(e?this.pause(d):this.play(d))}this._trigger(b.jPlayer.event.flashreset)}}else this.internal.ready=!0,this.internal.flash.jq.css({width:"0px",height:"0px"}),this.version.flash=c.version,this.version.needFlash!==this.version.flash&&this._error({type:b.jPlayer.error.VERSION,context:this.version.flash, +message:b.jPlayer.errorMsg.VERSION+this.version.flash,hint:b.jPlayer.errorHint.VERSION}),this._trigger(b.jPlayer.event.repeat),this._trigger(a);if(this.flash.gate)switch(a){case b.jPlayer.event.progress:this._getFlashStatus(c);this._updateInterface();this._trigger(a);break;case b.jPlayer.event.timeupdate:this._getFlashStatus(c);this._updateInterface();this._trigger(a);break;case b.jPlayer.event.play:this._seeked();this._updateButtons(!0);this._trigger(a);break;case b.jPlayer.event.pause:this._updateButtons(!1); +this._trigger(a);break;case b.jPlayer.event.ended:this._updateButtons(!1);this._trigger(a);break;case b.jPlayer.event.click:this._trigger(a);break;case b.jPlayer.event.error:this.status.waitForLoad=!0;this.status.waitForPlay=!0;this.status.video&&this.internal.flash.jq.css({width:"0px",height:"0px"});this._validString(this.status.media.poster)&&this.internal.poster.jq.show();this.css.jq.videoPlay.length&&this.status.video&&this.css.jq.videoPlay.show();this.status.video?this._flash_setVideo(this.status.media): +this._flash_setAudio(this.status.media);this._updateButtons(!1);this._error({type:b.jPlayer.error.URL,context:c.src,message:b.jPlayer.errorMsg.URL,hint:b.jPlayer.errorHint.URL});break;case b.jPlayer.event.seeking:this._seeking();this._trigger(a);break;case b.jPlayer.event.seeked:this._seeked();this._trigger(a);break;case b.jPlayer.event.ready:break;default:this._trigger(a)}return!1},_getFlashStatus:function(a){this.status.seekPercent=a.seekPercent;this.status.currentPercentRelative=a.currentPercentRelative; +this.status.currentPercentAbsolute=a.currentPercentAbsolute;this.status.currentTime=a.currentTime;this.status.duration=a.duration;this.status.readyState=4;this.status.networkState=0;this.status.playbackRate=1;this.status.ended=!1},_updateButtons:function(a){if(a!==f)this.status.paused=!a,this.css.jq.play.length&&this.css.jq.pause.length&&(a?(this.css.jq.play.hide(),this.css.jq.pause.show()):(this.css.jq.play.show(),this.css.jq.pause.hide()));this.css.jq.restoreScreen.length&&this.css.jq.fullScreen.length&& +(this.status.noFullScreen?(this.css.jq.fullScreen.hide(),this.css.jq.restoreScreen.hide()):this.options.fullScreen?(this.css.jq.fullScreen.hide(),this.css.jq.restoreScreen.show()):(this.css.jq.fullScreen.show(),this.css.jq.restoreScreen.hide()));this.css.jq.repeat.length&&this.css.jq.repeatOff.length&&(this.options.loop?(this.css.jq.repeat.hide(),this.css.jq.repeatOff.show()):(this.css.jq.repeat.show(),this.css.jq.repeatOff.hide()))},_updateInterface:function(){this.css.jq.seekBar.length&&this.css.jq.seekBar.width(this.status.seekPercent+ +"%");this.css.jq.playBar.length&&this.css.jq.playBar.width(this.status.currentPercentRelative+"%");this.css.jq.currentTime.length&&this.css.jq.currentTime.text(b.jPlayer.convertTime(this.status.currentTime));this.css.jq.duration.length&&this.css.jq.duration.text(b.jPlayer.convertTime(this.status.duration))},_seeking:function(){this.css.jq.seekBar.length&&this.css.jq.seekBar.addClass("jp-seeking-bg")},_seeked:function(){this.css.jq.seekBar.length&&this.css.jq.seekBar.removeClass("jp-seeking-bg")}, +_resetGate:function(){this.html.audio.gate=!1;this.html.video.gate=!1;this.flash.gate=!1},_resetActive:function(){this.html.active=!1;this.flash.active=!1},setMedia:function(a){var c=this,d=!1,e=this.status.media.poster!==a.poster;this._resetMedia();this._resetGate();this._resetActive();b.each(this.formats,function(e,f){var i=c.format[f].media==="video";b.each(c.solutions,function(b,e){if(c[e].support[f]&&c._validString(a[f])){var g=e==="html";i?(g?(c.html.video.gate=!0,c._html_setVideo(a),c.html.active= +!0):(c.flash.gate=!0,c._flash_setVideo(a),c.flash.active=!0),c.css.jq.videoPlay.length&&c.css.jq.videoPlay.show(),c.status.video=!0):(g?(c.html.audio.gate=!0,c._html_setAudio(a),c.html.active=!0):(c.flash.gate=!0,c._flash_setAudio(a),c.flash.active=!0),c.css.jq.videoPlay.length&&c.css.jq.videoPlay.hide(),c.status.video=!1);d=!0;return!1}});if(d)return!1});if(d){if((!this.status.nativeVideoControls||!this.html.video.gate)&&this._validString(a.poster))e?this.htmlElement.poster.src=a.poster:this.internal.poster.jq.show(); +this.status.srcSet=!0;this.status.media=b.extend({},a);this._updateButtons(!1);this._updateInterface()}else this._error({type:b.jPlayer.error.NO_SUPPORT,context:"{supplied:'"+this.options.supplied+"'}",message:b.jPlayer.errorMsg.NO_SUPPORT,hint:b.jPlayer.errorHint.NO_SUPPORT})},_resetMedia:function(){this._resetStatus();this._updateButtons(!1);this._updateInterface();this._seeked();this.internal.poster.jq.hide();clearTimeout(this.internal.htmlDlyCmdId);this.html.active?this._html_resetMedia():this.flash.active&& +this._flash_resetMedia()},clearMedia:function(){this._resetMedia();this.html.active?this._html_clearMedia():this.flash.active&&this._flash_clearMedia();this._resetGate();this._resetActive()},load:function(){this.status.srcSet?this.html.active?this._html_load():this.flash.active&&this._flash_load():this._urlNotSetError("load")},play:function(a){a=typeof a==="number"?a:NaN;this.status.srcSet?this.html.active?this._html_play(a):this.flash.active&&this._flash_play(a):this._urlNotSetError("play")},videoPlay:function(){this.play()}, +pause:function(a){a=typeof a==="number"?a:NaN;this.status.srcSet?this.html.active?this._html_pause(a):this.flash.active&&this._flash_pause(a):this._urlNotSetError("pause")},pauseOthers:function(){var a=this;b.each(this.instances,function(b,d){a.element!==d&&d.data("jPlayer").status.srcSet&&d.jPlayer("pause")})},stop:function(){this.status.srcSet?this.html.active?this._html_pause(0):this.flash.active&&this._flash_pause(0):this._urlNotSetError("stop")},playHead:function(a){a=this._limitValue(a,0,100); +this.status.srcSet?this.html.active?this._html_playHead(a):this.flash.active&&this._flash_playHead(a):this._urlNotSetError("playHead")},_muted:function(a){this.options.muted=a;this.html.used&&this._html_mute(a);this.flash.used&&this._flash_mute(a);!this.html.video.gate&&!this.html.audio.gate&&(this._updateMute(a),this._updateVolume(this.options.volume),this._trigger(b.jPlayer.event.volumechange))},mute:function(a){a=a===f?!0:!!a;this._muted(a)},unmute:function(a){a=a===f?!0:!!a;this._muted(!a)},_updateMute:function(a){if(a=== +f)a=this.options.muted;this.css.jq.mute.length&&this.css.jq.unmute.length&&(this.status.noVolume?(this.css.jq.mute.hide(),this.css.jq.unmute.hide()):a?(this.css.jq.mute.hide(),this.css.jq.unmute.show()):(this.css.jq.mute.show(),this.css.jq.unmute.hide()))},volume:function(a){a=this._limitValue(a,0,1);this.options.volume=a;this.html.used&&this._html_volume(a);this.flash.used&&this._flash_volume(a);!this.html.video.gate&&!this.html.audio.gate&&(this._updateVolume(a),this._trigger(b.jPlayer.event.volumechange))}, +volumeBar:function(a){if(this.css.jq.volumeBar.length){var b=this.css.jq.volumeBar.offset(),d=a.pageX-b.left,e=this.css.jq.volumeBar.width(),a=this.css.jq.volumeBar.height()-a.pageY+b.top,b=this.css.jq.volumeBar.height();this.options.verticalVolume?this.volume(a/b):this.volume(d/e)}this.options.muted&&this._muted(!1)},volumeBarValue:function(a){this.volumeBar(a)},_updateVolume:function(a){if(a===f)a=this.options.volume;a=this.options.muted?0:a;this.status.noVolume?(this.css.jq.volumeBar.length&&this.css.jq.volumeBar.hide(), +this.css.jq.volumeBarValue.length&&this.css.jq.volumeBarValue.hide(),this.css.jq.volumeMax.length&&this.css.jq.volumeMax.hide()):(this.css.jq.volumeBar.length&&this.css.jq.volumeBar.show(),this.css.jq.volumeBarValue.length&&(this.css.jq.volumeBarValue.show(),this.css.jq.volumeBarValue[this.options.verticalVolume?"height":"width"](a*100+"%")),this.css.jq.volumeMax.length&&this.css.jq.volumeMax.show())},volumeMax:function(){this.volume(1);this.options.muted&&this._muted(!1)},_cssSelectorAncestor:function(a){var c= +this;this.options.cssSelectorAncestor=a;this._removeUiClass();this.ancestorJq=a?b(a):[];a&&this.ancestorJq.length!==1&&this._warning({type:b.jPlayer.warning.CSS_SELECTOR_COUNT,context:a,message:b.jPlayer.warningMsg.CSS_SELECTOR_COUNT+this.ancestorJq.length+" found for cssSelectorAncestor.",hint:b.jPlayer.warningHint.CSS_SELECTOR_COUNT});this._addUiClass();b.each(this.options.cssSelector,function(a,b){c._cssSelector(a,b)})},_cssSelector:function(a,c){var d=this;typeof c==="string"?b.jPlayer.prototype.options.cssSelector[a]? +(this.css.jq[a]&&this.css.jq[a].length&&this.css.jq[a].unbind(".jPlayer"),this.options.cssSelector[a]=c,this.css.cs[a]=this.options.cssSelectorAncestor+" "+c,this.css.jq[a]=c?b(this.css.cs[a]):[],this.css.jq[a].length&&this.css.jq[a].bind("click.jPlayer",function(c){d[a](c);b(this).blur();return!1}),c&&this.css.jq[a].length!==1&&this._warning({type:b.jPlayer.warning.CSS_SELECTOR_COUNT,context:this.css.cs[a],message:b.jPlayer.warningMsg.CSS_SELECTOR_COUNT+this.css.jq[a].length+" found for "+a+" method.", +hint:b.jPlayer.warningHint.CSS_SELECTOR_COUNT})):this._warning({type:b.jPlayer.warning.CSS_SELECTOR_METHOD,context:a,message:b.jPlayer.warningMsg.CSS_SELECTOR_METHOD,hint:b.jPlayer.warningHint.CSS_SELECTOR_METHOD}):this._warning({type:b.jPlayer.warning.CSS_SELECTOR_STRING,context:c,message:b.jPlayer.warningMsg.CSS_SELECTOR_STRING,hint:b.jPlayer.warningHint.CSS_SELECTOR_STRING})},seekBar:function(a){if(this.css.jq.seekBar){var b=this.css.jq.seekBar.offset(),a=a.pageX-b.left,b=this.css.jq.seekBar.width(); +this.playHead(100*a/b)}},playBar:function(a){this.seekBar(a)},repeat:function(){this._loop(!0)},repeatOff:function(){this._loop(!1)},_loop:function(a){if(this.options.loop!==a)this.options.loop=a,this._updateButtons(),this._trigger(b.jPlayer.event.repeat)},currentTime:function(){},duration:function(){},gui:function(){},noSolution:function(){},option:function(a,c){var d=a;if(arguments.length===0)return b.extend(!0,{},this.options);if(typeof a==="string"){var e=a.split(".");if(c===f){for(var d=b.extend(!0, +{},this.options),g=0;g<e.length;g++)if(d[e[g]]!==f)d=d[e[g]];else return this._warning({type:b.jPlayer.warning.OPTION_KEY,context:a,message:b.jPlayer.warningMsg.OPTION_KEY,hint:b.jPlayer.warningHint.OPTION_KEY}),f;return d}for(var g=d={},h=0;h<e.length;h++)h<e.length-1?(g[e[h]]={},g=g[e[h]]):g[e[h]]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(a,b){c._setOption(a,b)});return this},_setOption:function(a,c){var d=this;switch(a){case "volume":this.volume(c); +break;case "muted":this._muted(c);break;case "cssSelectorAncestor":this._cssSelectorAncestor(c);break;case "cssSelector":b.each(c,function(a,b){d._cssSelector(a,b)});break;case "fullScreen":this.options[a]!==c&&(this._removeUiClass(),this.options[a]=c,this._refreshSize());break;case "size":!this.options.fullScreen&&this.options[a].cssClass!==c.cssClass&&this._removeUiClass();this.options[a]=b.extend({},this.options[a],c);this._refreshSize();break;case "sizeFull":this.options.fullScreen&&this.options[a].cssClass!== +c.cssClass&&this._removeUiClass();this.options[a]=b.extend({},this.options[a],c);this._refreshSize();break;case "autohide":this.options[a]=b.extend({},this.options[a],c);this._updateAutohide();break;case "loop":this._loop(c);break;case "nativeVideoControls":this.options[a]=b.extend({},this.options[a],c);this.status.nativeVideoControls=this._uaBlocklist(this.options.nativeVideoControls);this._restrictNativeVideoControls();this._updateNativeVideoControls();break;case "noFullScreen":this.options[a]= +b.extend({},this.options[a],c);this.status.nativeVideoControls=this._uaBlocklist(this.options.nativeVideoControls);this.status.noFullScreen=this._uaBlocklist(this.options.noFullScreen);this._restrictNativeVideoControls();this._updateButtons();break;case "noVolume":this.options[a]=b.extend({},this.options[a],c);this.status.noVolume=this._uaBlocklist(this.options.noVolume);this._updateVolume();this._updateMute();break;case "emulateHtml":this.options[a]!==c&&((this.options[a]=c)?this._emulateHtmlBridge(): +this._destroyHtmlBridge())}return this},_refreshSize:function(){this._setSize();this._addUiClass();this._updateSize();this._updateButtons();this._updateAutohide();this._trigger(b.jPlayer.event.resize)},_setSize:function(){this.options.fullScreen?(this.status.width=this.options.sizeFull.width,this.status.height=this.options.sizeFull.height,this.status.cssClass=this.options.sizeFull.cssClass):(this.status.width=this.options.size.width,this.status.height=this.options.size.height,this.status.cssClass= +this.options.size.cssClass);this.element.css({width:this.status.width,height:this.status.height})},_addUiClass:function(){this.ancestorJq.length&&this.ancestorJq.addClass(this.status.cssClass)},_removeUiClass:function(){this.ancestorJq.length&&this.ancestorJq.removeClass(this.status.cssClass)},_updateSize:function(){this.internal.poster.jq.css({width:this.status.width,height:this.status.height});!this.status.waitForPlay&&this.html.active&&this.status.video||this.html.video.available&&this.html.used&& +this.status.nativeVideoControls?this.internal.video.jq.css({width:this.status.width,height:this.status.height}):!this.status.waitForPlay&&this.flash.active&&this.status.video&&this.internal.flash.jq.css({width:this.status.width,height:this.status.height})},_updateAutohide:function(){var a=this,b=function(){a.css.jq.gui.fadeIn(a.options.autohide.fadeIn,function(){clearTimeout(a.internal.autohideId);a.internal.autohideId=setTimeout(function(){a.css.jq.gui.fadeOut(a.options.autohide.fadeOut)},a.options.autohide.hold)})}; +this.css.jq.gui.length&&(this.css.jq.gui.stop(!0,!0),clearTimeout(this.internal.autohideId),this.element.unbind(".jPlayerAutohide"),this.css.jq.gui.unbind(".jPlayerAutohide"),this.status.nativeVideoControls?this.css.jq.gui.hide():this.options.fullScreen&&this.options.autohide.full||!this.options.fullScreen&&this.options.autohide.restored?(this.element.bind("mousemove.jPlayer.jPlayerAutohide",b),this.css.jq.gui.bind("mousemove.jPlayer.jPlayerAutohide",b),this.css.jq.gui.hide()):this.css.jq.gui.show())}, +fullScreen:function(){this._setOption("fullScreen",!0)},restoreScreen:function(){this._setOption("fullScreen",!1)},_html_initMedia:function(){this.htmlElement.media.src=this.status.src;this.options.preload!=="none"&&this._html_load();this._trigger(b.jPlayer.event.timeupdate)},_html_setAudio:function(a){var c=this;b.each(this.formats,function(b,e){if(c.html.support[e]&&a[e])return c.status.src=a[e],c.status.format[e]=!0,c.status.formatType=e,!1});this.htmlElement.media=this.htmlElement.audio;this._html_initMedia()}, +_html_setVideo:function(a){var c=this;b.each(this.formats,function(b,e){if(c.html.support[e]&&a[e])return c.status.src=a[e],c.status.format[e]=!0,c.status.formatType=e,!1});if(this.status.nativeVideoControls)this.htmlElement.video.poster=this._validString(a.poster)?a.poster:"";this.htmlElement.media=this.htmlElement.video;this._html_initMedia()},_html_resetMedia:function(){this.htmlElement.media&&(this.htmlElement.media.id===this.internal.video.id&&!this.status.nativeVideoControls&&this.internal.video.jq.css({width:"0px", +height:"0px"}),this.htmlElement.media.pause())},_html_clearMedia:function(){if(this.htmlElement.media)this.htmlElement.media.src="",this.htmlElement.media.load()},_html_load:function(){if(this.status.waitForLoad)this.status.waitForLoad=!1,this.htmlElement.media.load();clearTimeout(this.internal.htmlDlyCmdId)},_html_play:function(a){var b=this;this._html_load();this.htmlElement.media.play();if(!isNaN(a))try{this.htmlElement.media.currentTime=a}catch(d){this.internal.htmlDlyCmdId=setTimeout(function(){b.play(a)}, +100);return}this._html_checkWaitForPlay()},_html_pause:function(a){var b=this;a>0?this._html_load():clearTimeout(this.internal.htmlDlyCmdId);this.htmlElement.media.pause();if(!isNaN(a))try{this.htmlElement.media.currentTime=a}catch(d){this.internal.htmlDlyCmdId=setTimeout(function(){b.pause(a)},100);return}a>0&&this._html_checkWaitForPlay()},_html_playHead:function(a){var b=this;this._html_load();try{if(typeof this.htmlElement.media.seekable==="object"&&this.htmlElement.media.seekable.length>0)this.htmlElement.media.currentTime= +a*this.htmlElement.media.seekable.end(this.htmlElement.media.seekable.length-1)/100;else if(this.htmlElement.media.duration>0&&!isNaN(this.htmlElement.media.duration))this.htmlElement.media.currentTime=a*this.htmlElement.media.duration/100;else throw"e";}catch(d){this.internal.htmlDlyCmdId=setTimeout(function(){b.playHead(a)},100);return}this.status.waitForLoad||this._html_checkWaitForPlay()},_html_checkWaitForPlay:function(){if(this.status.waitForPlay)this.status.waitForPlay=!1,this.css.jq.videoPlay.length&& +this.css.jq.videoPlay.hide(),this.status.video&&(this.internal.poster.jq.hide(),this.internal.video.jq.css({width:this.status.width,height:this.status.height}))},_html_volume:function(a){if(this.html.audio.available)this.htmlElement.audio.volume=a;if(this.html.video.available)this.htmlElement.video.volume=a},_html_mute:function(a){if(this.html.audio.available)this.htmlElement.audio.muted=a;if(this.html.video.available)this.htmlElement.video.muted=a},_flash_setAudio:function(a){var c=this;try{if(b.each(this.formats, +function(b,d){if(c.flash.support[d]&&a[d]){switch(d){case "m4a":case "fla":c._getMovie().fl_setAudio_m4a(a[d]);break;case "mp3":c._getMovie().fl_setAudio_mp3(a[d])}c.status.src=a[d];c.status.format[d]=!0;c.status.formatType=d;return!1}}),this.options.preload==="auto")this._flash_load(),this.status.waitForLoad=!1}catch(d){this._flashError(d)}},_flash_setVideo:function(a){var c=this;try{if(b.each(this.formats,function(b,d){if(c.flash.support[d]&&a[d]){switch(d){case "m4v":case "flv":c._getMovie().fl_setVideo_m4v(a[d])}c.status.src= +a[d];c.status.format[d]=!0;c.status.formatType=d;return!1}}),this.options.preload==="auto")this._flash_load(),this.status.waitForLoad=!1}catch(d){this._flashError(d)}},_flash_resetMedia:function(){this.internal.flash.jq.css({width:"0px",height:"0px"});this._flash_pause(NaN)},_flash_clearMedia:function(){try{this._getMovie().fl_clearMedia()}catch(a){this._flashError(a)}},_flash_load:function(){try{this._getMovie().fl_load()}catch(a){this._flashError(a)}this.status.waitForLoad=!1},_flash_play:function(a){try{this._getMovie().fl_play(a)}catch(b){this._flashError(b)}this.status.waitForLoad= +!1;this._flash_checkWaitForPlay()},_flash_pause:function(a){try{this._getMovie().fl_pause(a)}catch(b){this._flashError(b)}if(a>0)this.status.waitForLoad=!1,this._flash_checkWaitForPlay()},_flash_playHead:function(a){try{this._getMovie().fl_play_head(a)}catch(b){this._flashError(b)}this.status.waitForLoad||this._flash_checkWaitForPlay()},_flash_checkWaitForPlay:function(){if(this.status.waitForPlay)this.status.waitForPlay=!1,this.css.jq.videoPlay.length&&this.css.jq.videoPlay.hide(),this.status.video&& +(this.internal.poster.jq.hide(),this.internal.flash.jq.css({width:this.status.width,height:this.status.height}))},_flash_volume:function(a){try{this._getMovie().fl_volume(a)}catch(b){this._flashError(b)}},_flash_mute:function(a){try{this._getMovie().fl_mute(a)}catch(b){this._flashError(b)}},_getMovie:function(){return document[this.internal.flash.id]},_checkForFlash:function(a){var b=!1,d;if(window.ActiveXObject)try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+a),b=!0}catch(e){}else navigator.plugins&& +navigator.mimeTypes.length>0&&(d=navigator.plugins["Shockwave Flash"])&&navigator.plugins["Shockwave Flash"].description.replace(/.*\s(\d+\.\d+).*/,"$1")>=a&&(b=!0);return b},_validString:function(a){return a&&typeof a==="string"},_limitValue:function(a,b,d){return a<b?b:a>d?d:a},_urlNotSetError:function(a){this._error({type:b.jPlayer.error.URL_NOT_SET,context:a,message:b.jPlayer.errorMsg.URL_NOT_SET,hint:b.jPlayer.errorHint.URL_NOT_SET})},_flashError:function(a){var c;c=this.internal.ready?"FLASH_DISABLED": +"FLASH";this._error({type:b.jPlayer.error[c],context:this.internal.flash.swf,message:b.jPlayer.errorMsg[c]+a.message,hint:b.jPlayer.errorHint[c]});this.internal.flash.jq.css({width:"1px",height:"1px"})},_error:function(a){this._trigger(b.jPlayer.event.error,a);this.options.errorAlerts&&this._alert("Error!"+(a.message?"\n\n"+a.message:"")+(a.hint?"\n\n"+a.hint:"")+"\n\nContext: "+a.context)},_warning:function(a){this._trigger(b.jPlayer.event.warning,f,a);this.options.warningAlerts&&this._alert("Warning!"+ +(a.message?"\n\n"+a.message:"")+(a.hint?"\n\n"+a.hint:"")+"\n\nContext: "+a.context)},_alert:function(a){alert("jPlayer "+this.version.script+" : id='"+this.internal.self.id+"' : "+a)},_emulateHtmlBridge:function(){var a=this;b.each(b.jPlayer.emulateMethods.split(/\s+/g),function(b,d){a.internal.domNode[d]=function(b){a[d](b)}});b.each(b.jPlayer.event,function(c,d){var e=!0;b.each(b.jPlayer.reservedEvent.split(/\s+/g),function(a,b){if(b===c)return e=!1});e&&a.element.bind(d+".jPlayer.jPlayerHtml", +function(){a._emulateHtmlUpdate();var b=document.createEvent("Event");b.initEvent(c,!1,!0);a.internal.domNode.dispatchEvent(b)})})},_emulateHtmlUpdate:function(){var a=this;b.each(b.jPlayer.emulateStatus.split(/\s+/g),function(b,d){a.internal.domNode[d]=a.status[d]});b.each(b.jPlayer.emulateOptions.split(/\s+/g),function(b,d){a.internal.domNode[d]=a.options[d]})},_destroyHtmlBridge:function(){var a=this;this.element.unbind(".jPlayerHtml");b.each((b.jPlayer.emulateMethods+" "+b.jPlayer.emulateStatus+ +" "+b.jPlayer.emulateOptions).split(/\s+/g),function(b,d){delete a.internal.domNode[d]})}};b.jPlayer.error={FLASH:"e_flash",FLASH_DISABLED:"e_flash_disabled",NO_SOLUTION:"e_no_solution",NO_SUPPORT:"e_no_support",URL:"e_url",URL_NOT_SET:"e_url_not_set",VERSION:"e_version"};b.jPlayer.errorMsg={FLASH:"jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ",FLASH_DISABLED:"jPlayer's Flash fallback has been disabled by the browser due to the CSS rules you have used. Details: ", +NO_SOLUTION:"No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.",NO_SUPPORT:"It is not possible to play any media format provided in setMedia() on this browser using your current options.",URL:"Media URL could not be loaded.",URL_NOT_SET:"Attempt to issue media playback commands, while no media url is set.",VERSION:"jPlayer "+b.jPlayer.prototype.version.script+" needs Jplayer.swf version "+b.jPlayer.prototype.version.needFlash+" but found "};b.jPlayer.errorHint= +{FLASH:"Check your swfPath option and that Jplayer.swf is there.",FLASH_DISABLED:"Check that you have not display:none; the jPlayer entity or any ancestor.",NO_SOLUTION:"Review the jPlayer options: support and supplied.",NO_SUPPORT:"Video or audio formats defined in the supplied option are missing.",URL:"Check media URL is valid.",URL_NOT_SET:"Use setMedia() to set the media URL.",VERSION:"Update jPlayer files."};b.jPlayer.warning={CSS_SELECTOR_COUNT:"e_css_selector_count",CSS_SELECTOR_METHOD:"e_css_selector_method", +CSS_SELECTOR_STRING:"e_css_selector_string",OPTION_KEY:"e_option_key"};b.jPlayer.warningMsg={CSS_SELECTOR_COUNT:"The number of css selectors found did not equal one: ",CSS_SELECTOR_METHOD:"The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.",CSS_SELECTOR_STRING:"The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.",OPTION_KEY:"The option requested in jPlayer('option') is undefined."};b.jPlayer.warningHint={CSS_SELECTOR_COUNT:"Check your css selector and the ancestor.", +CSS_SELECTOR_METHOD:"Check your method name.",CSS_SELECTOR_STRING:"Check your css selector is a string.",OPTION_KEY:"Check your option name."}})(jQuery); \ No newline at end of file diff --git a/wolnelektury/static/js/base.js b/wolnelektury/static/js/base.js new file mode 100755 index 000000000..36a85d06f --- /dev/null +++ b/wolnelektury/static/js/base.js @@ -0,0 +1,93 @@ +(function($) { + $(function() { + $.fn.toggle_slide = function(p) { + cont = $(this); + short_el = p['short_el'] || $(':first-child', this); + long_el = p['long_el'] || short_el.next(); + button = p['button']; + short_text = p['short_text'], + long_text = p['long_text']; + + var toggle_fun = function(cont, short_el, long_el, button, short_text, long_text) { + var toggle = function() { + if (cont.hasClass('short')) { + cont.animate({"height": long_el.attr("cont_h")+'px'}, {duration: "fast" }).removeClass('short'); + short_el.hide(); + long_el.show(); + if (button && long_text) button.html(long_text); + } else { + cont.animate({"height": short_el.attr("cont_h")+'px'}, {duration: "fast" }).addClass('short'); + long_el.hide(); + short_el.show(); + if (button && short_text) button.html(short_text); + } + return false; + } + return toggle; + } + if (long_el.html().length <= short_el.html().length) + return; + + // ensure long element shown first + long_el.show();short_el.hide(); + long_el.attr("cont_h", $(this).height()).hide(); + short_el.show().attr("cont_h", $(this).height()); + $(this).addClass('short'); + + if (button && short_text) button.html(short_text); + if (button) button.hover( + function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); }, + function() { $(this).css({background: '#EEE'}); } + ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text)); + short_el.hover( + function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); }, + function() { $(this).css({background: '#FFF'}); } + ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text)); + long_el.hover( + function() { $(this).css({background: '#F3F3F3', cursor: 'pointer'}); }, + function() { $(this).css({background: '#FFF'}); } + ).click(toggle_fun(cont, short_el, long_el, button, short_text, long_text)); + }; + + + // Fragments + $('.fragment-short-text').each(function() { + var fragment = $(this).closest('.fragment'); + fragment.toggle_slide({ + short_el: $(this), + long_el: fragment.find('.fragment-text') + }) + }); + + + + + + +$('#themes-list-toggle').click(function(event) { + event.preventDefault(); + $('#themes-list').toggle('slow'); +}); + + +$('.open-player').click(function(event) { + event.preventDefault(); + window.open($(this).attr('href'), + 'player', + 'width=420, height=500' + ); +}); + + + $('.book-list-index').click(function(){ + $('.book-list-show-index').hide('slow'); + if($(this).parent().next('ul:not(:hidden)').length == 0){ + $(this).parent().next('ul').toggle('slow'); + } + return false; + }); + + + }); +})(jQuery) + diff --git a/wolnelektury/static/js/catalogue.js b/wolnelektury/static/js/catalogue.js index ee8a045dc..485780692 100644 --- a/wolnelektury/static/js/catalogue.js +++ b/wolnelektury/static/js/catalogue.js @@ -90,9 +90,20 @@ function changeBannerText() { } } -function autocomplete_result_handler(event, item) { - $(event.target).closest('form').submit(); +function autocomplete_format_item(ul, item) { + return $("<li></li>").data('item.autocomplete', item) + .append('<a href="'+item.url+'">'+item.label+ ' ('+item.category+')</a>') + .appendTo(ul); } + +function autocomplete_result_handler(event, ui) { + if (ui.item.url != undefined) { + location.href = ui.item.url; + } else { + $(event.target).closest('form').submit(); + } +} + function serverTime() { var time = null; $.ajax({url: '/katalog/zegar/', @@ -583,7 +594,10 @@ function serverTime() { } }); }*/ - + $("#custom-pdf-link").toggle( + function(ev) { $(".custom-pdf").show(); return false; }, + function(ev) { $(".custom-pdf").hide(); return false; } + ); }); })(jQuery) diff --git a/wolnelektury/static/js/dialogs.js b/wolnelektury/static/js/dialogs.js new file mode 100755 index 000000000..bf9d94b54 --- /dev/null +++ b/wolnelektury/static/js/dialogs.js @@ -0,0 +1,64 @@ +(function($) { + $(function() { + + // create containers for all ajaxable form links + $('.ajaxable').each(function() { + var $window = $("#ajaxable-window").clone(); + $window.attr("id", this.id + "-window"); + $('body').append($window); + + var trigger = '#' + this.id; + + var href = $(this).attr('href'); + if (href.search('\\?') != -1) + href += '&ajax=1'; + else href += '?ajax=1'; + + $window.jqm({ + ajax: href, + ajaxText: '<p><img src="' + STATIC_URL + 'img/indicator.gif" alt="*"/> ' + gettext("Loading") + '</p>', + target: $('.target', $window)[0], + overlay: 60, + trigger: trigger, + onShow: function(hash) { + var offset = $(hash.t).offset(); + hash.w.css({position: 'absolute', left: offset.left - hash.w.width() + $(hash.t).width(), top: offset.top}); + $('.header', hash.w).css({width: $(hash.t).width()}); + hash.w.show(); + }, + onLoad: function(hash) { + $('form', hash.w).each(function() { + if (this.action.search('[\\?&]ajax=1') != -1) + return; + if (this.action.search('\\?') != -1) + this.action += '&ajax=1'; + else this.action += '?ajax=1'; + }); + $('form', hash.w).ajaxForm({ + dataType: 'json', + target: $('.target', $window), + success: function(response) { + if (response.success) { + $('.target', $window).text(response.message); + setTimeout(function() { $window.jqmHide() }, 1000); + if (response.redirect) + window.location = response.redirect; + } + else { + $('.error', $window).remove(); + $.each(response.errors, function(id, errors) { + $('#id_' + id, $window).before('<span class="error">' + errors[0] + '</span>'); + }); + $('input[type=submit]', $window).removeAttr('disabled'); + return false; + } + } + }); + } + }); + }); + + + }); +})(jQuery) + diff --git a/wolnelektury/static/js/jquery-ui-1.8.16.custom.min.js b/wolnelektury/static/js/jquery-ui-1.8.16.custom.min.js new file mode 100644 index 000000000..a9c6fa316 --- /dev/null +++ b/wolnelektury/static/js/jquery-ui-1.8.16.custom.min.js @@ -0,0 +1,149 @@ +/*! + * jQuery UI 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.16", +keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({propAttr:c.fn.prop||c.fn.attr,_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d= +this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this, +"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart": +"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight, +outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a, +"tabindex"),d=isNaN(b);return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&& +a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&& +c.ui.isOverAxis(b,e,i)}})}})(jQuery); +;/*! + * jQuery UI Widget 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Widget + */ +(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)try{b(d).triggerHandler("remove")}catch(e){}k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){try{b(this).triggerHandler("remove")}catch(d){}});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]= +function(h){return!!b.data(h,a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)): +d;if(e&&d.charAt(0)==="_")return h;e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options= +b.extend(true,{},this.options,this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+ +"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled", +c);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery); +;/*! + * jQuery UI Mouse 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Mouse + * + * Depends: + * jquery.ui.widget.js + */ +(function(b){var d=false;b(document).mouseup(function(){d=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(c){return a._mouseDown(c)}).bind("click."+this.widgetName,function(c){if(true===b.data(c.target,a.widgetName+".preventClickEvent")){b.removeData(c.target,a.widgetName+".preventClickEvent");c.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+ +this.widgetName)},_mouseDown:function(a){if(!d){this._mouseStarted&&this._mouseUp(a);this._mouseDownEvent=a;var c=this,f=a.which==1,g=typeof this.options.cancel=="string"&&a.target.nodeName?b(a.target).closest(this.options.cancel).length:false;if(!f||g||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){c.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted= +this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault();return true}}true===b.data(a.target,this.widgetName+".preventClickEvent")&&b.removeData(a.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(e){return c._mouseMove(e)};this._mouseUpDelegate=function(e){return c._mouseUp(e)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);a.preventDefault();return d=true}},_mouseMove:function(a){if(b.browser.msie&& +!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted= +false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +;/* + * jQuery UI Position 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Position + */ +(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY, +left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+= +k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+(parseInt(c.curCSS(this,"marginRight",true))||0),w=m+q+(parseInt(c.curCSS(this,"marginBottom",true))||0),i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-= +m/2;i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left= +d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= +a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), +g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); +;/* + * jQuery UI Draggable 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Draggables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== +"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b= +this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;if(b.iframeFix)d(b.iframeFix===true?"iframe":b.iframeFix).each(function(){d('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")});return true},_mouseStart:function(a){var b=this.options; +this.helper=this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}); +this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);d.ui.ddmanager&&d.ui.ddmanager.dragStart(this,a);return true}, +_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b= +false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration, +10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},_mouseUp:function(a){this.options.iframeFix===true&&d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});d.ui.ddmanager&&d.ui.ddmanager.dragStop(this,a);return d.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle|| +!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&& +a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent= +this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"), +10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"), +10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[a.containment=="document"?0:d(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,a.containment=="document"?0:d(window).scrollTop()-this.offset.relative.top-this.offset.parent.top, +(a.containment=="document"?0:d(window).scrollLeft())+d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"?0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){a=d(a.containment);var b=a[0];if(b){a.offset();var c=d(b).css("overflow")!= +"hidden";this.containment=[(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0),(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0),(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"), +10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom];this.relative_container=a}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+ +this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&& +!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,h=a.pageY;if(this.originalPosition){var g;if(this.containment){if(this.relative_container){g=this.relative_container.offset();g=[this.containment[0]+g.left,this.containment[1]+g.top,this.containment[2]+g.left,this.containment[3]+g.top]}else g=this.containment;if(a.pageX-this.offset.click.left<g[0])e=g[0]+this.offset.click.left; +if(a.pageY-this.offset.click.top<g[1])h=g[1]+this.offset.click.top;if(a.pageX-this.offset.click.left>g[2])e=g[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>g[3])h=g[3]+this.offset.click.top}if(b.grid){h=b.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/b.grid[1])*b.grid[1]:this.originalPageY;h=g?!(h-this.offset.click.top<g[1]||h-this.offset.click.top>g[3])?h:!(h-this.offset.click.top<g[1])?h-b.grid[1]:h+b.grid[1]:h;e=b.grid[0]?this.originalPageX+Math.round((e-this.originalPageX)/ +b.grid[0])*b.grid[0]:this.originalPageX;e=g?!(e-this.offset.click.left<g[0]||e-this.offset.click.left>g[2])?e:!(e-this.offset.click.left<g[0])?e-b.grid[0]:e+b.grid[0]:e}}return{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop()),left:e-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&d.browser.version< +526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=false},_trigger:function(a,b,c){c=c||this._uiHash();d.ui.plugin.call(this,a,[b,c]);if(a=="drag")this.positionAbs=this._convertPositionTo("absolute");return d.Widget.prototype._trigger.call(this,a,b, +c)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});d.extend(d.ui.draggable,{version:"1.8.16"});d.ui.plugin.add("draggable","connectToSortable",{start:function(a,b){var c=d(this).data("draggable"),f=c.options,e=d.extend({},b,{item:c.element});c.sortables=[];d(f.connectToSortable).each(function(){var h=d.data(this,"sortable");if(h&&!h.options.disabled){c.sortables.push({instance:h,shouldRevert:h.options.revert}); +h.refreshPositions();h._trigger("activate",a,e)}})},stop:function(a,b){var c=d(this).data("draggable"),f=d.extend({},b,{item:c.element});d.each(c.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;c.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert)this.instance.options.revert=true;this.instance._mouseStop(a);this.instance.options.helper=this.instance.options._helper;c.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})}else{this.instance.cancelHelperRemoval= +false;this.instance._trigger("deactivate",a,f)}})},drag:function(a,b){var c=d(this).data("draggable"),f=this;d.each(c.sortables,function(){this.instance.positionAbs=c.positionAbs;this.instance.helperProportions=c.helperProportions;this.instance.offset.click=c.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=d(f).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",true); +this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return b.helper[0]};a.target=this.instance.currentItem[0];this.instance._mouseCapture(a,true);this.instance._mouseStart(a,true,true);this.instance.offset.click.top=c.offset.click.top;this.instance.offset.click.left=c.offset.click.left;this.instance.offset.parent.left-=c.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=c.offset.parent.top-this.instance.offset.parent.top; +c._trigger("toSortable",a);c.dropped=this.instance.element;c.currentItem=c.element;this.instance.fromOutside=c}this.instance.currentItem&&this.instance._mouseDrag(a)}else if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",a,this.instance._uiHash(this.instance));this.instance._mouseStop(a,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();this.instance.placeholder&& +this.instance.placeholder.remove();c._trigger("fromSortable",a);c.dropped=false}})}});d.ui.plugin.add("draggable","cursor",{start:function(){var a=d("body"),b=d(this).data("draggable").options;if(a.css("cursor"))b._cursor=a.css("cursor");a.css("cursor",b.cursor)},stop:function(){var a=d(this).data("draggable").options;a._cursor&&d("body").css("cursor",a._cursor)}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("opacity"))b._opacity= +a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable");if(a.scrollParent[0]!=document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){if(!c.axis||c.axis!= +"x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop+c.scrollSpeed;else if(a.pageY-b.overflowOffset.top<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop-c.scrollSpeed;if(!c.axis||c.axis!="y")if(b.overflowOffset.left+b.scrollParent[0].offsetWidth-a.pageX<c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft+c.scrollSpeed;else if(a.pageX-b.overflowOffset.left< +c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft-c.scrollSpeed}else{if(!c.axis||c.axis!="x")if(a.pageY-d(document).scrollTop()<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()-c.scrollSpeed);else if(d(window).height()-(a.pageY-d(document).scrollTop())<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()+c.scrollSpeed);if(!c.axis||c.axis!="y")if(a.pageX-d(document).scrollLeft()<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()- +c.scrollSpeed);else if(d(window).width()-(a.pageX-d(document).scrollLeft())<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()+c.scrollSpeed)}f!==false&&d.ui.ddmanager&&!c.dropBehaviour&&d.ui.ddmanager.prepareOffsets(b,a)}});d.ui.plugin.add("draggable","snap",{start:function(){var a=d(this).data("draggable"),b=a.options;a.snapElements=[];d(b.snap.constructor!=String?b.snap.items||":data(draggable)":b.snap).each(function(){var c=d(this),f=c.offset();this!=a.element[0]&&a.snapElements.push({item:this, +width:c.outerWidth(),height:c.outerHeight(),top:f.top,left:f.left})})},drag:function(a,b){for(var c=d(this).data("draggable"),f=c.options,e=f.snapTolerance,h=b.offset.left,g=h+c.helperProportions.width,n=b.offset.top,o=n+c.helperProportions.height,i=c.snapElements.length-1;i>=0;i--){var j=c.snapElements[i].left,l=j+c.snapElements[i].width,k=c.snapElements[i].top,m=k+c.snapElements[i].height;if(j-e<h&&h<l+e&&k-e<n&&n<m+e||j-e<h&&h<l+e&&k-e<o&&o<m+e||j-e<g&&g<l+e&&k-e<n&&n<m+e||j-e<g&&g<l+e&&k-e<o&& +o<m+e){if(f.snapMode!="inner"){var p=Math.abs(k-o)<=e,q=Math.abs(m-n)<=e,r=Math.abs(j-g)<=e,s=Math.abs(l-h)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:k-c.helperProportions.height,left:0}).top-c.margins.top;if(q)b.position.top=c._convertPositionTo("relative",{top:m,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:j-c.helperProportions.width}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:l}).left-c.margins.left}var t= +p||q||r||s;if(f.snapMode!="outer"){p=Math.abs(k-n)<=e;q=Math.abs(m-o)<=e;r=Math.abs(j-h)<=e;s=Math.abs(l-g)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:k,left:0}).top-c.margins.top;if(q)b.position.top=c._convertPositionTo("relative",{top:m-c.helperProportions.height,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:j}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:l-c.helperProportions.width}).left-c.margins.left}if(!c.snapElements[i].snapping&& +(p||q||r||s||t))c.options.snap.snap&&c.options.snap.snap.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[i].item}));c.snapElements[i].snapping=p||q||r||s||t}else{c.snapElements[i].snapping&&c.options.snap.release&&c.options.snap.release.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[i].item}));c.snapElements[i].snapping=false}}}});d.ui.plugin.add("draggable","stack",{start:function(){var a=d(this).data("draggable").options;a=d.makeArray(d(a.stack)).sort(function(c,f){return(parseInt(d(c).css("zIndex"), +10)||0)-(parseInt(d(f).css("zIndex"),10)||0)});if(a.length){var b=parseInt(a[0].style.zIndex)||0;d(a).each(function(c){this.style.zIndex=b+c});this[0].style.zIndex=b+a.length}}});d.ui.plugin.add("draggable","zIndex",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("zIndex"))b._zIndex=a.css("zIndex");a.css("zIndex",b.zIndex)},stop:function(a,b){a=d(this).data("draggable").options;a._zIndex&&d(b.helper).css("zIndex",a._zIndex)}})})(jQuery); +;/* + * jQuery UI Autocomplete 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + */ +(function(d){var e=0;d.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.propAttr("readOnly"))){g= +false;var f=d.ui.keyCode;switch(c.keyCode){case f.PAGE_UP:a._move("previousPage",c);break;case f.PAGE_DOWN:a._move("nextPage",c);break;case f.UP:a._move("previous",c);c.preventDefault();break;case f.DOWN:a._move("next",c);c.preventDefault();break;case f.ENTER:case f.NUMPAD_ENTER:if(a.menu.active){g=true;c.preventDefault()}case f.TAB:if(!a.menu.active)return;a.menu.select(c);break;case f.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!= +a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(g){g=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)}; +this.menu=d("<ul></ul>").addClass("ui-autocomplete").appendTo(d(this.options.appendTo||"body",b)[0]).mousedown(function(c){var f=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(h){h.target!==a.element[0]&&h.target!==f&&!d.ui.contains(f,h.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,f){f=f.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:f})&&/^key/.test(c.originalEvent.type)&& +a.element.val(f.value)},selected:function(c,f){var h=f.item.data("item.autocomplete"),i=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=i;setTimeout(function(){a.previous=i;a.selectedItem=h},1)}false!==a._trigger("select",c,{item:h})&&a.element.val(h.value);a.term=a.element.val();a.close(c);a.selectedItem=h},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"); +d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&& +b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,g;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,f){f(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(c,f){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:g,data:c,dataType:"json",autocompleteRequest:++e,success:function(h){this.autocompleteRequest===e&&f(h)},error:function(){this.autocompleteRequest===e&&f([])}})}}else this.source= +this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search",b)!==false)return this._search(a)},_search:function(a){this.pending++;this.element.addClass("ui-autocomplete-loading");this.source({term:a},this.response)},_response:function(a){if(!this.options.disabled&&a&&a.length){a=this._normalize(a);this._suggest(a);this._trigger("open")}else this.close(); +this.pending--;this.pending||this.element.removeClass("ui-autocomplete-loading")},close:function(a){clearTimeout(this.closing);if(this.menu.element.is(":visible")){this.menu.element.hide();this.menu.deactivate();this._trigger("close",a)}},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(a){if(a.length&&a[0].label&&a[0].value)return a;return d.map(a,function(b){if(typeof b==="string")return{label:b,value:b};return d.extend({label:b.label|| +b.value,value:b.value||b.label},b)})},_suggest:function(a){var b=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(b,a);this.menu.deactivate();this.menu.refresh();b.show();this._resizeMenu();b.position(d.extend({of:this.element},this.options.position));this.options.autoFocus&&this.menu.next(new d.Event("mouseover"))},_resizeMenu:function(){var a=this.menu.element;a.outerWidth(Math.max(a.width("").outerWidth(),this.element.outerWidth()))},_renderMenu:function(a,b){var g=this; +d.each(b,function(c,f){g._renderItem(a,f)})},_renderItem:function(a,b){return d("<li></li>").data("item.autocomplete",b).append(d("<a></a>").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, +"\\$&")},filter:function(a,b){var g=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return g.test(c.label||c.value||c)})}})})(jQuery); +(function(d){d.widget("ui.menu",{_create:function(){var e=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(a){if(d(a.target).closest(".ui-menu-item a").length){a.preventDefault();e.select(a)}});this.refresh()},refresh:function(){var e=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", +-1).mouseenter(function(a){e.activate(a,d(this).parent())}).mouseleave(function(){e.deactivate()})},activate:function(e,a){this.deactivate();if(this.hasScroll()){var b=a.offset().top-this.element.offset().top,g=this.element.scrollTop(),c=this.element.height();if(b<0)this.element.scrollTop(g+b);else b>=c&&this.element.scrollTop(g+b-c+a.height())}this.active=a.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",e,{item:a})},deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id"); +this._trigger("blur");this.active=null}},next:function(e){this.move("next",".ui-menu-item:first",e)},previous:function(e){this.move("prev",".ui-menu-item:last",e)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(e,a,b){if(this.active){e=this.active[e+"All"](".ui-menu-item").eq(0);e.length?this.activate(b,e):this.activate(b,this.element.children(a))}else this.activate(b, +this.element.children(a))},nextPage:function(e){if(this.hasScroll())if(!this.active||this.last())this.activate(e,this.element.children(".ui-menu-item:first"));else{var a=this.active.offset().top,b=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-a-b+d(this).height();return c<10&&c>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(e,g)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active|| +this.last()?":first":":last"))},previousPage:function(e){if(this.hasScroll())if(!this.active||this.first())this.activate(e,this.element.children(".ui-menu-item:last"));else{var a=this.active.offset().top,b=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=d(this).offset().top-a+b-d(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first"));this.activate(e,result)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active|| +this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element[d.fn.prop?"prop":"attr"]("scrollHeight")},select:function(e){this._trigger("selected",e,{item:this.active})}})})(jQuery); +; \ No newline at end of file diff --git a/wolnelektury/static/js/jquery.jqmodal.js b/wolnelektury/static/js/jquery.jqmodal.js index 248bb1931..3aac816e7 100644 --- a/wolnelektury/static/js/jquery.jqmodal.js +++ b/wolnelektury/static/js/jquery.jqmodal.js @@ -1,214 +1,69 @@ /* * jqModal - Minimalist Modaling with jQuery + * (http://dev.iceburg.net/jquery/jqModal/) * - * Copyright (c) 2007 Brice Burgess <bhb@iceburg.net>, http://www.iceburg.net - * Licensed under the MIT License: - * http://www.opensource.org/licenses/mit-license.php - * - * $Version: 2007.??.?? +r12 beta - * Requires: jQuery 1.1.3+ + * Copyright (c) 2007,2008 Brice Burgess <bhb@iceburg.net> + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * $Version: 03/01/2009 +r14 */ (function($) { -/** - * Initialize a set of elements as "modals". Modals typically are popup dialogs, - * notices, modal windows, and image containers. An expando ("_jqm") containing - * the UUID or "serial" of the modal is added to each element. This expando helps - * reference the modal's settings in the jqModal Hash Object (jQuery.jqm.hash) - * - * Accepts a parameter object with the following modal settings; - * - * (Integer) zIndex - Desired z-Index of the modal. This setting does not override (has no effect on) preexisting z-Index styling (set via CSS or inline style). - * (Integer) overlay - [0-100] Translucency percentage (opacity) of the body covering overlay. Set to 0 for NO overlay, and up to 100 for a 100% opaque overlay. - * (String) overlayClass - This class is applied to the body covering overlay. Allows CSS control of the overlay look (tint, background image, etc.). - * (String) closeClass - A close trigger is added to all elements matching this class within the modal. - * (Mixed) trigger - An open trigger is added to all matching elements within the DOM. Trigger can be a selector String, a jQuery collection of elements, a DOM element, or a False boolean. - * (Mixed) ajax - If not false; The URL (string) to load content from via an AJAX request. - * If ajax begins with a "@", the URL is extracted from the requested attribute of the triggering element. - * (Mixed) target - If not false; The element within the modal to load the ajax response (content) into. Allows retention of modal design (e.g. framing and close elements are not overwritten by the AJAX response). - * Target may be a selector string, jQuery collection of elements, or a DOM element -- but MUST exist within (as a child of) the modal. - * (Boolean) modal - If true, user interactivity will be locked to the modal window until closed. - * (Boolean) toTop - If true, modal will be posistioned as a first child of the BODY element when opened, and its DOM posistion restored when closed. This aids in overcoming z-Index stacking order/containment issues where overlay covers whole page *including* modal. - * (Mixed) onShow - User defined callback function fired when modal opened. - * (Mixed) onHide - User defined callback function fired when modal closed. - * (Mixed) onLoad - User defined callback function fired when ajax content loads. - * - * @name jqm - * @param Map options User defined settings for the modal(s). - * @type jQuery - * @cat Plugins/jqModal - */ -$.fn.jqm=function(p){ -var o = { -zIndex: 3000, +$.fn.jqm=function(o){ +var p={ overlay: 50, overlayClass: 'jqmOverlay', closeClass: 'jqmClose', trigger: '.jqModal', -ajax: false, -target: false, -modal: false, -toTop: false, -onShow: false, -onHide: false, -onLoad: false +ajax: F, +ajaxText: '', +target: F, +modal: F, +toTop: F, +onShow: F, +onHide: F, +onLoad: F }; - -// For each element (aka "modal") $.jqm() has been called on; -// IF the _jqm expando exists, return (do nothing) -// ELSE increment serials and add _jqm expando to element ("serialization") -// *AND*... -return this.each(function(){if(this._jqm)return;s++;this._jqm=s; - -// ... Add this element's serial to the jqModal Hash Object -// Hash is globally accessible via jQuery.jqm.hash. It consists of; -// c: {obj} config/options -// a: {bool} active state (true: active/visible, false: inactive/hidden) -// w: {JQ DOM Element} The modal element (window/dialog/notice/etc. container) -// s: {int} The serial number of this modal (same as "H[s].w[0]._jqm") -// t: {DOM Element} The triggering element -// *AND* ... -H[s]={c:$.extend(o,p),a:false,w:$(this).addClass('jqmID'+s),s:s}; - -// ... Attach events to trigger showing of this modal -o.trigger&&$(this).jqmAddTrigger(o.trigger); +return this.each(function(){if(this._jqm)return H[this._jqm].c=$.extend({},H[this._jqm].c,o);s++;this._jqm=s; +H[s]={c:$.extend(p,$.jqm.params,o),a:F,w:$(this).addClass('jqmID'+s),s:s}; +if(p.trigger)$(this).jqmAddTrigger(p.trigger); });}; -// Adds behavior to triggering elements via the hide-show (HS) function. -// -$.fn.jqmAddClose=function(e){return HS(this,e,'jqmHide');}; -$.fn.jqmAddTrigger=function(e){return HS(this,e,'jqmShow');}; - -// Hide/Show a modal -- first check if it is already shown or hidden via the toggle state (H[{modal serial}].a) -$.fn.jqmShow=function(t){return this.each(function(){!H[this._jqm].a&&$.jqm.open(this._jqm,t)});}; -$.fn.jqmHide=function(t){return this.each(function(){H[this._jqm].a&&$.jqm.close(this._jqm,t)});}; +$.fn.jqmAddClose=function(e){return hs(this,e,'jqmHide');}; +$.fn.jqmAddTrigger=function(e){return hs(this,e,'jqmShow');}; +$.fn.jqmShow=function(t){return this.each(function(){t=t||window.event;$.jqm.open(this._jqm,t);});}; +$.fn.jqmHide=function(t){return this.each(function(){t=t||window.event;$.jqm.close(this._jqm,t)});}; $.jqm = { hash:{}, - -// Function is executed by $.jqmShow to show a modal -// s: {INT} serial of modal -// t: {DOM Element} the triggering element - -// set local shortcuts -// h: {obj} this Modal's "hash" -// c: {obj} (h.c) config/options -// cc: {STR} closing class ('.'+h.c.closeClass) -// z: {INT} z-Index of Modal. If the Modal (h.w) has the z-index style set it will use this value before defaulting to the one passed in the config (h.c.zIndex) -// o: The overlay object -// mark this modal as active (h.a === true) -// set the triggering object (h.t) and the modal's z-Index. -open:function(s,t){var h=H[s],c=h.c,cc='.'+c.closeClass,z=/^\d+$/.test(h.w.css('z-index'))&&h.w.css('z-index')||c.zIndex,o=$('<div></div>').css({height:'100%',width:'100%',position:'fixed',left:0,top:0,'z-index':z-1,opacity:c.overlay/100});h.t=t;h.a=true;h.w.css('z-index',z); - - // IF the modal argument was passed as true; - // Bind the Keep Focus Function if no other Modals are open (!A[0]), - // Add this modal to the opened modals stack (A) for nested modal support, - // and Mark overlay to show wait cursor when mouse hovers over it. - if(c.modal) {!A[0]&&F('bind');A.push(s);o.css('cursor','wait');} - - // ELSE IF an overlay was requested (translucency set greater than 0); - // Attach a Close event to overlay to hide modal when overlay is clicked. +open:function(s,t){var h=H[s],c=h.c,cc='.'+c.closeClass,z=(parseInt(h.w.css('z-index'))),z=(z>0)?z:3000,o=$('<div></div>').css({height:'100%',width:'100%',position:'fixed',left:0,top:0,'z-index':z-1,opacity:c.overlay/100});if(h.a)return F;h.t=t;h.a=true;h.w.css('z-index',z); + if(c.modal) {if(!A[0])L('bind');A.push(s);} else if(c.overlay > 0)h.w.jqmAddClose(o); + else o=F; - // ELSE disable the overlay - else o=false; + h.o=(o)?o.addClass(c.overlayClass).prependTo('body'):F; + if(ie6){$('html,body').css({height:'100%',width:'100%'});if(o){o=o.css({position:'absolute'})[0];for(var y in {Top:1,Left:1})o.style.setExpression(y.toLowerCase(),"(_=(document.documentElement.scroll"+y+" || document.body.scroll"+y+"))+'px'");}} - // Add the Overlay to BODY if not disabled. - h.o=(o)?o.addClass(c.overlayClass).prependTo('body'):false; - - // IF IE6; - // Set the Overlay to 100% height/width, and fix-position it via JS workaround - if(ie6&&$('html,body').css({height:'100%',width:'100%'})&&o){o=o.css({position:'absolute'})[0];for(var y in {Top:1,Left:1})o.style.setExpression(y.toLowerCase(),"(_=(document.documentElement.scroll"+y+" || document.body.scroll"+y+"))+'px'");} - - // IF the modal's content is to be loaded via ajax; - // determine the target element {JQ} to recieve content (r), - // determine the URL {STR} to load content from (u) if(c.ajax) {var r=c.target||h.w,u=c.ajax,r=(typeof r == 'string')?$(r,h.w):$(r),u=(u.substr(0,1) == '@')?$(t).attr(u.substring(1)):u; + r.html(c.ajaxText).load(u,function(){if(c.onLoad)c.onLoad.call(this,h);if(cc)h.w.jqmAddClose($(cc,h.w));e(h);});} + else if(cc)h.w.jqmAddClose($(cc,h.w)); - // Load the Content (and once loaded); - // Fire the onLoad callback (if exists), - // Attach closing events to elements inside the modal that match the closingClass, - // and Execute the jqModal default Open Callback - r.load(u,function(){c.onLoad&&c.onLoad.call(this,h);cc&&h.w.jqmAddClose($(cc,h.w));O(h);});} - - // ELSE the modal content is NOT to be loaded via ajax; - // Attach closing events to elements inside the modal that match the closingClass - else cc&&h.w.jqmAddClose($(cc,h.w)); - - // IF toTop was passed and an overlay exists; - // Remember the DOM posistion of the modal by inserting a tagged (matching serial) <SPAN> before the modal - // Move the Modal from its current position to a first child of the body tag (after the overlay) - c.toTop&&h.o&&h.w.before('<span id="jqmP'+h.w[0]._jqm+'"></span>').insertAfter(h.o); - - // Execute user defined onShow callback, or else show (make visible) the modal. - // Execute the jqModal default Open Callback. - // Return false to prevent trigger click from being followed. - (c.onShow)?c.onShow(h):h.w.show();O(h);return false; - + if(c.toTop&&h.o)h.w.before('<span id="jqmP'+h.w[0]._jqm+'"></span>').insertAfter(h.o); + (c.onShow)?c.onShow(h):h.w.show();e(h);return F; }, - -// Function is executed by $.jqmHide to hide a modal - // mark this modal as inactive (h.a === false) -close:function(s){var h=H[s];h.a=false; - // If modal, remove from modal stack. - // If no modals in modal stack, unbind the Keep Focus Function - if(h.c.modal){A.pop();!A[0]&&F('unbind');} - - // IF toTop was passed and an overlay exists; - // Move modal back to its previous ("remembered") position. - h.c.toTop&&h.o&&$('#jqmP'+h.w[0]._jqm).after(h.w).remove(); - - // Execute user defined onHide callback, or else hide (make invisible) the modal and remove the overlay. - if(h.c.onHide)h.c.onHide(h);else{h.w.hide()&&h.o&&h.o.remove()}return false; -}}; - -// set jqModal scope shortcuts; -// s: {INT} serials placeholder -// H: {HASH} shortcut to jqModal Hash Object -// A: {ARRAY} Array of active/visible modals -// ie6: {bool} True if client browser is Internet Explorer 6 -// i: {JQ, DOM Element} iframe placeholder used to prevent active-x bleedthrough in IE6 -// NOTE: It is important to include the iframe styling (iframe.jqm) in your CSS! -// *AND* ... -var s=0,H=$.jqm.hash,A=[],ie6=$.browser.msie&&($.browser.version == "6.0"),i=$('<iframe src="javascript:false;document.write(\'\');" class="jqm"></iframe>').css({opacity:0}), - -// O: The jqModal default Open Callback; -// IF ie6; Add the iframe to the overlay (if overlay exists) OR to the modal (if an iframe doesn't already exist from a previous opening) -// Execute the Modal Focus Function -O=function(h){if(ie6)h.o&&h.o.html('<p style="width:100%;height:100%"/>').prepend(i)||(!$('iframe.jqm',h.w)[0]&&h.w.prepend(i)); f(h);}, - -// f: The Modal Focus Function; -// Attempt to focus the first visible input within the modal -f=function(h){try{$(':input:visible',h.w)[0].focus();}catch(e){}}, - -// F: The Keep Focus Function; -// Binds or Unbinds (t) the Focus Examination Function to keypresses and clicks -F=function(t){$()[t]("keypress",x)[t]("keydown",x)[t]("mousedown",x);}, - -// x: The Focus Examination Function; -// Fetch the current modal's Hash as h (supports nested modals) -// Determine if the click/press falls within the modal. If not (r===true); -// call the Modal Focus Function and prevent click/press follow-through (return false [!true]) -// ELSE if so (r===false); follow event (return true [!false]) -x=function(e){var h=H[A[A.length-1]],r=(!$(e.target).parents('.jqmID'+h.s)[0]);r&&f(h);return !r;}, - -// hide-show function; assigns click events to trigger elements that -// hide, show, or hide AND show modals. - -// Expandos (jqmShow and/or jqmHide) are added to all trigger elements. -// These Expandos hold an array of modal serials {INT} to show or hide. - -// w: {DOM Element} The modal element (window/dialog/notice/etc. container) -// e: {DOM Elemet||jQ Selector String} The triggering element -// y: {String} Type (jqmHide||jqmShow) - -// s: {array} the serial number of passed modals, calculated below; -HS=function(w,e,y){var s=[];w.each(function(){s.push(this._jqm)}); - -// for each triggering element attach the jqmHide or jqmShow expando (y) -// or else expand the expando with the current serial array - $(e).each(function(){if(this[y])$.extend(this[y],s); - - // Assign a click event on the trigger element which examines the element's - // jqmHide/Show expandos and attempts to execute $.jqmHide/Show on matching modals - else{this[y]=s;$(this).click(function(){for(var i in {jqmShow:1,jqmHide:1})for(var s in this[i])if(H[this[i][s]])H[this[i][s]].w[i](this);return false;});}});return w;}; +close:function(s){var h=H[s];if(!h.a)return F;h.a=F; + if(A[0]){A.pop();if(!A[0])L('unbind');} + if(h.c.toTop&&h.o)$('#jqmP'+h.w[0]._jqm).after(h.w).remove(); + if(h.c.onHide)h.c.onHide(h);else{h.w.hide();if(h.o)h.o.remove();} return F; +}, +params:{}}; +var s=0,H=$.jqm.hash,A=[],ie6=$.browser.msie&&($.browser.version == "6.0"),F=false, +i=$('<iframe src="javascript:false;document.write(\'\');" class="jqm"></iframe>').css({opacity:0}), +e=function(h){if(ie6)if(h.o)h.o.html('<p style="width:100%;height:100%"/>').prepend(i);else if(!$('iframe.jqm',h.w)[0])h.w.prepend(i); f(h);}, +f=function(h){try{$(':input:visible',h.w)[0].focus();}catch(_){}}, +L=function(t){$()[t]("keypress",m)[t]("keydown",m)[t]("mousedown",m);}, +m=function(e){var h=H[A[A.length-1]],r=(!$(e.target).parents('.jqmID'+h.s)[0]);if(r)f(h);return !r;}, +hs=function(w,t,c){return w.each(function(){var s=this._jqm;$(t).each(function() { + if(!this[c]){this[c]=[];$(this).click(function(){for(var i in {jqmShow:1,jqmHide:1})for(var s in this[i])if(H[this[i][s]])H[this[i][s]].w[i](this);return F;});}this[c].push(s);});});}; })(jQuery); \ No newline at end of file diff --git a/wolnelektury/static/js/locale.js b/wolnelektury/static/js/locale.js new file mode 100755 index 000000000..e765548ad --- /dev/null +++ b/wolnelektury/static/js/locale.js @@ -0,0 +1,29 @@ +var LOCALE_TEXTS = { + "pl": { + "Loading": "Ładowanie" + }, + "de": { + "Loading": "Herunterladen" + }, + "fr": { + "Loading": "Chargement" + }, + "en": { + "Loading": "Loading" + }, + "ru": { + "Loading": "Загрузка" + }, + "es": { + "Loading": "Cargando" + }, + "lt":{ + "Loading": "Krovimas" + }, + "uk":{ + "Loading": "Завантажується" + } +} +function gettext(text) { + return LOCALE_TEXTS[LANGUAGE_CODE][text]; +} \ No newline at end of file diff --git a/wolnelektury/static/js/pdcounter.js b/wolnelektury/static/js/pdcounter.js new file mode 100755 index 000000000..00c074280 --- /dev/null +++ b/wolnelektury/static/js/pdcounter.js @@ -0,0 +1,36 @@ +(function($) { + $(function() { + + + $('#countdown').each(function() { + var $this = $(this); + + var serverTime = function() { + var time = null; + $.ajax({url: '/zegar/', + async: false, dataType: 'text', + success: function(text) { + time = new Date(text); + }, error: function(http, message, exc) { + time = new Date(); + }}); + return time; + } + + if (LANGUAGE_CODE != 'en') { + $.countdown.setDefaults($.countdown.regional[LANGUAGE_CODE]); + } + else { + $.countdown.setDefaults($.countdown.regional['']); + } + + var d = new Date($this.attr('data-year'), 0, 1); + function re() {location.reload()}; + $this.countdown({until: d, format: 'ydHMS', serverSync: serverTime, + onExpiry: re, alwaysExpire: true}); + + }); + + + }); +})(jQuery) \ No newline at end of file diff --git a/wolnelektury/static/js/player.js b/wolnelektury/static/js/player.js new file mode 100755 index 000000000..fea845029 --- /dev/null +++ b/wolnelektury/static/js/player.js @@ -0,0 +1,33 @@ +(function($) { + $(function() { + + + $("#jplayer").jPlayer({ + swfPath: "/static/jplayer/", + solution: "html,flash", + supplied: $(this).attr('data-supplied'), + + ready: function() { + var player = $(this); + var setMedia = function(elem) { + var li = $(elem).parent(); + $('.jp-playlist-current').removeClass('jp-playlist-current'); + $(li).addClass('jp-playlist-current'); + var media = {} + + $('.mp3', li).each(function() {media['mp3'] = $(this).attr('href')}); + $('.ogg', li).each(function() {media['oga'] = $(this).attr('href')}); + + return player.jPlayer("setMedia", media); + }; + setMedia($('.play').first()).jPlayer("play"); + + $('.play').click(function() { + setMedia(this).jPlayer("play"); + }); + } + }); + + + }); +})(jQuery) \ No newline at end of file diff --git a/wolnelektury/static/js/search.js b/wolnelektury/static/js/search.js new file mode 100644 index 000000000..47b6665a6 --- /dev/null +++ b/wolnelektury/static/js/search.js @@ -0,0 +1,49 @@ + +var __bind = function (self, fn) { + return function() { fn.apply(self, arguments); }; +}; + +(function($){ + $.widget("wl.search", { + options: { + minLength: 0, + }, + + _create: function() { + var opts = { + minLength: this.options.minLength, + select: __bind(this, this.enter), + focus: function() { return false; }, + source: this.element.data('source'), + }; + this.element.autocomplete(opts).data("autocomplete")._renderItem = __bind(this, this.render_item); + }, + + enter: function(event, ui) { + if (ui.item.url != undefined) { + location.href = ui.item.url; + } else { + this.element.closest('form').submit(); + } + }, + + render_item: function (ul, item) { + return $("<li></li>").data('item.autocomplete', item) + .append('<a href="'+item.url+'">'+item.label+ ' ('+item.category+')</a>') + .appendTo(ul); + }, + + destroy: function() { + + }, + + + + }); + + $(function() { + $("#search").search().labelify({labelledClass: "blur"}); + }); + + +})(jQuery); diff --git a/wolnelektury/static/js/sponsors.js b/wolnelektury/static/js/sponsors.js new file mode 100755 index 000000000..7674379fe --- /dev/null +++ b/wolnelektury/static/js/sponsors.js @@ -0,0 +1,8 @@ +(function($) { + $(function() { + + $('.sponsor-logos').cycle({timeout: 3000}); + + }); +})(jQuery) + diff --git a/wolnelektury/static/opensearch.xml b/wolnelektury/static/opensearch.xml index 9cc056541..a603966ab 100644 --- a/wolnelektury/static/opensearch.xml +++ b/wolnelektury/static/opensearch.xml @@ -11,7 +11,7 @@ <Image height="16" width="16" type="image/x-icon">http://www.wolnelektury.pl/static/img/favicon.ico</Image> <Image height="64" width="64" type="image/png">http://www.wolnelektury.pl/static/img/wl_icon_64.png</Image> <Url type="application/atom+xml;profile=opds-catalog" - template="http://www.wolnelektury.pl/opds/search/?q={searchTerms}" /> + template="http://www.wolnelektury.pl/opds/search/?q={searchTerms}&author={atom:author}&translator={atom:contributor}&title={atom:title}" /> <Url type="text/html" method="GET" template="http://www.wolnelektury.pl/katalog/szukaj?q={searchTerms}" /> <Url type="application/x-suggestions+json" method="GET" template="http://www.wolnelektury.pl/katalog/jtags?mozhint=1&q={searchTerms}" /> <moz:SearchForm>http://www.wolnelektury.pl/katalog/</moz:SearchForm> diff --git a/wolnelektury/static/sponsors/css/footer_admin.css b/wolnelektury/static/sponsors/css/footer_admin.css index 1277bd2d3..fe19d30c9 100644 --- a/wolnelektury/static/sponsors/css/footer_admin.css +++ b/wolnelektury/static/sponsors/css/footer_admin.css @@ -66,3 +66,8 @@ background-color: #EEE; cursor: default; } + +.sponsors-sponsor img { + max-width: 120px; + max-height: 120px; +} diff --git a/wolnelektury/templates/404.html b/wolnelektury/templates/404.html index 50a56ea7e..8083d2865 100644 --- a/wolnelektury/templates/404.html +++ b/wolnelektury/templates/404.html @@ -1,27 +1,19 @@ +{% extends "base.html" %} {% load i18n %} -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" lang="pl" xml:lang="pl"> -<head> -<title>404 - {% trans "Page does not exist" %} - WolneLektury.pl - - - - - - - - -

{% trans "Page does not exist" %}

+ + +{% block titleextra %}404 - {% trans "Page does not exist" %}{% endblock %} + + +{% block body %} + +

{% trans "Page not found" %}

+ +

{% trans "We are sorry, but this page does not exist. Please check if you entered correct address or go to "%} {% trans "main page" %}.

- - - - \ No newline at end of file + + +{% endblock body %} diff --git a/wolnelektury/templates/auth/login.html b/wolnelektury/templates/auth/login.html deleted file mode 100644 index a168ff7f8..000000000 --- a/wolnelektury/templates/auth/login.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block title %}{% trans "Sign in" %} / {% trans "Register on"%} WolneLektury.pl{% endblock %} - -{% block body %} -

{% trans "Sign in" %} / {% trans "Register"%}

-
-

  • {{ search_form.q }}
  • {% trans "or" %} {% trans "return to main page" %}

    -
    -
    -

    {% trans "Sign in" %}

    -
      - {{ form.as_ul }} -
    1. -
    -

    -
    - -
    -

    {% trans "Register" %}

    - -

    -
    -{% endblock %} diff --git a/wolnelektury/templates/base.html b/wolnelektury/templates/base.html index 061197f9b..23cb46b56 100644 --- a/wolnelektury/templates/base.html +++ b/wolnelektury/templates/base.html @@ -1,82 +1,142 @@ - {% load i18n chunks compressed catalogue_tags sponsor_tags %} + {% load i18n compressed catalogue_tags sponsor_tags %} + {% load reporting_stats %} - {% block title %}WolneLektury.pl{% endblock %} + {% block title %}{% trans "Wolne Lektury" %} :: + {% block titleextra %}{% endblock %}{% endblock %} {% compressed_css "all" %} - - {% compressed_js "jquery" %} - {% compressed_js "all" %} + {% block extrahead %} {% endblock %} - + {% block bodycontent %} -
    - {% chunk "top-message" %} -
    +