1. offline
  2. django-tagging2

Commits

offline  committed a504ab6

initial

  • Participants
  • Branches default

Comments (0)

Files changed (3)

File newtags/fields.py

View file
+"""
+A custom Model Field for tagging.
+"""
+import pdb
+from django.db.models import signals
+from django.db.models.fields import CharField
+from django.utils.translation import ugettext_lazy as _
+
+from tagging import settings
+from tagging.models import Tag
+from tagging.utils import edit_string_for_tags
+from managers import TagManager
+import methods
+
+
+class TagField(CharField):
+    """
+    A "special" character field that actually works as a relationship to tags
+    "under the hood". This exposes a space-separated string of tags, but does
+    the splitting/reordering/etc. under the hood.
+    """
+    def __init__(self, *args, **kwargs):
+        kwargs['max_length'] = kwargs.get('max_length', 255)
+        kwargs['blank'] = kwargs.get('blank', True)
+        super(TagField, self).__init__(*args, **kwargs)
+
+    def contribute_to_class(self, cls, name):
+        manager = TagManager()
+        manager.model = cls
+        setattr(cls, 'tagmanager', manager)
+        for k,v in methods.__dict__.items():
+            if hasattr(v, '__call__'):
+                setattr(cls, k, v)
+        super(TagField, self).contribute_to_class(cls, name)
+
+        # Make this object the descriptor for field access.
+        setattr(cls, self.name, self)
+
+
+        # Save tags back to the database post-save
+        signals.post_save.connect(self._save, cls, True)
+
+    def __get__(self, instance, owner=None):
+        """
+        Tag getter. Returns an instance's tags if accessed on an instance, and
+        all of a model's tags if called on a class. That is, this model::
+
+           class Link(models.Model):
+               ...
+               tags = TagField()
+
+        Lets you do both of these::
+
+           >>> l = Link.objects.get(...)
+           >>> l.tags
+           'tag1 tag2 tag3'
+
+           >>> Link.tags
+           'tag1 tag2 tag3 tag4'
+
+        """
+        # Handle access on the model (i.e. Link.tags)
+        if instance is None:
+            return edit_string_for_tags(owner.tagmanager.usage_for_model())
+
+        tags = self._get_instance_tag_cache(instance)
+        if tags is None:
+            if instance.pk is None:
+                self._set_instance_tag_cache(instance, '')
+            else:
+                self._set_instance_tag_cache(
+                    instance, edit_string_for_tags(Tag.objects.get_for_object(instance)))
+        return self._get_instance_tag_cache(instance)
+
+    def __set__(self, instance, value):
+        """
+        Set an object's tags.
+        """
+        if instance is None:
+            raise AttributeError(_('%s can only be set on instances.') % self.name)
+        if settings.FORCE_LOWERCASE_TAGS and value is not None:
+            value = value.lower()
+        self._set_instance_tag_cache(instance, value)
+
+    def _save(self, **kwargs): 
+        """
+        Save tags back to the database
+        """
+        instance = kwargs['instance']
+        tags = self._get_instance_tag_cache(instance)
+        if tags is not None:
+            instance.update_tags(tags)
+
+    def __delete__(self, instance):
+        """
+        Clear all of an object's tags.
+        """
+        self._set_instance_tag_cache(instance, '')
+
+    def _get_instance_tag_cache(self, instance):
+        """
+        Helper: get an instance's tag cache.
+        """
+        return getattr(instance, '_%s_cache' % self.attname, None)
+
+    def _set_instance_tag_cache(self, instance, tags):
+        """
+        Helper: set an instance's tag cache.
+        """
+        setattr(instance, '_%s_cache' % self.attname, tags)
+
+    def get_internal_type(self):
+        return 'CharField'
+
+    def formfield(self, **kwargs):
+        from tagging import forms
+        defaults = {'form_class': forms.TagField}
+        defaults.update(kwargs)
+        return super(TagField, self).formfield(**defaults)

File newtags/managers.py

