Using cache middleware instead of various caching micro-strategies,
authorRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Fri, 19 Sep 2014 14:38:11 +0000 (16:38 +0200)
committerRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Fri, 19 Sep 2014 14:38:11 +0000 (16:38 +0200)
together with django-ssify for two-phased rendering.
Remove dynfunctional, unused and undocumented  API.
Finally, what looks like correct ancestry-aware counters on related tags
(it's a shame all tests were passing, probably need more of those).
Removed unneeded build_absolute_uri tag.

115 files changed:
apps/ajaxable/templates/ajaxable/form.html
apps/api/emitters.py [new file with mode: 0644]
apps/api/handlers.py
apps/api/templates/api/main.html
apps/api/tests.py
apps/api/urls.py
apps/catalogue/__init__.py
apps/catalogue/apps.py [new file with mode: 0644]
apps/catalogue/fields.py
apps/catalogue/helpers.py
apps/catalogue/management/commands/checkintegrity.py
apps/catalogue/migrations/0002_book_ancestor.py
apps/catalogue/migrations/0003_populate_ancestors.py [new file with mode: 0644]
apps/catalogue/migrations/0004_remove_booktags_count_related_info.py [new file with mode: 0644]
apps/catalogue/models/__init__.py
apps/catalogue/models/book.py
apps/catalogue/models/collection.py
apps/catalogue/models/fragment.py
apps/catalogue/models/listeners.py [deleted file]
apps/catalogue/models/source.py
apps/catalogue/models/tag.py
apps/catalogue/signals.py [new file with mode: 0644]
apps/catalogue/tasks.py
apps/catalogue/templates/catalogue/audiobook_list.html
apps/catalogue/templates/catalogue/book_detail.html
apps/catalogue/templates/catalogue/book_list.html
apps/catalogue/templates/catalogue/book_mini_box.html
apps/catalogue/templates/catalogue/book_searched.html
apps/catalogue/templates/catalogue/book_short.html
apps/catalogue/templates/catalogue/book_text.html
apps/catalogue/templates/catalogue/book_wide.html
apps/catalogue/templates/catalogue/daisy_list.html
apps/catalogue/templates/catalogue/latest_blog_posts.html [deleted file]
apps/catalogue/templates/catalogue/menu.html
apps/catalogue/templates/catalogue/related_books.html
apps/catalogue/templates/catalogue/search_multiple_hits.html
apps/catalogue/templates/catalogue/tag_list.html
apps/catalogue/templates/catalogue/tagged_object_list.html
apps/catalogue/templates/catalogue/work-list.html
apps/catalogue/templatetags/catalogue_tags.py
apps/catalogue/test_utils.py
apps/catalogue/tests/__init__.py
apps/catalogue/tests/tags.py
apps/catalogue/tests/test_visit.py [new file with mode: 0644]
apps/catalogue/urls.py
apps/catalogue/utils.py
apps/catalogue/views.py
apps/chunks/migrations/0002_auto_20140911_1253.py [new file with mode: 0644]
apps/chunks/models.py
apps/chunks/templatetags/__init__.py [deleted file]
apps/chunks/templatetags/chunks.py [deleted file]
apps/chunks/urls.py [new file with mode: 0644]
apps/chunks/views.py [new file with mode: 0644]
apps/dictionary/models.py
apps/funding/management/commands/funding_notify.py
apps/funding/models.py
apps/funding/templates/funding/disable_notifications.html [changed mode: 0755->0644]
apps/funding/templates/funding/includes/funding.html [new file with mode: 0644]
apps/funding/templates/funding/includes/fundings.html [new file with mode: 0644]
apps/funding/templates/funding/includes/offer_status.html [new file with mode: 0644]
apps/funding/templates/funding/includes/offer_status_more.html [new file with mode: 0644]
apps/funding/templates/funding/offer_detail.html
apps/funding/templates/funding/snippets/any_remaining.html [changed mode: 0755->0644]
apps/funding/templates/funding/tags/funding.html [deleted file]
apps/funding/templates/funding/tags/offer_status.html [deleted file]
apps/funding/templates/funding/tags/offer_status_more.html [deleted file]
apps/funding/templatetags/funding_tags.py
apps/funding/urls.py
apps/funding/views.py
apps/infopages/templates/infopages/infopage.html
apps/libraries/urls.py
apps/newtagging/models.py
apps/pdcounter/views.py
apps/picture/models.py
apps/picture/templates/picture/picture_detail.html
apps/picture/templates/picture/picture_list_thumb.html
apps/picture/templates/picture/picture_wide.html
apps/picture/templatetags/picture_tags.py
apps/picture/views.py
apps/polls/templates/polls/tags/poll.html
apps/polls/views.py
apps/search/templatetags/search_tags.py
apps/social/models.py
apps/social/templates/social/cite_info.html [new file with mode: 0644]
apps/social/templates/social/cite_promo.html
apps/social/templates/social/sets_form.html
apps/social/templates/social/shelf_tags.html
apps/social/templatetags/social_tags.py
apps/social/urls.py
apps/social/utils.py
apps/social/views.py
apps/sponsors/models.py
apps/sponsors/templatetags/__init__.py [deleted file]
apps/sponsors/templatetags/sponsor_tags.py [deleted file]
apps/sponsors/urls.py [new file with mode: 0644]
apps/sponsors/views.py [new file with mode: 0644]
apps/suggest/templates/publishing_suggest.html
apps/wolnelektury_core/__init__.py
apps/wolnelektury_core/apps.py [new file with mode: 0644]
apps/wolnelektury_core/models.py [deleted file]
apps/wolnelektury_core/signals.py [new file with mode: 0644]
apps/wolnelektury_core/static/js/base.js
apps/wolnelektury_core/templates/auth/login.html
apps/wolnelektury_core/templates/auth/login_register.html
apps/wolnelektury_core/templates/latest_blog_posts.html [new file with mode: 0644]
apps/wolnelektury_core/templates/main_page.html
apps/wolnelektury_core/templates/pagination/pagination.html
apps/wolnelektury_core/templates/superbase.html
apps/wolnelektury_core/templatetags/common_tags.py
apps/wolnelektury_core/views.py
requirements.txt
wolnelektury/settings/__init__.py
wolnelektury/settings/cache.py
wolnelektury/settings/custom.py
wolnelektury/urls.py

index 38113db..13586ac 100755 (executable)
@@ -1,10 +1,11 @@
 {% load i18n %}
 {% load i18n %}
+{% load ssi_csrf_token from ssify %}
 
 <h1>{{ title }}</h1>
 
 <form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8"
        class="cuteform{% if placeholdize %} hidelabels{% endif %}">
 
 <h1>{{ title }}</h1>
 
 <form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8"
        class="cuteform{% if placeholdize %} hidelabels{% endif %}">
-{% csrf_token %}
+{% ssi_csrf_token %}
 {% if honeypot %}
     {% load honeypot %}
     {% render_honeypot_field %}
 {% if honeypot %}
     {% load honeypot %}
     {% render_honeypot_field %}
diff --git a/apps/api/emitters.py b/apps/api/emitters.py
new file mode 100644 (file)
index 0000000..2f6f7e7
--- /dev/null
@@ -0,0 +1,70 @@
+# -*- 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.
+#
+"""
+Wrappers for piston Emitter classes.
+
+When outputting a queryset of selected models, instead of returning
+XML or JSON stanzas, SSI include statements are returned.
+
+"""
+from django.core.urlresolvers import reverse
+from django.db.models.query import QuerySet
+from piston.emitters import Emitter, XMLEmitter, JSONEmitter
+from catalogue.models import Book, Fragment, Tag
+from django.utils.translation import get_language
+
+
+class SsiQS(object):
+    """A wrapper for QuerySet that won't serialize."""
+
+    def __init__(self, queryset):
+        self.queryset = queryset
+
+    def __unicode__(self):
+        raise TypeError("This is not serializable.")
+
+    def get_ssis(self, emitter_format):
+        """Yields SSI include statements for the queryset."""
+        url_pattern = reverse('api_include',
+                kwargs={'model': self.queryset.model.__name__.lower(),
+                    'pk': '0000',
+                    'emitter_format': emitter_format,
+                    'lang': get_language(),
+                    })
+        for instance in self.queryset:
+            yield "<!--#include file='%s'-->" % url_pattern.replace('0000',
+                    str(instance.pk))
+
+
+class SsiEmitterMixin(object):
+    def construct(self):
+        if isinstance(self.data, QuerySet) and self.data.model in (Book,
+                Fragment, Tag):
+            return SsiQS(self.data)
+        else:
+            return super(SsiEmitterMixin, self).construct()
+
+
+class SsiJsonEmitter(SsiEmitterMixin, JSONEmitter):
+    def render(self, request):
+        try:
+            return super(SsiJsonEmitter, self).render(request)
+        except TypeError:
+            return '[%s]' % ",".join(self.construct().get_ssis('json'))
+
+Emitter.register('json', SsiJsonEmitter, 'application/json; charset=utf-8')
+
+
+class SsiXmlEmitter(SsiEmitterMixin, XMLEmitter):
+    def render(self, request):
+        try:
+            return super(SsiXmlEmitter, self).render(request)
+        except TypeError:
+            return '<?xml version="1.0" encoding="utf-8"?>\n' \
+                '<response><resource>%s</resource></response>' % \
+                '</resource><resource>'.join(self.construct().get_ssis('xml'))
+
+Emitter.register('xml', SsiXmlEmitter, 'text/xml; charset=utf-8')
+
index a3a5ce5..5fe931e 100644 (file)
@@ -2,29 +2,24 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # 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, timedelta
 import json
 
 import json
 
-from django.conf import settings
 from django.contrib.sites.models import Site
 from django.contrib.sites.models import Site
-from django.core.cache import get_cache
 from django.core.urlresolvers import reverse
 from django.utils.functional import lazy
 from django.core.urlresolvers import reverse
 from django.utils.functional import lazy
-from django.utils.timezone import utc
 from piston.handler import AnonymousBaseHandler, BaseHandler
 from piston.utils import rc
 from sorl.thumbnail import default
 
 from piston.handler import AnonymousBaseHandler, BaseHandler
 from piston.utils import rc
 from sorl.thumbnail import default
 
-from api.helpers import timestamp
-from api.models import Deleted
 from catalogue.forms import BookImportForm
 from catalogue.models import Book, Tag, BookMedia, Fragment, Collection
 from picture.models import Picture
 from picture.forms import PictureImportForm
 from catalogue.forms import BookImportForm
 from catalogue.models import Book, Tag, BookMedia, Fragment, Collection
 from picture.models import Picture
 from picture.forms import PictureImportForm
-from wolnelektury.utils import tz
 
 from stats.utils import piwik_track
 
 
 from stats.utils import piwik_track
 
+from . import emitters # Register our emitters
+
 API_BASE = WL_BASE = MEDIA_BASE = lazy(
     lambda: u'http://' + Site.objects.get_current().domain, unicode)()
 
 API_BASE = WL_BASE = MEDIA_BASE = lazy(
     lambda: u'http://' + Site.objects.get_current().domain, unicode)()
 
@@ -71,7 +66,10 @@ def read_tags(tags, allowed):
             raise ValueError('Category not allowed.')
 
         if category == 'book':
             raise ValueError('Category not allowed.')
 
         if category == 'book':
-            books.append(Book.objects.get(slug=slug))
+            try:
+                books.append(Book.objects.get(slug=slug))
+            except Book.DoesNotExist:
+                raise ValueError('Unknown book.')
 
         try:
             real_tags.append(Tag.objects.get(category=category, slug=slug))
 
         try:
             real_tags.append(Tag.objects.get(category=category, slug=slug))
@@ -174,8 +172,8 @@ class AnonymousBooksHandler(AnonymousBaseHandler, BookDetails):
         return book.tags.filter(category='genre')
 
     @piwik_track
         return book.tags.filter(category='genre')
 
     @piwik_track
-    def read(self, request, tags, top_level=False,
-                audiobooks=False, daisy=False):
+    def read(self, request, tags=None, top_level=False,
+                audiobooks=False, daisy=False, pk=None):
         """ Lists all books with given tags.
 
         :param tags: filtering tags; should be a path of categories
         """ Lists all books with given tags.
 
         :param tags: filtering tags; should be a path of categories
@@ -184,8 +182,14 @@ class AnonymousBooksHandler(AnonymousBaseHandler, BookDetails):
              it's children are aren't. By default all books matching the tags
              are returned.
         """
              it's children are aren't. By default all books matching the tags
              are returned.
         """
+        if pk is not None:
+            try:
+                return Book.objects.get(pk=pk)
+            except Book.DoesNotExist:
+                return rc.NOT_FOUND
+
         try:
         try:
-            tags, ancestors_ = read_tags(tags, allowed=book_tag_categories)
+            tags, _ancestors = read_tags(tags, allowed=book_tag_categories)
         except ValueError:
             return rc.NOT_FOUND
 
         except ValueError:
             return rc.NOT_FOUND
 
@@ -360,8 +364,13 @@ class TagsHandler(BaseHandler, TagDetails):
     fields = ['name', 'href', 'url']
 
     @piwik_track
     fields = ['name', 'href', 'url']
 
     @piwik_track
-    def read(self, request, category):
+    def read(self, request, category=None, pk=None):
         """ Lists all tags in the category (eg. all themes). """
         """ Lists all tags in the category (eg. all themes). """
+        if pk is not None:
+            try:
+                return Tag.objects.exclude(category='set').get(pk=pk)
+            except Book.DoesNotExist:
+                return rc.NOT_FOUND
 
         try:
             category_sng = category_singular[category]
 
         try:
             category_sng = category_singular[category]
@@ -442,274 +451,6 @@ class FragmentsHandler(BaseHandler, FragmentDetails):
             return rc.NOT_FOUND
 
 
             return rc.NOT_FOUND
 
 
-
-# Changes handlers
-
-class CatalogueHandler(BaseHandler):
-
-    @staticmethod
-    def fields(request, name):
-        fields_str = request.GET.get(name) if request is not None else None
-        return fields_str.split(',') if fields_str is not None else None
-
-    @staticmethod
-    def until(t=None):
-        """ Returns time suitable for use as upper time boundary for check.
-
-            Used to avoid issues with time between setting the change stamp
-            and actually saving the model in database.
-            Cuts the microsecond part to avoid issues with DBs where time has
-            more precision.
-
-            :param datetime t: manually sets the upper boundary
-
-        """
-        # set to five minutes ago, to avoid concurrency issues
-        if t is None:
-            t = datetime.utcnow().replace(tzinfo=utc) - timedelta(seconds=settings.API_WAIT)
-        # set to whole second in case DB supports something smaller
-        return t.replace(microsecond=0)
-
-    @staticmethod
-    def book_dict(book, fields=None):
-        all_fields = ['url', 'title', 'description',
-                      'gazeta_link', 'wiki_link',
-                      ] + Book.formats + BookMedia.formats.keys() + [
-                      'parent', 'parent_number',
-                      'tags',
-                      'license', 'license_description', 'source_name',
-                      'technical_editors', 'editors',
-                      'author', 'sort_key',
-                     ]
-        if fields:
-            fields = (f for f in fields if f in all_fields)
-        else:
-            fields = all_fields
-
-        extra_info = book.extra_info
-
-        obj = {}
-        for field in fields:
-
-            if field in Book.formats:
-                f = getattr(book, field+'_file')
-                if f:
-                    obj[field] = {
-                        'url': f.url,
-                        'size': f.size,
-                    }
-
-            elif field in BookMedia.formats:
-                media = []
-                for m in book.media.filter(type=field).iterator():
-                    media.append({
-                        'url': m.file.url,
-                        'size': m.file.size,
-                    })
-                if media:
-                    obj[field] = media
-
-            elif field == 'url':
-                obj[field] = book.get_absolute_url()
-
-            elif field == 'tags':
-                obj[field] = [t.id for t in book.tags.exclude(category='set').iterator()]
-
-            elif field == 'author':
-                obj[field] = ", ".join(t.name for t in book.tags.filter(category='author').iterator())
-
-            elif field == 'parent':
-                obj[field] = book.parent_id
-
-            elif field in ('license', 'license_description', 'source_name',
-                      'technical_editors', 'editors'):
-                f = extra_info.get(field)
-                if f:
-                    obj[field] = f
-
-            else:
-                f = getattr(book, field)
-                if f:
-                    obj[field] = f
-
-        obj['id'] = book.id
-        return obj
-
-    @classmethod
-    def book_changes(cls, request=None, since=0, until=None, fields=None):
-        since = datetime.fromtimestamp(int(since), tz)
-        until = cls.until(until)
-
-        changes = {
-            'time_checked': timestamp(until)
-        }
-
-        if not fields:
-            fields = cls.fields(request, 'book_fields')
-
-        added = []
-        updated = []
-        deleted = []
-
-        last_change = since
-        for book in Book.objects.filter(changed_at__gte=since,
-                    changed_at__lt=until).iterator():
-            book_d = cls.book_dict(book, fields)
-            updated.append(book_d)
-        if updated:
-            changes['updated'] = updated
-
-        for book in Deleted.objects.filter(content_type=Book,
-                    deleted_at__gte=since,
-                    deleted_at__lt=until,
-                    created_at__lt=since).iterator():
-            deleted.append(book.id)
-        if deleted:
-            changes['deleted'] = deleted
-
-        return changes
-
-    @staticmethod
-    def tag_dict(tag, fields=None):
-        all_fields = ('name', 'category', 'sort_key', 'description',
-                      'gazeta_link', 'wiki_link',
-                      'url', 'books',
-                     )
-
-        if fields:
-            fields = (f for f in fields if f in all_fields)
-        else:
-            fields = all_fields
-
-        obj = {}
-        for field in fields:
-
-            if field == 'url':
-                obj[field] = tag.get_absolute_url()
-
-            elif field == 'books':
-                obj[field] = [b.id for b in Book.tagged_top_level([tag]).iterator()]
-
-            elif field == 'sort_key':
-                obj[field] = tag.sort_key
-
-            else:
-                f = getattr(tag, field)
-                if f:
-                    obj[field] = f
-
-        obj['id'] = tag.id
-        return obj
-
-    @classmethod
-    def tag_changes(cls, request=None, since=0, until=None, fields=None, categories=None):
-        since = datetime.fromtimestamp(int(since), tz)
-        until = cls.until(until)
-
-        changes = {
-            'time_checked': timestamp(until)
-        }
-
-        if not fields:
-            fields = cls.fields(request, 'tag_fields')
-        if not categories:
-            categories = cls.fields(request, 'tag_categories')
-
-        all_categories = ('author', 'epoch', 'kind', 'genre')
-        if categories:
-            categories = (c for c in categories if c in all_categories)
-        else:
-            categories = all_categories
-
-        updated = []
-        deleted = []
-
-        for tag in Tag.objects.filter(category__in=categories,
-                    changed_at__gte=since,
-                    changed_at__lt=until
-                    ).exclude(items=None).iterator():
-            tag_d = cls.tag_dict(tag, fields)
-            updated.append(tag_d)
-        for tag in Tag.objects.filter(category__in=categories,
-                    created_at__lt=since,
-                    changed_at__gte=since,
-                    changed_at__lt=until,
-                    items=None).iterator():
-            deleted.append(tag.id)
-        if updated:
-            changes['updated'] = updated
-
-        for tag in Deleted.objects.filter(category__in=categories,
-                content_type=Tag,
-                    deleted_at__gte=since,
-                    deleted_at__lt=until,
-                    created_at__lt=since).iterator():
-            deleted.append(tag.id)
-        if deleted:
-            changes['deleted'] = deleted
-
-        return changes
-
-    @classmethod
-    def changes(cls, request=None, since=0, until=None, book_fields=None,
-                tag_fields=None, tag_categories=None):
-        until = cls.until(until)
-        since = int(since)
-
-        if not since:
-            cache = get_cache('api')
-            key = hash((book_fields, tag_fields, tag_categories,
-                    tuple(sorted(request.GET.items()))
-                  ))
-            value = cache.get(key)
-            if value is not None:
-                return value
-
-        changes = {
-            'time_checked': timestamp(until)
-        }
-
-        changes_by_type = {
-            'books': cls.book_changes(request, since, until, book_fields),
-            'tags': cls.tag_changes(request, since, until, tag_fields, tag_categories),
-        }
-
-        for model in changes_by_type:
-            for field in changes_by_type[model]:
-                if field == 'time_checked':
-                    continue
-                changes.setdefault(field, {})[model] = changes_by_type[model][field]
-
-        if not since:
-            cache.set(key, changes)
-
-        return changes
-
-
-class BookChangesHandler(CatalogueHandler):
-    allowed_methods = ('GET',)
-
-    @piwik_track
-    def read(self, request, since):
-        return self.book_changes(request, since)
-
-
-class TagChangesHandler(CatalogueHandler):
-    allowed_methods = ('GET',)
-
-    @piwik_track
-    def read(self, request, since):
-        return self.tag_changes(request, since)
-
-
-class ChangesHandler(CatalogueHandler):
-    allowed_methods = ('GET',)
-
-    @piwik_track
-    def read(self, request, since):
-        return self.changes(request, since)
-
-
 class PictureHandler(BaseHandler):
     model = Picture
     fields = ('slug', 'title')
 class PictureHandler(BaseHandler):
     model = Picture
     fields = ('slug', 'title')
index 5c81a2d..04244dc 100755 (executable)
@@ -1,6 +1,6 @@
 {% extends "base.html" %}
 {% load i18n %}
 {% extends "base.html" %}
 {% load i18n %}
-{% load common_tags %}
+{% load build_absolute_uri from fnp_common %}
 
 {% block title %}{% trans "WolneLektury.pl API" %}{% endblock %}
 
 
 {% block title %}{% trans "WolneLektury.pl API" %}{% endblock %}
 
index 87c4f75..94abe53 100644 (file)
@@ -16,89 +16,23 @@ import picture.tests
 
 
 @override_settings(
 
 
 @override_settings(
-    API_WAIT=-1,
-    NO_SEARCH_INDEX = True,
-    CACHES = {'api': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
-              'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
-              'permanent': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}}
+    NO_SEARCH_INDEX=True,
+    CACHES={'default': {
+        'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}},
+    SSIFY_CACHE_ALIASES=['default'],
+    SSIFY_RENDER=True,
 )
 class ApiTest(TestCase):
 )
 class ApiTest(TestCase):
-    pass
+    def load_json(self, url):
+        content = self.client.get(url).content
+        try:
+            data = json.loads(content)
+        except ValueError:
+            self.fail('No JSON could be decoded:', content)
+        return data
 
 
 
 
-class ChangesTest(ApiTest):
-
-    def test_basic(self):
-        book = Book(title='A Book')
-        book.save()
-        tag = Tag.objects.create(category='author', name='Author')
-        book.tags = [tag]
-        book.save()
-
-        changes = json.loads(self.client.get('/api/changes/0.json?book_fields=title&tag_fields=name').content)
-        self.assertEqual(changes['updated']['books'],
-                         [{'id': book.id, 'title': book.title}],
-                         'Invalid book format in changes')
-        self.assertEqual(changes['updated']['tags'],
-                         [{'id': tag.id, 'name': tag.name}],
-                         'Invalid tag format in changes')
-
-
-class BookChangesTests(ApiTest):
-
-    def setUp(self):
-        super(BookChangesTests, self).setUp()
-        self.book = Book.objects.create(slug='slug')
-
-    def test_basic(self):
-        # test book in book_changes.added
-        changes = json.loads(self.client.get('/api/book_changes/0.json').content)
-        self.assertEqual(len(changes['updated']),
-                         1,
-                         'Added book not in book_changes.updated')
-
-    def test_deleted_disappears(self):
-        # test deleted book disappears
-        Book.objects.all().delete()
-        changes = json.loads(self.client.get('/api/book_changes/0.json').content)
-        self.assertEqual(len(changes), 1,
-                         'Deleted book should disappear.')
-
-    def test_shelf(self):
-        changed_at = self.book.changed_at
-
-        # putting on a shelf should not update changed_at
-        shelf = Tag.objects.create(category='set', slug='shelf')
-        self.book.tags = [shelf]
-        self.assertEqual(self.book.changed_at,
-                         changed_at)
-
-class TagChangesTests(ApiTest):
-
-    def setUp(self):
-        super(TagChangesTests, self).setUp()
-        self.tag = Tag.objects.create(category='author')
-        self.book = Book.objects.create()
-        self.book.tags = [self.tag]
-        self.book.save()
-
-    def test_added(self):
-        # test tag in tag_changes.added
-        changes = json.loads(self.client.get('/api/tag_changes/0.json').content)
-        self.assertEqual(len(changes['updated']),
-                         1,
-                         'Added tag not in tag_changes.updated')
-
-    def test_empty_disappears(self):
-        self.book.tags = []
-        self.book.save()
-        changes = json.loads(self.client.get('/api/tag_changes/0.json').content)
-        self.assertEqual(len(changes), 1,
-                         'Empty or deleted tag should disappear.')
-
-
-
-class BookTests(TestCase):
+class BookTests(ApiTest):
 
     def setUp(self):
         self.tag = Tag.objects.create(category='author', slug='joe')
 
     def setUp(self):
         self.tag = Tag.objects.create(category='author', slug='joe')
@@ -108,23 +42,23 @@ class BookTests(TestCase):
         self.book_tagged.save()
 
     def test_book_list(self):
         self.book_tagged.save()
 
     def test_book_list(self):
-        books = json.loads(self.client.get('/api/books/').content)
+        books = self.load_json('/api/books/')
         self.assertEqual(len(books), 2,
                          'Wrong book list.')
 
     def test_tagged_books(self):
         self.assertEqual(len(books), 2,
                          'Wrong book list.')
 
     def test_tagged_books(self):
-        books = json.loads(self.client.get('/api/authors/joe/books/').content)
+        books = self.load_json('/api/authors/joe/books/')
 
         self.assertEqual([b['title'] for b in books], [self.book_tagged.title],
                         'Wrong tagged book list.')
 
     def test_detail(self):
 
         self.assertEqual([b['title'] for b in books], [self.book_tagged.title],
                         'Wrong tagged book list.')
 
     def test_detail(self):
-        book = json.loads(self.client.get('/api/books/a-book/').content)
+        book = self.load_json('/api/books/a-book/')
         self.assertEqual(book['title'], self.book.title,
                         'Wrong book details.')
 
 
         self.assertEqual(book['title'], self.book.title,
                         'Wrong book details.')
 
 
-class TagTests(TestCase):
+class TagTests(ApiTest):
 
     def setUp(self):
         self.tag = Tag.objects.create(category='author', slug='joe', name='Joe')
 
     def setUp(self):
         self.tag = Tag.objects.create(category='author', slug='joe', name='Joe')
@@ -133,12 +67,12 @@ class TagTests(TestCase):
         self.book.save()
 
     def test_tag_list(self):
         self.book.save()
 
     def test_tag_list(self):
-        tags = json.loads(self.client.get('/api/authors/').content)
+        tags = self.load_json('/api/authors/')
         self.assertEqual(len(tags), 1,
                         'Wrong tag list.')
 
     def test_tag_detail(self):
         self.assertEqual(len(tags), 1,
                         'Wrong tag list.')
 
     def test_tag_detail(self):
-        tag = json.loads(self.client.get('/api/authors/joe/').content)
+        tag = self.load_json('/api/authors/joe/')
         self.assertEqual(tag['name'], self.tag.name,
                         'Wrong tag details.')
 
         self.assertEqual(tag['name'], self.tag.name,
                         'Wrong tag details.')
 
index 7c12c01..e9b106c 100644 (file)
@@ -7,16 +7,12 @@ from django.views.decorators.csrf import csrf_exempt
 from django.views.generic import TemplateView
 from piston.authentication import OAuthAuthentication, oauth_access_token
 from piston.resource import Resource
 from django.views.generic import TemplateView
 from piston.authentication import OAuthAuthentication, oauth_access_token
 from piston.resource import Resource
-
+from ssify import ssi_included
 from api import handlers
 from api.helpers import CsrfExemptResource
 
 auth = OAuthAuthentication(realm="Wolne Lektury")
 
 from api import handlers
 from api.helpers import CsrfExemptResource
 
 auth = OAuthAuthentication(realm="Wolne Lektury")
 
-book_changes_resource = Resource(handler=handlers.BookChangesHandler)
-tag_changes_resource = Resource(handler=handlers.TagChangesHandler)
-changes_resource = Resource(handler=handlers.ChangesHandler)
-
 book_list_resource = CsrfExemptResource(handler=handlers.BooksHandler, authentication=auth)
 ebook_list_resource = Resource(handler=handlers.EBooksHandler)
 #book_list_resource = Resource(handler=handlers.BooksHandler)
 book_list_resource = CsrfExemptResource(handler=handlers.BooksHandler, authentication=auth)
 ebook_list_resource = Resource(handler=handlers.EBooksHandler)
 #book_list_resource = Resource(handler=handlers.BooksHandler)
@@ -33,6 +29,21 @@ fragment_list_resource = Resource(handler=handlers.FragmentsHandler)
 
 picture_resource = CsrfExemptResource(handler=handlers.PictureHandler, authentication=auth)
 
 
 picture_resource = CsrfExemptResource(handler=handlers.PictureHandler, authentication=auth)
 
+
+@ssi_included
+def incl(request, model, pk, emitter_format):
+    resource = {
+        'book': book_list_resource,
+        'fragment': fragment_list_resource,
+        'tag': tag_list_resource,
+        }[model]
+    resp = resource(request, pk=pk, emitter_format=emitter_format)
+    if emitter_format == 'xml':
+        # Ugly, but quick way of stripping <?xml?> header and <response> tags.
+        resp.content = resp.content[49:-11]
+    return resp
+
+
 urlpatterns = patterns(
     'piston.authentication',
     url(r'^oauth/request_token/$', 'oauth_request_token'),
 urlpatterns = patterns(
     'piston.authentication',
     url(r'^oauth/request_token/$', 'oauth_request_token'),
@@ -41,19 +52,13 @@ urlpatterns = patterns(
 
 ) + patterns('',
     url(r'^$', TemplateView.as_view(template_name='api/main.html'), name='api'),
 
 ) + patterns('',
     url(r'^$', TemplateView.as_view(template_name='api/main.html'), name='api'),
-
-
-    # changes handlers
-    url(r'^book_changes/(?P<since>\d*?)\.(?P<emitter_format>xml|json|yaml)$', book_changes_resource),
-    url(r'^tag_changes/(?P<since>\d*?)\.(?P<emitter_format>xml|json|yaml)$', tag_changes_resource),
-    # used by mobile app
-    url(r'^changes/(?P<since>\d*?)\.(?P<emitter_format>xml|json|yaml)$', changes_resource),
+    url(r'^include/(?P<model>book|fragment|tag)/(?P<pk>\d+)\.(?P<lang>.+)\.(?P<emitter_format>xml|json)$',
+        incl, name='api_include'),
 
     # info boxes (used by mobile app)
     url(r'book/(?P<id>\d*?)/info\.html$', 'catalogue.views.book_info'),
     url(r'tag/(?P<id>\d*?)/info\.html$', 'catalogue.views.tag_info'),
 
 
     # info boxes (used by mobile app)
     url(r'book/(?P<id>\d*?)/info\.html$', 'catalogue.views.book_info'),
     url(r'tag/(?P<id>\d*?)/info\.html$', 'catalogue.views.tag_info'),
 
-
     # books by collections
     url(r'^collections/$', collection_list_resource, name="api_collections"),
     url(r'^collections/(?P<slug>[^/]+)/$', collection_resource, name="api_collection"),
     # books by collections
     url(r'^collections/$', collection_list_resource, name="api_collections"),
     url(r'^collections/(?P<slug>[^/]+)/$', collection_resource, name="api_collection"),
index 2e7a89f..f3d44e1 100644 (file)
@@ -7,6 +7,9 @@ from django.conf import settings as settings
 from catalogue.utils import AppSettings
 
 
 from catalogue.utils import AppSettings
 
 
+default_app_config = 'catalogue.apps.CatalogueConfig'
+
+
 class Settings(AppSettings):
     """Default settings for catalogue app."""
     DEFAULT_LANGUAGE = u'pol'
 class Settings(AppSettings):
     """Default settings for catalogue app."""
     DEFAULT_LANGUAGE = u'pol'
diff --git a/apps/catalogue/apps.py b/apps/catalogue/apps.py
new file mode 100644 (file)
index 0000000..54bfc8f
--- /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.apps import AppConfig
+
+class CatalogueConfig(AppConfig):
+    name = 'catalogue'
+
+    def ready(self):
+        from . import signals
index d5cec2e..884ecef 100644 (file)
@@ -204,7 +204,7 @@ class BuildHtml(BuildEbook):
 
                 new_fragment.save()
                 new_fragment.tags = set(meta_tags + themes)
 
                 new_fragment.save()
                 new_fragment.tags = set(meta_tags + themes)
-            book.html_built.send(sender=book)
+            book.html_built.send(sender=type(self), instance=book)
             return True
         return False
 
             return True
         return False
 
@@ -235,19 +235,3 @@ class OverwritingFieldFile(FieldFile):
 
 class OverwritingFileField(models.FileField):
     attr_class = OverwritingFieldFile
 
 class OverwritingFileField(models.FileField):
     attr_class = OverwritingFieldFile
-
-
-try:
-    # check for south
-    from south.modelsinspector import add_introspection_rules
-except ImportError:
-    pass
-else:
-    add_introspection_rules([
-        (
-            [EbookField],
-            [],
-            {'format_name': ('format_name', {})}
-        )
-    ], ["^catalogue\.fields\.EbookField"])
-    add_introspection_rules([], ["^catalogue\.fields\.OverwritingFileField"])
index ddfa482..7ca2cbd 100644 (file)
-from django.db import connection
+# -*- 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.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import get_language
-from picture.models import Picture, PictureArea
-from catalogue.models import Fragment, Tag, Book
+from django.db.models import Count
+from .models import Tag, Book
 
 
 
 
-def _get_tag_relations_sql(tags):
-    select = """
-        SELECT Rx.object_id, Rx.content_type_id
-        FROM catalogue_tag_relation Rx"""
-    joins = []
-    where = ['WHERE Rx.tag_id = %d' % tags[0].pk]
-    for i, tag in enumerate(tags[1:]):
-        joins.append('INNER JOIN catalogue_tag_relation TR%(i)d '
-            'ON TR%(i)d.object_id = Rx.object_id '
-            'AND TR%(i)d.content_type_id = Rx.content_type_id' % {'i': i})
-        where.append('AND TR%d.tag_id = %d' % (i, tag.pk))
-    return " ".join([select] + joins + where)
+BOOK_CATEGORIES = ('author', 'epoch', 'genre', 'kind')
 
 
 
 
+def get_top_level_related_tags(tags=None, categories=BOOK_CATEGORIES):
+    """
+    Finds tags related to given tags through books, and counts their usage.
 
 
-def get_related_tags(tags):
-    # Get Tag fields for constructing tags in a raw query.
-    tag_fields = ('id', 'category', 'slug', 'sort_key', 'name_%s' % get_language())
-    tag_fields = ', '.join(
-            'T.%s' % connection.ops.quote_name(field)
-        for field in tag_fields)
-    tag_ids = tuple(t.pk for t in tags)
-
-    # This is based on fragments/areas sharing their works tags
-    qs = Tag.objects.raw('''
-        SELECT ''' + tag_fields + ''', COUNT(T.id) count
-        FROM (
-            -- R: TagRelations of all objects tagged with the given tags.
-            WITH R AS (
-                ''' + _get_tag_relations_sql(tags) + '''
-            )
-
-            SELECT ''' + tag_fields + ''', MAX(R4.object_id) ancestor
-
-            FROM R R1
-
-            -- R2: All tags of the found objects.
-            JOIN catalogue_tag_relation R2
-                ON R2.object_id = R1.object_id
-                    AND R2.content_type_id = R1.content_type_id
-
-            -- Tag data for output.
-            JOIN catalogue_tag T
-                ON T.id=R2.tag_id
-
-            -- Special case for books:
-            -- We want to exclude from output all the relations
-            -- between a book and a tag, if there's a relation between
-            -- the the book's ancestor and the tag in the result.
-            LEFT JOIN catalogue_book_ancestor A
-                ON A.from_book_id = R1.object_id
-                    AND R1.content_type_id = %s
-            LEFT JOIN catalogue_tag_relation R3
-                ON R3.tag_id = R2.tag_id
-                    AND R3.content_type_id = R1.content_type_id
-                    AND R3.object_id = A.to_book_id
-            LEFT JOIN R R4
-                ON R4.object_id = R3.object_id
-                AND R4.content_type_id = R3.content_type_id
-
-            WHERE
-                -- Exclude from the result the tags we started with.
-                R2.tag_id NOT IN %s
-                -- Special case for books: exclude descendants.
-                -- AND R4.object_id IS NULL
-                AND (
-                    -- Only count fragment tags on fragments
-                    -- and book tags for books.
-                    (R2.content_type_id IN %s AND T.category IN %s)
-                    OR
-                    (R2.content_type_id IN %s AND T.category IN %s)
-                )
-
-            GROUP BY T.id, R2.object_id, R2.content_type_id
-
-        ) T
-        -- Now group by tag and count occurencies.
-        WHERE ancestor IS NULL
-        GROUP BY ''' + tag_fields + '''
-        ORDER BY T.sort_key
-        ''', params=(
-            ContentType.objects.get_for_model(Book).pk,
-            tag_ids,
-            tuple(ContentType.objects.get_for_model(model).pk
-                for model in (Fragment, PictureArea)),
-            ('theme', 'object'),
-            tuple(ContentType.objects.get_for_model(model).pk
-                for model in (Book, Picture)),
-            ('author', 'epoch', 'genre', 'kind'),
-        ))
-    return qs
+    Takes ancestry into account: if a tag is applied to a book, its
+    usage on the book's descendants is ignored.
 
 
+    This is tested for PostgreSQL 9.1+, and might not work elsewhere.
+    It particular, it uses raw SQL using WITH clause, which is
+    supported in SQLite from v. 3.8.3, and is missing in MySQL.
+    http://bugs.mysql.com/bug.php?id=16244
 
 
-def get_fragment_related_tags(tags):
-    tag_fields = ', '.join(
-        'T.%s' % (connection.ops.quote_name(field.column))
-        for field in Tag._meta.fields)
+    """
+    # First, find all tag relations of relevant books.
+    bct = ContentType.objects.get_for_model(Book)
+    relations = Tag.intermediary_table_model.objects.filter(
+        content_type=bct)
+    if tags is not None:
+        tagged_books = Book.tagged.with_all(tags).only('pk')
+        relations = relations.filter(
+            object_id__in=tagged_books).exclude(
+            tag_id__in=[tag.pk for tag in tags])
 
 
-    tag_ids = tuple(t.pk for t in tags)
-        # This is based on fragments/areas sharing their works tags
-    return Tag.objects.raw('''
-        SELECT T.*, COUNT(T.id) count
-        FROM (
-
-            SELECT T.*
-
-            -- R1: TagRelations of all objects tagged with the given tags.
-            FROM (
-                ''' + _get_tag_relations_sql(tags) + '''
-            ) R1
-
-            -- R2: All tags of the found objects.
-            JOIN catalogue_tag_relation R2
-                ON R2.object_id = R1.object_id
-                    AND R2.content_type_id = R1.content_type_id
-
-            -- Tag data for output.
-            JOIN catalogue_tag T
-                ON T.id = R2.tag_id
+    rel_sql, rel_params = relations.query.sql_with_params()
 
 
-            WHERE
-                -- Exclude from the result the tags we started with.
-                R2.tag_id NOT IN %s
-            GROUP BY T.id, R2.object_id, R2.content_type_id
+    # Exclude those relations between a book and a tag,
+    # for which there is a relation between the book's ancestor
+    # and the tag and 
 
 
-        ) T
-        -- Now group by tag and count occurencies.
-        GROUP BY ''' + tag_fields + '''
-        ORDER BY T.sort_key
-        ''', params=(
-            tag_ids,
-        ))
-
-
-def tags_usage_for_books(categories):
-    tag_fields = ', '.join(
-            'T.%s' % (connection.ops.quote_name(field.column))
-        for field in Tag._meta.fields)
-
-    # This is based on fragments/areas sharing their works tags
     return Tag.objects.raw('''
     return Tag.objects.raw('''
-        SELECT T.*, COUNT(T.id) count
-        FROM (
-            SELECT T.*
-
-            FROM catalogue_tag_relation R1
-
-            -- Tag data for output.
-            JOIN catalogue_tag T
-                ON T.id=R1.tag_id
-
-            -- We want to exclude from output all the relations
-            -- between a book and a tag, if there's a relation between
-            -- the the book's ancestor and the tag in the result.
-            LEFT JOIN catalogue_book_ancestor A
-                ON A.from_book_id=R1.object_id
-            LEFT JOIN catalogue_tag_relation R3
-                ON R3.tag_id = R1.tag_id
-                    AND R3.content_type_id = R1.content_type_id
-                    AND R3.object_id = A.to_book_id
-
-            WHERE
-                R1.content_type_id = %s
-                -- Special case for books: exclude descendants.
-                AND R3.object_id IS NULL
-                AND T.category IN %s
-
-            -- TODO:
-            -- Shouldn't it just be 'distinct'?
-            -- Maybe it's faster this way.
-            GROUP BY T.id, R1.object_id, R1.content_type_id
-
-        ) T
-        -- Now group by tag and count occurencies.
-        GROUP BY ''' + tag_fields + '''
-        ORDER BY T.sort_key
-        ''', params=(
-            ContentType.objects.get_for_model(Book).pk,
-            tuple(categories),
-        ))
-
-
-def tags_usage_for_works(categories):
-    tag_fields = ', '.join(
-            'T.%s' % (connection.ops.quote_name(field.column))
-        for field in Tag._meta.fields)
-
-    return Tag.objects.raw('''
-        SELECT T.*, COUNT(T.id) count
-        FROM (
-
-            SELECT T.*
-
-            FROM catalogue_tag_relation R1
-
-            -- Tag data for output.
-            JOIN catalogue_tag T
-                ON T.id = R1.tag_id
-
-            -- Special case for books:
-            -- We want to exclude from output all the relations
-            -- between a book and a tag, if there's a relation between
-            -- the the book's ancestor and the tag in the result.
-            LEFT JOIN catalogue_book_ancestor A
-                ON A.from_book_id = R1.object_id
-                    AND R1.content_type_id = %s
-            LEFT JOIN catalogue_tag_relation R3
-                ON R3.tag_id = R1.tag_id
-                    AND R3.content_type_id = R1.content_type_id
-                    AND R3.object_id = A.to_book_id
-
-            WHERE
-                R1.content_type_id IN %s
-                -- Special case for books: exclude descendants.
-                AND R3.object_id IS NULL
-                AND T.category IN %s
-
-            -- TODO:
-            -- Shouldn't it just be 'distinct'?
-            -- Maybe it's faster this way.
-            GROUP BY T.id, R1.object_id, R1.content_type_id
-
-        ) T
-        -- Now group by tag and count occurencies.
-        GROUP BY ''' + tag_fields + '''
-        ORDER BY T.sort_key
-       
-        ''', params=(
-            ContentType.objects.get_for_model(Book).pk,
-            tuple(ContentType.objects.get_for_model(model).pk for model in (Book, Picture)),
-            categories,
-        ))
-
-
-def tags_usage_for_fragments(categories):
-    return Tag.objects.raw('''
-        SELECT t.*, count(t.id)
-        from catalogue_tag_relation r
-        join catalogue_tag t
-            on t.id = r.tag_id
-        where t.category IN %s
-        group by t.id
-        order by t.sort_key
-        ''', params=(
-            categories,
-        ))
+        WITH AllTagged AS (''' + rel_sql + ''')
+        SELECT catalogue_tag.*, COUNT(catalogue_tag.id) AS count
+        FROM catalogue_tag, AllTagged
+        WHERE catalogue_tag.id=AllTagged.tag_id
+            AND catalogue_tag.category IN %s
+            AND NOT EXISTS (
+                SELECT AncestorTagged.id
+                FROM catalogue_book_ancestor Ancestor,
+                    AllTagged AncestorTagged
+                WHERE Ancestor.from_book_id=AllTagged.object_id
+                    AND AncestorTagged.content_type_id=%s
+                    AND AncestorTagged.object_id=Ancestor.to_book_id
+                    AND AncestorTagged.tag_id=AllTagged.tag_id
+            )
+        GROUP BY catalogue_tag.id
+        ORDER BY sort_key''', rel_params + (categories, bct.pk))
index 51fcd94..6ae2b9a 100644 (file)
@@ -55,7 +55,7 @@ class Command(BaseCommand):
                         print "Is:       ", ", ".join(ancestors)
                         print "Should be:", ", ".join(parents)
                     if not options['dry_run']:
                         print "Is:       ", ", ".join(ancestors)
                         print "Should be:", ", ".join(parents)
                     if not options['dry_run']:
-                        book.fix_tree_tags()
+                        book.repopulate_ancestors()
                         if options['verbose']:
                             print "Fixed."
                     if options['verbose']:
                         if options['verbose']:
                             print "Fixed."
                     if options['verbose']:
index 4aa5828..9f304c3 100644 (file)
@@ -4,44 +4,6 @@ from __future__ import unicode_literals
 from django.db import models, migrations
 
 
 from django.db import models, migrations
 
 
-def fix_tree_tags(apps, schema_editor):
-    """Fixes the ancestry cache."""
-    # TODO: table names
-    from django.db import connection, transaction
-    if connection.vendor == 'postgres':
-        cursor = connection.cursor()
-        cursor.execute("""
-            WITH RECURSIVE ancestry AS (
-                SELECT book.id, book.parent_id
-                FROM catalogue_book AS book
-                WHERE book.parent_id IS NOT NULL
-                UNION
-                SELECT ancestor.id, book.parent_id
-                FROM ancestry AS ancestor, catalogue_book AS book
-                WHERE ancestor.parent_id = book.id
-                    AND book.parent_id IS NOT NULL
-                )
-            INSERT INTO catalogue_book_ancestor
-                (from_book_id, to_book_id)
-                SELECT id, parent_id
-                FROM ancestry
-                ORDER BY id;
-            """)
-    else:
-        Book = apps.get_model("catalogue", "Book")
-        for b in Book.objects.exclude(parent=None):
-            parent = b.parent
-            while parent is not None:
-                b.ancestor.add(parent)
-                parent = parent.parent
-
-
-def remove_book_tags(apps, schema_editor):
-    Tag = apps.get_model("catalogue", "Tag")
-    Book = apps.get_model("catalogue", "Book")
-    Tag.objects.filter(category='book').delete()
-
-
 class Migration(migrations.Migration):
 
     dependencies = [
 class Migration(migrations.Migration):
 
     dependencies = [
@@ -55,26 +17,4 @@ class Migration(migrations.Migration):
             field=models.ManyToManyField(related_name=b'descendant', null=True, editable=False, to='catalogue.Book', blank=True),
             preserve_default=True,
         ),
             field=models.ManyToManyField(related_name=b'descendant', null=True, editable=False, to='catalogue.Book', blank=True),
             preserve_default=True,
         ),
-
-        migrations.RunPython(fix_tree_tags),
-        migrations.RunPython(remove_book_tags),
-
-        migrations.AlterField(
-            model_name='tag',
-            name='category',
-            field=models.CharField(db_index=True, max_length=50, verbose_name='Category', choices=[(b'author', 'author'), (b'epoch', 'period'), (b'kind', 'form'), (b'genre', 'genre'), (b'theme', 'motif'), (b'set', 'set'), (b'thing', 'thing')]),
-        ),
-
-        migrations.RemoveField(
-            model_name='tag',
-            name='book_count',
-        ),
-        migrations.RemoveField(
-            model_name='tag',
-            name='picture_count',
-        ),
-        migrations.RemoveField(
-            model_name='book',
-            name='_related_info',
-        ),
     ]
     ]
diff --git a/apps/catalogue/migrations/0003_populate_ancestors.py b/apps/catalogue/migrations/0003_populate_ancestors.py
new file mode 100644 (file)
index 0000000..b611757
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+def populate_ancestors(apps, schema_editor):
+    """Fixes the ancestry cache."""
+    # TODO: table names
+    from django.db import connection, transaction
+    if connection.vendor == 'postgres':
+        cursor = connection.cursor()
+        cursor.execute("""
+            WITH RECURSIVE ancestry AS (
+                SELECT book.id, book.parent_id
+                FROM catalogue_book AS book
+                WHERE book.parent_id IS NOT NULL
+                UNION
+                SELECT ancestor.id, book.parent_id
+                FROM ancestry AS ancestor, catalogue_book AS book
+                WHERE ancestor.parent_id = book.id
+                    AND book.parent_id IS NOT NULL
+                )
+            INSERT INTO catalogue_book_ancestor
+                (from_book_id, to_book_id)
+                SELECT id, parent_id
+                FROM ancestry
+                ORDER BY id;
+            """)
+    else:
+        Book = apps.get_model("catalogue", "Book")
+        for book in Book.objects.exclude(parent=None):
+            parent = book.parent
+            while parent is not None:
+                book.ancestor.add(parent)
+                parent = parent.parent
+
+
+def remove_book_tags(apps, schema_editor):
+    Tag = apps.get_model("catalogue", "Tag")
+    Book = apps.get_model("catalogue", "Book")
+    Tag.objects.filter(category='book').delete()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('catalogue', '0002_book_ancestor'),
+    ]
+
+    operations = [
+        migrations.RunPython(populate_ancestors),
+        migrations.RunPython(remove_book_tags),
+    ]
diff --git a/apps/catalogue/migrations/0004_remove_booktags_count_related_info.py b/apps/catalogue/migrations/0004_remove_booktags_count_related_info.py
new file mode 100644 (file)
index 0000000..916224b
--- /dev/null
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('catalogue', '0003_populate_ancestors'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='tag',
+            name='category',
+            field=models.CharField(db_index=True, max_length=50, verbose_name='Category', choices=[(b'author', 'author'), (b'epoch', 'period'), (b'kind', 'form'), (b'genre', 'genre'), (b'theme', 'motif'), (b'set', 'set'), (b'thing', 'thing')]),
+        ),
+
+        migrations.RemoveField(
+            model_name='tag',
+            name='book_count',
+        ),
+        migrations.RemoveField(
+            model_name='tag',
+            name='picture_count',
+        ),
+        migrations.RemoveField(
+            model_name='book',
+            name='_related_info',
+        ),
+    ]
index 7651a9f..73b5109 100644 (file)
@@ -8,4 +8,3 @@ from catalogue.models.fragment import Fragment
 from catalogue.models.book import Book
 from catalogue.models.collection import Collection
 from catalogue.models.source import Source
 from catalogue.models.book import Book
 from catalogue.models.collection import Collection
 from catalogue.models.source import Source
-from catalogue.models.listeners import *
index e499afc..3c32481 100644 (file)
@@ -6,7 +6,6 @@ from collections import OrderedDict
 from random import randint
 import re
 from django.conf import settings
 from random import randint
 import re
 from django.conf import settings
-from django.core.cache import caches
 from django.db import connection, models, transaction
 from django.db.models import permalink
 import django.dispatch
 from django.db import connection, models, transaction
 from django.db.models import permalink
 import django.dispatch
@@ -15,18 +14,17 @@ from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext_lazy as _
 import jsonfield
 from fnpdjango.storage import BofhFileSystemStorage
 from django.utils.translation import ugettext_lazy as _
 import jsonfield
 from fnpdjango.storage import BofhFileSystemStorage
+from ssify import flush_ssi_includes
+from newtagging import managers
 from catalogue import constants
 from catalogue.fields import EbookField
 from catalogue.models import Tag, Fragment, BookMedia
 from catalogue.utils import create_zip
 from catalogue import app_settings
 from catalogue import tasks
 from catalogue import constants
 from catalogue.fields import EbookField
 from catalogue.models import Tag, Fragment, BookMedia
 from catalogue.utils import create_zip
 from catalogue import app_settings
 from catalogue import tasks
-from newtagging import managers
 
 bofh_storage = BofhFileSystemStorage()
 
 
 bofh_storage = BofhFileSystemStorage()
 
-permanent_cache = caches['permanent']
-
 
 def _cover_upload_to(i, n):
     return 'book/cover/%s.jpg' % i.slug
 
 def _cover_upload_to(i, n):
     return 'book/cover/%s.jpg' % i.slug
@@ -84,6 +82,8 @@ class Book(models.Model):
     html_built = django.dispatch.Signal()
     published = django.dispatch.Signal()
 
     html_built = django.dispatch.Signal()
     published = django.dispatch.Signal()
 
+    short_html_url_name = 'catalogue_book_short'
+
     class AlreadyExists(Exception):
         pass
 
     class AlreadyExists(Exception):
         pass
 
@@ -96,16 +96,19 @@ class Book(models.Model):
     def __unicode__(self):
         return self.title
 
     def __unicode__(self):
         return self.title
 
-    def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
+    def save(self, force_insert=False, force_update=False, **kwargs):
         from sortify import sortify
 
         self.sort_key = sortify(self.title)
         self.title = unicode(self.title) # ???
 
         from sortify import sortify
 
         self.sort_key = sortify(self.title)
         self.title = unicode(self.title) # ???
 
-        ret = super(Book, self).save(force_insert, force_update, **kwargs)
+        try:
+            author = self.tags.filter(category='author')[0].sort_key
+        except IndexError:
+            author = u''
+        self.sort_key_author = author
 
 
-        if reset_short_html:
-            self.reset_short_html()
+        ret = super(Book, self).save(force_insert, force_update, **kwargs)
 
         return ret
 
 
         return ret
 
@@ -152,20 +155,6 @@ class Book(models.Model):
     def get_daisy(self):
         return self.get_media("daisy")
 
     def get_daisy(self):
         return self.get_media("daisy")
 
-    def reset_short_html(self):
-        if self.id is None:
-            return
-
-        # Fragment.short_html relies on book's tags, so reset it here too
-        for fragm in self.fragments.all().iterator():
-            fragm.reset_short_html()
-
-        try:
-            author = self.tags.filter(category='author')[0].sort_key
-        except IndexError:
-            author = u''
-        type(self).objects.filter(pk=self.pk).update(sort_key_author=author)
-
     def has_description(self):
         return len(self.description) > 0
     has_description.short_description = _('description')
     def has_description(self):
         return len(self.description) > 0
     has_description.short_description = _('description')
@@ -318,11 +307,10 @@ class Book(models.Model):
             child.parent = None
             child.parent_number = 0
             child.save()
             child.parent = None
             child.parent_number = 0
             child.save()
-            tasks.fix_tree_tags.delay(child)
             if old_cover:
                 notify_cover_changed.append(child)
 
             if old_cover:
                 notify_cover_changed.append(child)
 
-        cls.fix_tree_tags()
+        cls.repopulate_ancestors()
 
         # No saves beyond this point.
 
 
         # No saves beyond this point.
 
@@ -347,11 +335,11 @@ class Book(models.Model):
         for child in notify_cover_changed:
             child.parent_cover_changed()
 
         for child in notify_cover_changed:
             child.parent_cover_changed()
 
-        cls.published.send(sender=book)
+        cls.published.send(sender=cls, instance=book)
         return book
 
     @classmethod
         return book
 
     @classmethod
-    def fix_tree_tags(cls):
+    def repopulate_ancestors(cls):
         """Fixes the ancestry cache."""
         # TODO: table names
         with transaction.atomic():
         """Fixes the ancestry cache."""
         # TODO: table names
         with transaction.atomic():
@@ -383,6 +371,24 @@ class Book(models.Model):
                         b.ancestor.add(parent)
                         parent = parent.parent
 
                         b.ancestor.add(parent)
                         parent = parent.parent
 
+    def flush_includes(self, languages=True):
+        if not languages:
+            return
+        if languages is True:
+            languages = [lc for (lc, _ln) in settings.LANGUAGES]
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/katalog/b/%d/mini.%s.html',
+                '/katalog/b/%d/mini_nolink.%s.html',
+                '/katalog/b/%d/short.%s.html',
+                '/katalog/b/%d/wide.%s.html',
+                '/api/include/book/%d.%s.json',
+                '/api/include/book/%d.%s.xml',
+                ]
+            for lang in languages
+            ])
+
     def cover_info(self, inherit=True):
         """Returns a dictionary to serve as fallback for BookInfo.
 
     def cover_info(self, inherit=True):
         """Returns a dictionary to serve as fallback for BookInfo.
 
