merge search into pretty branch
authorMarcin Koziej <marcin.koziej@nowoczesnapolska.org.pl>
Wed, 21 Dec 2011 09:03:56 +0000 (10:03 +0100)
committerMarcin Koziej <marcin.koziej@nowoczesnapolska.org.pl>
Wed, 21 Dec 2011 09:03:56 +0000 (10:03 +0100)
138 files changed:
apps/ajaxable/__init__.py [new file with mode: 0644]
apps/ajaxable/models.py [new file with mode: 0644]
apps/ajaxable/templates/ajaxable/form.html [new file with mode: 0755]
apps/ajaxable/templates/ajaxable/form_on_page.html [new file with mode: 0755]
apps/ajaxable/utils.py [new file with mode: 0755]
apps/api/handlers.py
apps/api/management/commands/mobileinit.py
apps/api/tests.py
apps/api/urls.py
apps/catalogue/admin.py
apps/catalogue/forms.py
apps/catalogue/management/commands/importbooks.py
apps/catalogue/management/commands/pack.py
apps/catalogue/migrations/0017_auto__add_field_book_language__del_unique_book_slug__add_unique_book_s.py [new file with mode: 0644]
apps/catalogue/migrations/0018_auto__del_filerecord.py [new file with mode: 0644]
apps/catalogue/migrations/0019_auto__add_field_book_cover.py [new file with mode: 0644]
apps/catalogue/migrations/0020_auto__del_field_tag_main_page.py [new file with mode: 0644]
apps/catalogue/migrations/0021_build_covers.py [new file with mode: 0644]
apps/catalogue/models.py
apps/catalogue/tasks.py [new file with mode: 0755]
apps/catalogue/templatetags/catalogue_tags.py
apps/catalogue/test_utils.py
apps/catalogue/tests/book_import.py
apps/catalogue/tests/files/fraszki.xml
apps/catalogue/urls.py
apps/catalogue/utils.py
apps/catalogue/views.py
apps/dictionary/templates/dictionary/note_list.html
apps/dictionary/views.py
apps/infopages/admin.py
apps/infopages/migrations/0002_auto__del_field_infopage_page_title__del_field_infopage_page_title_en_.py [new file with mode: 0644]
apps/infopages/models.py
apps/infopages/templates/infopages/infopage.html [new file with mode: 0755]
apps/infopages/templates/infopages/on_main.html [new file with mode: 0755]
apps/infopages/templatetags/__init__.py [new file with mode: 0644]
apps/infopages/templatetags/infopages_tags.py [new file with mode: 0755]
apps/infopages/urls.py [new file with mode: 0755]
apps/infopages/views.py
apps/lesmianator/urls.py
apps/lesmianator/views.py
apps/lessons/views.py
apps/opds/views.py
apps/pdcounter/urls.py [deleted file]
apps/pdcounter/views.py
apps/picture/__init__.py [new file with mode: 0644]
apps/picture/admin.py [new file with mode: 0644]
apps/picture/forms.py [new file with mode: 0644]
apps/picture/models.py [new file with mode: 0644]
apps/picture/tests/__init__.py [new file with mode: 0644]
apps/picture/tests/files/kandinsky-composition-viii.png [new file with mode: 0644]
apps/picture/tests/files/kandinsky-composition-viii.xml [new file with mode: 0644]
apps/picture/views.py [new file with mode: 0644]
apps/reporting/__init__.py [new file with mode: 0644]
apps/reporting/models.py [new file with mode: 0644]
apps/reporting/templates/reporting/catalogue.texml [new file with mode: 0755]
apps/reporting/templates/reporting/main.html [new file with mode: 0755]
apps/reporting/templatetags/__init__.py [new file with mode: 0755]
apps/reporting/templatetags/reporting_stats.py [new file with mode: 0755]
apps/reporting/urls.py [new file with mode: 0755]
apps/reporting/utils.py [new file with mode: 0755]
apps/reporting/views.py [new file with mode: 0644]
apps/sponsors/models.py
apps/sponsors/processors.py [deleted file]
apps/sponsors/static/sponsors/css/footer_admin.css
apps/sponsors/widgets.py
apps/stats/templates/stats/main.html [deleted file]
apps/stats/templatetags/__init__.py [deleted file]
apps/stats/templatetags/stats.py [deleted file]
apps/stats/urls.py [deleted file]
apps/stats/utils.py
apps/stats/views.py [deleted file]
apps/suggest/forms.py
apps/suggest/templates/publishing_suggest.html
apps/suggest/templates/publishing_suggest_full.html [deleted file]
apps/suggest/templates/suggest.html [deleted file]
apps/suggest/urls.py
apps/suggest/views.py
fabfile.py
lib/librarian
requirements.txt
scripts/make-tags
scripts/setmainpage.py [deleted file]
wolnelektury/settings.py
wolnelektury/static/css/base.css [new file with mode: 0755]
wolnelektury/static/css/book_box.css [new file with mode: 0755]
wolnelektury/static/css/catalogue.css [new file with mode: 0755]
wolnelektury/static/css/dialogs.css [new file with mode: 0755]
wolnelektury/static/css/header.css [new file with mode: 0755]
wolnelektury/static/css/main_page.css [new file with mode: 0755]
wolnelektury/static/css/master.css
wolnelektury/static/css/sponsors.css
wolnelektury/static/fonts/WL-Nav.ttf [new file with mode: 0644]
wolnelektury/static/fonts/WL.eot [new file with mode: 0644]
wolnelektury/static/fonts/WL.ttf [new file with mode: 0644]
wolnelektury/static/img/bg-header.png [new file with mode: 0644]
wolnelektury/static/js/catalogue.js
wolnelektury/static/js/dialogs.js [new file with mode: 0755]
wolnelektury/static/js/jquery.jqmodal.js
wolnelektury/static/js/locale.js [new file with mode: 0755]
wolnelektury/static/js/pdcounter.js [new file with mode: 0755]
wolnelektury/static/js/sponsors.js [new file with mode: 0755]
wolnelektury/static/sponsors/css/footer_admin.css
wolnelektury/templates/auth/login.html [deleted file]
wolnelektury/templates/base.html
wolnelektury/templates/catalogue/audiobook_list.html
wolnelektury/templates/catalogue/book_detail.html
wolnelektury/templates/catalogue/book_fragments.html
wolnelektury/templates/catalogue/book_list.html
wolnelektury/templates/catalogue/book_mini_box.html [new file with mode: 0755]
wolnelektury/templates/catalogue/book_sets.html
wolnelektury/templates/catalogue/book_short.html
wolnelektury/templates/catalogue/breadcrumbs.html [deleted file]
wolnelektury/templates/catalogue/catalogue.html [new file with mode: 0644]
wolnelektury/templates/catalogue/daisy_list.html
wolnelektury/templates/catalogue/differentiate_tags.html
wolnelektury/templates/catalogue/folded_tag_list.html [deleted file]
wolnelektury/templates/catalogue/main_page.html [deleted file]
wolnelektury/templates/catalogue/picture_detail.html [new file with mode: 0644]
wolnelektury/templates/catalogue/picture_list.html [new file with mode: 0644]
wolnelektury/templates/catalogue/search_form.html [deleted file]
wolnelektury/templates/catalogue/search_multiple_hits.html
wolnelektury/templates/catalogue/search_no_hits.html
wolnelektury/templates/catalogue/search_too_short.html
wolnelektury/templates/catalogue/tag_list.html
wolnelektury/templates/catalogue/tagged_object_list.html
wolnelektury/templates/catalogue/user_shelves.html
wolnelektury/templates/info/base.html [deleted file]
wolnelektury/templates/info/join_us.html
wolnelektury/templates/lesmianator/lesmianator.html
wolnelektury/templates/lesmianator/poem.html
wolnelektury/templates/lessons/document_list.html
wolnelektury/templates/main_page.html [new file with mode: 0755]
wolnelektury/templates/pdcounter/author_detail.html
wolnelektury/templates/pdcounter/book_stub_detail.html
wolnelektury/templates/pdcounter/pd_counter.html [deleted file]
wolnelektury/translation.py
wolnelektury/urls.py
wolnelektury/views.py [new file with mode: 0755]

diff --git a/apps/ajaxable/__init__.py b/apps/ajaxable/__init__.py
new file mode 100644 (file)
index 0000000..a010543
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/apps/ajaxable/templates/ajaxable/form.html b/apps/ajaxable/templates/ajaxable/form.html
new file mode 100755 (executable)
index 0000000..d8f0036
--- /dev/null
@@ -0,0 +1,9 @@
+{% load i18n %}
+<h1>{{ title }}</h1>
+
+<form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8" class="cuteform">
+<ol>
+    {{ form.as_ul }}
+    <li><input type="submit" value="{{ submit }}"/></li>
+</ol>
+</form>
\ 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 (executable)
index 0000000..61175d5
--- /dev/null
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block titleextra %}{{ title }}{% endblock %}
+
+{% block body %}
+
+    {% include ajax_template %}
+
+    {% if response_data.message %}
+        <p>{{ response_data.message }}</p>
+    {% endif %}
+
+{% endblock %}
diff --git a/apps/ajaxable/utils.py b/apps/ajaxable/utils.py
new file mode 100755 (executable)
index 0000000..14b5dfc
--- /dev/null
@@ -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"
+    # set to redirect after succesful ajax-less post
+    submit = _('Send')
+    redirect = None
+    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 is not None:
+                    return HttpResponseRedirect(urlquote_plus(
+                                redirect, safe='/?='))
+                response_data = {'success': True, 'message': self.success_message}
+            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)
index 8f06dea..f99dd90 100644 (file)
@@ -15,6 +15,7 @@ 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 stats.utils import piwik_track
 
@@ -93,15 +94,18 @@ 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. """
+    def read(self, request, book):
+        """ Returns details of a book, identified by a slug and lang. """
+        kwargs = Book.split_urlid(book)
+        if not kwargs:
+            return rc.NOT_FOUND
 
         try:
-            return Book.objects.get(slug=slug)
+            return Book.objects.get(**kwargs)
         except Book.DoesNotExist:
             return rc.NOT_FOUND
 
@@ -122,7 +126,7 @@ class AnonymousBooksHandler(AnonymousBaseHandler):
     @classmethod
     def href(cls, book):
         """ Returns an URI for a Book in the API. """
-        return API_BASE + reverse("api_book", args=[book.slug])
+        return API_BASE + reverse("api_book", args=[book.urlid()])
 
     @classmethod
     def url(cls, book):
@@ -202,7 +206,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))
 
 
@@ -246,9 +250,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
@@ -265,11 +268,18 @@ class FragmentDetailHandler(BaseHandler):
     fields = ['book', 'anchor', 'text', 'url', 'themes']
 
     @piwik_track
-    def read(self, request, slug, anchor):
+    def read(self, request, book, anchor):
         """ Returns details of a fragment, identified by book slug and anchor. """
+        kwargs = Book.split_urlid(book)
+        if not kwargs:
+            return rc.NOT_FOUND
+
+        fragment_kwargs = {}
+        for field, value in kwargs.items():
+            fragment_kwargs['book__' + field] = value
 
         try:
-            return Fragment.objects.get(book__slug=slug, anchor=anchor)
+            return Fragment.objects.get(anchor=anchor, **fragment_kwargs)
         except Fragment.DoesNotExist:
             return rc.NOT_FOUND
 
@@ -306,7 +316,7 @@ 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.urlid(), fragment.anchor])
 
     @classmethod
     def url(cls, fragment):
@@ -354,8 +364,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',
@@ -372,7 +381,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] = {
@@ -380,7 +389,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({
@@ -509,7 +518,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:
@@ -572,3 +581,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('catalogue.add_book'):
+            return rc.FORBIDDEN
+
+        data = json.loads(request.POST.get('data'))
+        form = BookImportForm(data)
+        if form.is_valid():
+            form.save()
+            return rc.CREATED
+        else:
+            return rc.NOT_FOUND
index 658b177..2abbfb3 100755 (executable)
@@ -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)
index 2c2e51c..74417ac 100644 (file)
@@ -8,6 +8,13 @@ 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
+import picture.tests
+from django.core.files.uploadedfile import SimpleUploadedFile
+from os import path
 
 
 class ApiTest(TestCase):
@@ -135,3 +142,21 @@ 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)
index 2d92eba..60b2064 100644 (file)
@@ -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,10 +47,10 @@ urlpatterns = patterns(
 
 
     # objects details
-    url(r'^books/(?P<slug>[a-z0-9-]+)/$', book_resource, name="api_book"),
+    url(r'^books/(?P<book>%s)/$' % Book.URLID_RE, book_resource, name="api_book"),
     url(r'^(?P<category>[a-z0-9-]+)/(?P<slug>[a-z0-9-]+)/$',
         tag_resource, name="api_tag"),
-    url(r'^books/(?P<slug>[a-z0-9-]+)/fragments/(?P<anchor>[a-z0-9-]+)/$',
+    url(r'^books/(?P<book>%s)/fragments/(?P<anchor>[a-z0-9-]+)/$' % Book.URLID_RE,
         fragment_resource, name="api_fragment"),
 
     # books by tags
@@ -62,4 +63,7 @@ urlpatterns = patterns(
 
     # tags by category
     url(r'^(?P<category>[a-z0-9-]+)/$', tag_list_resource),
+
+    # picture by slug
+    url(r'^pictures/$', picture_resource)
 )
index 88c985f..7ef2aca 100644 (file)
@@ -10,7 +10,7 @@ from catalogue.models import Tag, Book, Fragment, BookMedia
 
 
 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',)
index 92f50ed..b7bf249 100644 (file)
@@ -3,7 +3,6 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import forms
-from django.core.files.base import ContentFile
 from django.utils.translation import ugettext_lazy as _
 from slughifi import slughifi
 
@@ -17,6 +16,8 @@ class BookImportForm(forms.Form):
     book_xml = forms.CharField(required=False)
 
     def clean(self):
+        from django.core.files.base import ContentFile
+
         if not self.cleaned_data['book_xml_file']:
             if self.cleaned_data['book_xml']:
                 self.cleaned_data['book_xml_file'] = \
@@ -60,7 +61,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
         )
@@ -82,21 +83,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
+
index 4ea0fd3..b6ddc55 100644 (file)
@@ -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):
@@ -32,10 +33,36 @@ class Command(BaseCommand):
             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'))
+        fileid = book.fileid()
+        for ebook_format in Book.ebook_formats:
+            if os.path.isfile(file_base + '.' + ebook_format):
+                getattr(book, '%s_file' % ebook_format).save(
+                    '%s.%s' % (fileid, 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
 
@@ -44,13 +71,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.
@@ -85,35 +113,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'),
-                                                  search_index=options.get('search_index'))
+                        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
 
index c75f092..280c0f6 100755 (executable)
@@ -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_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 (file)
index 0000000..6d1edcf
--- /dev/null
@@ -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 (file)
index 0000000..66a6542
--- /dev/null
@@ -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 (file)
index 0000000..259d935
--- /dev/null
@@ -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 (file)
index 0000000..e9f7794
--- /dev/null
@@ -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 (file)
index 0000000..319decb
--- /dev/null
@@ -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']
index 1a2e8f8..8fb2109 100644 (file)
@@ -2,16 +2,17 @@
 # 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.core.files import File
 from django.template.loader import render_to_string
+from django.utils.datastructures import SortedDict
 from django.utils.safestring import mark_safe
 from django.utils.translation import get_language
 from django.core.urlresolvers import reverse
@@ -22,14 +23,13 @@ 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 ExistingFile, ORMDocProvider, create_zip, remove_zip
+from catalogue.utils import create_zip, split_tags
+from catalogue.tasks import touch_tag
+from shutil import copy
+from glob import glob
+import re
+from os import path
 
-from librarian import dcparser, html, epub, NoDublinCore
-import mutagen
-from mutagen import id3
-from slughifi import slughifi
-from sortify import sortify
-from os import unlink
 
 import search
 
@@ -43,13 +43,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
 
@@ -70,7 +63,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)
@@ -115,25 +107,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):
@@ -177,26 +166,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):
-        # 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
+    return lambda *args: get_dynamic_path(*args, ext=ext, maxlen=maxlen)
+
+
+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.fileid(), 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.fileid(),)
+    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,6 +265,9 @@ class BookMedia(models.Model):
         verbose_name_plural = _('book media')
 
     def save(self, *args, **kwargs):
+        from slughifi import slughifi
+        from catalogue.utils import ExistingFile, remove_zip
+
         try:
             old = BookMedia.objects.get(pk=self.pk)
         except BookMedia.DoesNotExist, e:
@@ -225,7 +280,7 @@ class BookMedia(models.Model):
         super(BookMedia, self).save(*args, **kwargs)
 
         # remove the zip package for book with modified media
-        remove_zip(self.book.slug)
+        remove_zip(self.book.fileid())
 
         extra_info = self.get_extra_info_value()
         extra_info.update(self.read_meta())
@@ -237,6 +292,8 @@ class BookMedia(models.Model):
         """
             Reads some metadata from the audiobook.
         """
+        import mutagen
+        from mutagen import id3
 
         artist_name = director_name = project = funded_by = ''
         if self.type == 'mp3':
@@ -269,6 +326,8 @@ class BookMedia(models.Model):
         """
             Reads source file SHA1 from audiobok metadata.
         """
+        import mutagen
+        from mutagen import id3
 
         if filetype == 'mp3':
             try:
@@ -290,7 +349,9 @@ 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)
+    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)
@@ -300,19 +361,27 @@ 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)
     tags     = managers.TagDescriptor(Tag)
 
     html_built = django.dispatch.Signal()
+    published = django.dispatch.Signal()
+
+    URLID_RE = r'[a-z0-9-]+(?:/[a-z]{3})?'
+    FILEID_RE = r'[a-z0-9-]+(?:_[a-z]{3})?'
 
     class AlreadyExists(Exception):
         pass
 
     class Meta:
+        unique_together = [['slug', 'language']]
         ordering = ('sort_key',)
         verbose_name = _('book')
         verbose_name_plural = _('books')
@@ -320,7 +389,45 @@ class Book(models.Model):
     def __unicode__(self):
         return self.title
 
+    def urlid(self, sep='/'):
+        stem = self.slug
+        if self.language != settings.CATALOGUE_DEFAULT_LANGUAGE:
+            stem += sep + self.language
+        return stem
+
+    def fileid(self):
+        return self.urlid('_')
+
+    @staticmethod
+    def split_urlid(urlid, sep='/', default_lang=settings.CATALOGUE_DEFAULT_LANGUAGE):
+        """Splits a URL book id into slug and language code.
+        
+        Returns a dictionary usable i.e. for object lookup, or None.
+
+        >>> Book.split_urlid("a-slug/pol", default_lang="eng")
+        {'slug': 'a-slug', 'language': 'pol'}
+        >>> Book.split_urlid("a-slug", default_lang="eng")
+        {'slug': 'a-slug', 'language': 'eng'}
+        >>> Book.split_urlid("a-slug_pol", "_", default_lang="eng")
+        {'slug': 'a-slug', 'language': 'pol'}
+        >>> Book.split_urlid("a-slug/eng", default_lang="eng")
+
+        """
+        parts = urlid.rsplit(sep, 1)
+        if len(parts) == 2:
+            if parts[1] == default_lang:
+                return None
+            return {'slug': parts[0], 'language': parts[1]}
+        else:
+            return {'slug': urlid, 'language': default_lang}
+
+    @classmethod
+    def split_fileid(cls, fileid):
+        return cls.split_urlid(fileid, '_')
+
     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(Book, self).save(force_insert, force_update)
@@ -332,14 +439,18 @@ class Book(models.Model):
 
     @permalink
     def get_absolute_url(self):
-        return ('catalogue.views.book_detail', [self.slug])
+        return ('catalogue.views.book_detail', [self.urlid()])
 
     @property
     def name(self):
         return self.title
 
     def book_tag_slug(self):
-        return ('l-' + self.slug)[:120]
+        stem = 'l-' + self.slug
+        if self.language != settings.CATALOGUE_DEFAULT_LANGUAGE:
+            return stem[:116] + ' ' + self.language
+        else:
+            return stem[:120]
 
     def book_tag(self):
         slug = self.book_tag_slug()
@@ -351,14 +462,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)
@@ -381,6 +492,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(cache_key = "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()
@@ -395,24 +507,17 @@ 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'<a href="%s">%s</a>' % (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 = []
             # files generated during publication
-            if self.has_media("html"):
-                formats.append(u'<a href="%s">%s</a>' % (reverse('book_text', kwargs={'slug': self.slug}), _('Read online')))
-            if self.has_media("pdf"):
-                formats.append(u'<a href="%s">PDF</a>' % self.get_media('pdf').url)
-            if self.has_media("mobi"):
-                formats.append(u'<a href="%s">MOBI</a>' % self.get_media('mobi').url)
-            if self.root_ancestor.has_media("epub"):
-                formats.append(u'<a href="%s">EPUB</a>' % self.root_ancestor.get_media('epub').url)
-            if self.has_media("txt"):
-                formats.append(u'<a href="%s">TXT</a>' % self.get_media('txt').url)
-            # other files
-            for m in self.media.order_by('type'):
-                formats.append(u'<a href="%s">%s</a>' % (m.file.url, m.type.upper()))
+            for ebook_format in self.ebook_formats:
+                if self.has_media(ebook_format):
+                    formats.append(u'<a href="%s">%s</a>' % (
+                        "", #self.get_media(ebook_format).url,
+                        ebook_format.upper()
+                    ))
 
             formats = [mark_safe(format) for format in formats]
 
@@ -423,17 +528,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
@@ -441,11 +551,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'
@@ -461,104 +566,105 @@ 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.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 librarian import pdf
-        from tempfile import NamedTemporaryFile
-        import os
+        from os import unlink
+        from django.core.files import File
+        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.fileid(),
+                    File(open(pdf.get_filename())))
+            self.pdf_file = current_self.pdf_file
+
+            # remove cached downloadables
+            remove_zip(settings.ALL_PDF_ZIP)
 
-        # remove zip with all pdf files
-        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 librarian import mobi
-        from tempfile import NamedTemporaryFile
-        import os
+        from django.core.files import File
+        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.fileid(), 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
+    def build_epub(self):
+        """(Re)builds the epub file."""
+        from django.core.files import File
+        from catalogue.utils import remove_zip
 
-        if self.parent:
-            # don't need an epub
-            return
+        epub = self.wldocument().as_epub()
 
-        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
-
-        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.fileid(),
+                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.fileid(), ContentFile(text.get_string()))
 
 
     def build_html(self):
-        from tempfile import NamedTemporaryFile
         from markupstring import MarkupString
+        from django.core.files.base import ContentFile
+        from slughifi import slughifi
+        from librarian import html
 
         meta_tags = list(self.tags.filter(
             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.fileid(),
+                    ContentFile(html_output.get_string()))
 
             # get ancestor l-tags for adding to new fragments
             ancestor_tags = []
@@ -608,7 +714,7 @@ class Book(models.Model):
         def pretty_file_name(book):
             return "%s/%s.%s" % (
                 b.get_extra_info_value()['author'],
-                b.slug,
+                b.fileid(),
                 format_)
 
         field_name = "%s_file" % format_
@@ -622,7 +728,7 @@ class Book(models.Model):
     def zip_audiobooks(self):
         bm = BookMedia.objects.filter(book=self, type='mp3')
         paths = map(lambda bm: (None, bm.file.path), bm)
-        result = create_zip.delay(paths, self.slug)
+        result = create_zip.delay(paths, self.fileid())
         return result.wait()
 
     def search_index(self):
@@ -642,6 +748,9 @@ class Book(models.Model):
 
     @classmethod
     def from_xml_file(cls, xml_file, **kwargs):
+        from django.core.files import File
+        from librarian import dcparser
+
         # use librarian to parse meta-data
         book_info = dcparser.parse(xml_file)
 
@@ -658,29 +767,33 @@ class Book(models.Model):
             build_epub=True, build_txt=True, build_pdf=True, build_mobi=True,
             search_index=True):
         import re
+        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, language=part_url.language))
                 except Book.DoesNotExist, e:
-                    raise Book.DoesNotExist(_('Book with slug = "%s" does not exist.') % slug)
+                    raise Book.DoesNotExist(_('Book "%s/%s" does not exist.') %
+                            (part_url.slug, part_url.language))
 
 
         # Read book metadata
-        book_base, book_slug = book_info.url.rsplit('/', 1)
+        book_slug = book_info.url.slug
+        language = book_info.language
         if re.search(r'[^a-zA-Z0-9-]', book_slug):
             raise ValueError('Invalid characters in slug')
-        book, created = Book.objects.get_or_create(slug=book_slug)
+        book, created = Book.objects.get_or_create(slug=book_slug, language=language)
 
         if created:
             book_shelves = []
         else:
             if not overwrite:
-                raise Book.AlreadyExists(_('Book %s already exists') % book_slug)
+                raise Book.AlreadyExists(_('Book %s/%s already exists') % (
+                        book_slug, language))
             # Save shelves for this book
             book_shelves = list(book.tags.filter(category='set'))
 
@@ -688,24 +801,7 @@ class Book(models.Model):
         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)
 
@@ -726,11 +822,13 @@ 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()
@@ -739,22 +837,27 @@ class Book(models.Model):
             book.search_index()
 
         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
         book.reset_tag_counter()
         book.reset_theme_counter()
 
+        cls.published.send(sender=book)
         return book
 
     def reset_tag_counter(self):
@@ -848,6 +951,57 @@ class Book(models.Model):
 
         return objects
 
+    @classmethod
+    def book_list(cls, filter=None):
+        """Generates a hierarchical listing of all books.
+
+        Books are optionally filtered with a test function.
+
+        """
+
+        books_by_parent = {}
+        books = cls.objects.all().order_by('parent_number', 'sort_key').only(
+                'title', 'parent', 'slug', 'language')
+        if filter:
+            books = books.filter(filter).distinct()
+            book_ids = set((book.pk for book in books))
+            for book in books:
+                parent = book.parent_id
+                if parent not in book_ids:
+                    parent = None
+                books_by_parent.setdefault(parent, []).append(book)
+        else:
+            for book in books:
+                books_by_parent.setdefault(book.parent_id, []).append(book)
+
+        orphans = []
+        books_by_author = SortedDict()
+        for tag in Tag.objects.filter(category='author'):
+            books_by_author[tag] = []
+
+        for book in books_by_parent.get(None,()):
+            authors = list(book.tags.filter(category='author'))
+            if authors:
+                for author in authors:
+                    books_by_author[author].append(book)
+            else:
+                orphans.append(book)
+
+        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))
@@ -858,7 +1012,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),
@@ -883,7 +1037,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' % (self.book.get_html_url(), self.anchor)
 
     def reset_short_html(self):
         if self.id is None:
@@ -910,20 +1064,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)
-
 ###########
 #
 # SIGNALS
@@ -934,7 +1074,8 @@ class FileRecord(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 (executable)
index 0000000..5566fe1
--- /dev/null
@@ -0,0 +1,16 @@
+# -*- 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
+
+
+@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)
index e433b8e..0ba4476 100644 (file)
@@ -140,6 +140,22 @@ def book_tree(book_list, books_by_parent):
     else:
         return ''
 
+@register.simple_tag
+def book_tree_texml(book_list, books_by_parent, depth=1):
+    return "".join("""
+            <cmd name='hspace'><parm>%(depth)dem</parm></cmd>%(title)s
+            <spec cat='align' /><cmd name="note"><parm>%(audiences)s</parm></cmd>
+            <spec cat='align' /><cmd name="note"><parm>%(audiobook)s</parm></cmd>
+            <ctrl ch='\\' />
+            %(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)
+
 
 @register.simple_tag
 def all_editors(extra_info):
