Commit f64167b8 by Alex Gaynor

Added support for querying on tags.

parent d401839e
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.fields.related import ManyToManyRel
from django.db.models.query_utils import QueryWrapper
from taggit.models import Tag, TaggedItem
from taggit.utils import require_instance_manager
class TaggableRel(ManyToManyRel):
def __init__(self):
self.to = TaggedItem
self.related_name = None
self.limit_choices_to = {}
self.symmetrical = True
self.multiple = True
self.through = None
class TaggableManager(object):
def __init__(self):
self.rel = TaggableRel()
self.editable = False
self.unique = False
self.creates_table = False
self.db_column = None
def __get__(self, instance, type):
manager = _TaggableManager()
manager.model = type
......@@ -14,6 +33,43 @@ class TaggableManager(object):
else:
manager.object_id = instance.pk
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)
def save_form_data(self, instance, value):
pass
def get_db_prep_lookup(self, lookup_type, value):
if lookup_type != "in":
raise ValueError("You can't do lookups other than in on Tags")
qs = TaggedItem.objects.filter(tag__name__in=value).values_list("pk", flat=True)
sql, params = qs.query.as_sql()
return QueryWrapper(("(%s)" % sql), params)
def related_query_name(self):
return None
def m2m_reverse_name(self):
return "id"
def m2m_column_name(self):
return "object_id"
def db_type(self):
return None
def m2m_db_table(self):
return self.rel.to._meta.db_table
def extra_filters(self, pieces, pos, negate):
if negate:
return []
prefix = "__".join(pieces[:pos+1])
return [("%s__content_type" % prefix, ContentType.objects.get_for_model(self.model))]
class _TaggableManager(models.Manager):
......
......@@ -18,4 +18,4 @@ class TaggedItem(models.Model):
tag = models.ForeignKey(Tag, related_name="items")
def __unicode__(self):
return "%s tagged with %" % (self.content_object, self.tag)
return "%s tagged with %s" % (self.content_object, self.tag)
......@@ -7,3 +7,12 @@ class Food(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager()
def __unicode__(self):
return self.name
class Pet(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager()
......@@ -2,13 +2,15 @@ from django.test import TestCase
from taggit.models import Tag
from taggit.tests.forms import FoodForm
from taggit.tests.models import Food
from taggit.tests.models import Food, Pet
class BaseTaggingTest(TestCase):
def assert_tags_equal(self, qs, tags):
tags = Tag.objects.filter(name__in=tags)
self.assertEqual(list(qs), list(tags))
class AddTagTestCase(BaseTaggingTest):
def test_add_tag(self):
apple = Food.objects.create(name="apple")
......@@ -29,11 +31,29 @@ class AddTagTestCase(BaseTaggingTest):
self.assert_tags_equal(Food.tags.all(), ['green', 'red'])
self.assert_tags_equal(Food.tags.most_common(), ['green', 'red'])
apple.tags.remove('green')
self.assert_tags_equal(apple.tags.all(), ['red'])
self.assert_tags_equal(Food.tags.all(), ['green', 'red'])
class LookupByTagTestCase(BaseTaggingTest):
def test_lookup_by_tag(self):
apple = Food.objects.create(name="apple")
apple.tags.add("red", "green")
pear = Food.objects.create(name="pear")
pear.tags.add("green")
self.assertEqual(list(Food.objects.filter(tags__in=["red"])), [apple])
self.assertEqual(list(Food.objects.filter(tags__in=["green"])), [apple, pear])
kitty = Pet.objects.create(name="kitty")
kitty.tags.add("fuzzy", "red")
dog = Pet.objects.create(name="dog")
dog.tags.add("woof", "red")
self.assertEqual(list(Food.objects.filter(tags__in=["red"]).distinct()), [apple])
class TaggableFormTestCase(BaseTaggingTest):
def test_form(self):
self.assertEqual(FoodForm.base_fields.keys(), ['name', 'tags'])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment