Add Book.ancestor m2m.
authorRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Wed, 10 Sep 2014 07:20:52 +0000 (09:20 +0200)
committerRadek Czajka <radekczajka@nowoczesnapolska.org.pl>
Wed, 10 Sep 2014 07:20:52 +0000 (09:20 +0200)
Remove *.*_counter, *.related_info, Tag.*_count model fields.

33 files changed:
apps/api/handlers.py
apps/api/management/commands/mobileinit.py
apps/catalogue/fields.py
apps/catalogue/helpers.py [new file with mode: 0644]
apps/catalogue/management/commands/checkintegrity.py
apps/catalogue/migrations/0002_book_ancestor.py [new file with mode: 0644]
apps/catalogue/models/book.py
apps/catalogue/models/listeners.py
apps/catalogue/models/tag.py
apps/catalogue/tasks.py
apps/catalogue/templates/catalogue/book_short.html
apps/catalogue/templates/catalogue/inline_tag_list.html
apps/catalogue/templates/catalogue/search_multiple_hits.html
apps/catalogue/templates/catalogue/tag_list.html
apps/catalogue/templatetags/catalogue_tags.py
apps/catalogue/tests/book_import.py
apps/catalogue/tests/tags.py
apps/catalogue/utils.py
apps/catalogue/views.py
apps/funding/models.py
apps/lesmianator/models.py
apps/newtagging/models.py
apps/opds/views.py
apps/picture/migrations/0002_remove_picture__related_info.py [new file with mode: 0644]
apps/picture/models.py
apps/picture/templates/picture/picture_wide.html
apps/picture/templatetags/picture_tags.py
apps/picture/tests/picture_import.py
apps/reporting/utils.py
apps/search/index.py
apps/search/templatetags/search_tags.py
apps/social/templatetags/social_tags.py
apps/social/utils.py

index e1792af..a3a5ce5 100644 (file)
@@ -19,7 +19,6 @@ 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 api.models import Deleted
 from catalogue.forms import BookImportForm
 from catalogue.models import Book, Tag, BookMedia, Fragment, Collection
-from catalogue.utils import related_tag_name
 from picture.models import Picture
 from picture.forms import PictureImportForm
 from wolnelektury.utils import tz
 from picture.models import Picture
 from picture.forms import PictureImportForm
 from wolnelektury.utils import tz
@@ -54,10 +53,11 @@ def read_tags(tags, allowed):
     :raises: ValueError when tags can't be found
     """
     if not tags:
     :raises: ValueError when tags can't be found
     """
     if not tags:
-        return []
+        return [], []
 
     tags = tags.strip('/').split('/')
     real_tags = []
 
     tags = tags.strip('/').split('/')
     real_tags = []
+    books = []
     while tags:
         category = tags.pop(0)
         slug = tags.pop(0)
     while tags:
         category = tags.pop(0)
         slug = tags.pop(0)
@@ -70,15 +70,14 @@ def read_tags(tags, allowed):
         if not category in allowed:
             raise ValueError('Category not allowed.')
 
         if not category in allowed:
             raise ValueError('Category not allowed.')
 
-        # !^%@#$^#!
         if category == 'book':
         if category == 'book':
-            slug = 'l-' + slug
+            books.append(Book.objects.get(slug=slug))
 
         try:
             real_tags.append(Tag.objects.get(category=category, slug=slug))
         except Tag.DoesNotExist:
             raise ValueError('Tag not found')
 
         try:
             real_tags.append(Tag.objects.get(category=category, slug=slug))
         except Tag.DoesNotExist:
             raise ValueError('Tag not found')
-    return real_tags
+    return real_tags, books
 
 
 # RESTful handlers
 
 
 # RESTful handlers
@@ -186,7 +185,7 @@ class AnonymousBooksHandler(AnonymousBaseHandler, BookDetails):
              are returned.
         """
         try:
              are returned.
         """
         try:
-            tags = read_tags(tags, allowed=book_tag_categories)
+            tags, ancestors_ = read_tags(tags, allowed=book_tag_categories)
         except ValueError:
             return rc.NOT_FOUND
 
         except ValueError:
             return rc.NOT_FOUND
 
@@ -247,7 +246,7 @@ def _tags_getter(category):
 def _tag_getter(category):
     @classmethod
     def get_tag(cls, book):
 def _tag_getter(category):
     @classmethod
     def get_tag(cls, book):
-        return ", ".join(related_tag_name(t) for t in book.related_info()['tags'].get(category, []))
+        return ', '.join(tag.name for tag in book.tags.filter(category=category))
     return get_tag
 for plural, singular in category_singular.items():
     setattr(BookDetails, plural, _tags_getter(singular))
     return get_tag
 for plural, singular in category_singular.items():
     setattr(BookDetails, plural, _tags_getter(singular))
@@ -369,7 +368,7 @@ class TagsHandler(BaseHandler, TagDetails):
         except KeyError, e:
             return rc.NOT_FOUND
 
         except KeyError, e:
             return rc.NOT_FOUND
 
-        tags = Tag.objects.filter(category=category_sng).exclude(book_count=0)
+        tags = Tag.objects.filter(category=category_sng).exclude(items=None)
         if tags.exists():
             return tags
         else:
         if tags.exists():
             return tags
         else:
@@ -433,7 +432,7 @@ class FragmentsHandler(BaseHandler, FragmentDetails):
 
         """
         try:
 
         """
         try:
-            tags = read_tags(tags, allowed=self.categories)
+            tags, ancestors = read_tags(tags, allowed=self.categories)
         except ValueError:
             return rc.NOT_FOUND
         fragments = Fragment.tagged.with_all(tags).select_related('book')
         except ValueError:
             return rc.NOT_FOUND
         fragments = Fragment.tagged.with_all(tags).select_related('book')
@@ -514,7 +513,7 @@ class CatalogueHandler(BaseHandler):
                 obj[field] = book.get_absolute_url()
 
             elif field == 'tags':
                 obj[field] = book.get_absolute_url()
 
             elif field == 'tags':
-                obj[field] = [t.id for t in book.tags.exclude(category__in=('book', 'set')).iterator()]
+                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 == 'author':
                 obj[field] = ", ".join(t.name for t in book.tags.filter(category='author').iterator())
@@ -627,13 +626,16 @@ class CatalogueHandler(BaseHandler):
 
         for tag in Tag.objects.filter(category__in=categories,
                     changed_at__gte=since,
 
         for tag in Tag.objects.filter(category__in=categories,
                     changed_at__gte=since,
-                    changed_at__lt=until).iterator():
-            # only serve non-empty tags
-            if tag.book_count:
-                tag_d = cls.tag_dict(tag, fields)
-                updated.append(tag_d)
-            elif tag.created_at < since:
-                deleted.append(tag.id)
+                    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
 
         if updated:
             changes['updated'] = updated
 
index 3d88a77..57b41aa 100755 (executable)
@@ -24,7 +24,7 @@ class Command(BaseCommand):
         for b in Book.objects.all():
             add_book(db, b)
         for t in Tag.objects.exclude(
         for b in Book.objects.all():
             add_book(db, b)
         for t in Tag.objects.exclude(
-                category__in=('book', 'set', 'theme')).exclude(book_count=0):
+                category__in=('book', 'set', 'theme')).exclude(items=None):
             # only add non-empty tags
             add_tag(db, t)
         db.commit()
             # only add non-empty tags
             add_tag(db, t)
         db.commit()
index 8ed628e..d5cec2e 100644 (file)
@@ -149,7 +149,6 @@ class BuildHtml(BuildEbook):
         if html_output:
             meta_tags = list(book.tags.filter(
                 category__in=('author', 'epoch', 'genre', 'kind')))
         if html_output:
             meta_tags = list(book.tags.filter(
                 category__in=('author', 'epoch', 'genre', 'kind')))
-            book_tag = book.book_tag()
 
             lang = book.language
             lang = LANGUAGES_3TO2.get(lang, lang)
 
             lang = book.language
             lang = LANGUAGES_3TO2.get(lang, lang)
@@ -162,13 +161,6 @@ class BuildHtml(BuildEbook):
                 fieldfile.field.attname: fieldfile
             })
 
                 fieldfile.field.attname: fieldfile
             })
 
-            # get ancestor l-tags for adding to new fragments
-            ancestor_tags = []
-            p = book.parent
-            while p:
-                ancestor_tags.append(p.book_tag())
-                p = p.parent
-
             # Extract fragments
             closed_fragments, open_fragments = html.extract_fragments(fieldfile.path)
             for fragment in closed_fragments.values():
             # Extract fragments
             closed_fragments, open_fragments = html.extract_fragments(fieldfile.path)
             for fragment in closed_fragments.values():
@@ -211,11 +203,9 @@ class BuildHtml(BuildEbook):
                         book=book, text=text, short_text=short_text)
 
                 new_fragment.save()
                         book=book, text=text, short_text=short_text)
 
                 new_fragment.save()
-                new_fragment.tags = set(meta_tags + themes + [book_tag] + ancestor_tags)
-            book.fix_tree_tags()
+                new_fragment.tags = set(meta_tags + themes)
             book.html_built.send(sender=book)
             return True
             book.html_built.send(sender=book)
             return True
-        book.fix_tree_tags()
         return False
 
 @BuildEbook.register('cover_thumb')
         return False
 
 @BuildEbook.register('cover_thumb')
diff --git a/apps/catalogue/helpers.py b/apps/catalogue/helpers.py
new file mode 100644 (file)
index 0000000..ddfa482
--- /dev/null
@@ -0,0 +1,253 @@
+from django.db import connection
+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
+
+
+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)
+
+
+
+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
+
+
+def get_fragment_related_tags(tags):
+    tag_fields = ', '.join(
+        'T.%s' % (connection.ops.quote_name(field.column))
+        for field in Tag._meta.fields)
+
+    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
+
+            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
+
+        ) 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,
+        ))
index 0892a78..51fcd94 100644 (file)
@@ -42,20 +42,20 @@ class Command(BaseCommand):
                             print "To resolve: republish parent book."
                             print
 
                             print "To resolve: republish parent book."
                             print
 
-                # Check for parent l-tags.
+                # Check for ancestry.
                 parents = []
                 parent = book.parent
                 while parent:
                     parents.append(parent)
                     parent = parent.parent
                 parents = []
                 parent = book.parent
                 while parent:
                     parents.append(parent)
                     parent = parent.parent
-                ltags = [b.book_tag() for b in parents]
-                if set(ltags) != set(book.tags.filter(category='book')):
+                ancestors = list(book.ancestor.all())
+                if set(ancestors) != set(parents):
                     if options['verbose']:
                     if options['verbose']:
