aboutsummaryrefslogtreecommitdiff
path: root/tagging/tests/tests.py
diff options
context:
space:
mode:
Diffstat (limited to 'tagging/tests/tests.py')
-rw-r--r--tagging/tests/tests.py384
1 files changed, 384 insertions, 0 deletions
diff --git a/tagging/tests/tests.py b/tagging/tests/tests.py
new file mode 100644
index 0000000..41a7895
--- /dev/null
+++ b/tagging/tests/tests.py
@@ -0,0 +1,384 @@
+# -*- coding: utf-8 -*-
+r"""
+>>> import os
+>>> from django import newforms as forms
+>>> from tagging import settings
+>>> from tagging.models import Tag, TaggedItem
+>>> from tagging.tests.models import Article, Link, Perch, Parrot, FormTest
+>>> from tagging.utils import calculate_cloud, get_tag_name_list, get_tag_list, get_tag, LINEAR
+>>> from tagging.validators import isTagList, isTag
+>>> from tagging.forms import TagField
+
+#############
+# Utilities #
+#############
+
+# Tag input ###################################################################
+
+# Tag names
+>>> get_tag_name_list(None)
+[]
+>>> get_tag_name_list('')
+[]
+>>> get_tag_name_list('foo')
+[u'foo']
+>>> get_tag_name_list('foo bar')
+[u'foo', u'bar']
+>>> get_tag_name_list('foo,bar')
+[u'foo', u'bar']
+>>> get_tag_name_list(', , foo , bar , ,baz, , ,')
+[u'foo', u'bar', u'baz']
+>>> get_tag_name_list('foo,ŠĐĆŽćžšđ')
+[u'foo', u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111']
+
+# Normalised Tag list input
+>>> cheese = Tag.objects.create(name='cheese')
+>>> toast = Tag.objects.create(name='toast')
+>>> get_tag_list(cheese)
+[<Tag: cheese>]
+>>> get_tag_list('cheese toast')
+[<Tag: cheese>, <Tag: toast>]
+>>> get_tag_list(u'cheese toast')
+[<Tag: cheese>, <Tag: toast>]
+>>> get_tag_list([])
+[]
+>>> get_tag_list(['cheese', 'toast'])
+[<Tag: cheese>, <Tag: toast>]
+>>> get_tag_list([cheese.id, toast.id])
+[<Tag: cheese>, <Tag: toast>]
+>>> get_tag_list(['cheese', 'toast', 'ŠĐĆŽćžšđ'])
+[<Tag: cheese>, <Tag: toast>]
+>>> get_tag_list([cheese, toast])
+[<Tag: cheese>, <Tag: toast>]
+>>> get_tag_list((cheese, toast))
+(<Tag: cheese>, <Tag: toast>)
+>>> get_tag_list(Tag.objects.filter(name__in=['cheese', 'toast']))
+[<Tag: cheese>, <Tag: toast>]
+>>> get_tag_list(['cheese', toast])
+Traceback (most recent call last):
+ ...
+ValueError: If a list or tuple of tags is provided, they must all be tag names, Tag objects or Tag ids.
+>>> get_tag_list(29)
+Traceback (most recent call last):
+ ...
+ValueError: The tag input given was invalid.
+
+# Normalised Tag input
+>>> get_tag(cheese)
+<Tag: cheese>
+>>> get_tag('cheese')
+<Tag: cheese>
+>>> get_tag(cheese.id)
+<Tag: cheese>
+>>> get_tag('mouse')
+
+# Tag clouds ##################################################################
+>>> tags = []
+>>> for line in open(os.path.join(os.path.dirname(__file__), 'tags.txt')).readlines():
+... name, count = line.rstrip().split()
+... tag = Tag(name=name)
+... tag.count = int(count)
+... tags.append(tag)
+
+>>> sizes = {}
+>>> for tag in calculate_cloud(tags, steps=5):
+... sizes[tag.font_size] = sizes.get(tag.font_size, 0) + 1
+
+# This isn't a pre-calculated test, just making sure it's consistent
+>>> sizes
+{1: 48, 2: 20, 3: 24, 4: 19, 5: 11}
+
+>>> sizes = {}
+>>> for tag in calculate_cloud(tags, steps=5, distribution=LINEAR):
+... sizes[tag.font_size] = sizes.get(tag.font_size, 0) + 1
+
+# This isn't a pre-calculated test, just making sure it's consistent
+>>> sizes
+{1: 97, 2: 12, 3: 7, 4: 2, 5: 4}
+
+>>> calculate_cloud(tags, steps=5, distribution='cheese')
+Traceback (most recent call last):
+ ...
+ValueError: Invalid font size distribution algorithm specified: cheese.
+
+##############
+# Validators #
+##############
+
+>>> isTagList('foo', {})
+>>> isTagList('foo bar baz', {})
+>>> isTagList('foo,bar,baz', {})
+>>> isTagList('foo, bar, baz', {})
+>>> isTagList('foo, ŠĐĆŽćžšđ, baz', {})
+>>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar', {})
+>>> isTagList('', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens, with a comma, space or comma followed by space used to separate each tag name.']
+>>> isTagList(' foo', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens, with a comma, space or comma followed by space used to separate each tag name.']
+>>> isTagList('foo ', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens, with a comma, space or comma followed by space used to separate each tag name.']
+>>> isTagList('foo bar', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens, with a comma, space or comma followed by space used to separate each tag name.']
+>>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must be no longer than 50 characters.']
+>>> isTag('f-o_1o', {})
+>>> isTag('ŠĐĆŽćžšđ', {})
+>>> isTag('f o o', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens.']
+
+###############
+# Form Fields #
+###############
+
+>>> t = TagField()
+>>> t.clean('foo')
+u'foo'
+>>> t.clean('foo bar baz')
+u'foo bar baz'
+>>> t.clean('foo,bar,baz')
+u'foo,bar,baz'
+>>> t.clean('foo, bar, baz')
+u'foo, bar, baz'
+>>> t.clean('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar')
+u'foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar'
+>>> t.clean(' foo')
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens, with a comma, space or comma followed by space used to separate each tag name.']
+>>> t.clean('foo ')
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens, with a comma, space or comma followed by space used to separate each tag name.']
+>>> t.clean('foo bar')
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens, with a comma, space or comma followed by space used to separate each tag name.']
+>>> t.clean('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar')
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Tag names must be no longer than 50 characters.']
+
+# Ensure that automatically created forms use TagField
+>>> TestForm = forms.form_for_model(FormTest)
+>>> form = TestForm()
+>>> form.fields['tags'].__class__.__name__
+'TagField'
+>>> instance = FormTest(tags='one two three')
+>>> TestInstanceForm = forms.form_for_instance(instance)
+>>> form = TestInstanceForm()
+>>> form.fields['tags'].__class__.__name__
+'TagField'
+
+###########
+# Tagging #
+###########
+
+# Basic tagging ###############################################################
+
+>>> dead = Parrot.objects.create(state='dead')
+>>> Tag.objects.update_tags(dead, 'foo bar ter')
+>>> Tag.objects.get_for_object(dead)
+[<Tag: bar>, <Tag: foo>, <Tag: ter>]
+>>> Tag.objects.update_tags(dead, 'foo bar baz')
+>>> Tag.objects.get_for_object(dead)
+[<Tag: bar>, <Tag: baz>, <Tag: foo>]
+>>> Tag.objects.add_tag(dead, 'foo')
+>>> Tag.objects.get_for_object(dead)
+[<Tag: bar>, <Tag: baz>, <Tag: foo>]
+>>> Tag.objects.add_tag(dead, 'zip')
+>>> Tag.objects.get_for_object(dead)
+[<Tag: bar>, <Tag: baz>, <Tag: foo>, <Tag: zip>]
+>>> Tag.objects.add_tag(dead, 'f o o')
+Traceback (most recent call last):
+ ...
+AttributeError: An invalid tag name was given: f o o. Tag names must contain only unicode alphanumeric characters, numbers, underscores or hyphens.
+
+# Note that doctest in Python 2.4 (and maybe 2.5?) doesn't support non-ascii
+# characters in output, so we're displaying the repr() here.
+>>> Tag.objects.update_tags(dead, 'ŠĐĆŽćžšđ')
+>>> repr(Tag.objects.get_for_object(dead))
+'[<Tag: \xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91>]'
+
+>>> Tag.objects.update_tags(dead, None)
+>>> Tag.objects.get_for_object(dead)
+[]
+
+# Using a model's TagField
+>>> f1 = FormTest.objects.create(tags=u'test3 test2 test1')
+>>> Tag.objects.get_for_object(f1)
+[<Tag: test1>, <Tag: test2>, <Tag: test3>]
+>>> f1.tags = u'test4'
+>>> f1.save()
+>>> Tag.objects.get_for_object(f1)
+[<Tag: test4>]
+>>> f1.tags = ''
+>>> f1.save()
+>>> Tag.objects.get_for_object(f1)
+[]
+
+# Forcing tags to lowercase
+>>> settings.FORCE_LOWERCASE_TAGS = True
+>>> Tag.objects.update_tags(dead, 'foO bAr Ter')
+>>> Tag.objects.get_for_object(dead)
+[<Tag: bar>, <Tag: foo>, <Tag: ter>]
+>>> Tag.objects.update_tags(dead, 'foO bAr baZ')
+>>> Tag.objects.get_for_object(dead)
+[<Tag: bar>, <Tag: baz>, <Tag: foo>]
+>>> Tag.objects.add_tag(dead, 'FOO')
+>>> Tag.objects.get_for_object(dead)
+[<Tag: bar>, <Tag: baz>, <Tag: foo>]
+>>> Tag.objects.add_tag(dead, 'Zip')
+>>> Tag.objects.get_for_object(dead)
+[<Tag: bar>, <Tag: baz>, <Tag: foo>, <Tag: zip>]
+>>> Tag.objects.update_tags(dead, None)
+>>> f1.tags = u'TEST5'
+>>> f1.save()
+>>> Tag.objects.get_for_object(f1)
+[<Tag: test5>]
+>>> f1.tags
+u'test5'
+
+# Retrieving tags by Model ####################################################
+
+>>> Tag.objects.usage_for_model(Parrot)
+[]
+>>> parrot_details = (
+... ('pining for the fjords', 9, True, 'foo bar'),
+... ('passed on', 6, False, 'bar baz ter'),
+... ('no more', 4, True, 'foo ter'),
+... ('late', 2, False, 'bar ter'),
+... )
+
+>>> for state, perch_size, perch_smelly, tags in parrot_details:
+... perch = Perch.objects.create(size=perch_size, smelly=perch_smelly)
+... parrot = Parrot.objects.create(state=state, perch=perch)
+... Tag.objects.update_tags(parrot, tags)
+
+>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True)]
+[(u'bar', 3), (u'baz', 1), (u'foo', 2), (u'ter', 3)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, min_count=2)]
+[(u'bar', 3), (u'foo', 2), (u'ter', 3)]
+
+# Limiting results to a subset of the model
+>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(state='no more'))]
+[(u'foo', 1), (u'ter', 1)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(state__startswith='p'))]
+[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(perch__size__gt=4))]
+[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(perch__smelly=True))]
+[(u'bar', 1), (u'foo', 2), (u'ter', 1)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, min_count=2, filters=dict(perch__smelly=True))]
+[(u'foo', 2)]
+>>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_model(Parrot, filters=dict(perch__size__gt=4))]
+[(u'bar', False), (u'baz', False), (u'foo', False), (u'ter', False)]
+>>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_model(Parrot, filters=dict(perch__size__gt=99))]
+[]
+
+# Related tags
+>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, counts=True)]
+[(u'baz', 1), (u'foo', 1), (u'ter', 2)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, min_count=2)]
+[(u'ter', 2)]
+>>> [tag.name for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, counts=False)]
+[u'baz', u'foo', u'ter']
+>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar', 'ter']), Parrot, counts=True)]
+[(u'baz', 1)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar', 'ter', 'baz']), Parrot, counts=True)]
+[]
+
+# Once again, with feeling (strings)
+>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model('bar', Parrot, counts=True)]
+[(u'baz', 1), (u'foo', 1), (u'ter', 2)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model('bar', Parrot, min_count=2)]
+[(u'ter', 2)]
+>>> [tag.name for tag in Tag.objects.related_for_model('bar', Parrot, counts=False)]
+[u'baz', u'foo', u'ter']
+>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(['bar', 'ter'], Parrot, counts=True)]
+[(u'baz', 1)]
+>>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(['bar', 'ter', 'baz'], Parrot, counts=True)]
+[]
+
+# Retrieving tagged objects by Model ##########################################
+
+>>> foo = Tag.objects.get(name='foo')
+>>> bar = Tag.objects.get(name='bar')
+>>> baz = Tag.objects.get(name='baz')
+>>> ter = Tag.objects.get(name='ter')
+>>> TaggedItem.objects.get_by_model(Parrot, foo)
+[<Parrot: no more>, <Parrot: pining for the fjords>]
+>>> TaggedItem.objects.get_by_model(Parrot, bar)
+[<Parrot: late>, <Parrot: passed on>, <Parrot: pining for the fjords>]
+
+# Intersections are supported
+>>> TaggedItem.objects.get_by_model(Parrot, [foo, baz])
+[]
+>>> TaggedItem.objects.get_by_model(Parrot, [foo, bar])
+[<Parrot: pining for the fjords>]
+>>> TaggedItem.objects.get_by_model(Parrot, [bar, ter])
+[<Parrot: late>, <Parrot: passed on>]
+
+# You can also pass Tag QuerySets
+>>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['foo', 'baz']))
+[]
+>>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['foo', 'bar']))
+[<Parrot: pining for the fjords>]
+>>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['bar', 'ter']))
+[<Parrot: late>, <Parrot: passed on>]
+
+# You can also pass strings and lists of strings
+>>> TaggedItem.objects.get_by_model(Parrot, 'foo baz')
+[]
+>>> TaggedItem.objects.get_by_model(Parrot, 'foo bar')
+[<Parrot: pining for the fjords>]
+>>> TaggedItem.objects.get_by_model(Parrot, 'bar ter')
+[<Parrot: late>, <Parrot: passed on>]
+>>> TaggedItem.objects.get_by_model(Parrot, ['foo', 'baz'])
+[]
+>>> TaggedItem.objects.get_by_model(Parrot, ['foo', 'bar'])
+[<Parrot: pining for the fjords>]
+>>> TaggedItem.objects.get_by_model(Parrot, ['bar', 'ter'])
+[<Parrot: late>, <Parrot: passed on>]
+
+# Issue 50 - Get by non-existent tag
+>>> TaggedItem.objects.get_by_model(Parrot, 'argatrons')
+[]
+
+# Retrieving related objects by Model #########################################
+
+# Related instances of the same Model
+>>> l1 = Link.objects.create(name='link 1')
+>>> Tag.objects.update_tags(l1, 'tag1 tag2 tag3 tag4 tag5')
+>>> l2 = Link.objects.create(name='link 2')
+>>> Tag.objects.update_tags(l2, 'tag1 tag2 tag3')
+>>> l3 = Link.objects.create(name='link 3')
+>>> Tag.objects.update_tags(l3, 'tag1')
+>>> l4 = Link.objects.create(name='link 4')
+>>> TaggedItem.objects.get_related(l1, Link)
+[<Link: link 2>, <Link: link 3>]
+>>> TaggedItem.objects.get_related(l1, Link, num=1)
+[<Link: link 2>]
+>>> TaggedItem.objects.get_related(l4, Link)
+[]
+
+# Related instance of a different Model
+>>> a1 = Article.objects.create(name='article 1')
+>>> Tag.objects.update_tags(a1, 'tag1 tag2 tag3 tag4')
+>>> TaggedItem.objects.get_related(a1, Link)
+[<Link: link 1>, <Link: link 2>, <Link: link 3>]
+>>> Tag.objects.update_tags(a1, 'tag6')
+>>> TaggedItem.objects.get_related(a1, Link)
+[]
+"""