update Tag.book_count in celery
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Mon, 19 Dec 2011 14:37:01 +0000 (15:37 +0100)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Mon, 19 Dec 2011 16:18:40 +0000 (17:18 +0100)
apps/api/handlers.py
apps/api/management/commands/mobileinit.py
apps/catalogue/forms.py
apps/catalogue/models.py
apps/catalogue/tasks.py [new file with mode: 0755]
apps/catalogue/views.py
apps/opds/views.py
wolnelektury/templates/catalogue/tag_list.html
wolnelektury/templates/catalogue/user_shelves.html

index e10a4b5..62708c9 100644 (file)
@@ -249,9 +249,8 @@ class TagsHandler(BaseHandler):
         except KeyError, e:
             return rc.NOT_FOUND
 
-        tags = Tag.objects.filter(category=category_sng)
-        tags = [t for t in tags if t.get_count() > 0]
-        if tags:
+        tags = Tag.objects.filter(category=category_sng).exclude(book_count=0)
+        if tags.exists():
             return tags
         else:
             return rc.NOT_FOUND
@@ -518,7 +517,7 @@ class CatalogueHandler(BaseHandler):
                     changed_at__gte=since,
                     changed_at__lt=until):
             # only serve non-empty tags
-            if tag.get_count():
+            if tag.book_count:
                 tag_d = cls.tag_dict(tag, fields)
                 updated.append(tag_d)
             elif tag.created_at < since:
index 658b177..2abbfb3 100755 (executable)
@@ -23,10 +23,10 @@ class Command(BaseCommand):
         db = init_db(last_checked)
         for b in Book.objects.all():
             add_book(db, b)
-        for t in Tag.objects.exclude(category__in=('book', 'set', 'theme')):
+        for t in Tag.objects.exclude(
+                category__in=('book', 'set', 'theme')).exclude(book_count=0):
             # only add non-empty tags
-            if t.get_count():
-                add_tag(db, t)
+            add_tag(db, t)
         db.commit()
         db.close()
         current(last_checked)
index 852513a..9707e79 100644 (file)
@@ -56,7 +56,7 @@ class ObjectSetsForm(forms.Form):
         self.fields['set_ids'] = forms.MultipleChoiceField(
             label=_('Shelves'),
             required=False,
-            choices=[(tag.id, "%s (%s)" % (tag.name, tag.get_count())) for tag in Tag.objects.filter(category='set', user=user)],
+            choices=[(tag.id, "%s (%s)" % (tag.name, tag.book_count)) for tag in Tag.objects.filter(category='set', user=user)],
             initial=[tag.id for tag in obj.tags.filter(category='set', user=user)],
             widget=forms.CheckboxSelectMultiple
         )
index 2b33964..359a89e 100644 (file)
@@ -3,7 +3,6 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from collections import namedtuple
-from datetime import datetime
 
 from django.db import models
 from django.db.models import permalink, Q
@@ -25,6 +24,7 @@ from newtagging.models import TagBase, tags_updated
 from newtagging import managers
 from catalogue.fields import JSONField, OverwritingFileField
 from catalogue.utils import create_zip
+from catalogue.tasks import touch_tag
 from shutil import copy
 from glob import glob
 import re
@@ -106,25 +106,22 @@ class Tag(TagBase):
     has_description.boolean = True
 
     def get_count(self):
-        """ returns global book count for book tags, fragment count for themes """
-
-        if self.book_count is None:
-            if self.category == 'book':
-                # never used
-                objects = Book.objects.none()
-            elif self.category == 'theme':
-                objects = Fragment.tagged.with_all((self,))
-            else:
-                objects = Book.tagged.with_all((self,)).order_by()
-                if self.category != 'set':
-                    # eliminate descendants
-                    l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects])
-                    descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
-                    if descendants_keys:
-                        objects = objects.exclude(pk__in=descendants_keys)
-            self.book_count = objects.count()
-            self.save()
-        return self.book_count
+        """Returns global book count for book tags, fragment count for themes."""
+
+        if self.category == 'book':
+            # never used
+            objects = Book.objects.none()
+        elif self.category == 'theme':
+            objects = Fragment.tagged.with_all((self,))
+        else:
+            objects = Book.tagged.with_all((self,)).order_by()
+            if self.category != 'set':
+                # eliminate descendants
+                l_tags = Tag.objects.filter(slug__in=[book.book_tag_slug() for book in objects])
+                descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
+                if descendants_keys:
+                    objects = objects.exclude(pk__in=descendants_keys)
+        return objects.count()
 
     @staticmethod
     def get_tag_list(tags):
@@ -802,15 +799,20 @@ class Book(models.Model):
             book.build_mobi()
 
         book_descendants = list(book.children.all())
+        descendants_tags = set()
         # add l-tag to descendants and their fragments
         while len(book_descendants) > 0:
             child_book = book_descendants.pop(0)
+            descendants_tags.update(child_book.tags)
             child_book.tags = list(child_book.tags) + [book_tag]
             child_book.save()
             for fragment in child_book.fragments.all():
                 fragment.tags = set(list(fragment.tags) + [book_tag])
             book_descendants += list(child_book.children.all())
 
+        for tag in descendants_tags:
+            touch_tag.delay(tag)
+
         book.save()
 
         # refresh cache
@@ -1034,7 +1036,8 @@ class Fragment(models.Model):
 def _tags_updated_handler(sender, affected_tags, **kwargs):
     # reset tag global counter
     # we want Tag.changed_at updated for API to know the tag was touched
-    Tag.objects.filter(pk__in=[tag.pk for tag in affected_tags]).update(book_count=None, changed_at=datetime.now())
+    for tag in affected_tags:
+        touch_tag.delay(tag)
 
     # if book tags changed, reset book tag counter
     if isinstance(sender, Book) and \
diff --git a/apps/catalogue/tasks.py b/apps/catalogue/tasks.py
new file mode 100755 (executable)
index 0000000..5566fe1
--- /dev/null
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from datetime import datetime
+from celery.task import task
+
+
+@task
+def touch_tag(tag):
+    update_dict = {
+        'book_count': tag.get_count(),
+        'changed_at': datetime.now(),
+    }
+
+    type(tag).objects.filter(pk=tag.pk).update(**update_dict)
index e6e5eb9..902907d 100644 (file)
@@ -29,6 +29,7 @@ from django.views.generic.list_detail import object_list
 from catalogue import models
 from catalogue import forms
 from catalogue.utils import split_tags, AttachmentHttpResponse, async_build_pdf
+from catalogue.tasks import touch_tag
 from pdcounter import models as pdcounter_models
 from pdcounter import views as pdcounter_views
 from suggest.forms import PublishingSuggestForm
@@ -56,9 +57,11 @@ class JSONResponse(HttpResponse):
 
 
 def catalogue(request):
-    tags = models.Tag.objects.exclude(category__in=('set', 'book'))
+    tags = models.Tag.objects.exclude(
+        category__in=('set', 'book')).exclude(book_count=0)
+    tags = list(tags)
     for tag in tags:
-        tag.count = tag.get_count()
+        tag.count = tag.book_count
     categories = split_tags(tags)
     fragment_tags = categories.get('theme', [])
 
@@ -520,12 +523,10 @@ def book_sets(request, book):
             new_shelves = [models.Tag.objects.get(pk=id) for id in form.cleaned_data['set_ids']]
 
             for shelf in [shelf for shelf in old_shelves if shelf not in new_shelves]:
-                shelf.book_count = None
-                shelf.save()
+                touch_tag(shelf)
 
             for shelf in [shelf for shelf in new_shelves if shelf not in old_shelves]:
-                shelf.book_count = None
-                shelf.save()
+                touch_tag(shelf)
 
             book.tags = new_shelves + list(book.tags.filter(~Q(category='set') | ~Q(user=request.user)))
             if request.is_ajax():
@@ -553,9 +554,7 @@ def remove_from_shelf(request, shelf, book):
 
     if shelf in book.tags:
         models.Tag.objects.remove_tag(book, shelf)
-
-        shelf.book_count = None
-        shelf.save()
+        touch_tag(shelf)
 
         return HttpResponse(_('Book was successfully removed from the shelf'))
     else:
index c7d3828..06e0119 100644 (file)
@@ -229,7 +229,7 @@ class ByCategoryFeed(Feed):
         return feed['title']
 
     def items(self, feed):
-        return (tag for tag in Tag.objects.filter(category=feed['category']) if tag.get_count() > 0)
+        return Tag.objects.filter(category=feed['category']).exclude(book_count=0)
 
     def item_title(self, item):
         return item.name
@@ -280,7 +280,7 @@ class UserFeed(Feed):
         return u"Półki użytkownika %s" % user.username
 
     def items(self, user):
-        return (tag for tag in Tag.objects.filter(category='set', user=user) if tag.get_count() > 0)
+        return Tag.objects.filter(category='set', user=user).exclude(book_count=0)
 
     def item_title(self, item):
         return item.name
index e96320e..3f842ac 100644 (file)
@@ -5,7 +5,7 @@
 {% else %}
     <ul>
         {% for tag in tags %}
-            <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.count }})</a></li>
+            <li><a href="{% catalogue_url choices tag %}">{{ tag }}&nbsp;({{ tag.book_count }})</a></li>
         {% endfor %}
     </ul>
 {% endif %}
index d047dbe..28c1222 100644 (file)
@@ -3,7 +3,7 @@
 {% if shelves %}
 <ul class="shelf-list">
 {% for shelf in shelves %}
-    <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "remove" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.get_count }})</a></li>
+    <li><a href="{% url delete_shelf shelf.slug %}" class="delete-shelf">{% trans "remove" %}</a> <a href="{{ shelf.get_absolute_url }}" class="visit-shelf">{{ shelf.name }} ({{ shelf.book_count }})</a></li>
 {% endfor %}
 </ul>
 {% else %}