Permalink
Browse files

Add mirror 'applications/tagging/'.

  • Loading branch information...
1 parent 8b96e13 commit 5d223c68ddf4c0385261a6f6d7d59f52e931e0e4 @bryanveloso bryanveloso committed Jun 22, 2008
View
@@ -5,3 +5,9 @@ applications/template_utils:
revision:
squash: true
remote: http://django-template-utils.googlecode.com/svn/trunk/template_utils
+applications/tagging:
+ local_branch: braid/svn/applications/tagging
+ type: svn
+ revision:
+ squash: true
+ remote: http://django-tagging.googlecode.com/svn/trunk/tagging
@@ -0,0 +1,30 @@
+from django.utils.translation import ugettext as _
+
+from tagging.managers import ModelTaggedItemManager, TagDescriptor
+
+VERSION = (0, 3, 'pre')
+
+class AlreadyRegistered(Exception):
+ """
+ An attempt was made to register a model more than once.
+ """
+ pass
+
+registry = []
+
+def register(model, tag_descriptor_attr='tags',
+ tagged_item_manager_attr='tagged'):
+ """
+ Sets the given model class up for working with tags.
+ """
+ if model in registry:
+ raise AlreadyRegistered(
+ _('The model %s has already been registered.') % model.__name__)
+ registry.append(model)
+
+ # Add tag descriptor
+ setattr(model, tag_descriptor_attr, TagDescriptor())
+
+ # Add custom manager
+ ModelTaggedItemManager().contribute_to_class(model,
+ tagged_item_manager_attr)
@@ -0,0 +1,110 @@
+"""
+A custom Model Field for tagging.
+"""
+from django.db.models import signals
+from django.db.models.fields import CharField
+from django.dispatch import dispatcher
+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 tagging.validators import isTagList
+
+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)
+ kwargs['validator_list'] = [isTagList] + kwargs.get('validator_list', [])
+ super(TagField, self).__init__(*args, **kwargs)
+
+ def contribute_to_class(self, cls, name):
+ 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
+ dispatcher.connect(self._save, signal=signals.post_save, sender=cls)
+
+ 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(Tag.objects.usage_for_model(owner))
+
+ 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, signal, sender, instance):
+ """
+ Save tags back to the database
+ """
+ tags = self._get_instance_tag_cache(instance)
+ if tags is not None:
+ Tag.objects.update_tags(instance, 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)
@@ -0,0 +1,23 @@
+"""
+Tagging components for Django's ``newforms`` form library.
+"""
+from django import newforms as forms
+from django.utils.translation import ugettext as _
+
+from tagging import settings
+from tagging.utils import parse_tag_input
+
+class TagField(forms.CharField):
+ """
+ A ``CharField`` which validates that its input is a valid list of
+ tag names.
+ """
+ def clean(self, value):
+ value = super(TagField, self).clean(value)
+ if value == u'':
+ return value
+ for tag_name in parse_tag_input(value):
+ if len(tag_name) > settings.MAX_TAG_LENGTH:
+ raise forms.ValidationError(
+ _('Each tag may be no more than %s characters long.') % settings.MAX_TAG_LENGTH)
+ return value
@@ -0,0 +1,35 @@
+from django.contrib.contenttypes.models import ContentType
+
+def fetch_content_objects(tagged_items, select_related_for=None):
+ """
+ Retrieves ``ContentType``s and content objects for the given list of
+ ``TaggedItems``, grouping the retrieval of content objects by model
+ to reduce the number of queries executed.
+
+ This results in ``number_of_content_types + 1`` queries rather than
+ the ``number_of_tagged_items * 2`` queries you'd get by iterating
+ over the list and accessing each item's ``object`` attribute.
+ """
+ if select_related_for is None: select_related_for = []
+
+ # Group content object pks by their content type pks
+ objects = {}
+ for item in tagged_items:
+ objects.setdefault(item.content_type_id, []).append(item.object_id)
+
+ # Retrieve content types and content objects in bulk
+ content_types = ContentType._default_manager.in_bulk(objects.keys())
+ for content_type_pk, object_pks in objects.items():
+ model = content_types[content_type_pk].model_class()
+ if not select_related_for:
+ objects[content_type_pk] = model._default_manager.in_bulk(object_pks)
+ elif content_types[content_type_pk].model in select_related_for:
+ objects[content_type_pk] = model._default_manager.select_related().in_bulk(object_pks)
+
+ # Set content types and content objects in the appropriate cache
+ # attributes, so accessing the 'content_type' and 'object'
+ # attributes on each tagged item won't result in further database
+ # hits.
+ for item in tagged_items:
+ item._object_cache = objects[item.content_type_id][item.object_id]
+ item._content_type_cache = content_types[item.content_type_id]
@@ -0,0 +1,68 @@
+"""
+Custom managers for Django models registered with the tagging
+application.
+"""
+from django.contrib.contenttypes.models import ContentType
+from django.db import models
+
+from tagging.models import Tag, TaggedItem
+
+class ModelTagManager(models.Manager):
+ """
+ A manager for retrieving tags for a particular model.
+ """
+ def get_query_set(self):
+ ctype = ContentType.objects.get_for_model(self.model)
+ return Tag.objects.filter(
+ items__content_type__pk=ctype.pk).distinct()
+
+ def cloud(self, *args, **kwargs):
+ return Tag.objects.cloud_for_model(self.model, *args, **kwargs)
+
+ def related(self, tags, *args, **kwargs):
+ return Tag.objects.related_for_model(tags, self.model, *args, **kwargs)
+
+ def usage(self, *args, **kwargs):
+ return Tag.objects.usage_for_model(self.model, *arg, **kwargs)
+
+class ModelTaggedItemManager(models.Manager):
+ """
+ A manager for retrieving model instances based on their tags.
+ """
+ def related_to(self, obj, queryset=None, num=None):
+ if queryset is None:
+ return TaggedItem.objects.get_related(obj, self.model, num=num)
+ else:
+ return TaggedItem.objects.get_related(obj, queryset, num=num)
+
+ def with_all(self, tags, queryset=None):
+ if queryset is None:
+ return TaggedItem.objects.get_by_model(self.model, tags)
+ else:
+ return TaggedItem.objects.get_by_model(queryset, tags)
+
+ def with_any(self, tags, queryset=None):
+ if queryset is None:
+ return TaggedItem.objects.get_union_by_model(self.model, tags)
+ else:
+ return TaggedItem.objects.get_union_by_model(queryset, tags)
+
+class TagDescriptor(object):
+ """
+ A descriptor which provides access to a ``ModelTagManager`` for
+ model classes and simple retrieval, updating and deletion of tags
+ for model instances.
+ """
+ def __get__(self, instance, owner):
+ if not instance:
+ tag_manager = ModelTagManager()
+ tag_manager.model = owner
+ return tag_manager
+ else:
+ return Tag.objects.get_for_object(instance)
+
+ def __set__(self, instance, value):
+ Tag.objects.update_tags(instance, value)
+
+ def __del__(self, instance):
+ Tag.objects.update_tags(instance, None)
Oops, something went wrong.

0 comments on commit 5d223c6

Please sign in to comment.