X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/6e404619938ccba6732fc9b8a3fb6c90a27bad76..1c07de888eadc1333264d627ffdff1066a5f01b9:/apps/newtagging/models.py diff --git a/apps/newtagging/models.py b/apps/newtagging/models.py index b70ab9a09..e9b114145 100644 --- a/apps/newtagging/models.py +++ b/apps/newtagging/models.py @@ -6,7 +6,7 @@ Models and managers for generic tagging. # Python 2.3 compatibility try: set -except NameError: +except NameError: from sets import Set as set from django.contrib.contenttypes import generic @@ -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,7 +48,17 @@ 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): """ Update tags associated with an object. @@ -54,7 +67,7 @@ class TagManager(models.Manager): current_tags = list(self.filter(items__content_type__pk=content_type.pk, items__object_id=obj.pk)) updated_tags = self.model.get_tag_list(tags) - + # Remove tags which no longer apply tags_for_removal = [tag for tag in current_tags \ if tag not in updated_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. @@ -83,8 +100,8 @@ class TagManager(models.Manager): ctype = ContentType.objects.get_for_model(obj) return self.filter(items__content_type__pk=ctype.pk, items__object_id=obj.pk) - - def _get_usage(self, model, counts=False, min_count=None, extra_joins=None, extra_criteria=None, params=None, extra=None, extra_tables=None): + + def _get_usage(self, model, counts=False, min_count=None, extra_joins=None, extra_criteria=None, params=None, extra=None): """ Perform the custom SQL query for ``usage_for_model`` and ``usage_for_queryset``. @@ -94,25 +111,25 @@ class TagManager(models.Manager): model_table = qn(model._meta.db_table) model_pk = '%s.%s' % (model_table, qn(model._meta.pk.column)) tag_columns = self._get_tag_columns() - + if extra is None: extra = {} extra_where = '' if 'where' in extra: extra_where = 'AND ' + ' AND '.join(extra['where']) - + query = """ SELECT DISTINCT %(tag_columns)s%(count_sql)s FROM %(tag)s - INNER JOIN %(tagged_item)s AS %(tagged_item_alias)s - ON %(tag)s.id = %(tagged_item_alias)s.tag_id + INNER JOIN %(tagged_item)s + ON %(tag)s.id = %(tagged_item)s.tag_id INNER JOIN %(model)s - ON %(tagged_item_alias)s.object_id = %(model_pk)s + ON %(tagged_item)s.object_id = %(model_pk)s %%s - WHERE %(tagged_item_alias)s.content_type_id = %(content_type_id)s + WHERE %(tagged_item)s.content_type_id = %(content_type_id)s %%s %(extra_where)s - GROUP BY %(tag_columns)s, %(tag)s.id, %(tag)s.name%(extra_tables)s + GROUP BY %(tag_columns)s, %(tag)s.id, %(tag)s.name %%s ORDER BY %(tag)s.%(ordering)s ASC""" % { 'tag': qn(self.model._meta.db_table), @@ -120,11 +137,9 @@ class TagManager(models.Manager): 'tag_columns': tag_columns, 'count_sql': counts and (', COUNT(%s)' % model_pk) or '', 'tagged_item': qn(self.intermediary_table_model._meta.db_table), - 'tagged_item_alias': qn('_newtagging_' + self.intermediary_table_model._meta.db_table), 'model': model_table, 'model_pk': model_pk, 'extra_where': extra_where, - 'extra_tables': ''.join((', %s.id' % qn(table)) for table in extra_tables), 'content_type_id': ContentType.objects.get_for_model(model).pk, } @@ -200,14 +215,23 @@ 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() - extra_tables = queryset.query.extra_tables + 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: extra_criteria = '' - return self._get_usage(queryset.model, counts, min_count, extra_joins, extra_criteria, params, extra, extra_tables=extra_tables) + return self._get_usage(queryset.model, counts, min_count, extra_joins, extra_criteria, params, extra) def related_for_model(self, tags, model, counts=False, min_count=None, extra=None): """ @@ -227,12 +251,12 @@ class TagManager(models.Manager): tag_count = len(tags) tagged_item_table = qn(self.intermediary_table_model._meta.db_table) tag_columns = self._get_tag_columns() - + if extra is None: extra = {} extra_where = '' if 'where' in extra: extra_where = 'AND ' + ' AND '.join(extra['where']) - + # Temporary table in this query is a hack to prevent MySQL from executing # inner query as dependant query (which could result in severe performance loss) query = """ @@ -304,7 +328,7 @@ class TaggedItemManager(models.Manager): def __init__(self, tag_model): super(TaggedItemManager, self).__init__() self.tag_model = tag_model - + def get_by_model(self, queryset_or_model, tags): """ Create a ``QuerySet`` containing instances of the specified @@ -476,7 +500,7 @@ class TaggedItemManager(models.Manager): def create_intermediary_table_model(model): """Create an intermediary table model for the specific tag model""" name = model.__name__ + 'Relation' - + class Meta: db_table = '%s_relation' % model._meta.db_table unique_together = (('tag', 'content_type', 'object_id'),) @@ -486,8 +510,8 @@ def create_intermediary_table_model(model): return u'%s [%s]' % (self.content_type.get_object_for_this_type(pk=self.object_id), self.tag) except ObjectDoesNotExist: return u' [%s]' % self.tag - - # Set up a dictionary to simulate declarations within a class + + # Set up a dictionary to simulate declarations within a class attrs = { '__module__': model.__module__, 'Meta': Meta, @@ -516,15 +540,15 @@ class TagMeta(ModelBase): class TagBase(models.Model): """Abstract class to be inherited by model classes.""" __metaclass__ = TagMeta - + class Meta: abstract = True - + @staticmethod def get_tag_list(tag_list): """ Utility function for accepting tag input in a flexible manner. - + You should probably override this method in your subclass. """ if isinstance(tag_list, TagBase):