Tagged view optimization.
[wolnelektury.git] / apps / newtagging / models.py
index 1c35254..7e0f949 100644 (file)
@@ -15,6 +15,7 @@ from django.db import connection, models
 from django.utils.translation import ugettext_lazy as _
 from django.db.models.base import ModelBase
 from django.core.exceptions import ObjectDoesNotExist
+from django.dispatch import Signal
 
 qn = connection.ops.quote_name
 
@@ -24,6 +25,8 @@ except ImportError:
     parse_lookup = None
 
 
+tags_updated = Signal(providing_args=["affected_tags"])
+
 def get_queryset_and_model(queryset_or_model):
     """
     Given a ``QuerySet`` or a ``Model``, returns a two-tuple of
@@ -45,6 +48,16 @@ class TagManager(models.Manager):
     def __init__(self, intermediary_table_model):
         super(TagManager, self).__init__()
         self.intermediary_table_model = intermediary_table_model
+        models.signals.pre_delete.connect(self.target_deleted)
+
+    def target_deleted(self, instance, **kwargs):
+        """ clear tag relations before deleting an object """
+        try:
+            int(instance.pk)
+        except ValueError:
+            return
+
+        self.update_tags(instance, [])
 
     def update_tags(self, obj, tags):
         """
@@ -63,10 +76,14 @@ class TagManager(models.Manager):
                                                object_id=obj.pk,
                                                tag__in=tags_for_removal).delete()
         # Add new tags
-        for tag in updated_tags:
+        tags_to_add = [tag for tag in updated_tags
+                       if tag not in current_tags]
+        for tag in tags_to_add:
             if tag not in current_tags:
                 self.intermediary_table_model._default_manager.create(tag=tag, content_object=obj)
 
+        tags_updated.send(sender=obj, affected_tags=tags_to_add + tags_for_removal)
+
     def remove_tag(self, obj, tag):
         """
         Remove tag from an object.
@@ -198,8 +215,18 @@ class TagManager(models.Manager):
         if parse_lookup:
             raise AttributeError("'TagManager.usage_for_queryset' is not compatible with pre-queryset-refactor versions of Django.")
 
-        extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:])
-        where, params = queryset.query.where.as_sql()
+        if getattr(queryset.query, 'get_compiler', None):
+            # Django 1.2+
+            compiler = queryset.query.get_compiler(using='default')
+            extra_joins = ' '.join(compiler.get_from_clause()[0][1:])
+            where, params = queryset.query.where.as_sql(
+                compiler.quote_name_unless_alias, compiler.connection
+            )
+        else:
+            # Django pre-1.2
+            extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:])
+            where, params = queryset.query.where.as_sql()
+
         if where:
             extra_criteria = 'AND %s' % where
         else:
@@ -412,7 +439,7 @@ class TaggedItemManager(models.Manager):
         else:
             return model._default_manager.none()
 
-    def get_related(self, obj, queryset_or_model, num=None):
+    def get_related(self, obj, queryset_or_model, num=None, ignore_by_tag=None):
         """
         Retrieve a list of instances of the specified model which share
         tags with the model instance ``obj``, ordered by the number of
@@ -420,6 +447,8 @@ class TaggedItemManager(models.Manager):
 
         If ``num`` is given, a maximum of ``num`` instances will be
         returned.
+
+        If ``ignore_by_tag`` is given, object tagged with it will be ignored.
         """
         queryset, model = get_queryset_and_model(queryset_or_model)
         model_table = qn(model._meta.db_table)
@@ -439,6 +468,15 @@ class TaggedItemManager(models.Manager):
             # instances for the same model.
             query += """
           AND related_tagged_item.object_id != %(tagged_item)s.object_id"""
+        if ignore_by_tag is not None:
+            query += """
+                AND NOT EXISTS (
+                    SELECT * FROM %(tagged_item)s
+                        WHERE %(tagged_item)s.object_id = %(model_pk)s
+                          AND %(tagged_item)s.content_type_id = %(content_type_id)s
+                          AND %(ignore_id)s = %(tagged_item)s.tag_id
+                )
+            """
         query += """
         GROUP BY %(model_pk)s
         ORDER BY %(count)s DESC
@@ -452,6 +490,7 @@ class TaggedItemManager(models.Manager):
             'content_type_id': content_type.pk,
             'related_content_type_id': related_content_type.pk,
             'limit_offset': num is not None and connection.ops.limit_offset_sql(num) or '',
+            'ignore_id': ignore_by_tag.id if ignore_by_tag else None,
         }
 
         cursor = connection.cursor()
@@ -477,6 +516,7 @@ def create_intermediary_table_model(model):
     class Meta:
         db_table = '%s_relation' % model._meta.db_table
         unique_together = (('tag', 'content_type', 'object_id'),)
+        app_label = model._meta.app_label
 
     def obj_unicode(self):
         try: