diff options
author | SVN-Git Migration <python-modules-team@lists.alioth.debian.org> | 2015-10-08 11:51:45 -0700 |
---|---|---|
committer | SVN-Git Migration <python-modules-team@lists.alioth.debian.org> | 2015-10-08 11:51:45 -0700 |
commit | 3b9f21a55fed735652716e63fedabad87899be81 (patch) | |
tree | b6f57d335a1be88466d7780a6bb3101e81df2dde /tagging/tests | |
parent | 2228968f3d51a3d686adb2839bf43e018432f941 (diff) | |
download | python-django-tagging-upstream/0.2.1.tar python-django-tagging-upstream/0.2.1.tar.gz |
Imported Upstream version 0.2.1upstream/0.2.1
Diffstat (limited to 'tagging/tests')
-rw-r--r-- | tagging/tests/models.py | 76 | ||||
-rw-r--r-- | tagging/tests/runtests.py | 16 | ||||
-rw-r--r-- | tagging/tests/settings.py | 54 | ||||
-rw-r--r-- | tagging/tests/tags.txt | 242 | ||||
-rw-r--r-- | tagging/tests/tests.py | 818 |
5 files changed, 628 insertions, 578 deletions
diff --git a/tagging/tests/models.py b/tagging/tests/models.py index 86754cd..69a5c7b 100644 --- a/tagging/tests/models.py +++ b/tagging/tests/models.py @@ -1,38 +1,38 @@ -from django.db import models - -from tagging.fields import TagField - -class Perch(models.Model): - size = models.IntegerField() - smelly = models.BooleanField(default=True) - -class Parrot(models.Model): - state = models.CharField(max_length=50) - perch = models.ForeignKey(Perch, null=True) - - def __unicode__(self): - return self.state - - class Meta: - ordering = ['state'] - -class Link(models.Model): - name = models.CharField(max_length=50) - - def __unicode__(self): - return self.name - - class Meta: - ordering = ['name'] - -class Article(models.Model): - name = models.CharField(max_length=50) - - def __unicode__(self): - return self.name - - class Meta: - ordering = ['name'] - -class FormTest(models.Model): - tags = TagField() +from django.db import models
+
+from tagging.fields import TagField
+
+class Perch(models.Model):
+ size = models.IntegerField()
+ smelly = models.BooleanField(default=True)
+
+class Parrot(models.Model):
+ state = models.CharField(max_length=50)
+ perch = models.ForeignKey(Perch, null=True)
+
+ def __unicode__(self):
+ return self.state
+
+ class Meta:
+ ordering = ['state']
+
+class Link(models.Model):
+ name = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ ordering = ['name']
+
+class Article(models.Model):
+ name = models.CharField(max_length=50)
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ ordering = ['name']
+
+class FormTest(models.Model):
+ tags = TagField()
diff --git a/tagging/tests/runtests.py b/tagging/tests/runtests.py index ab1d49c..e66ee86 100644 --- a/tagging/tests/runtests.py +++ b/tagging/tests/runtests.py @@ -1,8 +1,8 @@ -import os, sys -os.environ['DJANGO_SETTINGS_MODULE'] = 'tagging.tests.settings' - -from django.test.simple import run_tests - -failures = run_tests(None, verbosity=9) -if failures: - sys.exit(failures) +import os, sys
+os.environ['DJANGO_SETTINGS_MODULE'] = 'tagging.tests.settings'
+
+from django.test.simple import run_tests
+
+failures = run_tests(None, verbosity=9)
+if failures:
+ sys.exit(failures)
diff --git a/tagging/tests/settings.py b/tagging/tests/settings.py index 26e659b..93ba4b0 100644 --- a/tagging/tests/settings.py +++ b/tagging/tests/settings.py @@ -1,27 +1,27 @@ -import os -DIRNAME = os.path.dirname(__file__) - -DEFAULT_CHARSET = 'utf-8' - -DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = os.path.join(DIRNAME, 'tagging_test.db') - -#DATABASE_ENGINE = 'mysql' -#DATABASE_NAME = 'tagging_test' -#DATABASE_USER = 'root' -#DATABASE_PASSWORD = '' -#DATABASE_HOST = 'localhost' -#DATABASE_PORT = '3306' - -#DATABASE_ENGINE = 'postgresql_psycopg2' -#DATABASE_NAME = 'tagging_test' -#DATABASE_USER = 'postgres' -#DATABASE_PASSWORD = '' -#DATABASE_HOST = 'localhost' -#DATABASE_PORT = '5432' - -INSTALLED_APPS = ( - 'django.contrib.contenttypes', - 'tagging', - 'tagging.tests', -) +import os
+DIRNAME = os.path.dirname(__file__)
+
+DEFAULT_CHARSET = 'utf-8'
+
+DATABASE_ENGINE = 'sqlite3'
+DATABASE_NAME = os.path.join(DIRNAME, 'tagging_test.db')
+
+#DATABASE_ENGINE = 'mysql'
+#DATABASE_NAME = 'tagging_test'
+#DATABASE_USER = 'root'
+#DATABASE_PASSWORD = ''
+#DATABASE_HOST = 'localhost'
+#DATABASE_PORT = '3306'
+
+#DATABASE_ENGINE = 'postgresql_psycopg2'
+#DATABASE_NAME = 'tagging_test'
+#DATABASE_USER = 'postgres'
+#DATABASE_PASSWORD = ''
+#DATABASE_HOST = 'localhost'
+#DATABASE_PORT = '5432'
+
+INSTALLED_APPS = (
+ 'django.contrib.contenttypes',
+ 'tagging',
+ 'tagging.tests',
+)
diff --git a/tagging/tests/tags.txt b/tagging/tests/tags.txt index 8543411..61bfd9e 100644 --- a/tagging/tests/tags.txt +++ b/tagging/tests/tags.txt @@ -1,122 +1,122 @@ -NewMedia 53 -Website 45 -PR 44 -Status 44 -Collaboration 41 -Drupal 34 -Journalism 31 -Transparency 30 -Theory 29 -Decentralization 25 -EchoChamberProject 24 -OpenSource 23 -Film 22 -Blog 21 -Interview 21 -Political 21 -Worldview 21 -Communications 19 -Conference 19 -Folksonomy 15 -MediaCriticism 15 -Volunteer 15 -Dialogue 13 -InternationalLaw 13 -Rosen 12 -Evolution 11 -KentBye 11 -Objectivity 11 -Plante 11 -ToDo 11 -Advisor 10 -Civics 10 -Roadmap 10 -Wilber 9 -About 8 -CivicSpace 8 -Ecosystem 8 -Choice 7 -Murphy 7 -Sociology 7 -ACH 6 -del.icio.us 6 -IntelligenceAnalysis 6 -Science 6 -Credibility 5 -Distribution 5 -Diversity 5 -Errors 5 -FinalCutPro 5 -Fundraising 5 -Law 5 -PhilosophyofScience 5 -Podcast 5 -PoliticalBias 5 -Activism 4 -Analysis 4 -CBS 4 -DeceptionDetection 4 -Editing 4 -History 4 -RSS 4 -Social 4 -Subjectivity 4 -Vlog 4 -ABC 3 -ALTubes 3 -Economics 3 -FCC 3 -NYT 3 -Sirota 3 -Sundance 3 -Training 3 -Wiki 3 -XML 3 -Borger 2 -Brody 2 -Deliberation 2 -EcoVillage 2 -Identity 2 -LAMP 2 -Lobe 2 -Maine 2 -May 2 -MediaLogic 2 -Metaphor 2 -Mitchell 2 -NBC 2 -OHanlon 2 -Psychology 2 -Queen 2 -Software 2 -SpiralDynamics 2 -Strobel 2 -Sustainability 2 -Transcripts 2 -Brown 1 -Buddhism 1 -Community 1 -DigitalDivide 1 -Donnelly 1 -Education 1 -FairUse 1 -FireANT 1 -Google 1 -HumanRights 1 -KM 1 -Kwiatkowski 1 -Landay 1 -Loiseau 1 -Math 1 -Music 1 -Nature 1 -Schechter 1 -Screencast 1 -Sivaraksa 1 -Skype 1 -SocialCapital 1 -TagCloud 1 -Thielmann 1 -Thomas 1 -Tiger 1 +NewMedia 53
+Website 45
+PR 44
+Status 44
+Collaboration 41
+Drupal 34
+Journalism 31
+Transparency 30
+Theory 29
+Decentralization 25
+EchoChamberProject 24
+OpenSource 23
+Film 22
+Blog 21
+Interview 21
+Political 21
+Worldview 21
+Communications 19
+Conference 19
+Folksonomy 15
+MediaCriticism 15
+Volunteer 15
+Dialogue 13
+InternationalLaw 13
+Rosen 12
+Evolution 11
+KentBye 11
+Objectivity 11
+Plante 11
+ToDo 11
+Advisor 10
+Civics 10
+Roadmap 10
+Wilber 9
+About 8
+CivicSpace 8
+Ecosystem 8
+Choice 7
+Murphy 7
+Sociology 7
+ACH 6
+del.icio.us 6
+IntelligenceAnalysis 6
+Science 6
+Credibility 5
+Distribution 5
+Diversity 5
+Errors 5
+FinalCutPro 5
+Fundraising 5
+Law 5
+PhilosophyofScience 5
+Podcast 5
+PoliticalBias 5
+Activism 4
+Analysis 4
+CBS 4
+DeceptionDetection 4
+Editing 4
+History 4
+RSS 4
+Social 4
+Subjectivity 4
+Vlog 4
+ABC 3
+ALTubes 3
+Economics 3
+FCC 3
+NYT 3
+Sirota 3
+Sundance 3
+Training 3
+Wiki 3
+XML 3
+Borger 2
+Brody 2
+Deliberation 2
+EcoVillage 2
+Identity 2
+LAMP 2
+Lobe 2
+Maine 2
+May 2
+MediaLogic 2
+Metaphor 2
+Mitchell 2
+NBC 2
+OHanlon 2
+Psychology 2
+Queen 2
+Software 2
+SpiralDynamics 2
+Strobel 2
+Sustainability 2
+Transcripts 2
+Brown 1
+Buddhism 1
+Community 1
+DigitalDivide 1
+Donnelly 1
+Education 1
+FairUse 1
+FireANT 1
+Google 1
+HumanRights 1
+KM 1
+Kwiatkowski 1
+Landay 1
+Loiseau 1
+Math 1
+Music 1
+Nature 1
+Schechter 1
+Screencast 1
+Sivaraksa 1
+Skype 1
+SocialCapital 1
+TagCloud 1
+Thielmann 1
+Thomas 1
+Tiger 1
Wedgwood 1
\ No newline at end of file diff --git a/tagging/tests/tests.py b/tagging/tests/tests.py index 41a7895..b1c8032 100644 --- a/tagging/tests/tests.py +++ b/tagging/tests/tests.py @@ -1,384 +1,434 @@ -# -*- 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) -[] -""" +# -*- coding: utf-8 -*-
+r"""
+>>> import os
+>>> from django import newforms as forms
+>>> from tagging.forms import TagField
+>>> 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_list, get_tag, parse_tag_input
+>>> from tagging.utils import LINEAR
+>>> from tagging.validators import isTagList, isTag
+
+#############
+# Utilities #
+#############
+
+# Tag input ###################################################################
+
+# Simple space-delimited tags
+>>> parse_tag_input('one')
+[u'one']
+>>> parse_tag_input('one two')
+[u'one', u'two']
+>>> parse_tag_input('one two three')
+[u'one', u'three', u'two']
+>>> parse_tag_input('one one two two')
+[u'one', u'two']
+
+# Comma-delimited multiple words - an unquoted comma in the input will trigger
+# this.
+>>> parse_tag_input(',one')
+[u'one']
+>>> parse_tag_input(',one two')
+[u'one two']
+>>> parse_tag_input(',one two three')
+[u'one two three']
+>>> parse_tag_input('a-one, a-two and a-three')
+[u'a-one', u'a-two and a-three']
+
+# Double-quoted multiple words - a completed quote will trigger this.
+# Unclosed quotes are ignored.
+>>> parse_tag_input('"one')
+[u'one']
+>>> parse_tag_input('"one two')
+[u'one', u'two']
+>>> parse_tag_input('"one two three')
+[u'one', u'three', u'two']
+>>> parse_tag_input('"one two"')
+[u'one two']
+>>> parse_tag_input('a-one "a-two and a-three"')
+[u'a-one', u'a-two and a-three']
+
+# No loose commas - split on spaces
+>>> parse_tag_input('one two "thr,ee"')
+[u'one', u'thr,ee', u'two']
+
+# Loose commas - split on commas
+>>> parse_tag_input('"one", two three')
+[u'one', u'two three']
+
+# Double quotes can contain commas
+>>> parse_tag_input('a-one "a-two, and a-three"')
+[u'a-one', u'a-two, and a-three']
+>>> parse_tag_input('"two", one, one, two, "one"')
+[u'one', u'two']
+
+# Bad users! Naughty users!
+>>> parse_tag_input(None)
+[]
+>>> parse_tag_input('')
+[]
+>>> parse_tag_input('"')
+[]
+>>> parse_tag_input('""')
+[]
+>>> parse_tag_input('"' * 7)
+[]
+>>> parse_tag_input(',,,,,,')
+[]
+>>> parse_tag_input('",",",",",",","')
+[u',']
+>>> parse_tag_input('a-one "a-two" and "a-three')
+[u'a-one', u'a-three', u'a-two', u'and']
+
+# 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('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: 30, 3: 19, 4: 15, 5: 10}
+
+>>> 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 distribution algorithm specified: cheese.
+
+# Validators ##################################################################
+
+>>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Each tag may be no more than 50 characters long.']
+
+>>> isTag('"test"', {})
+>>> isTag(',test', {})
+>>> isTag('f o o', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Multiple tags were given.']
+>>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {})
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Each tag may be no more than 50 characters long.']
+
+###########
+# 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, ' ')
+Traceback (most recent call last):
+ ...
+AttributeError: No tags were given: " ".
+>>> Tag.objects.add_tag(dead, 'one two')
+Traceback (most recent call last):
+ ...
+AttributeError: Multiple tags were given: "one two".
+
+# 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')
+[]
+
+# Unions
+>>> TaggedItem.objects.get_union_by_model(Parrot, ['foo', 'ter'])
+[<Parrot: late>, <Parrot: no more>, <Parrot: passed on>, <Parrot: pining for the fjords>]
+>>> TaggedItem.objects.get_union_by_model(Parrot, ['bar', 'baz'])
+[<Parrot: late>, <Parrot: passed on>, <Parrot: pining for the fjords>]
+
+# 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)
+[]
+
+################
+# Model Fields #
+################
+
+# TagField ####################################################################
+
+# Ensure that automatically created forms use TagField
+>>> class TestForm(forms.ModelForm):
+... class Meta:
+... model = FormTest
+>>> form = TestForm()
+>>> form.fields['tags'].__class__.__name__
+'TagField'
+
+# Recreating string representaions of tag lists ###############################
+>>> plain = Tag.objects.create(name='plain')
+>>> spaces = Tag.objects.create(name='spa ces')
+>>> comma = Tag.objects.create(name='com,ma')
+
+>>> from tagging.utils import edit_string_for_tags
+>>> edit_string_for_tags([plain])
+u'plain'
+>>> edit_string_for_tags([plain, spaces])
+u'plain, spa ces'
+>>> edit_string_for_tags([plain, spaces, comma])
+u'plain, spa ces, "com,ma"'
+>>> edit_string_for_tags([plain, comma])
+u'plain "com,ma"'
+>>> edit_string_for_tags([comma, spaces])
+u'"com,ma", spa ces'
+
+###############
+# 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 qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar')
+Traceback (most recent call last):
+ ...
+ValidationError: [u'Each tag may be no more than 50 characters long.']
+"""
|