index acb01b6..098501e 100644 (file)
@@ -2,8 +2,10 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # 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 import settings
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
+from ssify import flush_ssi_includes
 
 
 class Collection(models.Model):
 
 
 class Collection(models.Model):
@@ -36,3 +38,12 @@ class Collection(models.Model):
         slugs = [slug.rstrip('/').rsplit('/', 1)[-1] if '/' in slug else slug
                     for slug in slugs]
         return models.Q(slug__in=slugs)
         slugs = [slug.rstrip('/').rsplit('/', 1)[-1] if '/' in slug else slug
                     for slug in slugs]
         return models.Q(slug__in=slugs)
+
+    def flush_includes(self, languages=True):
+        if not languages:
+            return
+        if languages is True:
+            languages = [lc for (lc, _ln) in settings.LANGUAGES]
+
+        flush_ssi_includes([
+            '/katalog/%s.json' % lang for lang in languages])
index 283a7d9..a3dbdea 100644 (file)
@@ -4,17 +4,12 @@
 #
 from django.conf import settings
 from django.contrib.contenttypes.fields import GenericRelation
 #
 from django.conf import settings
 from django.contrib.contenttypes.fields import GenericRelation
-from django.core.cache import caches
 from django.core.urlresolvers import reverse
 from django.db import models
 from django.core.urlresolvers import reverse
 from django.db import models
-from django.template.loader import render_to_string
-from django.utils.safestring import mark_safe
-from django.utils.translation import get_language, ugettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
 from newtagging import managers
 from catalogue.models import Tag
 from newtagging import managers
 from catalogue.models import Tag
-
-
-permanent_cache = caches['permanent']
+from ssify import flush_ssi_includes
 
 
 class Fragment(models.Model):
 
 
 class Fragment(models.Model):
@@ -29,6 +24,8 @@ class Fragment(models.Model):
     tags = managers.TagDescriptor(Tag)
     tag_relations = GenericRelation(Tag.intermediary_table_model)
 
     tags = managers.TagDescriptor(Tag)
     tag_relations = GenericRelation(Tag.intermediary_table_model)
 
+    short_html_url_name = 'catalogue_fragment_short'
+
     class Meta:
         ordering = ('book', 'anchor',)
         verbose_name = _('fragment')
     class Meta:
         ordering = ('book', 'anchor',)
         verbose_name = _('fragment')
@@ -38,30 +35,21 @@ class Fragment(models.Model):
     def get_absolute_url(self):
         return '%s#m%s' % (reverse('book_text', args=[self.book.slug]), self.anchor)
 
     def get_absolute_url(self):
         return '%s#m%s' % (reverse('book_text', args=[self.book.slug]), self.anchor)
 
-    def reset_short_html(self):
-        if self.id is None:
-            return
-
-        cache_key = "Fragment.short_html/%d/%s"
-        for lang, langname in settings.LANGUAGES:
-            permanent_cache.delete(cache_key % (self.id, lang))
-
     def get_short_text(self):
         """Returns short version of the fragment."""
         return self.short_text if self.short_text else self.text
 
     def get_short_text(self):
         """Returns short version of the fragment."""
         return self.short_text if self.short_text else self.text
 
-    def short_html(self):
-        if self.id:
-            cache_key = "Fragment.short_html/%d/%s" % (self.id, get_language())
-            short_html = permanent_cache.get(cache_key)
-        else:
-            short_html = None
-
-        if short_html is not None:
-            return mark_safe(short_html)
-        else:
-            short_html = unicode(render_to_string('catalogue/fragment_short.html',
-                {'fragment': self}))
-            if self.id:
-                permanent_cache.set(cache_key, short_html)
-            return mark_safe(short_html)
+    def flush_includes(self, languages=True):
+        if not languages:
+            return
+        if languages is True:
+            languages = [lc for (lc, _ln) in settings.LANGUAGES]
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/katalog/f/%d/short.%s.html',
+                '/api/include/fragment/%d.%s.json',
+                '/api/include/fragment/%d.%s.xml',
+                ]
+            for lang in languages
+            ])
diff --git a/apps/catalogue/models/listeners.py b/apps/catalogue/models/listeners.py
deleted file mode 100644 (file)
index d414eb2..0000000
+++ /dev/null
@@ -1,56 +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 import settings
-from django.core.cache import caches
-from django.db.models.signals import post_save, pre_delete, post_delete
-import django.dispatch
-from catalogue.models import BookMedia, Book, Collection
-from catalogue.utils import delete_from_cache_by_language
-
-
-permanent_cache = caches['permanent']
-
-
-def _pre_delete_handler(sender, instance, **kwargs):
-    """ refresh Book on BookMedia delete """
-    if sender == BookMedia:
-        instance.book.save()
-pre_delete.connect(_pre_delete_handler)
-
-
-def _post_delete_handler(sender, instance, **kwargs):
-    """ refresh Book on BookMedia delete """
-    if sender == Collection:
-        delete_from_cache_by_language(permanent_cache, 'catalogue.collection:%s/%%s' % instance.slug)
-        delete_from_cache_by_language(permanent_cache, 'catalogue.catalogue/%s')
-post_delete.connect(_post_delete_handler)
-
-
-def _post_save_handler(sender, instance, **kwargs):
-    """ refresh all the short_html stuff on BookMedia update """
-    if sender == BookMedia:
-        instance.book.save()
-        delete_from_cache_by_language(permanent_cache, 'catalogue.audiobook_list/%s')
-        delete_from_cache_by_language(permanent_cache, 'catalogue.daisy_list/%s')
-    elif sender == Collection:
-        delete_from_cache_by_language(permanent_cache, 'catalogue.collection:%s/%%s' % instance.slug)
-        delete_from_cache_by_language(permanent_cache, 'catalogue.catalogue/%s')
-post_save.connect(_post_save_handler)
-
-
-def post_publish(sender, **kwargs):
-    delete_from_cache_by_language(permanent_cache, 'catalogue.book_list/%s')
-    delete_from_cache_by_language(permanent_cache, 'catalogue.catalogue/%s')
-Book.published.connect(post_publish)
-
-
-if not settings.NO_SEARCH_INDEX:
-    @django.dispatch.receiver(post_delete, sender=Book)
-    def _remove_book_from_index_handler(sender, instance, **kwargs):
-        """ remove the book from search index, when it is deleted."""
-        from search.index import Index
-        idx = Index()
-        idx.remove_book(instance)
-        idx.index_tags()
index d131613..9aff4ef 100644 (file)
@@ -19,3 +19,27 @@ class Source(models.Model):
 
     def __unicode__(self):
         return self.netloc
 
     def __unicode__(self):
         return self.netloc
+
+    def save(self, *args, **kwargs):
+        from catalogue.models import Book
+        try:
+            str(self.pk)
+            old_self = type(self).objects.get(pk=self)
+        except type(self).DoesNotExist:
+            old_name = u''
+            old_netloc = self.netloc
+        else:
+            old_name = old_self.name
+            old_netloc = old_self.netloc
+
+        ret = super(Source, self).save(*args, **kwargs)
+
+        # If something really changed here, find relevant books
+        # and invalidate their cached includes.
+        if old_name != self.name or old_netloc != self.netloc:
+            for book in Book.objects.all():
+                source = book.extra_info.get('source_url', '')
+                if self.netloc in source or (old_netloc != self.netloc
+                        and old_netloc in source):
+                    book.flush_includes()
+        return ret
index fb25118..0e9442f 100644 (file)
@@ -3,11 +3,14 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.conf import settings
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.conf import settings
+from django.core.cache import caches
 from django.contrib.auth.models import User
 from django.db import models
 from django.db.models import permalink
 from django.contrib.auth.models import User
 from django.db import models
 from django.db.models import permalink
+from django.dispatch import Signal
 from django.utils.translation import ugettext_lazy as _
 from newtagging.models import TagBase
 from django.utils.translation import ugettext_lazy as _
 from newtagging.models import TagBase
+from ssify import flush_ssi_includes
 
 
 # Those are hard-coded here so that makemessages sees them.
 
 
 # Those are hard-coded here so that makemessages sees them.
@@ -42,6 +45,8 @@ class Tag(TagBase):
     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)
 
     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)
 
+    after_change = Signal(providing_args=['instance', 'languages'])
+
     class UrlDeprecationWarning(DeprecationWarning):
         pass
 
     class UrlDeprecationWarning(DeprecationWarning):
         pass
 
@@ -63,6 +68,60 @@ class Tag(TagBase):
         unique_together = (("slug", "category"),)
         app_label = 'catalogue'
 
         unique_together = (("slug", "category"),)
         app_label = 'catalogue'
 
+    def save(self, *args, **kwargs):
+        flush_cache = flush_all_includes = False
+        if self.pk and self.category != 'set':
+            # Flush the whole views cache.
+            # Seem a little harsh, but changed tag names, descriptions
+            # and links come up at any number of places.
+            flush_cache = True
+
+            # Find in which languages we need to flush related includes.
+            old_self = type(self).objects.get(pk=self.pk)
+            # Category shouldn't normally be changed, but just in case.
+            if self.category != old_self.category:
+                flush_all_includes = True
+            languages_changed = self.languages_changed(old_self)
+
+        ret = super(Tag, self).save(*args, **kwargs)
+
+        if flush_cache:
+            caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
+            if flush_all_includes:
+                flush_ssi_includes()
+            else:
+                self.flush_includes()
+            self.after_change.send(sender=type(self), instance=self, languages=languages_changed)
+
+        return ret
+
+    def languages_changed(self, old):
+        all_langs = [lc for (lc, _ln) in settings.LANGUAGES]
+        if (old.category, old.slug) != (self.category, self.slug):
+            return all_langs
+        languages = set()
+        for lang in all_langs:
+            name_field = 'name_%s' % lang
+            if getattr(old, name_field) != getattr(self, name_field):
+                languages.add(lang)
+        return languages
+
+    def flush_includes(self, languages=True):
+        if not languages:
+            return
+        if languages is True:
+            languages = [lc for (lc, _ln) in settings.LANGUAGES]
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/api/include/tag/%d.%s.json',
+                '/api/include/tag/%d.%s.xml',
+                ]
+            for lang in languages
+            ])
+        flush_ssi_includes([
+            '/katalog/%s.json' % lang for lang in languages])
+
     def __unicode__(self):
         return self.name
 
     def __unicode__(self):
         return self.name
 
diff --git a/apps/catalogue/signals.py b/apps/catalogue/signals.py
new file mode 100644 (file)
index 0000000..7260721
--- /dev/null
@@ -0,0 +1,104 @@
+# -*- 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 import settings
+from django.core.cache import caches
+from django.db.models.signals import post_save, post_delete
+from django.dispatch import receiver
+from ssify import flush_ssi_includes
+from newtagging.models import tags_updated
+from picture.models import Picture, PictureArea
+from .models import BookMedia, Book, Collection, Fragment, Tag
+
+
+####
+# BookMedia
+####
+
+
+@receiver([post_save, post_delete], sender=BookMedia)
+def bookmedia_save(sender, instance, **kwargs):
+    instance.book.save()
+
+
+####
+# Collection
+####
+
+
+@receiver(post_save, sender=Collection)
+def collection_save(sender, instance, **kwargs):
+    caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
+    flush_ssi_includes([
+        '/katalog/%s.json' % lang
+        for lang in [lc for (lc, _ln) in settings.LANGUAGES]])
+
+
+@receiver(post_delete, sender=Collection)
+def collection_delete(sender, instance, **kwargs):
+    flush_ssi_includes([
+        '/katalog/%s.json' % lang
+        for lang in [lc for (lc, _ln) in settings.LANGUAGES]])
+
+####
+# Book
+####
+
+
+@receiver(post_save, sender=Book)
+def book_save(sender, instance, **kwargs):
+    # Books come out anywhere.
+    caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
+    instance.flush_includes()
+
+
+@receiver(post_delete, sender=Book)
+def book_delete(sender, instance, **kwargs):
+    caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
+    flush_ssi_includes([
+        '/katalog/%s.json' % lang
+        for lang in [lc for (lc, _ln) in settings.LANGUAGES]])
+
+    if not settings.NO_SEARCH_INDEX:
+        # remove the book from search index, when it is deleted.
+        from search.index import Index
+        idx = Index()
+        idx.remove_book(instance)
+        idx.index_tags()
+
+
+####
+# Tag
+####
+
+
+@receiver(Tag.after_change)
+def tag_after_change(sender, instance, languages, **kwargs):
+    caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
+    flush_ssi_includes([
+        '/katalog/%s.json' % lang
+        for lang in [lc for (lc, _ln) in settings.LANGUAGES]])
+
+    for model in Book, Picture:
+        for instance in model.tagged.with_all([instance]).only('pk'):
+            instance.flush_includes()
+
+    if instance.category == 'author':
+        for model in Fragment, PictureArea:
+            for instance in model.tagged.with_all([instance]).only('pk'):
+                instance.flush_includes()
+
+
+@receiver(tags_updated)
+def receive_tags_updated(sender, instance, affected_tags, **kwargs):
+    categories = set(tag.category for tag in affected_tags
+        if tag.category not in ('set', 'book'))
+    if not categories:
+        return
+
+    caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
+    instance.flush_includes()
+    flush_ssi_includes([
+        '/katalog/%s.json' % lang
+        for lang in [lc for (lc, _ln) in settings.LANGUAGES]])
index 7d18068..159494e 100644 (file)
@@ -18,11 +18,6 @@ def touch_tag(tag):
     type(tag).objects.filter(pk=tag.pk).update(**update_dict)
 
 
     type(tag).objects.filter(pk=tag.pk).update(**update_dict)
 
 
-@task(ignore_result=True)
-def fix_tree_tags(book):
-    book.fix_tree_tags()
-
-
 @task
 def index_book(book_id, book_info=None, **kwargs):
     from catalogue.models import Book
 @task
 def index_book(book_id, book_info=None, **kwargs):
     from catalogue.models import Book
index 1358025..75576f4 100644 (file)
@@ -1,7 +1,7 @@
 {% extends "catalogue/book_list.html" %}
 {% load i18n %}
 {% load catalogue_tags %}
 {% extends "catalogue/book_list.html" %}
 {% load i18n %}
 {% load catalogue_tags %}
-{% load chunks %}
+{% load ssi_include from ssify %}
 
 {% block bodyid %}book-a-list{% endblock %}
 
 
 {% block bodyid %}book-a-list{% endblock %}
 
@@ -17,5 +17,5 @@
 {% block book_list_header %}{% trans "Listing of all audiobooks" %}{% endblock %}
 
 {% block book_list_info %}
 {% block book_list_header %}{% trans "Listing of all audiobooks" %}{% endblock %}
 
 {% block book_list_info %}
-{% chunk 'audiobook-list' %}
+{% ssi_include 'chunk' key='audiobook-list' %}
 {% endblock %}
 {% endblock %}
index 454bce1..398a43c 100644 (file)
@@ -1,6 +1,8 @@
 {% extends "base.html" %}
 {% load i18n %}
 {% load common_tags catalogue_tags %}
 {% extends "base.html" %}
 {% load i18n %}
 {% load common_tags catalogue_tags %}
+{% load ssify %}
+{% load build_absolute_uri from fnp_common %}
 
 {% block titleextra %}{{ book.pretty_title }}{% endblock %}
 {% block ogimage %}{% if book.cover %}{{ book.cover.url|build_absolute_uri:request }}{% endif %}{% endblock %}
 
 {% block titleextra %}{{ book.pretty_title }}{% endblock %}
 {% block ogimage %}{% if book.cover %}{{ book.cover.url|build_absolute_uri:request }}{% endif %}{% endblock %}
@@ -11,7 +13,7 @@
 
 {% block body %}
 
 
 {% block body %}
 
-{% book_wide book %}
+{% ssi_include 'catalogue_book_wide' pk=book.pk %}
 
 {% work_list book_children %}
 
 
 {% work_list book_children %}
 
@@ -20,7 +22,7 @@
 <section class="see-also" style="display: inline-block;">
 <h1>{% trans "Other versions" %}:</h1>
 {% for rel in book.other_versions %}
 <section class="see-also" style="display: inline-block;">
 <h1>{% trans "Other versions" %}:</h1>
 {% for rel in book.other_versions %}
-    {% book_mini rel %}
+    {% ssi_include 'book_mini' pk=rel.pk %}
 {% endfor %}
 </section>
 {% endif %}
 {% endfor %}
 </section>
 {% endif %}
index 260c71f..e29cd50 100644 (file)
@@ -1,18 +1,20 @@
 {% extends "base.html" %}
 {% load i18n %}
 {% load catalogue_tags %}
 {% extends "base.html" %}
 {% load i18n %}
 {% load catalogue_tags %}
-{% load chunks %}
+{% load ssi_include from ssify %}
 
 {% block bodyid %}book-a-list{% endblock %}
 
 {% block titleextra %}{% trans "Listing of all works" %}{% endblock %}
 
 {% block body %}
 
 {% block bodyid %}book-a-list{% endblock %}
 
 {% block titleextra %}{% trans "Listing of all works" %}{% endblock %}
 
 {% block body %}
+{% spaceless %}
+
     <h1>{% block book_list_header %}{% trans "Listing of all works" %}{% endblock %}</h1>
 
     <div class="left-column"><div class="normal-text" style="margin-bottom: 2em">
         {% block book_list_info %}
     <h1>{% block book_list_header %}{% trans "Listing of all works" %}{% endblock %}</h1>
 
     <div class="left-column"><div class="normal-text" style="margin-bottom: 2em">
         {% block book_list_info %}
-            {% chunk 'book-list' %}
+            {% ssi_include 'chunk' key='book-list' %}
         {% endblock %}
     </div></div>
 
         {% endblock %}
     </div></div>
 
@@ -32,4 +34,6 @@
       {% endblock %}
     </div>
     <a id="book-list-up" href="#top">{% trans "↑ top ↑" %}</a>
       {% endblock %}
     </div>
     <a id="book-list-up" href="#top">{% trans "↑ top ↑" %}</a>
+
+{% endspaceless %}
 {% endblock %}
 {% endblock %}
index 49257b1..88ec16d 100755 (executable)
@@ -1,11 +1,11 @@
+{% spaceless %}
 <div class="book-mini-box">
     <div class="book-mini-box-inner">
     {% if with_link %}
     <a href="{{ book.get_absolute_url }}">
     {% endif %}
         {% if book.cover_thumb %}
 <div class="book-mini-box">
     <div class="book-mini-box-inner">
     {% if with_link %}
     <a href="{{ book.get_absolute_url }}">
     {% endif %}
         {% if book.cover_thumb %}
-            <img src="{{ book.cover_thumb.url }}"
-                alt="{{ author_str }} – {{ book.title }}" class="cover" />
+            <img src="{{ book.cover_thumb.url }}" alt="{{ author_str }} – {{ book.title }}" class="cover" />
         {% endif %}
         {% if show_lang %}
             <span class="language" title="{{ book.language_name }}">{{ book.language_code }}</span>
         {% endif %}
         {% if show_lang %}
             <span class="language" title="{{ book.language_name }}">{{ book.language_code }}</span>
@@ -19,5 +19,4 @@
     {% endif %}
     </div>
 </div>
     {% endif %}
     </div>
 </div>
-
-
+{% endspaceless %}
\ No newline at end of file
index 783b14a..357491c 100644 (file)
@@ -1,10 +1,13 @@
-{% extends "catalogue/book_short.html" %}
-{% load i18n catalogue_tags %}
+{% spaceless %}
 
 
+{% load i18n %}
+{% load inline_tag_list from catalogue_tags %}
+{% load ssi_include from ssify %}
 
 
-{% block box-class %}search-result{% endblock %}
+<div class="search-result">
+
+{% ssi_include 'catalogue_book_short' pk=book.pk %}
 
 
-{% block right-column %}
 <div class="snippets">
   {% for hit in hits %}
   {% if hit.snippet %}
 <div class="snippets">
   {% for hit in hits %}
   {% if hit.snippet %}
@@ -28,5 +31,8 @@
   {% endfor %}
 </div>
 
   {% endfor %}
 </div>
 
-{% endblock %}
+<div style="clear: right"></div>
+
+</div>
 
 
+{% endspaceless %}
\ No newline at end of file
index 58859af..b069cb5 100644 (file)
@@ -1,5 +1,7 @@
+{% spaceless %}
 {% load i18n %}
 {% load i18n %}
-{% load catalogue_tags social_tags %}
+{% load catalogue_tags ssify %}
+{% load likes_book book_shelf_tags from social_tags %}
 <div class="{% block box-class %}book-box{% endblock %}">
 <div class="book-box-inner">
 <div class="book-left-column">
 <div class="{% block box-class %}book-box{% endblock %}">
 <div class="book-box-inner">
 <div class="book-left-column">
 <div class="book-box-body">
 
 
 <div class="book-box-body">
 
 
-<div class="star {% if not request.user|likes:book %}un{% endif %}like">
+{% likes_book book.pk as likes %}
+<div class="star {{ likes.if }}{{ likes.else }}un{{ likes.endif }}like">
     <div class="if-like" >
     <div class="if-like" >
-        <a id="social-book-sets-{{ book.slug }}" data-callback='social-book-sets' class='ajaxable' href='{% url "social_book_sets" book.slug %}'>
-            ★
-        </a>
+        <a id="social-book-sets-{{ book.slug }}" data-callback='social-book-sets' class='ajaxable' href='{% url "social_book_sets" book.slug %}'>★</a>
     </div>
     <div class="if-unlike">
         <form id="social-like-book-{{ book.slug }}" data-callback='social-like-book' method='post' class='ajax-form' action='{% url "social_like_book" book.slug %}'>
     </div>
     <div class="if-unlike">
         <form id="social-like-book-{{ book.slug }}" data-callback='social-like-book' method='post' class='ajax-form' action='{% url "social_like_book" book.slug %}'>
-            {% csrf_token %}
+            {% ssi_csrf_token %}
             <button type='submit'>☆</button>
         </form>
     </div>
             <button type='submit'>☆</button>
         </form>
     </div>
                     <a href="{{ parent.get_absolute_url }}">{{ parent.title }}</a>{% endfor %}
             </div>
             <div class="title">
                     <a href="{{ parent.get_absolute_url }}">{{ parent.title }}</a>{% endfor %}
             </div>
             <div class="title">
-                {% if main_link %}<a href="{{ main_link }}">{% endif %}
-                    {{ book.title }}
-                {% if main_link %}</a>{% endif %}
+                {% if main_link %}<a href="{{ main_link }}">{% endif %}{{ book.title }}{% if main_link %}</a>{% endif %}
             </div>
         </div>
 
 <div class="cover-area">
     {% if book.cover_thumb %}
         {% if main_link %}<a href="{{ main_link }}">{% endif %}
             </div>
         </div>
 
 <div class="cover-area">
     {% if book.cover_thumb %}
         {% if main_link %}<a href="{{ main_link }}">{% endif %}
-            <img src="{{ book.cover_thumb.url }}"
-                alt="Cover" class="cover" />
+            <img src="{{ book.cover_thumb.url }}" alt="Cover" class="cover" />
         {% if main_link %}</a>{% endif %}
     {% endif %}
     {% block cover-area-extra %}{% endblock %}
         {% if main_link %}</a>{% endif %}
     {% endif %}
     {% block cover-area-extra %}{% endblock %}
@@ -91,7 +89,7 @@
            {% endspaceless %}
         </div>
     </div>
            {% endspaceless %}
         </div>
     </div>
-    {% shelf_tags book %}
+    {% book_shelf_tags book.pk %}
 
     <ul class="book-box-tools">
         <li class="book-box-read">
 
     <ul class="book-box-tools">
         <li class="book-box-read">
     <div class="clearboth"></div>
 </div>
 </div>
     <div class="clearboth"></div>
 </div>
 </div>
+{% endspaceless %}
\ No newline at end of file
index 8dd1546..339cfc7 100644 (file)
@@ -1,6 +1,6 @@
 {% extends "catalogue/viewer_base.html" %}
 {% load i18n %}
 {% extends "catalogue/viewer_base.html" %}
 {% load i18n %}
-{% load catalogue_tags %}
+{% load catalogue_tags ssify %}
 {% load thumbnail %}
 
 
 {% load thumbnail %}
 
 
@@ -49,7 +49,7 @@
 <div id="big-pane" style="">
 
 <article id="main-text">
 <div id="big-pane" style="">
 
 <article id="main-text">
-{{ book.html_file.read|safe }}
+<!--#include file='{{ book.html_file.url }}'-->
 </article>
 
 <article id="other-text">
 </article>
 
 <article id="other-text">
@@ -78,7 +78,7 @@
         <li><a class="display-other" 
             data-other="{{ other_version.html_file.url }}"
             href="{% url 'book_text' other_version.slug %}">
         <li><a class="display-other" 
             data-other="{{ other_version.html_file.url }}"
             href="{% url 'book_text' other_version.slug %}">
-                {% book_mini other_version with_link=False %}
+                {% ssi_include 'catalogue_book_mini_nolink' pk=other_version.pk %}
                 </a>
         </li>
     {% endfor %}
                 </a>
         </li>
     {% endfor %}
@@ -95,6 +95,6 @@
 </div>
 
 <div class="box" id="book-short">
 </div>
 
 <div class="box" id="book-short">
-    {% book_short book %}
+    {% ssi_include 'catalogue_book_short' pk=book.pk %}
 </div>
 {% endblock footer %}
 </div>
 {% endblock footer %}
index 5ce66a3..b84acdb 100644 (file)
@@ -1,7 +1,8 @@
 {% extends "catalogue/book_short.html" %}
 {% load i18n %}
 {% extends "catalogue/book_short.html" %}
 {% load i18n %}
-{% load download_audio tag_list custom_pdf_link_li license_icon source_name from catalogue_tags %}
-{% load cite_promo from social_tags %}
+{% load choose_fragment download_audio tag_list custom_pdf_link_li license_icon source_name from catalogue_tags %}
+{% load choose_cite from social_tags %}
+{% load ssi_include from ssify %}
 
 
 {% block box-class %}book-wide-box{% endblock %}
 
 
 {% block box-class %}book-wide-box{% endblock %}
 
 {% block right-column %}
 <div class="right-column">
 
 {% block right-column %}
 <div class="right-column">
-    <div class="quote">
-  {% cite_promo book 1 %}
+  <div class="quote">
+    {% choose_cite book.pk as cite_promo %}
+    {% choose_fragment book.pk unless=cite_promo as fragment_promo %}
+    {{ cite_promo.if }}
+        {% ssi_include 'social_cite' pk=cite_promo %}
+    {{ cite_promo.endif }}
+    {{ fragment_promo.if }}
+        {% ssi_include 'catalogue_fragment_promo' pk=fragment_promo %}
+    {{ fragment_promo.endif }}
   </div>
 
   <div class="other-tools">
   </div>
 
   <div class="other-tools">
index 4f570b2..65d9d6b 100644 (file)
@@ -1,6 +1,6 @@
 {% extends "catalogue/book_list.html" %}
 {% load i18n %}
 {% extends "catalogue/book_list.html" %}
 {% load i18n %}
-{% load chunks %}
+{% load ssi_include from ssify %}
 
 {% block bodyid %}book-a-list{% endblock %}
 
 
 {% block bodyid %}book-a-list{% endblock %}
 
@@ -13,5 +13,5 @@
 {% block book_list_header %}{% trans "Listing of all DAISY files" %}{% endblock %}
 
 {% block book_list_info %}
 {% block book_list_header %}{% trans "Listing of all DAISY files" %}{% endblock %}
 
 {% block book_list_info %}
-{% chunk 'daisy-list' %}
+{% ssi_include 'chunk' key='daisy-list' %}
 {% endblock %}
 {% endblock %}
diff --git a/apps/catalogue/templates/catalogue/latest_blog_posts.html b/apps/catalogue/templates/catalogue/latest_blog_posts.html
deleted file mode 100644 (file)
index d2c90e1..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<ol>
-{% for post in posts %}
-    <li><a href="{{ post.link }}">{{ post.title }}</a></li>
-{% endfor %}
-</ol>
\ No newline at end of file
index b6bc172..5ddbd3b 100644 (file)
@@ -1,3 +1,4 @@
+{% spaceless %}
 {% load i18n static %}
 
 <a id="show-menu" href="{% url 'catalogue' %}">
 {% load i18n static %}
 
 <a id="show-menu" href="{% url 'catalogue' %}">
@@ -7,8 +8,7 @@
 <ul id="menu">
     {% for category, name, hash in categories %}
        <li class="hidden-box-wrapper menu">
 <ul id="menu">
     {% for category, name, hash in categories %}
        <li class="hidden-box-wrapper menu">
-               <a href="{% url 'catalogue' %}#{{ hash }}" class="hidden-box-trigger menu load-menu">
-                       {{ name }}</a>
+               <a href="{% url 'catalogue' %}#{{ hash }}" class="hidden-box-trigger menu load-menu">{{ name }}</a>
                <div class="hidden-box" id="menu-{{ category }}">
             <img src="{% static "img/indicator.gif" %}" alt="{% trans "Please wait…" %}" />
         </div>
                <div class="hidden-box" id="menu-{{ category }}">
             <img src="{% static "img/indicator.gif" %}" alt="{% trans "Please wait…" %}" />
         </div>
     {% endfor %}
 
        <li class="hidden-box-wrapper menu">
     {% endfor %}
 
        <li class="hidden-box-wrapper menu">
-               <a href="{% url 'catalogue' %}#kolekcje" class="hidden-box-trigger menu load-menu">
-                       {% trans "Collections" %}</a>
+               <a href="{% url 'catalogue' %}#kolekcje" class="hidden-box-trigger menu load-menu">{% trans "Collections" %}</a>
                <div class="hidden-box" id="menu-collections">
             <img src="{% static "img/indicator.gif" %}" alt="{% trans "Please wait…" %}" />
         </div>
        </li>
 
        <li class="menu">
                <div class="hidden-box" id="menu-collections">
             <img src="{% static "img/indicator.gif" %}" alt="{% trans "Please wait…" %}" />
         </div>
        </li>
 
        <li class="menu">
-               <a href="{% url 'book_list' %}" class="menu">
-                       {% trans "All books" %}</a>
+               <a href="{% url 'book_list' %}" class="menu">{% trans "All books" %}</a>
        </li>
        <li class="menu">
        </li>
        <li class="menu">
-               <a href="{% url 'audiobook_list' %}" class="menu">
-                       {% trans "Audiobooks" %}</a>
+               <a href="{% url 'audiobook_list' %}" class="menu">{% trans "Audiobooks" %}</a>
        </li>
        <li class="menu">
        </li>
        <li class="menu">
-               <a href="{% url 'daisy_list' %}" class="menu">
-                       {% trans "DAISY" %}</a>
+               <a href="{% url 'daisy_list' %}" class="menu">{% trans "DAISY" %}</a>
        </li>
        <li class="menu">
                <a href="{% url 'picture_list_thumb' %}" class="menu">
        </li>
        <li class="menu">
                <a href="{% url 'picture_list_thumb' %}" class="menu">
@@ -41,3 +37,5 @@
        </li>
 
 </ul>
        </li>
 
 </ul>
+
+{% endspaceless %}
\ No newline at end of file
index 219876e..48fb2ee 100755 (executable)
@@ -1,10 +1,16 @@
-{% load book_mini from catalogue_tags %}
-
 {% spaceless %}
 {% spaceless %}
+{% load catalogue_random_book from catalogue_tags %}
+{% load ssi_include from ssify %}
+
 {% for book in books %}
 {% for book in books %}
-    {% book_mini book %}
-{% endfor %}
-{% for book in random_related %}
-    {% book_mini book %}
+    {% ssi_include 'catalogue_book_mini' pk=book.pk %}
 {% endfor %}
 {% endfor %}
+
+{% if random %}
+    {% catalogue_random_book random_excluded as random_pk %}
+    {{ random_pk.if }}
+        {% ssi_include 'catalogue_book_mini' pk=random_pk %}
+    {{ random_pk.endif }}
+{% endif %}
+
 {% endspaceless %}
\ No newline at end of file
 {% endspaceless %}
\ No newline at end of file
index 667de9c..d1e1ded 100644 (file)
@@ -1,6 +1,9 @@
 {% extends "base.html" %}
 {% load i18n %}
 {% extends "base.html" %}
 {% load i18n %}
-{% load catalogue_tags search_tags pagination_tags %}
+{% load pagination_tags %}
+{% load inline_tag_list from catalogue_tags %}
+{% load book_searched from search_tags %}
+{% load ssi_include from ssify %}
 
 {% block titleextra %}{% trans "Search" %}{% endblock %}
 
 
 {% block titleextra %}{% trans "Search" %}{% endblock %}
 
@@ -55,7 +58,7 @@
     <div>
       <ol class="work-list">
        {% for result in results.title %}<li class="Book-item">
     <div>
       <ol class="work-list">
        {% for result in results.title %}<li class="Book-item">
-         {% book_short result.book %}
+         {% ssi_include 'catalogue_book_short' pk=result.book.pk %}
        </li>{% endfor %}
       </ol>
     </div>
        </li>{% endfor %}
       </ol>
     </div>
@@ -69,7 +72,7 @@
     </div>
     <div>
       <ol class="work-list">
     </div>
     <div>
       <ol class="work-list">
-       {% for author in results.author %}<li class="Book-item">{% book_short author.book %}</li>{% endfor %}
+       {% for author in results.author %}<li class="Book-item">{% ssi_include 'catalogue_book_short' pk=author.book.pk %}</li>{% endfor %}
       </ol>
     </div>
     {% endif %}
       </ol>
     </div>
     {% endif %}
@@ -82,7 +85,7 @@
     </div>
     <div>
       <ol class="work-list">
     </div>
     <div>
       <ol class="work-list">
-       {% for translator in results.translator %}<li class="Book-item">{% book_short translator.book %}</li>{% endfor %}
+       {% for translator in results.translator %}<li class="Book-item">{% ssi_include 'catalogue_book_short' pk=translator.book.pk %}</li>{% endfor %}
       </ol>
     </div>
     {% endif %}
       </ol>
     </div>
     {% endif %}
index 2143048..e0fecc0 100644 (file)
@@ -1,10 +1,12 @@
+{% spaceless %}
+
 {% load i18n %}
 {% load catalogue_tags %}
 {% if one_tag %}
     <p>{% trans "See full category" %} <a href="{% catalogue_url one_tag %}">{{ one_tag }}</a></p>
 {% else %}
     <ul>
 {% load i18n %}
 {% load catalogue_tags %}
 {% if one_tag %}
     <p>{% trans "See full category" %} <a href="{% catalogue_url one_tag %}">{{ one_tag }}</a></p>
 {% else %}
     <ul>
