Skip to content

Commit

Permalink
Got tests back in working order; documentation updates for Django 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathan.buchanan committed Oct 30, 2008
1 parent 34ef307 commit 179595d
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 124 deletions.
22 changes: 10 additions & 12 deletions docs/overview.txt
Expand Up @@ -76,7 +76,7 @@ the command ``svn update`` from within the ``tagging-trunk`` directory.
copy of the source code.

.. _`Subversion`: http://subversion.tigris.org
.. _`PYTHONPATH`: http://docs.python.org/tut/node8.html#SECTION008110000000000000000
.. _`PYTHONPATH`: http://www.python.org/doc/2.5.2/tut/node8.html#SECTION008120000000000000000
.. _`junction`: http://www.microsoft.com/technet/sysinternals/FileAndDisk/Junction.mspx
.. _`CHANGELOG`: http://django-tagging.googlecode.com/svn/trunk/CHANGELOG.txt
.. _`backwards-incompatible changes wiki page`: http://code.google.com/p/django-tagging/wiki/BackwardsIncompatibleChanges
Expand Down Expand Up @@ -119,8 +119,7 @@ Default: ``50``

An integer which specifies the maximum length which any tag is allowed
to have. This is used for validation in the ``django.contrib.admin``
application and in any ``newforms`` forms automatically generated using
``ModelForm``.
application and in any forms automatically generated using ``ModelForm``.


Registering your models
Expand Down Expand Up @@ -453,7 +452,7 @@ greater than 99::

>>> Tag.objects.usage_for_model(Widget, filters=dict(size__gt=99, user__username='Alan'))

.. _`field lookups`: http://www.djangoproject.com/documentation/db-api/#field-lookups
.. _`field lookups`: http://docs.djangoproject.com/en/dev/topics/db/queries/#field-lookups

**New in development version**

Expand Down Expand Up @@ -699,18 +698,17 @@ model::

This field will also validate that it has been given a valid list of
tag names, separated by a single comma, a single space or a comma
followed by a space, using the ``is_tag_list`` validator from
``tagging.validators``.
followed by a space.


Form fields
===========

The ``tagging.forms`` module contains a ``Field`` for use with
Django's `newforms library`_ which takes care of validating tag name
Django's `forms library`_ which takes care of validating tag name
input when used in your forms.

.. _`newforms library`: http://www.djangoproject.com/documentation/newforms/
.. _`forms library`: http://docs.djangoproject.com/en/dev/topics/forms/

Field types
-----------
Expand All @@ -722,9 +720,9 @@ A form ``Field`` which is displayed as a single-line text input, which
validates that the input it receives is a valid list of tag names.

When you generate a form for one of your models automatically, using
the ``ModelForm`` class provided by newforms, any
``tagging.fields.TagField`` fields in your model will automatically be
represented by a ``tagging.forms.TagField`` in the generated form.
the ``ModelForm`` class, any ``tagging.fields.TagField`` fields in your
model will automatically be represented by a ``tagging.forms.TagField``
in the generated form.


Generic views
Expand Down Expand Up @@ -774,7 +772,7 @@ template context variables which may be provided.

* ``tag``: The ``Tag`` instance for the given tag.

.. _`object_list documentation`: http://www.djangoproject.com/documentation/generic_views/#django-views-generic-list-detail-object-list
.. _`object_list documentation`: http://docs.djangoproject.com/en/dev/ref/generic-views/#django-views-generic-list-detail-object-list

Example usage
~~~~~~~~~~~~~
Expand Down
24 changes: 17 additions & 7 deletions tagging/forms.py
@@ -1,22 +1,27 @@
"""
Tagging components for Django's ``newforms`` form library.
Tagging components for Django's form library.
"""
from django import forms
from django.utils.translation import ugettext as _

from tagging import settings
from tagging.models import Tag
from tagging.validators import is_tag, is_tag_list
from tagging.utils import parse_tag_input

class AdminTagForm(forms.ModelForm):
class Meta:
model = Tag

def clean_name(self):
value = self.cleaned_data["name"]
return is_tag(value)

value = self.cleaned_data['name']
tag_names = parse_tag_input(value)
if len(tag_names) > 1:
raise ValidationError(_('Multiple tags were given.'))
elif len(tag_names[0]) > settings.MAX_TAG_LENGTH:
raise forms.ValidationError(
_('A tag may be no more than %s characters long.') %
settings.MAX_TAG_LENGTH)
return value

class TagField(forms.CharField):
"""
Expand All @@ -27,4 +32,9 @@ def clean(self, value):
value = super(TagField, self).clean(value)
if value == u'':
return value
return is_tag_list(value)
for tag_name in parse_tag_input(value):
if len(tag_name) > settings.MAX_TAG_LENGTH:
raise forms.ValidationError(
_('Each tag may be no more than %s characters long.') %
settings.MAX_TAG_LENGTH)
return value
11 changes: 8 additions & 3 deletions tagging/models.py
Expand Up @@ -267,7 +267,7 @@ class TaggedItemManager(models.Manager):
objects we're interested in, then use the ORM's ``__in``
lookup to return a ``QuerySet``.
now that the queryset-refactor branch is in the trunk, this can be
Now that the queryset-refactor branch is in the trunk, this can be
tidied up significantly.
"""
def get_by_model(self, queryset_or_model, tags):
Expand Down Expand Up @@ -419,11 +419,16 @@ def get_related(self, obj, queryset_or_model, num=None):
'tag': qn(self.model._meta.get_field('tag').rel.to._meta.db_table),
'content_type_id': content_type.pk,
'related_content_type_id': related_content_type.pk,
'limit_offset': num is not None and connection.ops.limit_offset_sql(num) or '',
# Hardcoding this for now just to get tests working again - this
# should now be handled by the query object.
'limit_offset': num is not None and 'LIMIT %s' or '',
}

cursor = connection.cursor()
cursor.execute(query, [obj.pk])
params = [obj.pk]
if num is not None:
params.append(num)
cursor.execute(query, params)
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
# Use in_bulk here instead of an id__in lookup, because id__in would
Expand Down
103 changes: 33 additions & 70 deletions tagging/tests/tests.py
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
tests = r"""
r"""
>>> import os
>>> from django import newforms as forms
>>> 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
>>> from tagging.validators import is_tag_list, is_tag
#############
# Utilities #
Expand Down Expand Up @@ -152,24 +152,6 @@
...
ValueError: Invalid distribution algorithm specified: cheese.
# Validators ##################################################################
>>> is_tag_list('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {})
Traceback (most recent call last):
...
ValidationError: [u'Each tag may be no more than 50 characters long.']
>>> is_tag('"test"', {})
>>> is_tag(',test', {})
>>> is_tag('f o o', {})
Traceback (most recent call last):
...
ValidationError: [u'Multiple tags were given.']
>>> is_tag_list('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {})
Traceback (most recent call last):
...
ValidationError: [u'Each tag may be no more than 50 characters long.']
###########
# Tagging #
###########
Expand Down Expand Up @@ -393,6 +375,36 @@
>>> 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 #
################
Expand Down Expand Up @@ -444,52 +456,3 @@
...
ValidationError: [u'Each tag may be no more than 50 characters long.']
"""

tests_pre_qsrf = tests + r"""
# Limiting results to a queryset
>>> Tag.objects.usage_for_queryset(Parrot.objects.filter())
Traceback (most recent call last):
...
AttributeError: 'TagManager.usage_for_queryset' is not compatible with pre-queryset-refactor versions of Django.
"""

tests_post_qsrf = tests + r"""
>>> from django.db.models import Q
# 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)]
"""

try:
from django.db.models.query import parse_lookup
except ImportError:
__test__ = {'post-qsrf': tests_post_qsrf}
else:
__test__ = {'pre-qsrf': tests_pre_qsrf}
32 changes: 0 additions & 32 deletions tagging/validators.py

This file was deleted.

0 comments on commit 179595d

Please sign in to comment.