@@ -168,22 +184,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()
@@ -264,23 +264,6 @@ def tag_list(tags, choices=None):
     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]
-    if choices is None:
-        choices = []
-    some_tags_hidden = False
-    tag_count = len(tags)
-
-    if tag_count == 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()
index a5f0b4f..58ef58a 100644 (file)
@@ -3,6 +3,7 @@ from django.test import TestCase
 import shutil
 import tempfile
 from slughifi import slughifi
+from librarian import WLURI
 
 class WLTestCase(TestCase):
     """
@@ -23,8 +24,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']
+    # allow single definition for multiple-value fields
+    _salias = {
+        'authors': 'author',
+    }
 
     def __init__(self, **kwargs):
         self.__dict = kwargs
@@ -35,18 +44,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_and_lang(slug, language),
         'about': u"http://wolnelektury.pl/example/URI/%s" % slug,
+        'language': language,
     }
index f65d880..a97c417 100644 (file)
@@ -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_and_lang(u"default-book", None),
             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_and_lang(u"default_book", None)
         BOOK_TEXT = "<utwor />"
         book = models.Book.from_text_and_meta(ContentFile(BOOK_TEXT), self.book_info)
 
@@ -242,6 +244,38 @@ class ChildImportTests(WLTestCase):
                         'wrong related theme list')
 
 
+class MultilingualBookImportTest(WLTestCase):
+    def setUp(self):
+        WLTestCase.setUp(self)
+        self.pol_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            **info_args("A book")
+        )
+
+        self.eng_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            **info_args("A book", "eng")
+        )
+
+    def test_multilingual_import(self):
+        BOOK_TEXT = """<utwor><opowiadanie><akap>A</akap></opowiadanie></utwor>"""
+
+        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 +292,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))
index edb29ab..90e7c12 100755 (executable)
@@ -12,7 +12,7 @@
 <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:identifier.url xml:lang="pl">http://wolnelektury.pl/katalog/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>
index 5d623fa..e128c60 100644 (file)
@@ -4,19 +4,20 @@
 #
 from django.conf.urls.defaults import *
 from catalogue.feeds import AudiobookFeed
-
+from catalogue.models import Book
+from picture.models import Picture
 
 urlpatterns = patterns('catalogue.views',
-    url(r'^$', 'main_page', name='main_page'),
+    url(r'^$', 'catalogue', name='catalogue'),
     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/(?P<shelf>[a-zA-Z0-9-]+)/(?P<book>%s)/usun$' % Book.URLID_RE, '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'^lektura/(?P<book>%s)/polki/' % Book.URLID_RE, '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'),
@@ -26,19 +27,23 @@ urlpatterns = patterns('catalogue.views',
     #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<slug>[a-zA-Z0-9-]+)\.zip', 'download_zip', {'format': 'audiobook'}, 'download_zip_audiobook'),
-
-    # tools
-    url(r'^zegar/$', 'clock', name='clock'),
+    #url(r'^zip/audiobook/(?P<book>%s)\.zip' % Book.FILEID_RE, 'download_zip', {'format': 'audiobook'}, 'download_zip_audiobook'),
 
     # 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-]+)/$',
+    url(r'^lektura/(?P<book>%s)\.html$' % Book.FILEID_RE, 'book_text', name='book_text'),
+    url(r'^lektura/(?P<book>%s)/$' % Book.URLID_RE, 'book_detail', name='book_detail'),
+    url(r'^lektura/(?P<book>%s)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$' % Book.URLID_RE,
         'book_fragments', name='book_fragments'),
 
     url(r'^(?P<tags>[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'),
 
     url(r'^audiobooki/(?P<type>mp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'),
-)
+
+    url(r'^custompdf/(?P<book_fileid>%s).pdf' % Book.FILEID_RE, 'download_custom_pdf'),
+
+) + patterns('picture.views',
+        # pictures - currently pictures are coupled with catalogue, hence the url is here
+        url(r'^obraz/?$', 'picture_list'),
+        url(r'^obraz/(?P<picture>%s)/?$' % Picture.URLID_RE, 'picture_detail')
+    )
 
index 0134701..acbd778 100644 (file)
@@ -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
@@ -18,7 +21,9 @@ 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'):
@@ -61,11 +66,12 @@ class ORMDocProvider(DocProvider):
     def __init__(self, book):
         self.book = book
 
-    def by_slug(self, slug):
-        if slug == self.book.slug:
-            return self.book.xml_file
+    def by_slug_and_lang(self, slug, language):
+        if slug == self.book.slug and language == self.language:
+            return open(self.book.xml_file.path)
         else:
-            return type(self.book).objects.get(slug=slug).xml_file
+            return type(self.book).objects.get(
+                    slug=slug, language=language).xml_file
 
 
 class LockFile(object):
@@ -131,3 +137,30 @@ 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."
index 64aada5..57a4975 100644 (file)
@@ -2,12 +2,6 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-import tempfile
-import zipfile
-import tarfile
-import sys
-import pprint
-import traceback
 import re
 import itertools
 from datetime import datetime
@@ -23,94 +17,44 @@ 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
 from catalogue import models
 from catalogue import forms
-from catalogue.utils import split_tags
-from newtagging import views as newtagging_views
+from catalogue.utils import split_tags, AttachmentHttpResponse, async_build_pdf
+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 slughifi import slughifi
 
+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))
 
 
 def book_list(request, filter=None, template_name='catalogue/book_list.html'):
     """ generates a listing of all books, optionally filtered with a test function """
 
-    form = forms.SearchForm()
-
-    books_by_parent = {}
-    books = models.Book.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))
-        for book in books:
-            parent = book.parent_id
-            if parent not in book_ids:
-                parent = None
-            books_by_parent.setdefault(parent, []).append(book)
-    else:
-        for book in books:
-            books_by_parent.setdefault(book.parent_id, []).append(book)
-
-    orphans = []
-    books_by_author = SortedDict()
+    books_by_author, orphans, books_by_parent = models.Book.book_list(filter)
     books_nav = SortedDict()
-    for tag in models.Tag.objects.filter(category='author'):
-        books_by_author[tag] = []
-
-    for book in books_by_parent.get(None,()):
-        authors = list(book.tags.filter(category='author'))
-        if authors:
-            for author in authors:
-                books_by_author[author].append(book)
-        else:
-            orphans.append(book)
-
     for tag in books_by_author:
         if books_by_author[tag]:
             books_nav.setdefault(tag.sort_key[0], []).append(tag)
@@ -237,23 +181,29 @@ def tagged_object_list(request, tags=''):
     )
 
 
-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, book, theme_slug):
+    kwargs = models.Book.split_urlid(book)
+    if kwargs is None:
+        raise Http404
+    book = get_object_or_404(models.Book, **kwargs)
+
+    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))
 
 
-def book_detail(request, slug):
+def book_detail(request, book):
+    kwargs = models.Book.split_urlid(book)
+    if kwargs is None:
+        raise Http404
     try:
-        book = models.Book.objects.get(slug=slug)
+        book = models.Book.objects.get(**kwargs)
     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)
@@ -286,13 +236,17 @@ def book_detail(request, slug):
         projects.add((project, meta.get('funded_by', '')))
     projects = sorted(projects)
 
-    form = forms.SearchForm()
+    custom_pdf_form = forms.CustomPDFForm()
     return render_to_response('catalogue/book_detail.html', locals(),
         context_instance=RequestContext(request))
 
 
-def book_text(request, slug):
-    book = get_object_or_404(models.Book, slug=slug)
+def book_text(request, book):
+    kwargs = models.Book.split_fileid(book)
+    if kwargs is None:
+        raise Http404
+    book = get_object_or_404(models.Book, **kwargs)
+
     if not book.has_html_file():
         raise Http404
     book_themes = {}
@@ -424,7 +378,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,
@@ -526,11 +480,15 @@ def user_shelves(request):
             context_instance=RequestContext(request))
 
 @cache.never_cache
-def book_sets(request, slug):
+def book_sets(request, book):
     if not request.user.is_authenticated():
         return HttpResponse(_('<p>To maintain your shelves you need to be logged in.</p>'))
 
-    book = get_object_or_404(models.Book, slug=slug)
+    kwargs = models.Book.split_urlid(book)
+    if kwargs is None:
+        raise Http404
+    book = get_object_or_404(models.Book, **kwargs)
+
     user_sets = models.Tag.objects.filter(category='set', user=request.user)
     book_sets = book.tags.filter(category='set', user=request.user)
 
@@ -541,12 +499,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():
@@ -565,14 +521,16 @@ def book_sets(request, slug):
 @require_POST
 @cache.never_cache
 def remove_from_shelf(request, shelf, book):
-    book = get_object_or_404(models.Book, slug=book)
+    kwargs = models.Book.split_urlid(book)
+    if kwargs is None:
+        raise Http404
+    book = get_object_or_404(models.Book, **kwargs)
+
     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:
@@ -599,6 +557,10 @@ def download_shelf(request, slug):
     without loading the whole file into memory. A similar approach can
     be used for large dynamic PDF files.
     """
+    from slughifi import slughifi
+    import tempfile
+    import zipfile
+
     shelf = get_object_or_404(models.Tag, slug=slug, category='set')
 
     formats = []
@@ -606,31 +568,18 @@ 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))
+        fileid = book.fileid()
+        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' % (fileid, ebook_format)))
     archive.close()
 
     response = HttpResponse(content_type='application/zip', mimetype='application/x-zip-compressed')
@@ -649,20 +598,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))
 
@@ -696,45 +639,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 =
 # =========
@@ -747,6 +651,9 @@ def import_book(request):
         try:
             book_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]))
@@ -756,14 +663,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'):
@@ -779,13 +678,37 @@ def tag_info(request, id):
     return HttpResponse(tag.description)
 
 
-def download_zip(request, format, slug):
+def download_zip(request, format, book=None):
+    kwargs = models.Book.split_fileid(book)
+
     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)
+    elif format == 'audiobook' and kwargs is not None:
+        book = get_object_or_404(models.Book, **kwargs)
         url = book.zip_audiobooks()
     else:
         raise Http404('No format specified for zip package')
     return HttpResponseRedirect(urlquote_plus(settings.MEDIA_URL + url, safe='/?='))
+
+
+def download_custom_pdf(request, book_fileid):
+    kwargs = models.Book.split_fileid(book_fileid)
+    if kwargs is None:
+        raise Http404
+    book = get_object_or_404(models.Book, **kwargs)
+
+    if request.method == 'GET':
+        form = forms.CustomPDFForm(request.GET)
+        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_fileid), file_path=pdf_file, mimetype="application/pdf")
+        else:
+            raise Http404(_('Incorrect customization options for PDF'))
+    else:
+        raise Http404(_('Bad method'))
index e0b10f3..cd88c17 100755 (executable)
@@ -1,16 +1,14 @@
 {% 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 %}
     <h1>{% trans "Footnotes" %}</h1>
-    {% search_form %}
 
 
 <p>
@@ -44,7 +42,7 @@
     <div class='dictionary-note'>
     {{ obj.html|safe }}
     <div class='dictionary-note-source'>
-    (<a href='{% url book_text obj.book.slug %}#{{ obj.anchor }}'>{{ obj.book.pretty_title }}</a>)
+    (<a href='{% url book_text obj.book.fileid %}#{{ obj.anchor }}'>{{ obj.book.pretty_title }}</a>)
     </div>
     </div>
 {% endfor %}
index e120063..7b9cd53 100755 (executable)
@@ -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":
index 66f2996..e5bc93c 100644 (file)
@@ -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 (file)
index 0000000..6ecd60b
--- /dev/null
@@ -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']
index 9fe7b32..cf9e9bf 100644 (file)
@@ -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 (executable)
index 0000000..dc9efe1
--- /dev/null
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load chunks %}
+
+{% block titleextra %}{{ page.title }}{% endblock %}
+
+{% block metadescription %}{{ left_column|striptags|truncatewords:10 }}{% endblock %}
+
+{% block body %}
+    <h1>{{ page.title }}</h1>
+
+    {% autoescape off %}
+    <div class="column-left">
+       {{ left_column }}
+    </div>
+    <div class="column-right">
+       {{ right_column }}
+    </div>
+    {% endautoescape %}
+{% endblock %}
diff --git a/apps/infopages/templates/infopages/on_main.html b/apps/infopages/templates/infopages/on_main.html
new file mode 100755 (executable)
index 0000000..dc0103c
--- /dev/null
@@ -0,0 +1,5 @@
+<ul>
+{% for page in objects %}
+    <li><a href="{{ page.get_absolute_url }}">{{ page.title }}</a></li>
+{% endfor %}
+</ul>
diff --git a/apps/infopages/templatetags/__init__.py b/apps/infopages/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/infopages/templatetags/infopages_tags.py b/apps/infopages/templatetags/infopages_tags.py
new file mode 100755 (executable)
index 0000000..d7c93ca
--- /dev/null
@@ -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 (executable)
index 0000000..081e0ef
--- /dev/null
@@ -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<slug>[a-zA-Z0-9_-]+)/$', 'infopage', name='infopage'),
+)
+
index 07a416b..d457653 100644 (file)
@@ -2,15 +2,19 @@
 # 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
 
-from catalogue.forms import SearchForm
 from infopages.models import InfoPage
 
+
 def infopage(request, slug):
-    form = SearchForm()
-    object = InfoPage.objects.get(slug=slug)
+    page = InfoPage.objects.get(slug=slug)
+
+    page = get_object_or_404(InfoPage, slug=slug)
+    rc = RequestContext(request)
+    left_column = Template(page.left_column).render(rc)
+    right_column = Template(page.right_column).render(rc)
 
-    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))
index e7bbb48..eeba610 100644 (file)
@@ -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<slug>[a-zA-Z0-9-]+)/$', 'poem_from_book', name='poem_from_book'),
+    url(r'^lektura/(?P<book>%s)/$' % Book.URLID_RE, 'poem_from_book', name='poem_from_book'),
     url(r'^polka/(?P<shelf>[a-zA-Z0-9-]+)/$', 'poem_from_set', name='poem_from_set'),
     url(r'^wiersz/(?P<poem>[a-zA-Z0-9-]+)/$', 'get_poem', name='get_poem'),
 )
index 56acb57..28cb32a 100644 (file)
@@ -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))
 
 
@@ -34,8 +33,11 @@ def new_poem(request):
 
 
 @cache.never_cache
-def poem_from_book(request, slug):
-    book = get_object_or_404(Book, slug=slug)
+def poem_from_book(request, book):
+    kwargs = Book.split_urlid(book)
+    if kwargs is None:
+        raise Http404
+    book = get_object_or_404(Book, **kwargs)
     user = request.user if request.user.is_authenticated() else None
     text = Poem.write(Continuations.get(book))
     p = Poem(slug=get_random_hash(text), text=text, created_by=user)