-       {% if choices %}
+        {% if choices %}
         {% for tag in tags %}
             <li><a href="{% catalogue_url choices tag %}">{{ tag }}{% if tag.count %}&nbsp;({{ tag.count }}){% endif %}</a></li>
         {% endfor %}
         {% for tag in tags %}
             <li><a href="{% catalogue_url choices tag %}">{{ tag }}{% if tag.count %}&nbsp;({{ tag.count }}){% endif %}</a></li>
         {% endfor %}
@@ -15,3 +17,5 @@
         {% endif %}
     </ul>
 {% endif %}
         {% endif %}
     </ul>
 {% endif %}
+
+{% endspaceless %}
\ No newline at end of file
index f2c4308..9027027 100644 (file)
@@ -1,6 +1,7 @@
 {% extends "base.html" %}
 {% load i18n %}
 {% load catalogue_tags switch_tag social_tags %}
 {% extends "base.html" %}
 {% load i18n %}
 {% load catalogue_tags switch_tag social_tags %}
+{% load ssi_include from ssify %}
 
 {% block titleextra %}{% title_from_tags tags %}{% endblock %}
 
 
 {% block titleextra %}{% title_from_tags tags %}{% endblock %}
 
         {% if theme_is_set %}
             {% work_list object_list %}
         {% else %}
         {% if theme_is_set %}
             {% work_list object_list %}
         {% else %}
-        {% cite_promo tags 1 %}
+
+        {% choose_cite tag_ids=tag_ids as cite_promo_pk %}
+        {% choose_fragment tag_ids=tag_ids unless=cite_promo as fragment_promo_pk %}
+        {{ cite_promo_pk.if }}
+            {% ssi_include 'social_cite' pk=cite_promo_pk %}
+        {{ cite_promo_pk.endif }}
+        {{ fragment_promo_pk.if }}
+            {% ssi_include 'catalogue_fragment_promo' pk=fragment_promo_pk %}
+        {{ fragment_promo_pk.endif }}
 
         <div class="see-also">
             {% if last_tag.gazeta_link or last_tag.wiki_link %}
 
         <div class="see-also">
             {% if last_tag.gazeta_link or last_tag.wiki_link %}
index fa33557..3026525 100755 (executable)
@@ -1,22 +1,19 @@
+{% spaceless %}
+
 {% load pagination_tags %}
 {% load pagination_tags %}
-{% load book_short class_name book_short from catalogue_tags %}
-{% load picture_short from picture_tags %}
+{% load class_name from catalogue_tags %}
+{% load ssi_include from ssify %}
 
 {% autopaginate object_list 10 %}
 
 {% autopaginate object_list 10 %}
-{% spaceless %}
+
 <ol class='work-list'>
 {% for item in object_list %}
     <li class='{{ item|class_name }}-item'>
 <ol class='work-list'>
 {% for item in object_list %}
     <li class='{{ item|class_name }}-item'>
-        {% if item.short_html %}
-            {{ item.short_html }}
-{# since we are using shor_html eerywhere, is it needed anymore? #}
-        {% elif item|class_name == "Picture"  %}
-            {% picture_short item %}
-        {% else %}
-            {% book_short item %}
-        {% endif %}
+        {% ssi_include item.short_html_url_name pk=item.pk %}
     </li>
 {% endfor %}
 </ol>
     </li>
 {% endfor %}
 </ol>
-{% endspaceless %}
+
 {% paginate %}
 {% paginate %}
+
+{% endspaceless %}
index 2edae9b..54fcf5a 100644 (file)
@@ -2,20 +2,18 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-import datetime
-import feedparser
 from random import randint
 from urlparse import urlparse
 
 from django.conf import settings
 from django import template
 from django.template import Node, Variable, Template, Context
 from random import randint
 from urlparse import urlparse
 
 from django.conf import settings
 from django import template
 from django.template import Node, Variable, Template, Context
-from django.core.cache import cache
 from django.core.urlresolvers import reverse
 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
 from django.core.urlresolvers import reverse
 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
+from django.utils.cache import add_never_cache_headers
 from django.utils.translation import ugettext as _
 
 from django.utils.translation import ugettext as _
 
-from catalogue.utils import split_tags
+from ssify import ssi_variable
 from catalogue.models import Book, BookMedia, Fragment, Tag, Source
 from catalogue.constants import LICENSES
 
 from catalogue.models import Book, BookMedia, Fragment, Tag, Source
 from catalogue.constants import LICENSES
 
@@ -279,25 +277,6 @@ class CatalogueURLNode(Node):
             return reverse('main_page')
 
 
             return reverse('main_page')
 
 
-@register.inclusion_tag('catalogue/latest_blog_posts.html')
-def latest_blog_posts(feed_url, posts_to_show=5):
-    try:
-        feed = feedparser.parse(str(feed_url))
-        posts = []
-        for i in range(posts_to_show):
-            pub_date = feed['entries'][i].published_parsed
-            published = datetime.date(pub_date[0], pub_date[1], pub_date[2])
-            posts.append({
-                'title': feed['entries'][i].title,
-                'summary': feed['entries'][i].summary,
-                'link': feed['entries'][i].link,
-                'date': published,
-                })
-        return {'posts': posts}
-    except:
-        return {'posts': []}
-
-
 @register.inclusion_tag('catalogue/tag_list.html')
 def tag_list(tags, choices=None):
     if choices is None:
 @register.inclusion_tag('catalogue/tag_list.html')
 def tag_list(tags, choices=None):
     if choices is None:
@@ -322,96 +301,24 @@ def book_info(book):
     return locals()
 
 
     return locals()
 
 
-@register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
-def book_wide(context, book):
-    ctx = book_short(context, book)
-    ctx['extra_info'] = book.extra_info
-    ctx['hide_about'] = ctx['extra_info'].get('about', '').startswith('http://wiki.wolnepodreczniki.pl')
-    ctx['themes'] = book.related_themes()
-    ctx['main_link'] = reverse('book_text', args=[book.slug]) if book.html_file else None
-    return ctx
-
-
-@register.inclusion_tag('catalogue/book_short.html', takes_context=True)
-def book_short(context, book):
-    stage_note, stage_note_url = book.stage_note()
-
-    return {
-        'book': book,
-        'has_audio': book.has_media('mp3'),
-        'main_link': book.get_absolute_url(),
-        'parents': book.parents(),
-        'tags': split_tags(book.tags.exclude(category__in=('set', 'theme'))),
-        'request': context.get('request'),
-        'show_lang': book.language_code() != settings.LANGUAGE_CODE,
-        'stage_note': stage_note,
-        'stage_note_url': stage_note_url,
-    }
-
-
-@register.inclusion_tag('catalogue/book_mini_box.html')
-def book_mini(book, with_link=True):
-    author_str = ", ".join(tag.name
-        for tag in book.tags.filter(category='author'))
-    return {
-        'book': book,
-        'author_str': author_str,
-        'with_link': with_link,
-        'show_lang': book.language_code() != settings.LANGUAGE_CODE,
-    }
-
-
 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
 def work_list(context, object_list):
     request = context.get('request')
     return locals()
 
 
 @register.inclusion_tag('catalogue/work-list.html', takes_context=True)
 def work_list(context, object_list):
     request = context.get('request')
     return locals()
 
 
-@register.inclusion_tag('catalogue/fragment_promo.html')
-def fragment_promo(arg=None):
-    if isinstance(arg, Book):
-        fragment = arg.choose_fragment()
-    else:
-        if arg is None:
-            fragments = Fragment.objects.all()
-        else:
-            fragments = Fragment.tagged.with_all(arg)
-        fragments = fragments.order_by().only('id')
-        fragments_count = fragments.count()
-        if fragments_count:
-            fragment = fragments.order_by()[randint(0, fragments_count - 1)]
-        else:
-            fragment = None
-
-    return {
-        'fragment': fragment,
-    }
-
-
-@register.inclusion_tag('catalogue/related_books.html')
-def related_books(book, limit=6, random=1, taken=0):
+@register.inclusion_tag('catalogue/related_books.html', takes_context=True)
+def related_books(context, book, limit=6, random=1, taken=0):
     limit = limit - taken
     limit = limit - taken
-    cache_key = "catalogue.related_books.%d.%d" % (book.id, limit - random)
-    related = cache.get(cache_key)
-    if related is None:
-        related = Book.tagged.related_to(book,
-                Book.objects.exclude(common_slug=book.common_slug)
-                ).exclude(ancestor=book)[:limit-random]
-        cache.set(cache_key, related, 1800)
-    if random:
-        random_books = Book.objects.exclude(
-                        pk__in=[b.pk for b in related] + [book.pk])
-        if random == 1:
-            count = random_books.count()
-            if count:
-                random_related = [random_books[randint(0, count - 1)]]
-        else:
-            random_related = list(random_books.order_by('?')[:random])
-    else:
-        random_related = []
+    related = Book.tagged.related_to(book,
+            Book.objects.exclude(common_slug=book.common_slug)
+            ).exclude(ancestor=book)[:limit-random]
+    random_excluded = [b.pk for b in related] + [book.pk]
     return {
     return {
+        'request': context['request'],
         'books': related,
         'books': related,
-        'random_related': random_related,
+        'random': random,
+        'random_excluded': random_excluded,
     }
 
 
     }
 
 
@@ -426,11 +333,6 @@ def catalogue_menu():
         ]}
 
 
         ]}
 
 
-@register.simple_tag
-def tag_url(category, slug):
-    return Tag.create_url(category, slug)
-
-
 @register.simple_tag
 def download_audio(book, daisy=True):
     links = []
 @register.simple_tag
 def download_audio(book, daisy=True):
     links = []
@@ -485,3 +387,31 @@ def source_name(url):
         return ''
     source, created = Source.objects.get_or_create(netloc=netloc)
     return source.name or netloc
         return ''
     source, created = Source.objects.get_or_create(netloc=netloc)
     return source.name or netloc
+
+
+@ssi_variable(register, patch_response=[add_never_cache_headers])
+def catalogue_random_book(request, exclude_ids):
+    queryset = Book.objects.exclude(pk__in=exclude_ids)
+    count = queryset.count()
+    if count:
+        return queryset[randint(0, count - 1)].pk
+    else:
+        return None
+
+
+@ssi_variable(register, patch_response=[add_never_cache_headers])
+def choose_fragment(request, book_id=None, tag_ids=None, unless=False):
+    if unless:
+        return None
+
+    if book_id is not None:
+        fragment = Book.objects.get(pk=book_id).choose_fragment()
+    else:
+        if tag_ids is not None:
+            tags = Tag.objects.filter(pk__in=tag_ids)
+            fragments = Fragment.tagged.with_all(tags).order_by().only('id')
+        else:
+            fragments = Fragment.objects.all().order_by().only('id')
+        fragment_count = fragments.count()
+        fragment = fragments[randint(0, fragment_count - 1)] if fragment_count else None
+    return fragment.pk if fragment is not None else None
index dd11e93..101a508 100644 (file)
@@ -15,14 +15,12 @@ from django.conf import settings
 @override_settings(
     MEDIA_ROOT=tempfile.mkdtemp(prefix='djangotest_'),
     CATALOGUE_DONT_BUILD=set(['pdf', 'mobi', 'epub', 'txt', 'fb2', 'cover']),
 @override_settings(
     MEDIA_ROOT=tempfile.mkdtemp(prefix='djangotest_'),
     CATALOGUE_DONT_BUILD=set(['pdf', 'mobi', 'epub', 'txt', 'fb2', 'cover']),
-    NO_SEARCH_INDEX = True,
-    CELERY_ALWAYS_EAGER = True,
+    NO_SEARCH_INDEX=True,
+    CELERY_ALWAYS_EAGER=True,
     CACHES={
     CACHES={
-            'api': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
             'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
             'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
-            'permanent': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
         },
         },
-    SOLR = settings.SOLR_TEST,
+    SOLR=settings.SOLR_TEST,
 )
 class WLTestCase(TestCase):
     """
 )
 class WLTestCase(TestCase):
     """
index 9c7a77c..79aed52 100644 (file)
@@ -8,3 +8,4 @@ from catalogue.tests.cover import *
 from catalogue.tests.search import *
 from catalogue.tests.tags import *
 from catalogue.tests.templatetags import *
 from catalogue.tests.search import *
 from catalogue.tests.tags import *
 from catalogue.tests.templatetags import *
+from .test_visit import *
index f10780c..42ea6e2 100644 (file)
@@ -31,8 +31,7 @@ class BooksByTagTests(WLTestCase):
 
     def test_nonexistent_tag(self):
         """ Looking for a non-existent tag should yield 404 """
 
     def test_nonexistent_tag(self):
         """ Looking for a non-existent tag should yield 404 """
-        # NOTE: this yields a false positive, 'cause of URL change
-        self.assertEqual(404, self.client.get('/katalog/autor/czeslaw_milosz/').status_code)
+        self.assertEqual(404, self.client.get('/katalog/autor/czeslaw-milosz/').status_code)
 
     def test_book_tag(self):
         """ Looking for a book tag isn't permitted """
 
     def test_book_tag(self):
         """ Looking for a book tag isn't permitted """
@@ -105,7 +104,8 @@ class TagRelatedTagsTests(WLTestCase):
         """ empty tag should have no related tags """
 
         cats = self.client.get('/katalog/autor/empty/').context['categories']
         """ empty tag should have no related tags """
 
         cats = self.client.get('/katalog/autor/empty/').context['categories']
-        self.assertEqual(cats, {}, 'tags related to empty tag')
+        self.assertEqual({k: v for (k, v) in cats.items() if v}, {},
+            'tags related to empty tag')
 
     def test_has_related(self):
         """ related own and descendants' tags should be generated """
 
     def test_has_related(self):
         """ related own and descendants' tags should be generated """
@@ -115,7 +115,7 @@ class TagRelatedTagsTests(WLTestCase):
                         'missing `author` related tag')
         self.assertTrue('Epoch' in [tag.name for tag in cats['epoch']],
                         'missing `epoch` related tag')
                         'missing `author` related tag')
         self.assertTrue('Epoch' in [tag.name for tag in cats['epoch']],
                         'missing `epoch` related tag')
-        self.assertFalse("kind" in cats,
+        self.assertFalse(cats.get("kind", False),
                         "There should be no child-only related `kind` tags")
         self.assertTrue("Genre" in [tag.name for tag in cats['genre']],
                         'missing `genre` related tag')
                         "There should be no child-only related `kind` tags")
         self.assertTrue("Genre" in [tag.name for tag in cats['genre']],
                         'missing `genre` related tag')
@@ -135,7 +135,7 @@ class TagRelatedTagsTests(WLTestCase):
 
         response = self.client.get('/katalog/rodzaj/kind/')
         cats = response.context['categories']
 
         response = self.client.get('/katalog/rodzaj/kind/')
         cats = response.context['categories']
