Commit f8e0d4d2 by Carl Meyer

tests for direct-tagging

parent 7ee9fd3a
......@@ -5,7 +5,6 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.generic import GenericForeignKey
from django.db import models
from django.db.models.related import RelatedObject
from django.db.models.fields import FieldDoesNotExist
from django.db.models.fields.related import ManyToManyRel
from django.db.models.query_utils import QueryWrapper
......@@ -126,17 +125,16 @@ class TaggableManager(object):
if len(cts) == 1:
return [("%s__content_type" % prefix, cts[0])]
return [("%s__content_type__in" % prefix, cts)]
return self.through._meta.db_table
class _TaggableManager(models.Manager):
def __init__(self, through=None):
self.through = through or TaggedItem
def __init__(self, through):
self.through = through
def get_query_set(self):
return self.through.tags_for(self.model, self.instance)
def lookup_kwargs(self):
def _lookup_kwargs(self):
return self.through.lookup_kwargs(self.instance)
@require_instance_manager
......@@ -144,7 +142,7 @@ class _TaggableManager(models.Manager):
for tag in tags:
if not isinstance(tag, Tag):
tag, _ = Tag.objects.get_or_create(name=tag)
self.through.objects.get_or_create(**dict(self.lookup_kwargs(),
self.through.objects.get_or_create(**dict(self._lookup_kwargs(),
tag=tag))
@require_instance_manager
......@@ -154,12 +152,12 @@ class _TaggableManager(models.Manager):
@require_instance_manager
def remove(self, *tags):
self.through.objects.filter(**self.lookup_kwargs()).filter(
self.through.objects.filter(**self._lookup_kwargs()).filter(
tag__name__in=tags).delete()
@require_instance_manager
def clear(self):
self.through.objects.filter(**self.lookup_kwargs()).delete()
self.through.objects.filter(**self._lookup_kwargs()).delete()
def most_common(self):
return self.get_query_set().annotate(
......@@ -168,13 +166,19 @@ class _TaggableManager(models.Manager):
@require_instance_manager
def similar_objects(self):
qs = self.through.objects.values(*self.lookup_kwargs().keys())
qs = self.through.objects.values(*self._lookup_kwargs().keys())
qs = qs.annotate(n=models.Count('pk'))
qs = qs.exclude(**self.lookup_kwargs())
qs = qs.exclude(**self._lookup_kwargs())
qs = qs.filter(tag__in=self.all())
qs = qs.order_by('-n')
if not 'content_object' in self.lookup_kwargs():
if 'content_object' in self._lookup_kwargs():
using_gfk = False
items = dict([(o.pk, o) for o in
self.through._meta.get_field('content_object').rel.to.objects.filter(
pk__in=[r['content_object'] for r in qs])])
else:
using_gfk = True
preload = defaultdict(set)
for result in qs:
preload[result["content_type"]].add(result["object_id"])
......@@ -187,10 +191,10 @@ class _TaggableManager(models.Manager):
results = []
for result in qs:
try:
obj = result['content_object']
except KeyError:
if using_gfk:
obj = items[result["content_type"]][result["object_id"]]
else:
obj = items[result["content_object"]]
obj.similar_tags = result["n"]
results.append(obj)
return results
......
import django
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.generic import GenericForeignKey
from django.db import models, IntegrityError
......@@ -26,7 +27,10 @@ class Tag(models.Model):
class TaggedItemBase(models.Model):
tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items")
if django.VERSION < (1, 2):
tag = models.ForeignKey(Tag, related_name="%(class)s_items")
else:
tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items")
def __unicode__(self):
return "%s tagged with %s" % (self.content_object, self.tag)
......@@ -47,7 +51,7 @@ class TaggedItemBase(models.Model):
if instance is not None:
return Tag.objects.filter(**{'%s__content_object' % cls.tag_relname(): instance})
else:
return Tag.objects.filter(**{'%s__content_object__isnull' % cls.tag_relname(): False})
return Tag.objects.filter(**{'%s__content_object__isnull' % cls.tag_relname(): False}).distinct()
class TaggedItem(TaggedItemBase):
......
from taggit.tests.tests import (AddTagTestCase, DeleteObjecTestCase,
LookupByTagTestCase, TaggableFormTestCase, SimilarityByTagTestCase,
TagReuseTestCase)
from taggit.tests.tests import (TaggableManagerTestCase, TaggableManagerDirectTestCase,
TaggableFormTestCase, TaggableFormDirectTestCase)
from django import forms
from taggit.tests.models import Food
from taggit.tests.models import Food, DirectFood
class FoodForm(forms.ModelForm):
class Meta:
model = Food
class DirectFoodForm(forms.ModelForm):
class Meta:
model = DirectFood
from django.db import models
from taggit.managers import TaggableManager
from taggit.models import TaggedItemBase
class Food(models.Model):
name = models.CharField(max_length=50)
......@@ -11,11 +11,29 @@ class Food(models.Model):
def __unicode__(self):
return self.name
class Pet(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager()
class HousePet(Pet):
trained = models.BooleanField()
\ No newline at end of file
trained = models.BooleanField()
class TaggedFood(TaggedItemBase):
content_object = models.ForeignKey('DirectFood')
class DirectFood(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager(through=TaggedFood)
class TaggedPet(TaggedItemBase):
content_object = models.ForeignKey('DirectPet')
class DirectPet(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager(through=TaggedPet)
class DirectHousePet(DirectPet):
trained = models.BooleanField()
......@@ -4,8 +4,8 @@ from contextlib import contextmanager
from django.test import TestCase
from taggit.models import Tag
from taggit.tests.forms import FoodForm
from taggit.tests.models import Food, Pet, HousePet
from taggit.tests.forms import FoodForm, DirectFoodForm
from taggit.tests.models import Food, Pet, HousePet, DirectFood, DirectPet, DirectHousePet
class BaseTaggingTest(TestCase):
......@@ -27,128 +27,136 @@ class BaseTaggingTest(TestCase):
self.fail("No exception raised, expected %s" % exc_type)
class AddTagTestCase(BaseTaggingTest):
class TaggableManagerTestCase(BaseTaggingTest):
food_model = Food
pet_model = Pet
housepet_model = HousePet
def test_add_tag(self):
apple = Food.objects.create(name="apple")
apple = self.food_model.objects.create(name="apple")
self.assertEqual(list(apple.tags.all()), [])
self.assertEqual(list(Food.tags.all()), [])
self.assertEqual(list(self.food_model.tags.all()), [])
apple.tags.add('green')
self.assert_tags_equal(apple.tags.all(), ['green'])
self.assert_tags_equal(Food.tags.all(), ['green'])
self.assert_tags_equal(self.food_model.tags.all(), ['green'])
pear = Food.objects.create(name="pear")
pear = self.food_model.objects.create(name="pear")
pear.tags.add('green')
self.assert_tags_equal(pear.tags.all(), ['green'])
self.assert_tags_equal(Food.tags.all(), ['green'])
self.assert_tags_equal(self.food_model.tags.all(), ['green'])
apple.tags.add('red')
self.assert_tags_equal(apple.tags.all(), ['green', 'red'])
self.assert_tags_equal(Food.tags.all(), ['green', 'red'])
self.assert_tags_equal(self.food_model.tags.all(), ['green', 'red'])
self.assert_tags_equal(Food.tags.most_common(), ['green', 'red'], sort=False)
self.assert_tags_equal(self.food_model.tags.most_common(), ['green', 'red'], sort=False)
apple.tags.remove('green')
self.assert_tags_equal(apple.tags.all(), ['red'])
self.assert_tags_equal(Food.tags.all(), ['green', 'red'])
self.assert_tags_equal(self.food_model.tags.all(), ['green', 'red'])
tag = Tag.objects.create(name="delicious")
apple.tags.add(tag)
self.assert_tags_equal(apple.tags.all(), ["red", "delicious"])
apple.delete()
self.assert_tags_equal(Food.tags.all(), ["green"])
self.assert_tags_equal(self.food_model.tags.all(), ["green"])
f = Food()
f = self.food_model()
with self.assert_raises(ValueError):
f.tags.all()
def test_unique_slug(self):
apple = Food.objects.create(name="apple")
apple = self.food_model.objects.create(name="apple")
apple.tags.add("Red", "red")
class DeleteObjecTestCase(BaseTaggingTest):
def test_delete_obj(self):
apple = Food.objects.create(name="apple")
apple = self.food_model.objects.create(name="apple")
apple.tags.add("red")
self.assert_tags_equal(apple.tags.all(), ["red"])
strawberry = Food.objects.create(name="strawberry")
strawberry = self.food_model.objects.create(name="strawberry")
strawberry.tags.add("red")
apple.delete()
self.assert_tags_equal(strawberry.tags.all(), ["red"])
class LookupByTagTestCase(BaseTaggingTest):
def test_lookup_by_tag(self):
apple = Food.objects.create(name="apple")
apple = self.food_model.objects.create(name="apple")
apple.tags.add("red", "green")
pear = Food.objects.create(name="pear")
pear = self.food_model.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])
self.assertEqual(list(self.food_model.objects.filter(tags__in=["red"])), [apple])
self.assertEqual(list(self.food_model.objects.filter(tags__in=["green"])), [apple, pear])
kitty = Pet.objects.create(name="kitty")
kitty = self.pet_model.objects.create(name="kitty")
kitty.tags.add("fuzzy", "red")
dog = Pet.objects.create(name="dog")
dog = self.pet_model.objects.create(name="dog")
dog.tags.add("woof", "red")
self.assertEqual(list(Food.objects.filter(tags__in=["red"]).distinct()), [apple])
self.assertEqual(list(self.food_model.objects.filter(tags__in=["red"]).distinct()), [apple])
tag = Tag.objects.get(name="woof")
self.assertEqual(list(Pet.objects.filter(tags__in=[tag])), [dog])
self.assertEqual(list(self.pet_model.objects.filter(tags__in=[tag])), [dog])
cat = HousePet.objects.create(name="cat", trained=True)
cat = self.housepet_model.objects.create(name="cat", trained=True)
cat.tags.add("fuzzy")
self.assertEqual(
map(lambda o: o.pk, Pet.objects.filter(tags__in=["fuzzy"])),
map(lambda o: o.pk, self.pet_model.objects.filter(tags__in=["fuzzy"])),
[kitty.pk, cat.pk]
)
def test_similarity_by_tag(self):
"""Test that pears are more similar to apples than watermelons"""
apple = self.food_model.objects.create(name="apple")
apple.tags.add("green", "juicy", "small", "sour")
pear = self.food_model.objects.create(name="pear")
pear.tags.add("green", "juicy", "small", "sweet")
watermelon = self.food_model.objects.create(name="watermelon")
watermelon.tags.add("green", "juicy", "large", "sweet")
similar_objs = apple.tags.similar_objects()
self.assertEqual(similar_objs, [pear, watermelon])
self.assertEqual(map(lambda x: x.similar_tags, similar_objs), [3, 2])
def test_tag_reuse(self):
apple = self.food_model.objects.create(name="apple")
apple.tags.add("juicy", "juicy")
self.assert_tags_equal(apple.tags.all(), ['juicy'])
class TaggableManagerDirectTestCase(TaggableManagerTestCase):
food_model = DirectFood
pet_model = DirectPet
housepet_model = DirectHousePet
class TaggableFormTestCase(BaseTaggingTest):
form_class = FoodForm
food_model = Food
def test_form(self):
self.assertEqual(FoodForm.base_fields.keys(), ['name', 'tags'])
self.assertEqual(self.form_class.base_fields.keys(), ['name', 'tags'])
f = FoodForm({'name': 'apple', 'tags': 'green, red, yummy'})
f = self.form_class({'name': 'apple', 'tags': 'green, red, yummy'})
self.assertEqual(str(f), """<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" value="apple" maxlength="50" /></td></tr>\n<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value="green, red, yummy" id="id_tags" /></td></tr>""")
f.save()
apple = Food.objects.get(name='apple')
apple = self.food_model.objects.get(name='apple')
self.assert_tags_equal(apple.tags.all(), ['green', 'red', 'yummy'])
f = FoodForm({'name': 'apple', 'tags': 'green, red, yummy, delicious'}, instance=apple)
f = self.form_class({'name': 'apple', 'tags': 'green, red, yummy, delicious'}, instance=apple)
f.save()
apple = Food.objects.get(name='apple')
apple = self.food_model.objects.get(name='apple')
self.assert_tags_equal(apple.tags.all(), ['green', 'red', 'yummy', 'delicious'])
self.assertEqual(Food.objects.count(), 1)
self.assertEqual(self.food_model.objects.count(), 1)
f = FoodForm({"name": "raspberry"})
f = self.form_class({"name": "raspberry"})
raspberry = f.save()
self.assert_tags_equal(raspberry.tags.all(), [])
f = FoodForm(instance=apple)
f = self.form_class(instance=apple)
self.assertEqual(str(f), """<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" value="apple" maxlength="50" /></td></tr>\n<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value="green, red, yummy, delicious" id="id_tags" /></td></tr>""")
class SimilarityByTagTestCase(BaseTaggingTest):
def test_similarity_by_tag(self):
"""Test that pears are more similar to apples than watermelons"""
apple = Food.objects.create(name="apple")
apple.tags.add("green", "juicy", "small", "sour")
pear = Food.objects.create(name="pear")
pear.tags.add("green", "juicy", "small", "sweet")
watermelon = Food.objects.create(name="watermelon")
watermelon.tags.add("green", "juicy", "large", "sweet")
similar_objs = apple.tags.similar_objects()
self.assertEqual(similar_objs, [pear, watermelon])
self.assertEqual(map(lambda x: x.similar_tags, similar_objs), [3, 2])
class TagReuseTestCase(BaseTaggingTest):
def test_tag_reuse(self):
apple = Food.objects.create(name="apple")
apple.tags.add("juicy", "juicy")
self.assert_tags_equal(apple.tags.all(), ['juicy'])
class TaggableFormDirectTestCase(TaggableFormTestCase):
form_class = DirectFoodForm
food_model = DirectFood
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