From cbad6ab096e23aea74c913fd37921652eca9728a Mon Sep 17 00:00:00 2001 From: luxagraf Date: Sun, 27 Mar 2011 14:40:24 -0500 Subject: fixed link retriever to work with pinboard, eliminated django-tagging from links and rewrote base code to optimize for pinboard --- lib/taggit/managers.py | 244 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 lib/taggit/managers.py (limited to 'lib/taggit/managers.py') diff --git a/lib/taggit/managers.py b/lib/taggit/managers.py new file mode 100644 index 0000000..55201b4 --- /dev/null +++ b/lib/taggit/managers.py @@ -0,0 +1,244 @@ +from django.contrib.contenttypes.generic import GenericRelation +from django.contrib.contenttypes.models import ContentType +from django.db import models +from django.db.models.fields.related import ManyToManyRel, RelatedField, add_lazy_relation +from django.db.models.related import RelatedObject +from django.utils.text import capfirst +from django.utils.translation import ugettext_lazy as _ + +from taggit.forms import TagField +from taggit.models import TaggedItem, GenericTaggedItemBase +from taggit.utils import require_instance_manager + + +try: + all +except NameError: + # 2.4 compat + try: + from django.utils.itercompat import all + except ImportError: + # 1.1.X compat + def all(iterable): + for item in iterable: + if not item: + return False + return True + + +class TaggableRel(ManyToManyRel): + def __init__(self): + self.related_name = None + self.limit_choices_to = {} + self.symmetrical = True + self.multiple = True + self.through = None + + +class TaggableManager(RelatedField): + def __init__(self, verbose_name=_("Tags"), + help_text=_("A comma-separated list of tags."), through=None, blank=False): + self.through = through or TaggedItem + self.rel = TaggableRel() + self.verbose_name = verbose_name + self.help_text = help_text + self.blank = blank + self.editable = True + self.unique = False + self.creates_table = False + self.db_column = None + self.choices = None + self.serialize = False + self.null = True + self.creation_counter = models.Field.creation_counter + models.Field.creation_counter += 1 + + def __get__(self, instance, model): + if instance is not None and instance.pk is None: + raise ValueError("%s objects need to have a primary key value " + "before you can access their tags." % model.__name__) + manager = _TaggableManager( + through=self.through, model=model, instance=instance + ) + return manager + + def contribute_to_class(self, cls, name): + self.name = self.column = name + self.model = cls + cls._meta.add_field(self) + setattr(cls, name, self) + if not cls._meta.abstract: + if isinstance(self.through, basestring): + def resolve_related_class(field, model, cls): + self.through = model + self.post_through_setup(cls) + add_lazy_relation( + cls, self, self.through, resolve_related_class + ) + else: + self.post_through_setup(cls) + + def post_through_setup(self, cls): + self.use_gfk = ( + self.through is None or issubclass(self.through, GenericTaggedItemBase) + ) + self.rel.to = self.through._meta.get_field("tag").rel.to + if self.use_gfk: + tagged_items = GenericRelation(self.through) + tagged_items.contribute_to_class(cls, "tagged_items") + + def save_form_data(self, instance, value): + getattr(instance, self.name).set(*value) + + def formfield(self, form_class=TagField, **kwargs): + defaults = { + "label": capfirst(self.verbose_name), + "help_text": self.help_text, + "required": not self.blank + } + defaults.update(kwargs) + return form_class(**defaults) + + def value_from_object(self, instance): + if instance.pk: + return self.through.objects.filter(**self.through.lookup_kwargs(instance)) + return self.through.objects.none() + + def related_query_name(self): + return self.model._meta.module_name + + def m2m_reverse_name(self): + return self.through._meta.get_field_by_name("tag")[0].column + + def m2m_target_field_name(self): + return self.model._meta.pk.name + + def m2m_reverse_target_field_name(self): + return self.rel.to._meta.pk.name + + def m2m_column_name(self): + if self.use_gfk: + return self.through._meta.virtual_fields[0].fk_field + return self.through._meta.get_field('content_object').column + + def db_type(self, connection=None): + return None + + def m2m_db_table(self): + return self.through._meta.db_table + + def extra_filters(self, pieces, pos, negate): + if negate or not self.use_gfk: + return [] + prefix = "__".join(["tagged_items"] + pieces[:pos-2]) + cts = map(ContentType.objects.get_for_model, _get_subclasses(self.model)) + if len(cts) == 1: + return [("%s__content_type" % prefix, cts[0])] + return [("%s__content_type__in" % prefix, cts)] + + def bulk_related_objects(self, new_objs, using): + return [] + + +class _TaggableManager(models.Manager): + def __init__(self, through, model, instance): + self.through = through + self.model = model + self.instance = instance + + def get_query_set(self): + return self.through.tags_for(self.model, self.instance) + + def _lookup_kwargs(self): + return self.through.lookup_kwargs(self.instance) + + @require_instance_manager + def add(self, *tags): + str_tags = set([ + t + for t in tags + if not isinstance(t, self.through.tag_model()) + ]) + tag_objs = set(tags) - str_tags + # If str_tags has 0 elements Django actually optimizes that to not do a + # query. Malcolm is very smart. + existing = self.through.tag_model().objects.filter( + name__in=str_tags + ) + tag_objs.update(existing) + + for new_tag in str_tags - set(t.name for t in existing): + tag_objs.add(self.through.tag_model().objects.create(name=new_tag)) + + for tag in tag_objs: + self.through.objects.get_or_create(tag=tag, **self._lookup_kwargs()) + + @require_instance_manager + def set(self, *tags): + self.clear() + self.add(*tags) + + @require_instance_manager + def remove(self, *tags): + self.through.objects.filter(**self._lookup_kwargs()).filter( + tag__name__in=tags).delete() + + @require_instance_manager + def clear(self): + self.through.objects.filter(**self._lookup_kwargs()).delete() + + def most_common(self): + return self.get_query_set().annotate( + num_times=models.Count(self.through.tag_relname()) + ).order_by('-num_times') + + @require_instance_manager + def similar_objects(self): + lookup_kwargs = self._lookup_kwargs() + lookup_keys = sorted(lookup_kwargs) + qs = self.through.objects.values(*lookup_kwargs.keys()) + qs = qs.annotate(n=models.Count('pk')) + qs = qs.exclude(**lookup_kwargs) + qs = qs.filter(tag__in=self.all()) + qs = qs.order_by('-n') + + # TODO: This all feels like a bit of a hack. + items = {} + if len(lookup_keys) == 1: + # Can we do this without a second query by using a select_related() + # somehow? + f = self.through._meta.get_field_by_name(lookup_keys[0])[0] + objs = f.rel.to._default_manager.filter(**{ + "%s__in" % f.rel.field_name: [r["content_object"] for r in qs] + }) + for obj in objs: + items[(getattr(obj, f.rel.field_name),)] = obj + else: + preload = {} + for result in qs: + preload.setdefault(result['content_type'], set()) + preload[result["content_type"]].add(result["object_id"]) + + for ct, obj_ids in preload.iteritems(): + ct = ContentType.objects.get_for_id(ct) + for obj in ct.model_class()._default_manager.filter(pk__in=obj_ids): + items[(ct.pk, obj.pk)] = obj + + results = [] + for result in qs: + obj = items[ + tuple(result[k] for k in lookup_keys) + ] + obj.similar_tags = result["n"] + results.append(obj) + return results + + +def _get_subclasses(model): + subclasses = [model] + for f in model._meta.get_all_field_names(): + field = model._meta.get_field_by_name(f)[0] + if (isinstance(field, RelatedObject) and + getattr(field.field.rel, "parent_link", None)): + subclasses.extend(_get_subclasses(field.model)) + return subclasses -- cgit v1.2.3-70-g09d2