-        self.assertFalse('kind' in cats,
+        self.assertFalse(cats.get('kind', False),
                          'filtering tag wrongly included in related')
         cats = self.client.get('/katalog/motyw/theme/').context['categories']
         self.assertFalse('Theme' in [tag.name for tag in cats['theme']],
                          'filtering tag wrongly included in related')
         cats = self.client.get('/katalog/motyw/theme/').context['categories']
         self.assertFalse('Theme' in [tag.name for tag in cats['theme']],
@@ -155,12 +155,23 @@ class TagRelatedTagsTests(WLTestCase):
 
         cats = self.client.get('/katalog/epoka/epoch/').context['categories']
         self.assertTrue(('ChildKind', 2) in [(tag.name, tag.count) for tag in cats['kind']],
 
         cats = self.client.get('/katalog/epoka/epoch/').context['categories']
         self.assertTrue(('ChildKind', 2) in [(tag.name, tag.count) for tag in cats['kind']],
-                    'wrong related kind tags on tag page')
+                    'wrong related kind tags on tag page, got: ' +
+                    unicode([(tag.name, tag.count) for tag in cats['kind']]))
 
         # all occurencies of theme should be counted
         self.assertTrue(('Theme', 4) in [(tag.name, tag.count) for tag in cats['theme']],
                     'wrong related theme count')
 
 
         # all occurencies of theme should be counted
         self.assertTrue(('Theme', 4) in [(tag.name, tag.count) for tag in cats['theme']],
                     'wrong related theme count')
 
+    def test_query_child_tag(self):
+        """
+        If child and parent have a common tag, but parent isn't included
+        in the result, child should still count.
+        """
+        cats = self.client.get('/katalog/gatunek/childgenre/').context['categories']
+        self.assertTrue(('Epoch', 2) in [(tag.name, tag.count) for tag in cats['epoch']],
+                    'wrong related kind tags on tag page, got: ' +
+                    unicode([(tag.name, tag.count) for tag in cats['epoch']]))
+
 
 class CleanTagRelationTests(WLTestCase):
     """ tests for tag relations cleaning after deleting things """
 
 class CleanTagRelationTests(WLTestCase):
     """ tests for tag relations cleaning after deleting things """
@@ -183,7 +194,7 @@ class CleanTagRelationTests(WLTestCase):
 
         models.Book.objects.all().delete()
         cats = self.client.get('/katalog/rodzaj/k/').context['categories']
 
         models.Book.objects.all().delete()
         cats = self.client.get('/katalog/rodzaj/k/').context['categories']
-        self.assertEqual(cats, {})
+        self.assertEqual({k: v for (k, v) in cats.items() if v}, {})
         self.assertEqual(models.Fragment.objects.all().count(), 0,
                          "orphaned fragments left")
         self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
         self.assertEqual(models.Fragment.objects.all().count(), 0,
                          "orphaned fragments left")
         self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
@@ -236,7 +247,7 @@ class TestIdenticalTag(WLTestCase):
             context = self.client.get('/katalog/%s/tag/' % localcat).context
             self.assertEqual(1, len(context['object_list']))
             self.assertNotEqual({}, context['categories'])
             context = self.client.get('/katalog/%s/tag/' % localcat).context
             self.assertEqual(1, len(context['object_list']))
             self.assertNotEqual({}, context['categories'])
-            self.assertFalse(cat in context['categories'])
+            self.assertFalse(context['categories'].get(cat, False))
 
 
 class BookTagsTests(WLTestCase):
 
 
 class BookTagsTests(WLTestCase):
@@ -281,6 +292,6 @@ class BookTagsTests(WLTestCase):
         context = self.client.get('/katalog/').context
         self.assertEqual([(tag.name, tag.count) for tag in context['categories']['author']],
                          [('Jim Lazy', 1), ('Common Man', 1)])
         context = self.client.get('/katalog/').context
         self.assertEqual([(tag.name, tag.count) for tag in context['categories']['author']],
                          [('Jim Lazy', 1), ('Common Man', 1)])
-        self.assertEqual([(tag.name, tag.count) for tag in context['fragment_tags']],
+        self.assertEqual([(tag.name, tag.count) for tag in context['categories']['theme']],
                          [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
 
                          [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
 
diff --git a/apps/catalogue/tests/test_visit.py b/apps/catalogue/tests/test_visit.py
new file mode 100644 (file)
index 0000000..1bdbdbd
--- /dev/null
@@ -0,0 +1,79 @@
+# -*- 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 catalogue import models
+from catalogue.test_utils import BookInfoStub, PersonStub, WLTestCase, info_args
+from django.core.files.base import ContentFile
+
+
+class VisitTest(WLTestCase):
+    """Simply create some objects and visit some views."""
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        author = PersonStub(("Jane",), "Doe")
+        book_info = BookInfoStub(author=author, genre="Genre",
+            epoch='Epoch', kind="Kind", **info_args(u"A book"))
+        self.book = models.Book.from_text_and_meta(ContentFile('''
+            <utwor>
+            <opowiadanie>
+                <akap>
+                    <begin id="b1" />
+                    <motyw id="m1">Theme</motyw>
+                    Test
+                    <end id="e1" />
+                </akap>
+            </opowiadanie>
+            </utwor>
+            '''), book_info)
+        self.collection = models.Collection.objects.create(
+            title='Biblioteczka Boya', slug='boy', book_slugs='a-book')
+
+    def test_visit_urls(self):
+        """ book description should return authors, ancestors, book """
+        url_map = {
+            200: [
+                '',
+                'lektury/',
+                'lektury/boy/',
+                'nowe/',
+                'lektura/a-book/',
+                'lektura/a-book.html',
+                'lektura/a-book/motyw/theme/',
+                'motyw/theme/',
+                'autor/jane-doe/',
+                'autor/jane-doe/gatunek/genre/',
+                'autor/jane-doe/gatunek/genre/motyw/theme/',
+                'pl.json',
+                'b/%d/mini.pl.html' % self.book.pk,
+                'b/%d/mini_nolink.pl.html' % self.book.pk,
+                'b/%d/short.pl.html' % self.book.pk,
+                'b/%d/wide.pl.html' % self.book.pk,
+                'f/%d/promo.pl.html' % self.book.fragments.all()[0].pk,
+                'f/%d/short.pl.html' % self.book.fragments.all()[0].pk,
+                ],
+            404: [
+                'lektury/nonexistent/',  # Nonexistent Collection.
+                'lektura/nonexistent/',  # Nonexistent Book.
+                'lektura/nonexistent.html',  # Nonexistent Book's HTML.
+                'lektura/nonexistent/motyw/theme/',  # Nonexistent Book's theme.
+                'lektura/a-book/motyw/nonexistent/',  # Nonexistent theme in a Book.
+                'autor/nonexistent/',  # Nonexistent author.
+                'motyw/nonexistent/',  # Nonexistent theme.
+                'zh.json',  # Nonexistent language.
+                'b/%d/mini.pl.html' % (self.book.pk + 100),  # Nonexistent book.
+                'b/%d/mini_nolink.pl.html' % (self.book.pk + 100),  # Nonexistent book.
+                'b/%d/short.pl.html' % (self.book.pk + 100),  # Nonexistent book.
+                'b/%d/wide.pl.html' % (self.book.pk + 100),  # Nonexistent book.
+                'f/%d/promo.pl.html' % (self.book.fragments.all()[0].pk + 100),  # Nonexistent fragment.
+                'f/%d/short.pl.html' % (self.book.fragments.all()[0].pk + 100),  # Nonexistent fragment.
+                ]
+            }
+        prefix = '/katalog/'
+        for expected_status, urls in url_map.items():
+            for url in urls:
+                status = self.client.get(prefix + url).status_code
+                self.assertEqual(status, expected_status,
+                    "Wrong status code for '%s'. Expected %d, got %d." % (
+                        prefix + url, expected_status, status))
index 7b1fb3d..05f8766 100644 (file)
@@ -18,6 +18,8 @@ urlpatterns = patterns('picture.views',
     url(r'^obraz/(?P<slug>%s).html$' % SLUG, 'picture_viewer', name='picture_viewer'),
     url(r'^obraz/(?P<slug>%s)/$' % SLUG, 'picture_detail'),
 
     url(r'^obraz/(?P<slug>%s).html$' % SLUG, 'picture_viewer', name='picture_viewer'),
     url(r'^obraz/(?P<slug>%s)/$' % SLUG, 'picture_detail'),
 
+    url(r'^p/(?P<pk>\d+)/short\.(?P<lang>.+)\.html', 'picture_short', name='picture_short'),
+    url(r'^pa/(?P<pk>\d+)/short\.(?P<lang>.+)\.html', 'picturearea_short', name='picture_area_short'),
 )
 
 urlpatterns += patterns('',
 )
 
 urlpatterns += patterns('',
@@ -64,6 +66,15 @@ urlpatterns += patterns('catalogue.views',
     url(r'^lektura/(?P<slug>%s)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$' % SLUG,
         'book_fragments', name='book_fragments'),
 
     url(r'^lektura/(?P<slug>%s)/motyw/(?P<theme_slug>[a-zA-Z0-9-]+)/$' % SLUG,
         'book_fragments', name='book_fragments'),
 
+    # Includes.
+    url(r'^(?P<lang>[^/]+)\.json$', 'catalogue_json'),
+    url(r'^b/(?P<pk>\d+)/mini\.(?P<lang>.+)\.html', 'book_mini', name='catalogue_book_mini'),
+    url(r'^b/(?P<pk>\d+)/mini_nolink\.(?P<lang>.+)\.html', 'book_mini', {'with_link': False}, name='catalogue_book_mini_nolink'),
+    url(r'^b/(?P<pk>\d+)/short\.(?P<lang>.+)\.html', 'book_short', name='catalogue_book_short'),
+    url(r'^b/(?P<pk>\d+)/wide\.(?P<lang>.+)\.html', 'book_wide', name='catalogue_book_wide'),
+    url(r'^f/(?P<pk>\d+)/promo\.(?P<lang>.+)\.html', 'fragment_promo', name='catalogue_fragment_promo'),
+    url(r'^f/(?P<pk>\d+)/short\.(?P<lang>.+)\.html', 'fragment_short', name='catalogue_fragment_short'),
+
     # This should be the last pattern.
     url(r'^(?P<tags>[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'),
 )
     # This should be the last pattern.
     url(r'^(?P<tags>[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'),
 )
index 91e782e..bcc5a0b 100644 (file)
@@ -2,8 +2,7 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from __future__ import with_statement
-
+from collections import defaultdict
 import hashlib
 import random
 import re
 import hashlib
 import random
 import re
@@ -37,14 +36,21 @@ def get_random_hash(seed):
     return urlsafe_b64encode(sha_digest).replace('=', '').replace('_', '-').lower()
 
 
     return urlsafe_b64encode(sha_digest).replace('=', '').replace('_', '-').lower()
 
 
-def split_tags(tags, initial=None):
-    if initial is None:
-        result = {}
+def split_tags(*tag_lists):
+    if len(tag_lists) == 1:
+        result = defaultdict(list)
+        for tag in tag_lists[0]:
+            result[tag.category].append(tag)
     else:
     else:
-        result = initial
-
-    for tag in tags:
-        result.setdefault(tag.category, []).append(tag)
+        result = defaultdict(dict)
+        for tag_list in tag_lists:
+            for tag in tag_list:
+                try:
+                    result[tag.category][tag.pk].count += tag.count
+                except KeyError:
+                    result[tag.category][tag.pk] = tag
+        for k, v in result.items():
+            result[k] = sorted(v.values(), key=lambda tag: tag.sort_key)
     return result
 
 
     return result
 
 
index f47329a..e5514f0 100644 (file)
@@ -6,107 +6,108 @@ from collections import OrderedDict
 import re
 
 from django.conf import settings
 import re
 
 from django.conf import settings
-from django.core.cache import get_cache
 from django.template import RequestContext
 from django.template.loader import render_to_string
 from django.template import RequestContext
 from django.template.loader import render_to_string
-from django.shortcuts import render_to_response, get_object_or_404
+from django.shortcuts import render_to_response, get_object_or_404, render
 from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect, JsonResponse
 from django.core.urlresolvers import reverse
 from django.db.models import Q
 from django.contrib.auth.decorators import login_required, user_passes_test
 from django.utils.http import urlquote_plus
 from django.utils import translation
 from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect, JsonResponse
 from django.core.urlresolvers import reverse
 from django.db.models import Q
 from django.contrib.auth.decorators import login_required, user_passes_test
 from django.utils.http import urlquote_plus
 from django.utils import translation
-from django.utils.translation import get_language, ugettext as _, ugettext_lazy
-from django.views.decorators.vary import vary_on_headers
+from django.utils.translation import ugettext as _, ugettext_lazy
 
 from ajaxable.utils import AjaxableFormView
 
 from ajaxable.utils import AjaxableFormView
-from catalogue import models
-from catalogue import forms
-from .helpers import get_related_tags, get_fragment_related_tags, tags_usage_for_books, tags_usage_for_works, tags_usage_for_fragments
-from catalogue.utils import split_tags, MultiQuerySet, SortedMultiQuerySet
-from catalogue.templatetags.catalogue_tags import tag_list, collection_list
 from pdcounter import models as pdcounter_models
 from pdcounter import views as pdcounter_views
 from pdcounter import models as pdcounter_models
 from pdcounter import views as pdcounter_views
-from suggest.forms import PublishingSuggestForm
 from picture.models import Picture, PictureArea
 from picture.views import picture_list_thumb
 from picture.models import Picture, PictureArea
 from picture.views import picture_list_thumb
+from ssify import ssi_included, ssi_expect, SsiVariable as V
+from suggest.forms import PublishingSuggestForm
+from catalogue import forms
+from catalogue.helpers import get_top_level_related_tags
+from catalogue import models
+from catalogue.utils import split_tags, MultiQuerySet, SortedMultiQuerySet
+from catalogue.templatetags.catalogue_tags import tag_list, collection_list
 
 staff_required = user_passes_test(lambda user: user.is_staff)
 
 staff_required = user_passes_test(lambda user: user.is_staff)
-permanent_cache = get_cache('permanent')
-
-
-@vary_on_headers('X-Requested-With')
-def catalogue(request):
-    #cache_key = 'catalogue.catalogue/' + get_language()
-    #output = permanent_cache.get(cache_key)
-    output = None
-
-    if output is None:
-        common_categories = ('author',)
-        split_categories = ('epoch', 'genre', 'kind')
-
-        categories = split_tags(tags_usage_for_works(common_categories))
-        book_categories = split_tags(tags_usage_for_books(split_categories))
-        picture_categories = split_tags(
-            models.Tag.objects.usage_for_model(Picture, counts=True).filter(
-                category__in=split_categories))
-        # we want global usage for themes
-        fragment_tags = list(tags_usage_for_fragments(('theme',)))
-        collections = models.Collection.objects.all()
-
-        render_tag_list = lambda x: render_to_string(
-            'catalogue/tag_list.html', tag_list(x))
-
-        def render_split(with_books, with_pictures):
-            ctx = {}
-            if with_books:
-                ctx['books'] = render_tag_list(with_books)
-            if with_pictures:
-                ctx['pictures'] = render_tag_list(with_pictures)
-            return render_to_string('catalogue/tag_list_split.html', ctx)
-
-        output = {}
-        output['theme'] = render_tag_list(fragment_tags)
-        for category in common_categories:
-            output[category] = render_tag_list(categories.get(category, []))
-        for category in split_categories:
-            output[category] = render_split(
-                book_categories.get(category, []),
-                picture_categories.get(category, []))
-
-        output['collections'] = render_to_string(
-            'catalogue/collection_list.html', collection_list(collections))
-        #permanent_cache.set(cache_key, output)
-    if request.is_ajax():
+
+
+def catalogue(request, as_json=False):
+    common_categories = ('author',)
+    split_categories = ('epoch', 'genre', 'kind')
+
+    categories = split_tags(
+        get_top_level_related_tags(categories=common_categories),
+        models.Tag.objects.usage_for_model(
+            models.Fragment, counts=True).filter(category='theme'),
+        models.Tag.objects.usage_for_model(
+            Picture, counts=True).filter(category__in=common_categories),
+        models.Tag.objects.usage_for_model(
+            PictureArea, counts=True).filter(
+            category='theme')
+    )
+    book_categories = split_tags(
+        get_top_level_related_tags(categories=split_categories)
+        )
+    picture_categories = split_tags(
+        models.Tag.objects.usage_for_model(
+            Picture, counts=True).filter(
+            category__in=split_categories),
+        )
+
+    collections = models.Collection.objects.all()
+
+    def render_tag_list(tags):
+        render_to_string('catalogue/tag_list.html', tag_list(tags))
+
+    def render_split(with_books, with_pictures):
+        ctx = {}
+        if with_books:
+            ctx['books'] = render_tag_list(with_books)
+        if with_pictures:
+            ctx['pictures'] = render_tag_list(with_pictures)
+        return render_to_string('catalogue/tag_list_split.html', ctx)
+
+    output = {}
+    output['theme'] = render_tag_list(categories.get('theme', []))
+    for category in common_categories:
+        output[category] = render_tag_list(categories.get(category, []))
+    for category in split_categories:
+        output[category] = render_split(
+            book_categories.get(category, []),
+            picture_categories.get(category, []))
+
+    output['collections'] = render_to_string(
+        'catalogue/collection_list.html', collection_list(collections))
+    if as_json:
         return JsonResponse(output)
     else:
         return render_to_response('catalogue/catalogue.html', locals(),
             context_instance=RequestContext(request))
 
 
         return JsonResponse(output)
     else:
         return render_to_response('catalogue/catalogue.html', locals(),
             context_instance=RequestContext(request))
 
 
+@ssi_included
+def catalogue_json(request):
+    return catalogue(request, True)
+
+
 def book_list(request, filter=None, get_filter=None,
         template_name='catalogue/book_list.html',
         nav_template_name='catalogue/snippets/book_list_nav.html',
         list_template_name='catalogue/snippets/book_list.html',
 def book_list(request, filter=None, get_filter=None,
         template_name='catalogue/book_list.html',
         nav_template_name='catalogue/snippets/book_list_nav.html',
         list_template_name='catalogue/snippets/book_list.html',
-        cache_key='catalogue.book_list',
         context=None,
         ):
     """ generates a listing of all books, optionally filtered with a test function """
         context=None,
         ):
     """ generates a listing of all books, optionally filtered with a test function """
-    cache_key = "%s/%s" % (cache_key, get_language())
-    cached = permanent_cache.get(cache_key)
-    if cached is not None:
-        rendered_nav, rendered_book_list = cached
-    else:
-        if get_filter:
-            filter = get_filter()
-        books_by_author, orphans, books_by_parent = models.Book.book_list(filter)
-        books_nav = OrderedDict()
-        for tag in books_by_author:
-            if books_by_author[tag]:
-                books_nav.setdefault(tag.sort_key[0], []).append(tag)
-        rendered_nav = render_to_string(nav_template_name, locals())
-        rendered_book_list = render_to_string(list_template_name, locals())
-        permanent_cache.set(cache_key, (rendered_nav, rendered_book_list))
+    if get_filter:
+        filter = get_filter()
+    books_by_author, orphans, books_by_parent = models.Book.book_list(filter)
+    books_nav = OrderedDict()
+    for tag in books_by_author:
+        if books_by_author[tag]:
+            books_nav.setdefault(tag.sort_key[0], []).append(tag)
+    rendered_nav = render_to_string(nav_template_name, locals())
+    rendered_book_list = render_to_string(list_template_name, locals())
     return render_to_response(template_name, locals(),
         context_instance=RequestContext(request))
 
     return render_to_response(template_name, locals(),
         context_instance=RequestContext(request))
 
@@ -115,13 +116,13 @@ def audiobook_list(request):
     return book_list(request, Q(media__type='mp3') | Q(media__type='ogg'),
                      template_name='catalogue/audiobook_list.html',
                      list_template_name='catalogue/snippets/audiobook_list.html',
     return book_list(request, Q(media__type='mp3') | Q(media__type='ogg'),
                      template_name='catalogue/audiobook_list.html',
                      list_template_name='catalogue/snippets/audiobook_list.html',
-                     cache_key='catalogue.audiobook_list')
+                     )
 
 
 def daisy_list(request):
     return book_list(request, Q(media__type='daisy'),
                      template_name='catalogue/daisy_list.html',
 
 
 def daisy_list(request):
     return book_list(request, Q(media__type='daisy'),
                      template_name='catalogue/daisy_list.html',
-                     cache_key='catalogue.daisy_list')
+                     )
 
 
 def collection(request, slug):
 
 
 def collection(request, slug):
@@ -136,7 +137,6 @@ def collection(request, slug):
         raise ValueError('How do I show this kind of collection? %s' % coll.kind)
     return view(request, get_filter=coll.get_query,
                      template_name=tmpl,
         raise ValueError('How do I show this kind of collection? %s' % coll.kind)
     return view(request, get_filter=coll.get_query,
                      template_name=tmpl,
-                     cache_key='catalogue.collection:%s' % coll.slug,
                      context={'collection': coll})
 
 
                      context={'collection': coll})
 
 
@@ -179,19 +179,14 @@ def tagged_object_list(request, tags=''):
     except AttributeError:
         pass
 
     except AttributeError:
         pass
 
-    if len([tag for tag in tags if tag.category == 'book']):
-        raise Http404
-
     # beginning of digestion
     theme_is_set = [tag for tag in tags if tag.category == 'theme']
     shelf_is_set = [tag for tag in tags if tag.category == 'set']
     only_shelf = shelf_is_set and len(tags) == 1
     only_my_shelf = only_shelf and request.user.is_authenticated() and request.user == tags[0].user
     # beginning of digestion
     theme_is_set = [tag for tag in tags if tag.category == 'theme']
     shelf_is_set = [tag for tag in tags if tag.category == 'set']
     only_shelf = shelf_is_set and len(tags) == 1
     only_my_shelf = only_shelf and request.user.is_authenticated() and request.user == tags[0].user
+    tags_pks = [tag.pk for tag in tags]
 
 
-
-    objects =  None
-    categories = {}
-    object_queries = []
+    objects = None
 
     if theme_is_set:
         shelf_tags = [tag for tag in tags if tag.category == 'set']
 
     if theme_is_set:
         shelf_tags = [tag for tag in tags if tag.category == 'set']
@@ -200,47 +195,53 @@ def tagged_object_list(request, tags=''):
         areas = PictureArea.tagged.with_all(fragment_tags)
 
         if shelf_tags:
         areas = PictureArea.tagged.with_all(fragment_tags)
 
         if shelf_tags:
-            # FIXME: book tags here
             books = models.Book.tagged.with_all(shelf_tags).order_by()
             books = models.Book.tagged.with_all(shelf_tags).order_by()
-            l_tags = models.Tag.objects.filter(category='book',
-                slug__in=[book.book_tag_slug() for book in books.iterator()])
-            fragments = models.Fragment.tagged.with_any(l_tags, fragments)
+            fragments = fragments.filter(Q(book__in=books) | Q(book__ancestor__in=books))
+            areas = PictureArea.objects.none()
 
 
-        related_tags = get_fragment_related_tags(tags)
-        categories = split_tags(related_tags, categories)
-        object_queries.insert(0, fragments)
-
-        area_keys = [area.pk for area in areas.iterator()]
-        if area_keys:
-            related_tags = PictureArea.tags.usage(counts=True,
-                                                         filters={'pk__in': area_keys})
-            related_tags = (tag for tag in related_tags if tag not in fragment_tags)
-
-            categories = split_tags(related_tags, categories)
+        categories = split_tags(
+            models.Tag.objects.usage_for_queryset(fragments, counts=True
+                ).exclude(pk__in=tags_pks),
+            models.Tag.objects.usage_for_queryset(areas, counts=True
+                ).exclude(pk__in=tags_pks)
+            )
 
         # we want the Pictures to go first
 
         # we want the Pictures to go first
-        object_queries.insert(0, areas)
-        objects = MultiQuerySet(*object_queries)
+        objects = MultiQuerySet(areas, fragments)
     else:
     else:
+        all_books = models.Book.tagged.with_all(tags)
         if shelf_is_set:
         if shelf_is_set:
-            books = models.Book.tagged.with_all(tags).order_by(
-                'sort_key_author', 'title')
+            books = all_books.order_by('sort_key_author', 'title')
+            pictures = Pictures.objects.none()
+            related_book_tags = models.Tag.objects.usage_for_queryset(
+                books, counts=True).exclude(
+                category='set').exclude(pk__in=tags_pks)
         else:
             books = models.Book.tagged_top_level(tags).order_by(
                 'sort_key_author', 'title')
         else:
             books = models.Book.tagged_top_level(tags).order_by(
                 'sort_key_author', 'title')
-
-        pictures = Picture.tagged.with_all(tags).order_by(
-            'sort_key_author', 'title')
-
-        categories = split_tags(get_related_tags(tags))
+            pictures = Picture.tagged.with_all(tags).order_by(
+                'sort_key_author', 'title')
+            related_book_tags = get_top_level_related_tags(tags)
+
+        fragments = models.Fragment.objects.filter(book__in=all_books)
+        areas = PictureArea.objects.filter(picture__in=pictures)
+
+        categories = split_tags(
+            related_book_tags,
+            models.Tag.objects.usage_for_queryset(
+                pictures, counts=True).exclude(pk__in=tags_pks),
+            models.Tag.objects.usage_for_queryset(
+                fragments, counts=True).filter(
+                category='theme').exclude(pk__in=tags_pks),
+            models.Tag.objects.usage_for_queryset(
+                areas, counts=True).filter(
+                category__in=('theme', 'thing')).exclude(
+                pk__in=tags_pks),
+        )
 
         objects = SortedMultiQuerySet(pictures, books,
             order_by=('sort_key_author', 'title'))
 
 
         objects = SortedMultiQuerySet(pictures, books,
             order_by=('sort_key_author', 'title'))
 
-
-    if not objects:
-        objects = models.Book.objects.none()
-
     return render_to_response('catalogue/tagged_object_list.html',
         {
             'object_list': objects,
     return render_to_response('catalogue/tagged_object_list.html',
         {
             'object_list': objects,
@@ -249,6 +250,7 @@ def tagged_object_list(request, tags=''):
             'only_my_shelf': only_my_shelf,
             'formats_form': forms.DownloadFormatsForm(),
             'tags': tags,
             'only_my_shelf': only_my_shelf,
             'formats_form': forms.DownloadFormatsForm(),
             'tags': tags,
+            'tags_ids': tags_pks,
             'theme_is_set': theme_is_set,
         },
         context_instance=RequestContext(request))
             'theme_is_set': theme_is_set,
         },
         context_instance=RequestContext(request))
@@ -342,7 +344,7 @@ def _no_diacritics_regexp(query):
 
 def unicode_re_escape(query):
     """ Unicode-friendly version of re.escape """
 
 def unicode_re_escape(query):
     """ Unicode-friendly version of re.escape """
-    return re.sub('(?u)(\W)', r'\\\1', query)
+    return re.sub(r'(?u)(\W)', r'\\\1', query)
 
 def _word_starts_with(name, prefix):
     """returns a Q object getting models having `name` contain a word
 
 def _word_starts_with(name, prefix):
     """returns a Q object getting models having `name` contain a word
@@ -429,10 +431,10 @@ def _get_result_link(match, tag_list):
 
 def _get_result_type(match):
     if isinstance(match, models.Book) or isinstance(match, pdcounter_models.BookStub):
 
 def _get_result_type(match):
     if isinstance(match, models.Book) or isinstance(match, pdcounter_models.BookStub):
-        type = 'book'
+        match_type = 'book'
     else:
     else:
-        type = match.category
-    return type
+        match_type = match.category
+    return match_type
 
 
 def books_starting_with(prefix):
 
 
 def books_starting_with(prefix):
@@ -603,3 +605,84 @@ class CustomPDFFormView(AjaxableFormView):
 
     def context_description(self, request, obj):
         return obj.pretty_title()
 
     def context_description(self, request, obj):
         return obj.pretty_title()
+
+
+####
+# Includes
+####
+
+
+@ssi_included
+def book_mini(request, pk, with_link=True):
+    book = get_object_or_404(models.Book, pk=pk)
+    author_str = ", ".join(tag.name
+        for tag in book.tags.filter(category='author'))
+    return render(request, 'catalogue/book_mini_box.html', {
+        'book': book,
+        'author_str': author_str,
+        'with_link': with_link,
+        'show_lang': book.language_code() != settings.LANGUAGE_CODE,
+    })
+
+
+@ssi_included(get_ssi_vars=lambda pk: (lambda ipk: (
+        ('ssify.get_csrf_token',),
+        ('social_tags.likes_book', (ipk,)),
+        ('social_tags.book_shelf_tags', (ipk,)),
+    ))(ssi_expect(pk, int)))
+def book_short(request, pk):
+    book = get_object_or_404(models.Book, pk=pk)
+    stage_note, stage_note_url = book.stage_note()
+
+    return render(request, 'catalogue/book_short.html', {
+        'book': book,
+        'has_audio': book.has_media('mp3'),
+        'main_link': book.get_absolute_url(),
+        'parents': book.parents(),
+        'tags': split_tags(book.tags.exclude(category__in=('set', 'theme'))),
+        'show_lang': book.language_code() != settings.LANGUAGE_CODE,
+        'stage_note': stage_note,
+        'stage_note_url': stage_note_url,
+    })
+
+
+@ssi_included(get_ssi_vars=lambda pk: book_short.get_ssi_vars(pk) +
+    (lambda ipk: (
+        ('social_tags.choose_cite', [ipk]),
+        ('catalogue_tags.choose_fragment', [ipk], {
+            'unless': V('social_tags.choose_cite', [ipk])}),
+    ))(ssi_expect(pk, int)))
+def book_wide(request, pk):
+    book = get_object_or_404(models.Book, pk=pk)
+    stage_note, stage_note_url = book.stage_note()
+    extra_info = book.extra_info
+
+    return render(request, 'catalogue/book_wide.html', {
+        'book': book,
+        'has_audio': book.has_media('mp3'),
+        'parents': book.parents(),
+        'tags': split_tags(book.tags.exclude(category__in=('set', 'theme'))),
+        'show_lang': book.language_code() != settings.LANGUAGE_CODE,
+        'stage_note': stage_note,
+        'stage_note_url': stage_note_url,
+
+        'main_link': reverse('book_text', args=[book.slug]) if book.html_file else None,
+        'extra_info': extra_info,
+        'hide_about': extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl'),
+        'themes': book.related_themes(),
+    })
+
+
+@ssi_included
+def fragment_short(request, pk):
+    fragment = get_object_or_404(models.Fragment, pk=pk)
+    return render(request, 'catalogue/fragment_short.html',
+        {'fragment': fragment})
+
+
+@ssi_included
+def fragment_promo(request, pk):
+    fragment = get_object_or_404(models.Fragment, pk=pk)
+    return render(request, 'catalogue/fragment_promo.html', {
+        'fragment': fragment
+    })
diff --git a/apps/chunks/migrations/0002_auto_20140911_1253.py b/apps/chunks/migrations/0002_auto_20140911_1253.py
new file mode 100644 (file)
index 0000000..09d541c
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+def null_to_blank(apps, schema_editor):
+    Chunk = apps.get_model("chunks", "Chunk")
+    Chunk.objects.filter(content=None).update(content='')
+    Chunk.objects.filter(description=None).update(description='')
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chunks', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RunPython(null_to_blank),
+        migrations.AlterField(
+            model_name='chunk',
+            name='content',
+            field=models.TextField(verbose_name='content', blank=True),
+        ),
+        migrations.AlterField(
+            model_name='chunk',
+            name='description',
+            field=models.CharField(max_length=255, verbose_name='Description', blank=True),
+        ),
+    ]
index 5cdf8fe..7f5410b 100644 (file)
@@ -1,6 +1,7 @@
-from django.core.cache import cache
+from django.conf import settings
 from django.db import models
 from django.db import models
-from django.utils.translation import ugettext_lazy as _, get_language
+from django.utils.translation import ugettext_lazy as _
+from ssify import flush_ssi_includes
 
 
 class Chunk(models.Model):
 
 
 class Chunk(models.Model):
@@ -9,8 +10,8 @@ class Chunk(models.Model):
     any template with the use of a special template tag.
     """
     key = models.CharField(_('key'), help_text=_('A unique name for this chunk of content'), primary_key=True, max_length=255)
     any template with the use of a special template tag.
     """
     key = models.CharField(_('key'), help_text=_('A unique name for this chunk of content'), primary_key=True, max_length=255)
-    description = models.CharField(_('description'), blank=True, null=True, max_length=255)
-    content = models.TextField(_('content'), blank=True, null=True)
+    description = models.CharField(_('description'), blank=True, max_length=255)
+    content = models.TextField(_('content'), blank=True)
 
     class Meta:
         ordering = ('key',)
 
     class Meta:
         ordering = ('key',)
@@ -20,15 +21,17 @@ class Chunk(models.Model):
     def __unicode__(self):
         return self.key
 
     def __unicode__(self):
         return self.key
 
-    @staticmethod
-    def cache_key(key):
-        return 'chunk/%s/%s' % (key, get_language())
-
     def save(self, *args, **kwargs):
         ret = super(Chunk, self).save(*args, **kwargs)
     def save(self, *args, **kwargs):
         ret = super(Chunk, self).save(*args, **kwargs)
-        cache.delete(self.cache_key(self.key))
+        self.flush_includes()
         return ret
 
         return ret
 
+    def flush_includes(self):
+        flush_ssi_includes([
+            '/chunks/chunk/%s.%s.html' % (self.key, lang)
+            for lang in [lc for (lc, _ln) in settings.LANGUAGES]])
+
+
 
 class Attachment(models.Model):
     key = models.CharField(_('key'), help_text=_('A unique name for this attachment'), primary_key=True, max_length=255)
 
 class Attachment(models.Model):
     key = models.CharField(_('key'), help_text=_('A unique name for this attachment'), primary_key=True, max_length=255)
@@ -40,4 +43,3 @@ class Attachment(models.Model):
 
     def __unicode__(self):
         return self.key
 
     def __unicode__(self):
         return self.key
-
diff --git a/apps/chunks/templatetags/__init__.py b/apps/chunks/templatetags/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/apps/chunks/templatetags/chunks.py b/apps/chunks/templatetags/chunks.py
deleted file mode 100644 (file)
index cc25df7..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-from django import template
-from django.db import models
-from django.core.cache import cache
-
-
-register = template.Library()
-
-Chunk = models.get_model('chunks', 'chunk')
-Attachment = models.get_model('chunks', 'attachment')
-
-
-@register.simple_tag
-def chunk(key, cache_time=0):
-    try:
-        cache_key = Chunk.cache_key(key)
-        c = cache.get(cache_key)
-        if c is None:
-            c = Chunk.objects.get(key=key)
-            cache.set(cache_key, c, int(cache_time))
-        content = c.content
-    except Chunk.DoesNotExist:
-        n = Chunk(key=key)
-        n.save()
-        return ''
-    return content
-
-
-@register.simple_tag
-def attachment(key, cache_time=0):
-    try:
-        cache_key = 'attachment_' + key
-        c = cache.get(cache_key)
-        if c is None:
-            c = Attachment.objects.get(key=key)
-            cache.set(cache_key, c, int(cache_time))
-        return c.attachment.url
-    except Attachment.DoesNotExist:
-        return ''
diff --git a/apps/chunks/urls.py b/apps/chunks/urls.py
new file mode 100644 (file)
index 0000000..005cadd
--- /dev/null
@@ -0,0 +1,9 @@
+# -*- 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 import patterns, url
+
+urlpatterns = patterns('chunks.views',
+    url(r'^chunk/(?P<key>.+)\.(?P<lang>.+)\.html$', 'chunk', name='chunk'),
+)
diff --git a/apps/chunks/views.py b/apps/chunks/views.py
new file mode 100644 (file)
index 0000000..cbcf5bf
--- /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.http import HttpResponse
+from ssify import ssi_included
+from .models import Chunk
+
+@ssi_included
+def chunk(request, key):
+    chunk, created = Chunk.objects.get_or_create(key=key)
+    return HttpResponse(chunk.content)
index 7df3d75..2256acf 100644 (file)
@@ -30,6 +30,6 @@ def build_notes(book):
                                html=html_str,
                                sort_key=sortify(text_str).strip()[:128])
 
                                html=html_str,
                                sort_key=sortify(text_str).strip()[:128])
 
-def notes_from_book(sender, **kwargs):
-    build_notes.delay(sender)
+def notes_from_book(sender, instance, **kwargs):
+    build_notes.delay(instance)
 Book.html_built.connect(notes_from_book)
 Book.html_built.connect(notes_from_book)
index 3051276..1a30ffc 100755 (executable)
@@ -18,12 +18,17 @@ class Command(BaseCommand):
         from datetime import date, timedelta
         from funding.models import Offer
         from funding import app_settings
         from datetime import date, timedelta
         from funding.models import Offer
         from funding import app_settings
+        from django.core.cache import caches
+        from django.conf import settings
 
         verbose = options['verbose']
 
         for offer in Offer.past().filter(notified_end=None):
             if verbose:
                 print 'Notify end:', offer
 
         verbose = options['verbose']
 
         for offer in Offer.past().filter(notified_end=None):
             if verbose:
                 print 'Notify end:', offer
+            # The 'WL fund' list needs to be updated.
+            caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
+            offer.flush_includes()
             offer.notify_end()
 
         current = Offer.current()
             offer.notify_end()
 
         current = Offer.current()
index a9c3d87..e36b732 100644 (file)
@@ -4,18 +4,19 @@
 #
 from datetime import date, datetime
 from urllib import urlencode
 #
 from datetime import date, datetime
 from urllib import urlencode
+from django.conf import settings
+from django.contrib.sites.models import Site
 from django.core.urlresolvers import reverse
 from django.core.mail import send_mail
 from django.core.urlresolvers import reverse
 from django.core.mail import send_mail
-from django.conf import settings
-from django.template.loader import render_to_string
 from django.db import models
 from django.db import models
+from django.template.loader import render_to_string
 from django.utils.timezone import utc
 from django.utils.translation import ugettext_lazy as _, override
 import getpaid
 from django.utils.timezone import utc
 from django.utils.translation import ugettext_lazy as _, override
 import getpaid
+from ssify import flush_ssi_includes
 from catalogue.models import Book
 from catalogue.utils import get_random_hash
 from polls.models import Poll
 from catalogue.models import Book
 from catalogue.utils import get_random_hash
 from polls.models import Poll
-from django.contrib.sites.models import Site
 from . import app_settings
 
 
 from . import app_settings
 
 
@@ -58,10 +59,27 @@ class Offer(models.Model):
             self.pk is not None and
             type(self).objects.values('book').get(pk=self.pk)['book'] != self.book_id)
         retval = super(Offer, self).save(*args, **kw)
             self.pk is not None and
             type(self).objects.values('book').get(pk=self.pk)['book'] != self.book_id)
         retval = super(Offer, self).save(*args, **kw)
+        self.flush_includes()
         if published_now:
             self.notify_published()
         return retval
 
         if published_now:
             self.notify_published()
         return retval
 
+    def flush_includes(self):
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/wesprzyj/o/%d/top-bar.%s.html',
+                '/wesprzyj/o/%d/detail-bar.%s.html',
+                '/wesprzyj/o/%d/list-bar.%s.html',
+                '/wesprzyj/o/%d/status.%s.html',
+                '/wesprzyj/o/%d/status-more.%s.html',
+                ] + [
+                    '/wesprzyj/o/%%d/fundings/%d.%%s.html' % page
+                    for page in range(1, len(self.funding_payed()) // 10 + 2)
+                ]
+            for lang in [lc for (lc, _ln) in settings.LANGUAGES]
+            ])
+
     def is_current(self):
         return self.start <= date.today() <= self.end and self == self.current()
 
     def is_current(self):
         return self.start <= date.today() <= self.end and self == self.current()
 
@@ -247,7 +265,9 @@ class Funding(models.Model):
     def save(self, *args, **kwargs):
         if self.email and not self.notify_key:
             self.notify_key = get_random_hash(self.email)
     def save(self, *args, **kwargs):
         if self.email and not self.notify_key:
             self.notify_key = get_random_hash(self.email)