View file
+from django.contrib.contenttypes.models import ContentType
+from django.db import connection, models
+from django.db.models import Avg, Max, Min, Count
+
+from tagging.models import TaggedItem, Tag
+from tagging.utils import get_tag_list, LOGARITHMIC, calculate_cloud
+
+
+class TagManager(models.Manager):
+    def _get_usage(self, queryset, counts=False, min_count=None, order=None):
+        """
+        Obtain a list of dicts with tag names and counters if counts is True
+        """
+
+        ctype = ContentType.objects.get_for_model(self.model)
+        qs = TaggedItem.objects.filter(content_type=ctype).values('tag__name')
+        if counts:
+            qs = qs.annotate(count=Count('tag')).order_by(order)
+        tags = []
+        for item in qs:
+            tag = {'name': item['tag__name']}
+            if counts:
+                tag.update({'count': item['count']})
+            tags.append(tag)
+        return tags
+
+
+    def usage_for_queryset(self, queryset, counts=False, min_count=None, order=None):
+        """
+        Obtain a list of tags associated with instances of a model
+        contained in the given queryset.
+
+        If ``counts`` is True, a ``count`` attribute will be added to
+        each tag, indicating how many times it has been used against
+        the Model class in question.
+
+        If ``min_count`` is given, only tags which have a ``count``
+        greater than or equal to ``min_count`` will be returned.
+        Passing a value for ``min_count`` implies ``counts=True``.
+        """
+        if not order:
+            order = '-tag__name'
+        return self._get_usage(queryset, counts, min_count, order)
+
+
+    def usage_for_model(self, counts=False, min_count=None, filters=None, order=None):
+        """
+        Obtain a list of tags associated with instances of the given
+        Model class.
+
+        If ``counts`` is True, a ``count`` attribute will be added to
+        each tag, indicating how many times it has been used against
+        the Model class in question.
+
+        If ``min_count`` is given, only tags which have a ``count``
+        greater than or equal to ``min_count`` will be returned.
+        Passing a value for ``min_count`` implies ``counts=True``.
+
+        To limit the tags (and counts, if specified) returned to those
+        used by a subset of the Model's instances, pass a dictionary
+        of field lookups to be applied to the given Model as the
+        ``filters`` argument.
+        """
+        if filters is None: filters = {}
+
+        queryset = self.model._default_manager.filter()
+        for f in filters.items():
+            queryset.query.add_filter(f)
+        return self.usage_for_queryset(queryset, counts, min_count, order)
+
+
+
+    def related_for_model(self, tags, counts=False, min_count=None):
+        """
+        Obtain a list of tags related to a given list of tags - that
+        is, other tags used by items which have all the given tags.
+
+        If ``counts`` is True, a ``count`` attribute will be added to
+        each tag, indicating the number of items which have it in
+        addition to the given list of tags.
+
+        If ``min_count`` is given, only tags which have a ``count``
+        greater than or equal to ``min_count`` will be returned.
+        Passing a value for ``min_count`` implies ``counts=True``.
+        """
+        tags = get_tag_list(tags)
+        tags_id = set([tag.id for tag in tags])
+        tagged_items_id = set([tagged_item.object_id for tagged_item in TaggedItem.objects.filter(tag__id__in=tags_id)])
+        items_id = [item.id for item in self.model._default_manager.filter(pk__in=tagged_items_id)]
+        newtags_id = set([tagged_item.tag.id for tagged_item in TaggedItem.objects.filter(object_id__in=items_id)])
+        return Tag.objects.filter(pk__in=newtags_id)
+
+
+    def cloud_for_model(self, steps=4, distribution=LOGARITHMIC,
+                        filters=None, min_count=None):
+        """
+        Obtain a list of tags associated with instances of the given
+        Model, giving each tag a ``count`` attribute indicating how
+        many times it has been used and a ``font_size`` attribute for
+        use in displaying a tag cloud.
+
+        ``steps`` defines the range of font sizes - ``font_size`` will
+        be an integer between 1 and ``steps`` (inclusive).
+
+        ``distribution`` defines the type of font size distribution
+        algorithm which will be used - logarithmic or linear. It must
+        be either ``tagging.utils.LOGARITHMIC`` or
+        ``tagging.utils.LINEAR``.
+
+        To limit the tags displayed in the cloud to those associated
+        with a subset of the Model's instances, pass a dictionary of
+        field lookups to be applied to the given Model as the
+        ``filters`` argument.
+
+        To limit the tags displayed in the cloud to those with a
+        ``count`` greater than or equal to ``min_count``, pass a value
+        for the ``min_count`` argument.
+        """
+        tags = list(self.usage_for_model(counts=True, filters=filters,
+                                         min_count=min_count))
+        return calculate_cloud(tags, steps, distribution)

File newtags/methods.py

View file
+from django.contrib.contenttypes.models import ContentType
+from tagging.models import Tag, TaggedItem
+from tagging.utils import parse_tag_input
+from tagging import settings
+
+def get_for_object(self):
+    """
+    Create a queryset matching all tags associated with the given
+    object.
+    """
+    ctype = ContentType.objects.get_for_model(self)
+    return Tag.objects.filter(items__content_type__pk=ctype.pk, items__object_id=self.pk)
+
+
+def add_tag(self, tag_name):
+    """
+    Associates the given object with a tag.
+    """
+    tag_names = parse_tag_input(tag_name)
+    if not len(tag_names):
+       raise AttributeError(_('No tags were given: "%s".') % tag_name)
+    if len(tag_names) > 1:
+       raise AttributeError(_('Multiple tags were given: "%s".') % tag_name)
+    tag_name = tag_names[0]
+    if settings.FORCE_LOWERCASE_TAGS:
+        tag_name = tag_name.lower()
+    tag, created = Tag.objects.get_or_create(name=tag_name)
+    ctype = ContentType.objects.get_for_model(self)
+    TaggedItem._default_manager.get_or_create(tag=tag, content_type=ctype, object_id=self.pk)
+    
+    
+    
+def update_tags(self, tag_names):
+    """
+    Update tags associated with an object.
+    """
+    ctype = ContentType.objects.get_for_model(self)
+    current_tags = list(Tag.objects.filter(items__content_type=ctype, items__object_id=self.pk))
+    updated_tag_names = parse_tag_input(tag_names)
+    if settings.FORCE_LOWERCASE_TAGS:
+        updated_tag_names = [t.lower() for t in updated_tag_names]
+
+        # Remove tags which no longer apply
+    tags_for_removal = [tag for tag in current_tags if tag.name not in updated_tag_names]
+    if len(tags_for_removal):
+        TaggedItem._default_manager.filter(content_type=ctype, object_id=self.pk,
+                                               tag__in=tags_for_removal).delete()
+        # Add new tags
+    current_tag_names = [tag.name for tag in current_tags]
+    for tag_name in updated_tag_names:
+        if tag_name not in current_tag_names:
+            tag, created = Tag.objects.get_or_create(name=tag_name)
+            TaggedItem._default_manager.create(tag=tag, object=self)