-                        print "Wrong book tags for book:", book
-                        print "Is:       ", ", ".join(sorted(t.slug for t in book.tags.filter(category='book')))
-                        print "Should be:", ", ".join(sorted(t.slug for t in ltags))
+                        print "Wrong ancestry for book:", book
+                        print "Is:       ", ", ".join(ancestors)
+                        print "Should be:", ", ".join(parents)
                     if not options['dry_run']:
                     if not options['dry_run']:
-                        book.tags = ltags + list(book.tags.exclude(category='book'))
+                        book.fix_tree_tags()
                         if options['verbose']:
                             print "Fixed."
                     if options['verbose']:
                         if options['verbose']:
                             print "Fixed."
                     if options['verbose']:
diff --git a/apps/catalogue/migrations/0002_book_ancestor.py b/apps/catalogue/migrations/0002_book_ancestor.py
new file mode 100644 (file)
index 0000000..4aa5828
--- /dev/null
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+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 = [
+        ('catalogue', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='book',
+            name='ancestor',
+            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',
+        ),
+    ]
index 8f5f107..37d9b71 100644 (file)
@@ -3,10 +3,11 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from collections import OrderedDict
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from collections import OrderedDict
+from random import randint
 import re
 from django.conf import settings
 from django.core.cache import caches
 import re
 from django.conf import settings
 from django.core.cache import caches
-from django.db import models
+from django.db import connection, models, transaction
 from django.db.models import permalink
 import django.dispatch
 from django.contrib.contenttypes.fields import GenericRelation
 from django.db.models import permalink
 import django.dispatch
 from django.contrib.contenttypes.fields import GenericRelation
@@ -17,7 +18,7 @@ from fnpdjango.storage import BofhFileSystemStorage
 from catalogue import constants
 from catalogue.fields import EbookField
 from catalogue.models import Tag, Fragment, BookMedia
 from catalogue import constants
 from catalogue.fields import EbookField
 from catalogue.models import Tag, Fragment, BookMedia
-from catalogue.utils import create_zip, split_tags, related_tag_name
+from catalogue.utils import create_zip, split_tags
 from catalogue import app_settings
 from catalogue import tasks
 from newtagging import managers
 from catalogue import app_settings
 from catalogue import tasks
 from newtagging import managers
@@ -72,8 +73,8 @@ class Book(models.Model):
 
     parent = models.ForeignKey('self', blank=True, null=True,
         related_name='children')
 
     parent = models.ForeignKey('self', blank=True, null=True,
         related_name='children')
-
-    _related_info = jsonfield.JSONField(blank=True, null=True, editable=False)
+    ancestor = models.ManyToManyField('self', blank=True, null=True,
+        editable=False, related_name='descendant', symmetrical=False)
 
     objects  = models.Manager()
     tagged   = managers.ModelTaggedItemManager(Tag)
 
     objects  = models.Manager()
     tagged   = managers.ModelTaggedItemManager(Tag)
@@ -127,18 +128,6 @@ class Book(models.Model):
     def language_name(self):
         return dict(settings.LANGUAGES).get(self.language_code(), "")
 
     def language_name(self):
         return dict(settings.LANGUAGES).get(self.language_code(), "")
 
-    def book_tag_slug(self):
-        return ('l-' + self.slug)[:120]
-
-    def book_tag(self):
-        slug = self.book_tag_slug()
-        book_tag, created = Tag.objects.get_or_create(slug=slug, category='book')
-        if created:
-            book_tag.name = self.title[:50]
-            book_tag.sort_key = self.title.lower()
-            book_tag.save()
-        return book_tag
-
     def has_media(self, type_):
         if type_ in Book.formats:
             return bool(getattr(self, "%s_file" % type_))
     def has_media(self, type_):
         if type_ in Book.formats:
             return bool(getattr(self, "%s_file" % type_))
@@ -167,7 +156,6 @@ class Book(models.Model):
         if self.id is None:
             return
 
         if self.id is None:
             return
 
-        type(self).objects.filter(pk=self.pk).update(_related_info=None)
         # Fragment.short_html relies on book's tags, so reset it here too
         for fragm in self.fragments.all().iterator():
             fragm.reset_short_html()
         # Fragment.short_html relies on book's tags, so reset it here too
         for fragm in self.fragments.all().iterator():
             fragm.reset_short_html()
@@ -336,6 +324,8 @@ class Book(models.Model):
             if old_cover:
                 notify_cover_changed.append(child)
 
             if old_cover:
                 notify_cover_changed.append(child)
 
+        cls.fix_tree_tags()
+
         # No saves beyond this point.
 
         # Build cover.
         # No saves beyond this point.
 
         # Build cover.
@@ -362,43 +352,38 @@ class Book(models.Model):
         cls.published.send(sender=book)
         return book
 
         cls.published.send(sender=book)
         return book
 
-    def fix_tree_tags(self):
-        """Fixes the l-tags on the book's subtree.
-
-        Makes sure that:
-        * the book has its parents book-tags,
-        * its fragments have the book's and its parents book-tags,
-        * runs those for every child book too,
-        * touches all relevant tags,
-        * resets tag and theme counter on the book and its ancestry.
-        """
-        def fix_subtree(book, parent_tags):
-            affected_tags = set(book.tags)
-            book.tags = list(book.tags.exclude(category='book')) + parent_tags
-            sub_parent_tags = parent_tags + [book.book_tag()]
-            for frag in book.fragments.all():
-                affected_tags.update(frag.tags)
-                frag.tags = list(frag.tags.exclude(category='book')
-                                    ) + sub_parent_tags
-            for child in book.children.all():
-                affected_tags.update(fix_subtree(child, sub_parent_tags))
-            return affected_tags
-
-        parent_tags = []
-        parent = self.parent
-        while parent is not None:
-            parent_tags.append(parent.book_tag())
-            parent = parent.parent
-
-        affected_tags = fix_subtree(self, parent_tags)
-        for tag in affected_tags:
-            tasks.touch_tag(tag)
-
-        book = self
-        while book is not None:
-            book.reset_tag_counter()
-            book.reset_theme_counter()
-            book = book.parent
+    @classmethod
+    def fix_tree_tags(cls):
+        """Fixes the ancestry cache."""
+        # TODO: table names
+        with transaction.atomic():
+            cursor = connection.cursor()
+            if connection.vendor == 'postgres':
+                cursor.execute("TRUNCATE catalogue_book_ancestor")
+                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:
+                cursor.execute("DELETE FROM catalogue_book_ancestor")
+                for b in cls.objects.exclude(parent=None):
+                    parent = b.parent
+                    while parent is not None:
+                        b.ancestor.add(parent)
+                        parent = parent.parent
 
     def cover_info(self, inherit=True):
         """Returns a dictionary to serve as fallback for BookInfo.
 
     def cover_info(self, inherit=True):
         """Returns a dictionary to serve as fallback for BookInfo.
@@ -419,6 +404,11 @@ class Book(models.Model):
             info = parent_info
         return info
 
             info = parent_info
         return info
 
+    def related_themes(self):
+        return Tag.objects.usage_for_queryset(
+            Fragment.objects.filter(models.Q(book=self) | models.Q(book__ancestor=self)),
+            counts=True).filter(category='theme')
+
     def parent_cover_changed(self):
         """Called when parent book's cover image is changed."""
         if not self.cover_info(inherit=False):
     def parent_cover_changed(self):
         """Called when parent book's cover image is changed."""
         if not self.cover_info(inherit=False):
@@ -435,116 +425,19 @@ class Book(models.Model):
         """Find other versions (i.e. in other languages) of the book."""
         return type(self).objects.filter(common_slug=self.common_slug).exclude(pk=self.pk)
 
         """Find other versions (i.e. in other languages) of the book."""
         return type(self).objects.filter(common_slug=self.common_slug).exclude(pk=self.pk)
 
-    def related_info(self):
-        """Keeps info about related objects (tags, media) in cache field."""
-        if self._related_info is not None:
-            return self._related_info
-        else:
-            rel = {'tags': {}, 'media': {}}
-
-            tags = self.tags.filter(category__in=(
-                    'author', 'kind', 'genre', 'epoch'))
-            tags = split_tags(tags)
-            for category in tags:
-                cat = []
-                for tag in tags[category]:
-                    tag_info = {'slug': tag.slug, 'name': tag.name}
-                    for lc, ln in settings.LANGUAGES:
-                        tag_name = getattr(tag, "name_%s" % lc)
-                        if tag_name:
-                            tag_info["name_%s" % lc] = tag_name
-                    cat.append(tag_info)
-                rel['tags'][category] = cat
-
-            for media_format in BookMedia.formats:
-                rel['media'][media_format] = self.has_media(media_format)
-
-            book = self
-            parents = []
-            while book.parent:
-                parents.append((book.parent.title, book.parent.slug))
-                book = book.parent
-            parents = parents[::-1]
-            if parents:
-                rel['parents'] = parents
-
-            if self.pk:
-                type(self).objects.filter(pk=self.pk).update(_related_info=rel)
-            return rel
-
-    def related_themes(self):
-        theme_counter = self.theme_counter
-        book_themes = list(Tag.objects.filter(pk__in=theme_counter.keys()))
-        for tag in book_themes:
-            tag.count = theme_counter[tag.pk]
-        return book_themes
-
-    def reset_tag_counter(self):
-        if self.id is None:
-            return
-
-        cache_key = "Book.tag_counter/%d" % self.id
-        permanent_cache.delete(cache_key)
-        if self.parent:
-            self.parent.reset_tag_counter()
-
-    @property
-    def tag_counter(self):
-        if self.id:
-            cache_key = "Book.tag_counter/%d" % self.id
-            tags = permanent_cache.get(cache_key)
-        else:
-            tags = None
-
-        if tags is None:
-            tags = {}
-            for child in self.children.all().order_by().iterator():
-                for tag_pk, value in child.tag_counter.iteritems():
-                    tags[tag_pk] = tags.get(tag_pk, 0) + value
-            for tag in self.tags.exclude(category__in=('book', 'theme', 'set')).order_by().iterator():
-                tags[tag.pk] = 1
-
-            if self.id:
-                permanent_cache.set(cache_key, tags)
-        return tags
-
-    def reset_theme_counter(self):
-        if self.id is None:
-            return
-
-        cache_key = "Book.theme_counter/%d" % self.id
-        permanent_cache.delete(cache_key)
-        if self.parent:
-            self.parent.reset_theme_counter()
-
-    @property
-    def theme_counter(self):
-        if self.id:
-            cache_key = "Book.theme_counter/%d" % self.id
-            tags = permanent_cache.get(cache_key)
-        else:
-            tags = None
-
-        if tags is None:
-            tags = {}
-            for fragment in Fragment.tagged.with_any([self.book_tag()]).order_by().iterator():
-                for tag in fragment.tags.filter(category='theme').order_by().iterator():
-                    tags[tag.pk] = tags.get(tag.pk, 0) + 1
-
-            if self.id:
-                permanent_cache.set(cache_key, tags)
-        return tags
+    def parents(self):
+        books = []
+        parent = self.parent
+        while parent is not None:
+            books.insert(0, parent)
+            parent = parent.parent
+        return books
 
     def pretty_title(self, html_links=False):
 
     def pretty_title(self, html_links=False):