-        return super(Funding, self).save(*args, **kwargs)
+        ret = super(Funding, self).save(*args, **kwargs)
+        self.offer.flush_includes()
+        return ret
 
     @classmethod
     def notify_funders(cls, subject, template_name, extra_context=None,
 
     @classmethod
     def notify_funders(cls, subject, template_name, extra_context=None,
diff --git a/apps/funding/templates/funding/includes/funding.html b/apps/funding/templates/funding/includes/funding.html
new file mode 100644 (file)
index 0000000..66804a4
--- /dev/null
@@ -0,0 +1,53 @@
+{% spaceless %}
+
+{% load i18n %}
+{% load time_tags %}
+
+{% if offer %}
+
+<div class="funding {{ add_class }}" data-offer-id="{{offer.id}}" style="">
+    {% if closeable %}<a href="#" class="close">X</a>{% endif %}
+    {% if link and is_current %}
+        <div class="call-area">
+            <a class="call honking" href="{% url 'funding_current' offer.slug %}">
+                {% trans "Support!" %}</a>
+            <div class="learn-more">
+                <a href="{% url 'infopage' 'wesprzyj' %}">{% trans "Learn more" %}</a>
+            </div>
+        </div>
+    {% endif %}
+    <div class="description {% if link and is_current %}with-button{% endif %}">
+    {% if link %}<a href="{% if is_current %}{% url 'funding_current' offer.slug %}{% else %}{{ offer.get_absolute_url }}{% endif %}">{% endif %}
+    {% if show_title %}
+        {% if is_current and show_title_calling %}<strong style="margin-right: .6em;">{% trans "Help free the book!" %}</strong>{% endif %}
+        <span class="funding-title{% if not is_current %}-strong{% endif %}">{{ offer }}</span>
+    {% endif %}
+
+    <div class="progress"
+        style="text-align: center; background-size: {{ percentage|stringformat:'.2f' }}% 1px;"
+    >
+        {% if sum %}
+            <span class="piece progress-collected">{% trans "collected" %}: {{ sum }} zł</span>
+        {% endif %}
+        {% if not is_win %}
+            <span class="piece progress-target"><span class="{% if sum %}progress-extralabel{% endif %}">{% trans "needed" %}: </span>{{ offer.target }} zł</span>
+        {% endif %}
+        {% if is_current %}
+            <span class="piece progress-until"><span class="progress-extralabel">{% trans "until fundraiser end" %}:</span>
+                <span class="countdown inline" data-until='{{ offer.end|date_to_utc:True|utc_for_js }}'></span>
+            </span>
+        {% else %}
+            <div style="clear: both"></div>
+        {% endif %}
+    </div>
+    {% if link %}</a>{% endif %}
+    </div>
+    <div style="clear: both"></div>
+</div>
+{% if closeable %}
+    <div class="funding-handle">{% trans "Help free the book!" %}</div>
+{% endif %}
+
+{% endif %}
+
+{% endspaceless %}
diff --git a/apps/funding/templates/funding/includes/fundings.html b/apps/funding/templates/funding/includes/fundings.html
new file mode 100644 (file)
index 0000000..e13e1ec
--- /dev/null
@@ -0,0 +1,28 @@
+{% spaceless %}
+
+{% load i18n %}
+{% load pagination_tags %}
+
+<table class="wlfund">
+
+{% for funding in fundings %}
+    <tr class="funding-plus">
+        <td class="oneline">{{ funding.payed_at.date }}</td>
+        <td>
+            {% if funding.name %}
+                {{ funding.name }}
+            {% else %}
+                <em>{% trans "Anonymous" %}</em>
+            {% endif %}
+        </td>
+        <td>{{ funding.amount }}&nbsp;zł</td>
+        <td>&nbsp;
+            {% for perk in funding.perks.all %}
+                {{ perk.name }}{% if not forloop.last %},{% endif %}
+            {% endfor %}
+        </td>
+{% endfor %}
+</table>
+
+{% endspaceless %}{% paginate %}
+
diff --git a/apps/funding/templates/funding/includes/offer_status.html b/apps/funding/templates/funding/includes/offer_status.html
new file mode 100644 (file)
index 0000000..5828ce4
--- /dev/null
@@ -0,0 +1,31 @@
+{% load i18n %}
+
+{% if offer.is_current %}
+    {% if offer.is_win %}
+        <p>
+            {% blocktrans with end=offer.end %}The fundraiser
+            ends on {{ end }}. The full amount has been successfully
+            raised, but you can still contribute and help liberate
+            more books.{% endblocktrans %}
+        </p>
+    {% else %}
+    <p>
+        <strong>{% blocktrans with target=offer.target|floatformat %}W need {{target}} zł to digitize it,
+        compile it and publish for free in multiple formats.{% endblocktrans %}</strong>
+    </p>
+    <p>
+        {% blocktrans with end=offer.end %}If we raise enought money before {{end}} we will
+        publish it and make it available for everyone.{% endblocktrans %}
+    </p>
+    {% endif %}
+{% else %}
+    {% if offer.is_win %}
+        <p>
+            {% trans "Full amount was successfully raised!" %}
+        </p>
+    {% else %}
+        <p>
+            {% trans "The amount needed was not raised." %}
+        </p>
+    {% endif %}
+{% endif %}
diff --git a/apps/funding/templates/funding/includes/offer_status_more.html b/apps/funding/templates/funding/includes/offer_status_more.html
new file mode 100644 (file)
index 0000000..c94c9a8
--- /dev/null
@@ -0,0 +1,26 @@
+{% load i18n %}
+
+{% if offer.is_current %}
+        <p>
+            {% include "funding/snippets/any_remaining.html" %}
+        </p>
+{% else %}
+    <p class="date">{% trans "Fundraiser span" %}: {{ offer.start }} – {{ offer.end }}</p>
+    {% if offer.is_win %}
+        <p>
+            {% if offer.book %}
+                {% blocktrans with bu=offer.book.get_absolute_url bt=offer.book %}The book
+                <a href="{{ bu }}">{{ bt }}</a> has been already published.{% endblocktrans %}
+            {% else %}
+                {% if offer.redakcja_url %}
+                    {% blocktrans with r=offer.redakcja_url %}You can follow
+                    the work on the <a href="{{ r }}">Editorial Platform</a>.{% endblocktrans %}
+                {% endif %}
+            {% endif %}
+        </p>
+    {% endif %}
+
+    {% if offer.remaining %}
+        <p>{% include "funding/snippets/any_remaining.html" %}</p>
+    {% endif %}
+{% endif %}
index 217d5b8..70930c4 100644 (file)
@@ -1,11 +1,11 @@
 {% extends "base.html" %}
 {% load url from future %}
 {% load i18n static %}
 {% extends "base.html" %}
 {% load url from future %}
 {% load i18n static %}
-{% load funding_tags %}
 {% load pagination_tags %}
 {% load fnp_share %}
 {% load thumbnail %}
 {% load build_absolute_uri from fnp_common %}
 {% load pagination_tags %}
 {% load fnp_share %}
 {% load thumbnail %}
 {% load build_absolute_uri from fnp_common %}
+{% load ssi_include from ssify %}
 
 
 {% block titleextra %}{{ object }}{% endblock %}
 
 
 {% block titleextra %}{{ object }}{% endblock %}
@@ -19,7 +19,7 @@
 
 <h1>{{ object }}</h1>
 
 
 <h1>{{ object }}</h1>
 
-{% funding object show_title=False %}
+{% ssi_include 'funding_detail_bar' pk=object.pk %}
 <div class="white-box">
     <div class="funding-details-intro">
         {% if object.cover %}
 <div class="white-box">
     <div class="funding-details-intro">
         {% if object.cover %}
@@ -34,9 +34,9 @@
         <div class="normal-text">
         <h3>{% trans "Help free the book!" %}</h3>
         {{ object.description|safe }}
         <div class="normal-text">
         <h3>{% trans "Help free the book!" %}</h3>
         {{ object.description|safe }}
-        {% offer_status object %}
+        {% ssi_include 'funding_status' pk=object.pk %}
         <a href='//nowoczesnapolska.org.pl/pomoz-nam/wesprzyj-nas/' target="_blank" style='float:right;border:1px solid #ddd;padding: 1em;margin:0 0 1em 1em;background:#eee;'><img src='//nowoczesnapolska.org.pl/wp-content/themes/koed/annoy/procent.png' alt='1%' style='float:left;margin-right: 1em;margin-top:.2em;'>Możesz też przekazać<br/>1% podatku na rozwój biblioteki. &rarr;</a>
         <a href='//nowoczesnapolska.org.pl/pomoz-nam/wesprzyj-nas/' target="_blank" style='float:right;border:1px solid #ddd;padding: 1em;margin:0 0 1em 1em;background:#eee;'><img src='//nowoczesnapolska.org.pl/wp-content/themes/koed/annoy/procent.png' alt='1%' style='float:left;margin-right: 1em;margin-top:.2em;'>Możesz też przekazać<br/>1% podatku na rozwój biblioteki. &rarr;</a>
-        {% offer_status_more object %}
+        {% ssi_include 'funding_status_more' pk=object.pk %}
         <p><a href="{% url 'infopage' 'wesprzyj' %}">{% trans "Learn more" %}</a>.</p>
         </div>
         
         <p><a href="{% url 'infopage' 'wesprzyj' %}">{% trans "Learn more" %}</a>.</p>
         </div>
         
 <h2>{% trans "Supporters" %}:</h2>
 
 <div class="white-box normal-text">
 <h2>{% trans "Supporters" %}:</h2>
 
 <div class="white-box normal-text">
-{% with object.funding_payed.all as fundings %}
-    
-    <table class="wlfund">
-    {% autopaginate fundings 10 %}
-    {% for funding in fundings %}
-        <tr class="funding-plus">
-            <td class="oneline">{{ funding.payed_at.date }}</td>
-            <td>
-                {% if funding.name %}
-                    {{ funding.name }}
-                {% else %}
-                    <em>{% trans "Anonymous" %}</em>
-                {% endif %}
-            </td>
-            <td>{{ funding.amount }}&nbsp;zł</td>
-            <td>&nbsp;
-                {% for perk in funding.perks.all %}
-                    {{ perk.name }}{% if not forloop.last %},{% endif %}
-                {% endfor %}
-            </td>
-    {% endfor %}
-    </table>
-    
-    {% paginate %}
-{% endwith %}
+    {% ssi_include 'funding_fundings' pk=object.pk page=page %}
 </div>
 
 {% endblock %}
 </div>
 
 {% endblock %}
diff --git a/apps/funding/templates/funding/tags/funding.html b/apps/funding/templates/funding/tags/funding.html
deleted file mode 100755 (executable)
index 4628251..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-{% load i18n %}
-{% load time_tags %}
-{% if offer %}
-{% spaceless %}
-<div class="funding {{ add_class }}" data-offer-id="{{offer.id}}" style="">
-    {% if closeable %}<a href="#" class="close">X</a>{% endif %}
-    {% if link and is_current %}
-        <div class="call-area">
-            <a class="call honking" href="{% url 'funding_current' offer.slug %}">
-                {% trans "Support!" %}</a>
-            <div class="learn-more">
-                <a href="{% url 'infopage' 'wesprzyj' %}">{% trans "Learn more" %}</a>
-            </div>
-        </div>
-    {% endif %}
-    <div class="description {% if link and is_current %}with-button{% endif %}">
-    {% if link %}<a href="{% if is_current %}{% url 'funding_current' offer.slug %}{% else %}{{ offer.get_absolute_url }}{% endif %}">{% endif %}
-    {% if show_title %}
-        {% if is_current and show_title_calling %}<strong style="margin-right: .6em;">{% trans "Help free the book!" %}</strong>{% endif %}
-        <span class="funding-title{% if not is_current %}-strong{% endif %}">{{ offer }}</span>
-    {% endif %}
-
-    <div class="progress"
-        style="text-align: center; background-size: {{ percentage|stringformat:'.2f' }}% 1px;"
-    >
-        {% if sum %}
-            <span class="piece progress-collected">{% trans "collected" %}: {{ sum }} zł</span>
-        {% endif %}
-        {% if not is_win %}
-            <span class="piece progress-target"><span class="{% if sum %}progress-extralabel{% endif %}">{% trans "needed" %}: </span>{{ offer.target }} zł</span>
-        {% endif %}
-        {% if is_current %}
-            <span class="piece progress-until"><span class="progress-extralabel">{% trans "until fundraiser end" %}:</span>
-                <span class="countdown inline" data-until='{{ offer.end|date_to_utc:True|utc_for_js }}'></span>
-            </span>
-        {% else %}
-            <div style="clear: both"></div>
-        {% endif %}
-    </div>
-    {% if link %}</a>{% endif %}
-    </div>
-    <div style="clear: both"></div>
-</div>
-{% if closeable %}
-    <div class="funding-handle">{% trans "Help free the book!" %}</div>
-{% endif %}
-{% endspaceless %}
-{% endif %}
diff --git a/apps/funding/templates/funding/tags/offer_status.html b/apps/funding/templates/funding/tags/offer_status.html
deleted file mode 100755 (executable)
index 5828ce4..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-{% load i18n %}
-
-{% if offer.is_current %}
-    {% if offer.is_win %}
-        <p>
-            {% blocktrans with end=offer.end %}The fundraiser
-            ends on {{ end }}. The full amount has been successfully
-            raised, but you can still contribute and help liberate
-            more books.{% endblocktrans %}
-        </p>
-    {% else %}
-    <p>
-        <strong>{% blocktrans with target=offer.target|floatformat %}W need {{target}} zł to digitize it,
-        compile it and publish for free in multiple formats.{% endblocktrans %}</strong>
-    </p>
-    <p>
-        {% blocktrans with end=offer.end %}If we raise enought money before {{end}} we will
-        publish it and make it available for everyone.{% endblocktrans %}
-    </p>
-    {% endif %}
-{% else %}
-    {% if offer.is_win %}
-        <p>
-            {% trans "Full amount was successfully raised!" %}
-        </p>
-    {% else %}
-        <p>
-            {% trans "The amount needed was not raised." %}
-        </p>
-    {% endif %}
-{% endif %}
diff --git a/apps/funding/templates/funding/tags/offer_status_more.html b/apps/funding/templates/funding/tags/offer_status_more.html
deleted file mode 100755 (executable)
index c94c9a8..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-{% load i18n %}
-
-{% if offer.is_current %}
-        <p>
-            {% include "funding/snippets/any_remaining.html" %}
-        </p>
-{% else %}
-    <p class="date">{% trans "Fundraiser span" %}: {{ offer.start }} – {{ offer.end }}</p>
-    {% if offer.is_win %}
-        <p>
-            {% if offer.book %}
-                {% blocktrans with bu=offer.book.get_absolute_url bt=offer.book %}The book
-                <a href="{{ bu }}">{{ bt }}</a> has been already published.{% endblocktrans %}
-            {% else %}
-                {% if offer.redakcja_url %}
-                    {% blocktrans with r=offer.redakcja_url %}You can follow
-                    the work on the <a href="{{ r }}">Editorial Platform</a>.{% endblocktrans %}
-                {% endif %}
-            {% endif %}
-        </p>
-    {% endif %}
-
-    {% if offer.remaining %}
-        <p>{% include "funding/snippets/any_remaining.html" %}</p>
-    {% endif %}
-{% endif %}
index 1a7c872..5dbeec6 100755 (executable)
@@ -3,49 +3,18 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import template
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import template
+from ssify import ssi_variable
+from ssify.utils import ssi_cache_control
 from ..models import Offer
 from ..utils import sanitize_payment_title
 
 register = template.Library()
 
 
 from ..models import Offer
 from ..utils import sanitize_payment_title
 
 register = template.Library()
 
 
-@register.inclusion_tag("funding/tags/funding.html", takes_context=True)
-def funding(context, offer=None, link=False, closeable=False, show_title=True, show_title_calling=True, add_class=""):
-    if offer is None and context.get('funding_no_show_current') is None:
-        offer = Offer.current()
-        is_current = True
-    elif offer is not None:
-        is_current = offer.is_current()
+@ssi_variable(register, patch_response=[ssi_cache_control(must_revalidate=True, max_age=0)])
+def current_offer(request):
+    offer = Offer.current()
+    return offer.pk if offer is not None else None
 
 
-    if offer is None:
-        return {}
-
-    offer_sum = offer.sum()
-    return {
-        'offer': offer,
-        'sum': offer_sum,
-        'is_current': is_current,
-        'is_win': offer_sum >= offer.target,
-        'missing': offer.target - offer_sum,
-        'percentage': 100 * offer_sum / offer.target,
-        'link': link,
-        'closeable': closeable,
-        'show_title': show_title,
-        'show_title_calling': show_title_calling,
-        'add_class': add_class,
-    }
-
-
-@register.inclusion_tag("funding/tags/offer_status.html")
-def offer_status(offer):
-    return {
-        'offer': offer,
-    }
-
-@register.inclusion_tag("funding/tags/offer_status_more.html")
-def offer_status_more(offer):
-    return {
-        'offer': offer,
-    }
 
 register.filter(sanitize_payment_title)
 
 register.filter(sanitize_payment_title)
index 2b8e5d6..4d806e8 100644 (file)
@@ -8,7 +8,7 @@ from .views import (WLFundView, OfferDetailView, OfferListView,
                 ThanksView, NoThanksView, CurrentView, DisableNotifications)
 
 
                 ThanksView, NoThanksView, CurrentView, DisableNotifications)
 
 
-urlpatterns = patterns('',
+urlpatterns = patterns('funding.views',
 
     url(r'^$', CurrentView.as_view(), name='funding_current'),
     url(r'^teraz/$', CurrentView.as_view()),
 
     url(r'^$', CurrentView.as_view(), name='funding_current'),
     url(r'^teraz/$', CurrentView.as_view()),
@@ -23,4 +23,12 @@ urlpatterns = patterns('',
     url(r'^wylacz_email/$', DisableNotifications.as_view(), name='funding_disable_notifications'),
 
     url(r'^getpaid/', include('getpaid.urls')),
     url(r'^wylacz_email/$', DisableNotifications.as_view(), name='funding_disable_notifications'),
 
     url(r'^getpaid/', include('getpaid.urls')),
+
+    # Includes
+    url(r'^o/(?P<pk>\d+)/top-bar\.(?P<lang>.+)\.html$', 'top_bar', name='funding_top_bar'),
+    url(r'^o/(?P<pk>\d+)/detail-bar\.(?P<lang>.+)\.html$', 'detail_bar', name='funding_detail_bar'),
+    url(r'^o/(?P<pk>\d+)/list-bar\.(?P<lang>.+)\.html$', 'list_bar', name='funding_list_bar'),
+    url(r'^o/(?P<pk>\d+)/status\.(?P<lang>.+)\.html$', 'offer_status', name='funding_status'),
+    url(r'^o/(?P<pk>\d+)/status-more\.(?P<lang>.+)\.html$', 'offer_status_more', name='funding_status_more'),
+    url(r'^o/(?P<pk>\d+)/fundings/(?P<page>\d+)\.(?P<lang>.+)\.html$', 'offer_fundings', name='funding_fundings'),
 )
 )
index 9dd18cc..7c9adef 100644 (file)
@@ -2,12 +2,15 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # 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.paginator import Paginator, InvalidPage
 from django.core.urlresolvers import reverse
 from django.http import Http404
 from django.core.urlresolvers import reverse
 from django.http import Http404
-from django.shortcuts import redirect, get_object_or_404
+from django.shortcuts import get_object_or_404, redirect, render
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic import TemplateView, FormView, ListView
 from getpaid.models import Payment
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic import TemplateView, FormView, ListView
 from getpaid.models import Payment
+from ssify import ssi_included
+from ssify.utils import ssi_cache_control
 from . import app_settings
 from .forms import FundingForm
 from .models import Offer, Spent, Funding
 from . import app_settings
 from .forms import FundingForm
 from .models import Offer, Spent, Funding
@@ -94,6 +97,7 @@ class OfferDetailView(FormView):
     def get_context_data(self, *args, **kwargs):
         ctx = super(OfferDetailView, self).get_context_data(*args, **kwargs)
         ctx['object'] = self.object
     def get_context_data(self, *args, **kwargs):
         ctx = super(OfferDetailView, self).get_context_data(*args, **kwargs)
         ctx['object'] = self.object
+        ctx['page'] = self.request.GET.get('page', 1)
         if self.object.is_current():
             ctx['funding_no_show_current'] = True
         return ctx
         if self.object.is_current():
             ctx['funding_no_show_current'] = True
         return ctx
@@ -153,3 +157,72 @@ class DisableNotifications(TemplateView):
     def post(self, *args, **kwargs):
         self.object.disable_notifications()
         return redirect(self.request.get_full_path())
     def post(self, *args, **kwargs):
         self.object.disable_notifications()
         return redirect(self.request.get_full_path())
+
+
+def offer_bar(request, pk, link=False, closeable=False, show_title=True, show_title_calling=True, add_class=""):
+    offer = get_object_or_404(Offer, pk=pk)
+    offer_sum = offer.sum()
+
+    return render(request, "funding/includes/funding.html", {
+        'offer': offer,
+        'sum': offer_sum,
+        'is_current': offer.is_current(),
+        'is_win': offer_sum >= offer.target,
+        'missing': offer.target - offer_sum,
+        'percentage': 100 * offer_sum / offer.target,
+        'link': link,
+        'closeable': closeable,
+        'show_title': show_title,
+        'show_title_calling': show_title_calling,
+        'add_class': add_class,
+    })
+
+
+@ssi_included(patch_response=[ssi_cache_control(must_revalidate=True)])
+def top_bar(request, pk):
+    return offer_bar(request, pk,
+        link=True, closeable=True, add_class="funding-top-header")
+
+
+@ssi_included(patch_response=[ssi_cache_control(must_revalidate=True)])
+def list_bar(request, pk):
+    return offer_bar(request, pk,
+        link=True, show_title_calling=False)
+
+
+@ssi_included(patch_response=[ssi_cache_control(must_revalidate=True)])
+def detail_bar(request, pk):
+    return offer_bar(request, pk,
+        show_title=False)
+
+
+@ssi_included(patch_response=[ssi_cache_control(must_revalidate=True)])
+def offer_status(request, pk):
+    offer = get_object_or_404(Offer, pk=pk)
+    return render(request, "funding/includes/offer_status.html", {
+        'offer': offer,
+    })
+
+
+@ssi_included(patch_response=[ssi_cache_control(must_revalidate=True)])
+def offer_status_more(request, pk):
+    offer = get_object_or_404(Offer, pk=pk)
+    return render(request, "funding/includes/offer_status_more.html", {
+        'offer': offer,
+    })
+
+
+@ssi_included(patch_response=[ssi_cache_control(must_revalidate=True)])
+def offer_fundings(request, pk, page):
+    offer = get_object_or_404(Offer, pk=pk)
+    fundings = offer.funding_payed()
+    paginator = Paginator(fundings, 10, 2)
+    try:
+        page_obj = paginator.page(int(page))
+    except InvalidPage:
+        raise Http404
+    return render(request, "funding/includes/fundings.html", {
+        "paginator": paginator,
+        "page_obj": page_obj,
+        "fundings": page_obj.object_list,
+    })
index db2d49b..75dae08 100755 (executable)
@@ -1,6 +1,5 @@
 {% extends "base.html" %}
 {% load i18n %}
 {% extends "base.html" %}
 {% load i18n %}
-{% load chunks %}
 
 {% block titleextra %}{{ page.title }}{% endblock %}
 
 
 {% block titleextra %}{{ page.title }}{% endblock %}
 
index 93d605d..754b17e 100644 (file)
@@ -3,12 +3,10 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.conf.urls import patterns, url
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.conf.urls import patterns, url
-from django.http import HttpResponseRedirect
 
 
 urlpatterns = patterns('libraries.views',
     url(r'^$', 'main_view', name='libraries_main_view'),
 
 
 urlpatterns = patterns('libraries.views',
     url(r'^$', 'main_view', name='libraries_main_view'),
-    url(r'^/$', lambda x: HttpResponseRedirect(x.path[:-1])),
-    url(r'^/(?P<slug>[a-zA-Z0-9_-]+)$', 'catalog_view', name='libraries_catalog_view'),
-    url(r'^/(?P<catalog_slug>[a-zA-Z0-9_-]+)/(?P<slug>[a-zA-Z0-9_-]+)$', 'library_view', name='libraries_library_view'),
+    url(r'^(?P<slug>[a-zA-Z0-9_-]+)$', 'catalog_view', name='libraries_catalog_view'),
+    url(r'^(?P<catalog_slug>[a-zA-Z0-9_-]+)/(?P<slug>[a-zA-Z0-9_-]+)$', 'library_view', name='libraries_library_view'),
 )
 )
index ead442f..694f5b8 100644 (file)
@@ -71,7 +71,7 @@ class TagManager(models.Manager):
             if tag not in current_tags:
                 self.intermediary_table_model._default_manager.create(tag=tag, content_object=obj)
 
             if tag not in current_tags:
                 self.intermediary_table_model._default_manager.create(tag=tag, content_object=obj)
 
-        tags_updated.send(sender=obj, affected_tags=tags_to_add + tags_for_removal)
+        tags_updated.send(sender=type(obj), instance=obj, affected_tags=tags_to_add + tags_for_removal)
 
     def remove_tag(self, obj, tag):
         """
 
     def remove_tag(self, obj, tag):
         """
@@ -179,7 +179,7 @@ class TaggedItemManager(models.Manager):
         if not tags:
             return queryset.none()
         # TODO: presumes reverse generic relation
         if not tags:
             return queryset.none()
         # TODO: presumes reverse generic relation
-        return queryset.filter(tag_relations__tag__in=tags)
+        return queryset.filter(tag_relations__tag__in=tags).distinct()
 
     def get_related(self, obj, queryset_or_model):
         """
 
     def get_related(self, obj, queryset_or_model):
         """
index 3e2092e..e5b4421 100644 (file)
@@ -5,10 +5,12 @@
 from datetime import datetime
 from django.template import RequestContext
 from django.shortcuts import render_to_response, get_object_or_404
 from datetime import datetime
 from django.template import RequestContext
 from django.shortcuts import render_to_response, get_object_or_404
-from pdcounter import models
+from django.views.decorators import cache
 from suggest.forms import PublishingSuggestForm
 from suggest.forms import PublishingSuggestForm
+from . import models
 
 
 
 
+@cache.never_cache
 def book_stub_detail(request, slug):
     book = get_object_or_404(models.BookStub, slug=slug)
     if book.pd and not book.in_pd():
 def book_stub_detail(request, slug):
     book = get_object_or_404(models.BookStub, slug=slug)
     if book.pd and not book.in_pd():
@@ -21,6 +23,7 @@ def book_stub_detail(request, slug):
         context_instance=RequestContext(request))
 
 
         context_instance=RequestContext(request))
 
 
+@cache.never_cache
 def author_detail(request, slug):
     author = get_object_or_404(models.Author, slug=slug)
     if not author.alive():
 def author_detail(request, slug):
     author = get_object_or_404(models.Author, slug=slug)
     if not author.alive():
index 5fc3722..42a26c3 100644 (file)
@@ -10,11 +10,8 @@ from django.conf import settings
 from django.contrib.contenttypes.fields import GenericRelation
 from django.core.files.storage import FileSystemStorage
 from django.utils.datastructures import SortedDict
 from django.contrib.contenttypes.fields import GenericRelation
 from django.core.files.storage import FileSystemStorage
 from django.utils.datastructures import SortedDict
-from django.template.loader import render_to_string
-from django.utils.safestring import mark_safe
-from django.core.cache import caches
-from catalogue.utils import split_tags
 from fnpdjango.utils.text.slughifi import slughifi
 from fnpdjango.utils.text.slughifi import slughifi
+from ssify import flush_ssi_includes
 from picture import tasks
 from StringIO import StringIO
 import jsonfield
 from picture import tasks
 from StringIO import StringIO
 import jsonfield
@@ -23,13 +20,11 @@ import logging
 
 from PIL import Image
 
 
 from PIL import Image
 
-from django.utils.translation import get_language, ugettext_lazy as _
+from django.utils.translation import ugettext_lazy as _
 from newtagging import managers
 from os import path
 
 
 from newtagging import managers
 from os import path
 
 
-permanent_cache = caches['permanent']
-
 picture_storage = FileSystemStorage(location=path.join(
         settings.MEDIA_ROOT, 'pictures'),
         base_url=settings.MEDIA_URL + "pictures/")
 picture_storage = FileSystemStorage(location=path.join(
         settings.MEDIA_ROOT, 'pictures'),
         base_url=settings.MEDIA_URL + "pictures/")
@@ -48,6 +43,8 @@ class PictureArea(models.Model):
     tags        = managers.TagDescriptor(catalogue.models.Tag)
     tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
     tags        = managers.TagDescriptor(catalogue.models.Tag)
     tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
+    short_html_url_name = 'picture_area_short'
+
     @classmethod
     def rectangle(cls, picture, kind, coords):
         pa = PictureArea()
     @classmethod
     def rectangle(cls, picture, kind, coords):
         pa = PictureArea()
@@ -56,35 +53,18 @@ class PictureArea(models.Model):
         pa.area = coords
         return pa
 
         pa.area = coords
         return pa
 
-    def reset_short_html(self):
-        if self.id is None:
+    def flush_includes(self, languages=True):
+        if not languages:
             return
             return
-
-        cache_key = "PictureArea.short_html/%d/%s"
-        for lang, langname in settings.LANGUAGES:
-            permanent_cache.delete(cache_key % (self.id, lang))
-
-
-    def short_html(self):
-        if self.id:
-            cache_key = "PictureArea.short_html/%d/%s" % (self.id, get_language())
-            short_html = permanent_cache.get(cache_key)
-        else:
-            short_html = None
-
-        if short_html is not None:
-            return mark_safe(short_html)
-        else:
-            theme = self.tags.filter(category='theme')
-            theme = theme and theme[0] or None
-            thing = self.tags.filter(category='thing')
-            thing = thing and thing[0] or None
-            area = self
-            short_html = unicode(render_to_string(
-                    'picture/picturearea_short.html', locals()))
-            if self.id:
-                permanent_cache.set(cache_key, short_html)
-            return mark_safe(short_html)
+        if languages is True:
+            languages = [lc for (lc, _ln) in settings.LANGUAGES]
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/katalog/pa/%d/short.%s.html',
+                ]
+            for lang in languages
+            ])
 
 
 class Picture(models.Model):
 
 
 class Picture(models.Model):
@@ -114,6 +94,8 @@ class Picture(models.Model):
     tags        = managers.TagDescriptor(catalogue.models.Tag)
     tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
     tags        = managers.TagDescriptor(catalogue.models.Tag)
     tag_relations = GenericRelation(catalogue.models.Tag.intermediary_table_model)
 
+    short_html_url_name = 'picture_short'
+
     class AlreadyExists(Exception):
         pass
 
     class AlreadyExists(Exception):
         pass
 
@@ -123,15 +105,18 @@ class Picture(models.Model):
         verbose_name = _('picture')
         verbose_name_plural = _('pictures')
 
         verbose_name = _('picture')
         verbose_name_plural = _('pictures')
 
-    def save(self, force_insert=False, force_update=False, reset_short_html=True, **kwargs):
+    def save(self, force_insert=False, force_update=False, **kwargs):
         from sortify import sortify
 
         self.sort_key = sortify(self.title)
 
         from sortify import sortify
 
         self.sort_key = sortify(self.title)
 
-        ret = super(Picture, self).save(force_insert, force_update)
+        try:
+            author = self.tags.filter(category='author')[0].sort_key
+        except IndexError:
+            author = u''
+        self.sort_key_author = author
 
 
-        if reset_short_html:
-            self.reset_short_html()
+        ret = super(Picture, self).save(force_insert, force_update)
 
         return ret
 
 
         return ret
 
@@ -327,48 +312,9 @@ class Picture(models.Model):
             self._info = info
         return self._info
 
             self._info = info
         return self._info
 
-    def reset_short_html(self):
-        if self.id is None:
-            return
-
-        for area in self.areas.all().iterator():
-            area.reset_short_html()
-
-        try:
-            author = self.tags.filter(category='author')[0].sort_key
-        except IndexError:
-            author = u''
-        type(self).objects.filter(pk=self.pk).update(sort_key_author=author)
-
-        cache_key = "Picture.short_html/%d/%s"
-        for lang, langname in settings.LANGUAGES:
-            permanent_cache.delete(cache_key % (self.id, lang))
-
-    def short_html(self):
-        if self.id:
-            cache_key = "Picture.short_html/%d/%s" % (self.id, get_language())
-            short_html = permanent_cache.get(cache_key)
-        else:
-            short_html = None
-
-        if short_html is not None:
-            return mark_safe(short_html)
-        else:
-            tags = self.tags.filter(category__in=('author', 'kind', 'epoch', 'genre'))
-            tags = split_tags(tags)
-
-            short_html = unicode(render_to_string(
-                    'picture/picture_short.html',
-                    {'picture': self, 'tags': tags}))
-
-            if self.id:
-                permanent_cache.set(cache_key, short_html)
-            return mark_safe(short_html)
-
     def pretty_title(self, html_links=False):
         picture = self
     def pretty_title(self, html_links=False):
         picture = self
-        names = [(tag.name,
-                  catalogue.models.Tag.create_url('author', tag.slug))
+        names = [(tag.name, tag.get_absolute_url())
                  for tag in self.tags.filter(category='author')]
         names.append((self.title, self.get_absolute_url()))
 
                  for tag in self.tags.filter(category='author')]
         names.append((self.title, self.get_absolute_url()))
 
@@ -378,7 +324,19 @@ class Picture(models.Model):
             names = [tag[0] for tag in names]
         return ', '.join(names)
 
             names = [tag[0] for tag in names]
         return ', '.join(names)
 
-    # copied from book.py, figure out
     def related_themes(self):
         return catalogue.models.Tag.objects.usage_for_queryset(
             self.areas.all(), counts=True).filter(category__in=('theme', 'thing'))
     def related_themes(self):
         return catalogue.models.Tag.objects.usage_for_queryset(
             self.areas.all(), counts=True).filter(category__in=('theme', 'thing'))
+
+    def flush_includes(self, languages=True):
+        if not languages:
+            return
+        if languages is True:
+            languages = [lc for (lc, _ln) in settings.LANGUAGES]
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/katalog/p/%d/short.%s.html',
+                ]
+            for lang in languages
+            ])
index a3cbf7d..51bd6b9 100644 (file)
@@ -2,7 +2,7 @@
 {% load i18n %}
 {% load picture_tags catalogue_tags pagination_tags %}
 {% load thumbnail %}
 {% load i18n %}
 {% load picture_tags catalogue_tags pagination_tags %}
 {% load thumbnail %}
-{% load build_absolute_uri from common_tags %}
+{% load build_absolute_uri from fnp_common %}
 
 
 {% block ogimage %}{% thumbnail picture.image_file "535" upscale="false" as thumb %}{{ thumb.url|build_absolute_uri:request }}{% endthumbnail %}{% endblock %}
 
 
 {% block ogimage %}{% thumbnail picture.image_file "535" upscale="false" as thumb %}{{ thumb.url|build_absolute_uri:request }}{% endthumbnail %}{% endblock %}
index 71cd702..4fce98a 100644 (file)
@@ -1,9 +1,7 @@
 {% extends "base.html" %}
 {% load i18n %}
 {% extends "base.html" %}
 {% load i18n %}
-{% load chunks %}
-{% load picture_tags %}
-{% load thumbnail %}
 {% load static %}
 {% load static %}
+{% load ssi_include from ssify %}
 
 {% block bodyid %}picture-list{% endblock %}
 
 
 {% block bodyid %}picture-list{% endblock %}
 
@@ -15,7 +13,7 @@
 
     <div class="left-column"><div class="normal-text">
         {% block book_list_info %}
 
     <div class="left-column"><div class="normal-text">
         {% block book_list_info %}
-            {% chunk 'picture-list' %}
+            {% ssi_include 'chunk' key='picture-list' %}
         {% endblock %}
     </div></div>
     <div class="right-column" id="logo-space">
         {% endblock %}
     </div></div>
     <div class="right-column" id="logo-space">
@@ -30,7 +28,7 @@
   <ol class="work-list">{% spaceless %}
 {% for picture in book_list %}
    <li class="Picture-item">
   <ol class="work-list">{% spaceless %}
 {% for picture in book_list %}
    <li class="Picture-item">
- {% picture_short picture %} 
+   {% ssi_include 'picture_short' pk=picture.pk %}
    </li>
 {% endfor %}
   {% endspaceless %}</ol>
    </li>
 {% endfor %}
   {% endspaceless %}</ol>
index 28cd34f..cb05889 100644 (file)
@@ -1,7 +1,6 @@
 {% extends "picture/picture_short.html" %}
 {% load i18n %}
 {% load picture_tags thumbnail %}
 {% extends "picture/picture_short.html" %}
 {% load i18n %}
 {% load picture_tags thumbnail %}
-{% load cite_promo from social_tags %}
 
 
 {% block box-class %}book-wide-box{% endblock %}
 
 
 {% block box-class %}book-wide-box{% endblock %}
index 1767ce2..7e464fa 100644 (file)
@@ -12,16 +12,6 @@ register = template.Library()
 
 cropper = CustomCroppingEngine()
 
 
 cropper = CustomCroppingEngine()
 
-@register.inclusion_tag('picture/picture_short.html', takes_context=True)
-def picture_short(context, picture):
-    context.update({
-        'picture': picture,
-        'main_link': picture.get_absolute_url(),
-        'request': context.get('request'),
-        'tags': split_tags(picture.tags),
-        })
-    return context
-
 @register.inclusion_tag('picture/picture_wide.html', takes_context=True)
 def picture_wide(context, picture):
     context.update({
 @register.inclusion_tag('picture/picture_wide.html', takes_context=True)
 def picture_wide(context, picture):
     context.update({
index 11b08bc..4bc2ab0 100644 (file)
@@ -4,10 +4,11 @@
 #
 from collections import OrderedDict
 from django.contrib.auth.decorators import permission_required
 #
 from collections import OrderedDict
 from django.contrib.auth.decorators import permission_required
-from django.shortcuts import render_to_response, get_object_or_404
+from django.shortcuts import render_to_response, get_object_or_404, render
 from django.template import RequestContext
 from django.template import RequestContext
-from picture.models import Picture
+from picture.models import Picture, PictureArea
 from catalogue.utils import split_tags
 from catalogue.utils import split_tags
+from ssify import ssi_included
 
 # was picture/picture_list.html list (without thumbs)
 def picture_list(request, filter=None, get_filter=None, template_name='catalogue/picture_list.html', cache_key=None, context=None):
 
 # was picture/picture_list.html list (without thumbs)
 def picture_list(request, filter=None, get_filter=None, template_name='catalogue/picture_list.html', cache_key=None, context=None):
@@ -87,3 +88,23 @@ def import_picture(request):
         return HttpResponse(_("Error importing file: %r") % import_form.errors)
 
 
         return HttpResponse(_("Error importing file: %r") % import_form.errors)
 
 
+@ssi_included
+def picture_short(request, pk):
+    picture = get_object_or_404(Picture, pk=pk)
+
+    return render(request, 'picture/picture_short.html', {
+        'picture': picture,
+        'main_link': picture.get_absolute_url(),
+        'request': request,
+        'tags': split_tags(picture.tags),
+        })
+
+
+@ssi_included
+def picturearea_short(request, pk):
+    area = get_object_or_404(PictureArea, pk=pk)
+    theme = area.tags.filter(category='theme')
+    theme = theme and theme[0] or None
+    thing = area.tags.filter(category='thing')
+    thing = thing and thing[0] or None
+    return render(request, 'picture/picturearea_short.html', locals())
index 8566a9d..4c4d2b5 100644 (file)
@@ -1,4 +1,5 @@
 {% load i18n %}
 {% load i18n %}
+{% load ssi_csrf_token from ssify %}
 
 {% if poll %}
     {% if voted_already %}
 
 {% if poll %}
     {% if voted_already %}
@@ -18,7 +19,7 @@
     {% else %}
         <div class="poll">
             <p>{{poll.question}}</p>
     {% else %}
         <div class="poll">
             <p>{{poll.question}}</p>
-            <form action="{{poll.get_absolute_url}}" method="post">{% csrf_token %}
+            <form action="{{poll.get_absolute_url}}" method="post">{% ssi_csrf_token %}
             {{ form.vote }}
             <input type="submit" value="{% trans "Submit" %}" />
             </form>
             {{ form.vote }}
             <input type="submit" value="{% trans "Submit" %}" />
             </form>
index ef5f50f..79540c6 100644 (file)
@@ -2,34 +2,36 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.views.decorators.http import require_http_methods\r
-from django.shortcuts import get_object_or_404, redirect, render_to_response\r
-from django.core.urlresolvers import reverse\r
+from django.core.urlresolvers import reverse
+from django.shortcuts import get_object_or_404, redirect, render_to_response
 from django.template import RequestContext
 from django.template import RequestContext
+from django.views.decorators import cache
+from django.views.decorators.http import require_http_methods
 
 
-from .models import Poll, PollItem\r
+from .models import Poll, PollItem
 from .forms import PollForm
 
 from .forms import PollForm
 
-\r
-@require_http_methods(['GET', 'POST'])\r
+
+@cache.never_cache
+@require_http_methods(['GET', 'POST'])
 def poll(request, slug):
 
     poll = get_object_or_404(Poll, slug=slug, open=True)
 
 def poll(request, slug):
 
     poll = get_object_or_404(Poll, slug=slug, open=True)
 
-    if request.method == 'POST':\r
+    if request.method == 'POST':
         redirect_to = reverse('poll', args = [slug])
         redirect_to = reverse('poll', args = [slug])
-        form = PollForm(request.POST, poll = poll)\r
-        if form.is_valid():\r
-            if not poll.voted(request.session):\r
-                try:\r
+        form = PollForm(request.POST, poll = poll)
+        if form.is_valid():
+            if not poll.voted(request.session):
+                try:
                     poll_item = PollItem.objects.filter(pk=form.cleaned_data['vote'], poll=poll).get()
                     poll_item = PollItem.objects.filter(pk=form.cleaned_data['vote'], poll=poll).get()
-                except PollItem.DoesNotExist:\r
-                    pass\r
-                else:\r
+                except PollItem.DoesNotExist:
+                    pass
+                else:
                     poll_item.vote(request.session)
         return redirect(redirect_to)
     elif request.method == 'GET':
         context = RequestContext(request)
                     poll_item.vote(request.session)
         return redirect(redirect_to)
     elif request.method == 'GET':
         context = RequestContext(request)
-        context['poll'] = poll\r
+        context['poll'] = poll
         context['voted_already'] = poll.voted(request.session)
         return render_to_response('polls/poll.html', context)
         context['voted_already'] = poll.voted(request.session)
         return render_to_response('polls/poll.html', context)
index 65d9427..8dbad9d 100644 (file)
@@ -10,7 +10,6 @@ from django import template
 # from django.db.models import Q
 # from django.utils.translation import ugettext as _
 from catalogue.models import Book
 # from django.db.models import Q
 # from django.utils.translation import ugettext as _
 from catalogue.models import Book
-from catalogue.templatetags.catalogue_tags import book_short
 import re
 # from catalogue.forms import SearchForm
 # from catalogue.utils import split_tags
 import re
 # from catalogue.forms import SearchForm
 # from catalogue.utils import split_tags
@@ -49,6 +48,8 @@ def book_searched(context, result):
         snip = snip.replace("\n", "<br />").replace('---', '&mdash;')
         hit['snippet'] = snip
 
         snip = snip.replace("\n", "<br />").replace('---', '&mdash;')
         hit['snippet'] = snip
 
-    ctx = book_short(context, book)
-    ctx['hits'] = hits and zip(*hits)[1] or []
-    return ctx
+    return {
+        'request': context['request'],
+        'book': book,
+        'hits':  hits and zip(*hits)[1] or []
+    }
index df1b175..8902807 100644 (file)
@@ -4,8 +4,9 @@
 #
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 #
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
+from django.conf import settings
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
-
+from ssify import flush_ssi_includes
 from catalogue.models import Book
 
 
 from catalogue.models import Book
 
 
@@ -44,3 +45,18 @@ class Cite(models.Model):
     def get_absolute_url(self):
         """This is used for testing."""
         return "%s?choose_cite=%d" % (reverse('main_page'), self.id)
     def get_absolute_url(self):
         """This is used for testing."""
         return "%s?choose_cite=%d" % (reverse('main_page'), self.id)
+
+    def save(self, *args, **kwargs):
+        ret = super(Cite, self).save(*args, **kwargs)
+        self.flush_includes()
+        return ret
+
+    def flush_includes(self):
+        flush_ssi_includes([
+            template % (self.pk, lang)
+            for template in [
+                '/ludzie/cite/%s.%s.html',
+                '/ludzie/cite_main/%s.%s.html',
+            ]
+            for lang in [lc for (lc, _ln) in settings.LANGUAGES]] +
+            ['/ludzie/cite_info/%s.html' % self.pk])
diff --git a/apps/social/templates/social/cite_info.html b/apps/social/templates/social/cite_info.html
new file mode 100644 (file)
index 0000000..4ee84d3
--- /dev/null
@@ -0,0 +1,18 @@
+{% spaceless %}
+
+{% if cite.image %}
+    {% if cite.image_link %}<a href="{{ cite.image_link }}">{% endif %}
+    {% if cite.image_title %}
+        {{ cite.image_title }}{% else %}
+        untitled{% endif %}{% if cite.image_link %}</a>{% endif %},
+    {% if cite.image_author %}{{ cite.image_author }},{% endif %}
+    {% if cite.image_license_link %}<a href="{{ cite.image_license_link }}">{% endif %}
+    {{ cite.image_license }}
+    {% if cite.image_license_link %}</a>{% endif %}
+{% else %}
+    <a href="http://www.flickr.com/photos/lou/430980641/">books about architecture</a>,
+    saikofish@Flickr,
+    <a href="http://creativecommons.org/licenses/by-nc-sa/2.0/">CC BY NC SA</a>.
+{% endif %}
+
+{% endspaceless %}
\ No newline at end of file
index c3a73e8..15786cf 100755 (executable)
@@ -1,6 +1,13 @@
+{% spaceless %}
+
 {% load i18n %}
 
 {% load i18n %}
 
+{% if main %}
+    <section id="big-cite"{% if cite.image %} style="background-image: url('{{ cite.image.url }}'); background-position: 50% {{ cite.image_shift|default_if_none:50 }}%;"{% endif %} >
+{% endif %}
+
 {% if cite %}
 {% if cite %}
+
 <a href="{{ cite.link }}" class="cite{% if cite.small %} cite-small{% endif %}">
     {% if cite.vip %}
         <p class='vip mono'><span>{{ cite.vip }} {% trans "recommends" %}:</span></p>
 <a href="{{ cite.link }}" class="cite{% if cite.small %} cite-small{% endif %}">
     {% if cite.vip %}
         <p class='vip mono'><span>{{ cite.vip }} {% trans "recommends" %}:</span></p>
     <p class="source mono"><span>{{ cite.book.pretty_title }}</span></p>
     {% endif %}
 </a>
     <p class="source mono"><span>{{ cite.book.pretty_title }}</span></p>
     {% endif %}
 </a>
-{% else %}
-    {% if fallback %}
-        {% load fragment_promo from catalogue_tags %}
-        {% fragment_promo ctx %}
-    {% endif %}
+
 {% endif %}
 {% endif %}
+
+
+{% if main %}
+    </section>
+{% endif %}
+
+{% endspaceless %}
\ No newline at end of file
index 2f4a1d3..5974a2a 100755 (executable)
@@ -1,15 +1,16 @@
 {% load i18n %}
 {% load i18n %}
+{% load ssi_csrf_token from ssify %}
 <h1>{{ title }}</h1>
 
 <form action="{% url 'social_unlike_book' view_kwargs.slug %}" method="post" accept-charset="utf-8"
        class="cuteform{% if placeholdize %} hidelabels{% endif %}">
 <h1>{{ title }}</h1>
 
 <form action="{% url 'social_unlike_book' view_kwargs.slug %}" method="post" accept-charset="utf-8"
        class="cuteform{% if placeholdize %} hidelabels{% endif %}">
-{% csrf_token %}
+{% ssi_csrf_token %}
     <input type="submit" value="{% trans "Remove from my shelf" %}"/>
 </form>
 
 <form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8"
        class="cuteform{% if placeholdize %} hidelabels{% endif %}">
     <input type="submit" value="{% trans "Remove from my shelf" %}"/>
 </form>
 
 <form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8"
        class="cuteform{% if placeholdize %} hidelabels{% endif %}">
-{% csrf_token %}
+{% ssi_csrf_token %}
 <ol>
     <div id="id___all__"></div>
     {{ form.as_ul }}
 <ol>
     <div id="id___all__"></div>
     {{ form.as_ul }}
index eabc961..2baa0f0 100755 (executable)
@@ -1,7 +1,9 @@
-{% if tags.exists %}
+{% spaceless %}
+
 <ul class='social-shelf-tags'>
     {% for tag in tags %}
         <li><a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a></li>
     {% endfor %}
 </ul>
 <ul class='social-shelf-tags'>
     {% for tag in tags %}
         <li><a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a></li>
     {% endfor %}
 </ul>
-{% endif %}
+
+{% endspaceless %}
\ No newline at end of file
index bb1b4bc..7065467 100755 (executable)
@@ -5,30 +5,36 @@
 from random import randint
 from django.db.models import Q
 from django import template
 from random import randint
 from django.db.models import Q
 from django import template
-from catalogue.models import Book
+from django.utils.functional import lazy
+from django.utils.cache import add_never_cache_headers
+from catalogue.models import Book, Tag
+from ssify import ssi_variable
+from ssify.utils import ssi_vary_on_cookie
 from social.models import Cite
 from social.utils import likes, cites_for_tags
 
 register = template.Library()
 
 from social.models import Cite
 from social.utils import likes, cites_for_tags
 
 register = template.Library()
 
-register.filter('likes', likes)
 
 
+@ssi_variable(register, patch_response=[ssi_vary_on_cookie])
+def likes_book(request, book_id):
+    return likes(request.user, Book.objects.get(pk=book_id), request)
 
 
-@register.assignment_tag(takes_context=True)
-def choose_cite(context, ctx=None):
+
+def choose_cite(request, book_id=None, tag_ids=None):
     """Choose a cite for main page, for book or for set of tags."""
     try:
     """Choose a cite for main page, for book or for set of tags."""
     try:
-        request = context['request']
         assert request.user.is_staff
         assert 'choose_cite' in request.GET
         cite = Cite.objects.get(pk=request.GET['choose_cite'])
     except (AssertionError, Cite.DoesNotExist):
         assert request.user.is_staff
         assert 'choose_cite' in request.GET
         cite = Cite.objects.get(pk=request.GET['choose_cite'])
     except (AssertionError, Cite.DoesNotExist):
-        if ctx is None:
-            cites = Cite.objects.all()
-        elif isinstance(ctx, Book):
-            cites = Cite.objects.filter(Q(book=ctx) | Q(book__ancestor=ctx))
+        if book_id is not None:
+            cites = Cite.objects.filter(Q(book=book_id) | Q(book__ancestor=book_id))
+        elif tag_ids is not None:
+            tags = Tag.objects.filter(pk__in=tag_ids)
+            cites = cites_for_tags(tags)
         else:
         else:
-            cites = cites_for_tags(ctx)
+            cites = Cite.objects.all()
         stickies = cites.filter(sticky=True)
         count = stickies.count()
         if count:
         stickies = cites.filter(sticky=True)
         count = stickies.count()
         if count:
@@ -42,6 +48,12 @@ def choose_cite(context, ctx=None):
     return cite
 
 
     return cite
 
 
+@ssi_variable(register, name='choose_cite', patch_response=[add_never_cache_headers])
+def choose_cite_tag(request, book_id=None, tag_ids=None):
+    cite = choose_cite(request, book_id, tag_ids)
+    return cite.pk if cite is not None else None
+
+
 @register.inclusion_tag('social/cite_promo.html')
 def render_cite(cite):
     return {
 @register.inclusion_tag('social/cite_promo.html')
 def render_cite(cite):
     return {
@@ -49,20 +61,18 @@ def render_cite(cite):
     }
 
 
     }
 
 
-@register.inclusion_tag('social/cite_promo.html', takes_context=True)
-def cite_promo(context, ctx=None, fallback=False):
-    return {
-        'cite': choose_cite(context, ctx),
-        'fallback': fallback,
-        'ctx': ctx,
-    }
-
-
-@register.inclusion_tag('social/shelf_tags.html', takes_context=True)
-def shelf_tags(context, book):
-    user = context['request'].user
-    if not user.is_authenticated():
-        tags = []
-    else:
-        tags = book.tags.filter(category='set', user=user).exclude(name='')
-    return {'tags': tags}
+@ssi_variable(register, patch_response=[ssi_vary_on_cookie])
+def book_shelf_tags(request, book_id):
+    if not request.user.is_authenticated():
+        return None
+    book = Book.objects.get(pk=book_id)
+    lks = likes(request.user, book, request)
+    def get_value():
+        if not lks:
+            return ''
+        tags = book.tags.filter(category='set', user=request.user).exclude(name='')
+        if not tags:
+            return ''
+        ctx = {'tags': tags}
+        return template.loader.render_to_string('social/shelf_tags.html', ctx)
+    return lazy(get_value, unicode)()
index b25ab25..3642d91 100755 (executable)
@@ -3,14 +3,20 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.conf.urls import patterns, url
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django.conf.urls import patterns, url
+from django.views.decorators.cache import never_cache
 from social.views import ObjectSetsFormView
 
 urlpatterns = patterns('social.views',
     url(r'^lektura/(?P<slug>[a-z0-9-]+)/lubie/$', 'like_book', name='social_like_book'),
     url(r'^lektura/(?P<slug>[a-z0-9-]+)/nie_lubie/$', 'unlike_book', name='social_unlike_book'),
 from social.views import ObjectSetsFormView
 
 urlpatterns = patterns('social.views',
     url(r'^lektura/(?P<slug>[a-z0-9-]+)/lubie/$', 'like_book', name='social_like_book'),
     url(r'^lektura/(?P<slug>[a-z0-9-]+)/nie_lubie/$', 'unlike_book', name='social_unlike_book'),
-    url(r'^lektura/(?P<slug>[a-z0-9-]+)/polki/$', ObjectSetsFormView(), name='social_book_sets'),
+    url(r'^lektura/(?P<slug>[a-z0-9-]+)/polki/$', never_cache(ObjectSetsFormView()), name='social_book_sets'),
     url(r'^polka/$', 'my_shelf', name='social_my_shelf'),
 
     url(r'^polka/$', 'my_shelf', name='social_my_shelf'),
 
+    # Includes
+    url(r'^cite/(?P<pk>\d+)\.(?P<lang>.+)\.html$', 'cite', name='social_cite'),
+    url(r'^cite_main/(?P<pk>\d+)\.(?P<lang>.+)\.html$', 'cite', {'main': True}, name='social_cite_main'),
+    url(r'^cite_info/(?P<pk>\d+).html$', 'cite_info', name='social_cite_info'),
+
     #~ 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<slug>%s)/usun$' % SLUG, 'remove_from_shelf', name='remove_from_shelf'),
     #~ url(r'^polki/$', 'user_shelves', name='user_shelves'),
     #~ 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<slug>%s)/usun$' % SLUG, 'remove_from_shelf', name='remove_from_shelf'),
     #~ url(r'^polki/$', 'user_shelves', name='user_shelves'),
index c6a9353..bf1c242 100755 (executable)
@@ -2,15 +2,44 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
+from collections import defaultdict
+from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
 from django.db.models import Q
+from django.utils.functional import lazy
 from catalogue.models import Book, Tag
 from catalogue import utils
 from catalogue.tasks import touch_tag
 from social.models import Cite
 
 
 from catalogue.models import Book, Tag
 from catalogue import utils
 from catalogue.tasks import touch_tag
 from social.models import Cite
 
 
-def likes(user, work):
-    return user.is_authenticated() and work.tags.filter(category='set', user=user).exists()
+def likes(user, work, request=None):
+    if not user.is_authenticated():
+        return False
+
+    if request is None:
+        return work.tags.filter(category='set', user=user).exists()
+
+    if not hasattr(request, 'social_likes'):
+        # tuple: unchecked, checked, liked
+        request.social_likes = defaultdict(lambda:(set(), set(), set()))
+
+    ct = ContentType.objects.get_for_model(type(work))
+    likes_t = request.social_likes[ct.pk]
+    if work.pk in likes_t[1]:
+        return work.pk in likes_t[2]
+    else:
+        likes_t[0].add(work.pk)
+        def _likes():
+            if likes_t[0]:
+                ids = tuple(likes_t[0])
+                likes_t[0].clear()
+                likes_t[2].update(Tag.intermediary_table_model.objects.filter(
+                    content_type_id=ct.pk, tag__user_id=user.pk,
+                    object_id__in=ids
+                ).distinct().values_list('object_id', flat=True))
+                likes_t[1].update(ids)
+            return work.pk in likes_t[2]
+        return lazy(_likes, bool)()
 
 
 def get_set(user, name):
 
 
 def get_set(user, name):
index 446c5c4..49c9b70 100644 (file)
@@ -10,7 +10,9 @@ from django.views.decorators.http import require_POST
 from ajaxable.utils import AjaxableFormView
 
 from catalogue.models import Book
 from ajaxable.utils import AjaxableFormView
 
 from catalogue.models import Book
+from ssify import ssi_included
 from social import forms
 from social import forms
+from .models import Cite
 from social.utils import get_set, likes, set_sets
 
 
 from social.utils import get_set, likes, set_sets
 
 
@@ -69,3 +71,20 @@ def unlike_book(request, slug):
         return JsonResponse({"success": True, "msg": "ok", "like": False})
     else:
         return redirect(book)
         return JsonResponse({"success": True, "msg": "ok", "like": False})
     else:
         return redirect(book)
+
+
+@ssi_included
+def cite(request, pk, main=False):
+    cite = get_object_or_404(Cite, pk=pk)
+    return render(request, 'social/cite_promo.html', {
+        'main': main,
+        'cite': cite,
+    })
+
+
+@ssi_included(use_lang=False)
+def cite_info(request, pk):
+    cite = get_object_or_404(Cite, pk=pk)
+    return render(request, 'social/cite_info.html', {
+        'cite': cite,
+    })
index fd3c3a9..0565b97 100644 (file)
@@ -12,6 +12,7 @@ from PIL import Image
 
 from jsonfield import JSONField
 from django.core.files.base import ContentFile
 
 from jsonfield import JSONField
 from django.core.files.base import ContentFile
+from ssify import flush_ssi_includes
 
 THUMB_WIDTH = 120
 THUMB_HEIGHT = 120
 
 THUMB_WIDTH = 120
 THUMB_HEIGHT = 120
@@ -94,7 +95,12 @@ class SponsorPage(models.Model):
             'sponsors': self.populated_sponsors(),
             'page': self
         })
             'sponsors': self.populated_sponsors(),
             'page': self
         })
-        return super(SponsorPage, self).save(*args, **kwargs)
+        ret = super(SponsorPage, self).save(*args, **kwargs)
+        self.flush_includes()
+        return ret
+
+    def flush_includes(self):
+        flush_ssi_includes(['/sponsors/page/%s.html' % self.name])
 
     def __unicode__(self):
         return self.name
 
     def __unicode__(self):
         return self.name
diff --git a/apps/sponsors/templatetags/__init__.py b/apps/sponsors/templatetags/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/apps/sponsors/templatetags/sponsor_tags.py b/apps/sponsors/templatetags/sponsor_tags.py
deleted file mode 100644 (file)
index 3670123..0000000
+++ /dev/null
@@ -1,21 +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 import template
-from django.utils.safestring import mark_safe
-
-from sponsors import models
-
-
-register = template.Library()
-
-
-def sponsor_page(name):
-    try:
-        page = models.SponsorPage.objects.get(name=name)
-    except:
-        return u''
-    return mark_safe(page.html)
-
-sponsor_page = register.simple_tag(sponsor_page)
diff --git a/apps/sponsors/urls.py b/apps/sponsors/urls.py
new file mode 100644 (file)
index 0000000..6da6186
--- /dev/null
@@ -0,0 +1,9 @@
+# -*- 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 import patterns, url
+
+urlpatterns = patterns('sponsors.views',
+    url(r'^page/(?P<name>.+)\.html$', 'page', name='sponsor_page'),
+)
diff --git a/apps/sponsors/views.py b/apps/sponsors/views.py
new file mode 100644 (file)
index 0000000..9a34089
--- /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 django.http import HttpResponse
+from ssify import ssi_included
+from .models import SponsorPage
+
+
+@ssi_included(use_lang=False)
+def page(request, name):
+    try:
+        page = SponsorPage.objects.get(name=name)
+    except SponsorPage.DoesNotExist:
+        return HttpResponse(u'')
+    return HttpResponse(page.html)
index 6efbb90..2ac0ec2 100755 (executable)
@@ -1,10 +1,11 @@
 {% load i18n %}
 {% load honeypot %}
 {% load i18n %}
 {% load honeypot %}
+{% load ssi_csrf_token from ssify %}
 
 <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">
 
 <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 %}
+{% ssi_csrf_token %}
 {% render_honeypot_field %}
 <ol>
     <li><span class="error">{{ form.contact.errors }}</span><label for="id_contact">{{ form.contact.label }}</label> {{ form.contact }}</li>
 {% render_honeypot_field %}
 <ol>
     <li><span class="error">{{ form.contact.errors }}</span><label for="id_contact">{{ form.contact.label }}</label> {{ form.contact }}</li>
index e69de29..56037a0 100644 (file)
@@ -0,0 +1,5 @@
+# -*- 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.
+#
+default_app_config = 'wolnelektury_core.apps.WLCoreConfig'
diff --git a/apps/wolnelektury_core/apps.py b/apps/wolnelektury_core/apps.py
new file mode 100644 (file)
index 0000000..996ccae
--- /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.apps import AppConfig
+
+class WLCoreConfig(AppConfig):
+    name = 'wolnelektury_core'
+
+    def ready(self):
+        from . import signals
diff --git a/apps/wolnelektury_core/models.py b/apps/wolnelektury_core/models.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/apps/wolnelektury_core/signals.py b/apps/wolnelektury_core/signals.py
new file mode 100644 (file)
index 0000000..5eb7e88
--- /dev/null
@@ -0,0 +1,28 @@
+# -*- 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 import settings
+from django.core.cache import caches
+from django.db.models.signals import post_save, post_delete
+from django.dispatch import receiver
+
+from funding.models import Spent
+from infopages.models import InfoPage
+from libraries.models import Catalog, Library
+from pdcounter.models import Author, BookStub
+
+
+@receiver([post_save, post_delete])
+def flush_views_after_manual_change(sender, **kwargs):
+    """Flushes views cache after changes with some models.
+
+    Changes to those models happen infrequently, so we can afford
+    to just flush the cache on those instances.
+
+    If changes become too often, relevant bits should be separated
+    as ssi_included views and flushed individually when needed.
+
+    """
+    if sender in (Catalog, Library, InfoPage, Author, BookStub, Spent):
+        caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
index 4b6c5d3..f2a8e26 100644 (file)
@@ -73,7 +73,7 @@
                                                $current = $hidden;
                         if ($(this).hasClass('load-menu') && !menu_loaded) {
                             $.ajax({
                                                $current = $hidden;
                         if ($(this).hasClass('load-menu') && !menu_loaded) {
                             $.ajax({
-                                url: '/katalog/',
+                                url: '/katalog/' + LANGUAGE_CODE + '.json',
                                 dataType: "json",
                             }).done(function(data) {
                                 $.each(data, function(index, value) {
                                 dataType: "json",
                             }).done(function(data) {
                                 $.each(data, function(index, value) {
index 671f32a..756b8ad 100644 (file)
@@ -1,9 +1,11 @@
 {% load i18n %}
 {% load i18n %}
+{% load ssi_csrf_token from ssify %}
+
 <h1>{{ title }}</h1>
 
 <form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8"
        class="cuteform{% if placeholdize %} hidelabels{% endif %}">
 <h1>{{ title }}</h1>
 
 <form action="{{ request.get_full_path }}" method="post" accept-charset="utf-8"
        class="cuteform{% if placeholdize %} hidelabels{% endif %}">
-{% csrf_token %}
+{% ssi_csrf_token %}
 <ol>
     <div id="id_{% if form_prefix %}{{ form_prefix }}-{% endif %}__all__"></div>
     {{ form.as_ul }}
 <ol>
     <div id="id_{% if form_prefix %}{{ form_prefix }}-{% endif %}__all__"></div>
     {{ form.as_ul }}
index 64b0b88..80d3d23 100755 (executable)
@@ -1,6 +1,7 @@
 {% extends "auth/login.html" %}
 {% load i18n %}
 {% load honeypot %}
 {% extends "auth/login.html" %}
 {% load i18n %}
 {% load honeypot %}
+{% load ssi_csrf_token from ssify %}
 
 {% block extra %}
 
 
 {% block extra %}
 
@@ -10,7 +11,7 @@
 
 <form action="{% url 'register' %}" method="post" accept-charset="utf-8"
        class="cuteform hidelabels">
 
 <form action="{% url 'register' %}" method="post" accept-charset="utf-8"
        class="cuteform hidelabels">
-{% csrf_token %}
+{% ssi_csrf_token %}
 {% render_honeypot_field %}
 <ol>
     <div id="id_register-__all__"></div>
 {% render_honeypot_field %}
 <ol>
     <div id="id_register-__all__"></div>
diff --git a/apps/wolnelektury_core/templates/latest_blog_posts.html b/apps/wolnelektury_core/templates/latest_blog_posts.html
new file mode 100644 (file)
index 0000000..24d486e
--- /dev/null
@@ -0,0 +1,9 @@
+{% spaceless %}
+
+<ol>
+{% for post in posts %}
+    <li><a href="{{ post.link }}">{{ post.title }}</a></li>
+{% endfor %}
+</ol>
+
+{% endspaceless %}
\ No newline at end of file
index fd51cf3..118f3ac 100755 (executable)
@@ -1,60 +1,51 @@
 {% extends "base.html" %}
 {% load static from staticfiles %}
 {% extends "base.html" %}
 {% load static from staticfiles %}
-{% load cache chunks i18n catalogue_tags infopages_tags social_tags %}
+{% load i18n catalogue_tags infopages_tags social_tags %}
+{% load ssi_include from ssify %}
 
 
 {% block title %}{% trans "Wolne Lektury internet library" %}{% endblock %}
 {% block ogtitle %}{% trans "Wolne Lektury internet library" %}{% endblock %}
 
 
 
 {% block title %}{% trans "Wolne Lektury internet library" %}{% endblock %}
 {% block ogtitle %}{% trans "Wolne Lektury internet library" %}{% endblock %}
 
-{% block body %}
-    <section id="big-cite"{% if cite.image %}
-        style="
-            background-image: url('{{ cite.image.url }}');
-            background-position: 50% {{ cite.image_shift|default_if_none:50 }}%;
-        "{% endif %} >
-        {% render_cite cite %}
-    </section>
+{% block body %}{% spaceless %}
+
+    {% choose_cite as cite_pk %}
+    {{ cite_pk.if }}
+        {% ssi_include 'social_cite_main' pk=cite_pk %}
+    {{ cite_pk.endif }}
 
 
-    {% spaceless %}
 
 
     <section id="promo-box">
         <h1>{% trans "What's new?" %}</h1>
         <div id="promo-box-body">
 
 
     <section id="promo-box">
         <h1>{% trans "What's new?" %}</h1>
         <div id="promo-box-body">
-            {% chunk "promo" %}
+            {% ssi_include 'chunk' key='promo' %}
         </div>
     </section>
 
 
     <section id="main-last">
         <h1><a href="{% url 'recent_list' %}">{% trans "Recent publications" %}</a></h1>
         </div>
     </section>
 
 
     <section id="main-last">
         <h1><a href="{% url 'recent_list' %}">{% trans "Recent publications" %}</a></h1>
-        {% cache 60 last-published-on-main LANGUAGE_CODE %}
             {% for book in last_published %}
             {% for book in last_published %}
-                {% book_mini book %}
+                {% ssi_include 'catalogue_book_mini' pk=book.pk %}
             {% endfor %}
             {% endfor %}
-        {% endcache %}
     </section>
 
     <div class="clearboth"></div>
 
     <section class="infopages-box">
         <h1>{% trans "News" %}</h1>
     </section>
 
     <div class="clearboth"></div>
 
     <section class="infopages-box">
         <h1>{% trans "News" %}</h1>
-       {# 135 is the id of new publications category of our master blog. perhaps this URL should go to settings. #}
-        {% cache 1800 latest-blog-posts %}
-            {% latest_blog_posts "http://nowoczesnapolska.org.pl/feed/?cat=-135" %}
-        {% endcache %}
+        {% ssi_include 'latest_blog_posts' %}
     </section>
 
 
     <section class="infopages-box">
     </section>
 
 
     <section class="infopages-box">
-        <h1>{% trans "Utilities" %}</h2>
+        <h1>{% trans "Utilities" %}</h1>
 
         <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-->
 
         <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 'reporting_catalogue_pdf' %}">
-                       {% trans "Download the catalogue in PDF format." %}
-               </a></li>
+            <li><a href="{% url 'reporting_catalogue_pdf' %}">{% trans "Download the catalogue in PDF format." %}</a></li>
             <!--li><a href="{% url 'infopage' "widget" %}">{% trans "Widget" %}</a></li-->
             <li><a href="{% url 'suggest_publishing' %}" id="suggest-publishing" class="ajaxable">{% trans "Missing a book?" %}</a></li>
             <li><a href="{% url 'publish_plan' %}">{% trans "Publishing plan" %}</a></li>
             <!--li><a href="{% url 'infopage' "widget" %}">{% trans "Widget" %}</a></li-->
             <li><a href="{% url 'suggest_publishing' %}" id="suggest-publishing" class="ajaxable">{% trans "Missing a book?" %}</a></li>
             <li><a href="{% url 'publish_plan' %}">{% trans "Publishing plan" %}</a></li>
     <section class="infopages-box">
         <h1>{% trans "Information" %}</h1>
         <ul>
     <section class="infopages-box">
         <h1>{% trans "Information" %}</h1>
         <ul>
-            <li><a href="http://nowoczesnapolska.org.pl/prywatnosc/">{% trans "Privacy policy" %}</a></li>
-        {% cache 60 infopages-on-main LANGUAGE_CODE %}
+            <li><a href="https://nowoczesnapolska.org.pl/prywatnosc/">{% trans "Privacy policy" %}</a></li>
             {% infopages_on_main %}
             {% infopages_on_main %}
-        {% endcache %}
         </ul>
 
         <div class="social-links">
         </ul>
 
         <div class="social-links">
-            <a href="http://pl-pl.facebook.com/pages/Wolne-Lektury/203084073268"
-                title='Wolne Lektury @ Facebook'>
+            <a href="https://pl-pl.facebook.com/pages/Wolne-Lektury/203084073268" title='Wolne Lektury @ Facebook'>
                 <img src="{% static "img/social/f.png" %}" alt="Wolne Lektury @ Facebook" />
             </a>
                 <img src="{% static "img/social/f.png" %}" alt="Wolne Lektury @ Facebook" />
             </a>
-            <a href="http://nk.pl/profile/30441509"
-                title='Wolne Lektury @ NK'>
+            <a href="https://nk.pl/profile/30441509" title='Wolne Lektury @ NK'>
                 <img src="{% static "img/social/nk.png" %}" alt="Wolne Lektury @ NK.pl" />
             </a>
         </div>
     </section>
 
 
                 <img src="{% static "img/social/nk.png" %}" alt="Wolne Lektury @ NK.pl" />
             </a>
         </div>
     </section>
 
 
-    {% endspaceless %}
-
-{% endblock %}
-
-
-{% block add_footer %}
-<p>{% trans "Image used:" %} 
-{% if cite.image %}
-    {% if cite.image_link %}<a href="{{ cite.image_link }}">{% endif %}
-    {% if cite.image_title %}
-        {{ cite.image_title }}{% else %}
-        untitled{% endif %}{% if cite.image_link %}</a>{% endif %},
-    {% if cite.image_author %}{{ cite.image_author }},{% endif %}
-    {% if cite.image_license_link %}<a href="{{ cite.image_license_link }}">{% endif %}
-    {{ cite.image_license }}
-    {% if cite.image_license_link %}</a>{% endif %}
-{% else %}
-    <a href="http://www.flickr.com/photos/lou/430980641/">books about architecture</a>,
-    saikofish@Flickr,
-    <a href="http://creativecommons.org/licenses/by-nc-sa/2.0/">CC BY NC SA</a>.
-{% endif %}
-</p>
-{% endblock %}
+{% endspaceless %}{% endblock %}
+
+
+{% block add_footer %}{% spaceless %}
+{{ cite_pk.if }}
+    <p>{% trans "Image used:" %}</p>
+    {% ssi_include 'social_cite_info' pk=cite_pk %}
+    </p>
+{{ cite_pk.endif }}
+{% endspaceless %}{% endblock %}
index 7489d4a..432cf69 100644 (file)
@@ -2,25 +2,25 @@
 {% if is_paginated %}
 <div class="pagination">
     {% if page_obj.has_previous %}
 {% if is_paginated %}
 <div class="pagination">
     {% if page_obj.has_previous %}
-        <a href="?page={{ page_obj.previous_page_number }}{{ getvars }}" class="prev">&lsaquo;&lsaquo; {% trans "previous" %}</a>
+        <a href="?page={{ page_obj.previous_page_number }}{{ getvars }}" class="prev">&lsaquo;&lsaquo; {% trans "previous" %} </a>
     {% else %}
     {% else %}
-        <span class="disabled prev">&lsaquo;&lsaquo; {% trans "previous" %}</span>
+        <span class="disabled prev">&lsaquo;&lsaquo; {% trans "previous" %} </span>
     {% endif %}
     {% for page in pages %}
         {% if page %}
             {% ifequal page page_obj.number %}
     {% endif %}
     {% for page in pages %}
         {% if page %}
             {% ifequal page page_obj.number %}
-                <span class="current page">{{ page }}</span>
+                <span class="current page"> {{ page }} </span>
             {% else %}
             {% else %}
-                <a href="?page={{ page }}{{ getvars }}" class="page">{{ page }}</a>
+                <a href="?page={{ page }}{{ getvars }}" class="page"> {{ page }} </a>
             {% endifequal %}
         {% else %}
             ...
         {% endif %}
     {% endfor %}
     {% if page_obj.has_next %}
             {% endifequal %}
         {% else %}
             ...
         {% endif %}
     {% endfor %}
     {% if page_obj.has_next %}
-        <a href="?page={{ page_obj.next_page_number }}{{ getvars }}" class="next">{% trans "next" %} &rsaquo;&rsaquo;</a>
+        <a href="?page={{ page_obj.next_page_number }}{{ getvars }}" class="next"> {% trans "next" %} &rsaquo;&rsaquo;</a>
     {% else %}
     {% else %}
-        <span class="disabled next">{% trans "next" %} &rsaquo;&rsaquo;</span>
+        <span class="disabled next"> {% trans "next" %} &rsaquo;&rsaquo;</span>
     {% endif %}
 </div>
 {% endif %}
     {% endif %}
 </div>
 {% endif %}
index 46afe28..c3259a3 100644 (file)
@@ -1,10 +1,12 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
+{% spaceless %}
 <html lang="{{ LANGUAGE_CODE }}" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
 <html lang="{{ LANGUAGE_CODE }}" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
-       {% load cache compressed i18n %}
-       {% load static from staticfiles %}
-    {% load catalogue_tags funding_tags reporting_stats sponsor_tags %}
-    {% load chunks %}
+    {% load compressed i18n %}
+    {% load static from staticfiles %}
+    {% load catalogue_tags funding_tags reporting_stats %}
     {% load piwik_tags %}
     {% load piwik_tags %}
+    {% load ssi_include ssi_csrf_token from ssify %}
+    {% load user_username user_is_staff from common_tags %}
     <head>
         <meta charset="utf-8">
         <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
     <head>
         <meta charset="utf-8">
         <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
         <meta property="og:title" content="{% block ogtitle %}{% endblock %}" />
         <meta property="og:type" content="{% block ogtype %}website{% endblock %}" />
         <meta property="og:image" content="{% block ogimage %}{{ FULL_STATIC_URL }}img/wiatrak.png{% endblock %}" />
         <meta property="og:title" content="{% block ogtitle %}{% endblock %}" />
         <meta property="og:type" content="{% block ogtype %}website{% endblock %}" />
         <meta property="og:image" content="{% block ogimage %}{{ FULL_STATIC_URL }}img/wiatrak.png{% endblock %}" />
-        <meta name="description" 
-            content="{% block metadescription %}Darmowe, opracowane, pełne teksty lektur, e-booki, audiobooki i pliki DAISY na wolnej licencji.{% endblock %}" />
+        <meta name="description" content="{% block metadescription %}Darmowe, opracowane, pełne teksty lektur, e-booki, audiobooki i pliki DAISY na wolnej licencji.{% endblock %}" />
         {% block ogextra %}{% endblock %}
         {% block ogextra %}{% endblock %}
-        
 
 
-        <title>{% block title %}{% trans "Wolne Lektury" %} :: 
-            {% block titleextra %}{% endblock %}{% endblock %}</title>
+        <title>{% block title %}{% trans "Wolne Lektury" %} :: {% block titleextra %}{% endblock %}{% endblock %}</title>
         <link rel="icon" href="{% static 'img/favicon.png' %}" type="image/png" />
         <link rel="icon" href="{% static 'img/favicon.png' %}" type="image/png" />
-        <link rel="search" type="application/opensearchdescription+xml" title="Wolne Lektury" 
-            href="{% static 'opensearch.xml' %}" />
+        <link rel="search" type="application/opensearchdescription+xml" title="Wolne Lektury" href="{% static 'opensearch.xml' %}" />
         {% compressed_css "main" %}
         {% block extrahead %}
         {% endblock %}
         {% compressed_css "main" %}
         {% block extrahead %}
         {% endblock %}
     <body id="{% block bodyid %}base{% endblock %}">
 
         {% block bodycontent %}
     <body id="{% block bodyid %}base{% endblock %}">
 
         {% block bodycontent %}
-        {% funding link=1 closeable=1 add_class="funding-top-header" %}
+
+        {% if not funding_no_show_current %}
+            {% current_offer as current_offer %}
+            {{ current_offer.if }}
+                {% ssi_include 'funding_top_bar' pk=current_offer %}
+            {{ current_offer.endif }}
+        {% endif %}
+
         <div id="header-wrapper">
         <header id="main">
             <a href="/" id="logo">
         <div id="header-wrapper">
         <header id="main">
             <a href="/" id="logo">
-                <img src="{% static 'img/logo-neon.png' %}"
-                    alt="Wolne Lektury" />
+                <img src="{% static 'img/logo-neon.png' %}" alt="Wolne Lektury" />
             </a>
 
             <p id="user-info">
             </a>
 
             <p id="user-info">
-                {% if user.is_authenticated %}
-                    {% trans "Welcome" %}, 
-                    <span class="hidden-box-wrapper">
+                {% user_username as user_username %}
+                {% user_is_staff as user_is_staff %}
+                {{ user_username.if }}{% trans "Welcome" %}, <span class="hidden-box-wrapper">
                         <a href="{% url 'user_settings' %}" class="hidden-box-trigger">
                         <a href="{% url 'user_settings' %}" class="hidden-box-trigger">
-                            <strong>{{ user.username }}</strong>
+                            <strong>{{ user_username }}</strong>
                         </a>
                         <span id="user-menu" class="hidden-box">
                             <a href="{% url 'account_set_password' %}">{% trans "Password" %}</a><br/>
                             <a href="{% url 'account_email' %}">{% trans "E-mail" %}</a><br/>
                             <a href="{% url 'socialaccount_connections' %}">{% trans "Social accounts" %}</a><br/>
                         </span>
                         </a>
                         <span id="user-menu" class="hidden-box">
                             <a href="{% url 'account_set_password' %}">{% trans "Password" %}</a><br/>
                             <a href="{% url 'account_email' %}">{% trans "E-mail" %}</a><br/>
                             <a href="{% url 'socialaccount_connections' %}">{% trans "Social accounts" %}</a><br/>
                         </span>
-                    </span>
-                    | <a href="{% url 'social_my_shelf' %}" id="user-shelves-link">{% trans "My shelf" %}</a>
-                    {% if user.is_staff %}
-                    | <a href="/admin/">{% trans "Administration" %}</a>
-                    {% endif %}
-                    | <a href="{% url 'logout' %}?next={% block logout %}{{ request.get_full_path }}{% endblock %}">{% trans "Logout" %}</a>
-                {% else %}
-                    <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 %}
+                    </span> | <a href="{% url 'social_my_shelf' %}" id="user-shelves-link">{% trans "My shelf" %}</a>
+                {{ user_username.endif }}
+                {{ user_is_staff.if }} | <a href="/admin/">{% trans "Administration" %}</a>
+                {{ user_is_staff.endif }}
+                {{ user_username.if }} | <a href="{% url 'logout' %}?next={% block logout %}{{ request.get_full_path }}{% endblock %}">{% trans "Logout" %}</a>
+                {{ user_username.else }}
+                    <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>
+                {{ user_username.endif }}
             </p>
 
             <p id="tagline">
             </p>
 
             <p id="tagline">
-                {% cache 60 tagline LANGUAGE_CODE %}
                     {% url 'book_list' as b %}
                     {% url 'infopage' 'prawa' as r %}
                         {% count_books book_count %}
                     {% url 'book_list' as b %}
                     {% url 'infopage' 'prawa' as r %}
                         {% count_books book_count %}
@@ -77,7 +73,6 @@
                     {% plural %}
                     <a href='{{b}}'>{{c}}</a> free readings you have <a href='{{r}}'>right to</a>
                     {% endblocktrans %}
                     {% plural %}
                     <a href='{{b}}'>{{c}}</a> free readings you have <a href='{{r}}'>right to</a>
                     {% endblocktrans %}
-                {% endcache %}
             </p>
 
             <form id="search-area" action="{% url 'search' %}">
             </p>
 
             <form id="search-area" action="{% url 'search' %}">
             <div id="lang-menu" class="hoverget">
                 <span id='lang-button' class='hoverclick'>
                     <span class="lang-flag">⚐</span>
             <div id="lang-menu" class="hoverget">
                 <span id='lang-button' class='hoverclick'>
                     <span class="lang-flag">⚐</span>
-                    <span class="label">{% trans "Language versions" %}</span>
+                    <span class="label"> {% trans "Language versions" %}</span>
                 </span>
                 <div id="lang-menu-items">
                 {% for lang in LANGUAGES %}
                     <form action="{% url 'django.views.i18n.set_language' %}" method="post">
                 </span>
                 <div id="lang-menu-items">
                 {% for lang in LANGUAGES %}
                     <form action="{% url 'django.views.i18n.set_language' %}" method="post">
-                    {% csrf_token %}
+                    {% ssi_csrf_token %}
                     <input type="hidden" name="language" value="{{ lang.0 }}" />
                     <input type="hidden" name="language" value="{{ lang.0 }}" />
-                    <button type="submit"
-                        lang="{{ lang.0 }}"
-                        class="{% ifequal lang.0 LANGUAGE_CODE %}active{% endifequal %}"
-                        >{{ lang.1 }}</button>
+                    <button type="submit" lang="{{ lang.0 }}" class="{% ifequal lang.0 LANGUAGE_CODE %}active{% endifequal %}">{{ lang.1 }}</button>
                     </form>
                 {% endfor %}
                 </div>
                     </form>
                 {% endfor %}
                 </div>
 
         <div id="footer-wrapper">
         <footer id="main">
 
         <div id="footer-wrapper">
         <footer id="main">
-            {% chunk 'footer' %}
+            {% ssi_include 'chunk' key='footer' %}
             {% block add_footer %}{% endblock %}
             {% block add_footer %}{% endblock %}
-            {% sponsor_page "footer" %}
+            {% ssi_include 'sponsor_page' name='footer' %}
         </footer>
         </div>
 
         </footer>
         </div>
 
 
 
         <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
 
 
         <script type="text/javascript" src="//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>
+        <script type="text/javascript">var LANGUAGE_CODE="{{ LANGUAGE_CODE }}"; var STATIC_URL="{{ STATIC_URL }}";</script>
         {% compressed_js "base" %}
 
         {% tracking_code %}
         {% compressed_js "base" %}
 
         {% tracking_code %}
         <script src="{% static "js/contrib/modernizr.custom.19652.js" %}"></script>
     </body>
 </html>
         <script src="{% static "js/contrib/modernizr.custom.19652.js" %}"></script>
     </body>
 </html>
+{% endspaceless %}
\ No newline at end of file
index eddf9f1..ab08a3e 100644 (file)
@@ -3,9 +3,17 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import template
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from django import template
+from ssify import ssi_variable
+from ssify.utils import ssi_vary_on_cookie
+
 register = template.Library()
 
 
 register = template.Library()
 
 
-@register.filter
-def build_absolute_uri(uri, request):
-    return request.build_absolute_uri(uri)
+@ssi_variable(register, patch_response=[ssi_vary_on_cookie])
+def user_username(request):
+    return request.user.username
+
+
+@ssi_variable(register, patch_response=[ssi_vary_on_cookie])
+def user_is_staff(request):
+    return request.user.is_staff
index 80156bf..1ccd24a 100644 (file)
@@ -2,33 +2,32 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # 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 datetime import date, datetime
 import feedparser
 
 import feedparser
 
+from django.conf import settings
 from django.contrib import auth
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
 from django.core.cache import cache
 from django.http import HttpResponse, HttpResponseRedirect
 from django.contrib import auth
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
 from django.core.cache import cache
 from django.http import HttpResponse, HttpResponseRedirect
-from django.shortcuts import render_to_response
-from django.template import RequestContext
+from django.shortcuts import render
 from django.utils.http import urlquote_plus
 from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.cache import never_cache
 
 from django.utils.http import urlquote_plus
 from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.cache import never_cache
 
-from django.conf import settings
 from ajaxable.utils import AjaxableFormView
 from ajaxable.utils import AjaxableFormView
-from catalogue.models import Book
 from ajaxable.utils import placeholdized
 from ajaxable.utils import placeholdized
-from social.templatetags.social_tags import choose_cite
+from catalogue.models import Book
+from ssify import ssi_included
 
 
 def main_page(request):
     last_published = Book.objects.exclude(cover_thumb='').filter(parent=None).order_by('-created_at')[:4]
 
 
 def main_page(request):
     last_published = Book.objects.exclude(cover_thumb='').filter(parent=None).order_by('-created_at')[:4]
-    cite = choose_cite(RequestContext(request))
 
 
-    return render_to_response("main_page.html", locals(),
-        context_instance=RequestContext(request))
+    return render(request, "main_page.html", {
+        'last_published': last_published,
+    })
 
 
 class LoginFormView(AjaxableFormView):
 
 
 class LoginFormView(AjaxableFormView):
@@ -119,11 +118,30 @@ def publish_plan(request):
                     })
         cache.set(cache_key, plan, 1800)
 
                     })
         cache.set(cache_key, plan, 1800)
 
-    return render_to_response("publish_plan.html", {'plan': plan},
-        context_instance=RequestContext(request))
+    return render(request, "publish_plan.html", {'plan': plan})
 
 
 @login_required
 def user_settings(request):
 
 
 @login_required
 def user_settings(request):
-    return render_to_response("user.html",
-        context_instance=RequestContext(request))
+    return render(request, "user.html")
+
+
+@ssi_included(use_lang=False, timeout=1800)
+def latest_blog_posts(request, feed_url=None, posts_to_show=5):
+    if feed_url is None:
+        feed_url = settings.LATEST_BLOG_POSTS
+    try:
+        feed = feedparser.parse(str(feed_url))
+        posts = []
+        for i in range(posts_to_show):
+            pub_date = feed['entries'][i].published_parsed
+            published = date(pub_date[0], pub_date[1], pub_date[2])
+            posts.append({
+                'title': feed['entries'][i].title,
+                'summary': feed['entries'][i].summary,
+                'link': feed['entries'][i].link,
+                'date': published,
+                })
+    except:
+        posts = []
+    return render(request, 'latest_blog_posts.html', {'posts': posts})
index cdab158..4d2d4ad 100644 (file)
@@ -1,23 +1,21 @@
--i http://py.mdrn.pl/simple/ 
+-i http://py.mdrn.pl/simple/
 
 # django
 Django>=1.7,<1.8
 fnpdjango>=0.1.15,<0.2
 
 # django
 Django>=1.7,<1.8
 fnpdjango>=0.1.15,<0.2
-South>=0.7 # migrations for django
 django-pipeline>=1.3,<1.4
 django-pagination>=1.0
 django-maintenancemode>=0.10
 django-piston==0.2.2.1.2
 django-pipeline>=1.3,<1.4
 django-pagination>=1.0
 django-maintenancemode>=0.10
 django-piston==0.2.2.1.2
-jsonfield>=0.9.20
+jsonfield>=0.9.22,<1.0
 django-picklefield
 django-modeltranslation==0.8b2
 django-picklefield
 django-modeltranslation==0.8b2
-django-allauth>=0.16,<0.17
+# django-allauth>=0.17,<0.18
+# django-allauth pre-0.18 version with Django 1.7 migrations
+-e git+git://github.com/pennersr/django-allauth.git@9cc09402d3dd768bc1221e0bb7a438d6295812f5#egg=django-allauth
 
 pytz
 
 
 pytz
 
-# Some contrib apps still need it
-simplejson
-
 django-honeypot
 django-uni-form
 
 django-honeypot
 django-uni-form
 
@@ -54,3 +52,4 @@ sunburnt
 django-getpaid>=1.6,<1.7
 httplib2
 Texml
 django-getpaid>=1.6,<1.7
 httplib2
 Texml
+django-ssify>=0.2.1,<0.3
index 58fbc42..4f61940 100644 (file)
@@ -26,17 +26,21 @@ TEMPLATE_CONTEXT_PROCESSORS = (
 )
 
 MIDDLEWARE_CLASSES = [
 )
 
 MIDDLEWARE_CLASSES = [
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'ssify.middleware.SsiMiddleware',
+    'django.middleware.cache.UpdateCacheMiddleware',
+    'ssify.middleware.PrepareForCacheMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'django.contrib.admindocs.middleware.XViewMiddleware',
     'pagination.middleware.PaginationMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'django.contrib.admindocs.middleware.XViewMiddleware',
     'pagination.middleware.PaginationMiddleware',
-    'django.middleware.locale.LocaleMiddleware',
+    'ssify.middleware.LocaleMiddleware',
     'maintenancemode.middleware.MaintenanceModeMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'fnpdjango.middleware.SetRemoteAddrFromXRealIP',
     'maintenancemode.middleware.MaintenanceModeMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'fnpdjango.middleware.SetRemoteAddrFromXRealIP',
+    'django.middleware.cache.FetchFromCacheMiddleware',
 ]
 
 ROOT_URLCONF = 'wolnelektury.urls'
 ]
 
 ROOT_URLCONF = 'wolnelektury.urls'
@@ -89,15 +93,13 @@ INSTALLED_APPS_CONTRIB = [
     'pipeline',
     'piston',
     'piwik',
     'pipeline',
     'piston',
     'piwik',
-    #'rosetta',
-    #'south',
     'sorl.thumbnail',
     'kombu.transport.django',
     'honeypot',
     'sorl.thumbnail',
     'kombu.transport.django',
     'honeypot',
-    #'django_nose',
     'fnpdjango',
     'getpaid',
     'getpaid.backends.payu',
     'fnpdjango',
     'getpaid',
     'getpaid.backends.payu',
+    'ssify',
 
     #allauth stuff
     'uni_form',
 
     #allauth stuff
     'uni_form',
index d4beab3..a9cc70f 100644 (file)
@@ -1,6 +1,3 @@
-from os import path
-from .paths import PROJECT_DIR
-
 CACHES = {
     'default': {
         'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
 CACHES = {
     'default': {
         'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
@@ -8,17 +5,14 @@ CACHES = {
             '127.0.0.1:11211',
         ]
     },
             '127.0.0.1:11211',
         ]
     },
-    'permanent': {
+    'ssify': {
         'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
         'TIMEOUT': None,
         'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
         'TIMEOUT': None,
+        'KEY_PREFIX': 'ssify',
         'LOCATION': [
             '127.0.0.1:11211',
         'LOCATION': [
             '127.0.0.1:11211',
-        ]
-    },
-    'api': {
-        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
-        'LOCATION': path.join(PROJECT_DIR, '../django_cache/'),
-        'KEY_PREFIX': 'api',
-        'TIMEOUT': 86400,
+        ],
     },
 }
     },
 }
+
+CACHE_MIDDLEWARE_SECONDS = 24 * 60 * 60
index a0bab7a..8830ebe 100644 (file)
@@ -1,6 +1,3 @@
-# seconds until a changes appears in the changes api
-API_WAIT = 10
-
 # limit number of filtering tags
 MAX_TAG_LIST = 6
 
 # limit number of filtering tags
 MAX_TAG_LIST = 6
 
@@ -16,3 +13,5 @@ CATALOGUE_CUSTOMPDF_RATE_LIMIT = '1/m'
 # set to 'new' or 'old' to skip time-consuming test
 # for TeX morefloats library version
 LIBRARIAN_PDF_MOREFLOATS = None
 # set to 'new' or 'old' to skip time-consuming test
 # for TeX morefloats library version
 LIBRARIAN_PDF_MOREFLOATS = None
+
+LATEST_BLOG_POSTS = "http://nowoczesnapolska.org.pl/feed/?cat=-135"
index abe872f..2dfd4d4 100644 (file)
@@ -2,8 +2,6 @@
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-import os
-
 from django.conf.urls import include, patterns, url
 from django.conf import settings
 from django.contrib import admin
 from django.conf.urls import include, patterns, url
 from django.conf import settings
 from django.contrib import admin
@@ -23,6 +21,11 @@ urlpatterns = patterns('wolnelektury_core.views',
     url(r'^uzytkownik/signup/$', wolnelektury_core.views.RegisterFormView(), name='register'),
     url(r'^uzytkownik/logout/$', 'logout_then_redirect', name='logout'),
     url(r'^uzytkownik/zaloguj-utworz/$', wolnelektury_core.views.LoginRegisterFormView(), name='login_register'),
     url(r'^uzytkownik/signup/$', wolnelektury_core.views.RegisterFormView(), name='register'),
     url(r'^uzytkownik/logout/$', 'logout_then_redirect', name='logout'),
     url(r'^uzytkownik/zaloguj-utworz/$', wolnelektury_core.views.LoginRegisterFormView(), name='login_register'),
+
+    # Includes.
+    url(r'^latests_blog_posts.html$',
+        wolnelektury_core.views.latest_blog_posts,
+        name='latest_blog_posts'),
 )
 
 urlpatterns += patterns('',
 )
 
 urlpatterns += patterns('',
@@ -38,7 +41,9 @@ urlpatterns += patterns('',
     url(r'^czekaj/', include('waiter.urls')),
     url(r'^wesprzyj/', include('funding.urls')),
     url(r'^ankieta/', include('polls.urls')),
     url(r'^czekaj/', include('waiter.urls')),
     url(r'^wesprzyj/', include('funding.urls')),
     url(r'^ankieta/', include('polls.urls')),
-    url(r'^biblioteki', include('libraries.urls')),
+    url(r'^biblioteki/', include('libraries.urls')),
+    url(r'^chunks/', include('chunks.urls')),
+    url(r'^sponsors/', include('sponsors.urls')),
 
     # Admin panel
     url(r'^admin/catalogue/book/import$', 'catalogue.views.import_book', name='import_book'),
 
     # Admin panel
     url(r'^admin/catalogue/book/import$', 'catalogue.views.import_book', name='import_book'),
@@ -76,9 +81,3 @@ urlpatterns += patterns('',
     url(r'^wolontariat/$', RedirectView.as_view(
         url='/info/mozesz-nam-pomoc/')),
 )
     url(r'^wolontariat/$', RedirectView.as_view(
         url='/info/mozesz-nam-pomoc/')),
 )
-    
-
-if 'rosetta' in settings.INSTALLED_APPS:
-    urlpatterns += patterns('',
-        url(r'^rosetta/', include('rosetta.urls')),
-    )