X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/2c7b7a116ebce28ca62052456e8eaae5176e1786..9c2555ad140dca1c09cde628bd22aac811e05b7e:/src/newtagging/models.py diff --git a/src/newtagging/models.py b/src/newtagging/models.py index 83aabe4fc..84149cc82 100644 --- a/src/newtagging/models.py +++ b/src/newtagging/models.py @@ -1,4 +1,6 @@ -# -*- 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. +# """ Models and managers for generic tagging. """ @@ -10,7 +12,7 @@ from django.dispatch import Signal qn = connection.ops.quote_name -tags_updated = Signal(providing_args=["affected_tags"]) +tags_updated = Signal() def get_queryset_and_model(queryset_or_model): @@ -31,11 +33,14 @@ def get_queryset_and_model(queryset_or_model): # Managers # ############ class TagManager(models.Manager): - def __init__(self, intermediary_table_model): + def __init__(self): super(TagManager, self).__init__() - self.intermediary_table_model = intermediary_table_model models.signals.pre_delete.connect(self.target_deleted) + @property + def intermediary_table_model(self): + return self.model.intermediary_table_model + def target_deleted(self, instance, **kwargs): """ clear tag relations before deleting an object """ try: @@ -52,7 +57,7 @@ class TagManager(models.Manager): content_type = ContentType.objects.get_for_model(obj) 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) + updated_tags = tags # Remove tags which no longer apply tags_for_removal = [tag for tag in current_tags if tag not in updated_tags] @@ -62,10 +67,11 @@ class TagManager(models.Manager): object_id=obj.pk, tag__in=tags_for_removal).delete() # Add new tags - tags_to_add = [tag for tag in updated_tags - if tag not in current_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: + existing = self.intermediary_table_model.objects.filter( + content_type__pk=content_type.pk, object_id=obj.pk, tag=tag) + if not existing: self.intermediary_table_model.objects.create(tag=tag, content_object=obj) tags_updated.send(sender=type(obj), instance=obj, affected_tags=tags_to_add + tags_for_removal) @@ -78,6 +84,16 @@ class TagManager(models.Manager): self.intermediary_table_model.objects.filter( content_type__pk=content_type.pk, object_id=obj.pk, tag=tag).delete() + def add_tag(self, obj, tag): + """ + Add tag to an object. + """ + content_type = ContentType.objects.get_for_model(obj) + relations = self.intermediary_table_model.objects.filter( + content_type__pk=content_type.pk, object_id=obj.pk, tag=tag) + if not relations: + self.intermediary_table_model.objects.create(tag=tag, content_object=obj) + def get_for_object(self, obj): """ Create a queryset matching all tags associated with the given @@ -145,9 +161,9 @@ class TagManager(models.Manager): class TaggedItemManager(models.Manager): - def __init__(self, tag_model): - super(TaggedItemManager, self).__init__() - self.tag_model = tag_model + @property + def tag_model(self): + return self.model.tag_model def get_by_model(self, queryset_or_model, tags): """ @@ -155,7 +171,6 @@ class TaggedItemManager(models.Manager): model associated with a given tag or list of tags. """ queryset, model = get_queryset_and_model(queryset_or_model) - tags = self.tag_model.get_tag_list(tags) if not tags: # No existing tags were given return queryset.none() @@ -172,7 +187,6 @@ class TaggedItemManager(models.Manager): model associated with *any* of the given list of tags. """ queryset, model = get_queryset_and_model(queryset_or_model) - tags = self.tag_model.get_tag_list(tags) if not tags: return queryset.none() # TODO: presumes reverse generic relation @@ -189,38 +203,3 @@ class TaggedItemManager(models.Manager): # Do we know it's 'tags'? return queryset.filter(tag_relations__tag__in=obj.tags).annotate( count=models.Count('pk')).order_by('-count').exclude(pk=obj.pk) - - -########## -# Models # -########## - -class TagMeta(ModelBase): - """Metaclass for tag models (models inheriting from TagBase).""" - def __new__(mcs, name, bases, attrs): - model = super(TagMeta, mcs).__new__(mcs, name, bases, attrs) - if not model._meta.abstract: - # Register custom managers for concrete models - TagManager(model.intermediary_table_model).contribute_to_class(model, 'objects') - TaggedItemManager(model).contribute_to_class(model.intermediary_table_model, 'objects') - return model - - -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): - return [tag_list] - else: - return tag_list