-        book = self
-        rel_info = book.related_info()
-        names = [(related_tag_name(tag), Tag.create_url('author', tag['slug']))
-                    for tag in rel_info['tags'].get('author', ())]
-        if 'parents' in rel_info:
-            books = [(name, Book.create_url(slug))
-                        for name, slug in rel_info['parents']]
-            names.extend(reversed(books))
-        names.append((self.title, self.get_absolute_url()))
+        names = [(tag.name, tag.get_absolute_url())
+            for tag in self.tags.filter(category='author')]
+        books = self.parents() + [self]
+        names.extend([(b.title, b.get_absolute_url()) for b in books])
 
         if html_links:
             names = ['<a href="%s">%s</a>' % (tag[1], tag[0]) for tag in names]
 
         if html_links:
             names = ['<a href="%s">%s</a>' % (tag[1], tag[0]) for tag in names]
@@ -560,17 +453,8 @@ class Book(models.Model):
         also tagged with those tags.
 
         """
         also tagged with those tags.
 
         """
-        # get relevant books and their tags
         objects = cls.tagged.with_all(tags)
         objects = cls.tagged.with_all(tags)
-        parents = objects.exclude(children=None).only('slug')
-        # eliminate descendants
-        l_tags = Tag.objects.filter(category='book',
-            slug__in=[book.book_tag_slug() for book in parents.iterator()])
-        descendants_keys = [book.pk for book in cls.tagged.with_any(l_tags).only('pk').iterator()]
-        if descendants_keys:
-            objects = objects.exclude(pk__in=descendants_keys)
-
-        return objects
+        return objects.exclude(ancestor__in=objects)
 
     @classmethod
     def book_list(cls, filter=None):
 
     @classmethod
     def book_list(cls, filter=None):
@@ -634,15 +518,19 @@ class Book(models.Model):
             return None, None
 
     def choose_fragment(self):
             return None, None
 
     def choose_fragment(self):
-        tag = self.book_tag()
-        fragments = Fragment.tagged.with_any([tag])
-        if fragments.exists():
-            return fragments.order_by('?')[0]
+        fragments = self.fragments.order_by()
+        fragments_count = fragments.count()
+        if not fragments_count and self.children.exists():
+            fragments = Fragment.objects.filter(book__ancestor=self).order_by()
+            fragments_count = fragments.count()
+        if fragments_count:
+            return fragments[randint(0, fragments_count - 1)]
         elif self.parent:
             return self.parent.choose_fragment()
         else:
             return None
 
         elif self.parent:
             return self.parent.choose_fragment()
         else:
             return None
 
+
 # add the file fields
 for format_ in Book.formats:
     field_name = "%s_file" % format_
 # add the file fields
 for format_ in Book.formats:
     field_name = "%s_file" % format_
index b7c5d55..031c4f1 100644 (file)
@@ -15,25 +15,6 @@ from newtagging.models import tags_updated
 permanent_cache = caches['permanent']
 
 
 permanent_cache = caches['permanent']
 
 
-def _tags_updated_handler(sender, affected_tags, **kwargs):
-    # reset tag global counter
-    # we want Tag.changed_at updated for API to know the tag was touched
-    for tag in affected_tags:
-        tasks.touch_tag(tag)
-
-    # if book tags changed, reset book tag counter
-    if isinstance(sender, Book) and \
-                Tag.objects.filter(pk__in=(tag.pk for tag in affected_tags)).\
-                    exclude(category__in=('book', 'theme', 'set')).count():
-        sender.reset_tag_counter()
-    # if fragment theme changed, reset book theme counter
-    elif isinstance(sender, Fragment) and \
-                Tag.objects.filter(pk__in=(tag.pk for tag in affected_tags)).\
-                    filter(category='theme').count():
-        sender.book.reset_theme_counter()
-tags_updated.connect(_tags_updated_handler)
-
-
 def _pre_delete_handler(sender, instance, **kwargs):
     """ refresh Book on BookMedia delete """
     if sender == BookMedia:
 def _pre_delete_handler(sender, instance, **kwargs):
     """ refresh Book on BookMedia delete """
     if sender == BookMedia:
index b1e3d69..bff9932 100644 (file)
@@ -19,7 +19,6 @@ TAG_CATEGORIES = (
     ('genre', _('genre')),
     ('theme', _('theme')),
     ('set', _('set')),
     ('genre', _('genre')),
     ('theme', _('theme')),
     ('set', _('set')),
-    ('book', _('book')),
     ('thing', _('thing')), # things shown on pictures
 )
 
     ('thing', _('thing')), # things shown on pictures
 )
 
@@ -37,8 +36,6 @@ class Tag(TagBase):
     description = models.TextField(_('description'), blank=True)
 
     user = models.ForeignKey(User, blank=True, null=True)
     description = models.TextField(_('description'), blank=True)
 
     user = models.ForeignKey(User, blank=True, null=True)
-    book_count = models.IntegerField(_('book count'), blank=True, null=True)
-    picture_count = models.IntegerField(_('picture count'), blank=True, null=True)
     gazeta_link = models.CharField(blank=True, max_length=240)
     culturepl_link = models.CharField(blank=True, max_length=240)
     wiki_link = models.CharField(blank=True, max_length=240)
     gazeta_link = models.CharField(blank=True, max_length=240)
     culturepl_link = models.CharField(blank=True, max_length=240)
     wiki_link = models.CharField(blank=True, max_length=240)
@@ -77,11 +74,6 @@ class Tag(TagBase):
     def get_absolute_url(self):
         return ('catalogue.views.tagged_object_list', [self.url_chunk])
 
     def get_absolute_url(self):
         return ('catalogue.views.tagged_object_list', [self.url_chunk])
 
-    def clean(self):
-        if self.category == 'book' and (self.gazeta_link or self.wiki_link):
-            raise ValidationError(ugettext(
-                u"Book tags can't have attached links. Set them directly on the book instead of it's tag."))
-
     @classmethod
     @permalink
     def create_url(cls, category, slug):
     @classmethod
     @permalink
     def create_url(cls, category, slug):
@@ -94,41 +86,6 @@ class Tag(TagBase):
     has_description.short_description = _('description')
     has_description.boolean = True
 
     has_description.short_description = _('description')
     has_description.boolean = True
 
-    def get_count(self):
-        """Returns global book count for book tags, fragment count for themes."""
-        from catalogue.models import Book, Fragment
-
-        if self.category == 'book':
-            # never used
-            objects = Book.objects.none()
-        elif self.category == 'theme':
-            objects = Fragment.tagged.with_all((self,))
-        else:
-            objects = Book.tagged.with_all((self,)).order_by()
-            if self.category != 'set':
-                # eliminate descendants
-                l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects.iterator()])
-                descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags).iterator()]
-                if descendants_keys:
-                    objects = objects.exclude(pk__in=descendants_keys)
-        return objects.count()
-
-    # I shouldn't break the get_count() api
-    # just to include pictures.
-    def get_picture_count(self):
-        from picture.models import Picture, PictureArea
-
-        if self.category == 'book':
-            # never used
-            objects = Picture.objects.none()
-        elif self.category == 'theme':
-            objects = PictureArea.tagged.with_all((self,))
-        elif self.category == 'thing':
-            objects = Picture.tagged.with_all((self,))
-        else:
-            objects = Picture.tagged.with_all((self,)).order_by()
-        return objects.count()
-
     @staticmethod
     def get_tag_list(tags):
         if isinstance(tags, basestring):
     @staticmethod
     def get_tag_list(tags):
         if isinstance(tags, basestring):
@@ -145,7 +102,7 @@ class Tag(TagBase):
                     category = Tag.categories_rev[name]
                 else:
                     try:
                     category = Tag.categories_rev[name]
                 else:
                     try:
-                        real_tags.append(Tag.objects.exclude(category='book').get(slug=name))
+                        real_tags.append(Tag.objects.get(slug=name))
                         deprecated = True
                     except Tag.MultipleObjectsReturned, e:
                         ambiguous_slugs.append(name)
                         deprecated = True
                     except Tag.MultipleObjectsReturned, e:
                         ambiguous_slugs.append(name)
index 663a5bc..7d18068 100644 (file)
@@ -12,8 +12,6 @@ from wolnelektury.utils import localtime_to_utc
 # TODO: move to model?
 def touch_tag(tag):
     update_dict = {
 # TODO: move to model?
 def touch_tag(tag):
     update_dict = {
-        'book_count': tag.get_count(),
-        'picture_count': tag.get_picture_count(),
         'changed_at': localtime_to_utc(datetime.now()),
     }
 
         'changed_at': localtime_to_utc(datetime.now()),
     }
 
index 9c90bdd..58859af 100644 (file)
 
         <div class="book-box-head">
             <div class="author">
 
         <div class="book-box-head">
             <div class="author">
