From 29425a36c920e9b54e5860429ef3e3ce639fb155 Mon Sep 17 00:00:00 2001 From: SVN-Git Migration Date: Thu, 8 Oct 2015 11:51:49 -0700 Subject: Imported Upstream version 0.3.1 --- ._CHANGELOG.txt | Bin 0 -> 187 bytes ._MANIFEST.in | Bin 0 -> 184 bytes ._README.txt | Bin 0 -> 184 bytes ._setup.py | Bin 187 -> 0 bytes CHANGELOG.txt | 10 +- INSTALL.txt | 2 +- PKG-INFO | 2 +- docs/._overview.txt | Bin 0 -> 188 bytes docs/overview.txt | 6 - setup.py | 12 +- tagging/._models.py | Bin 0 -> 187 bytes tagging/._settings.py | Bin 0 -> 184 bytes tagging/__init__.py | 48 +- tagging/models.py | 14 +- tagging/tests/._settings.py | Bin 0 -> 184 bytes tagging/tests/._tags.txt | Bin 0 -> 184 bytes tagging/tests/settings.py | 28 +- tagging/tests/tests.py | 1331 ++++++++++++++++++++++++++++--------------- 18 files changed, 968 insertions(+), 485 deletions(-) create mode 100644 ._CHANGELOG.txt create mode 100644 ._MANIFEST.in create mode 100644 ._README.txt delete mode 100644 ._setup.py create mode 100644 docs/._overview.txt create mode 100644 tagging/._models.py create mode 100644 tagging/._settings.py create mode 100644 tagging/tests/._settings.py create mode 100644 tagging/tests/._tags.txt diff --git a/._CHANGELOG.txt b/._CHANGELOG.txt new file mode 100644 index 0000000..9bf97be Binary files /dev/null and b/._CHANGELOG.txt differ diff --git a/._MANIFEST.in b/._MANIFEST.in new file mode 100644 index 0000000..8af97db Binary files /dev/null and b/._MANIFEST.in differ diff --git a/._README.txt b/._README.txt new file mode 100644 index 0000000..8645f1c Binary files /dev/null and b/._README.txt differ diff --git a/._setup.py b/._setup.py deleted file mode 100644 index b052229..0000000 Binary files a/._setup.py and /dev/null differ diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1a8c360..4303c2a 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,9 +2,15 @@ Django Tagging Changelog ======================== +Version 0.3.1, Not released: +---------------------------- -SVN Trunk Changes: ------------------- +* Fixed Django 1.2 support (did not add anything new) + +Version 0.3.0, 22nd August 2009: +-------------------------------- + +* Fixes for Django 1.0 compatibility. * Added a ``tagging.generic`` module for working with list of objects which have generic relations, containing a ``fetch_content_objects`` diff --git a/INSTALL.txt b/INSTALL.txt index 7f90cca..9f13a3a 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -10,5 +10,5 @@ somewhere on your Python path; this is useful if you're working from a Subversion checkout. Note that this application requires Python 2.3 or later, and Django -0.96 or later. You can obtain Python from http://www.python.org/ and +1.0 or later. You can obtain Python from http://www.python.org/ and Django from http://www.djangoproject.com/. \ No newline at end of file diff --git a/PKG-INFO b/PKG-INFO index 5808eb1..2d1e79c 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: django-tagging -Version: 0.3 +Version: 0.3.1 Summary: Generic tagging application for Django Home-page: http://code.google.com/p/django-tagging/ Author: Jonathan Buchanan diff --git a/docs/._overview.txt b/docs/._overview.txt new file mode 100644 index 0000000..c4db396 Binary files /dev/null and b/docs/._overview.txt differ diff --git a/docs/overview.txt b/docs/overview.txt index 8b65075..feb08c9 100644 --- a/docs/overview.txt +++ b/docs/overview.txt @@ -125,8 +125,6 @@ application and in any forms automatically generated using ``ModelForm``. Registering your models ======================= -**New in developement version** - Your Django models can be registered with the tagging application to access some additional tagging-related features. @@ -366,8 +364,6 @@ methods: greater than or equal to ``min_count``, pass a value for the ``min_count`` argument. -**New in development version** - * ``usage_for_queryset(queryset, counts=False, min_count=None)`` -- Obtains a list of tags associated with instances of a model contained in the given queryset. @@ -454,8 +450,6 @@ greater than 99:: .. _`field lookups`: http://docs.djangoproject.com/en/dev/topics/db/queries/#field-lookups -**New in development version** - The ``usage_for_queryset`` method allows you to pass a pre-filtered queryset to be used when determining tag usage:: diff --git a/setup.py b/setup.py index a755df8..0670dfe 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,10 @@ import os from distutils.command.install import INSTALL_SCHEMES from distutils.core import setup +import tagging + + + def fullsplit(path, result=None): """ Split a pathname into components (the opposite of os.path.join) in a @@ -45,16 +49,10 @@ for dirpath, dirnames, filenames in os.walk(tagging_dir): elif filenames: data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]]) -# Dynamically calculate the version based on tagging.VERSION -version_tuple = (0, 3, None) -if version_tuple[2] is not None: - version = "%d.%d_%s" % version_tuple -else: - version = "%d.%d" % version_tuple[:2] setup( name = 'django-tagging', - version = version, + version = tagging.get_version(), description = 'Generic tagging application for Django', author = 'Jonathan Buchanan', author_email = 'jonathan.buchanan@gmail.com', diff --git a/tagging/._models.py b/tagging/._models.py new file mode 100644 index 0000000..bdd7231 Binary files /dev/null and b/tagging/._models.py differ diff --git a/tagging/._settings.py b/tagging/._settings.py new file mode 100644 index 0000000..3c4ee38 Binary files /dev/null and b/tagging/._settings.py differ diff --git a/tagging/__init__.py b/tagging/__init__.py index 9241c20..03d6c03 100644 --- a/tagging/__init__.py +++ b/tagging/__init__.py @@ -1,8 +1,20 @@ -from django.utils.translation import ugettext as _ +VERSION = (0, 3, 1, "final", 0) -from tagging.managers import ModelTaggedItemManager, TagDescriptor -VERSION = (0, 3, 'pre') + +def get_version(): + if VERSION[3] == "final": + return "%s.%s.%s" % (VERSION[0], VERSION[1], VERSION[2]) + elif VERSION[3] == "dev": + if VERSION[2] == 0: + return "%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[3], VERSION[4]) + return "%s.%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[2], VERSION[3], VERSION[4]) + else: + return "%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[2], VERSION[3]) + + +__version__ = get_version() + class AlreadyRegistered(Exception): """ @@ -10,21 +22,41 @@ class AlreadyRegistered(Exception): """ pass + registry = [] + def register(model, tag_descriptor_attr='tags', tagged_item_manager_attr='tagged'): """ Sets the given model class up for working with tags. """ + + from tagging.managers import ModelTaggedItemManager, TagDescriptor + if model in registry: - raise AlreadyRegistered( - _('The model %s has already been registered.') % model.__name__) - registry.append(model) + raise AlreadyRegistered("The model '%s' has already been " + "registered." % model._meta.object_name) + if hasattr(model, tag_descriptor_attr): + raise AttributeError("'%s' already has an attribute '%s'. You must " + "provide a custom tag_descriptor_attr to register." % ( + model._meta.object_name, + tag_descriptor_attr, + ) + ) + if hasattr(model, tagged_item_manager_attr): + raise AttributeError("'%s' already has an attribute '%s'. You must " + "provide a custom tagged_item_manager_attr to register." % ( + model._meta.object_name, + tagged_item_manager_attr, + ) + ) # Add tag descriptor setattr(model, tag_descriptor_attr, TagDescriptor()) # Add custom manager - ModelTaggedItemManager().contribute_to_class(model, - tagged_item_manager_attr) + ModelTaggedItemManager().contribute_to_class(model, tagged_item_manager_attr) + + # Finally register in registry + registry.append(model) diff --git a/tagging/models.py b/tagging/models.py index d43f22d..860cf81 100644 --- a/tagging/models.py +++ b/tagging/models.py @@ -162,8 +162,18 @@ class TagManager(models.Manager): Passing a value for ``min_count`` implies ``counts=True``. """ - extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:]) - where, params = queryset.query.where.as_sql() + if getattr(queryset.query, 'get_compiler', None): + # Django 1.2+ + compiler = queryset.query.get_compiler(using='default') + extra_joins = ' '.join(compiler.get_from_clause()[0][1:]) + where, params = queryset.query.where.as_sql( + compiler.quote_name_unless_alias, compiler.connection + ) + else: + # Django pre-1.2 + extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:]) + where, params = queryset.query.where.as_sql() + if where: extra_criteria = 'AND %s' % where else: diff --git a/tagging/tests/._settings.py b/tagging/tests/._settings.py new file mode 100644 index 0000000..6092bc4 Binary files /dev/null and b/tagging/tests/._settings.py differ diff --git a/tagging/tests/._tags.txt b/tagging/tests/._tags.txt new file mode 100644 index 0000000..3348eaa Binary files /dev/null and b/tagging/tests/._tags.txt differ diff --git a/tagging/tests/settings.py b/tagging/tests/settings.py index 26e659b..74eb909 100644 --- a/tagging/tests/settings.py +++ b/tagging/tests/settings.py @@ -3,22 +3,22 @@ DIRNAME = os.path.dirname(__file__) DEFAULT_CHARSET = 'utf-8' -DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = os.path.join(DIRNAME, 'tagging_test.db') +test_engine = os.environ.get("TAGGING_TEST_ENGINE", "sqlite3") -#DATABASE_ENGINE = 'mysql' -#DATABASE_NAME = 'tagging_test' -#DATABASE_USER = 'root' -#DATABASE_PASSWORD = '' -#DATABASE_HOST = 'localhost' -#DATABASE_PORT = '3306' +DATABASE_ENGINE = test_engine +DATABASE_NAME = os.environ.get("TAGGING_DATABASE_NAME", "tagging_test") +DATABASE_USER = os.environ.get("TAGGING_DATABASE_USER", "") +DATABASE_PASSWORD = os.environ.get("TAGGING_DATABASE_PASSWORD", "") +DATABASE_HOST = os.environ.get("TAGGING_DATABASE_HOST", "localhost") + +if test_engine == "sqlite": + DATABASE_NAME = os.path.join(DIRNAME, 'tagging_test.db') + DATABASE_HOST = "" +elif test_engine == "mysql": + DATABASE_PORT = os.environ.get("TAGGING_DATABASE_PORT", 3306) +elif test_engine == "postgresql_psycopg2": + DATABASE_PORT = os.environ.get("TAGGING_DATABASE_PORT", 5432) -#DATABASE_ENGINE = 'postgresql_psycopg2' -#DATABASE_NAME = 'tagging_test' -#DATABASE_USER = 'postgres' -#DATABASE_PASSWORD = '' -#DATABASE_HOST = 'localhost' -#DATABASE_PORT = '5432' INSTALLED_APPS = ( 'django.contrib.contenttypes', diff --git a/tagging/tests/tests.py b/tagging/tests/tests.py index 5d6d8b5..f5c9e37 100644 --- a/tagging/tests/tests.py +++ b/tagging/tests/tests.py @@ -1,458 +1,901 @@ # -*- coding: utf-8 -*- -r""" ->>> import os ->>> from django import forms ->>> from django.db.models import Q ->>> 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 + +import os +from django import forms +from django.db.models import Q +from django.test import TestCase +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, edit_string_for_tags, get_tag_list, get_tag, parse_tag_input +from tagging.utils import LINEAR ############# # 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) -[] ->>> get_tag_list('cheese toast') -[, ] ->>> get_tag_list('cheese,toast') -[, ] ->>> get_tag_list([]) -[] ->>> get_tag_list(['cheese', 'toast']) -[, ] ->>> get_tag_list([cheese.id, toast.id]) -[, ] ->>> get_tag_list(['cheese', 'toast', 'ŠĐĆŽćžšđ']) -[, ] ->>> get_tag_list([cheese, toast]) -[, ] ->>> get_tag_list((cheese, toast)) -(, ) ->>> get_tag_list(Tag.objects.filter(name__in=['cheese', '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) - ->>> get_tag('cheese') - ->>> get_tag(cheese.id) - ->>> 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. - +class TestParseTagInput(TestCase): + def test_with_simple_space_delimited_tags(self): + """ Test with simple space-delimited tags. """ + + self.assertEquals(parse_tag_input('one'), [u'one']) + self.assertEquals(parse_tag_input('one two'), [u'one', u'two']) + self.assertEquals(parse_tag_input('one two three'), [u'one', u'three', u'two']) + self.assertEquals(parse_tag_input('one one two two'), [u'one', u'two']) + + def test_with_comma_delimited_multiple_words(self): + """ Test with comma-delimited multiple words. + An unquoted comma in the input will trigger this. """ + + self.assertEquals(parse_tag_input(',one'), [u'one']) + self.assertEquals(parse_tag_input(',one two'), [u'one two']) + self.assertEquals(parse_tag_input(',one two three'), [u'one two three']) + self.assertEquals(parse_tag_input('a-one, a-two and a-three'), + [u'a-one', u'a-two and a-three']) + + def test_with_double_quoted_multiple_words(self): + """ Test with double-quoted multiple words. + A completed quote will trigger this. Unclosed quotes are ignored. """ + + self.assertEquals(parse_tag_input('"one'), [u'one']) + self.assertEquals(parse_tag_input('"one two'), [u'one', u'two']) + self.assertEquals(parse_tag_input('"one two three'), [u'one', u'three', u'two']) + self.assertEquals(parse_tag_input('"one two"'), [u'one two']) + self.assertEquals(parse_tag_input('a-one "a-two and a-three"'), + [u'a-one', u'a-two and a-three']) + + def test_with_no_loose_commas(self): + """ Test with no loose commas -- split on spaces. """ + self.assertEquals(parse_tag_input('one two "thr,ee"'), [u'one', u'thr,ee', u'two']) + + def test_with_loose_commas(self): + """ Loose commas - split on commas """ + self.assertEquals(parse_tag_input('"one", two three'), [u'one', u'two three']) + + def test_tags_with_double_quotes_can_contain_commas(self): + """ Double quotes can contain commas """ + self.assertEquals(parse_tag_input('a-one "a-two, and a-three"'), + [u'a-one', u'a-two, and a-three']) + self.assertEquals(parse_tag_input('"two", one, one, two, "one"'), + [u'one', u'two']) + + def test_with_naughty_input(self): + """ Test with naughty input. """ + + # Bad users! Naughty users! + self.assertEquals(parse_tag_input(None), []) + self.assertEquals(parse_tag_input(''), []) + self.assertEquals(parse_tag_input('"'), []) + self.assertEquals(parse_tag_input('""'), []) + self.assertEquals(parse_tag_input('"' * 7), []) + self.assertEquals(parse_tag_input(',,,,,,'), []) + self.assertEquals(parse_tag_input('",",",",",",","'), [u',']) + self.assertEquals(parse_tag_input('a-one "a-two" and "a-three'), + [u'a-one', u'a-three', u'a-two', u'and']) + +class TestNormalisedTagListInput(TestCase): + def setUp(self): + self.cheese = Tag.objects.create(name='cheese') + self.toast = Tag.objects.create(name='toast') + + def test_single_tag_object_as_input(self): + self.assertEquals(get_tag_list(self.cheese), [self.cheese]) + + def test_space_delimeted_string_as_input(self): + ret = get_tag_list('cheese toast') + self.assertEquals(len(ret), 2) + self.failUnless(self.cheese in ret) + self.failUnless(self.toast in ret) + + def test_comma_delimeted_string_as_input(self): + ret = get_tag_list('cheese,toast') + self.assertEquals(len(ret), 2) + self.failUnless(self.cheese in ret) + self.failUnless(self.toast in ret) + + def test_with_empty_list(self): + self.assertEquals(get_tag_list([]), []) + + def test_list_of_two_strings(self): + ret = get_tag_list(['cheese', 'toast']) + self.assertEquals(len(ret), 2) + self.failUnless(self.cheese in ret) + self.failUnless(self.toast in ret) + + def test_list_of_tag_primary_keys(self): + ret = get_tag_list([self.cheese.id, self.toast.id]) + self.assertEquals(len(ret), 2) + self.failUnless(self.cheese in ret) + self.failUnless(self.toast in ret) + + def test_list_of_strings_with_strange_nontag_string(self): + ret = get_tag_list(['cheese', 'toast', 'ŠĐĆŽćžšđ']) + self.assertEquals(len(ret), 2) + self.failUnless(self.cheese in ret) + self.failUnless(self.toast in ret) + + def test_list_of_tag_instances(self): + ret = get_tag_list([self.cheese, self.toast]) + self.assertEquals(len(ret), 2) + self.failUnless(self.cheese in ret) + self.failUnless(self.toast in ret) + + def test_tuple_of_instances(self): + ret = get_tag_list((self.cheese, self.toast)) + self.assertEquals(len(ret), 2) + self.failUnless(self.cheese in ret) + self.failUnless(self.toast in ret) + + def test_with_tag_filter(self): + ret = get_tag_list(Tag.objects.filter(name__in=['cheese', 'toast'])) + self.assertEquals(len(ret), 2) + self.failUnless(self.cheese in ret) + self.failUnless(self.toast in ret) + + def test_with_invalid_input_mix_of_string_and_instance(self): + try: + get_tag_list(['cheese', self.toast]) + except ValueError, ve: + self.assertEquals(str(ve), + 'If a list or tuple of tags is provided, they must all be tag names, Tag objects or Tag ids.') + except Exception, e: + raise self.failureException('the wrong type of exception was raised: type [%s] value [%]' %\ + (str(type(e)), str(e))) + else: + raise self.failureException('a ValueError exception was supposed to be raised!') + + def test_with_invalid_input(self): + try: + get_tag_list(29) + except ValueError, ve: + self.assertEquals(str(ve), 'The tag input given was invalid.') + except Exception, e: + raise self.failureException('the wrong type of exception was raised: type [%s] value [%s]' %\ + (str(type(e)), str(e))) + else: + raise self.failureException('a ValueError exception was supposed to be raised!') + + def test_with_tag_instance(self): + self.assertEquals(get_tag(self.cheese), self.cheese) + + def test_with_string(self): + self.assertEquals(get_tag('cheese'), self.cheese) + + def test_with_primary_key(self): + self.assertEquals(get_tag(self.cheese.id), self.cheese) + + def test_nonexistent_tag(self): + self.assertEquals(get_tag('mouse'), None) + +class TestCalculateCloud(TestCase): + def setUp(self): + self.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) + self.tags.append(tag) + + def test_default_distribution(self): + sizes = {} + for tag in calculate_cloud(self.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 + self.assertEquals(sizes[1], 48) + self.assertEquals(sizes[2], 30) + self.assertEquals(sizes[3], 19) + self.assertEquals(sizes[4], 15) + self.assertEquals(sizes[5], 10) + + def test_linear_distribution(self): + sizes = {} + for tag in calculate_cloud(self.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 + self.assertEquals(sizes[1], 97) + self.assertEquals(sizes[2], 12) + self.assertEquals(sizes[3], 7) + self.assertEquals(sizes[4], 2) + self.assertEquals(sizes[5], 4) + + def test_invalid_distribution(self): + try: + calculate_cloud(self.tags, steps=5, distribution='cheese') + except ValueError, ve: + self.assertEquals(str(ve), 'Invalid distribution algorithm specified: cheese.') + except Exception, e: + raise self.failureException('the wrong type of exception was raised: type [%s] value [%s]' %\ + (str(type(e)), str(e))) + else: + raise self.failureException('a ValueError exception was supposed to be raised!') + ########### # Tagging # ########### -# Basic tagging ############################################################### - ->>> dead = Parrot.objects.create(state='dead') ->>> Tag.objects.update_tags(dead, 'foo,bar,"ter"') ->>> Tag.objects.get_for_object(dead) -[, , ] ->>> Tag.objects.update_tags(dead, '"foo" bar "baz"') ->>> Tag.objects.get_for_object(dead) -[, , ] ->>> Tag.objects.add_tag(dead, 'foo') ->>> Tag.objects.get_for_object(dead) -[, , ] ->>> Tag.objects.add_tag(dead, 'zip') ->>> Tag.objects.get_for_object(dead) -[, , , ] ->>> 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.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) -[, , ] ->>> f1.tags = u'test4' ->>> f1.save() ->>> Tag.objects.get_for_object(f1) -[] ->>> 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.objects.update_tags(dead, 'foO bAr baZ') ->>> Tag.objects.get_for_object(dead) -[, , ] ->>> Tag.objects.add_tag(dead, 'FOO') ->>> Tag.objects.get_for_object(dead) -[, , ] ->>> Tag.objects.add_tag(dead, 'Zip') ->>> Tag.objects.get_for_object(dead) -[, , , ] ->>> Tag.objects.update_tags(dead, None) ->>> f1.tags = u'TEST5' ->>> f1.save() ->>> Tag.objects.get_for_object(f1) -[] ->>> 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) -[, ] ->>> TaggedItem.objects.get_by_model(Parrot, bar) -[, , ] - -# Intersections are supported ->>> TaggedItem.objects.get_by_model(Parrot, [foo, baz]) -[] ->>> TaggedItem.objects.get_by_model(Parrot, [foo, bar]) -[] ->>> TaggedItem.objects.get_by_model(Parrot, [bar, ter]) -[, ] - -# Issue 114 - Intersection with non-existant tags ->>> TaggedItem.objects.get_intersection_by_model(Parrot, []) -[] - -# 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'])) -[] ->>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['bar', 'ter'])) -[, ] - -# 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') -[] ->>> TaggedItem.objects.get_by_model(Parrot, 'bar ter') -[, ] ->>> TaggedItem.objects.get_by_model(Parrot, ['foo', 'baz']) -[] ->>> TaggedItem.objects.get_by_model(Parrot, ['foo', 'bar']) -[] ->>> TaggedItem.objects.get_by_model(Parrot, ['bar', 'ter']) -[, ] - -# Issue 50 - Get by non-existent tag ->>> TaggedItem.objects.get_by_model(Parrot, 'argatrons') -[] - -# Unions ->>> TaggedItem.objects.get_union_by_model(Parrot, ['foo', 'ter']) -[, , , ] ->>> TaggedItem.objects.get_union_by_model(Parrot, ['bar', 'baz']) -[, , ] - -# Issue 114 - Union with non-existant tags ->>> TaggedItem.objects.get_union_by_model(Parrot, []) -[] - -# 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) -[, ] ->>> TaggedItem.objects.get_related(l1, Link, num=1) -[] ->>> TaggedItem.objects.get_related(l4, Link) -[] - -# Limit related items ->>> TaggedItem.objects.get_related(l1, Link.objects.exclude(name='link 3')) -[] - -# 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) -[, , ] ->>> Tag.objects.update_tags(a1, 'tag6') ->>> TaggedItem.objects.get_related(a1, Link) -[] - -# Limiting results to a queryset ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(state='no more'), counts=True)] -[(u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(state__startswith='p'), counts=True)] -[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4), counts=True)] -[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), counts=True)] -[(u'bar', 1), (u'foo', 2), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), min_count=2)] -[(u'foo', 2)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(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_queryset(Parrot.objects.filter(perch__size__gt=99))] -[] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), counts=True)] -[(u'bar', 2), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), min_count=2)] -[(u'bar', 2)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')))] -[(u'bar', False), (u'foo', False), (u'ter', False)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(state='passed on'), counts=True)] -[(u'bar', 2), (u'foo', 2), (u'ter', 2)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(state__startswith='p'), min_count=2)] -[(u'ter', 2)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(Q(perch__size__gt=6) | Q(perch__smelly=False)), counts=True)] -[(u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(perch__smelly=True).filter(state__startswith='l'), counts=True)] -[(u'bar', 1), (u'ter', 1)] - +class TestBasicTagging(TestCase): + def setUp(self): + self.dead_parrot = Parrot.objects.create(state='dead') + + def test_update_tags(self): + Tag.objects.update_tags(self.dead_parrot, 'foo,bar,"ter"') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(get_tag('foo') in tags) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('ter') in tags) + + Tag.objects.update_tags(self.dead_parrot, '"foo" bar "baz"') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('baz') in tags) + self.failUnless(get_tag('foo') in tags) + + def test_add_tag(self): + # start off in a known, mildly interesting state + Tag.objects.update_tags(self.dead_parrot, 'foo bar baz') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('baz') in tags) + self.failUnless(get_tag('foo') in tags) + + # try to add a tag that already exists + Tag.objects.add_tag(self.dead_parrot, 'foo') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('baz') in tags) + self.failUnless(get_tag('foo') in tags) + + # now add a tag that doesn't already exist + Tag.objects.add_tag(self.dead_parrot, 'zip') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 4) + self.failUnless(get_tag('zip') in tags) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('baz') in tags) + self.failUnless(get_tag('foo') in tags) + + def test_add_tag_invalid_input_no_tags_specified(self): + # start off in a known, mildly interesting state + Tag.objects.update_tags(self.dead_parrot, 'foo bar baz') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('baz') in tags) + self.failUnless(get_tag('foo') in tags) + + try: + Tag.objects.add_tag(self.dead_parrot, ' ') + except AttributeError, ae: + self.assertEquals(str(ae), 'No tags were given: " ".') + except Exception, e: + raise self.failureException('the wrong type of exception was raised: type [%s] value [%s]' %\ + (str(type(e)), str(e))) + else: + raise self.failureException('an AttributeError exception was supposed to be raised!') + + def test_add_tag_invalid_input_multiple_tags_specified(self): + # start off in a known, mildly interesting state + Tag.objects.update_tags(self.dead_parrot, 'foo bar baz') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('baz') in tags) + self.failUnless(get_tag('foo') in tags) + + try: + Tag.objects.add_tag(self.dead_parrot, 'one two') + except AttributeError, ae: + self.assertEquals(str(ae), 'Multiple tags were given: "one two".') + except Exception, e: + raise self.failureException('the wrong type of exception was raised: type [%s] value [%s]' %\ + (str(type(e)), str(e))) + else: + raise self.failureException('an AttributeError exception was supposed to be raised!') + + def test_update_tags_exotic_characters(self): + # start off in a known, mildly interesting state + Tag.objects.update_tags(self.dead_parrot, 'foo bar baz') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('baz') in tags) + self.failUnless(get_tag('foo') in tags) + + Tag.objects.update_tags(self.dead_parrot, u'ŠĐĆŽćžšđ') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 1) + self.assertEquals(tags[0].name, u'ŠĐĆŽćžšđ') + + Tag.objects.update_tags(self.dead_parrot, u'你好') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 1) + self.assertEquals(tags[0].name, u'你好') + + def test_update_tags_with_none(self): + # start off in a known, mildly interesting state + Tag.objects.update_tags(self.dead_parrot, 'foo bar baz') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(get_tag('bar') in tags) + self.failUnless(get_tag('baz') in tags) + self.failUnless(get_tag('foo') in tags) + + Tag.objects.update_tags(self.dead_parrot, None) + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 0) + +class TestModelTagField(TestCase): + """ Test the 'tags' field on models. """ + + def test_create_with_tags_specified(self): + f1 = FormTest.objects.create(tags=u'test3 test2 test1') + tags = Tag.objects.get_for_object(f1) + test1_tag = get_tag('test1') + test2_tag = get_tag('test2') + test3_tag = get_tag('test3') + self.failUnless(None not in (test1_tag, test2_tag, test3_tag)) + self.assertEquals(len(tags), 3) + self.failUnless(test1_tag in tags) + self.failUnless(test2_tag in tags) + self.failUnless(test3_tag in tags) + + def test_update_via_tags_field(self): + f1 = FormTest.objects.create(tags=u'test3 test2 test1') + tags = Tag.objects.get_for_object(f1) + test1_tag = get_tag('test1') + test2_tag = get_tag('test2') + test3_tag = get_tag('test3') + self.failUnless(None not in (test1_tag, test2_tag, test3_tag)) + self.assertEquals(len(tags), 3) + self.failUnless(test1_tag in tags) + self.failUnless(test2_tag in tags) + self.failUnless(test3_tag in tags) + + f1.tags = u'test4' + f1.save() + tags = Tag.objects.get_for_object(f1) + test4_tag = get_tag('test4') + self.assertEquals(len(tags), 1) + self.assertEquals(tags[0], test4_tag) + + f1.tags = '' + f1.save() + tags = Tag.objects.get_for_object(f1) + self.assertEquals(len(tags), 0) + +class TestSettings(TestCase): + def setUp(self): + self.original_force_lower_case_tags = settings.FORCE_LOWERCASE_TAGS + self.dead_parrot = Parrot.objects.create(state='dead') + + def tearDown(self): + settings.FORCE_LOWERCASE_TAGS = self.original_force_lower_case_tags + + def test_force_lowercase_tags(self): + """ Test forcing tags to lowercase. """ + + settings.FORCE_LOWERCASE_TAGS = True + + Tag.objects.update_tags(self.dead_parrot, 'foO bAr Ter') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + foo_tag = get_tag('foo') + bar_tag = get_tag('bar') + ter_tag = get_tag('ter') + self.failUnless(foo_tag in tags) + self.failUnless(bar_tag in tags) + self.failUnless(ter_tag in tags) + + Tag.objects.update_tags(self.dead_parrot, 'foO bAr baZ') + tags = Tag.objects.get_for_object(self.dead_parrot) + baz_tag = get_tag('baz') + self.assertEquals(len(tags), 3) + self.failUnless(bar_tag in tags) + self.failUnless(baz_tag in tags) + self.failUnless(foo_tag in tags) + + Tag.objects.add_tag(self.dead_parrot, 'FOO') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 3) + self.failUnless(bar_tag in tags) + self.failUnless(baz_tag in tags) + self.failUnless(foo_tag in tags) + + Tag.objects.add_tag(self.dead_parrot, 'Zip') + tags = Tag.objects.get_for_object(self.dead_parrot) + self.assertEquals(len(tags), 4) + zip_tag = get_tag('zip') + self.failUnless(bar_tag in tags) + self.failUnless(baz_tag in tags) + self.failUnless(foo_tag in tags) + self.failUnless(zip_tag in tags) + + f1 = FormTest.objects.create() + f1.tags = u'TEST5' + f1.save() + tags = Tag.objects.get_for_object(f1) + test5_tag = get_tag('test5') + self.assertEquals(len(tags), 1) + self.failUnless(test5_tag in tags) + self.assertEquals(f1.tags, u'test5') + +class TestTagUsageForModelBaseCase(TestCase): + def test_tag_usage_for_model_empty(self): + self.assertEquals(Tag.objects.usage_for_model(Parrot), []) + +class TestTagUsageForModel(TestCase): + def setUp(self): + 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) + + def test_tag_usage_for_model(self): + tag_usage = Tag.objects.usage_for_model(Parrot, counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 4) + self.failUnless((u'bar', 3) in relevant_attribute_list) + self.failUnless((u'baz', 1) in relevant_attribute_list) + self.failUnless((u'foo', 2) in relevant_attribute_list) + self.failUnless((u'ter', 3) in relevant_attribute_list) + + def test_tag_usage_for_model_with_min_count(self): + tag_usage = Tag.objects.usage_for_model(Parrot, min_count = 2) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless((u'bar', 3) in relevant_attribute_list) + self.failUnless((u'foo', 2) in relevant_attribute_list) + self.failUnless((u'ter', 3) in relevant_attribute_list) + + def test_tag_usage_with_filter_on_model_objects(self): + tag_usage = Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(state='no more')) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 2) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(state__startswith='p')) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 4) + self.failUnless((u'bar', 2) in relevant_attribute_list) + self.failUnless((u'baz', 1) in relevant_attribute_list) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(perch__size__gt=4)) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 4) + self.failUnless((u'bar', 2) in relevant_attribute_list) + self.failUnless((u'baz', 1) in relevant_attribute_list) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(perch__smelly=True)) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless((u'bar', 1) in relevant_attribute_list) + self.failUnless((u'foo', 2) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_model(Parrot, min_count=2, filters=dict(perch__smelly=True)) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 1) + self.failUnless((u'foo', 2) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_model(Parrot, filters=dict(perch__size__gt=4)) + relevant_attribute_list = [(tag.name, hasattr(tag, 'counts')) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 4) + self.failUnless((u'bar', False) in relevant_attribute_list) + self.failUnless((u'baz', False) in relevant_attribute_list) + self.failUnless((u'foo', False) in relevant_attribute_list) + self.failUnless((u'ter', False) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_model(Parrot, filters=dict(perch__size__gt=99)) + relevant_attribute_list = [(tag.name, hasattr(tag, 'counts')) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 0) + +class TestTagsRelatedForModel(TestCase): + def setUp(self): + 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) + + def test_related_for_model_with_tag_query_sets_as_input(self): + related_tags = Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless((u'baz', 1) in relevant_attribute_list) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 2) in relevant_attribute_list) + + related_tags = Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, min_count=2) + relevant_attribute_list = [(tag.name, tag.count) for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 1) + self.failUnless((u'ter', 2) in relevant_attribute_list) + + related_tags = Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, counts=False) + relevant_attribute_list = [tag.name for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless(u'baz' in relevant_attribute_list) + self.failUnless(u'foo' in relevant_attribute_list) + self.failUnless(u'ter' in relevant_attribute_list) + + related_tags = Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar', 'ter']), Parrot, counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 1) + self.failUnless((u'baz', 1) in relevant_attribute_list) + + related_tags = Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar', 'ter', 'baz']), Parrot, counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 0) + + def test_related_for_model_with_tag_strings_as_input(self): + # Once again, with feeling (strings) + related_tags = Tag.objects.related_for_model('bar', Parrot, counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless((u'baz', 1) in relevant_attribute_list) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 2) in relevant_attribute_list) + + related_tags = Tag.objects.related_for_model('bar', Parrot, min_count=2) + relevant_attribute_list = [(tag.name, tag.count) for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 1) + self.failUnless((u'ter', 2) in relevant_attribute_list) + + related_tags = Tag.objects.related_for_model('bar', Parrot, counts=False) + relevant_attribute_list = [tag.name for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless(u'baz' in relevant_attribute_list) + self.failUnless(u'foo' in relevant_attribute_list) + self.failUnless(u'ter' in relevant_attribute_list) + + related_tags = Tag.objects.related_for_model(['bar', 'ter'], Parrot, counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 1) + self.failUnless((u'baz', 1) in relevant_attribute_list) + + related_tags = Tag.objects.related_for_model(['bar', 'ter', 'baz'], Parrot, counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in related_tags] + self.assertEquals(len(relevant_attribute_list), 0) + +class TestGetTaggedObjectsByModel(TestCase): + def setUp(self): + 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) + + self.foo = Tag.objects.get(name='foo') + self.bar = Tag.objects.get(name='bar') + self.baz = Tag.objects.get(name='baz') + self.ter = Tag.objects.get(name='ter') + + self.pining_for_the_fjords_parrot = Parrot.objects.get(state='pining for the fjords') + self.passed_on_parrot = Parrot.objects.get(state='passed on') + self.no_more_parrot = Parrot.objects.get(state='no more') + self.late_parrot = Parrot.objects.get(state='late') + + def test_get_by_model_simple(self): + parrots = TaggedItem.objects.get_by_model(Parrot, self.foo) + self.assertEquals(len(parrots), 2) + self.failUnless(self.no_more_parrot in parrots) + self.failUnless(self.pining_for_the_fjords_parrot in parrots) + + parrots = TaggedItem.objects.get_by_model(Parrot, self.bar) + self.assertEquals(len(parrots), 3) + self.failUnless(self.late_parrot in parrots) + self.failUnless(self.passed_on_parrot in parrots) + self.failUnless(self.pining_for_the_fjords_parrot in parrots) + + def test_get_by_model_intersection(self): + parrots = TaggedItem.objects.get_by_model(Parrot, [self.foo, self.baz]) + self.assertEquals(len(parrots), 0) + + parrots = TaggedItem.objects.get_by_model(Parrot, [self.foo, self.bar]) + self.assertEquals(len(parrots), 1) + self.failUnless(self.pining_for_the_fjords_parrot in parrots) + + parrots = TaggedItem.objects.get_by_model(Parrot, [self.bar, self.ter]) + self.assertEquals(len(parrots), 2) + self.failUnless(self.late_parrot in parrots) + self.failUnless(self.passed_on_parrot in parrots) + + # Issue 114 - Intersection with non-existant tags + parrots = TaggedItem.objects.get_intersection_by_model(Parrot, []) + self.assertEquals(len(parrots), 0) + + def test_get_by_model_with_tag_querysets_as_input(self): + parrots = TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['foo', 'baz'])) + self.assertEquals(len(parrots), 0) + + parrots = TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['foo', 'bar'])) + self.assertEquals(len(parrots), 1) + self.failUnless(self.pining_for_the_fjords_parrot in parrots) + + parrots = TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['bar', 'ter'])) + self.assertEquals(len(parrots), 2) + self.failUnless(self.late_parrot in parrots) + self.failUnless(self.passed_on_parrot in parrots) + + def test_get_by_model_with_strings_as_input(self): + parrots = TaggedItem.objects.get_by_model(Parrot, 'foo baz') + self.assertEquals(len(parrots), 0) + + parrots = TaggedItem.objects.get_by_model(Parrot, 'foo bar') + self.assertEquals(len(parrots), 1) + self.failUnless(self.pining_for_the_fjords_parrot in parrots) + + parrots = TaggedItem.objects.get_by_model(Parrot, 'bar ter') + self.assertEquals(len(parrots), 2) + self.failUnless(self.late_parrot in parrots) + self.failUnless(self.passed_on_parrot in parrots) + + def test_get_by_model_with_lists_of_strings_as_input(self): + parrots = TaggedItem.objects.get_by_model(Parrot, ['foo', 'baz']) + self.assertEquals(len(parrots), 0) + + parrots = TaggedItem.objects.get_by_model(Parrot, ['foo', 'bar']) + self.assertEquals(len(parrots), 1) + self.failUnless(self.pining_for_the_fjords_parrot in parrots) + + parrots = TaggedItem.objects.get_by_model(Parrot, ['bar', 'ter']) + self.assertEquals(len(parrots), 2) + self.failUnless(self.late_parrot in parrots) + self.failUnless(self.passed_on_parrot in parrots) + + def test_get_by_nonexistent_tag(self): + # Issue 50 - Get by non-existent tag + parrots = TaggedItem.objects.get_by_model(Parrot, 'argatrons') + self.assertEquals(len(parrots), 0) + + def test_get_union_by_model(self): + parrots = TaggedItem.objects.get_union_by_model(Parrot, ['foo', 'ter']) + self.assertEquals(len(parrots), 4) + self.failUnless(self.late_parrot in parrots) + self.failUnless(self.no_more_parrot in parrots) + self.failUnless(self.passed_on_parrot in parrots) + self.failUnless(self.pining_for_the_fjords_parrot in parrots) + + parrots = TaggedItem.objects.get_union_by_model(Parrot, ['bar', 'baz']) + self.assertEquals(len(parrots), 3) + self.failUnless(self.late_parrot in parrots) + self.failUnless(self.passed_on_parrot in parrots) + self.failUnless(self.pining_for_the_fjords_parrot in parrots) + + # Issue 114 - Union with non-existant tags + parrots = TaggedItem.objects.get_union_by_model(Parrot, []) + self.assertEquals(len(parrots), 0) + +class TestGetRelatedTaggedItems(TestCase): + def setUp(self): + 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) + + self.l1 = Link.objects.create(name='link 1') + Tag.objects.update_tags(self.l1, 'tag1 tag2 tag3 tag4 tag5') + self.l2 = Link.objects.create(name='link 2') + Tag.objects.update_tags(self.l2, 'tag1 tag2 tag3') + self.l3 = Link.objects.create(name='link 3') + Tag.objects.update_tags(self.l3, 'tag1') + self.l4 = Link.objects.create(name='link 4') + + self.a1 = Article.objects.create(name='article 1') + Tag.objects.update_tags(self.a1, 'tag1 tag2 tag3 tag4') + + def test_get_related_objects_of_same_model(self): + related_objects = TaggedItem.objects.get_related(self.l1, Link) + self.assertEquals(len(related_objects), 2) + self.failUnless(self.l2 in related_objects) + self.failUnless(self.l3 in related_objects) + + related_objects = TaggedItem.objects.get_related(self.l4, Link) + self.assertEquals(len(related_objects), 0) + + def test_get_related_objects_of_same_model_limited_number_of_results(self): + # This fails on Oracle because it has no support for a 'LIMIT' clause. + # See http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:127412348064 + + # ask for no more than 1 result + related_objects = TaggedItem.objects.get_related(self.l1, Link, num=1) + self.assertEquals(len(related_objects), 1) + self.failUnless(self.l2 in related_objects) + + def test_get_related_objects_of_same_model_limit_related_items(self): + related_objects = TaggedItem.objects.get_related(self.l1, Link.objects.exclude(name='link 3')) + self.assertEquals(len(related_objects), 1) + self.failUnless(self.l2 in related_objects) + + def test_get_related_objects_of_different_model(self): + related_objects = TaggedItem.objects.get_related(self.a1, Link) + self.assertEquals(len(related_objects), 3) + self.failUnless(self.l1 in related_objects) + self.failUnless(self.l2 in related_objects) + self.failUnless(self.l3 in related_objects) + + Tag.objects.update_tags(self.a1, 'tag6') + related_objects = TaggedItem.objects.get_related(self.a1, Link) + self.assertEquals(len(related_objects), 0) + +class TestTagUsageForQuerySet(TestCase): + def setUp(self): + 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) + + def test_tag_usage_for_queryset(self): + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(state='no more'), counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 2) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(state__startswith='p'), counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 4) + self.failUnless((u'bar', 2) in relevant_attribute_list) + self.failUnless((u'baz', 1) in relevant_attribute_list) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4), counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 4) + self.failUnless((u'bar', 2) in relevant_attribute_list) + self.failUnless((u'baz', 1) in relevant_attribute_list) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless((u'bar', 1) in relevant_attribute_list) + self.failUnless((u'foo', 2) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), min_count=2) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 1) + self.failUnless((u'foo', 2) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4)) + relevant_attribute_list = [(tag.name, hasattr(tag, 'counts')) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 4) + self.failUnless((u'bar', False) in relevant_attribute_list) + self.failUnless((u'baz', False) in relevant_attribute_list) + self.failUnless((u'foo', False) in relevant_attribute_list) + self.failUnless((u'ter', False) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=99)) + relevant_attribute_list = [(tag.name, hasattr(tag, 'counts')) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 0) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless((u'bar', 2) in relevant_attribute_list) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), min_count=2) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 1) + self.failUnless((u'bar', 2) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l'))) + relevant_attribute_list = [(tag.name, hasattr(tag, 'counts')) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless((u'bar', False) in relevant_attribute_list) + self.failUnless((u'foo', False) in relevant_attribute_list) + self.failUnless((u'ter', False) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.exclude(state='passed on'), counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 3) + self.failUnless((u'bar', 2) in relevant_attribute_list) + self.failUnless((u'foo', 2) in relevant_attribute_list) + self.failUnless((u'ter', 2) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.exclude(state__startswith='p'), min_count=2) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 1) + self.failUnless((u'ter', 2) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.exclude(Q(perch__size__gt=6) | Q(perch__smelly=False)), counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 2) + self.failUnless((u'foo', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + + tag_usage = Tag.objects.usage_for_queryset(Parrot.objects.exclude(perch__smelly=True).filter(state__startswith='l'), counts=True) + relevant_attribute_list = [(tag.name, tag.count) for tag in tag_usage] + self.assertEquals(len(relevant_attribute_list), 2) + self.failUnless((u'bar', 1) in relevant_attribute_list) + self.failUnless((u'ter', 1) in relevant_attribute_list) + ################ # 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.'] -""" +class TestTagFieldInForms(TestCase): + def test_tag_field_in_modelform(self): + # Ensure that automatically created forms use TagField + class TestForm(forms.ModelForm): + class Meta: + model = FormTest + + form = TestForm() + self.assertEquals(form.fields['tags'].__class__.__name__, 'TagField') + + def test_recreation_of_tag_list_string_representations(self): + plain = Tag.objects.create(name='plain') + spaces = Tag.objects.create(name='spa ces') + comma = Tag.objects.create(name='com,ma') + self.assertEquals(edit_string_for_tags([plain]), u'plain') + self.assertEquals(edit_string_for_tags([plain, spaces]), u'plain, spa ces') + self.assertEquals(edit_string_for_tags([plain, spaces, comma]), u'plain, spa ces, "com,ma"') + self.assertEquals(edit_string_for_tags([plain, comma]), u'plain "com,ma"') + self.assertEquals(edit_string_for_tags([comma, spaces]), u'"com,ma", spa ces') + + def test_tag_d_validation(self): + t = TagField() + self.assertEquals(t.clean('foo'), u'foo') + self.assertEquals(t.clean('foo bar baz'), u'foo bar baz') + self.assertEquals(t.clean('foo,bar,baz'), u'foo,bar,baz') + self.assertEquals(t.clean('foo, bar, baz'), u'foo, bar, baz') + self.assertEquals(t.clean('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar'), + u'foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar') + try: + t.clean('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar') + except forms.ValidationError, ve: + self.assertEquals(str(ve), "[u'Each tag may be no more than 50 characters long.']") + except Exception, e: + raise e + else: + raise self.failureException('a ValidationError exception was supposed to have been raised.') -- cgit v1.2.3