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.
{% 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 %}">
-{% csrf_token %}
+{% ssi_csrf_token %}
{% if honeypot %}
{% load honeypot %}
{% render_honeypot_field %}
--- /dev/null
+# -*- 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')
+
# 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
-from django.conf import settings
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.utils.timezone import utc
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 wolnelektury.utils import tz
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)()
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))
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
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:
- tags, ancestors_ = read_tags(tags, allowed=book_tag_categories)
+ tags, _ancestors = read_tags(tags, allowed=book_tag_categories)
except ValueError:
return rc.NOT_FOUND
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). """
+ 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]
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')
{% extends "base.html" %}
{% load i18n %}
-{% load common_tags %}
+{% load build_absolute_uri from fnp_common %}
{% block title %}{% trans "WolneLektury.pl API" %}{% endblock %}
@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):
- 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')
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):
- 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):
- 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.')
-class TagTests(TestCase):
+class TagTests(ApiTest):
def setUp(self):
self.tag = Tag.objects.create(category='author', slug='joe', name='Joe')
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):
- 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.')
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")
-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)
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'),
) + 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'),
-
# books by collections
url(r'^collections/$', collection_list_resource, name="api_collections"),
url(r'^collections/(?P<slug>[^/]+)/$', collection_resource, name="api_collection"),
from catalogue.utils import AppSettings
+default_app_config = 'catalogue.apps.CatalogueConfig'
+
+
class Settings(AppSettings):
"""Default settings for catalogue app."""
DEFAULT_LANGUAGE = u'pol'
--- /dev/null
+# -*- 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
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
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"])
-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.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('''
- 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))
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']:
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 = [
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',
- ),
]
--- /dev/null
+# -*- 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),
+ ]
--- /dev/null
+# -*- 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',
+ ),
+ ]
from catalogue.models.book import Book
from catalogue.models.collection import Collection
from catalogue.models.source import Source
-from catalogue.models.listeners import *
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.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 newtagging import managers
bofh_storage = BofhFileSystemStorage()
-permanent_cache = caches['permanent']
-
def _cover_upload_to(i, n):
return 'book/cover/%s.jpg' % i.slug
html_built = django.dispatch.Signal()
published = django.dispatch.Signal()
+ short_html_url_name = 'catalogue_book_short'
+
class AlreadyExists(Exception):
pass
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) # ???
- 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
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')
child.parent = None
child.parent_number = 0
child.save()
- tasks.fix_tree_tags.delay(child)
if old_cover:
notify_cover_changed.append(child)
- cls.fix_tree_tags()
+ cls.repopulate_ancestors()
# No saves beyond this point.
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
- def fix_tree_tags(cls):
+ def repopulate_ancestors(cls):
"""Fixes the ancestry cache."""
# TODO: table names
with transaction.atomic():
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.
# 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 ssify import flush_ssi_includes
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)
+
+ 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])
#
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.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
-
-
-permanent_cache = caches['permanent']
+from ssify import flush_ssi_includes
class Fragment(models.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')
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 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
+ ])
+++ /dev/null
-# -*- 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()
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
# 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.dispatch import Signal
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.
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
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
--- /dev/null
+# -*- 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]])
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
{% extends "catalogue/book_list.html" %}
{% load i18n %}
{% load catalogue_tags %}
-{% load chunks %}
+{% load ssi_include from ssify %}
{% block bodyid %}book-a-list{% endblock %}
{% block book_list_header %}{% trans "Listing of all audiobooks" %}{% endblock %}
{% block book_list_info %}
-{% chunk 'audiobook-list' %}
+{% ssi_include 'chunk' key='audiobook-list' %}
{% endblock %}
{% 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 body %}
-{% book_wide book %}
+{% ssi_include 'catalogue_book_wide' pk=book.pk %}
{% work_list book_children %}
<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 %}
{% 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 %}
+{% 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 %}
- {% chunk 'book-list' %}
+ {% ssi_include 'chunk' key='book-list' %}
{% endblock %}
</div></div>
{% endblock %}
</div>
<a id="book-list-up" href="#top">{% trans "↑ top ↑" %}</a>
+
+{% endspaceless %}
{% endblock %}
+{% 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 %}
- <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 %}
</div>
</div>
-
-
+{% endspaceless %}
\ No newline at end of file
-{% 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 %}
{% endfor %}
</div>
-{% endblock %}
+<div style="clear: right"></div>
+
+</div>
+{% endspaceless %}
\ No newline at end of file
+{% spaceless %}
{% 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="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" >
- <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 %}'>
- {% csrf_token %}
+ {% ssi_csrf_token %}
<button type='submit'>☆</button>
</form>
</div>
<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 %}
- <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 %}
{% endspaceless %}
</div>
</div>
- {% shelf_tags book %}
+ {% book_shelf_tags book.pk %}
<ul class="book-box-tools">
<li class="book-box-read">
<div class="clearboth"></div>
</div>
</div>
+{% endspaceless %}
\ No newline at end of file
{% extends "catalogue/viewer_base.html" %}
{% load i18n %}
-{% load catalogue_tags %}
+{% load catalogue_tags ssify %}
{% load thumbnail %}
<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">
<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 %}
</div>
<div class="box" id="book-short">
- {% book_short book %}
+ {% ssi_include 'catalogue_book_short' pk=book.pk %}
</div>
{% endblock footer %}
{% 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 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">
{% extends "catalogue/book_list.html" %}
{% load i18n %}
-{% load chunks %}
+{% load ssi_include from ssify %}
{% block bodyid %}book-a-list{% endblock %}
{% 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 %}
+++ /dev/null
-<ol>
-{% for post in posts %}
- <li><a href="{{ post.link }}">{{ post.title }}</a></li>
-{% endfor %}
-</ol>
\ No newline at end of file
+{% spaceless %}
{% load i18n static %}
<a id="show-menu" href="{% url 'catalogue' %}">
<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>
{% 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">
- <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">
- <a href="{% url 'audiobook_list' %}" class="menu">
- {% trans "Audiobooks" %}</a>
+ <a href="{% url 'audiobook_list' %}" class="menu">{% trans "Audiobooks" %}</a>
</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>
</ul>
+
+{% endspaceless %}
\ No newline at end of file
-{% load book_mini from catalogue_tags %}
-
{% spaceless %}
+{% load catalogue_random_book from catalogue_tags %}
+{% load ssi_include from ssify %}
+
{% 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 %}
+
+{% 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
{% 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 %}
<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>
</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 %}
</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 %}
+{% 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>
- {% if choices %}
+ {% if choices %}
{% for tag in tags %}
<li><a href="{% catalogue_url choices tag %}">{{ tag }}{% if tag.count %} ({{ tag.count }}){% endif %}</a></li>
{% endfor %}
{% endif %}
</ul>
{% endif %}
+
+{% endspaceless %}
\ No newline at end of file
{% extends "base.html" %}
{% load i18n %}
{% load catalogue_tags switch_tag social_tags %}
+{% load ssi_include from ssify %}
{% block titleextra %}{% title_from_tags tags %}{% endblock %}
{% 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 %}
+{% spaceless %}
+
{% 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 %}
-{% spaceless %}
+
<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>
-{% endspaceless %}
+
{% paginate %}
+
+{% endspaceless %}
# 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 django.core.cache import cache
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 catalogue.utils import split_tags
+from ssify import ssi_variable
from catalogue.models import Book, BookMedia, Fragment, Tag, Source
from catalogue.constants import LICENSES
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:
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/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
- 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 {
+ 'request': context['request'],
'books': related,
- 'random_related': random_related,
+ 'random': random,
+ 'random_excluded': random_excluded,
}
]}
-@register.simple_tag
-def tag_url(category, slug):
- return Tag.create_url(category, slug)
-
-
@register.simple_tag
def download_audio(book, daisy=True):
links = []
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
@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={
- 'api': {'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):
"""
from catalogue.tests.search import *
from catalogue.tests.tags import *
from catalogue.tests.templatetags import *
+from .test_visit import *
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 """
""" 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 """
'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')
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']],
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')
+ 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 """
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,
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):
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)])
--- /dev/null
+# -*- 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))
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('',
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 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
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:
- 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
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.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.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 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 suggest.forms import PublishingSuggestForm
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)
-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))
+@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',
- cache_key='catalogue.book_list',
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 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',
- cache_key='catalogue.daisy_list')
+ )
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,
- cache_key='catalogue.collection:%s' % coll.slug,
context={'collection': coll})
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
+ 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']
areas = PictureArea.tagged.with_all(fragment_tags)
if shelf_tags:
- # FIXME: book tags here
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
- object_queries.insert(0, areas)
- objects = MultiQuerySet(*object_queries)
+ objects = MultiQuerySet(areas, fragments)
else:
+ all_books = models.Book.tagged.with_all(tags)
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')
-
- 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'))
-
- if not objects:
- objects = models.Book.objects.none()
-
return render_to_response('catalogue/tagged_object_list.html',
{
'object_list': objects,
'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))
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 _get_result_type(match):
if isinstance(match, models.Book) or isinstance(match, pdcounter_models.BookStub):
- type = 'book'
+ match_type = 'book'
else:
- type = match.category
- return type
+ match_type = match.category
+ return match_type
def books_starting_with(prefix):
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
+ })
--- /dev/null
+# -*- 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),
+ ),
+ ]
-from django.core.cache import cache
+from django.conf import settings
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):
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',)
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)
- cache.delete(self.cache_key(self.key))
+ self.flush_includes()
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)
def __unicode__(self):
return self.key
-
+++ /dev/null
-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 ''
--- /dev/null
+# -*- 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'),
+)
--- /dev/null
+# -*- 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)
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)
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
+ # The 'WL fund' list needs to be updated.
+ caches[settings.CACHE_MIDDLEWARE_ALIAS].clear()
+ offer.flush_includes()
offer.notify_end()
current = Offer.current()
#
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.conf import settings
-from django.template.loader import render_to_string
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 ssify import flush_ssi_includes
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
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
+ 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 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,
--- /dev/null
+{% 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 %}
--- /dev/null
+{% 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 }} zł</td>
+ <td>
+ {% for perk in funding.perks.all %}
+ {{ perk.name }}{% if not forloop.last %},{% endif %}
+ {% endfor %}
+ </td>
+{% endfor %}
+</table>
+
+{% endspaceless %}{% paginate %}
+
--- /dev/null
+{% 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 %}
--- /dev/null
+{% 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 %}
{% 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 ssi_include from ssify %}
{% block titleextra %}{{ object }}{% endblock %}
<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="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. →</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>
<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 }} zł</td>
- <td>
- {% 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 %}
+++ /dev/null
-{% 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 %}
+++ /dev/null
-{% 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 %}
+++ /dev/null
-{% 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 %}
# 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()
-@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)
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'^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'),
)
# 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.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 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
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
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,
+ })
{% extends "base.html" %}
{% load i18n %}
-{% load chunks %}
{% block titleextra %}{{ page.title }}{% endblock %}
# 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'),
- 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'),
)
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):
"""
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):
"""
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 . 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():
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():
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 ssify import flush_ssi_includes
from picture import tasks
from StringIO import StringIO
import jsonfield
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
-permanent_cache = caches['permanent']
-
picture_storage = FileSystemStorage(location=path.join(
settings.MEDIA_ROOT, 'pictures'),
base_url=settings.MEDIA_URL + "pictures/")
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()
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
-
- 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):
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
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)
- 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
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
- 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()))
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 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
+ ])
{% 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 %}
{% extends "base.html" %}
{% load i18n %}
-{% load chunks %}
-{% load picture_tags %}
-{% load thumbnail %}
{% load static %}
+{% load ssi_include from ssify %}
{% block bodyid %}picture-list{% endblock %}
<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">
<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>
{% extends "picture/picture_short.html" %}
{% load i18n %}
{% load picture_tags thumbnail %}
-{% load cite_promo from social_tags %}
{% block box-class %}book-wide-box{% endblock %}
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({
#
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 picture.models import Picture
+from picture.models import Picture, PictureArea
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):
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())
{% load i18n %}
+{% load ssi_csrf_token from ssify %}
{% if poll %}
{% if voted_already %}
{% 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>
# 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.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
-\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)
- if request.method == 'POST':\r
+ if request.method == 'POST':
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()
- 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)
- context['poll'] = poll\r
+ context['poll'] = poll
context['voted_already'] = poll.voted(request.session)
return render_to_response('polls/poll.html', context)
# 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
snip = snip.replace("\n", "<br />").replace('---', '—')
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 []
+ }
#
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 ssify import flush_ssi_includes
from catalogue.models import Book
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])
--- /dev/null
+{% 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
+{% spaceless %}
+
{% 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 %}
+
<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>
-{% else %}
- {% if fallback %}
- {% load fragment_promo from catalogue_tags %}
- {% fragment_promo ctx %}
- {% endif %}
+
{% endif %}
+
+
+{% if main %}
+ </section>
+{% endif %}
+
+{% endspaceless %}
\ No newline at end of file
{% 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 %}">
-{% 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 %}">
-{% csrf_token %}
+{% ssi_csrf_token %}
<ol>
<div id="id___all__"></div>
{{ form.as_ul }}
-{% 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>
-{% endif %}
+
+{% endspaceless %}
\ No newline at end of file
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()
-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:
- 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):
- 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:
- cites = cites_for_tags(ctx)
+ cites = Cite.objects.all()
stickies = cites.filter(sticky=True)
count = stickies.count()
if count:
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', 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)()
# 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'),
- 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'),
+ # 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'),
# 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.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
-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):
from ajaxable.utils import AjaxableFormView
from catalogue.models import Book
+from ssify import ssi_included
from social import forms
+from .models import Cite
from social.utils import get_set, likes, set_sets
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,
+ })
from jsonfield import JSONField
from django.core.files.base import ContentFile
+from ssify import flush_ssi_includes
THUMB_WIDTH = 120
THUMB_HEIGHT = 120
'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
+++ /dev/null
-# -*- 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)
--- /dev/null
+# -*- 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'),
+)
--- /dev/null
+# -*- 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)
{% 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">
-{% 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>
+# -*- 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'
--- /dev/null
+# -*- 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
--- /dev/null
+# -*- 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()
$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) {
{% 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 %}">
-{% csrf_token %}
+{% ssi_csrf_token %}
<ol>
<div id="id_{% if form_prefix %}{{ form_prefix }}-{% endif %}__all__"></div>
{{ form.as_ul }}
{% extends "auth/login.html" %}
{% load i18n %}
{% load honeypot %}
+{% load ssi_csrf_token from ssify %}
{% block extra %}
<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>
--- /dev/null
+{% spaceless %}
+
+<ol>
+{% for post in posts %}
+ <li><a href="{{ post.link }}">{{ post.title }}</a></li>
+{% endfor %}
+</ol>
+
+{% endspaceless %}
\ No newline at end of file
{% 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 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">
- {% chunk "promo" %}
+ {% ssi_include 'chunk' key='promo' %}
</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 %}
- {% book_mini book %}
+ {% ssi_include 'catalogue_book_mini' pk=book.pk %}
{% endfor %}
- {% endcache %}
</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">
- <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-->
- <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>
<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 %}
- {% endcache %}
</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>
- <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>
- {% 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 %}
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
- <a href="?page={{ page_obj.previous_page_number }}{{ getvars }}" class="prev">‹‹ {% trans "previous" %}</a>
+ <a href="?page={{ page_obj.previous_page_number }}{{ getvars }}" class="prev">‹‹ {% trans "previous" %} </a>
{% else %}
- <span class="disabled prev">‹‹ {% trans "previous" %}</span>
+ <span class="disabled prev">‹‹ {% trans "previous" %} </span>
{% 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 %}
- <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 %}
- <a href="?page={{ page_obj.next_page_number }}{{ getvars }}" class="next">{% trans "next" %} ››</a>
+ <a href="?page={{ page_obj.next_page_number }}{{ getvars }}" class="next"> {% trans "next" %} ››</a>
{% else %}
- <span class="disabled next">{% trans "next" %} ››</span>
+ <span class="disabled next"> {% trans "next" %} ››</span>
{% endif %}
</div>
{% endif %}
<!DOCTYPE html>
+{% spaceless %}
<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 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">
<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 %}
-
- <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="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 %}
<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">
- <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">
- {% 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">
- <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>
- </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">
- {% cache 60 tagline LANGUAGE_CODE %}
{% url 'book_list' as b %}
{% url 'infopage' 'prawa' as r %}
{% count_books book_count %}
{% 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' %}">
<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">
- {% csrf_token %}
+ {% ssi_csrf_token %}
<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>
<div id="footer-wrapper">
<footer id="main">
- {% chunk 'footer' %}
+ {% ssi_include 'chunk' key='footer' %}
{% block add_footer %}{% endblock %}
- {% sponsor_page "footer" %}
+ {% ssi_include 'sponsor_page' name='footer' %}
</footer>
</div>
<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 %}
<script src="{% static "js/contrib/modernizr.custom.19652.js" %}"></script>
</body>
</html>
+{% endspaceless %}
\ No newline at end of file
# 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.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
# 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
+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.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.conf import settings
from ajaxable.utils import AjaxableFormView
-from catalogue.models import Book
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]
- 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):
})
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):
- 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})
--i http://py.mdrn.pl/simple/
+-i http://py.mdrn.pl/simple/
# 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
-jsonfield>=0.9.20
+jsonfield>=0.9.22,<1.0
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
-# Some contrib apps still need it
-simplejson
-
django-honeypot
django-uni-form
django-getpaid>=1.6,<1.7
httplib2
Texml
+django-ssify>=0.2.1,<0.3
)
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.csrf.CsrfViewMiddleware',
'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',
+ 'django.middleware.cache.FetchFromCacheMiddleware',
]
ROOT_URLCONF = 'wolnelektury.urls'
'pipeline',
'piston',
'piwik',
- #'rosetta',
- #'south',
'sorl.thumbnail',
'kombu.transport.django',
'honeypot',
- #'django_nose',
'fnpdjango',
'getpaid',
'getpaid.backends.payu',
+ 'ssify',
#allauth stuff
'uni_form',
-from os import path
-from .paths import PROJECT_DIR
-
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'127.0.0.1:11211',
]
},
- 'permanent': {
+ 'ssify': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'TIMEOUT': None,
+ 'KEY_PREFIX': 'ssify',
'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
-# seconds until a changes appears in the changes api
-API_WAIT = 10
-
# limit number of filtering tags
MAX_TAG_LIST = 6
# 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"
# 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
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('',
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'),
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')),
- )