aboutsummaryrefslogtreecommitdiff
path: root/tagging/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'tagging/models.py')
-rw-r--r--tagging/models.py131
1 files changed, 79 insertions, 52 deletions
diff --git a/tagging/models.py b/tagging/models.py
index 860cf81..9e89e43 100644
--- a/tagging/models.py
+++ b/tagging/models.py
@@ -1,29 +1,31 @@
"""
-Models and managers for generic tagging.
+Models and managers for tagging.
"""
-# Python 2.3 compatibility
-try:
- set
-except NameError:
- from sets import Set as set
-
-from django.contrib.contenttypes import generic
-from django.contrib.contenttypes.models import ContentType
-from django.db import connection, models
-from django.db.models.query import QuerySet
+from django.db import models
+from django.db import connection
+from django.utils.encoding import smart_text
+from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes.fields import GenericForeignKey
from tagging import settings
-from tagging.utils import calculate_cloud, get_tag_list, get_queryset_and_model, parse_tag_input
from tagging.utils import LOGARITHMIC
+from tagging.utils import get_tag_list
+from tagging.utils import calculate_cloud
+from tagging.utils import parse_tag_input
+from tagging.utils import get_queryset_and_model
+
qn = connection.ops.quote_name
+
############
# Managers #
############
class TagManager(models.Manager):
+
def update_tags(self, obj, tag_names):
"""
Update tags associated with an object.
@@ -36,12 +38,13 @@ class TagManager(models.Manager):
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 \
+ 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__pk=ctype.pk,
- object_id=obj.pk,
- tag__in=tags_for_removal).delete()
+ TaggedItem._default_manager.filter(
+ content_type__pk=ctype.pk,
+ object_id=obj.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:
@@ -55,9 +58,11 @@ class TagManager(models.Manager):
"""
tag_names = parse_tag_input(tag_name)
if not len(tag_names):
- raise AttributeError(_('No tags were given: "%s".') % tag_name)
+ raise AttributeError(
+ _('No tags were given: "%s".') % tag_name)
if len(tag_names) > 1:
- raise AttributeError(_('Multiple tags were given: "%s".') % tag_name)
+ raise AttributeError(
+ _('Multiple tags were given: "%s".') % tag_name)
tag_name = tag_names[0]
if settings.FORCE_LOWERCASE_TAGS:
tag_name = tag_name.lower()
@@ -75,12 +80,14 @@ class TagManager(models.Manager):
return self.filter(items__content_type__pk=ctype.pk,
items__object_id=obj.pk)
- def _get_usage(self, model, counts=False, min_count=None, extra_joins=None, extra_criteria=None, params=None):
+ def _get_usage(self, model, counts=False, min_count=None,
+ extra_joins=None, extra_criteria=None, params=None):
"""
Perform the custom SQL query for ``usage_for_model`` and
``usage_for_queryset``.
"""
- if min_count is not None: counts = True
+ if min_count is not None:
+ counts = True
model_table = qn(model._meta.db_table)
model_pk = '%s.%s' % (model_table, qn(model._meta.pk.column))
@@ -112,7 +119,8 @@ class TagManager(models.Manager):
params.append(min_count)
cursor = connection.cursor()
- cursor.execute(query % (extra_joins, extra_criteria, min_count_sql), params)
+ cursor.execute(query % (extra_joins, extra_criteria, min_count_sql),
+ params)
tags = []
for row in cursor.fetchall():
t = self.model(*row[:2])
@@ -121,7 +129,8 @@ class TagManager(models.Manager):
tags.append(t)
return tags
- def usage_for_model(self, model, counts=False, min_count=None, filters=None):
+ def usage_for_model(self, model, counts=False, min_count=None,
+ filters=None):
"""
Obtain a list of tags associated with instances of the given
Model class.
@@ -139,7 +148,8 @@ class TagManager(models.Manager):
of field lookups to be applied to the given Model as the
``filters`` argument.
"""
- if filters is None: filters = {}
+ if filters is None:
+ filters = {}
queryset = model._default_manager.filter()
for f in filters.items():
@@ -161,24 +171,16 @@ class TagManager(models.Manager):
greater than or equal to ``min_count`` will be returned.
Passing a value for ``min_count`` implies ``counts=True``.
"""
-
- 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()
+ compiler = queryset.query.get_compiler(using=queryset.db)
+ where, params = compiler.compile(queryset.query.where)
+ extra_joins = ' '.join(compiler.get_from_clause()[0][1:])
if where:
extra_criteria = 'AND %s' % where
else:
extra_criteria = ''
- return self._get_usage(queryset.model, counts, min_count, extra_joins, extra_criteria, params)
+ return self._get_usage(queryset.model, counts, min_count,
+ extra_joins, extra_criteria, params)
def related_for_model(self, tags, model, counts=False, min_count=None):
"""
@@ -193,13 +195,16 @@ class TagManager(models.Manager):
greater than or equal to ``min_count`` will be returned.
Passing a value for ``min_count`` implies ``counts=True``.
"""
- if min_count is not None: counts = True
+ if min_count is not None:
+ counts = True
+
tags = get_tag_list(tags)
tag_count = len(tags)
tagged_item_table = qn(TaggedItem._meta.db_table)
query = """
SELECT %(tag)s.id, %(tag)s.name%(count_sql)s
- FROM %(tagged_item)s INNER JOIN %(tag)s ON %(tagged_item)s.tag_id = %(tag)s.id
+ FROM %(tagged_item)s INNER JOIN %(tag)s ON
+ %(tagged_item)s.tag_id = %(tag)s.id
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tagged_item)s.object_id IN
(
@@ -216,12 +221,14 @@ class TagManager(models.Manager):
%(min_count_sql)s
ORDER BY %(tag)s.name ASC""" % {
'tag': qn(self.model._meta.db_table),
- 'count_sql': counts and ', COUNT(%s.object_id)' % tagged_item_table or '',
+ 'count_sql': counts and ', COUNT(%s.object_id)' %
+ tagged_item_table or '',
'tagged_item': tagged_item_table,
'content_type_id': ContentType.objects.get_for_model(model).pk,
'tag_id_placeholders': ','.join(['%s'] * tag_count),
'tag_count': tag_count,
- 'min_count_sql': min_count is not None and ('HAVING COUNT(%s.object_id) >= %%s' % tagged_item_table) or '',
+ 'min_count_sql': min_count is not None and (
+ 'HAVING COUNT(%s.object_id) >= %%s' % tagged_item_table) or '',
}
params = [tag.pk for tag in tags] * 2
@@ -267,6 +274,7 @@ class TagManager(models.Manager):
min_count=min_count))
return calculate_cloud(tags, steps, distribution)
+
class TaggedItemManager(models.Manager):
"""
FIXME There's currently no way to get the ``GROUP BY`` and ``HAVING``
@@ -280,6 +288,7 @@ class TaggedItemManager(models.Manager):
Now that the queryset-refactor branch is in the trunk, this can be
tidied up significantly.
"""
+
def get_by_model(self, queryset_or_model, tags):
"""
Create a ``QuerySet`` containing instances of the specified
@@ -405,7 +414,8 @@ class TaggedItemManager(models.Manager):
related_content_type = ContentType.objects.get_for_model(model)
query = """
SELECT %(model_pk)s, COUNT(related_tagged_item.object_id) AS %(count)s
- FROM %(model)s, %(tagged_item)s, %(tag)s, %(tagged_item)s related_tagged_item
+ FROM %(model)s, %(tagged_item)s, %(tag)s,
+ %(tagged_item)s related_tagged_item
WHERE %(tagged_item)s.object_id = %%s
AND %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tag)s.id = %(tagged_item)s.tag_id
@@ -441,23 +451,27 @@ class TaggedItemManager(models.Manager):
cursor.execute(query, params)
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
- # Use in_bulk here instead of an id__in lookup, because id__in would
- # clobber the ordering.
+ # Use in_bulk here instead of an id__in lookup,
+ # because id__in would clobber the ordering.
object_dict = queryset.in_bulk(object_ids)
- return [object_dict[object_id] for object_id in object_ids \
+ return [object_dict[object_id] for object_id in object_ids
if object_id in object_dict]
else:
return []
+
##########
# Models #
##########
+@python_2_unicode_compatible
class Tag(models.Model):
"""
A tag.
"""
- name = models.CharField(_('name'), max_length=50, unique=True, db_index=True)
+ name = models.CharField(
+ _('name'), max_length=settings.MAX_TAG_LENGTH,
+ unique=True, db_index=True)
objects = TagManager()
@@ -466,17 +480,30 @@ class Tag(models.Model):
verbose_name = _('tag')
verbose_name_plural = _('tags')
- def __unicode__(self):
+ def __str__(self):
return self.name
+
+@python_2_unicode_compatible
class TaggedItem(models.Model):
"""
Holds the relationship between a tag and the item being tagged.
"""
- tag = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items')
- content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
- object_id = models.PositiveIntegerField(_('object id'), db_index=True)
- object = generic.GenericForeignKey('content_type', 'object_id')
+ tag = models.ForeignKey(
+ Tag,
+ verbose_name=_('tag'),
+ related_name='items')
+
+ content_type = models.ForeignKey(
+ ContentType,
+ verbose_name=_('content type'))
+
+ object_id = models.PositiveIntegerField(
+ _('object id'),
+ db_index=True)
+
+ object = GenericForeignKey(
+ 'content_type', 'object_id')
objects = TaggedItemManager()
@@ -486,5 +513,5 @@ class TaggedItem(models.Model):
verbose_name = _('tagged item')
verbose_name_plural = _('tagged items')
- def __unicode__(self):
- return u'%s [%s]' % (self.object, self.tag)
+ def __str__(self):
+ return '%s [%s]' % (smart_text(self.object), smart_text(self.tag))