index 242526d..9314d1c 100644 (file)
@@ -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(),
-        })
+    )
index 44baf5b..c907fe1 100644 (file)
@@ -234,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
@@ -285,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
diff --git a/apps/pdcounter/urls.py b/apps/pdcounter/urls.py
deleted file mode 100644 (file)
index 2325137..0000000
+++ /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'),
-)
-
index efcfe95..b07ee11 100644 (file)
@@ -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 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/apps/picture/admin.py b/apps/picture/admin.py
new file mode 100644 (file)
index 0000000..fb6bcf2
--- /dev/null
@@ -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 (file)
index 0000000..ad5096b
--- /dev/null
@@ -0,0 +1,24 @@
+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=True)
+
+    def clean(self):
+        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."))
+        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 (file)
index 0000000..6ac54fe
--- /dev/null
@@ -0,0 +1,135 @@
+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 librarian import dcparser, picture
+
+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')
+
+    URLID_RE = r'[a-z0-9-]+'
+    FILEID_RE = r'[a-z0-9-]+'
+
+    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)
+
+        return ret
+
+    def __unicode__(self):
+        return self.title
+
+    @permalink
+    def get_absolute_url(self):
+        return ('picture.views.picture_detail', [self.urlid()])
+
+    def urlid(self):
+        return self.slug
+
+    @classmethod
+    def from_xml_file(cls, xml_file, image_file=None, overwrite=False):
+        """
+        Import xml and it's accompanying image file.
+        """
+        from django.core.files import File
+        from librarian.picture import WLPicture
+        close_xml_file = False
+
+        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)
+
+            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
+
+            picture.tags = catalogue.models.Tag.tags_from_info(picture_xml.picture_info)
+
+            if image_file is not None:
+                img = image_file
+            else:
+                img = picture_xml.image_file()
+
+            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()
+        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'):
+            info = dcparser.parse(self.xml_file.path, picture.PictureInfo)
+            self._info = info
+        return self._info
diff --git a/apps/picture/tests/__init__.py b/apps/picture/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/picture/tests/files/kandinsky-composition-viii.png b/apps/picture/tests/files/kandinsky-composition-viii.png
new file mode 100644 (file)
index 0000000..a809eb5
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 (file)
index 0000000..036bdf7
--- /dev/null
@@ -0,0 +1,35 @@
+<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.digital xml:lang="pl">1090 x 755 px</dc:format.dimensions.digital>
+      <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="obiekt" obiekt="kosmos">
+    <div type="area" x1="17" y1="31" x2="275" y2="309"/>
+  </sem>
+  <sem type="obiekt" obiekt="nieporządek">
+    <div type="area" x1="300" y1="138" x2="976" y2="514"/>
+  </sem>
+</picture>
diff --git a/apps/picture/views.py b/apps/picture/views.py
new file mode 100644 (file)
index 0000000..c5be3be
--- /dev/null
@@ -0,0 +1,31 @@
+from picture.models import Picture
+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))
diff --git a/apps/reporting/__init__.py b/apps/reporting/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/apps/reporting/models.py b/apps/reporting/models.py
new file mode 100644 (file)
index 0000000..740b927
--- /dev/null
@@ -0,0 +1,8 @@
+# -*- 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.
+#
+
+
+# import views here, so that signals are attached correctly
+from reporting.views import catalogue_pdf
diff --git a/apps/reporting/templates/reporting/catalogue.texml b/apps/reporting/templates/reporting/catalogue.texml
new file mode 100755 (executable)
index 0000000..c17ed07
--- /dev/null
@@ -0,0 +1,118 @@
+{% load catalogue_tags %}
+<TeXML xmlns="http://getfo.sourceforge.net/texml/ns1">
+    <TeXML escape="0">
+    \documentclass[a4paper, oneside, 11pt]{book}
+
+\usepackage[MeX]{polski}
+
+\usepackage[xetex]{graphicx}
+\usepackage{xunicode}
+\usepackage{xltxtra}
+
+\usepackage{scalefnt}
+\usepackage[colorlinks=true,linkcolor=black,setpagesize=false,urlcolor=black,xetex]{hyperref}
+
+\usepackage{longtable}
+
+\setmainfont [
+%ExternalLocation,
+UprightFont = JunicodeWL-Regular,
+ItalicFont = JunicodeWL-Italic,
+BoldFont = JunicodeWL-Regular,
+BoldItalicFont = JunicodeWL-Italic,
+SmallCapsFont = JunicodeWL-Regular,
+SmallCapsFeatures = {Letters={SmallCaps,UppercaseSmallCaps}},
+Numbers=OldStyle,
+Scale=1.04,
+LetterSpace=-1.0
+] {JunicodeWL}
+
+\pagestyle{plain}
+\usepackage{fancyhdr}
+
+\makeatletter
+
+\usepackage{color}
+\definecolor{note}{gray}{.3}
+
+\setlength{\hoffset}{-1cm}
+\setlength{\oddsidemargin}{0pt}
+\setlength{\marginparsep}{0pt}
+\setlength{\marginparwidth}{0pt}
+
+\setlength{\voffset}{0pt}
+\setlength{\topmargin}{0pt}
+\setlength{\headheight}{0pt}
+\setlength{\headsep}{0pt}
+\setlength{\leftmargin}{0em}
+\setlength{\rightmargin}{0em}
+\setlength{\textheight}{24cm}
+\setlength{\textwidth}{17.5cm}
+
+
+\pagestyle{fancy}
+\fancyhf{}
+\renewcommand{\headrulewidth}{0pt}
+\renewcommand{\footrulewidth}{0pt}
+\lfoot{\footnotesize Katalog biblioteki internetowej WolneLektury.pl, \today}
+\cfoot{}
+\rfoot{\footnotesize \thepage}
+
+\clubpenalty=100000
+\widowpenalty=100000
+
+
+% see http://osdir.com/ml/tex.xetex/2005-10/msg00003.html
+\newsavebox{\ximagebox}\newlength{\ximageheight}
+\newsavebox{\xglyphbox}\newlength{\xglyphheight}
+\newcommand{\xbox}[1]
+{\savebox{\ximagebox}{#1}\settoheight{\ximageheight}{\usebox {\ximagebox}}%
+\savebox{\xglyphbox}{\char32}\settoheight{\xglyphheight}{\usebox {\xglyphbox}}%
+\raisebox{\ximageheight}[0pt][0pt]{%\raisebox{-\xglyphheight}[0pt] [0pt]{%
+\makebox[0pt][l]{\usebox{\xglyphbox}}}%}%
+\usebox{\ximagebox}%
+\raisebox{0pt}[0pt][0pt]{\makebox[0pt][r]{\usebox{\xglyphbox}}}}
+
+
+\newcommand{\name}[1]{%
+\\
+\Large{#1}%
+}
+
+\newcommand{\note}[1]{%
+\small{\color{note}{#1}}%
+}
+
+
+\begin{document}
+
+    \noindent \begin{minipage}[t]{.35\textwidth}\vspace{0pt}
+        \href{http://www.wolnelektury.pl}{\xbox{\includegraphics[width=\textwidth]{wl-logo.png}}}
+    \end{minipage}
+
+    \begin{minipage}[t]{.65\textwidth}\vspace{0pt}
+        \begin{flushright}
+            \section*{Katalog biblioteki internetowej 
+                \href{http://www.wolnelektury.pl/}{WolneLektury.pl}.}
+            stan na \today
+        \end{flushright}
+    \end{minipage}
+
+    \begin{longtable}{p{9.5cm} p{5.5cm}r p{2cm}}
+
+        <TeXML escape="1">
+            {% book_tree_texml orphans books_by_parent %}
+            {% for author, group in books_by_author.items %}
+                {% if group %}
+                    <cmd name="name"><parm>{{ author }}</parm></cmd>
+                    <ctrl ch='\' />
+
+                    {% book_tree_texml group books_by_parent %}
+                {% endif %}
+            {% endfor %}
+        </TeXML>
+
+    \end{longtable}
+    \end{document}
+    </TeXML>
+</TeXML>
\ No newline at end of file
diff --git a/apps/reporting/templates/reporting/main.html b/apps/reporting/templates/reporting/main.html
new file mode 100755 (executable)
index 0000000..c629f10
--- /dev/null
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load reporting_stats catalogue_tags %}
+
+{% block titleextra %}{% trans "Reports" %}{% endblock %}
+
+{% block bodyid %}reports-stats{% endblock %}
+
+
+{% block body %}
+    <h1>Statystyka</h1>
+
+    <p><a href="{% url reporting_catalogue_pdf %}">Katalog biblioteki w formacie PDF.</a></p>
+
+    <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>Niezależne książki:</td><td>{% count_books_root %}</td></tr>
+
+        <tr><th>Media</th><th>Liczba</th><th>Rozmiar</th><th>Do wymiany</th></tr>
+        {% for mt in media_types %}
+            <tr><td>{{ mt.type }}:</td>
+                <td>{{ mt.count }}</td>
+                <td>{{ mt.size|filesizeformat }}</td>
+                <td>{{ mt.deprecated }}
+                    {% for m in mt.deprecated_files %}
+                        <br/><a href="{{ m.book.get_absolute_url }}">{{ m }}</a>
+                    {% endfor %}
+                </td>
+            </tr>
+        {% endfor %}
+    </table>
+
+{% endblock %}
diff --git a/apps/reporting/templatetags/__init__.py b/apps/reporting/templatetags/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/apps/reporting/templatetags/reporting_stats.py b/apps/reporting/templatetags/reporting_stats.py
new file mode 100755 (executable)
index 0000000..dceee00
--- /dev/null
@@ -0,0 +1,61 @@
+# -*- 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.
+#
+import feedparser
+from functools import wraps
+import datetime
+
+from django import template
+
+from catalogue.models import Book, BookMedia
+
+
+register = template.Library()
+
+class StatsNode(template.Node):
+    def __init__(self, value, varname=None):
+        self.value = value
+        self.varname = varname
+
+    def render(self, context):
+        if self.varname:
+            context[self.varname] = self.value
+            return ''
+        else:
+            return self.value
+
+
+def register_counter(f):
+    """Turns a simple counting function into a registered counter tag.
+
+    You can run a counter tag as a simple {% tag_name %} tag, or
+    as {% tag_name var_name %} to store the result in a variable.
+
+    """
+    @wraps(f)
+    def wrapped(parser, token):
+        try:
+            tag_name, args = token.contents.split(None, 1)
+        except ValueError:
+            args = None
+        return StatsNode(f(), args)
+
+    return register.tag(wrapped)
+
+
+@register_counter
+def count_books_all():
+    return Book.objects.all().count()
+
+@register_counter
+def count_books_nonempty():
+    return Book.objects.exclude(html_file='').count()
+
+@register_counter
+def count_books_empty():
+    return Book.objects.filter(html_file='').count()
+
+@register_counter
+def count_books_root():
+    return Book.objects.filter(parent=None).count()
diff --git a/apps/reporting/urls.py b/apps/reporting/urls.py
new file mode 100755 (executable)
index 0000000..f509215
--- /dev/null
@@ -0,0 +1,12 @@
+# -*- 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('reporting.views',
+    url(r'^$', 'stats_page', name='reporting_stats'),
+    url(r'^katalog.pdf$', 'catalogue_pdf', name='reporting_catalogue_pdf'),
+)
+
diff --git a/apps/reporting/utils.py b/apps/reporting/utils.py
new file mode 100755 (executable)
index 0000000..cc4e97a
--- /dev/null
@@ -0,0 +1,103 @@
+# -*- 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 errno import ENOENT
+import os
+import os.path
+from django.conf import settings
+import logging
+from django.http import HttpResponse
+
+logger = logging.getLogger(__name__)
+
+
+def render_to_pdf(output_path, template, context=None, add_files=None):
+    """Renders a TeXML document into a PDF file.
+
+    :param str output_path: is where the PDF file should go
+    :param str template: is a TeXML template path
+    :param context: is context for rendering the template
+    :param dict add_files: a dictionary of additional files XeTeX will need
+    """
+
+    from StringIO import StringIO
+    import shutil
+    from tempfile import mkdtemp
+    import subprocess
+    import Texml.processor
+    from django.template.loader import render_to_string
+
+    rendered = render_to_string(template, context)
+    texml = StringIO(rendered.encode('utf-8'))
+    tempdir = mkdtemp(prefix = "render_to_pdf-")
+    tex_path = os.path.join(tempdir, "doc.tex")
+    with open(tex_path, 'w') as tex_file:
+        Texml.processor.process(texml, tex_file, encoding="utf-8")
+
+    if add_files:
+        for add_name, src_file in add_files.items():
+            add_path = os.path.join(tempdir, add_name)
+            if hasattr(src_file, "read"):
+                with open(add_path, 'w') as add_file:
+                    add_file.write(add_file.read())
+            else:
+                shutil.copy(src_file, add_path)
+
+    cwd = os.getcwd()
+    os.chdir(tempdir)
+    try:
+        subprocess.check_call(['xelatex', '-interaction=batchmode', tex_path],
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        try:
+            os.makedirs(os.path.dirname(output_path))
+        except:
+            pass
+        shutil.move(os.path.join(tempdir, "doc.pdf"), output_path)
+    finally:
+        os.chdir(cwd)
+        shutil.rmtree(tempdir)
+
+
+def read_chunks(f, size=8192):
+    chunk = f.read(size)
+    while chunk:
+        yield chunk
+        chunk = f.read(size)
+
+
+def generated_file_view(file_name, mime_type, send_name=None, signals=None):
+    file_path = os.path.join(settings.MEDIA_ROOT, file_name)
+    file_url = os.path.join(settings.MEDIA_URL, file_name)
+    if send_name is None:
+        send_name = os.path.basename(file_name)
+
+    def signal_handler(*args, **kwargs):
+        try:
+            os.unlink(file_path)
+        except OSError as oe:
+            if oe.errno != ENOENT:
+                raise oe
+
+    if signals:
+        for signal in signals:
+            signal.connect(signal_handler, weak=False)
+
+    def decorator(func):
+        def view(request, *args, **kwargs):
+            if not os.path.exists(file_path):
+                func(file_path, *args, **kwargs)
+
+            if hasattr(send_name, "__call__"):
+                name = send_name()
+            else:
+                name = send_name
+
+            response = HttpResponse(mimetype=mime_type)
+            response['Content-Disposition'] = 'attachment; filename=%s' % name
+            with open(file_path) as f:
+                for chunk in read_chunks(f):
+                    response.write(chunk)
+            return response
+        return view
+    return decorator
diff --git a/apps/reporting/views.py b/apps/reporting/views.py
new file mode 100644 (file)
index 0000000..958e080
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- 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.
+#
+import os.path
+from datetime import date
+from django.conf import settings
+from django.db.models import Count
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from catalogue.models import Book, BookMedia
+from reporting.utils import render_to_pdf, generated_file_view
+
+
+def stats_page(request):
+    media = BookMedia.objects.count()
+    media_types = BookMedia.objects.values('type').\
+            annotate(count=Count('type')).\
+            order_by('type')
+    for mt in media_types:
+        mt['size'] = sum(b.file.size for b in BookMedia.objects.filter(type=mt['type']))
+        if mt['type'] in ('mp3', 'ogg'):
+            deprecated = BookMedia.objects.filter(
+                    type=mt['type'], source_sha1=None)
+            mt['deprecated'] = deprecated.count()
+            mt['deprecated_files'] = deprecated.order_by('book', 'name')
+        else:
+            mt['deprecated'] = '-'
+
+    return render_to_response('reporting/main.html',
+                locals(), context_instance=RequestContext(request))
+
+
+@generated_file_view('reports/katalog.pdf', 'application/pdf', 
+        send_name=lambda: 'wolnelektury_%s.pdf' % date.today(),
+        signals=[Book.published])
+def catalogue_pdf(path):
+    books_by_author, orphans, books_by_parent = Book.book_list()
+    render_to_pdf(path, 'reporting/catalogue.texml', locals(), {
+            "wl-logo.png": os.path.join(settings.STATIC_ROOT, "img/logo-big.png"),
+        })
index d91c8f4..1e0d2e5 100644 (file)
@@ -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 (file)
index 112241d..0000000
+++ /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',)
index 1277bd2..fe19d30 100644 (file)
@@ -66,3 +66,8 @@
     background-color: #EEE;
     cursor: default;
 }
+
+.sponsors-sponsor img {
+    max-width: 120px;
+    max-height: 120px;
+}
index e4b30bb..fc13873 100644 (file)
@@ -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/stats/templates/stats/main.html b/apps/stats/templates/stats/main.html
deleted file mode 100755 (executable)
index 2be411d..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-{% load stats catalogue_tags %}
-
-{% block title %}Statystyka w  WolneLektury.pl{% endblock %}
-
-{% block bodyid %}tagged-object-list{% endblock %}
-
-{% block body %}
-    <h1>Statystyka</h1>
-    {% search_form %}
-
-    <table>
-        <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>Niezależne książki:</td><td>{% count_books_root %}</td></tr>
-
-        <tr><th>Media</th><th>Liczba</th><th>Rozmiar</th><th>Do wymiany</th></tr>
-        {% for mt in media_types %}
-            <tr><td>{{ mt.type }}:</td>
-                <td>{{ mt.count }}</td>
-                <td>{{ mt.size|filesizeformat }}</td>
-                <td>{{ mt.deprecated }}</td>
-            </tr>
-        {% endfor %}
-    </table>
-
-{% endblock %}
diff --git a/apps/stats/templatetags/__init__.py b/apps/stats/templatetags/__init__.py
deleted file mode 100755 (executable)
index e69de29..0000000
diff --git a/apps/stats/templatetags/stats.py b/apps/stats/templatetags/stats.py
deleted file mode 100755 (executable)
index dceee00..0000000
+++ /dev/null
@@ -1,61 +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.
-#
-import feedparser
-from functools import wraps
-import datetime
-
-from django import template
-
-from catalogue.models import Book, BookMedia
-
-
-register = template.Library()
-
-class StatsNode(template.Node):
-    def __init__(self, value, varname=None):
-        self.value = value
-        self.varname = varname
-
-    def render(self, context):
-        if self.varname:
-            context[self.varname] = self.value
-            return ''
-        else:
-            return self.value
-
-
-def register_counter(f):
-    """Turns a simple counting function into a registered counter tag.
-
-    You can run a counter tag as a simple {% tag_name %} tag, or
-    as {% tag_name var_name %} to store the result in a variable.
-
-    """
-    @wraps(f)
-    def wrapped(parser, token):
-        try:
-            tag_name, args = token.contents.split(None, 1)
-        except ValueError:
-            args = None
-        return StatsNode(f(), args)
-
-    return register.tag(wrapped)
-
-
-@register_counter
-def count_books_all():
-    return Book.objects.all().count()
-
-@register_counter
-def count_books_nonempty():
-    return Book.objects.exclude(html_file='').count()
-
-@register_counter
-def count_books_empty():
-    return Book.objects.filter(html_file='').count()
-
-@register_counter
-def count_books_root():
-    return Book.objects.filter(parent=None).count()
diff --git a/apps/stats/urls.py b/apps/stats/urls.py
deleted file mode 100755 (executable)
index 3b62409..0000000
+++ /dev/null
@@ -1,11 +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('stats.views',
-    url(r'^$', 'stats_page', name='stats'),
-)
-
index 7ee4cfd..1218e00 100644 (file)
@@ -1,4 +1,7 @@
-
+# -*- 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.contrib.sites.models import Site
 from piwik.django.models import PiwikSite
 from django.conf import settings
diff --git a/apps/stats/views.py b/apps/stats/views.py
deleted file mode 100644 (file)
index b4fd44b..0000000
+++ /dev/null
@@ -1,24 +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.db.models import Count
-from django.shortcuts import render_to_response
-from django.template import RequestContext
-
-from catalogue.models import Book, BookMedia
-
-
-def stats_page(request):
-    media = BookMedia.objects.count()
-    media_types = BookMedia.objects.values('type').\
-            annotate(count=Count('type')).\
-            order_by('type')
-    for mt in media_types:
-        mt['size'] = sum(b.file.size for b in BookMedia.objects.filter(type=mt['type']))
-        mt['deprecated'] = BookMedia.objects.filter(
-            type=mt['type'], source_sha1=None).count() if mt['type'] in ('mp3', 'ogg') else '-'
-
-    return render_to_response('stats/main.html',
-                locals(), context_instance=RequestContext(request))
index 14cec03..b7c614f 100644 (file)
@@ -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)
index a6ed283..3e71000 100755 (executable)
@@ -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 (executable)
index c5d8c28..0000000
+++ /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 (file)
index c7fdd81..0000000
+++ /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
index b769e49..ae4ac15 100644 (file)
@@ -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'),
 )
 
index 24ee12c..15b65f2 100644 (file)
 # 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.')
index 8ad5f50..3d1be71 100644 (file)
@@ -165,4 +165,4 @@ def restart_webserver():
     print '>>> restart webserver'
     run('touch %(path)s/%(project_name)s.wsgi' % env)
     print '>>> restart Celery'
-    sudo('supervisorctl restart celery.%(project_name)s:' % env)
+    sudo('supervisorctl restart celery.%(project_name)s:' % env, shell=False)
index d7ba2c6..5b40766 160000 (submodule)
@@ -1 +1 @@
-Subproject commit d7ba2c607dacf7a6136b83a1588b5adf2278ad46
+Subproject commit 5b407667ca47cf4d9752821fd49e5611737146d2
index dfb01f1..ed684cc 100644 (file)
@@ -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
index c1fe3ff..0afb859 100755 (executable)
@@ -3,8 +3,6 @@
 ROOT=$(git rev-parse --show-toplevel)
 
 find $ROOT -name '*.py' | xargs etags -o ${ROOT}/TAGS
-# librarian is a submodule now
-#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
diff --git a/scripts/setmainpage.py b/scripts/setmainpage.py
deleted file mode 100755 (executable)
index 3244243..0000000
+++ /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('.')
-
-
index 826abb2..4184c4e 100644 (file)
@@ -111,7 +111,7 @@ TEMPLATE_DIRS = [
     path.join(PROJECT_DIR, 'templates'),
 ]
 
-LOGIN_URL = '/uzytkownicy/login/'
+LOGIN_URL = '/uzytkownicy/zaloguj/'
 
 LOGIN_REDIRECT_URL = '/'
 
@@ -138,6 +138,7 @@ INSTALLED_APPS = [
     'modeltranslation',
 
     # our
+    'ajaxable',
     'api',
     'catalogue',
     'chunks',
@@ -148,24 +149,33 @@ INSTALLED_APPS = [
     'newtagging',
     'opds',
     'pdcounter',
+    'reporting',
     'sponsors',
     'stats',
     'suggest',
+    'picture',
     'search',
-
-    #
-    'django_nose',
 ]
 
 #CACHE_BACKEND = 'locmem:///?max_entries=3000'
-CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
+#CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
 CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True
 
 # CSS and JavaScript file groups
 COMPRESS_CSS = {
     'all': {
-        'source_filenames': ('css/master.css', 'css/jquery.countdown.css',
-                             'css/master.plain.css', 'css/sponsors.css', 'css/facelist_2-0.css', 'css/ui-lightness/jquery-ui-1.8.16.custom.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/book_box.css',
+            'css/catalogue.css',
+            'css/sponsors.css',
+        ],
         'output_filename': 'css/all.min?.css',
     },
     'book': {
@@ -179,29 +189,36 @@ COMPRESS_CSS = {
 }
 
 COMPRESS_JS = {
-    'jquery': {
-        'source_filenames': ('js/jquery.js', 'js/jquery-ui-1.8.16.custom.min.js'),
-        'output_filename': 'js/jquery.min.js',
-    },
-    'all': {
-        'source_filenames': ('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/locale.js',
+            'js/dialogs.js',
+            'js/sponsors.js',
+            'js/pdcounter.js',
+
+            #~ 'js/jquery.autocomplete.js',
+            #~ 'js/jquery.labelify.js', 'js/catalogue.js',
             ),
-        'output_filename': 'js/all?.min.js',
-    },
-    'book': {
-        'source_filenames': ('js/jquery.eventdelegation.js', 'js/jquery.scrollto.js', 'js/jquery.highlightfade.js', 'js/book.js',),
-        'output_filename': 'js/book?.min.js',
+        'output_filename': 'js/base?.min.js',
     },
-    'book_ie': {
-        'source_filenames': ('js/ierange-m2.js',),
-        'output_filename': 'js/book_ie?.min.js',
-    }
+    #~ 'book': {
+        #~ 'source_filenames': ('js/jquery.eventdelegation.js', 'js/jquery.scrollto.js', 'js/jquery.highlightfade.js', 'js/book.js',),
+        #~ 'source_filenames': [],
+        #~ 'output_filename': 'js/book?.min.js',
+    #~ },
+    #~ 'book_ie': {
+        #~ 'source_filenames': ('js/ierange-m2.js',),
+        #~ 'source_filenames': [],
+        #~ 'output_filename': 'js/book_ie?.min.js',
+    #~ }
 
 }
 
@@ -241,6 +258,8 @@ ALL_EPUB_ZIP = 'wolnelektury_pl_epub'
 ALL_PDF_ZIP = 'wolnelektury_pl_pdf'
 ALL_MOBI_ZIP = 'wolnelektury_pl_mobi'
 
+CATALOGUE_DEFAULT_LANGUAGE = 'pol'
+
 PAGINATION_INVALID_PAGE_RAISES_404 = True
 
 import djcelery
diff --git a/wolnelektury/static/css/base.css b/wolnelektury/static/css/base.css
new file mode 100755 (executable)
index 0000000..35aed2e
--- /dev/null
@@ -0,0 +1,67 @@
+/* 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: .9em;
+    line-height: 1.4em;
+}
+
+
+a {
+    color: #02adb7;
+    text-decoration: none;
+}
+
+h2 {
+    margin: 0;
+    font-size: 1em;
+    font-weight: normal;
+}
+
+
+.grid-line {
+    height: 2.6em;
+    padding-top: 1.4em;
+    padding-bottom: 0;
+}
+
+.mono, .mono-small {
+    font-family: "Lucida Sans Typewriter", courier;
+    font-size: .9em;
+}
+
+
+.clearboth {
+    clear: both;
+}
+
+#header-content, div#main-content, div#half-header-content {
+    width: 75em;
+    margin: auto;
+}
+
+
+
+
+#footer {
+    font-size: .75em;
+    color: #777;
+    border-top: 1px solid #ddd;
+    margin-top: 2.5em;
+}
diff --git a/wolnelektury/static/css/book_box.css b/wolnelektury/static/css/book_box.css
new file mode 100755 (executable)
index 0000000..2faa1a2
--- /dev/null
@@ -0,0 +1,99 @@
+.book-mini-box, .book-box {
+    display: inline-block;
+    margin: 0;
+    vertical-align: top;
+}
+
+
+.book-box {
+    width: 37.5em;
+}
+
+.book-mini-box {
+    width: 12.5em;
+}
+
+.book-mini-box a, .book-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;
+}
+
+.book-mini-box a {
+    height: 20em;
+    margin: .1em;
+}
+.book-box-inner {
+    height: 14.4em;
+    margin: .5em;
+}
+
+.book-mini-box img, .book-box img {
+    width: 10.8em;
+    height: 14.4em;
+}
+.book-mini-box img {
+    margin-bottom: .3em;
+}
+.book-box img {
+    float: left;
+    margin-right: 1.5em;
+}
+
+.book-mini-box .author {
+    color: #777;
+}
+
+
+.book-box-body {
+    height: 13em;
+    overflow: hidden;
+}
+.book-box-head {
+    min-height: 7em;
+}
+.book-box-tag {
+    font-size: .8em;
+    margin-right: .5em;
+}
+.book-box-download {
+    position: relative;
+}
+.book-box-formats {
+    display: none;
+    top: -2em;
+    position: absolute;
+    height: 2em;
+    width: 100em;
+}
+.book-box-formats a {
+    margin-right: 1em;
+}
+.book-box-download:hover .book-box-formats {
+    display: block;
+}
+
+.book-box-tools a:before {
+    content: "⇩";
+    font-family: WL-Nav;
+    font-size: 2em;
+    margin-right: .25em;
+    vertical-align: middle;
+}
+.book-box-read {
+    width: 10em;
+}
+.book-box-download {
+    width: 6em;
+}
+.book-box-audiobook {
+    width: 6em;
+}
diff --git a/wolnelektury/static/css/catalogue.css b/wolnelektury/static/css/catalogue.css
new file mode 100755 (executable)
index 0000000..c971ca8
--- /dev/null
@@ -0,0 +1,11 @@
+#books-list ol {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+#books-list li {
+    display: inline-block;
+    margin: 0;
+    padding: 0;
+}
diff --git a/wolnelektury/static/css/dialogs.css b/wolnelektury/static/css/dialogs.css
new file mode 100755 (executable)
index 0000000..dc76e6c
--- /dev/null
@@ -0,0 +1,87 @@
+.cuteform ol, .cuteform ul {
+    padding: 0;
+    margin: 0;
+    list-style: none;
+}
+
+.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;
+}
+
+
+.jqmOverlay { background-color: #000; }
+
+
+.dialog-window {
+    position: absolute;
+    display: none;
+    background-color: transparent;
+    margin-top: -0.5em;
+    margin-left: 1em;
+}
+
+.dialog-window div.header {
+    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;
+}
+
+
+#login-window {
+    width: 24em;
+}
+#register-window {
+    width: 24em;
+}
+
+#suggest-window {
+    width: 24em;
+}
+
+#suggest-window textarea {
+    width: 19em;
+    height: 6em;
+}
+
+#suggest-publishing-window {
+    width: 26em;
+}
+
+#suggest-publishing-window textarea {
+       width: 21em;
+    height: 3em;
+}
diff --git a/wolnelektury/static/css/header.css b/wolnelektury/static/css/header.css
new file mode 100755 (executable)
index 0000000..9e81463
--- /dev/null
@@ -0,0 +1,177 @@
+/* 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 {
+    color: #969696;
+    background: #191919;
+}
+
+#half-header {
+    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: 4em;
+}
+
+#logo a {
+    font-family: WL-Logo;
+    font-size: 1.9em;
+    color:#f7f7f7;
+}
+
+#logo img {
+    max-width: 15em;
+}
+
+#tagline {
+    display: inline-block;
+    margin-left: 16em;
+}
+
+#search {
+    margin: 0;
+    background: #444;
+    margin-left: 16em;
+    width: 59em;
+}
+
+#search-field {
+    display: inline-block;
+    width: 50em;
+    padding-left: .5;
+    padding-right: .5;
+    padding: .1em .5em 0 .5em;
+}
+
+#search-field input {
+    height: 2.8em;
+    border: none;
+    width: 49.5em;
+    font-size: 1em;
+    padding-left: .5em;
+    -webkit-border-radius: .5em;
+    -moz-border-radius: .5em;
+    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;
+}
+
+#search-button {
+    display: inline-block;
+    background: #02adb7;
+    padding: 0;
+    margin: 0;
+    width: 8em;
+}
+#search-button button {
+    font-size: 1em;
+    /* should match grid-line */
+    height: 4em;
+    border: none;
+    background: #02adb7;
+    color: white;
+    width: 100%;
+    padding: 0;
+}
+
+#search-button button span {
+    position:relative;
+    top: -.2em;
+}
+
+
+#nav-line {
+    background-color: #e2e2e2;
+    height: 3.95em;
+}
+
+ul#catalogue {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+ul#catalogue li {
+    background-color: #e2e2e2;
+    float: left;
+}
+ul#catalogue a {
+    display: block;
+    padding-left: 1.5em;
+    padding-right: 1.5em;
+    /* must match grid-line */
+    height: 2.7em;
+    padding-top: 1.3em;
+}
+
+
+
+#lang-button:after {
+    padding-left: .5em;
+    content: "↓";
+    font-family: WL-Nav;
+    vertical-align: middle;
+}
+#lang-menu {
+    position: relative;
+    float: right;
+    display: block;
+    padding-left: 1.5em;
+    padding-right: 1.5em;
+    /* must match grid-line */
+    height: 2.7em;
+    padding-top: 1.3em;
+    background: #f7f7f7;
+}
+
+#lang-menu-items button {
+    display: none;
+    background: #f7f7f7;
+    color: #444;
+    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 {
+    font-weight: bold;
+}
diff --git a/wolnelektury/static/css/main_page.css b/wolnelektury/static/css/main_page.css
new file mode 100755 (executable)
index 0000000..94401cb
--- /dev/null
@@ -0,0 +1,80 @@
+#big-cite {
+    background-color: white;
+    padding: 4em 12em;
+    margin: 0;
+}
+
+#big-cite a {
+    color: black;
+    display: block;
+}
+
+#big-cite h2 {
+    margin: 0;
+}
+
+
+#big-cite-text {
+    margin: .5em 0;
+    font-size: 1.75em;
+    line-height: 1.3em;
+}
+
+
+#big-cite-source {
+    color: #02adb7;
+    margin: 0;
+}
+
+
+#promo-box {
+    float: right;
+    width: 24em;
+    /* should match grid-line */
+    margin-top: -4em;
+}
+#promo-box-header {
+    padding-left: 2em;
+    padding-right: 2em;
+    background: #191919;
+    color: white;
+}
+#promo-box-body {
+    border-bottom: 2px solid #efefef;
+    padding: 1.3em 2em;
+    height: 23em;
+    background: #efefef;
+}
+#promo-box-title {
+    color: #02ADB7;
+    height: 2.7em;
+    margin: 0;
+}
+#promo-box-body p {
+    margin-top: 0;
+}
+
+.infopages-box {
+    width: 16.75em;
+    display: inline-block;
+    margin: .5em 0 0 0;
+    padding: 0 1em;
+    vertical-align: top;
+}
+.infopages-box h2 {
+    color: #02ADB7;
+}
+.infopages-box a {
+    color: black;
+}
+
+.infopages-box ol, .infopages-box ul {
+    font-size: .8em;
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.social-links {
+    margin-top: 1em;
+}
\ No newline at end of file
index f4a93e7..85f1a40 100644 (file)
@@ -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;
 }