-                {% for tag in related.tags.author %}
-                    <a href="{% tag_url 'author' tag.slug %}">{% related_tag_name tag %}</a>{% if not forloop.last %},
-                {% endif %}{% endfor %}{% for title, slug in related.parents %},
-                    <a href="{% url 'book_detail' slug %}">{{ title }}</a>{% endfor %}
+                {% for tag in tags.author %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a>{% if not forloop.last %},
+                {% endif %}{% endfor %}{% for parent in parents %},
+                    <a href="{{ parent.get_absolute_url }}">{{ parent.title }}</a>{% endfor %}
             </div>
             <div class="title">
             </div>
             <div class="title">
-                               {% if main_link %}<a href="{{ main_link }}">{% endif %}
-                                       {{ book.title }}
-                           {% if main_link %}</a>{% endif %}
-                       </div>
+                {% if main_link %}<a href="{{ main_link }}">{% endif %}
+                    {{ book.title }}
+                {% if main_link %}</a>{% endif %}
+            </div>
         </div>
 
 <div class="cover-area">
         </div>
 
 <div class="cover-area">
     {% block cover-area-extra %}{% endblock %}
 </div>
         <div class="tags">
     {% block cover-area-extra %}{% endblock %}
 </div>
         <div class="tags">
-               {% spaceless %}
+            {% spaceless %}
 
             <span class="category">
             <span class="mono"> {% trans "Epoch" %}:</span>&nbsp;<span class="book-box-tag">
 
             <span class="category">
             <span class="mono"> {% trans "Epoch" %}:</span>&nbsp;<span class="book-box-tag">
-               {% for tag in related.tags.epoch %}
-                       <a href="{% tag_url 'epoch' tag.slug %}">{% related_tag_name tag %}</a>
-                       {% if not forloop.last %}<span>, </span>{% endif %}
+                {% for tag in tags.epoch %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a>
+                    {% if not forloop.last %}<span>, </span>{% endif %}
                 {% endfor %}
             </span></span>
 
             <span class="category">
             <span class="mono"> {% trans "Kind" %}:</span>&nbsp;<span class="book-box-tag">
                 {% endfor %}
             </span></span>
 
             <span class="category">
             <span class="mono"> {% trans "Kind" %}:</span>&nbsp;<span class="book-box-tag">
-               {% for tag in related.tags.kind %}
-                       <a href="{% tag_url 'kind' tag.slug %}">{% related_tag_name tag %}</a>
-                       {% if not forloop.last %}<span>, </span>{% endif %}
+                {% for tag in tags.kind %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a>
+                    {% if not forloop.last %}<span>, </span>{% endif %}
                 {% endfor %}
             </span></span>
 
             <span class="category">
             <span class="mono"> {% trans "Genre" %}:</span>&nbsp;<span class="book-box-tag">
                 {% endfor %}
             </span></span>
 
             <span class="category">
             <span class="mono"> {% trans "Genre" %}:</span>&nbsp;<span class="book-box-tag">
-               {% for tag in related.tags.genre %}
-                       <a href="{% tag_url 'genre' tag.slug %}">{% related_tag_name tag %}</a>
-                       {% if not forloop.last %}<span>, </span>{% endif %}
+                {% for tag in tags.genre %}
+                    <a href="{{ tag.get_absolute_url }}">{{ tag.name }}</a>
+                    {% if not forloop.last %}<span>, </span>{% endif %}
                 {% endfor %}
             </span></span>
 
                 {% endfor %}
             </span></span>
 
         <li class="book-box-download hoverget">
             <a class="downarrow hoverclick">{% trans "Download" %}</a>
             <div class="book-box-formats">
         <li class="book-box-download hoverget">
             <a class="downarrow hoverclick">{% trans "Download" %}</a>
             <div class="book-box-formats">
-             {% if book.pdf_file %}
-             <span><a href="{{ book.pdf_file.url}}">PDF</a> {% trans "to print" %}</span>
-             {% endif %}
-             {% if  book.epub_file %}
-             <span><a href="{{ book.epub_file.url}}">EPUB</a> {% trans "for an e-book reader" %}</span>
-             {% endif %}
-             {% if  book.mobi_file %}
-             <span><a href="{{ book.mobi_file.url}}">MOBI</a> {% trans "for Kindle" %}</span>
-             {% endif %}
-             {% if  book.fb2_file %}
-             <span><a href="{{ book.fb2_file.url}}">FB2</a> {% trans "FictionBook" %}</span>
-             {% endif %}
-             {% if  book.txt_file %}
-             <span><a href="{{ book.txt_file.url}}">TXT</a> {% trans "for advanced usage" %}</span>
-             {% endif %}
+             {% if book.pdf_file %}
+             <span><a href="{{ book.pdf_file.url}}">PDF</a> {% trans "to print" %}</span>
+             {% endif %}
+             {% if  book.epub_file %}
+             <span><a href="{{ book.epub_file.url}}">EPUB</a> {% trans "for an e-book reader" %}</span>
+             {% endif %}
+             {% if  book.mobi_file %}
+             <span><a href="{{ book.mobi_file.url}}">MOBI</a> {% trans "for Kindle" %}</span>
+             {% endif %}
+             {% if  book.fb2_file %}
+             <span><a href="{{ book.fb2_file.url}}">FB2</a> {% trans "FictionBook" %}</span>
+             {% endif %}
+             {% if  book.txt_file %}
+             <span><a href="{{ book.txt_file.url}}">TXT</a> {% trans "for advanced usage" %}</span>
+             {% endif %}
             </div>
         </li>
         <li class="book-box-audiobook">
             </div>
         </li>
         <li class="book-box-audiobook">
-        {% if related.media.mp3 or related.media.ogg %}
+        {% if has_audio %}
             <a href="{% url 'book_player' book.slug %}" class="open-player downarrow">{% trans "Listen" %}</a>
         {% endif %}
         </li>
             <a href="{% url 'book_player' book.slug %}" class="open-player downarrow">{% trans "Listen" %}</a>
         {% endif %}
         </li>
index cd48c3e..59b1acc 100755 (executable)
@@ -6,11 +6,11 @@
        <ul>
        {% if choices %}
         {% for tag in tags %}
        <ul>
        {% if choices %}
         {% for tag in tags %}
-            <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
+            <li><a href="{% catalogue_url choices tag %}">{{ tag }}{% if tag.count %}&nbsp;({{ tag.count }}){% endif %}</a></li>
         {% endfor %}
         {% else %}
         {% for tag in tags %}
         {% endfor %}
         {% else %}
         {% for tag in tags %}
-            <li><a href="{{ tag.get_absolute_url }}">{{ tag }}&nbsp;({{ tag.book_count }})</a></li>
+            <li><a href="{{ tag.get_absolute_url }}">{% if tag.count %}&nbsp;({{ tag.count }}){% endif %}</a></li>
         {% endfor %}
     {% endif %}
     </ul>
         {% endfor %}
     {% endif %}
     </ul>
index c9d5a35..667de9c 100644 (file)
@@ -10,7 +10,7 @@
     {% if did_you_mean %}
       <span class="did_you_mean">{% trans "Did you mean" %} <a href="{% url 'search' %}?q={{did_you_mean|urlencode}}">{{did_you_mean|lower}}</a>?</span>
     {% endif %}
     {% if did_you_mean %}
       <span class="did_you_mean">{% trans "Did you mean" %} <a href="{% url 'search' %}?q={{did_you_mean|urlencode}}">{{did_you_mean|lower}}</a>?</span>
     {% endif %}
-    <!-- tu pójdą trafienia w tagi: Autorzy - z description oraz motywy i rodzaje (z book_count) -->
+    <!-- tu pójdą trafienia w tagi: Autorzy - z description oraz motywy i rodzaje -->
       <div class="inline-tag-lists top-tag-list">
        {% if tags.author %}
        <div>
       <div class="inline-tag-lists top-tag-list">
        {% if tags.author %}
        <div>
index 87097b1..2143048 100644 (file)
@@ -6,11 +6,11 @@
     <ul>
        {% if choices %}
         {% for tag in tags %}
     <ul>
        {% if choices %}
         {% for tag in tags %}
-            <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
+            <li><a href="{% catalogue_url choices tag %}">{{ tag }}{% if tag.count %}&nbsp;({{ tag.count }}){% endif %}</a></li>
         {% endfor %}
         {% else %}
         {% for tag in tags %}
         {% endfor %}
         {% else %}
         {% for tag in tags %}
-            <li><a href="{{ tag.get_absolute_url }}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
+            <li><a href="{{ tag.get_absolute_url }}">{{ tag }}{% if tag.count %}&nbsp;({{ tag.count }}){% endif %}</a></li>
         {% endfor %}
         {% endif %}
     </ul>
         {% endfor %}
         {% endif %}
     </ul>
index 1cc0bb7..515c1fe 100644 (file)
@@ -15,7 +15,7 @@ from django.core.urlresolvers import reverse
 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
 from django.utils.translation import ugettext as _
 
 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
 from django.utils.translation import ugettext as _
 
-from catalogue.utils import related_tag_name as _related_tag_name
+from catalogue.utils import split_tags
 from catalogue.models import Book, BookMedia, Fragment, Tag, Source
 from catalogue.constants import LICENSES
 
 from catalogue.models import Book, BookMedia, Fragment, Tag, Source
 from catalogue.constants import LICENSES
 
@@ -324,23 +324,12 @@ def book_info(book):
 
 @register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
 def book_wide(context, book):
 
 @register.inclusion_tag('catalogue/book_wide.html', takes_context=True)
 def book_wide(context, book):
-    book_themes = book.related_themes()
-    extra_info = book.extra_info
-    hide_about = extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl')
-    stage_note, stage_note_url = book.stage_note()
-
-    return {
-        'book': book,
-        'main_link': reverse('book_text', args=[book.slug]) if book.html_file else None,
-        'related': book.related_info(),
-        'extra_info': extra_info,
-        'hide_about': hide_about,
-        'themes': book_themes,
-        'request': context.get('request'),
-        'show_lang': book.language_code() != settings.LANGUAGE_CODE,
-        'stage_note': stage_note,
-        'stage_note_url': stage_note_url,
-    }
+    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)
 
 
 @register.inclusion_tag('catalogue/book_short.html', takes_context=True)
@@ -349,8 +338,10 @@ def book_short(context, book):
 
     return {
         'book': book,
 
     return {
         'book': book,
+        'has_audio': book.has_media('mp3'),
         'main_link': book.get_absolute_url(),
         'main_link': book.get_absolute_url(),
-        'related': book.related_info(),
+        '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,
         'request': context.get('request'),
         'show_lang': book.language_code() != settings.LANGUAGE_CODE,
         'stage_note': stage_note,
@@ -360,8 +351,8 @@ def book_short(context, book):
 
 @register.inclusion_tag('catalogue/book_mini_box.html')
 def book_mini(book, with_link=True):
 
 @register.inclusion_tag('catalogue/book_mini_box.html')
 def book_mini(book, with_link=True):
-    author_str = ", ".join(related_tag_name(tag)
-        for tag in book.related_info()['tags'].get('author', ()))
+    author_str = ", ".join(tag.name
+        for tag in book.tags.filter(category='author'))
     return {
         'book': book,
         'author_str': author_str,
     return {
         'book': book,
         'author_str': author_str,
@@ -378,14 +369,19 @@ def work_list(context, object_list):
 
 @register.inclusion_tag('catalogue/fragment_promo.html')
 def fragment_promo(arg=None):
 
 @register.inclusion_tag('catalogue/fragment_promo.html')
 def fragment_promo(arg=None):
-    if arg is None:
-        fragments = Fragment.objects.all().order_by('?')
-        fragment = fragments[0] if fragments.exists() else None
-    elif isinstance(arg, Book):
+    if isinstance(arg, Book):
         fragment = arg.choose_fragment()
     else:
         fragment = arg.choose_fragment()
     else:
-        fragments = Fragment.tagged.with_all(arg).order_by('?')
-        fragment = fragments[0] if fragments.exists() else None
+        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,
 
     return {
         'fragment': fragment,
@@ -400,7 +396,7 @@ def related_books(book, limit=6, random=1, taken=0):
     if related is None:
         related = Book.tagged.related_to(book,
                 Book.objects.exclude(common_slug=book.common_slug)
     if related is None:
         related = Book.tagged.related_to(book,
                 Book.objects.exclude(common_slug=book.common_slug)
-                ).exclude(tag_relations__tag=book.book_tag())[:limit-random]
+                ).exclude(ancestor=book)[:limit-random]
         cache.set(cache_key, related, 1800)
     if random:
         random_books = Book.objects.exclude(
         cache.set(cache_key, related, 1800)
     if random:
         random_books = Book.objects.exclude(
@@ -437,7 +433,6 @@ def tag_url(category, slug):
 
 @register.simple_tag
 def download_audio(book, daisy=True):
 
 @register.simple_tag
 def download_audio(book, daisy=True):
-    related = book.related_info()
     links = []
     if related['media'].get('mp3'):
         links.append("<a href='%s'>%s</a>" %
     links = []
     if related['media'].get('mp3'):
         links.append("<a href='%s'>%s</a>" %
@@ -475,11 +470,6 @@ def license_icon(license_url):
     }
 
 
     }
 
 
-@register.simple_tag
-def related_tag_name(tag, lang=None):
-    return _related_tag_name(tag, lang)
-
-
 @register.filter
 def class_name(obj):
     return obj.__class__.__name__
 @register.filter
 def class_name(obj):
     return obj.__class__.__name__
index 4b01a8f..775fc29 100644 (file)
@@ -295,13 +295,15 @@ class TreeImportTest(WLTestCase):
                 u"There should be only parent on common tag page."
             )
         pies = models.Tag.objects.get(slug='pies')
                 u"There should be only parent on common tag page."
             )
         pies = models.Tag.objects.get(slug='pies')
-        self.assertEqual(self.parent.theme_counter, {pies.pk: 1},
+        themes = self.parent.related_themes()
+        self.assertEqual(len(themes), 1,
                 u"There should be child theme in parent theme counter."
             )
                 u"There should be child theme in parent theme counter."
             )
-        epoch = models.Tag.objects.get(slug='x-epoch')
-        self.assertEqual(epoch.book_count, 1,
-                u"There should be only parent in common tag's counter."
-            )
+        # TODO: book_count is deprecated, update here.
+        #~ epoch = models.Tag.objects.get(slug='x-epoch')
+        #~ self.assertEqual(epoch.book_count, 1,
+                #~ u"There should be only parent in common tag's counter."
+            #~ )
 
     def test_child_republish(self):
         CHILD_TEXT = """<utwor>
 
     def test_child_republish(self):
         CHILD_TEXT = """<utwor>
@@ -320,13 +322,14 @@ class TreeImportTest(WLTestCase):
             )
         pies = models.Tag.objects.get(slug='pies')
         kot = models.Tag.objects.get(slug='kot')
             )
         pies = models.Tag.objects.get(slug='pies')
         kot = models.Tag.objects.get(slug='kot')
-        self.assertEqual(self.parent.theme_counter, {pies.pk: 1, kot.pk: 1},
+        self.assertEqual(len(self.parent.related_themes()), 2,
                 u"There should be child themes in parent theme counter."
             )
                 u"There should be child themes in parent theme counter."
             )
-        epoch = models.Tag.objects.get(slug='x-epoch')
-        self.assertEqual(epoch.book_count, 1,
-                u"There should only be parent in common tag's counter."
-            )
+        # TODO: book_count is deprecated, update here.
+        #~ epoch = models.Tag.objects.get(slug='x-epoch')
+        #~ self.assertEqual(epoch.book_count, 1,
+                #~ u"There should only be parent in common tag's counter."
+            #~ )
 
     def test_book_change_child(self):
         second_child_info = BookInfoStub(
 
     def test_book_change_child(self):
         second_child_info = BookInfoStub(
@@ -357,13 +360,14 @@ class TreeImportTest(WLTestCase):
                 u"There should be parent and old child on common tag page."
             )
         kot = models.Tag.objects.get(slug='kot')
                 u"There should be parent and old child on common tag page."
             )
         kot = models.Tag.objects.get(slug='kot')
-        self.assertEqual(self.parent.theme_counter, {kot.pk: 1},
+        self.assertEqual(len(self.parent.related_themes()), 1,
                 u"There should only be new child themes in parent theme counter."
             )
         epoch = models.Tag.objects.get(slug='x-epoch')
                 u"There should only be new child themes in parent theme counter."
             )
         epoch = models.Tag.objects.get(slug='x-epoch')
-        self.assertEqual(epoch.book_count, 2,
-                u"There should be parent and old child in common tag's counter."
-            )
+        # book_count deprecated, update test.
+        #~ self.assertEqual(epoch.book_count, 2,
+                #~ u"There should be parent and old child in common tag's counter."
+            #~ )
         self.assertEqual(
                 list(self.client.get('/katalog/lektura/parent/motyw/kot/'
                     ).context['fragments']),
         self.assertEqual(
                 list(self.client.get('/katalog/lektura/parent/motyw/kot/'
                     ).context['fragments']),
index 503b98b..f10780c 100644 (file)
@@ -115,28 +115,27 @@ class TagRelatedTagsTests(WLTestCase):
                         'missing `author` related tag')
         self.assertTrue('Epoch' in [tag.name for tag in cats['epoch']],
                         'missing `epoch` related tag')
                         'missing `author` related tag')
         self.assertTrue('Epoch' in [tag.name for tag in cats['epoch']],
                         'missing `epoch` related tag')
-        self.assertTrue("ChildKind" in [tag.name for tag in cats['kind']],
-                        "missing `kind` related tag")
+        self.assertFalse("kind" in cats,
+                        "There should be no child-only related `kind` tags")
         self.assertTrue("Genre" in [tag.name for tag in cats['genre']],
                         'missing `genre` related tag')
         self.assertTrue("Genre" in [tag.name for tag in cats['genre']],
                         'missing `genre` related tag')
-        self.assertTrue("ChildGenre" in [tag.name for tag in cats['genre']],
-                        "missing child's related tag")
+        self.assertFalse("ChildGenre" in [tag.name for tag in cats['genre']],
+                        "There should be no child-only related `genre` tags")
         self.assertTrue("GchildGenre" in [tag.name for tag in cats['genre']],
                         "missing grandchild's related tag")
         self.assertTrue('Theme' in [tag.name for tag in cats['theme']],
                         "missing related theme")
         self.assertTrue("GchildGenre" in [tag.name for tag in cats['genre']],
                         "missing grandchild's related tag")
         self.assertTrue('Theme' in [tag.name for tag in cats['theme']],
                         "missing related theme")
-        self.assertTrue('Child1Theme' in [tag.name for tag in cats['theme']],
-                        "missing child's related theme")
+        self.assertFalse('Child1Theme' in [tag.name for tag in cats['theme']],
+                        "There should be no child-only related `theme` tags")
         self.assertTrue('GChildTheme' in [tag.name for tag in cats['theme']],
                         "missing grandchild's related theme")
 
         self.assertTrue('GChildTheme' in [tag.name for tag in cats['theme']],
                         "missing grandchild's related theme")
 
-
     def test_related_differ(self):
         """ related tags shouldn't include filtering tags """
 
         response = self.client.get('/katalog/rodzaj/kind/')
         cats = response.context['categories']
     def test_related_differ(self):
         """ related tags shouldn't include filtering tags """
 
         response = self.client.get('/katalog/rodzaj/kind/')
         cats = response.context['categories']
-        self.assertFalse('Kind' in [tag.name for tag in cats['kind']],
+        self.assertFalse('kind' in cats,
                          'filtering tag wrongly included in related')
         cats = self.client.get('/katalog/motyw/theme/').context['categories']
         self.assertFalse('Theme' in [tag.name for tag in cats['theme']],
                          'filtering tag wrongly included in related')
         cats = self.client.get('/katalog/motyw/theme/').context['categories']
         self.assertFalse('Theme' in [tag.name for tag in cats['theme']],
@@ -194,7 +193,6 @@ class CleanTagRelationTests(WLTestCase):
         """ there should be no tag relations left after deleting tags """
 
         models.Tag.objects.all().delete()
         """ there should be no tag relations left after deleting tags """
 
         models.Tag.objects.all().delete()
-        self.assertEqual(len(self.book.related_info()['tags']), 0)
         self.assertEqual(len(self.book.related_themes()), 0)
         self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
                          "orphaned TagRelation objects left")
         self.assertEqual(len(self.book.related_themes()), 0)
         self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
                          "orphaned TagRelation objects left")
@@ -225,10 +223,9 @@ class TestIdenticalTag(WLTestCase):
         """ there should be all related tags in relevant categories """
         book = models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
 
         """ there should be all related tags in relevant categories """
         book = models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
 
-        related_info = book.related_info()
         related_themes = book.related_themes()
         for category in 'author', 'kind', 'genre', 'epoch':
         related_themes = book.related_themes()
         for category in 'author', 'kind', 'genre', 'epoch':
-            self.assertTrue('tag' in [tag['slug'] for tag in related_info['tags'][category]],
+            self.assertTrue('tag' in [tag.slug for tag in book.tags.filter(category=category)],
                             'missing related tag for %s' % category)
         self.assertTrue('tag' in [tag.slug for tag in related_themes])
 
                             'missing related tag for %s' % category)
         self.assertTrue('tag' in [tag.slug for tag in related_themes])
 
@@ -270,12 +267,11 @@ class BookTagsTests(WLTestCase):
         """ book should have own tags and whole tree's themes """
 
         book = models.Book.objects.get(slug='parent')
         """ book should have own tags and whole tree's themes """
 
         book = models.Book.objects.get(slug='parent')
-        related_info = book.related_info()
         related_themes = book.related_themes()
 
         related_themes = book.related_themes()
 
-        self.assertEqual([t['slug'] for t in related_info['tags']['author']],
+        self.assertEqual([t.slug for t in book.tags.filter(category='author')],
                          ['common-man'])
                          ['common-man'])
-        self.assertEqual([t['slug'] for t in related_info['tags']['kind']],
+        self.assertEqual([t.slug for t in book.tags.filter(category='kind')],
                          ['kind'])
         self.assertEqual([(tag.name, tag.count) for tag in related_themes],
                          [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
                          ['kind'])
         self.assertEqual([(tag.name, tag.count) for tag in related_themes],
                          [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
@@ -283,8 +279,8 @@ class BookTagsTests(WLTestCase):
     def test_catalogue_tags(self):
         """ test main page tags and counts """
         context = self.client.get('/katalog/').context
     def test_catalogue_tags(self):
         """ test main page tags and counts """
         context = self.client.get('/katalog/').context
-        self.assertEqual([(tag.name, tag.book_count) for tag in context['categories']['author']],
+        self.assertEqual([(tag.name, tag.count) for tag in context['categories']['author']],
                          [('Jim Lazy', 1), ('Common Man', 1)])
                          [('Jim Lazy', 1), ('Common Man', 1)])
-        self.assertEqual([(tag.name, tag.book_count) for tag in context['categories']['theme']],
+        self.assertEqual([(tag.name, tag.count) for tag in context['fragment_tags']],
                          [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
 
                          [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
 
index a0e834c..df7e438 100644 (file)
@@ -178,8 +178,8 @@ class SortedMultiQuerySet(MultiQuerySet):
         self.order_by = kwargs.pop('order_by', None)
         self.sortfn = kwargs.pop('sortfn', None)
         if self.order_by is not None:
         self.order_by = kwargs.pop('order_by', None)
         self.sortfn = kwargs.pop('sortfn', None)
         if self.order_by is not None:
-            self.sortfn = lambda a, b: cmp(getattr(a, self.order_by),
-                                           getattr(b, self.order_by))
+            self.sortfn = lambda a, b: cmp((getattr(a, f) for f in self.order_by),
+                                           (getattr(b, f) for f in self.order_by))
         super(SortedMultiQuerySet, self).__init__(*args, **kwargs)
 
     def __getitem__(self, item):
         super(SortedMultiQuerySet, self).__init__(*args, **kwargs)
 
     def __getitem__(self, item):
@@ -349,10 +349,5 @@ This can sometimes occupy lots of memory, so trim it here a bit.
             or []
 
 
             or []
 
 
-def related_tag_name(tag_info, language=None):
-    return tag_info.get("name_%s" % (language or get_language()),
-        tag_info.get("name_%s" % settings.LANGUAGE_CODE, ""))
-
-
 def delete_from_cache_by_language(cache, key_template):
     cache.delete_many([key_template % lc for lc, ln in settings.LANGUAGES])
 def delete_from_cache_by_language(cache, key_template):
     cache.delete_many([key_template % lc for lc, ln in settings.LANGUAGES])
index 0026b0a..76aaddf 100644 (file)
@@ -23,6 +23,7 @@ from django.views.decorators.vary import vary_on_headers
 from ajaxable.utils import AjaxableFormView
 from catalogue import models
 from catalogue import forms
 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 catalogue.utils import split_tags, MultiQuerySet, SortedMultiQuerySet
 from catalogue.templatetags.catalogue_tags import tag_list, collection_list
 from pdcounter import models as pdcounter_models
@@ -37,26 +38,27 @@ permanent_cache = get_cache('permanent')
 
 @vary_on_headers('X-Requested-With')
 def catalogue(request):
 
 @vary_on_headers('X-Requested-With')
 def catalogue(request):
-    cache_key = 'catalogue.catalogue/' + get_language()
-    output = permanent_cache.get(cache_key)
+    #cache_key = 'catalogue.catalogue/' + get_language()
+    #output = permanent_cache.get(cache_key)
+    output = None
 
     if output is None:
 
     if output is None:
-        tags = models.Tag.objects.exclude(
-            category__in=('set', 'book')).exclude(book_count=0, picture_count=0)
-        tags = list(tags)
-        for tag in tags:
-            tag.count = tag.book_count + tag.picture_count
-        categories = split_tags(tags)
-        fragment_tags = categories.get('theme', [])
+        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))
         collections = models.Collection.objects.all()
 
         render_tag_list = lambda x: render_to_string(
             'catalogue/tag_list.html', tag_list(x))
-        has_pictures = lambda x: filter(lambda y: y.picture_count > 0, x)
-        has_books = lambda x: filter(lambda y: y.book_count > 0, x)
-        def render_split(tags):
-            with_books = has_books(tags)
-            with_pictures = has_pictures(tags)
+
+        def render_split(with_books, with_pictures):
             ctx = {}
             if with_books:
                 ctx['books'] = render_tag_list(with_books)
             ctx = {}
             if with_books:
                 ctx['books'] = render_tag_list(with_books)
@@ -64,17 +66,18 @@ def catalogue(request):
                 ctx['pictures'] = render_tag_list(with_pictures)
             return render_to_string('catalogue/tag_list_split.html', ctx)
 
                 ctx['pictures'] = render_tag_list(with_pictures)
             return render_to_string('catalogue/tag_list_split.html', ctx)
 
-        output = {'theme': {}}
+        output = {}
         output['theme'] = render_tag_list(fragment_tags)
         output['theme'] = render_tag_list(fragment_tags)
-        for category, tags in categories.items():
-            if category in ('author', 'theme'):
-                output[category] = render_tag_list(tags)
-            else:
-                output[category] = render_split(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))
 
         output['collections'] = render_to_string(
             'catalogue/collection_list.html', collection_list(collections))
-        permanent_cache.set(cache_key, output)
+        #permanent_cache.set(cache_key, output)
     if request.is_ajax():
         return JsonResponse(output)
     else:
     if request.is_ajax():
         return JsonResponse(output)
     else:
@@ -142,7 +145,7 @@ def differentiate_tags(request, tags, ambiguous_slugs):
     beginning = '/'.join(tag.url_chunk for tag in tags)
     unparsed = '/'.join(ambiguous_slugs[1:])
     options = []
     beginning = '/'.join(tag.url_chunk for tag in tags)
     unparsed = '/'.join(ambiguous_slugs[1:])
     options = []
-    for tag in models.Tag.objects.exclude(category='book').filter(slug=ambiguous_slugs[0]):
+    for tag in models.Tag.objects.filter(slug=ambiguous_slugs[0]):
         options.append({
             'url_args': '/'.join((beginning, tag.url_chunk, unparsed)).strip('/'),
             'tags': [tag]
         options.append({
             'url_args': '/'.join((beginning, tag.url_chunk, unparsed)).strip('/'),
             'tags': [tag]
@@ -198,25 +201,14 @@ def tagged_object_list(request, tags=''):
         areas = PictureArea.tagged.with_all(fragment_tags)
 
         if shelf_tags:
         areas = PictureArea.tagged.with_all(fragment_tags)
 
         if shelf_tags:
+            # FIXME: book tags here
             books = models.Book.tagged.with_all(shelf_tags).order_by()
             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)
 
             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)
 
-        # newtagging goes crazy if we just try:
-        #related_tags = models.Tag.objects.usage_for_queryset(fragments, counts=True,
-        #                    extra={'where': ["catalogue_tag.category != 'book'"]})
-
-        related_tags = []
-
-        fragment_keys = [fragment.pk for fragment in fragments.iterator()]
-        if fragment_keys:
-            related_tags = models.Fragment.tags.usage(counts=True,
-                                filters={'pk__in': fragment_keys}).exclude(
-                                category='book')
-            related_tags = (tag for tag in related_tags if tag not in fragment_tags)
-            categories = split_tags(related_tags, categories)
-
+        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()]
         object_queries.insert(0, fragments)
 
         area_keys = [area.pk for area in areas.iterator()]
@@ -232,41 +224,19 @@ def tagged_object_list(request, tags=''):
         objects = MultiQuerySet(*object_queries)
     else:
         if shelf_is_set:
         objects = MultiQuerySet(*object_queries)
     else:
         if shelf_is_set:
-            books = models.Book.tagged.with_all(tags).order_by('sort_key_author')
+            books = models.Book.tagged.with_all(tags).order_by(
+                'sort_key_author', 'title')
         else:
         else:
-            books = models.Book.tagged_top_level(tags).order_by('sort_key_author')
+            books = models.Book.tagged_top_level(tags).order_by(
+                'sort_key_author', 'title')
 
 
-        pictures = Picture.tagged.with_all(tags).order_by('sort_key_author')
+        pictures = Picture.tagged.with_all(tags).order_by(
+            'sort_key_author', 'title')
 
 
-        related_counts = {}
-        if books.count() > 0:
-            # get related tags from `tag_counter` and `theme_counter`
-            tags_pks = [tag.pk for tag in tags]
-            for book in books:
-                for tag_pk, value in itertools.chain(book.tag_counter.iteritems(), book.theme_counter.iteritems()):
-                    if tag_pk in tags_pks:
-                        continue
-                    related_counts[tag_pk] = related_counts.get(tag_pk, 0) + value
+        categories = split_tags(get_related_tags(tags))
 
 
-        if pictures.count() > 0:
-            tags_pks = [tag.pk for tag in tags]
-            for picture in pictures:
-                for tag_pk, value in itertools.chain(picture.tag_counter.iteritems(), picture.theme_counter.iteritems()):
-                    if tag_pk in tags_pks:
-                        continue
-                    related_counts[tag_pk] = related_counts.get(tag_pk, 0) + value
-
-        related_tags = models.Tag.objects.filter(pk__in=related_counts.keys())
-        related_tags = [tag for tag in related_tags if tag not in tags]
-
-        for tag in related_tags:
-            tag.count = related_counts[tag.pk]
-
-        categories = split_tags(related_tags)
-        del related_tags
-
-
-        objects = SortedMultiQuerySet(pictures, books, order_by='sort_key_author')
+        objects = SortedMultiQuerySet(pictures, books,
+            order_by=('sort_key_author', 'title'))
 
 
     if not objects:
 
 
     if not objects:
@@ -278,7 +248,7 @@ def tagged_object_list(request, tags=''):
             'object_list': objects,
             'categories': categories,
             'only_shelf': only_shelf,
             'object_list': objects,
             'categories': categories,
             'only_shelf': only_shelf,
-            'only_author': only_author,
+            #~ 'only_author': only_author,
             'only_my_shelf': only_my_shelf,
             'formats_form': forms.DownloadFormatsForm(),
             'tags': tags,
             'only_my_shelf': only_my_shelf,
             'formats_form': forms.DownloadFormatsForm(),
             'tags': tags,
@@ -289,10 +259,9 @@ def tagged_object_list(request, tags=''):
 
 def book_fragments(request, slug, theme_slug):
     book = get_object_or_404(models.Book, slug=slug)
 
 def book_fragments(request, slug, theme_slug):
     book = get_object_or_404(models.Book, slug=slug)
-
-    book_tag = book.book_tag()
     theme = get_object_or_404(models.Tag, slug=theme_slug, category='theme')
     theme = get_object_or_404(models.Tag, slug=theme_slug, category='theme')
-    fragments = models.Fragment.tagged.with_all([book_tag, theme])
+    fragments = models.Fragment.tagged.with_all([theme]).filter(
+        Q(book=book) | Q(book__ancestor=book))
 
     return render_to_response('catalogue/book_fragments.html', locals(),
         context_instance=RequestContext(request))
 
     return render_to_response('catalogue/book_fragments.html', locals(),
         context_instance=RequestContext(request))
@@ -353,7 +322,6 @@ def book_text(request, slug):
 
     if not book.has_html_file():
         raise Http404
 
     if not book.has_html_file():
         raise Http404
-    related = book.related_info()
     return render_to_response('catalogue/book_text.html', locals(),
         context_instance=RequestContext(request))
 
     return render_to_response('catalogue/book_text.html', locals(),
         context_instance=RequestContext(request))
 
@@ -443,9 +411,9 @@ def _tags_starting_with(prefix, user=None):
     books = models.Book.objects.filter(_word_starts_with('title', prefix))
     tags = models.Tag.objects.filter(_word_starts_with('name', prefix))
     if user and user.is_authenticated():
     books = models.Book.objects.filter(_word_starts_with('title', prefix))
     tags = models.Tag.objects.filter(_word_starts_with('name', prefix))
     if user and user.is_authenticated():
-        tags = tags.filter(~Q(category='book') & (~Q(category='set') | Q(user=user)))
+        tags = tags.filter(~Q(category='set') | Q(user=user))
     else:
     else:
-        tags = tags.filter(~Q(category='book') & ~Q(category='set'))
+        tags = tags.exclude(category='set')
 
     prefix_regexp = re.compile(_word_starts_with_regexp(prefix))
     return list(books) + list(tags) + [app for app in _apps if prefix_regexp.search(app.lower)] + list(book_stubs) + list(authors)
 
     prefix_regexp = re.compile(_word_starts_with_regexp(prefix))
     return list(books) + list(tags) + [app for app in _apps if prefix_regexp.search(app.lower)] + list(book_stubs) + list(authors)
index 800a15a..a9c3d87 100644 (file)
@@ -13,7 +13,7 @@ from django.utils.timezone import utc
 from django.utils.translation import ugettext_lazy as _, override
 import getpaid
 from catalogue.models import Book
 from django.utils.translation import ugettext_lazy as _, override
 import getpaid
 from catalogue.models import Book
-from catalogue.utils import get_random_hash, related_tag_name
+from catalogue.utils import get_random_hash
 from polls.models import Poll
 from django.contrib.sites.models import Site
 from . import app_settings
 from polls.models import Poll
 from django.contrib.sites.models import Site
 from . import app_settings
@@ -176,7 +176,7 @@ class Offer(models.Model):
             'funding/email/published.txt', {
                 'offer': self,
                 'book': self.book,
             'funding/email/published.txt', {
                 'offer': self,
                 'book': self.book,
-                'author': ", ".join(related_tag_name(a) for a in self.book.related_info()['tags']['author']),
+                'author': self.book.pretty_title(),
                 'current': self.current(),
             })
 
                 'current': self.current(),
             })
 
index 3482e08..1f558e9 100644 (file)
@@ -142,13 +142,7 @@ class Continuations(models.Model):
 
     @classmethod
     def for_set(cls, tag):
 
     @classmethod
     def for_set(cls, tag):
-        # book contains its descendants, we don't want them twice
-        books = Book.tagged.with_any((tag,))
-        l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books.iterator()])
-        descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags).iterator()]
-        if descendants_keys:
-            books = books.exclude(pk__in=descendants_keys)
-
+        books = Book.tagged_top_level([tag])
         cont_tabs = (cls.get(b) for b in books.iterator())
         return reduce(cls.join_conts, cont_tabs)
 
         cont_tabs = (cls.get(b) for b in books.iterator())
         return reduce(cls.join_conts, cont_tabs)
 
index 3f66681..7e9936f 100644 (file)
@@ -104,6 +104,7 @@ class TagManager(models.Manager):
         of field lookups to be applied to the given Model as the
         ``filters`` argument.
         """
         of field lookups to be applied to the given Model as the
         ``filters`` argument.
         """
+        # TODO: Do we really need this filters stuff?
         if filters is None: filters = {}
 
         queryset = model._default_manager.filter()
         if filters is None: filters = {}
 
         queryset = model._default_manager.filter()
@@ -158,18 +159,15 @@ class TaggedItemManager(models.Manager):
         """
         queryset, model = get_queryset_and_model(queryset_or_model)
         tags = self.tag_model.get_tag_list(tags)
         """
         queryset, model = get_queryset_and_model(queryset_or_model)
         tags = self.tag_model.get_tag_list(tags)
-        tag_count = len(tags)
-        if not tag_count:
+        if not tags:
             # No existing tags were given
             return queryset.none()
             # No existing tags were given
             return queryset.none()
-        elif tag_count == 1:
-            # Optimisation for single tag - fall through to the simpler
-            # query below.
-            return queryset.filter(tag_relations__tag=tags[0])
 
         # TODO: presumes reverse generic relation
 
         # TODO: presumes reverse generic relation
-        return queryset.filter(tag_relations__tag__in=tags
-            ).annotate(count=models.Count('pk')).filter(count=len(tags))
+        # Multiple joins are WAY faster than having-count, at least on Postgres 9.1.
+        for tag in tags:
+            queryset = queryset.filter(tag_relations__tag=tag)
+        return queryset
 
     def get_union_by_model(self, queryset_or_model, tags):
         """
 
     def get_union_by_model(self, queryset_or_model, tags):
         """
index 33ca9df..c529e02 100644 (file)
@@ -238,7 +238,7 @@ class ByCategoryFeed(Feed):
         return feed['title']
 
     def items(self, feed):
         return feed['title']
 
     def items(self, feed):
-        return Tag.objects.filter(category=feed['category']).exclude(book_count=0)
+        return Tag.objects.filter(category=feed['category']).exclude(items=None)
 
     def item_title(self, item):
         return item.name
 
     def item_title(self, item):
         return item.name
@@ -264,13 +264,7 @@ class ByTagFeed(AcquisitionFeed):
         return get_object_or_404(Tag, category=category, slug=slug)
 
     def items(self, tag):
         return get_object_or_404(Tag, category=category, slug=slug)
 
     def items(self, tag):
-        books = Book.tagged.with_any([tag])
-        l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books.iterator()])
-        descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
-        if descendants_keys:
-            books = books.exclude(pk__in=descendants_keys)
-
-        return books
+        return Book.tagged_top_level([tag])
 
 
 @factory_decorator(logged_in_or_basicauth())
 
 
 @factory_decorator(logged_in_or_basicauth())
@@ -289,7 +283,7 @@ class UserFeed(Feed):
         return u"Półki użytkownika %s" % user.username
 
     def items(self, user):
         return u"Półki użytkownika %s" % user.username
 
     def items(self, user):
-        return Tag.objects.filter(category='set', user=user).exclude(book_count=0)
+        return Tag.objects.filter(category='set', user=user).exclude(items=None)
 
     def item_title(self, item):
         return item.name
 
     def item_title(self, item):
         return item.name
@@ -300,9 +294,6 @@ class UserFeed(Feed):
     def item_description(self):
         return u''
 
     def item_description(self):
         return u''
 
-# no class decorators in python 2.5
-#UserFeed = factory_decorator(logged_in_or_basicauth())(UserFeed)
-
 
 @factory_decorator(logged_in_or_basicauth())
 @piwik_track
 
 @factory_decorator(logged_in_or_basicauth())
 @piwik_track
@@ -322,9 +313,6 @@ class UserSetFeed(AcquisitionFeed):
     def items(self, tag):
         return Book.tagged.with_any([tag])
 
     def items(self, tag):
         return Book.tagged.with_any([tag])
 
-# no class decorators in python 2.5
-#UserSetFeed = factory_decorator(logged_in_or_basicauth())(UserSetFeed)
-
 
 @piwik_track
 class SearchFeed(AcquisitionFeed):
 
 @piwik_track
 class SearchFeed(AcquisitionFeed):
diff --git a/apps/picture/migrations/0002_remove_picture__related_info.py b/apps/picture/migrations/0002_remove_picture__related_info.py
new file mode 100644 (file)
index 0000000..10542c8
--- /dev/null
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('picture', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='picture',
+            name='_related_info',
+        ),
+    ]
index 74d8631..75ea4c3 100644 (file)
@@ -106,8 +106,6 @@ class Picture(models.Model):
     culturepl_link   = models.CharField(blank=True, max_length=240)
     wiki_link     = models.CharField(blank=True, max_length=240)
 
     culturepl_link   = models.CharField(blank=True, max_length=240)
     wiki_link     = models.CharField(blank=True, max_length=240)
 
-    _related_info = jsonfield.JSONField(blank=True, null=True, editable=False)
-
     width       = models.IntegerField(null=True)
     height      = models.IntegerField(null=True)
 
     width       = models.IntegerField(null=True)
     height      = models.IntegerField(null=True)
 
@@ -333,7 +331,6 @@ class Picture(models.Model):
         if self.id is None:
             return
 
         if self.id is None:
             return
 
-        type(self).objects.filter(pk=self.pk).update(_related_info=None)
         for area in self.areas.all().iterator():
             area.reset_short_html()
 
         for area in self.areas.all().iterator():
             area.reset_short_html()
 
@@ -350,7 +347,7 @@ class Picture(models.Model):
     def short_html(self):
         if self.id:
             cache_key = "Picture.short_html/%d/%s" % (self.id, get_language())
     def short_html(self):
         if self.id:
             cache_key = "Picture.short_html/%d/%s" % (self.id, get_language())
-            short_html = get_cache('permanent').get(cache_key)
+            short_html = permanent_cache.get(cache_key)
         else:
             short_html = None
 
         else:
             short_html = None
 
@@ -365,12 +362,11 @@ class Picture(models.Model):
                     {'picture': self, 'tags': tags}))
 
             if self.id:
                     {'picture': self, 'tags': tags}))
 
             if self.id:
