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
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
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):
"""
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.
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:
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
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)
# 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
'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()