Commit f64167b8 by Alex Gaynor

Added support for querying on tags.

parent d401839e
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models 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.models import Tag, TaggedItem
from taggit.utils import require_instance_manager 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): 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): def __get__(self, instance, type):
manager = _TaggableManager() manager = _TaggableManager()
manager.model = type manager.model = type
...@@ -14,6 +33,43 @@ class TaggableManager(object): ...@@ -14,6 +33,43 @@ class TaggableManager(object):
else: else:
manager.object_id = instance.pk manager.object_id = instance.pk
return manager 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): class _TaggableManager(models.Manager):
......
...@@ -18,4 +18,4 @@ class TaggedItem(models.Model): ...@@ -18,4 +18,4 @@ class TaggedItem(models.Model):
tag = models.ForeignKey(Tag, related_name="items") tag = models.ForeignKey(Tag, related_name="items")
def __unicode__(self): 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): ...@@ -7,3 +7,12 @@ class Food(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
tags = TaggableManager() 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 ...@@ -2,13 +2,15 @@ from django.test import TestCase
from taggit.models import Tag from taggit.models import Tag
from taggit.tests.forms import FoodForm from taggit.tests.forms import FoodForm
from taggit.tests.models import Food from taggit.tests.models import Food, Pet
class BaseTaggingTest(TestCase): class BaseTaggingTest(TestCase):
def assert_tags_equal(self, qs, tags): def assert_tags_equal(self, qs, tags):
tags = Tag.objects.filter(name__in=tags) tags = Tag.objects.filter(name__in=tags)
self.assertEqual(list(qs), list(tags)) self.assertEqual(list(qs), list(tags))
class AddTagTestCase(BaseTaggingTest): class AddTagTestCase(BaseTaggingTest):
def test_add_tag(self): def test_add_tag(self):
apple = Food.objects.create(name="apple") apple = Food.objects.create(name="apple")
...@@ -29,11 +31,29 @@ class AddTagTestCase(BaseTaggingTest): ...@@ -29,11 +31,29 @@ class AddTagTestCase(BaseTaggingTest):
self.assert_tags_equal(Food.tags.all(), ['green', 'red']) self.assert_tags_equal(Food.tags.all(), ['green', 'red'])
self.assert_tags_equal(Food.tags.most_common(), ['green', 'red']) self.assert_tags_equal(Food.tags.most_common(), ['green', 'red'])
apple.tags.remove('green') apple.tags.remove('green')
self.assert_tags_equal(apple.tags.all(), ['red']) self.assert_tags_equal(apple.tags.all(), ['red'])
self.assert_tags_equal(Food.tags.all(), ['green', '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): class TaggableFormTestCase(BaseTaggingTest):
def test_form(self): def test_form(self):
self.assertEqual(FoodForm.base_fields.keys(), ['name', 'tags']) 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