-                get_cache('permanent').set(cache_key, short_html)
+                permanent_cache.set(cache_key, short_html)
             return mark_safe(short_html)
 
     def pretty_title(self, html_links=False):
         picture = self
             return mark_safe(short_html)
 
     def pretty_title(self, html_links=False):
         picture = self
-        # TODO Add translations (related_tag_info)
         names = [(tag.name,
                   catalogue.models.Tag.create_url('author', tag.slug))
                  for tag in self.tags.filter(category='author')]
         names = [(tag.name,
                   catalogue.models.Tag.create_url('author', tag.slug))
                  for tag in self.tags.filter(category='author')]
@@ -382,89 +378,7 @@ class Picture(models.Model):
             names = [tag[0] for tag in names]
         return ', '.join(names)
 
             names = [tag[0] for tag in names]
         return ', '.join(names)
 
-    def related_info(self):
-        """Keeps info about related objects (tags) in cache field."""
-        if self._related_info is not None:
-            return self._related_info
-        else:
-            rel = {'tags': {}}
-
-            tags = self.tags.filter(category__in=(
-                    'author', 'kind', 'genre', 'epoch'))
-            tags = split_tags(tags)
-            for category in tags:
-                cat = []
-                for tag in tags[category]:
-                    tag_info = {'slug': tag.slug, 'name': tag.name}
-                    for lc, ln in settings.LANGUAGES:
-                        tag_name = getattr(tag, "name_%s" % lc)
-                        if tag_name:
-                            tag_info["name_%s" % lc] = tag_name
-                    cat.append(tag_info)
-                rel['tags'][category] = cat
-
-
-            if self.pk:
-                type(self).objects.filter(pk=self.pk).update(_related_info=rel)
-            return rel
-
     # copied from book.py, figure out
     def related_themes(self):
     # copied from book.py, figure out
     def related_themes(self):
-        # self.theme_counter hides a computation, so a line below actually makes sense
-        theme_counter = self.theme_counter
-        picture_themes = list(catalogue.models.Tag.objects.filter(pk__in=theme_counter.keys()))
-        for tag in picture_themes:
-            tag.count = theme_counter[tag.pk]
-        return picture_themes
-
-    def reset_tag_counter(self):
-        if self.id is None:
-            return
-
-        cache_key = "Picture.tag_counter/%d" % self.id
-        permanent_cache.delete(cache_key)
-        if self.parent:
-            self.parent.reset_tag_counter()
-
-    @property
-    def tag_counter(self):
-        if self.id:
-            cache_key = "Picture.tag_counter/%d" % self.id
-            tags = permanent_cache.get(cache_key)
-        else:
-            tags = None
-
-        if tags is None:
-            tags = {}
-            # do we need to do this? there are no children here.
-            for tag in self.tags.exclude(category__in=('book', 'theme', 'thing', 'set')).order_by().iterator():
-                tags[tag.pk] = 1
-
-            if self.id:
-                permanent_cache.set(cache_key, tags)
-        return tags
-
-    def reset_theme_counter(self):
-        if self.id is None:
-            return
-
-        cache_key = "Picture.theme_counter/%d" % self.id
-        permanent_cache.delete(cache_key)
-
-    @property
-    def theme_counter(self):
-        if self.id:
-            cache_key = "Picture.theme_counter/%d" % self.id
-            tags = permanent_cache.get(cache_key)
-        else:
-            tags = None
-
-        if tags is None:
-            tags = {}
-            for area in PictureArea.objects.filter(picture=self).order_by().iterator():
-                for tag in area.tags.filter(category__in=('theme', 'thing')).order_by().iterator():
-                    tags[tag.pk] = tags.get(tag.pk, 0) + 1
-
-            if self.id:
-                permanent_cache.set(cache_key, tags)
-        return tags
+        return catalogue.models.Tag.usage_for_queryset(
+            self.areas.all(), counts=True).filter(category__in=('theme', 'thing'))
index 8525d32..28cd34f 100644 (file)
@@ -23,7 +23,7 @@
          <p>{% trans "Motifs and themes" %}</p>
           <ul>
             {% for theme in themes %}
          <p>{% trans "Motifs and themes" %}</p>
           <ul>
             {% for theme in themes %}
