# -*- 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 ############# # 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. ########### # 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)] ################ # 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.'] """