Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Py3k #117

Merged
merged 10 commits into from

3 participants

@EnTeQuAk

Very simple port to Python3, does not contain any refactoring yet.

docs/index.txt
@@ -4,8 +4,7 @@ Welcome to django-taggit's documentation!
``django-taggit`` is a reusable Django application designed to making adding
tagging to your project easy and fun.
-``django-taggit`` works with Django 1.1 and 1.2 (see :doc:`issues` for known
-issues with older versions of Django), and Python 2.4-2.X.
+``django-taggit`` works with Django 1.4+ and Python 2.7-3.X.

Actually, it is 1.4.5+

Fixed in a0b5da6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@xordoquy xordoquy commented on the diff
taggit/models.py
@@ -10,7 +12,7 @@ class TagBase(models.Model):
name = models.CharField(verbose_name=_('Name'), unique=True, max_length=100)
slug = models.SlugField(verbose_name=_('Slug'), unique=True, max_length=100)
- def __unicode__(self):
+ def __str__(self):

The classes should have the python_2_unicode_compatible decorator.
See https://docs.djangoproject.com/en/dev/topics/python3/#str-and-unicode-methods

Thanks, fixed in d9fb9b1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
taggit/models.py
((5 lines not shown))
class ItemBase(models.Model):
- def __unicode__(self):
+ def __str__(self):

Missing decorator (cf previous comment)

Thanks, fixed (see previous comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@xordoquy xordoquy commented on the diff
@@ -31,5 +31,5 @@
'Programming Language :: Python',
'Framework :: Django',
],
- test_suite='taggit.tests.runtests.runtests',
+ test_suite='runtests.runtests',
)

We should probably add the python3 trove classifier too

Also on that file, the line version=".".join(map(str, VERSION)), should probably be updated. Not sure the str will work here under python3.

Fixed in a7bf54f, the version work pretty well with python3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@xordoquy xordoquy commented on the diff
.travis.yml
((6 lines not shown))
env:
+ - DJANGO=https://github.com/django/django/archive/master.tar.gz

Probably comment out master as the build won't pass yet (too many queries for a test)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@apollo13 apollo13 merged commit d9fb9b1 into alex:master
@apollo13
Collaborator
@EnTeQuAk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
15 .travis.yml
@@ -3,17 +3,28 @@ language: python
python:
- "2.6"
- "2.7"
+ - "3.2"
+ - "3.3"
env:
+ - DJANGO=https://github.com/django/django/archive/master.tar.gz

Probably comment out master as the build won't pass yet (too many queries for a test)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
- DJANGO=django==1.5 --use-mirrors
- DJANGO=django==1.4.5 --use-mirrors
- - DJANGO=django==1.3.7 --use-mirrors
install:
- pip install $DJANGO
+ - pip install -r requirements/travis-ci.txt --use-mirrors
script:
- - python setup.py test
+ - coverage run --source django_taggit runtests.py
+ - coverage report
notifications:
email: false
+
+matrix:
+ exclude:
+ - python: "3.2"
+ env: DJANGO=django==1.4.5 --use-mirrors
+ - python: "3.3"
+ env: DJANGO=django==1.4.5 --use-mirrors
View
1  MANIFEST.in
@@ -1,2 +1,3 @@
+include README.rst AUTHORS CHANGES.rst LICENSE
recursive-include docs *.txt
recursive-include taggit/locale *
View
6 docs/changelog.txt
@@ -1,6 +1,12 @@
Changelog
=========
+unreleased
+~~~~~~~~~~
+
+ * Python3 support
+ * Works only with Django 1.4 and Django 1.5
+
0.9.2
~~~~~
View
4 docs/index.txt
@@ -4,8 +4,7 @@ Welcome to django-taggit's documentation!
``django-taggit`` is a reusable Django application designed to making adding
tagging to your project easy and fun.
-``django-taggit`` works with Django 1.1 and 1.2 (see :doc:`issues` for known
-issues with older versions of Django), and Python 2.4-2.X.
+``django-taggit`` works with Django 1.4.5+ and Python 2.7-3.X.
.. toctree::
:maxdepth: 2
@@ -15,7 +14,6 @@ issues with older versions of Django), and Python 2.4-2.X.
admin
api
custom_tagging
- issues
external_apps
changelog
View
9 docs/issues.txt
@@ -1,9 +0,0 @@
-Known Issues
-============
-
-Currently there is 1 known issue:
-
- * When run under Django 1.1, doing ``Model.objects.all().delete()`` (or any
- bulk deletion operation) on a model with a ``TaggableManager`` will result
- in losing the tags for items beyond just those assosciated with the deleted
- objects. This issue is not present in Django 1.2.
View
1  requirements/docs.txt
@@ -0,0 +1 @@
+Sphinx
View
2  requirements/test.txt
@@ -0,0 +1,2 @@
+Django
+coverage
View
1  requirements/travis-ci.txt
@@ -0,0 +1 @@
+coverage
View
0  taggit/tests/runtests.py → runtests.py
File renamed without changes
View
7 setup.py
@@ -29,7 +29,12 @@
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.2',
+ 'Programming Language :: Python :: 3.3',
'Framework :: Django',
],
- test_suite='taggit.tests.runtests.runtests',
+ test_suite='runtests.runtests',
)