-            <li><a href="{% url 'picture_viewer' picture.slug %}#theme-{{theme.slug}}">{{ theme }}{# ({{ theme.picture_count }})#}</a></li>
+            <li><a href="{% url 'picture_viewer' picture.slug %}#theme-{{theme.slug}}">{{ theme }}</a></li>
                {% endfor %}
             </ul>
            {% endif %}
                {% endfor %}
             </ul>
            {% endif %}
@@ -31,7 +31,7 @@
          <p>{% trans "Objects" %}</p>
           <ul>
             {% for thing in things %}
          <p>{% trans "Objects" %}</p>
           <ul>
             {% for thing in things %}
-            <li><a href="{% url 'picture_viewer' picture.slug %}#object-{{thing.slug}}">{{ thing }}{# ({{ thing.picture_count }})#}</a></li>
+            <li><a href="{% url 'picture_viewer' picture.slug %}#object-{{thing.slug}}">{{ thing }}</a></li>
             {% endfor %}
           </ul>
          {% endif %}
             {% endfor %}
           </ul>
          {% endif %}
index 6282610..1767ce2 100644 (file)
@@ -17,7 +17,6 @@ def picture_short(context, picture):
     context.update({
         'picture': picture,
         'main_link': picture.get_absolute_url(),
     context.update({
         'picture': picture,
         'main_link': picture.get_absolute_url(),
-        # 'related': picture.related_info(),
         'request': context.get('request'),
         'tags': split_tags(picture.tags),
         })
         'request': context.get('request'),
         'tags': split_tags(picture.tags),
         })
@@ -28,7 +27,6 @@ def picture_wide(context, picture):
     context.update({
         'picture': picture,
         'main_link': picture.get_absolute_url(),
     context.update({
         'picture': picture,
         'main_link': picture.get_absolute_url(),
-        # 'related': picture.related_info(),
         'request': context.get('request'),
         'tags': split_tags(picture.tags),
         })
         'request': context.get('request'),
         'tags': split_tags(picture.tags),
         })
index 1b01d33..7b1f648 100644 (file)
@@ -21,7 +21,7 @@ class PictureTest(WLTestCase):
         assert themes == set([(u'theme', u'nieporządek'), (u'thing', u'kosmos')]), \
             'Bad themes on Picture areas: %s' % themes
 
         assert themes == set([(u'theme', u'nieporządek'), (u'thing', u'kosmos')]), \
             'Bad themes on Picture areas: %s' % themes
 
-        pic_themes = set([tag.name for tag in picture.tags if tag.category in ('theme', 'object')])
+        pic_themes = set([tag.name for tag in picture.tags if tag.category in ('theme', 'thing')])
         assert not pic_themes, 'Unwanted themes set on Pictures: %s' % pic_themes
 
         picture.delete()
         assert not pic_themes, 'Unwanted themes set on Pictures: %s' % pic_themes
 
         picture.delete()
index 06a1680..8ecb9b0 100755 (executable)
@@ -113,7 +113,7 @@ def generated_file_view(file_name, mime_type, send_name=None, signals=None):
             else:
                 name = send_name
 
             else:
                 name = send_name
 
-            response = HttpResponse(mimetype=mime_type)
+            response = HttpResponse(content_type=mime_type)
             response['Content-Disposition'] = 'attachment; filename=%s' % name
             with open(file_path) as f:
                 for chunk in read_chunks(f):
             response['Content-Disposition'] = 'attachment; filename=%s' % name
             with open(file_path) as f:
                 for chunk in read_chunks(f):
index 7fb60b5..7f8bf9f 100644 (file)
@@ -852,7 +852,7 @@ class Search(SolrIndex):
                 q |= self.index.Q(**{field: query + "*"})
             else:
                 q |= self.make_term_query(query, field=field)
                 q |= self.index.Q(**{field: query + "*"})
             else:
                 q |= self.make_term_query(query, field=field)
-        qu = self.index.query(q).exclude(tag_category="book")
+        qu = self.index.query(q)
 
         return self.search_tags(qu, pdcounter=pdcounter)
 
 
         return self.search_tags(qu, pdcounter=pdcounter)
 
index a167f02..65d9427 100644 (file)
@@ -10,6 +10,7 @@ from django import template
 # from django.db.models import Q
 # from django.utils.translation import ugettext as _
 from catalogue.models import Book
 # from django.db.models import Q
 # from django.utils.translation import ugettext as _
 from catalogue.models import Book
+from catalogue.templatetags.catalogue_tags import book_short
 import re
 # from catalogue.forms import SearchForm
 # from catalogue.utils import split_tags
 import re
 # from catalogue.forms import SearchForm
 # from catalogue.utils import split_tags
@@ -48,11 +49,6 @@ def book_searched(context, result):
         snip = snip.replace("\n", "<br />").replace('---', '&mdash;')
         hit['snippet'] = snip
 
         snip = snip.replace("\n", "<br />").replace('---', '&mdash;')
         hit['snippet'] = snip
 
-    return {
-        'related': book.related_info(),
-        'book': book,
-        'main_link': book.get_absolute_url(),
-        'request': context.get('request'),
-        'hits': hits and zip(*hits)[1] or [],
-        'main_link': book.get_absolute_url(),
-    }
+    ctx = book_short(context, book)
+    ctx['hits'] = hits and zip(*hits)[1] or []
+    return ctx
index ecf2c66..bb1b4bc 100755 (executable)
@@ -3,6 +3,7 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from random import randint
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from random import randint
+from django.db.models import Q
 from django import template
 from catalogue.models import Book
 from social.models import Cite
 from django import template
 from catalogue.models import Book
 from social.models import Cite
@@ -25,9 +26,7 @@ def choose_cite(context, ctx=None):
         if ctx is None:
             cites = Cite.objects.all()
         elif isinstance(ctx, Book):
         if ctx is None:
             cites = Cite.objects.all()
         elif isinstance(ctx, Book):
-            cites = ctx.cite_set.all()
-            if not cites.exists():
-                cites = cites_for_tags([ctx.book_tag()])
+            cites = Cite.objects.filter(Q(book=ctx) | Q(book__ancestor=ctx))
         else:
             cites = cites_for_tags(ctx)
         stickies = cites.filter(sticky=True)
         else:
             cites = cites_for_tags(ctx)
         stickies = cites.filter(sticky=True)
index 43bc029..c6a9353 100755 (executable)
@@ -37,11 +37,9 @@ def set_sets(user, work, sets):
         touch_tag(shelf)
 
     # delete empty tags
         touch_tag(shelf)
 
     # delete empty tags
-    Tag.objects.filter(category='set', user=user, book_count=0).delete()
+    Tag.objects.filter(category='set', user=user, items=None).delete()
 
 
 def cites_for_tags(tags):
     """Returns a QuerySet with all Cites for books with given tags."""
 
 
 def cites_for_tags(tags):
     """Returns a QuerySet with all Cites for books with given tags."""
-    books = Book.tagged.with_all(tags).order_by().values_list('id', flat=True)
-    books = list(books)
-    return Cite.objects.filter(book__id__in=books)
+    return Cite.objects.filter(book__in=Book.tagged.with_all(tags))