@@ -1185,3 +1190,27 @@ div.shown-tags p, div.all-tags p {
 #footnotes .pagination {
     margin-top: 1em;
 }
+
+
+/* report */
+.stats td {
+    vertical-align: top;
+}
+
+/* ============ */
+/* = 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;
+}
+
index a1798b1..1624f5f 100644 (file)
@@ -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/fonts/WL-Nav.ttf b/wolnelektury/static/fonts/WL-Nav.ttf
new file mode 100644 (file)
index 0000000..05282b8
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 (file)
index 0000000..53fedbd
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 (file)
index 0000000..7feb6b4
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 (file)
index 0000000..f7e572e
Binary files /dev/null and b/wolnelektury/static/img/bg-header.png differ
index 02f5b6c..4857806 100644 (file)
@@ -594,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 (executable)
index 0000000..0793a7f
--- /dev/null
@@ -0,0 +1,56 @@
+(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() {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)
+                            }
+                            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)
+
index 248bb19..3aac816 100644 (file)
 /*
  * 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 (executable)
index 0000000..e765548
--- /dev/null
@@ -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 (executable)
index 0000000..00c0742
--- /dev/null
@@ -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/sponsors.js b/wolnelektury/static/js/sponsors.js
new file mode 100755 (executable)
index 0000000..7674379
--- /dev/null
@@ -0,0 +1,8 @@
+(function($) {
+    $(function() {
+
+        $('.sponsor-logos').cycle({timeout: 3000});
+
+    });
+})(jQuery)
+
index 1277bd2..fe19d30 100644 (file)
@@ -66,3 +66,8 @@
     background-color: #EEE;
     cursor: default;
 }
+
+.sponsors-sponsor img {
+    max-width: 120px;
+    max-height: 120px;
+}
diff --git a/wolnelektury/templates/auth/login.html b/wolnelektury/templates/auth/login.html
deleted file mode 100644 (file)
index a168ff7..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-
-{% block title %}{% trans "Sign in" %} / {% trans "Register on"%} WolneLektury.pl{% endblock %}
-
-{% block body %}
-    <h1>{% trans "Sign in" %} / {% trans "Register"%}</h1>
-    <form action="." method="get" accept-charset="utf-8" id="search-form">
-        <p><li>{{ search_form.q }} <input type="submit" value="{% trans "Search" %}" /></li> <strong>{% trans "or" %}</strong> <a href="{% url main_page %}">{% trans "return to main page" %}</a></p>
-    </form>
-    <form method="post" action="." id="login-form" class="cuteform">
-        <h2>{% trans "Sign in" %}</h2>
-        <ol>
-            {{ form.as_ul }}
-            <li><input type="submit" value="{% trans "Sign in" %}" /></li>
-        </ol>
-        <p><input type="hidden" name="next" value="{{ next }}" /></p>
-    </form>
-
-    <form action="." method="post" accept-charset="utf-8" id="registration-form">
-        <h2>{% trans "Register" %}</h2>
-
-        <p><input type="submit" value="{% trans "Register" %}"/></p>
-    </form>
-{% endblock %}
index 061197f..534f5cf 100644 (file)
 <!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">
-       {% load i18n chunks compressed catalogue_tags sponsor_tags %}
+       {% load i18n compressed catalogue_tags sponsor_tags %}
     <head>
         <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
         <meta http-equiv="Content-Style-Type" content="text/css" />
         <meta name="description" 
             content="{% block metadescription %}Darmowe opracowane, pełne teksty lektur, e-booki, audiobooki i pliki DAISY na wolnej licencji.{% endblock %}" />
-        <title>{% block title %}WolneLektury.pl{% endblock %}</title>
+        <title>{% block title %}{% trans "Wolne Lektury" %} :: 
+            {% block titleextra %}{% endblock %}{% endblock %}</title>
         <link rel="icon" href="{{ STATIC_URL }}img/favicon.png" type="image/png" />
         <link rel="search" type="application/opensearchdescription+xml" title="Wolne Lektury" href="{{ STATIC_URL }}opensearch.xml" />
         {% compressed_css "all" %}
-        <script type="text/javascript">var LANGUAGE_CODE = "{{ LANGUAGE_CODE }}";</script>
-        {% compressed_js "jquery" %}
-        {% compressed_js "all" %}
+
         {% block extrahead %}
         {% endblock %}
     </head>
     <body id="{% block bodyid %}base{% endblock %}">
-        <!--[if lt IE 7]><link href={{ STATIC_URL }}infobar/infobar.css rel=stylesheet>
-        <div id=infobar><a href=http://browsehappy.pl/infobar>
-        {% trans "Internet Explorer cannot display this site properly. Click here to read more..." %}
-        </a></div><div id=viewplot><script src={{ STATIC_URL }}infobar/infobar.js></script><![endif]-->
+
         {% block bodycontent %}
-        <div id="top-message">
-            {% chunk "top-message" %}
-        </div>
-        <div id="header">
+
+        <div id="header" class="grid-line">
+
+        <div id="header-content">
             <div id="logo">
-                <a class="logo" href="/"><img src="{% block logo_url %}{{ STATIC_URL }}img/logo-bez.png{% endblock %}" alt="WolneLektury.pl" />
-                <br/>szkolna biblioteka internetowa</a>
-                {% block tagline %}{% endblock %}
+                <a class="logo" href="/">
+                Wolne Lektury</a>
             </div>
-            <div id="user-info" style="display:none">
+
+            <div id="tagline">
+                <a href=''>1666</a> darmowych lektur do których masz <a href=''>prawo</a>
+            </div>
+
+            <p id="user-info" class="mono">
                 {% if user.is_authenticated %}
-                    <p>
-                        {% trans "Welcome" %}, <strong>{{ user.username }}</strong>
-                        | <a href="{% url user_shelves %}" id="user-shelves-link">{% trans "Your shelves" %}</a>
-                        {% if user.is_staff %}
-                        | <a href="/admin/">{% trans "Administration" %}</a>
-                        {% endif %}
-                                               | <a href="{% url suggest %}" id="suggest-link">{% trans "Report a bug" %}</a>
-                        | <a href="{% url logout %}?next={{ request.get_full_path }}">{% trans "Logout" %}</a>
-                    </p>
+                    {% trans "Welcome" %}, <strong>{{ user.username }}</strong>
+                    | <a href="{% url user_shelves %}" id="user-shelves-link">{% trans "Your shelves" %}</a>
+                    {% if user.is_staff %}
+                    | <a href="/admin/">{% trans "Administration" %}</a>
+                    {% endif %}
+                    | <a href="{% url logout %}?next={{ request.get_full_path }}">{% trans "Logout" %}</a>
                 {% else %}
-                    <p><a href="{% url suggest %}" id="suggest-link">{% trans "Report a bug" %}</a>
-                        | <a href="{% url login %}" class="login-register-link">{% trans "Sign in" %} / {% trans "Register" %}</a></p>
+                    <a href="{% url login %}?next={{ request.path }}"
+                        id="login" class="ajaxable">
+                            {% trans "Sign in" %}</a>
+                    /
+                    <a href="{% url register %}?next={{ request.path }}"
+                        id="register" class="ajaxable">
+                            {% trans "Register" %}</a>
                 {% endif %}
+            </p>
+
+
+            <div class="clearboth"></div>
+
+        </div>
+        </div>
+
+        <div id="half-header">
+        <div id="half-header-content">
+
+
+
+            <form id="search">
+                
+                <span id="search-field" class="grid-line">
+                    <input title="np. Leśmian" name="q" autocomplete="off">
+                </span><span id="search-button">
+                    <button type='submit'><span class="mono">{% trans "Search" %}</span></button>
+                </span>
+                
+            </form>
+
+
+
+            <div class="clearboth"></div>
+        </div>
+        </div>
+
+
+
+        <div id="main-content">
+
+            <div id="nav-line">
+            <ul id="catalogue">
+                <li><a href="{% url book_list %}"><span class='mono'>{% trans "All books" %}</span></a></li>
+                <li><a href="{% url audiobook_list %}"><span class='mono'>{% trans "Audiobooks" %}</span></a></li>
+                <li><a href="{% url daisy_list %}"><span class='mono'>{% trans "DAISY" %}</span></a></li>
+                <li><a href="{% url catalogue %}#autorzy"><span class='mono'>{% trans "Authors" %}</span></a></li>
+                <li><a href="{% url catalogue %}#epoki"><span class='mono'>{% trans "Epochs" %}</span></a></li>
+                <li><a href="{% url catalogue %}#rodzaje"><span class='mono'>{% trans "Kinds" %}</span></a></li>
+                <li><a href="{% url catalogue %}#gatunki"><span class='mono'>{% trans "Genres" %}</span></a></li>
+                <li><a href="{% url catalogue %}#motywy"><span class='mono'>{% trans "Themes" %}</span></a></li>
+            </ul>
+
+            <form action="{% url django.views.i18n.set_language %}" method="post">
+            <div id="lang-menu">
+                <span id='lang-button' class='mono-small'>
+                    {% trans "Language versions" %}</span>
+                <div id="lang-menu-items">
+                {% for lang in LANGUAGES %}
+                    <button type="submit" name="language"
+                        class="{% ifequal lang.0 LANGUAGE_CODE %}active{% endifequal %} {% if forloop.last %}last{% endif %}"
+                        value="{{ lang.0 }}">{{ lang.1 }}</button>
+                {% endfor %}
+                </div>
             </div>
-            <div class="social-links" style="float:right">
-                <a href="http://pl-pl.facebook.com/pages/Wolne-Lektury/203084073268"><img src="{{ STATIC_URL }}img/social/facebook.png" alt="WolneLektury @ Facebook" /></a>
-                <a href="http://nk.pl/profile/30441509"><img src="{{ STATIC_URL }}img/social/naszaklasa.png" alt="WolneLektury @ NK" /></a>
-            </div>
-                               <form action="{% url django.views.i18n.set_language %}" method="post">
-            <div class="lang-menu" style="float:right;">
-                                       {% spaceless %}
-                                       {% for lang in LANGUAGES %}                                          
-                                           <button type="submit" name="language" title="{{ lang.1 }}"
-                                                       class="{% ifequal lang.0 LANGUAGE_CODE %}active{% endifequal %} {% if forloop.last %}last{% endif %}" 
-                                                       value="{{ lang.0 }}">{{ lang.0|upper }}</button>
-                                       {% endfor %}    
-                                       {% endspaceless %}                              
-                       </div>
-                </form>
-            {# publication plan consultations - form link #}
-            <div style="clear:right;float:right" class="big-top-link">
-                <a href="{% url suggest_publishing %}" data-ajax="{% url suggest_publishing %}?ajax=1" id="suggest-publishing-link">
-                    {% trans "Didn't find a book? Make a suggestion." %}
-                </a>
+            </form>
             </div>
+
             <div class="clearboth"></div>
-        </div>
-        <div id="maincontent">
+
+
+
             {% block body %}
             {% endblock %}
-        </div>
+
+
+
+
         <div class="clearboth"></div>
+
+
+
         <div id="footer">
             <p>
                {% blocktrans %}
 
                        {% sponsor_page "footer" %}
         </div>
-        <div id="login-register-window">
-            <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-            <div class="target">
-                <form method="post" action="{% url login %}" id="login-form" class="cuteform">
-                    <h2>{% trans "Sign in" %} / <a href="#" id="show-registration-form" style="font-size: 0.85em; font-weight: normal">{% trans "Register" %}</a></h2>
-                    <p><span id="id_login-__all__"></span></p>
-                    <ol>
-                        {% authentication_form %}
-                        <li><input type="submit" value="{% trans "Sign in" %}" /></li>
-                    </ol>
-                </form>
-                <form method="post" action="{% url register %}" id="registration-form" class="cuteform" style="display: none;">
-                    <h2><a href="#" id="show-login-form" style="font-size: 0.85em; font-weight: normal">{% trans "Sign in" %}</a> / {% trans "Register" %}</h2>
-                    <p><span id="id_registration-__all__"></span></p>
-                    <ol>
-                        {% user_creation_form %}
-                        <li><input type="submit" value="{% trans "Register" %}" /></li>
-                    </ol>
-                </form>
-            </div>
-        </div>
-        <div id="user-shelves-window">
-            <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-            <div class="target">
-                <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-            </div>
-        </div>
-        <div id="suggest-window">
-            <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-            <div class="target">
-                <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-            </div>
-        </div>
-        <div id="suggest-publishing-window">
-            <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
+
+        </div>{# end main-content #}
+
+
+        {# template #}
+        <div id="ajaxable-window" class='dialog-window'>
+            <div class="header mono"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
             <div class="target">
                 <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
             </div>
         </div>
+
+
         {% endblock bodycontent %}
-        {{ piwik_tag|safe }}
+
+
+        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+        <script type="text/javascript">
+            var LANGUAGE_CODE = "{{ LANGUAGE_CODE }}";
+            var STATIC_URL = "{{ STATIC_URL }}";
+        </script>
+        {% compressed_js "base" %}
+
+        <!--{{ piwik_tag|safe }}
         <script type="text/javascript">
         var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
         document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
         <script type="text/javascript">
         var pageTracker = _gat._getTracker("UA-2576694-1");
         pageTracker._trackPageview();
-        </script>
+        </script>-->
     </body>
 </html>
index a9b8ba0..d794cd9 100644 (file)
@@ -3,7 +3,7 @@
 
 {% block bodyid %}book-a-list{% endblock %}
 
-{% block title %}{% trans "Listing of all audiobooks on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Listing of all audiobooks" %}{% endblock %}
 
 {% block metadescription %}Darmowe audiobooki na wolnej licencji. Lektury czytane przez znanych aktorów.{% endblock %}
 
index ff7b519..5c0a047 100644 (file)
@@ -2,7 +2,7 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{{ book.title }} {% trans "on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{{ book.title }}{% endblock %}
 
 {% block metadescription %}{% book_title book %}. {{ block.super }}{% endblock %}
 
@@ -10,9 +10,6 @@
 
 {% block body %}
     <h1>{% book_title book %}</h1>
-    <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 id='breadcrumbs'>
             <div id="toggle-description"><p></p></div>
         {% endif %}
         <div id="formats">
-            <p class="change-sets">{% trans "Put a book" %} <span><a href="{% url catalogue.views.book_sets book.slug %}" class="jqm-trigger">{% trans "on the shelf!" %}</a></span></p>
+            <p class="change-sets">{% trans "Put a book" %} <span><a href="{% url catalogue.views.book_sets book.urlid %}" class="jqm-trigger">{% trans "on the shelf!" %}</a></span></p>
             <div class="clearboth"></div>
             <div class="wrap">
                 {% if book.has_html_file %}
-                    <a class="online" href="{% url book_text book.slug %}">{% trans "Read online" %}</a>
+                    <a class="online" href="{% url book_text book.fileid %}">{% trans "Read online" %}</a>
                 {% endif %}
                 <div class="download">
                     {% if book.pdf_file %}
                         <a href="{{ book.pdf_file.url }}"><img src="{{ STATIC_URL }}img/pdf.png" title="{% trans "Download PDF" %} &ndash; {% trans "for reading" %} {% trans "and printing using" %} Adobe Reader" %}" alt="{% trans "Download PDF" %}" /></a>
                     {% endif %}
-                    {% if book.root_ancestor.epub_file %}
-                        <a href="{{ book.root_ancestor.epub_file.url }}"><img src="{{ STATIC_URL }}img/epub.png" title="{% trans "Download EPUB" %} &ndash; {% trans "for reading" %} {% trans "on mobile devices" %}" alt="{% trans "Download EPUB" %}" /></a>
+                    {% if book.epub_file %}
+                        <a href="{{ book.epub_file.url }}"><img src="{{ STATIC_URL }}img/epub.png" title="{% trans "Download EPUB" %} &ndash; {% trans "for reading" %} {% trans "on mobile devices" %}" alt="{% trans "Download EPUB" %}" /></a>
                     {% endif %}
                     {% if book.mobi_file %}
                         <a href="{{ book.mobi_file.url }}"><img src="{{ STATIC_URL }}img/mobi.png" title="{% trans "Download MOBI" %} &ndash; {% trans "for reading" %} {% trans "on mobile devices" %}" alt="{% trans "Download MOBI" %}" /></a>
                     {% if book.txt_file %}
                         <a href="{{ book.txt_file.url }}"><img src="{{ STATIC_URL }}img/txt.png" title="{% trans "Download TXT" %} &ndash; {% trans "for reading" %} {% trans "on small displays, for example mobile phones" %}" alt="{% trans "Download TXT" %}" /></a>
                     {% endif %}
-                    {% for media in book.get_odt %}
-                        <a href="{{ media.file.url }}"><img src="{{ STATIC_URL }}img/odt.png" title="{% trans "Download ODT" %} &ndash; {% trans "for reading" %} {% trans "and editing using" %} OpenOffice.org: {{ media.name }}" alt="{% trans "Download ODT" %}" /></a>
-                    {% endfor %}
+                       
+                    {% if book.pdf_file %}
+                       <br/><a href="#" id="custom-pdf-link">{% trans "Dowload customized PDF" %}</a>.
+                   {% endif %}
+                       <div style="display: none" class="custom-pdf">
+                         <form action="{% url catalogue.views.download_custom_pdf book.fileid %}" method="GET">
+                           {{custom_pdf_form.as_p}}
+                           <input type="submit" value="{% trans "Download" %}"/>
+                         </form>
+                       </div>
                 </div>
                 {% if book.has_mp3_file or book.has_ogg_file or book.has_daisy_file %}
                     <p class="header">
                 {% endif %}
             </ul>
             <p><a href="{{ book.xml_file.url }}">{% trans "View XML source" %}</a></p>
-            <p><a href="{% url poem_from_book book.slug %}">Miksuj ten utwór</a></p>
+            <p><a href="{% url poem_from_book book.urlid %}">Miksuj ten utwór</a></p>
         </div>
         <div id="themes-list">
             <h2>{% trans "Work's themes " %}</h2>
             <ul>
             {% for theme in book_themes %}
-                <li><a href="{% url book_fragments book.slug,theme.slug %}">{{ theme }} ({{ theme.count }})</a></li>
+                <li><a href="{% url book_fragments book.urlid theme.slug %}">{{ theme }} ({{ theme.count }})</a></li>
             {% endfor %}
             </ul>
         </div>
index e71a190..6f5f8c8 100644 (file)
@@ -2,15 +2,12 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{% trans "Theme" %} {{ theme }} {% trans "in work " %} {{ book }} {% trans "on" %} WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Theme" %} {{ theme }} {% trans "in work " %} {{ book }}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
     <h1>{% trans "Theme" %} {{ theme }} {% trans "in work " %} {{ book }} </h1>
-    <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="{{ book.get_absolute_url }}">{% trans "return to book's page" %}</a></p>
-    </form>
 
     {% autopaginate fragments 10 %}
     <div id="books-list">
index dd01066..b4336d3 100644 (file)
@@ -4,19 +4,11 @@
 
 {% block bodyid %}book-a-list{% endblock %}
 
-{% block title %}{% trans "Listing of all works on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Listing of all works" %}{% endblock %}
 
 {% block body %}
     <h1>{% block book_list_header %}{% trans "Listing of all works" %}{% endblock %}</h1>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} {{ form.tags }} <input type="submit" value="{% trans "Search" %}" />
-            <strong>{% trans "or" %}</strong> {% trans "see" %}: 
-            <span class='collections'>
-                <a href="{% url catalogue.views.book_list %}">{% trans "all books" %}</a>
-                <a href="{% url catalogue.views.audiobook_list %}">{% trans "audiobooks" %}</a>
-                <a href="{% url catalogue.views.daisy_list %}">{% trans "DAISY" %}</a>
-            </span></p>
-    </form>
+
     <div class="column-left">{% block book_list_info %}{% endblock %}</div><div style='clear:both;'></div>
     <a name="top">
     <div id="book-list-nav">
@@ -33,6 +25,7 @@
         {% endfor %}    
     </div>
     <div id="book-list">
+      {% block book_list %}
         {% book_tree orphans books_by_parent %}
         {% for author, group in books_by_author.items %}
             {% if group %}
@@ -43,6 +36,7 @@
                 </div>
             {% endif %}
         {% endfor %}
+      {% endblock %}
     </div>
     <div id="book-list-up">
         <p><a href="#top">{% trans "↑ top ↑" %}</a></p>
diff --git a/wolnelektury/templates/catalogue/book_mini_box.html b/wolnelektury/templates/catalogue/book_mini_box.html
new file mode 100755 (executable)
index 0000000..9b61283
--- /dev/null
@@ -0,0 +1,20 @@
+{% load thumbnail %}
+<div class="book-mini-box">
+    <a href="{{ book.get_absolute_url }}">
+        {% if book.cover %}
+            <img src="
+                {% thumbnail book.cover "216x288" as thumb %}
+                    {{ thumb.url }}
+                {% empty %}
+                    {{ book.cover.url }}
+                {% endthumbnail %}
+            " alt="Cover" />
+        {% endif %}
+        {% for author in authors %}
+            <div class="mono author">{{ author }}</div>
+            {{ book.title }}
+        {% endfor %}
+    </a>
+</div>
+
+
index 1eee61d..f151650 100644 (file)
@@ -9,7 +9,7 @@
 {% if not user.tag_set.count %}
     <p>{% trans "You do not have any shelves. You can create one below, if you want to."%}</p>
 {% else %}
-    <form action="{% url catalogue.views.book_sets book.slug %}" method="POST" id="putOnShelf" accept-charset="utf-8" class="cuteform">
+    <form action="{% url catalogue.views.book_sets book.urlid %}" method="POST" id="putOnShelf" accept-charset="utf-8" class="cuteform">
     <ol>
         <li>{{ form.set_ids }}</li>
         <li><input type="submit" value="{% trans "Put on the shelf!" %}"/></li>
index 8b36718..1b64666 100644 (file)
@@ -1,18 +1,71 @@
 {% load i18n %}
-<div class="book">
-    <div class="change-sets">
-        <a href="{% url catalogue.views.book_sets book.slug %}" class="jqm-trigger">{% trans "Put on the shelf!" %}</a>
-    </div>
-    {% if book.children.all|length %}
-        <div class="book-parent-thumbnail"></div>
-    {% else %}
-        <div class="book-thumbnail"></div>
-    {% endif %}
-    <div class="book-description">
-        <h2><a href="{{ book.get_absolute_url }}">{{ book.title }}</a></h2>
-        {% if formats %}
-            <p style="margin: 0">{% trans "Jump to" %}: {{ formats|join:", " }}</p>
+{% load thumbnail %}
+<div class="book-box">
+<div class="book-box-inner">
+    <a href="{{ book.get_absolute_url }}">
+        {% if book.cover %}
+            <img src="
+                {% thumbnail book.cover "216x288" as thumb %}
+                    {{ thumb.url }}
+                {% empty %}
+                    {{ book.cover.url }}
+                {% endthumbnail %}
+            " alt="Cover" />
         {% endif %}
-        <p style="margin: 0">{% trans "Categories" %}: {{ tags|join:", " }}</p>
+    </a>
+    <div class="book-box-body">
+        <div class="book-box-head">
+            <div class="mono author">
+            {% for author in tags.author %}
+                {{ author }}
+            {% endfor %}
+            </div>
+            <div class="title">{{ book.title }}</div>
+        </div>
+        <div class="tags">
+            {% spaceless %}
+
+            <span class="mono">{% trans "Epoch" %}:&nbsp;</span>
+            <span class="book-box-tag">
+                {% for tag in tags.epoch %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+                {% endfor %}
+            </span>
+
+            <span class="mono">{% trans "Kind" %}:&nbsp;</span>
+            <span class="book-box-tag">
+                {% for tag in tags.kind %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+                {% endfor %}
+            </span>
+
+            <span class="mono">{% trans "Genre" %}:&nbsp;</span>
+            <span class="book-box-tag">
+                {% for tag in tags.genre %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }} </a>
+                {% endfor %}
+            </span>
+
+            {% endspaceless %}
+        </div>
     </div>
-</div>
\ No newline at end of file
+    <ul class="book-box-tools">
+        <li class="book-box-read">
+        {% if book.html_file %}
+            <a href="" class="mono">{% trans "Read online" %}</a>
+        {% endif %}
+        </li>
+        <li class="book-box-download">
+            <a class="mono">{% trans "Download" %}</a>
+            <div class="book-box-formats mono">
+                {{ formats|join:"" }}
+            </div>
+        </li>
+        <li class="book-box-audiobook">
+        {% if book.has_mp3_file %}
+            <a href="" class="mono">{% trans "Audiobook" %}</a>
+        {% endif %}
+        </li>
+    </ul>
+</div>
+</div>
diff --git a/wolnelektury/templates/catalogue/breadcrumbs.html b/wolnelektury/templates/catalogue/breadcrumbs.html
deleted file mode 100644 (file)
index 5aafeb9..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-{% load i18n %}
-{% load catalogue_tags %}
-<div id="searchContainer">
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-    <ul class="facelist-selections" style="float:left;">
-        {% for tag in tag_list %}
-        <li class="facelist-selection-item">
-               <span style="margin-left: 5px;">
-               <a href="{% catalogue_url tag %}">{{ tag }}</a>
-               <a class="facelist-close" href="{% catalogue_url tag_list -tag %}">x</a>
-               </span>
-           </li>
-        {% empty %}
-            <li class="facelist-selection-item">
-            </li>
-        {% endfor %}
-        {% if search_form %}
-            <li>{{ search_form.q }} {{ search_form.tags }}</li>
-        {% endif %}
-    </ul>
-    <input type="submit" value="{% trans "Search" %}" id="searchSubmit"/>
-    </form>
-</div>    
-<div class="clearboth"></div>
diff --git a/wolnelektury/templates/catalogue/catalogue.html b/wolnelektury/templates/catalogue/catalogue.html
new file mode 100644 (file)
index 0000000..1423577
--- /dev/null
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load catalogue_tags %}
+
+
+{% block titleextra %}{% trans "Catalogue" %}{% endblock %}
+
+{% block bodyid %}catalogue-catalogue{% endblock %}
+
+{% block body %}
+    <h1>{% trans "Catalogue" %}</h1>
+
+    <h2></a>{% trans "Authors" %}<a name="autorzy"></a></h2>
+    {% tag_list categories.author %}
+
+    <h2>{% trans "Kinds" %}<a name="rodzaje"></a></h2>
+    {% tag_list categories.kind %}
+
+    <h2>{% trans "Genres" %}<a name="gatunki"></a></h2>
+    {% tag_list categories.genre %}
+
+    <h2>{% trans "Epochs" %}<a name="epoki"></a></h2>
+    {% tag_list categories.epoch %}
+
+    <h2>{% trans "Themes and topics" %}<a name="motywy"></a></h2>
+    {% tag_list fragment_tags %}
+{% endblock %}
index b637bed..88e95a4 100644 (file)
@@ -3,7 +3,7 @@
 
 {% block bodyid %}book-a-list{% endblock %}
 
-{% block title %}{% trans "Listing of all DAISY files on WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Listing of all DAISY files" %}{% endblock %}
 
 {% block extrahead %}
     <link rel="alternate" type="application/atom+xml" title="{% trans "Latest DAISY audiobooks" %}" href="{% url audiobook_feed 'daisy' %}" />
index 1e7a1a7..b40d3fe 100644 (file)
@@ -2,13 +2,12 @@
 {% load i18n %}
 {% load catalogue_tags %}
 
-{% block title %}{% title_from_tags tags %} w WolneLektury.pl{% endblock %}
+{% block titleextra %}{% title_from_tags tags %}{% endblock %}
 
 {% block bodyid %}differentiate_tags{% endblock %}
 
 {% block body %}
     <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
 
        <p>{% trans "The criteria are ambiguous. Please select one of the following options:" %}</p>
     <div id="books-list">
diff --git a/wolnelektury/templates/catalogue/folded_tag_list.html b/wolnelektury/templates/catalogue/folded_tag_list.html
deleted file mode 100644 (file)
index a6ff53a..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-{% load i18n %}
-{% load catalogue_tags %}
-{% if one_tag %}
-    <p>{% trans "Show full category" %} <a href="{% catalogue_url one_tag %}">{{ one_tag }}</a>.</p>
-{% else %}
-    <div class="shown-tags">
-        <ul class="shown-tags">
-            {% for tag in shown_tags %}
-                <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
-            {% endfor %}
-        </ul>
-        {% if some_tags_hidden %}
-            <p><a href="#" class="show-all-tags">{% trans "See more" %}</a></p>
-        {% endif %}
-    </div>
-    <div class="all-tags">
-        <ul>
-            {% for tag in tags %}
-                <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
-            {% endfor %}
-        </ul>
-        <p><a href="#" class="hide-all-tags">{% trans "Hide" %}</a></p>
-    </div>
-{% endif %}
diff --git a/wolnelektury/templates/catalogue/main_page.html b/wolnelektury/templates/catalogue/main_page.html
deleted file mode 100644 (file)
index 8bd5bde..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-{% load catalogue_tags chunks cache stats %}
-
-
-{% block tagline %}
-<div id='tagline'>
-
-{% count_books_nonempty count_books %}
-{% blocktrans count count_books as c %}
-{{c}} book from <a href='http://domenapubliczna.org'>public domain</a> or under
-a <a href='http://creativecommons.org/licenses/by-sa/3.0/deed.pl'>free license</a>.
-{% plural %}
-{{c}} books from <a href='http://domenapubliczna.org'>public domain</a> or under
-a <a href='http://creativecommons.org/licenses/by-sa/3.0/deed.pl'>free license</a>.
-{% endblocktrans %}
-
-</div>
-{% endblock %}
-
-
-{% block bodyid %}main-page{% endblock %}
-
-{% block body %}
-    <div id="site-description">
-        {% chunk "site-description" %}
-    </div>
-    <div class="clearboth"></div>
-    <form action="{% url search %}" method="get" accept-charset="utf-8" id="search-form">
-        <p>{{ form.q }} {{ form.tags }} <input type="submit" value="{% trans "Search" %}" />
-            <strong>{% trans "or" %}</strong> {% trans "see" %}: 
-            <span class='collections'>
-                <a href="{% url catalogue.views.book_list %}">{% trans "all books" %}</a>
-                <a href="{% url catalogue.views.audiobook_list %}">{% trans "audiobooks" %}</a>
-                <a href="{% url catalogue.views.daisy_list %}">{% trans "DAISY" %}</a>
-            </span></p>
-    </form>
-
-    <div id="intro">
-        <p id="tags-description">↓ {% trans "Browse books by categories" %} ↓</p>
-        <div id="propaganda">
-            <h2>Turniej Elektrybałtów</h2>
-            <p><a target="_blank" href='http://turniej.wolnelektury.pl'>
-                <img src='{{ STATIC_URL }}img/turniej-maly.png' alt='Logo Turnieju' width='60' height='64' style='float:left;margin:1em;' />
-            </a>
-            Ogłaszamy <strong>otwarty konkurs na najlepszego automatycznego poetę</strong>! Konkurs startuje
-            12 września 2011 roku – z okazji 90 rocznicy urodzin Stanisława Lema.
-            Termin nadsyłania prac upływa 10 listopada 2011 roku.
-            </p>
-            <p class="see-more"><a target="_blank" href="http://turniej.wolnelektury.pl" >Idź do strony konkursu ⇒</a></p>
-
-            <h2>Wolne Lektury na Kindle</h2>
-
-            <p><a target="_blank" href='{{ STATIC_URL }}img/kindle-poster.png'>
-                <img src='{{ STATIC_URL }}img/kindle-poster-260.png' alt='Wolne Lektury na Kindle' width='260' height='260' />
-            </a></p>
-
-            <p>Dobra wiadomość dla wszystkich posiadaczy czytnika Kindle – od
-            dzisiaj książki w bibliotece Wolne Lektury dostępne są w nowym formacie MOBI.</p>
-
-            <p>Życzymy miłego czytania!</p>
-
-
-            {% comment %}    <h2>{% trans "Books for every school level" %}</h2>
-            <ul>
-                <li><a href="">{% trans "primary school" %}</a></li>
-                <li><a href="">{% trans "gymnasium" %}</a></li>
-                <li><a href="">{% trans "high school" %}</a></li>
-            </ul>    {% endcomment %}
-
-            <h2>{% trans "Your shelves with books" %}</h2>
-            {% if user.is_authenticated %}
-                {% if shelves %}
-                <ul class="shelf-list">
-                {% for shelf in shelves %}
-                    <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "delete" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.get_count }})</a></li>
-                {% endfor %}
-                </ul>
-                {% else %}
-                <p>{% trans "You do not own any shelves. You can create one below, if you want to." %}</p>
-                {% endif %}
-                <hr />
-                <form action="{% url catalogue.views.new_set %}" method="post" accept-charset="utf-8" class="cuteform">
-                <ol>
-                    <li>{{ new_set_form.name }} <input type="submit" value="{% trans "Create shelf" %}"/></li>
-                </ol>
-                </form>
-                <br/>
-            {% else %}
-                <p>{% trans "Create your own book set. You can share it with friends by sending them link to your shelf." %}</p>
-                <p>{% trans "You need to " %} <a class="login-register-link" href="#">{% trans "sign in" %}</a> {% trans "to manage your shelves." %}</p>
-            {% endif %}
-
-            <h2>Nowy Leśmianator</h2>
-            <p>Tym razem to Ty decydujesz, co wpadnie do miksera &ndash; a efekt możesz pokazać znajomym!
-                </p>
-            <p class="see-more" style="clear:both"><a href="{% url lesmianator %}">{% trans "Twórzże się!" %} ⇒</a></p>
-
-            <h2>{% trans "Wolne Lektury Widget" %}</h2>
-            <p>{% trans "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:" %}</p>
-            <textarea rows="1" cols="35" class='widget-code' title='widget'><!-- START {% trans "Insert this element in place where you want display the widget" %} -->
-&lt;div id="wl" /&gt;
-&lt;!-- END --&gt;
-&lt;!-- START {% trans "Place this element just before closing body tag: &lt;/body&gt;" %} --&gt;
-&lt;script type="text/javascript" src="http://www.wolnelektury.pl/static/js/widget.js"&gt;&lt;/script&gt;
-&lt;!-- END --&gt;</textarea>
-                <p class="see-more"><a href="{% url widget %}" title="{% trans "Wolne Lektury Widget" %}">{% trans "See more" %} ⇒</a></p>
-
-            <div id="lessons">
-                <h2><a href="{% url lessons_document_list %}">{% trans "Hand-outs for teachers" %}</a></h2>
-                <p>{% trans "Lessons' prospects and other ideas for using Wolnelektury.pl for teaching." %}</p>
-                <p class="see-more"><a href="{% url lessons_document_list %}" title="{% trans "Hand-outs for teachers" %}">{% trans "See more" %} ⇒</a></p>
-            </div>
-        </div>
-        <div id="tags-list">
-            <div id="categories-list">
-                {% if categories.author %}
-                    <h2>{% trans "Authors" %}</h2>
-                    {% folded_tag_list categories.author 'Authors' %}
-                {% endif %}
-                {% if categories.kind %}
-                    <h2>{% trans "Kinds" %}</h2>
-                    {% folded_tag_list categories.kind 'Kinds' %}
-                {% endif %}
-                {% if categories.genre %}
-                    <h2>{% trans "Genres" %}</h2>
-                    {% folded_tag_list categories.genre 'Genres' %}
-                {% endif %}
-                {% if categories.epoch %}
-                    <h2>{% trans "Epochs" %}</h2>
-                    {% folded_tag_list categories.epoch 'Epochs' %}
-                {% endif %}
-            </div>
-            <div id="themes-list">
-                {% if fragment_tags %}
-                    <h2>{% trans "Themes and topics" %}</h2>
-                    {% folded_tag_list fragment_tags 'Themes' %}
-                {% endif %}
-                <h2>{% trans "Themes groups" %}</h2>
-                <div class="shown-tags">
-                    <ol>
-                        <li>środowisko miejskie i wiejskie
-                        <span class="subcategories"><a href="/katalog/motyw/miasto/">Miasto</a>, <a href="/katalog/motyw/warszawa/">Warszawa</a>, <a href="/katalog/motyw/mieszczanin/">Mieszczanin</a>, <a href="/katalog/motyw/handel/">Handel</a>, <a href="/katalog/motyw/robotnik/">Robotnik</a>, <a href="/katalog/motyw/zyd/">Żyd</a>, <a href="/katalog/motyw/wies/">Wieś</a>, <a href="/katalog/motyw/sielanka/">Sielanka</a>, <a href="/katalog/motyw/chlop/">Chłop</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/natura/">Natura</a>, <a href="/katalog/motyw/przestrzen/">Przestrzeń</a></span></li>
-
-                        <li>polityczny obraz świata
-                        <span class="subcategories"><a href="/katalog/motyw/panstwo/">Państwo</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/cnota/">Cnota</a>, <a href="/katalog/motyw/obywatel/">Obywatel</a>, <a href="/katalog/motyw/patriota/">Patriota</a>, <a href="/katalog/motyw/ojczyzna/">Ojczyzna</a>, <a href="/katalog/motyw/narod/">Naród</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/rycerz/">Rycerz</a>, <a href="/katalog/motyw/zolnierz/">Żołnierz</a>, <a href="/katalog/motyw/wojna/">Wojna</a>, <a href="/katalog/motyw/wrog/">Wróg</a>, <a href="/katalog/motyw/zwyciestwo/">Zwycięstwo</a>, <a href="/katalog/motyw/walka/">Walka</a>, <a href="/katalog/motyw/sila/">Siła</a>, <a href="/katalog/motyw/historia/">Historia</a>, <a href="/katalog/motyw/powstanie/">Powstanie</a>, <a href="/katalog/motyw/smierc-bohaterska/">Śmierć bohaterska</a>, <a href="/katalog/motyw/slawa/">Sława</a>, <a href="/katalog/motyw/rewolucja/">Rewolucja</a>, <a href="/katalog/motyw/sad/">Sąd</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a></span></li>
-
-                        <li>przyroda
-                        <span class="subcategories"><a href="/katalog/motyw/natura/">Natura</a>, <a href="/katalog/motyw/zywioly/">Żywioły</a>, <a href="/katalog/motyw/ogien/">Ogień</a>, <a href="/katalog/motyw/ziemia/">Ziemia</a>, <a href="/katalog/motyw/wiatr/">Wiatr</a>, <a href="/katalog/motyw/woda/">Woda</a>, <a href="/katalog/motyw/wiosna/">Wiosna</a>, <a href="/katalog/motyw/lato/">Lato</a>, <a href="/katalog/motyw/jesien/">Jesień</a>, <a href="/katalog/motyw/zima/">Zima</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/slonce/">Słońce</a>, <a href="/katalog/motyw/ksiezyc/">Księżyc</a>, <a href="/katalog/motyw/gwiazda/">Gwiazda</a>, <a href="/katalog/motyw/oblok/">Obłok</a>, <a href="/katalog/motyw/noc/">Noc</a>, <a href="/katalog/motyw/swiatlo/">Światło</a>, <a href="/katalog/motyw/gora/">Góra</a>, <a href="/katalog/motyw/rzeka/">Rzeka</a>, <a href="/katalog/motyw/morze/">Morze</a>, <a href="/katalog/motyw/burza/">Burza</a>, <a href="/katalog/motyw/deszcz/">Deszcz</a>, <a href="/katalog/motyw/bloto/">Błoto</a>, <a href="/katalog/motyw/przyroda-nieozywiona/">Przyroda nieożywiona</a>, <a href="/katalog/motyw/rosliny/">Rośliny</a>, <a href="/katalog/motyw/kwiaty/">Kwiaty</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/sielanka/">Sielanka</a>, <a href="/katalog/motyw/raj/">Raj</a>, <a href="/katalog/motyw/jablko/">Jabłko</a>, <a href="/katalog/motyw/drzewo/">Drzewo</a>, <a href="/katalog/motyw/zwierzeta/">Zwierzęta</a>, <a href="/katalog/motyw/ptak/">Ptak</a>, <a href="/katalog/motyw/motyl/">Motyl</a>, <a href="/katalog/motyw/kot/">Kot</a>, <a href="/katalog/motyw/kon/">Koń</a>, <a href="/katalog/motyw/pies/">Pies</a>, <a href="/katalog/motyw/waz/">Wąż</a>, <a href="/katalog/motyw/potwor/">Potwór</a></span></li>
-                    </ol>
-                    <p><a href="#" class="show-all-tags" title="{% trans "Themes and topics" %}">{% trans "See more" %}</a></p>
-                </div>
-                <div class="all-tags">
-                    <ol>
-                    <li>cielesność
-                    <span class="subcategories"><a href="/katalog/motyw/cialo/">Ciało</a>, <a href="/katalog/motyw/krew/">Krew</a>, <a href="/katalog/motyw/zdrowie/">Zdrowie</a>, <a href="/katalog/motyw/choroba/">Choroba</a>, <a href="/katalog/motyw/kaleka/">Kaleka</a></span></li>
-
-                    <li>dom
-                    <span class="subcategories"><a href="/katalog/motyw/dom/">Dom</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/przestrzen/">Przestrzeń</a>, <a href="/katalog/motyw/gospodarz/">Gospodarz</a>, <a href="/katalog/motyw/gospodyni/">Gospodyni</a>, <a href="/katalog/motyw/sasiad/">Sąsiad</a>, <a href="/katalog/motyw/gosc/">Gość</a>, <a href="/katalog/motyw/bezdomnosc/">Bezdomność</a>, <a href="/katalog/motyw/bezpieczenstwo/">Bezpieczeństwo</a>, <a href="/katalog/motyw/niebezpieczenstwo/">Niebezpieczeństwo</a></span></li>
-
-                    <li>działania nieczyste
-                    <span class="subcategories"><a href="/katalog/motyw/szantaz/">Szantaż</a>, <a href="/katalog/motyw/zazdrosc/">Zazdrość</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a>, <a href="/katalog/motyw/zemsta/">Zemsta</a>, <a href="/katalog/motyw/klamstwo/">Kłamstwo</a>, <a href="/katalog/motyw/falsz/">Fałsz</a>, <a href="/katalog/motyw/pozory/">Pozory</a>, <a href="/katalog/motyw/tajemnica/">Tajemnica</a></span></li>
-
-                    <li>dziedzictwo
-                    <span class="subcategories"><a href="/katalog/motyw/dziedzictwo/">Dziedzictwo</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a>, <a href="/katalog/motyw/pamiec/">Pamięć</a>, <a href="/katalog/motyw/historia/">Historia</a>, <a href="/katalog/motyw/narod/">Naród</a>, <a href="/katalog/motyw/krew/">Krew</a>, <a href="/katalog/motyw/panstwo/">Państwo</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/patriota/">Patriota</a>, <a href="/katalog/motyw/ruiny/">Ruiny</a>, <a href="/katalog/motyw/dom/">Dom</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a></span></li>
-
-                    <li>dźwięk
-                    <span class="subcategories"><a href="/katalog/motyw/cisza/">Cisza</a>, <a href="/katalog/motyw/muzyka/">Muzyka</a>, <a href="/katalog/motyw/spiew/">Śpiew</a>, <a href="/katalog/motyw/poezja/">Poezja</a></span></li>
-
-                    <li>edukacja
-                    <span class="subcategories"><a href="/katalog/motyw/uczen/">Uczeń</a>, <a href="/katalog/motyw/szkola/">Szkoła</a>, <a href="/katalog/motyw/nauczyciel/">Nauczyciel</a>, <a href="/katalog/motyw/nauczycielka/">Nauczycielka</a>, <a href="/katalog/motyw/nauka/">Nauka</a>, <a href="/katalog/motyw/wiedza/">Wiedza</a>, <a href="/katalog/motyw/dziecinstwo/">Dzieciństwo</a>, <a href="/katalog/motyw/mlodosc/">Młodość</a>, <a href="/katalog/motyw/doroslosc/">Dorosłość</a></span></li>
-
-                    <li>egzystencja ludzka
-                    <span class="subcategories"><a href="/katalog/motyw/kondycja-ludzka/">Kondycja ludzka</a>, <a href="/katalog/motyw/los/">Los</a>, <a href="/katalog/motyw/bladzenie/">Błądzenie</a>, <a href="/katalog/motyw/bunt/">Bunt</a>, <a href="/katalog/motyw/buntownik/">Buntownik</a>, <a href="/katalog/motyw/pielgrzym/">Pielgrzym</a>, <a href="/katalog/motyw/theatrum-mundi/">Theatrum mundi</a>, <a href="/katalog/motyw/zycie-jako-wedrowka/">Życie jako wędrówka</a>, <a href="/katalog/motyw/zycie-snem/">Życie snem</a></span></li>
-
-                    <li>etapy życia
-                    <span class="subcategories"><a href="/katalog/motyw/dziecinstwo/">Dzieciństwo</a>, <a href="/katalog/motyw/mlodosc/">Młodość</a>, <a href="/katalog/motyw/doroslosc/">Dorosłość</a>, <a href="/katalog/motyw/panna-mloda/">Panna młoda</a>, <a href="/katalog/motyw/zona/">Żona</a>, <a href="/katalog/motyw/maz/">Mąż</a>, <a href="/katalog/motyw/wdowa/">Wdowa</a>, <a href="/katalog/motyw/wdowiec/">Wdowiec</a>, <a href="/katalog/motyw/starosc/">Starość</a>, <a href="/katalog/motyw/czas/">Czas</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a>, <a href="/katalog/motyw/kondycja-ludzka/">Kondycja ludzka</a></span></li>
-
-                    <li>fauna
-                    <span class="subcategories"><a href="/katalog/motyw/zwierzeta/">Zwierzęta</a>, <a href="/katalog/motyw/kot/">Kot</a>, <a href="/katalog/motyw/kon/">Koń</a>, <a href="/katalog/motyw/motyl/">Motyl</a>, <a href="/katalog/motyw/pies/">Pies</a>, <a href="/katalog/motyw/ptak/">Ptak</a>, <a href="/katalog/motyw/waz/">Wąż</a></span></li>
-
-                    <li>flora
-                    <span class="subcategories"><a href="/katalog/motyw/rosliny/">Rośliny</a>, <a href="/katalog/motyw/kwiaty/">Kwiaty</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/drzewo/">Drzewo</a></span></li>
-
-                    <li>historie miłosne
-                    <span class="subcategories"><a href="/katalog/motyw/milosc/">Miłość</a>, <a href="/katalog/motyw/milosc-platoniczna/">Miłość platoniczna</a>, <a href="/katalog/motyw/milosc-romantyczna/">Miłość romantyczna</a>, <a href="/katalog/motyw/milosc-silniejsza-niz-smierc/">Miłość silniejsza niż śmierć</a>, <a href="/katalog/motyw/milosc-spelniona/">Miłość spełniona</a>, <a href="/katalog/motyw/milosc-tragiczna/">Miłość tragiczna</a>, <a href="/katalog/motyw/kochanek/">Kochanek</a>, <a href="/katalog/motyw/kochanek-romantyczny/">Kochanek romantyczny</a>, <a href="/katalog/motyw/flirt/">Flirt</a>, <a href="/katalog/motyw/pocalunek/">Pocałunek</a>, <a href="/katalog/motyw/pozadanie/">Pożądanie</a>, <a href="/katalog/motyw/list/">List</a>, <a href="/katalog/motyw/serce/">Serce</a>, <a href="/katalog/motyw/lzy/">Łzy</a>, <a href="/katalog/motyw/przysiega/">Przysięga</a>, <a href="/katalog/motyw/tesknota/">Tęsknota</a>, <a href="/katalog/motyw/wspomnienia/">Wspomnienia</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a>, <a href="/katalog/motyw/rozczarowanie/">Rozczarowanie</a>, <a href="/katalog/motyw/rozpacz/">Rozpacz</a>, <a href="/katalog/motyw/malzenstwo/">Małżeństwo</a>, <a href="/katalog/motyw/slub/">Ślub</a>, <a href="/katalog/motyw/panna-mloda/">Panna młoda</a>, <a href="/katalog/motyw/przyjazn/">Przyjaźń</a></span></li>
-
-                    <li>jedzenie i picie
-                    <span class="subcategories"><a href="/katalog/motyw/glod/">Głód</a>, <a href="/katalog/motyw/bieda/">Bieda</a>, <a href="/katalog/motyw/chleb/">Chleb</a>, <a href="/katalog/motyw/jedzenie/">Jedzenie</a>, <a href="/katalog/motyw/uczta/">Uczta</a>, <a href="/katalog/motyw/wino/">Wino</a>, <a href="/katalog/motyw/alkohol/">Alkohol</a>, <a href="/katalog/motyw/pijanstwo/">Pijaństwo</a></span></li>
-
-                    <li>konflikty
-                    <span class="subcategories"><a href="/katalog/motyw/klotnia/">Kłótnia</a>, <a href="/katalog/motyw/bijatyka/">Bijatyka</a>, <a href="/katalog/motyw/sila/">Siła</a>, <a href="/katalog/motyw/przemoc/">Przemoc</a>, <a href="/katalog/motyw/krew/">Krew</a>, <a href="/katalog/motyw/konflikt/">Konflikt</a>, <a href="/katalog/motyw/walka/">Walka</a>, <a href="/katalog/motyw/wojna/">Wojna</a>, <a href="/katalog/motyw/powstanie/">Powstanie</a>, <a href="/katalog/motyw/bunt/">Bunt</a>, <a href="/katalog/motyw/rewolucja/">Rewolucja</a></span></li>
-
-                    <li>momenty graniczne
-                    <span class="subcategories"><a href="/katalog/motyw/narodziny/">Narodziny</a>, <a href="/katalog/motyw/smierc/">Śmierć</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a>, <a href="/katalog/motyw/zmartwychwstanie/">Zmartwychwstanie</a></span></li>
-
-                    <li>nadużycie władzy
-                    <span class="subcategories"><a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/przemoc/">Przemoc</a>, <a href="/katalog/motyw/strach/">Strach</a>, <a href="/katalog/motyw/kara/">Kara</a></span></li>
-
-                    <li>nacjonalizm
-                    <span class="subcategories"><a href="/katalog/motyw/polak/">Polak</a>, <a href="/katalog/motyw/niemiec/">Niemiec</a>, <a href="/katalog/motyw/rosjanin/">Rosjanin</a>, <a href="/katalog/motyw/car/">Car</a>, <a href="/katalog/motyw/zyd/">Żyd</a>, <a href="/katalog/motyw/narod/">Naród</a>, <a href="/katalog/motyw/obcy/">Obcy</a>, <a href="/katalog/motyw/wrog/">Wróg</a>, <a href="/katalog/motyw/niebezpieczenstwo/">Niebezpieczeństwo</a></span></li>
-
-                    <li>nastroje melancholijne
-                    <span class="subcategories"><a href="/katalog/motyw/nuda/">Nuda</a>, <a href="/katalog/motyw/melancholia/">Melancholia</a>, <a href="/katalog/motyw/ruiny/">Ruiny</a>, <a href="/katalog/motyw/wspomnienia/">Wspomnienia</a>, <a href="/katalog/motyw/marzenie/">Marzenie</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/tesknota/">Tęsknota</a>, <a href="/katalog/motyw/rozpacz/">Rozpacz</a>, <a href="/katalog/motyw/smierc/">Śmierć</a>, <a href="/katalog/motyw/los/">Los</a>, <a href="/katalog/motyw/kondycja-ludzka/">Kondycja ludzka</a></span></li>
-
-                    <li>nastroje rewolucyjne
-                    <span class="subcategories"><a href="/katalog/motyw/rewolucja/">Rewolucja</a>, <a href="/katalog/motyw/walka-klas/">Walka klas</a>, <a href="/katalog/motyw/robotnik/">Robotnik</a>, <a href="/katalog/motyw/chlop/">Chłop</a>, <a href="/katalog/motyw/pozycja-spoleczna/">Pozycja społeczna</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/przemoc/">Przemoc</a>, <a href="/katalog/motyw/sprawiedliwosc/">Sprawiedliwość</a></span></li>
-
-                    <li>podporządkowanie
-                    <span class="subcategories"><a href="/katalog/motyw/sluga/">Sługa</a>, <a href="/katalog/motyw/pan/">Pan</a>, <a href="/katalog/motyw/praca/">Praca</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a></span></li>
-
-                    <li>pokrewieństwo
-                    <span class="subcategories"><a href="/katalog/motyw/rodzina/">Rodzina</a>, <a href="/katalog/motyw/ojciec/">Ojciec</a>, <a href="/katalog/motyw/matka/">Matka</a>, <a href="/katalog/motyw/dziecko/">Dziecko</a>, <a href="/katalog/motyw/syn/">Syn</a>, <a href="/katalog/motyw/corka/">Córka</a>, <a href="/katalog/motyw/brat/">Brat</a>, <a href="/katalog/motyw/siostra/">Siostra</a>, <a href="/katalog/motyw/sierota/">Sierota</a>, <a href="/katalog/motyw/dziedzictwo/">Dziedzictwo</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a></span></li>
-
-                    <li>polityczny obraz świata
-                    <span class="subcategories"><a href="/katalog/motyw/panstwo/">Państwo</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/cnota/">Cnota</a>, <a href="/katalog/motyw/obywatel/">Obywatel</a>, <a href="/katalog/motyw/patriota/">Patriota</a>, <a href="/katalog/motyw/ojczyzna/">Ojczyzna</a>, <a href="/katalog/motyw/narod/">Naród</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/rycerz/">Rycerz</a>, <a href="/katalog/motyw/zolnierz/">Żołnierz</a>, <a href="/katalog/motyw/wojna/">Wojna</a>, <a href="/katalog/motyw/wrog/">Wróg</a>, <a href="/katalog/motyw/zwyciestwo/">Zwycięstwo</a>, <a href="/katalog/motyw/walka/">Walka</a>, <a href="/katalog/motyw/sila/">Siła</a>, <a href="/katalog/motyw/historia/">Historia</a>, <a href="/katalog/motyw/powstanie/">Powstanie</a>, <a href="/katalog/motyw/smierc-bohaterska/">Śmierć bohaterska</a>, <a href="/katalog/motyw/slawa/">Sława</a>, <a href="/katalog/motyw/rewolucja/">Rewolucja</a>, <a href="/katalog/motyw/sad/">Sąd</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a></span></li>
-
-                    <li>pory roku
-                    <span class="subcategories"><a href="/katalog/motyw/wiosna/">Wiosna</a>, <a href="/katalog/motyw/lato/">Lato</a>, <a href="/katalog/motyw/jesien/">Jesień</a>, <a href="/katalog/motyw/zima/">Zima</a>, <a href="/katalog/motyw/czas/">Czas</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a></span></li>
-
-                    <li>posiadanie
-                    <span class="subcategories"><a href="/katalog/motyw/pieniadz/">Pieniądz</a>, <a href="/katalog/motyw/handel/">Handel</a>, <a href="/katalog/motyw/korzysc/">Korzyść</a>, <a href="/katalog/motyw/chciwosc/">Chciwość</a>, <a href="/katalog/motyw/bieda/">Bieda</a>, <a href="/katalog/motyw/bogactwo/">Bogactwo</a>, <a href="/katalog/motyw/skapiec/">Skąpiec</a>, <a href="/katalog/motyw/wlasnosc/">Własność</a>, <a href="/katalog/motyw/zlodziej/">Złodziej</a>, <a href="/katalog/motyw/zebrak/">Żebrak</a></span></li>
-
-                    <li>poświęcenie
-                    <span class="subcategories"><a href="/katalog/motyw/poswiecenie/">Poświęcenie</a>, <a href="/katalog/motyw/ofiara/">Ofiara</a>, <a href="/katalog/motyw/prometeusz/">Prometeusz</a>, <a href="/katalog/motyw/milosierdzie/">Miłosierdzie</a>, <a href="/katalog/motyw/chrystus/">Chrystus</a>, <a href="/katalog/motyw/zbawienie/">Zbawienie</a></span></li>
-
-                    <li>poznanie
-                    <span class="subcategories"><a href="/katalog/motyw/filozof/">Filozof</a>, <a href="/katalog/motyw/madrosc/">Mądrość</a>, <a href="/katalog/motyw/medrzec/">Mędrzec</a>, <a href="/katalog/motyw/glupiec/">Głupiec</a>, <a href="/katalog/motyw/glupota/">Głupota</a>, <a href="/katalog/motyw/rozum/">Rozum</a>, <a href="/katalog/motyw/wiedza/">Wiedza</a>, <a href="/katalog/motyw/prawda/">Prawda</a>, <a href="/katalog/motyw/falsz/">Fałsz</a></span></li>
-
-                    <li>poznanie alternatywne
-                    <span class="subcategories"><a href="/katalog/motyw/szaleniec/">Szaleniec</a>, <a href="/katalog/motyw/szalenstwo/">Szaleństwo</a>, <a href="/katalog/motyw/prawda/">Prawda</a>, <a href="/katalog/motyw/pozory/">Pozory</a>, <a href="/katalog/motyw/obraz-swiata/">Obraz świata</a>, <a href="/katalog/motyw/serce/">Serce</a>, <a href="/katalog/motyw/wiedza/">Wiedza</a>, <a href="/katalog/motyw/madrosc/">Mądrość</a>, <a href="/katalog/motyw/dusza/">Dusza</a>, <a href="/katalog/motyw/duch/">Duch</a>, <a href="/katalog/motyw/cialo/">Ciało</a></span></li>
-
-                    <li>praca
-                    <span class="subcategories"><a href="/katalog/motyw/praca/">Praca</a>, <a href="/katalog/motyw/sluga/">Sługa</a></span></li>
-
-                    <li>przyroda
-                    <span class="subcategories"><a href="/katalog/motyw/natura/">Natura</a>, <a href="/katalog/motyw/zywioly/">Żywioły</a>, <a href="/katalog/motyw/ogien/">Ogień</a>, <a href="/katalog/motyw/ziemia/">Ziemia</a>, <a href="/katalog/motyw/wiatr/">Wiatr</a>, <a href="/katalog/motyw/woda/">Woda</a>, <a href="/katalog/motyw/wiosna/">Wiosna</a>, <a href="/katalog/motyw/lato/">Lato</a>, <a href="/katalog/motyw/jesien/">Jesień</a>, <a href="/katalog/motyw/zima/">Zima</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/slonce/">Słońce</a>, <a href="/katalog/motyw/ksiezyc/">Księżyc</a>, <a href="/katalog/motyw/gwiazda/">Gwiazda</a>, <a href="/katalog/motyw/oblok/">Obłok</a>, <a href="/katalog/motyw/noc/">Noc</a>, <a href="/katalog/motyw/swiatlo/">Światło</a>, <a href="/katalog/motyw/gora/">Góra</a>, <a href="/katalog/motyw/rzeka/">Rzeka</a>, <a href="/katalog/motyw/morze/">Morze</a>, <a href="/katalog/motyw/burza/">Burza</a>, <a href="/katalog/motyw/deszcz/">Deszcz</a>, <a href="/katalog/motyw/bloto/">Błoto</a>, <a href="/katalog/motyw/przyroda-nieozywiona/">Przyroda nieożywiona</a>, <a href="/katalog/motyw/rosliny/">Rośliny</a>, <a href="/katalog/motyw/kwiaty/">Kwiaty</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/sielanka/">Sielanka</a>, <a href="/katalog/motyw/raj/">Raj</a>, <a href="/katalog/motyw/jablko/">Jabłko</a>, <a href="/katalog/motyw/drzewo/">Drzewo</a>, <a href="/katalog/motyw/zwierzeta/">Zwierzęta</a>, <a href="/katalog/motyw/ptak/">Ptak</a>, <a href="/katalog/motyw/motyl/">Motyl</a>, <a href="/katalog/motyw/kot/">Kot</a>, <a href="/katalog/motyw/kon/">Koń</a>, <a href="/katalog/motyw/pies/">Pies</a>, <a href="/katalog/motyw/waz/">Wąż</a>, <a href="/katalog/motyw/potwor/">Potwór</a></span></li>
-
-                    <li>regulacja postępowania
-                    <span class="subcategories"><a href="/katalog/motyw/cnota/">Cnota</a>, <a href="/katalog/motyw/sprawiedliwosc/">Sprawiedliwość</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a></span></li>
-
-                    <li>role społeczne
-                    <span class="subcategories"><a href="/katalog/motyw/kobieta/">Kobieta</a>, <a href="/katalog/motyw/mezczyzna/">Mężczyzna</a>, <a href="/katalog/motyw/maz/">Mąż</a>, <a href="/katalog/motyw/zona/">Żona</a>, <a href="/katalog/motyw/matka/">Matka</a>, <a href="/katalog/motyw/ojciec/">Ojciec</a>, <a href="/katalog/motyw/dziecko/">Dziecko</a>, <a href="/katalog/motyw/syn/">Syn</a>, <a href="/katalog/motyw/corka/">Córka</a>, <a href="/katalog/motyw/brat/">Brat</a>, <a href="/katalog/motyw/siostra/">Siostra</a>, <a href="/katalog/motyw/wdowa/">Wdowa</a>, <a href="/katalog/motyw/wdowiec/">Wdowiec</a>, <a href="/katalog/motyw/nauczyciel/">Nauczyciel</a>, <a href="/katalog/motyw/nauczycielka/">Nauczycielka</a>, <a href="/katalog/motyw/uczen/">Uczeń</a>, <a href="/katalog/motyw/poeta/">Poeta</a>, <a href="/katalog/motyw/literat/">Literat</a>, <a href="/katalog/motyw/lekarz/">Lekarz</a>, <a href="/katalog/motyw/sedzia/">Sędzia</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/zolnierz/">Żołnierz</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/filozof/">Filozof</a>, <a href="/katalog/motyw/ksiadz/">Ksiądz</a></span></li>
-
-                    <li>rycerskie czasy
-                    <span class="subcategories"><a href="/katalog/motyw/zamek/">Zamek</a>, <a href="/katalog/motyw/ruiny/">Ruiny</a>, <a href="/katalog/motyw/rycerz/">Rycerz</a>, <a href="/katalog/motyw/honor/">Honor</a>, <a href="/katalog/motyw/wiernosc/">Wierność</a>, <a href="/katalog/motyw/obowiazek/">Obowiązek</a>, <a href="/katalog/motyw/walka/">Walka</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/dama/">Dama</a></span></li>
-
-                    <li>rzeczywistość nadprzyrodzona
-                    <span class="subcategories"><a href="/katalog/motyw/bog/">Bóg</a>, <a href="/katalog/motyw/chrystus/">Chrystus</a>, <a href="/katalog/motyw/matka-boska/">Matka Boska</a>, <a href="/katalog/motyw/aniol/">Anioł</a>, <a href="/katalog/motyw/szatan/">Szatan</a>, <a href="/katalog/motyw/diabel/">Diabeł</a>, <a href="/katalog/motyw/duch/">Duch</a>, <a href="/katalog/motyw/dusza/">Dusza</a>, <a href="/katalog/motyw/wampir/">Wampir</a>, <a href="/katalog/motyw/upior/">Upiór</a>, <a href="/katalog/motyw/czary/">Czary</a>, <a href="/katalog/motyw/czarownica/">Czarownica</a></span></li>
-
-                    <li>struktura społeczna
-                    <span class="subcategories"><a href="/katalog/motyw/chlop/">Chłop</a>, <a href="/katalog/motyw/mieszczanin/">Mieszczanin</a>, <a href="/katalog/motyw/zyd/">Żyd</a>, <a href="/katalog/motyw/szlachcic/">Szlachcic</a>, <a href="/katalog/motyw/ksiadz/">Ksiądz</a>, <a href="/katalog/motyw/robotnik/">Robotnik</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/pozycja-spoleczna/">Pozycja społeczna</a>, <a href="/katalog/motyw/dworek/">Dworek</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a></span></li>
-
-                    <li>sarmatyzm
-                    <span class="subcategories"><a href="/katalog/motyw/polak/">Polak</a>, <a href="/katalog/motyw/sarmata/">Sarmata</a>, <a href="/katalog/motyw/szlachcic/">Szlachcic</a>, <a href="/katalog/motyw/przedmurze-chrzescijanstwa/">Przedmurze chrześcijaństwa</a>, <a href="/katalog/motyw/matka-boska/">Matka Boska</a>, <a href="/katalog/motyw/religia/">Religia</a></span></li>
-
-                    <li>sprawowanie władzy
-                    <span class="subcategories"><a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/panstwo/">Państwo</a>, <a href="/katalog/motyw/wladza/">Władza</a>, <a href="/katalog/motyw/przemoc/">Przemoc</a>, <a href="/katalog/motyw/sad/">Sąd</a>, <a href="/katalog/motyw/kara/">Kara</a>, <a href="/katalog/motyw/wiezienie/">Więzienie</a></span></li>
-
-                    <li>śmierć
-                    <span class="subcategories"><a href="/katalog/motyw/smierc/">Śmierć</a>, <a href="/katalog/motyw/danse-macabre/">Danse macabre</a>, <a href="/katalog/motyw/gotycyzm/">Gotycyzm</a>, <a href="/katalog/motyw/grob/">Grób</a>, <a href="/katalog/motyw/otchlan/">Otchłań</a>, <a href="/katalog/motyw/pogrzeb/">Pogrzeb</a>, <a href="/katalog/motyw/samobojstwo/">Samobójstwo</a>, <a href="/katalog/motyw/krew/">Krew</a>, <a href="/katalog/motyw/trup/">Trup</a>, <a href="/katalog/motyw/morderstwo/">Morderstwo</a>, <a href="/katalog/motyw/zaloba/">Żałoba</a>, <a href="/katalog/motyw/zmartwychwstanie/">Zmartwychwstanie</a>, <a href="/katalog/motyw/melancholia/">Melancholia</a>, <a href="/katalog/motyw/vanitas/">Vanitas</a>, <a href="/katalog/motyw/los/">Los</a>, <a href="/katalog/motyw/kondycja-ludzka/">Kondycja ludzka</a></span></li>
-
-                    <li>środowisko miejskie i wiejskie
-                    <span class="subcategories"><a href="/katalog/motyw/miasto/">Miasto</a>, <a href="/katalog/motyw/warszawa/">Warszawa</a>, <a href="/katalog/motyw/mieszczanin/">Mieszczanin</a>, <a href="/katalog/motyw/handel/">Handel</a>, <a href="/katalog/motyw/robotnik/">Robotnik</a>, <a href="/katalog/motyw/zyd/">Żyd</a>, <a href="/katalog/motyw/wies/">Wieś</a>, <a href="/katalog/motyw/sielanka/">Sielanka</a>, <a href="/katalog/motyw/chlop/">Chłop</a>, <a href="/katalog/motyw/ogrod/">Ogród</a>, <a href="/katalog/motyw/natura/">Natura</a>, <a href="/katalog/motyw/przestrzen/">Przestrzeń</a></span></li>
-
-                    <li>świat w perspektywie etycznej
-                    <span class="subcategories"><a href="/katalog/motyw/kuszenie/">Kuszenie</a>, <a href="/katalog/motyw/zwatpienie/">Zwątpienie</a>, <a href="/katalog/motyw/wyrzuty-sumienia/">Wyrzuty sumienia</a>, <a href="/katalog/motyw/wina/">Wina</a>, <a href="/katalog/motyw/grzech/">Grzech</a>, <a href="/katalog/motyw/kara/">Kara</a>, <a href="/katalog/motyw/los/">Los</a>, <a href="/katalog/motyw/koniec-swiata/">Koniec świata</a>, <a href="/katalog/motyw/wieza-babel/">Wieża Babel</a>, <a href="/katalog/motyw/zbawienie/">Zbawienie</a>, <a href="/katalog/motyw/zaswiaty/">Zaświaty</a>, <a href="/katalog/motyw/czysciec/">Czyściec</a>, <a href="/katalog/motyw/raj/">Raj</a>, <a href="/katalog/motyw/niesmiertelnosc/">Nieśmiertelność</a>, <a href="/katalog/motyw/przysiega/">Przysięga</a>, <a href="/katalog/motyw/przeklenstwo/">Przekleństwo</a>, <a href="/katalog/motyw/religia/">Religia</a>, <a href="/katalog/motyw/obrzedy/">Obrzędy</a>, <a href="/katalog/motyw/modlitwa/">Modlitwa</a>, <a href="/katalog/motyw/niedziela/">Niedziela</a>, <a href="/katalog/motyw/przedmurze-chrzescijanstwa/">Przedmurze chrześcijaństwa</a>, <a href="/katalog/motyw/ksiadz/">Ksiądz</a>, <a href="/katalog/motyw/poboznosc/">Pobożność</a>, <a href="/katalog/motyw/swietoszek/">Świętoszek</a>, <a href="/katalog/motyw/swiety/">Święty</a>, <a href="/katalog/motyw/wierzenia/">Wierzenia</a>, <a href="/katalog/motyw/zabobony/">Zabobony</a></span></li>
-
-                    <li>świętowanie
-                    <span class="subcategories"><a href="/katalog/motyw/wesele/">Wesele</a>, <a href="/katalog/motyw/uczta/">Uczta</a>, <a href="/katalog/motyw/jedzenie/">Jedzenie</a>, <a href="/katalog/motyw/pijanstwo/">Pijaństwo</a>, <a href="/katalog/motyw/zabawa/">Zabawa</a>, <a href="/katalog/motyw/taniec/">Taniec</a>, <a href="/katalog/motyw/muzyka/">Muzyka</a>, <a href="/katalog/motyw/smiech/">Śmiech</a>, <a href="/katalog/motyw/spiew/">Śpiew</a>, <a href="/katalog/motyw/bijatyka/">Bijatyka</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a>, <a href="/katalog/motyw/wierzenia/">Wierzenia</a>, <a href="/katalog/motyw/zabobony/">Zabobony</a></span></li>
-
-                    <li>tożsamość pozorna i podwójna
-                    <span class="subcategories"><a href="/katalog/motyw/portret/">Portret</a>, <a href="/katalog/motyw/lustro/">Lustro</a>, <a href="/katalog/motyw/cien/">Cień</a>, <a href="/katalog/motyw/sobowtor/">Sobowtór</a>, <a href="/katalog/motyw/maska/">Maska</a>, <a href="/katalog/motyw/przebranie/">Przebranie</a>, <a href="/katalog/motyw/stroj/">Strój</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a></span></li>
-
-                    <li>trunki
-                    <span class="subcategories"><a href="/katalog/motyw/alkohol/">Alkohol</a>, <a href="/katalog/motyw/wino/">Wino</a>, <a href="/katalog/motyw/carpe-diem/">Carpe diem</a>, <a href="/katalog/motyw/pijanstwo/">Pijaństwo</a>, <a href="/katalog/motyw/karczma/">Karczma</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a></span></li>
-
-                    <li>typy bohaterów
-                    <span class="subcategories"><a href="/katalog/motyw/samotnik/">Samotnik</a>, <a href="/katalog/motyw/buntownik/">Buntownik</a>, <a href="/katalog/motyw/pielgrzym/">Pielgrzym</a>, <a href="/katalog/motyw/szaleniec/">Szaleniec</a>, <a href="/katalog/motyw/filozof/">Filozof</a>, <a href="/katalog/motyw/medrzec/">Mędrzec</a>, <a href="/katalog/motyw/obcy/">Obcy</a>, <a href="/katalog/motyw/przywodca/">Przywódca</a>, <a href="/katalog/motyw/realista/">Realista</a>, <a href="/katalog/motyw/idealista/">Idealista</a>, <a href="/katalog/motyw/spolecznik/">Społecznik</a>, <a href="/katalog/motyw/syzyf/">Syzyf</a>, <a href="/katalog/motyw/prometeusz/">Prometeusz</a>, <a href="/katalog/motyw/sluga/">Sługa</a>, <a href="/katalog/motyw/uczen/">Uczeń</a></span></li>
-
-                    <li>ukrywanie/ujawnianie
-                    <span class="subcategories"><a href="/katalog/motyw/tajemnica/">Tajemnica</a>, <a href="/katalog/motyw/przysiega/">Przysięga</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a>, <a href="/katalog/motyw/klamstwo/">Kłamstwo</a>, <a href="/katalog/motyw/falsz/">Fałsz</a></span></li>
-
-                    <li>upływ czasu
-                    <span class="subcategories"><a href="/katalog/motyw/wspomnienia/">Wspomnienia</a>, <a href="/katalog/motyw/marzenie/">Marzenie</a>, <a href="/katalog/motyw/pamiec/">Pamięć</a>, <a href="/katalog/motyw/przemijanie/">Przemijanie</a>, <a href="/katalog/motyw/czas/">Czas</a>, <a href="/katalog/motyw/smierc/">Śmierć</a></span></li>
-
-                    <li>widzenie
-                    <span class="subcategories"><a href="/katalog/motyw/oko/">Oko</a>, <a href="/katalog/motyw/wzrok/">Wzrok</a>, <a href="/katalog/motyw/sen/">Sen</a>, <a href="/katalog/motyw/marzenie/">Marzenie</a>, <a href="/katalog/motyw/wizja/">Wizja</a>, <a href="/katalog/motyw/przeczucie/">Przeczucie</a>, <a href="/katalog/motyw/duch/">Duch</a>, <a href="/katalog/motyw/dusza/">Dusza</a>, <a href="/katalog/motyw/proroctwo/">Proroctwo</a></span></li>
-
-                    <li>wina i przebaczenie
-                    <span class="subcategories"><a href="/katalog/motyw/grzech/">Grzech</a>, <a href="/katalog/motyw/wina/">Wina</a>, <a href="/katalog/motyw/wyrzuty-sumienia/">Wyrzuty sumienia</a>, <a href="/katalog/motyw/syn-marnotrawny/">Syn marnotrawny</a>, <a href="/katalog/motyw/pokora/">Pokora</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a>, <a href="/katalog/motyw/milosierdzie/">Miłosierdzie</a>, <a href="/katalog/motyw/zbawienie/">Zbawienie</a></span></li>
-
-                    <li>wizerunki kobiety
-                    <span class="subcategories"><a href="/katalog/motyw/kobieta/">Kobieta</a>, <a href="/katalog/motyw/kobieta-demoniczna/">Kobieta demoniczna</a>, <a href="/katalog/motyw/kobieta-upadla/">Kobieta „upadła”</a>, <a href="/katalog/motyw/czarownica/">Czarownica</a>, <a href="/katalog/motyw/dama/">Dama</a>, <a href="/katalog/motyw/proznosc/">Próżność</a>, <a href="/katalog/motyw/cialo/">Ciało</a>, <a href="/katalog/motyw/corka/">Córka</a>, <a href="/katalog/motyw/siostra/">Siostra</a>, <a href="/katalog/motyw/zona/">Żona</a>, <a href="/katalog/motyw/matka/">Matka</a></span></li>
-
-                    <li>w kręgu sztuki
-                    <span class="subcategories"><a href="/katalog/motyw/artysta/">Artysta</a>, <a href="/katalog/motyw/sztuka/">Sztuka</a>, <a href="/katalog/motyw/literat/">Literat</a>, <a href="/katalog/motyw/poeta/">Poeta</a>, <a href="/katalog/motyw/poetka/">Poetka</a>, <a href="/katalog/motyw/poezja/">Poezja</a>, <a href="/katalog/motyw/muzyka/">Muzyka</a>, <a href="/katalog/motyw/taniec/">Taniec</a>, <a href="/katalog/motyw/spiew/">Śpiew</a>, <a href="/katalog/motyw/teatr/">Teatr</a>, <a href="/katalog/motyw/ksiazka/">Książka</a>, <a href="/katalog/motyw/slowo/">Słowo</a>, <a href="/katalog/motyw/slawa/">Sława</a>, <a href="/katalog/motyw/niesmiertelnosc/">Nieśmiertelność</a></span></li>
-
-                    <li>wychodźstwo i uwięzienie
-                    <span class="subcategories"><a href="/katalog/motyw/emigrant/">Emigrant</a>, <a href="/katalog/motyw/tesknota/">Tęsknota</a>, <a href="/katalog/motyw/obcy/">Obcy</a>, <a href="/katalog/motyw/wiezienie/">Więzienie</a>, <a href="/katalog/motyw/przemiana/">Przemiana</a>, <a href="/katalog/motyw/wiezien/">Więzień</a>, <a href="/katalog/motyw/wolnosc/">Wolność</a>, <a href="/katalog/motyw/niewola/">Niewola</a>, <a href="/katalog/motyw/wygnanie/">Wygnanie</a>, <a href="/katalog/motyw/zeslaniec/">Zesłaniec</a>, <a href="/katalog/motyw/zbrodnia/">Zbrodnia</a>, <a href="/katalog/motyw/zbrodniarz/">Zbrodniarz</a></span></li>
-
-                    <li>zagrożenie
-                    <span class="subcategories"><a href="/katalog/motyw/niebezpieczenstwo/">Niebezpieczeństwo</a>, <a href="/katalog/motyw/trucizna/">Trucizna</a>, <a href="/katalog/motyw/falsz/">Fałsz</a>, <a href="/katalog/motyw/zdrada/">Zdrada</a>, <a href="/katalog/motyw/choroba/">Choroba</a>, <a href="/katalog/motyw/smierc/">Śmierć</a></span></li>
-
-                    <li>zajęcia i zawody
-                    <span class="subcategories"><a href="/katalog/motyw/lekarz/">Lekarz</a>, <a href="/katalog/motyw/prawnik/">Prawnik</a>, <a href="/katalog/motyw/sedzia/">Sędzia</a>, <a href="/katalog/motyw/nauczyciel/">Nauczyciel</a>, <a href="/katalog/motyw/nauczycielka/">Nauczycielka</a>, <a href="/katalog/motyw/literat/">Literat</a>, <a href="/katalog/motyw/poeta/">Poeta</a>, <a href="/katalog/motyw/poetka/">Poetka</a>, <a href="/katalog/motyw/artysta/">Artysta</a>, <a href="/katalog/motyw/zolnierz/">Żołnierz</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/sluga/">Sługa</a>, <a href="/katalog/motyw/rycerz/">Rycerz</a></span></li>
-
-                    <li>życie dworskie
-                    <span class="subcategories"><a href="/katalog/motyw/dworzanin/">Dworzanin</a>, <a href="/katalog/motyw/dwor/">Dwór</a>, <a href="/katalog/motyw/dama/">Dama</a>, <a href="/katalog/motyw/fircyk/">Fircyk</a>, <a href="/katalog/motyw/blazen/">Błazen</a>, <a href="/katalog/motyw/krol/">Król</a>, <a href="/katalog/motyw/urzednik/">Urzędnik</a>, <a href="/katalog/motyw/sluga/">Sługa</a>, <a href="/katalog/motyw/grzecznosc/">Grzeczność</a>, <a href="/katalog/motyw/obyczaje/">Obyczaje</a>, <a href="/katalog/motyw/pochlebstwo/">Pochlebstwo</a></span></li>
-
-                    <li>żywioły
-                    <span class="subcategories"><a href="/katalog/motyw/zywioly/">Żywioły</a>, <a href="/katalog/motyw/ogien/">Ogień</a>, <a href="/katalog/motyw/ziemia/">Ziemia</a>, <a href="/katalog/motyw/wiatr/">Wiatr</a>, <a href="/katalog/motyw/woda/">Woda</a>, <a href="/katalog/motyw/przestrzen/">Przestrzeń</a></span></li>
-                    </ol>
-                    <p><a href="#" class="hide-all-tags">{% trans "Hide" %}</a></p>
-                </div>
-            </div>
-            <div class="clearboth"></div>
-        </div>
-    </div>
-    <div class="clearboth"></div>
-
-    <div id="site-info">
-        <div id="latest-blog-posts">
-            <h2>{% trans "News" %}</h2>
-            {% cache 1800 latest-blog-posts %}
-            {% latest_blog_posts "http://nowoczesnapolska.org.pl/category/wolne-lektury/feed/" %}
-            {% endcache %}
-            <p class="see-more"><a href="http://www.nowoczesnapolska.org.pl/">{% trans "See our blog" %} ⇒</a></p>
-        </div>
-        <div id="you-can-help">
-            <h2>{% trans "You can help us!" %}</h2>
-            <ul>
-                <li>{% trans "Become a volunteer &ndash; an editor, developer or translator." %}</li>
-                <li>{% trans "Gain new skills and experience." %}</li>
-                <li>{% trans "Join an open project of creating an innovative online library." %}</li>
-            </ul>
-            <p class="see-more"><a href="{% url help_us %}" title="{% trans "You can help us!" %}">{% trans "You can help us!" %} ⇒</a></p>
-        </div>
-        <div id="about-us">
-            <h2>{% trans "About us" %}</h2>
-            <p>
-               {% blocktrans %}
-                       Internet 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.
-                       {% endblocktrans %}
-            </p>
-            <p class="see-more"><a href="{% url about_us %}" title="{% trans "About us" %}">{% trans "See more" %} ⇒</a></p>
-        </div>
-    </div>
-{% endblock %}
-
-
-{% block add_footer %}
-
-<p>
-{% blocktrans %}
-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
-according to terms described in the <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons
-3.0 Attribution License</a>.
-{% endblocktrans %}
-</p>
-
-{% endblock %}
diff --git a/wolnelektury/templates/catalogue/picture_detail.html b/wolnelektury/templates/catalogue/picture_detail.html
new file mode 100644 (file)
index 0000000..f4aabb4
--- /dev/null
@@ -0,0 +1,100 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load catalogue_tags pagination_tags %}
+{% load thumbnail %}
+
+
+{% block titleextra %}{{ picture.title }}{% endblock %}
+
+{% block bodyid %}picture-detail{% endblock %}
+
+{% block body %}
+    <h1>{{picture.title}}</h1>
+
+    <div id="books-list">
+        <div id='breadcrumbs'>
+            {% if categories.author %}
+                {% for tag in categories.author %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
+                {% endfor %}
+                &#187; 
+            {% endif %}
+        </div>
+
+       {% thumbnail picture.image_file "400x500" upscale="false" as im %}
+       <img style="margin:{{ im|margin:"400x500" }}" src="{{ im.url }}" width="{{ im.x }}" height="{{ im.y }}" />
+       {% endthumbnail %}
+
+        {% if picture.info.license %}
+        <p>{% trans "Work is licensed under " %} <a href="{{ picture.info.license }}">{{ picture.info.license_description }}</a>.</p>
+        {% endif %}
+        <p>{% trans "Based on" %}: {{ picture.info.source_name }}</p>
+        {% if picture.info.description %}
+            <div id="description">
+                <div id='description-long'>{{ picture.info.description|safe }}</div>
+                <div id='description-short'>{{ picture.info.description|safe|truncatewords_html:30 }}</div>
+            </div>
+            <div id="toggle-description"><p></p></div>
+        {% endif %}
+
+    </div>
+
+    <div id="tags-list">
+        <div id="book-info">
+            <h2>{% trans "Details" %}</h2>
+            <ul>
+                <li>
+                    {% trans "Author" %}:
+                    {% for tag in categories.author %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
+                    {% endfor %}
+                </li>
+                <li>
+                    {% trans "Epoch" %}:
+                    {% for tag in categories.epoch %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
+                    {% endfor %}
+                </li>
+                <li>
+                    {% trans "Kind" %}:
+                    {% for tag in categories.kind %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
+                    {% endfor %}
+                </li>
+            </ul>
+            <h2>{% trans "Other resources" %}</h2>
+            <ul>
+                {% if picture.info.source_url %}
+                <li><a href="{{ picture.info.source_url }}">{% trans "Source of the image" %}</a></li>
+                {% endif %}
+                {% if picture.info.about and not hide_about %}
+                <li><a href="{{ picture.info.about }}">{% trans "Image on the Editor's Platform" %}</a></li>
+                {% endif %}
+{% comment %}
+                {% if book.gazeta_link %}
+                <li><a href="{{ book.gazeta_link }}">{% trans "Picture description on Lektury.Gazeta.pl" %}</a></li>
+                {% endif %}
+                {% if book.wiki_link %}
+                <li><a href="{{ book.wiki_link }}">{% trans "Book description on Wikipedia" %}</a></li>
+                {% endif %}
+{% endcomment %}
+            </ul>
+            <p><a href="{{ picture.xml_file.url }}">{% trans "View XML source" %}</a></p>
+        </div>
+        <div id="themes-list">
+            <h2>{% trans "Work's themes " %}</h2>
+            <ul>
+            {% for theme in picture_themes %}
+                <li><a href="{{ theme.get_absolute_url }}">{{ theme }} ({{ theme.count }})</a></li>
+            {% endfor %}
+            </ul>
+        </div>
+        <div class="clearboth"></div>
+    </div>
+    <div id="set-window">
+        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
+        <div class="target">
+            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
+        </div>
+    </div>
+{% endblock %}
diff --git a/wolnelektury/templates/catalogue/picture_list.html b/wolnelektury/templates/catalogue/picture_list.html
new file mode 100644 (file)
index 0000000..a17d987
--- /dev/null
@@ -0,0 +1,32 @@
+{% extends "catalogue/book_list.html" %}
+{% load i18n %}
+{% load catalogue_tags chunks %}
+{% load thumbnail %}
+
+{% block bodyid %}picture-list{% endblock %}
+
+{% block titleextra %}{% trans "Listing of all pictures" %}{% endblock %}
+
+{% block picture_list_header %}{% trans "Listing of all pictures" %}{% endblock %}
+
+
+{% block book_list %}
+{% for author, group in pictures_by_author.items %}
+<a name="{{ author.slug }}"/>
+<div class="group">
+  <h2><a href="{{ author.get_absolute_url }}">{{ author }}</a></h2>
+  {% for picture in group %}
+  <div class="picture">
+    {% thumbnail picture.image_file "300x300" as im %}
+    <img style="float: left; margin:{{ im|margin:"300x300" }}" src="{{ im.url }}" width="{{ im.x }}" height="{{ im.y }}" />
+    {% endthumbnail %}
+    <span class="title"><a href="{{picture.get_absolute_url}}">{{picture.title}}</a></span>
+    <br class="clearboth"/>
+  </div>
+  {% endfor %}
+</div>
+  
+{% endfor %}
+
+{% endblock %}
+
diff --git a/wolnelektury/templates/catalogue/search_form.html b/wolnelektury/templates/catalogue/search_form.html
deleted file mode 100755 (executable)
index 4535226..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-{% load i18n %}
-<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 the main page" %}</a></p>
-</form>
index dc3696e..15c5d71 100644 (file)
@@ -2,14 +2,12 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{% trans "Searching in" %} WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Search" %}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% 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>
+    <h1>{% trans "Search" %}</h1>
 
     <div id="results">
       <ol>
     </div>
 {% endcomment %}
 
-    <div id="set-window">
-        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-        <div class="target">
-            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-        </div>
-    </div>
 {% endblock %}
index bffefe3..3f51559 100644 (file)
@@ -2,13 +2,12 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{% trans "Search in WolneLektury.pl" %}{% endblock %}
+{% block titleextra %}{% trans "Search" %}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
-    <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
+    <h1>{% trans "Search" %}</h1>
 
     <div id="books-list">
         <p>{% trans "Sorry! Search cirteria did not match any resources." %}</p>
index e10ddc2..62d0ad0 100644 (file)
@@ -2,23 +2,16 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags %}
 
-{% block title %}{% trans "Searching in" %} WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Search" %}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
-    <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
+    <h1>{% trans "Search" %}</h1>
 
     <div id="books-list">
         <p>{% trans "Sorry! Search query must have at least two characters." %}</p>
         {% include "info/join_us.html" %}
     </div>
 
-    <div id="set-window">
-        <div class="header"><a href="#" class="jqmClose">{% trans "Close" %}</a></div>
-        <div class="target">
-            <p><img src="{{ STATIC_URL }}img/indicator.gif" alt="*"/> {% trans "Loading" %}</p>
-        </div>
-    </div>
 {% endblock %}
\ No newline at end of file
index e96320e..3f842ac 100644 (file)
@@ -5,7 +5,7 @@
 {% else %}
     <ul>
         {% for tag in tags %}
-            <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
+            <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.book_count }})</a></li>
         {% endfor %}
     </ul>
 {% endif %}
index fec9105..7cae9eb 100644 (file)
@@ -2,13 +2,12 @@
 {% load i18n %}
 {% load catalogue_tags pagination_tags switch_tag %}
 
-{% block title %}{% title_from_tags tags %} w WolneLektury.pl{% endblock %}
+{% block titleextra %}{% title_from_tags tags %}{% endblock %}
 
 {% block bodyid %}tagged-object-list{% endblock %}
 
 {% block body %}
     <h1>{% title_from_tags tags %}</h1>
-    {% breadcrumbs tags %}
 
     {% if only_shelf and not object_list %}
     <div id="books-list">
         {% endif %}
 
         {% if object_list %}
+            {% spaceless %}
             <ol>
             {% for book in object_list %}
                 <li>
                     {% if user_is_owner %}
-                        <a href="{% url remove_from_shelf last_tag.slug book.slug %}" class="remove-from-shelf">{% trans "Delete" %}</a>
+                        <a href="{% url remove_from_shelf last_tag.slug book.urlid %}" class="remove-from-shelf">{% trans "Delete" %}</a>
                     {% endif %}
                     {{ book.short_html }}</li>
             {% endfor %}
             </ol>
+            {% endspaceless %}
             {% paginate %}
         {% else %}
             {% trans "Sorry! Search cirteria did not match any resources." %}
index d047dbe..28c1222 100644 (file)
@@ -3,7 +3,7 @@
 {% if shelves %}
 <ul class="shelf-list">
 {% for shelf in shelves %}
-    <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "remove" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.get_count }})</a></li>
+    <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "remove" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.book_count }})</a></li>
 {% endfor %}
 </ul>
 {% else %}
diff --git a/wolnelektury/templates/info/base.html b/wolnelektury/templates/info/base.html
deleted file mode 100644 (file)
index ea15136..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-{% load chunks %}
-
-{% block title %}{{ object.page_title }}{% endblock %}
-
-{% block metadescription %}{{ object.left_column|striptags|truncatewords:10 }}{% endblock %}
-
-{% block body %}
-    <h1>{{ object.title }}</h1>
-    <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 the main page" %}</a></p>
-    </form>
-
-    {% autoescape off %}
-    <div class="column-left">
-       {{ object.left_column }}
-    </div>
-    <div class="column-right">
-       {{ object.right_column }}
-    </div>
-       {% endautoescape %}
-{% endblock %}
index fee416e..415b13a 100644 (file)
@@ -1,5 +1,5 @@
 {% load i18n %}
-{% load stats %}
+{% load reporting_stats %}
 
 {% count_books_nonempty book_count %}
 <p>
@@ -19,4 +19,4 @@ or transferring 1&#37; of your income tax</a>.
 <p>{% blocktrans %}Become an editor of Wolne Lektury! Find out if
 we're currently working on a reading you're looking for and prepare
 a publication by yourself by logging into the Editorial Platform.{% endblocktrans %}
-<a href='{% url help_us %}'>{% trans "More..." %}</a></p>
+<a href='{% url infopage 'mozesz-nam-pomoc' %}'>{% trans "More..." %}</a></p>
index 5c8490c..90e96f6 100644 (file)
@@ -2,7 +2,7 @@
 {% load i18n %}
 {% load catalogue_tags %}
 
-{% block title %}Leśmianator w WolneLektury.pl{% endblock %}
+{% block titleextra %}Leśmianator{% endblock %}
 
 {% block metadescription %}Stwórz własny wierszmiks z utworów znajdujących się na Wolnych Lekturach.{% endblock %}
 
@@ -10,9 +10,6 @@
 
 {% block body %}
     <h1>Leśmianator</h1>
-    <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">
         <p>Leśmianator tworzy wierszmiksy – dzięki niemu
index 03d1e9d..23e9437 100644 (file)
@@ -15,7 +15,7 @@
                 {# shelf or global mixing #}
                 <a href='' class='menu-link'>Twórzże się jeszcze raz!</a>
             {% else %}{% if book %} 
-                <a href='{% url poem_from_book book.slug %}' class='menu-link'>Twórzże się jeszcze raz!</a>
+                <a href='{% url poem_from_book book.urlid %}' class='menu-link'>Twórzże się jeszcze raz!</a>
             {% endif %}{% endif %}
             <span style='float: right'>Wolne Lektury przepuszczone przez mikser.</a>
         </div>
@@ -40,7 +40,7 @@
             {% if book %}
                 <p>Tekst powstał przez zmiksowanie utworu
                 <a href="{{ book.get_absolute_url }}">{{ book.title }}</a>.<br/>
-                <a href="{% url poem_from_book book.slug %}">Zmiksuj go ponownie</a>
+                <a href="{% url poem_from_book book.urlid %}">Zmiksuj go ponownie</a>
                 albo <a href="{% url lesmianator %}">zobacz</a>, co jeszcze możesz zamieszać.</p>
             {% else %}{% if books %}
                 <p>Tekst powstał przez zmiksowanie utworów:</p>
index 511bbfc..b4e44e2 100644 (file)
@@ -4,7 +4,7 @@
 
 {% block bodyid %}document-list-body{% endblock %}
 
-{% block title %}{% trans "Hand-outs for teachers on " %}WolneLektury.pl{% endblock %}
+{% block titleextra %}{% trans "Hand-outs for teachers" %}{% endblock %}
 
 {% block metadescription %}Scenariusze lekcji. Materiały dla nauczycieli na wolnej licencji.{% endblock %}
 
@@ -30,9 +30,7 @@
 {% endblock extrahead %}
 {% block body %}
     <h1>{% trans "Hand-outs for teachers" %}</h1>
-    <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="document-list">
         {% chunk "document-list" %}
     </div>
diff --git a/wolnelektury/templates/main_page.html b/wolnelektury/templates/main_page.html
new file mode 100755 (executable)
index 0000000..ddb90fa
--- /dev/null
@@ -0,0 +1,77 @@
+{% extends "base.html" %}
+{% load cache i18n catalogue_tags infopages_tags %}
+
+
+{% block title %}{% trans "Wolne Lektury internet library" %}{% endblock %}
+
+{% block body %}
+
+    <blockquote id="big-cite">
+        <a href='http://google.com'>
+        <h2 class="mono">Jarosław Lipszyc poleca:</h2>
+        <p id="big-cite-text">Dobranoc, obróć jeszcze raz na mnie oczęta,<br/>
+        (…) Daj mi pierś ucałować<br/>
+        Dobranoc, zapięta.</p>
+        <p class="mono" id="big-cite-source">Adam Mickiewicz, Dziady część III</p>
+        </a>
+    </blockquote>
+
+
+    {% spaceless %}
+
+
+    <div id="promo-box">
+        <div class="grid-line" id="promo-box-header">
+            <h2 class="mono">Trwa konkurs</h2>
+        </div>
+        <div id="promo-box-body">
+            <p id="promo-box-title" class="mono">Konkurs poezji automatycznej</p>
+            <p>Znacie Leśmianatora? To niewielki skrypt miskujący na życzenie
+            wiersze z Wolnych Lektur.</p>
+        </div>
+    </div>
+
+
+    <h2 class="grid-line"><span class="mono">Ostatnie publikacje</span></h2>
+        {% for book in last_published %}
+            {{ book.mini_box }}
+        {% endfor %}
+
+    <div class="infopages-box">
+        <h2 class="grid-line"><span class='mono'>Aktualności</span></h2>
+        {% cache 1800 latest-blog-posts %}
+            {% latest_blog_posts "http://nowoczesnapolska.org.pl/category/wolne-lektury/feed/" %}
+        {% endcache %}
+    </div>
+
+
+    <div class="infopages-box">
+        <h2 class="grid-line"><span class='mono'>Narzędzia</span></h2>
+
+        <ul>
+            <li><a href="{% url suggest %}" id="suggest" class="ajaxable">{% trans "Report a bug or suggestion" %}</a></li>
+            <li><a href="http://turniej.wolnelektury.pl">Turniej Elektrybałtów</a></li>
+            <li><a href="{% url lesmianator %}">Leśmianator</a></li>
+            <li><a href="">{% trans "Mobile app" %}</a></li>
+            <li><a href="{% url infopage "widget" %}">{% trans "Widget" %}</a></li>
+            <li><a href="">{% trans "Public domain counter" %}</a></li>
+            <li><a href="{% url suggest_publishing %}" id="suggest-publishing" class="ajaxable">{% trans "Missing a book?" %}</a></li>
+        </ul>
+    </div>
+
+
+    <div class="infopages-box">
+        <h2 class="grid-line"><span class='mono'>Informacje</span></h2>
+
+        {% infopages_on_main %}
+
+        <div class="social-links">
+            <a href="http://pl-pl.facebook.com/pages/Wolne-Lektury/203084073268"><img src="{{ STATIC_URL }}img/social/facebook.png" alt="WolneLektury @ Facebook" /></a>
+            <a href="http://nk.pl/profile/30441509"><img src="{{ STATIC_URL }}img/social/naszaklasa.png" alt="WolneLektury @ NK" /></a>
+        </div>
+    </div>
+
+
+    {% endspaceless %}
+
+{% endblock %}
index 1fd0985..7cdcf2a 100644 (file)
@@ -1,7 +1,7 @@
 {% extends "base.html" %}
 {% load i18n %}
 
-{% block title %}{{ author.name }} w WolneLektury.pl{% endblock %}
+{% block titleextra %}{{ author.name }}{% endblock %}
 
 {% block metadescription %}Licznik domeny publicznej: {{author.name}}.{% endblock %}
 
@@ -9,9 +9,6 @@
 
 {% block body %}
     <h1>{{ author.name }}</h1>
-    <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">
         {% if author.has_description %}
@@ -42,7 +39,7 @@
             {% else %}
                 <div>
                     <p>{% trans "This author's works will become part of public domain and will be allowed to be published without restrictions in" %}</p>
-                    {% include "pdcounter/pd_counter.html" %}
+                    <div id='countdown' data-year='{{ pd_counter }}'></div>
                     <p>{% trans "<a href='http://domenapubliczna.org/co-to-jest-domena-publiczna/'>Find out</a> why Internet libraries can't publish this author's works." %}</p>
                 </div>
             {% endif %}
index 78b04d6..6e77a78 100644 (file)
@@ -1,7 +1,7 @@
 {% extends "base.html" %}
 {% load i18n %}
 
-{% block title %}{{ book.title }} w WolneLektury.pl{% endblock %}
+{% block titleextra %}{{ book.title }}{% endblock %}
 
 {% block metadescription %}Licznik domeny publicznej: {{ book.title }}.{% endblock %}
 
@@ -9,9 +9,6 @@
 
 {% block body %}
     <h1>{{ book.author }}, {{ book.title }}</h1>
-    <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">
     {% if book.in_pd %}
@@ -19,7 +16,7 @@
        {% else %}
            {% if book.pd %}
                        <p>{% trans "This work will become part of public domain and will be allowed to be published without restrictions in" %}</p>
-                   {% include "pdcounter/pd_counter.html" %}
+            <div id='countdown' data-year='{{ pd_counter }}'></div>
                        <p>{% trans "<a href='http://domenapubliczna.org/co-to-jest-domena-publiczna/'>Find out</a> why Internet libraries can't publish this work." %}</p>
                {% else %}
                    <p>{% trans "This work is copyrighted." %}
diff --git a/wolnelektury/templates/pdcounter/pd_counter.html b/wolnelektury/templates/pdcounter/pd_counter.html
deleted file mode 100644 (file)
index fd157ad..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<div id='countdown'></div>
-<script>
-{% if LANGUAGE_CODE != 'en' %}
-    $.countdown.setDefaults($.countdown.regional['{{ LANGUAGE_CODE }}']);
-{% else %}
-    $.countdown.setDefaults($.countdown.regional['']);
-{% endif %}
-d = new Date({{ pd_counter }}, 0, 1);
-function re() {location.reload()};
-$('#countdown').countdown({until: d, format: 'ydHMS', serverSync: serverTime,
-onExpiry: re, alwaysExpire: true});
-</script>
index f9f7ab9..cda9ebb 100644 (file)
@@ -7,7 +7,7 @@ from modeltranslation.translator import translator, TranslationOptions
 from infopages.models import InfoPage
 
 class InfoPageTranslationOptions(TranslationOptions):
-    fields = ('page_title', 'title', 'left_column', 'right_column')
+    fields = ('title', 'left_column', 'right_column')
 
 translator.register(InfoPage, InfoPageTranslationOptions)
 
index 2a199c7..f7d5015 100644 (file)
@@ -4,39 +4,37 @@ import os
 from django.conf.urls.defaults import *
 from django.conf import settings
 from django.contrib import admin
+import views
 
-from catalogue.forms import SearchForm
 
-from infopages.models import InfoPage
+admin.autodiscover()
 
+urlpatterns = patterns('wolnelektury.views',
+    url(r'^$', 'main_page', name='main_page'),
 
-admin.autodiscover()
+    url(r'^zegar/$', 'clock', name='clock'),
+
+    # Authentication
+    url(r'^uzytkownicy/zaloguj/$', views.LoginFormView(), name='login'),
+    url(r'^uzytkownicy/utworz/$', views.RegisterFormView(), name='register'),
+    url(r'^uzytkownicy/wyloguj/$', 'logout_then_redirect', name='logout'),
+)
 
-urlpatterns = patterns('',
+urlpatterns += patterns('',
     url(r'^katalog/', include('catalogue.urls')),
     url(r'^materialy/', include('lessons.urls')),
     url(r'^opds/', include('opds.urls')),
     url(r'^sugestia/', include('suggest.urls')),
     url(r'^lesmianator/', include('lesmianator.urls')),
     url(r'^przypisy/', include('dictionary.urls')),
-    url(r'^statystyka/', include('stats.urls')),
-
-    # Static pages
-    url(r'^mozesz-nam-pomoc/$', 'infopages.views.infopage', {'slug': 'help_us'}, name='help_us'),
-    url(r'^o-projekcie/$', 'infopages.views.infopage', {'slug': 'about_us'}, name='about_us'),
-    url(r'^widget/$', 'infopages.views.infopage', {'slug': 'widget'}, name='widget'),
+    url(r'^raporty/', include('reporting.urls')),
+    url(r'^info/', include('infopages.urls')),
 
     # Admin panel
     url(r'^admin/catalogue/book/import$', 'catalogue.views.import_book', name='import_book'),
     url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
     url(r'^admin/', include(admin.site.urls)),
 
-    # Authentication
-    url(r'^uzytkownicy/zaloguj/$', 'catalogue.views.login', name='login'),
-    url(r'^uzytkownicy/wyloguj/$', 'catalogue.views.logout_then_redirect', name='logout'),
-    url(r'^uzytkownicy/utworz/$', 'catalogue.views.register', name='register'),
-    url(r'^uzytkownicy/login/$', 'django.contrib.auth.views.login', name='simple_login'),
-
     # API
     (r'^api/', include('api.urls')),
 
@@ -47,15 +45,23 @@ urlpatterns = patterns('',
         {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
     url(r'^%s(?P<path>.*)$' % settings.STATIC_URL[1:], 'django.views.static.serve',
         {'document_root': settings.STATIC_ROOT, 'show_indexes': True}),
-    url(r'^$', 'django.views.generic.simple.redirect_to', {'url': 'katalog/'}),
     url(r'^i18n/', include('django.conf.urls.i18n')),
 )
 
 urlpatterns += patterns('django.views.generic.simple',
     # old static pages - redirected
-    (r'^1procent/$', 'redirect_to', {'url': 'http://nowoczesnapolska.org.pl/wesprzyj_nas/'}),
-    (r'^wolontariat/$', 'redirect_to', {'url': '/mozesz-nam-pomoc/'}),
-    (r'^epub/$', 'redirect_to', {'url': '/katalog/lektury/'}),
+    url(r'^1procent/$', 'redirect_to',
+        {'url': 'http://nowoczesnapolska.org.pl/wesprzyj_nas/'}),
+    url(r'^epub/$', 'redirect_to',
+        {'url': '/katalog/lektury/'}),
+    url(r'^mozesz-nam-pomoc/$', 'redirect_to',
+        {'url': '/info/mozesz-nam-pomoc'}),
+    url(r'^o-projekcie/$', 'redirect_to',
+        {'url': '/info/o-projekcie'}),
+    url(r'^widget/$', 'redirect_to',
+        {'url': '/info/widget'}),
+    url(r'^wolontariat/$', 'redirect_to',
+        {'url': '/info/mozesz-nam-pomoc/'}),
 )
     
 
diff --git a/wolnelektury/views.py b/wolnelektury/views.py
new file mode 100755 (executable)
index 0000000..0af07f4
--- /dev/null
@@ -0,0 +1,68 @@
+from datetime import datetime
+
+from django.contrib import auth
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.http import urlquote_plus
+from django.utils.translation import ugettext_lazy as _
+from django.views.decorators import cache
+
+from ajaxable.utils import AjaxableFormView
+from catalogue.models import Book
+
+
+def main_page(request):
+    last_published = Book.objects.exclude(html_file='').order_by('-created_at')[:4]
+
+    return render_to_response("main_page.html", locals(),
+        context_instance=RequestContext(request))
+
+
+class LoginFormView(AjaxableFormView):
+    form_class = AuthenticationForm
+    #template = "auth/login.html"
+    title = _('Sign in')
+    submit = _('Sign in')
+
+    def __call__(self, request):
+        if request.user.is_authenticated():
+            return HttpResponseRedirect('/')
+        return super(LoginFormView, self).__call__(request)
+
+    def success(self, form, request):
+        auth.login(request, form.get_user())
+
+
+class RegisterFormView(AjaxableFormView):
+    form_class = UserCreationForm
+    #template = "auth/register.html"
+    title = _('Register')
+    submit = _('Register')
+
+    def __call__(self, request):
+        if request.user.is_authenticated():
+            return HttpResponseRedirect('/')
+        return super(RegisterFormView, self).__call__(request)
+
+    def success(self, form, request):
+        user = form.save()
+        user = auth.authenticate(
+            username=form.cleaned_data['username'],
+            password=form.cleaned_data['password1']
+        )
+        auth.login(request, user)
+
+
+@cache.never_cache
+def logout_then_redirect(request):
+    auth.logout(request)
+    return HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
+
+
+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'))