# -*- 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) [] >>> get_tag_list('cheese toast') [, ] >>> get_tag_list(u'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: 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.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, '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.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]) [, ] # 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') [] # 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) [] # 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) [] """