We should probably add the python3 trove classifier too

Also on that file, the line version=".".join(map(str, VERSION)), should probably be updated. Not sure the str will work here under python3.

Fixed in a7bf54f, the version work pretty well with python3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
View
2  taggit/admin.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from django.contrib import admin
from taggit.models import Tag, TaggedItem
View
5 taggit/forms.py
@@ -1,12 +1,15 @@
+from __future__ import unicode_literals
+
from django import forms
from django.utils.translation import ugettext as _
+from django.utils import six
from taggit.utils import parse_tags, edit_string_for_tags
class TagWidget(forms.TextInput):
def render(self, name, value, attrs=None):
- if value is not None and not isinstance(value, basestring):
+ if value is not None and not isinstance(value, six.string_types):
value = edit_string_for_tags([o.tag for o in value.select_related("tag")])
return super(TagWidget, self).render(name, value, attrs)
View
43 taggit/managers.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from django.contrib.contenttypes.generic import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
@@ -5,27 +7,13 @@
from django.db.models.related import RelatedObject
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _
+from django.utils import six
from taggit.forms import TagField
from taggit.models import TaggedItem, GenericTaggedItemBase
from taggit.utils import require_instance_manager
-try:
- all
-except NameError:
- # 2.4 compat
- try:
- from django.utils.itercompat import all
- except ImportError:
- # 1.1.X compat
- def all(iterable):
- for item in iterable:
- if not item:
- return False
- return True
-
-
class TaggableRel(ManyToManyRel):
def __init__(self):
self.related_name = None
@@ -68,7 +56,7 @@ def contribute_to_class(self, cls, name):
cls._meta.add_field(self)
setattr(cls, name, self)
if not cls._meta.abstract:
- if isinstance(self.through, basestring):
+ if isinstance(self.through, six.string_types):
def resolve_related_class(field, model, cls):
self.through = model
self.post_through_setup(cls)
@@ -78,6 +66,14 @@ def resolve_related_class(field, model, cls):
else:
self.post_through_setup(cls)
+ def __lt__(self, other):
+ """
+ Required contribute_to_class as Django uses bisect
+ for ordered class contribution and bisect requires
+ a orderable type in py3.
+ """
+ return False
+
def post_through_setup(self, cls):
self.use_gfk = (
self.through is None or issubclass(self.through, GenericTaggedItemBase)
@@ -132,7 +128,8 @@ def extra_filters(self, pieces, pos, negate):
if negate or not self.use_gfk:
return []
prefix = "__".join(["tagged_items"] + pieces[:pos-2])
- cts = map(ContentType.objects.get_for_model, _get_subclasses(self.model))
+ get = ContentType.objects.get_for_model
+ cts = [get(obj) for obj in _get_subclasses(self.model)]
if len(cts) == 1:
return [("%s__content_type" % prefix, cts[0])]
return [("%s__content_type__in" % prefix, cts)]
@@ -197,7 +194,7 @@ def most_common(self):
def similar_objects(self):
lookup_kwargs = self._lookup_kwargs()
lookup_keys = sorted(lookup_kwargs)
- qs = self.through.objects.values(*lookup_kwargs.keys())
+ qs = self.through.objects.values(*six.iterkeys(lookup_kwargs))
qs = qs.annotate(n=models.Count('pk'))
qs = qs.exclude(**lookup_kwargs)
qs = qs.filter(tag__in=self.all())
@@ -220,7 +217,7 @@ def similar_objects(self):
preload.setdefault(result['content_type'], set())
preload[result["content_type"]].add(result["object_id"])
- for ct, obj_ids in preload.iteritems():
+ for ct, obj_ids in preload.items():
ct = ContentType.objects.get_for_id(ct)
for obj in ct.model_class()._default_manager.filter(pk__in=obj_ids):
items[(ct.pk, obj.pk)] = obj
@@ -243,3 +240,11 @@ def _get_subclasses(model):
getattr(field.field.rel, "parent_link", None)):
subclasses.extend(_get_subclasses(field.model))
return subclasses
+
+
+# `total_ordering` does not exist in Django 1.4, as such
+# we special case this import to be py3k specific which
+# is not supported by Django 1.4
+if six.PY3:
+ from django.utils.functional import total_ordering
+ TaggableManager = total_ordering(TaggableManager)
View
40 taggit/migrations/0001_initial.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
import datetime
from south.db import db
from south.v2 import SchemaMigration
@@ -9,52 +11,52 @@ class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Tag'
- db.create_table(u'taggit_tag', (
- (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ db.create_table('taggit_tag', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=100)),
))
- db.send_create_signal(u'taggit', ['Tag'])
+ db.send_create_signal('taggit', ['Tag'])
# Adding model 'TaggedItem'
- db.create_table(u'taggit_taggeditem', (
- (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('tag', self.gf('django.db.models.fields.related.ForeignKey')(related_name=u'taggit_taggeditem_items', to=orm['taggit.Tag'])),
+ db.create_table('taggit_taggeditem', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('tag', self.gf('django.db.models.fields.related.ForeignKey')(related_name='taggit_taggeditem_items', to=orm['taggit.Tag'])),
('object_id', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
- ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name=u'taggit_taggeditem_tagged_items', to=orm['contenttypes.ContentType'])),
+ ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='taggit_taggeditem_tagged_items', to=orm['contenttypes.ContentType'])),
))
- db.send_create_signal(u'taggit', ['TaggedItem'])
+ db.send_create_signal('taggit', ['TaggedItem'])
def backwards(self, orm):
# Deleting model 'Tag'
- db.delete_table(u'taggit_tag')
+ db.delete_table('taggit_tag')
# Deleting model 'TaggedItem'
- db.delete_table(u'taggit_taggeditem')
+ db.delete_table('taggit_taggeditem')
models = {
- u'contenttypes.contenttype': {
+ 'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
- u'taggit.tag': {
+ 'taggit.tag': {
'Meta': {'object_name': 'Tag'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
- u'taggit.taggeditem': {
+ 'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
}
}
- complete_apps = ['taggit']
+ complete_apps = ['taggit']
View
24 taggit/migrations/0002_unique_tagnames.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
import datetime
from south.db import db
from south.v2 import SchemaMigration
@@ -9,35 +11,35 @@ class Migration(SchemaMigration):
def forwards(self, orm):
# Adding unique constraint on 'Tag', fields ['name']
- db.create_unique(u'taggit_tag', ['name'])
+ db.create_unique('taggit_tag', ['name'])
def backwards(self, orm):
# Removing unique constraint on 'Tag', fields ['name']
- db.delete_unique(u'taggit_tag', ['name'])
+ db.delete_unique('taggit_tag', ['name'])
models = {
- u'contenttypes.contenttype': {
+ 'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
- u'taggit.tag': {
+ 'taggit.tag': {
'Meta': {'object_name': 'Tag'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
- u'taggit.taggeditem': {
+ 'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
}
}
- complete_apps = ['taggit']
+ complete_apps = ['taggit']
View
15 taggit/models.py
@@ -1,16 +1,20 @@
+from __future__ import unicode_literals
+
import django
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.generic import GenericForeignKey
from django.db import models, IntegrityError, transaction
from django.template.defaultfilters import slugify as default_slugify
from django.utils.translation import ugettext_lazy as _, ugettext
+from django.utils.encoding import python_2_unicode_compatible
+@python_2_unicode_compatible
class TagBase(models.Model):
name = models.CharField(verbose_name=_('Name'), unique=True, max_length=100)
slug = models.SlugField(verbose_name=_('Slug'), unique=True, max_length=100)
- def __unicode__(self):
+ def __str__(self):

The classes should have the python_2_unicode_compatible decorator.
See https://docs.djangoproject.com/en/dev/topics/python3/#str-and-unicode-methods

Thanks, fixed in d9fb9b1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
return self.name
class Meta:
@@ -57,9 +61,9 @@ class Meta:
verbose_name_plural = _("Tags")
-
+@python_2_unicode_compatible
class ItemBase(models.Model):
- def __unicode__(self):
+ def __str__(self):
return ugettext("%(object)s tagged with %(tag)s") % {
"object": self.content_object,
"tag": self.tag
@@ -90,10 +94,7 @@ def bulk_lookup_kwargs(cls, instances):
class TaggedItemBase(ItemBase):
- if django.VERSION < (1, 2):
- tag = models.ForeignKey(Tag, related_name="%(class)s_items")
- else:
- tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items")
+ tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items")
class Meta:
abstract = True
View
2  taggit/tests/forms.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from django import forms
from taggit.tests.models import Food, DirectFood, CustomPKFood, OfficialFood
View
37 taggit/tests/models.py
@@ -1,26 +1,32 @@
+from __future__ import unicode_literals
+
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
from taggit.managers import TaggableManager
from taggit.models import (TaggedItemBase, GenericTaggedItemBase, TaggedItem,
TagBase, Tag)
+@python_2_unicode_compatible
class Food(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class Pet(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager()
- def __unicode__(self):
+ def __str__(self):
return self.name
+
class HousePet(Pet):
trained = models.BooleanField()
@@ -30,22 +36,31 @@ class HousePet(Pet):
class TaggedFood(TaggedItemBase):
content_object = models.ForeignKey('DirectFood')
+
class TaggedPet(TaggedItemBase):
content_object = models.ForeignKey('DirectPet')
+
+@python_2_unicode_compatible
class DirectFood(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager(through="TaggedFood")
+ def __str__(self):
+ return self.name
+
+
+@python_2_unicode_compatible
class DirectPet(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager(through=TaggedPet)
- def __unicode__(self):
+ def __str__(self):
return self.name
+
class DirectHousePet(DirectPet):
trained = models.BooleanField()
@@ -58,20 +73,22 @@ class TaggedCustomPKFood(TaggedItemBase):
class TaggedCustomPKPet(TaggedItemBase):
content_object = models.ForeignKey('CustomPKPet')
+@python_2_unicode_compatible
class CustomPKFood(models.Model):
name = models.CharField(max_length=50, primary_key=True)
tags = TaggableManager(through=TaggedCustomPKFood)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class CustomPKPet(models.Model):
name = models.CharField(max_length=50, primary_key=True)
tags = TaggableManager(through=TaggedCustomPKPet)
- def __unicode__(self):
+ def __str__(self):
return self.name
class CustomPKHousePet(CustomPKPet):
@@ -85,20 +102,22 @@ class OfficialTag(TagBase):
class OfficialThroughModel(GenericTaggedItemBase):
tag = models.ForeignKey(OfficialTag, related_name="tagged_items")
+@python_2_unicode_compatible
class OfficialFood(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager(through=OfficialThroughModel)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class OfficialPet(models.Model):
name = models.CharField(max_length=50)
tags = TaggableManager(through=OfficialThroughModel)
- def __unicode__(self):
+ def __str__(self):
return self.name
class OfficialHousePet(OfficialPet):
@@ -129,6 +148,7 @@ def slugify(self, tag, i=None):
slug += "-%d" % i
return slug
+
class ArticleTaggedItem(TaggedItem):
class Meta:
proxy = True
@@ -137,7 +157,8 @@ class Meta:
def tag_model(self):
return ArticleTag
+
class Article(models.Model):
title = models.CharField(max_length=100)
- tags = TaggableManager(through=ArticleTaggedItem)
+ tags = TaggableManager(through=ArticleTaggedItem)
View
90 taggit/tests/tests.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from unittest import TestCase as UnitTestCase
import django
@@ -5,6 +7,8 @@
from django.core.exceptions import ValidationError
from django.db import connection
from django.test import TestCase, TransactionTestCase
+from django.utils import six
+from django.utils.encoding import force_text
from taggit.managers import TaggableManager
from taggit.models import Tag, TaggedItem
@@ -19,7 +23,7 @@
class BaseTaggingTest(object):
def assert_tags_equal(self, qs, tags, sort=True, attr="name"):
- got = map(lambda tag: getattr(tag, attr), qs)
+ got = [getattr(obj, attr) for obj in qs]
if sort:
got.sort()
tags.sort()
@@ -52,7 +56,7 @@ def _get_form_str(self, form_str):
return form_str
def assert_form_renders(self, form, html):
- self.assertEqual(str(form), self._get_form_str(html))
+ self.assertHTMLEqual(str(form), self._get_form_str(html))
class BaseTaggingTestCase(TestCase, BaseTaggingTest):
pass
@@ -210,10 +214,12 @@ def test_lookup_by_tag(self):
cat = self.housepet_model.objects.create(name="cat", trained=True)
cat.tags.add("fuzzy")
- self.assertEqual(
- map(lambda o: o.pk, self.pet_model.objects.filter(tags__name__in=["fuzzy"])),
- [kitty.pk, cat.pk]
- )
+ pks = self.pet_model.objects.filter(tags__name__in=["fuzzy"])
+ model_name = self.pet_model.__name__
+ self.assertQuerysetEqual(pks,
+ ['<{0}: kitty>'.format(model_name),
+ '<{0}: cat>'.format(model_name)],
+ ordered=False)
def test_exclude(self):
apple = self.food_model.objects.create(name="apple")
@@ -224,10 +230,12 @@ def test_exclude(self):
guava = self.food_model.objects.create(name="guava")
- self.assertEqual(
- map(lambda o: o.pk, self.food_model.objects.exclude(tags__name__in=["red"])),
- [pear.pk, guava.pk],
- )
+ pks = self.food_model.objects.exclude(tags__name__in=["red"])
+ model_name = self.food_model.__name__
+ self.assertQuerysetEqual(pks,
+ ['<{0}: pear>'.format(model_name),
+ '<{0}: guava>'.format(model_name)],
+ ordered=False)
def test_similarity_by_tag(self):
"""Test that pears are more similar to apples than watermelons"""
@@ -242,7 +250,8 @@ def test_similarity_by_tag(self):
similar_objs = apple.tags.similar_objects()
self.assertEqual(similar_objs, [pear, watermelon])
- self.assertEqual(map(lambda x: x.similar_tags, similar_objs), [3, 2])
+ self.assertEqual([obj.similar_tags for obj in similar_objs],
+ [3, 2])
def test_tag_reuse(self):
apple = self.food_model.objects.create(name="apple")
@@ -268,7 +277,7 @@ def test_taggeditem_unicode(self):
ross.tags.add("president")
self.assertEqual(
- unicode(self.taggeditem_model.objects.all()[0]),
+ force_text(self.taggeditem_model.objects.all()[0]),
"ross tagged with president"
)
@@ -328,10 +337,7 @@ def test_extra_fields(self):
pear = self.food_model.objects.create(name="Pear")
pear.tags.add("delicious")
- self.assertEqual(
- map(lambda o: o.pk, self.food_model.objects.filter(tags__official=False)),
- [apple.pk],
- )
+ self.assertEqual(apple, self.food_model.objects.get(tags__official=False))
class TaggableFormTestCase(BaseTaggingTestCase):
@@ -339,7 +345,7 @@ class TaggableFormTestCase(BaseTaggingTestCase):
food_model = Food
def test_form(self):
- self.assertEqual(self.form_class.base_fields.keys(), ['name', 'tags'])
+ self.assertEqual(list(self.form_class.base_fields), ['name', 'tags'])
f = self.form_class({'name': 'apple', 'tags': 'green, red, yummy'})
self.assert_form_renders(f, """<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" value="apple" maxlength="50" /></td></tr>
@@ -375,7 +381,7 @@ def test_formfield(self):
tm = TaggableManager(verbose_name='categories', help_text='Add some categories', blank=True)
ff = tm.formfield()
self.assertEqual(ff.label, 'Categories')
- self.assertEqual(ff.help_text, u'Add some categories')
+ self.assertEqual(ff.help_text, 'Add some categories')
self.assertEqual(ff.required, False)
self.assertEqual(ff.clean(""), [])
@@ -407,54 +413,54 @@ def test_with_simple_space_delimited_tags(self):
"""
Test with simple space-delimited tags.
"""
- self.assertEqual(parse_tags('one'), [u'one'])
- self.assertEqual(parse_tags('one two'), [u'one', u'two'])
- self.assertEqual(parse_tags('one two three'), [u'one', u'three', u'two'])
- self.assertEqual(parse_tags('one one two two'), [u'one', u'two'])
+ self.assertEqual(parse_tags('one'), ['one'])
+ self.assertEqual(parse_tags('one two'), ['one', 'two'])
+ self.assertEqual(parse_tags('one two three'), ['one', 'three', 'two'])
+ self.assertEqual(parse_tags('one one two two'), ['one', '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.assertEqual(parse_tags(',one'), [u'one'])
- self.assertEqual(parse_tags(',one two'), [u'one two'])
- self.assertEqual(parse_tags(',one two three'), [u'one two three'])
+ self.assertEqual(parse_tags(',one'), ['one'])
+ self.assertEqual(parse_tags(',one two'), ['one two'])
+ self.assertEqual(parse_tags(',one two three'), ['one two three'])
self.assertEqual(parse_tags('a-one, a-two and a-three'),
- [u'a-one', u'a-two and a-three'])
+ ['a-one', '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.assertEqual(parse_tags('"one'), [u'one'])
- self.assertEqual(parse_tags('"one two'), [u'one', u'two'])
- self.assertEqual(parse_tags('"one two three'), [u'one', u'three', u'two'])
- self.assertEqual(parse_tags('"one two"'), [u'one two'])
+ self.assertEqual(parse_tags('"one'), ['one'])
+ self.assertEqual(parse_tags('"one two'), ['one', 'two'])
+ self.assertEqual(parse_tags('"one two three'), ['one', 'three', 'two'])
+ self.assertEqual(parse_tags('"one two"'), ['one two'])
self.assertEqual(parse_tags('a-one "a-two and a-three"'),
- [u'a-one', u'a-two and a-three'])
+ ['a-one', 'a-two and a-three'])
def test_with_no_loose_commas(self):
"""
Test with no loose commas -- split on spaces.
"""
- self.assertEqual(parse_tags('one two "thr,ee"'), [u'one', u'thr,ee', u'two'])
+ self.assertEqual(parse_tags('one two "thr,ee"'), ['one', 'thr,ee', 'two'])
def test_with_loose_commas(self):
"""
Loose commas - split on commas
"""
- self.assertEqual(parse_tags('"one", two three'), [u'one', u'two three'])
+ self.assertEqual(parse_tags('"one", two three'), ['one', 'two three'])
def test_tags_with_double_quotes_can_contain_commas(self):
"""
Double quotes can contain commas
"""
self.assertEqual(parse_tags('a-one "a-two, and a-three"'),
- [u'a-one', u'a-two, and a-three'])
+ ['a-one', 'a-two, and a-three'])
self.assertEqual(parse_tags('"two", one, one, two, "one"'),
- [u'one', u'two'])
+ ['one', 'two'])
def test_with_naughty_input(self):
"""
@@ -467,16 +473,16 @@ def test_with_naughty_input(self):
self.assertEqual(parse_tags('""'), [])
self.assertEqual(parse_tags('"' * 7), [])
self.assertEqual(parse_tags(',,,,,,'), [])
- self.assertEqual(parse_tags('",",",",",",","'), [u','])
+ self.assertEqual(parse_tags('",",",",",",","'), [','])
self.assertEqual(parse_tags('a-one "a-two" and "a-three'),
- [u'a-one', u'a-three', u'a-two', u'and'])
+ ['a-one', 'a-three', 'a-two', 'and'])
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.assertEqual(edit_string_for_tags([plain]), u'plain')
- self.assertEqual(edit_string_for_tags([plain, spaces]), u'"spa ces", plain')
- self.assertEqual(edit_string_for_tags([plain, spaces, comma]), u'"com,ma", "spa ces", plain')
- self.assertEqual(edit_string_for_tags([plain, comma]), u'"com,ma", plain')
- self.assertEqual(edit_string_for_tags([comma, spaces]), u'"com,ma", "spa ces"')
+ self.assertEqual(edit_string_for_tags([plain]), 'plain')
+ self.assertEqual(edit_string_for_tags([plain, spaces]), '"spa ces", plain')
+ self.assertEqual(edit_string_for_tags([plain, spaces, comma]), '"com,ma", "spa ces", plain')
+ self.assertEqual(edit_string_for_tags([plain, comma]), '"com,ma", plain')
+ self.assertEqual(edit_string_for_tags([comma, spaces]), '"com,ma", "spa ces"')
View
41 taggit/utils.py
@@ -1,5 +1,8 @@
-from django.utils.encoding import force_unicode
+from __future__ import unicode_literals
+
+from django.utils.encoding import force_text
from django.utils.functional import wraps
+from django.utils import six
def parse_tags(tagstring):
@@ -16,13 +19,13 @@ def parse_tags(tagstring):
if not tagstring:
return []
- tagstring = force_unicode(tagstring)
+ tagstring = force_text(tagstring)
# Special case - if there are no commas or double quotes in the
# input, we don't *do* a recall... I mean, we know we only need to
# split on spaces.
- if u',' not in tagstring and u'"' not in tagstring:
- words = list(set(split_strip(tagstring, u' ')))
+ if ',' not in tagstring and '"' not in tagstring:
+ words = list(set(split_strip(tagstring, ' ')))
words.sort()
return words
@@ -36,39 +39,39 @@ def parse_tags(tagstring):
i = iter(tagstring)
try:
while True:
- c = i.next()
- if c == u'"':
+ c = six.next(i)
+ if c == '"':
if buffer:
- to_be_split.append(u''.join(buffer))
+ to_be_split.append(''.join(buffer))
buffer = []
# Find the matching quote
open_quote = True
- c = i.next()
- while c != u'"':
+ c = six.next(i)
+ while c != '"':
buffer.append(c)
- c = i.next()
+ c = six.next(i)
if buffer:
- word = u''.join(buffer).strip()
+ word = ''.join(buffer).strip()
if word:
words.append(word)
buffer = []
open_quote = False
else:
- if not saw_loose_comma and c == u',':
+ if not saw_loose_comma and c == ',':
saw_loose_comma = True
buffer.append(c)
except StopIteration:
# If we were parsing an open quote which was never closed treat
# the buffer as unquoted.
if buffer:
- if open_quote and u',' in buffer:
+ if open_quote and ',' in buffer:
saw_loose_comma = True
- to_be_split.append(u''.join(buffer))
+ to_be_split.append(''.join(buffer))
if to_be_split:
if saw_loose_comma:
- delimiter = u','
+ delimiter = ','
else:
- delimiter = u' '
+ delimiter = ' '
for chunk in to_be_split:
words.extend(split_strip(chunk, delimiter))
words = list(set(words))
@@ -76,7 +79,7 @@ def parse_tags(tagstring):
return words
-def split_strip(string, delimiter=u','):
+def split_strip(string, delimiter=','):
"""
Splits ``string`` on ``delimiter``, stripping each resulting string
and returning a list of non-empty strings.
@@ -110,11 +113,11 @@ def edit_string_for_tags(tags):
names = []
for tag in tags:
name = tag.name
- if u',' in name or u' ' in name:
+ if ',' in name or ' ' in name:
names.append('"%s"' % name)
else:
names.append(name)
- return u', '.join(sorted(names))
+ return ', '.join(sorted(names))
def require_instance_manager(func):
View
2  taggit/views.py
@@ -1,3 +1,5 @@
+from __future__ import unicode_literals
+
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404
from django.views.generic.list_detail import object_list
Something went wrong with that request. Please try again.