Commit ed8af524 by Florian Apolloner

Merge branch 'develop'

parents 316ee157 20780f93
Changelog Changelog
========= =========
0.12.2 (21.09.2014)
~~~~~~~~~~~~~~~~~~~
* Fixed 1.7 migrations.
0.12.1 (10.08.2014) 0.12.1 (10.08.2014)
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
* Final (hopefully) fixes for the upcoming Django 1.7 release. * Final (hopefully) fixes for the upcoming Django 1.7 release.
......
...@@ -3,3 +3,14 @@ license-file = LICENSE ...@@ -3,3 +3,14 @@ license-file = LICENSE
[wheel] [wheel]
universal=1 universal=1
[flake8]
# ignore=NONE
# max-line-length=100
# E302: expected 2 blank lines, found 1 [E302]
# E501: line too long
ignore=E501,E302
[isort]
forced_separate=tests,taggit
...@@ -7,7 +7,7 @@ f.close() ...@@ -7,7 +7,7 @@ f.close()
setup( setup(
name='django-taggit', name='django-taggit',
version='0.12.1', version='0.12.2',
description='django-taggit is a reusable Django application for simple tagging.', description='django-taggit is a reusable Django application for simple tagging.',
long_description=readme, long_description=readme,
author='Alex Gaynor', author='Alex Gaynor',
......
VERSION = (0, 12, 1) VERSION = (0, 12, 2)
from __future__ import unicode_literals from __future__ import unicode_literals
from django import forms from django import forms
from django.utils.translation import ugettext as _
from django.utils import six from django.utils import six
from django.utils.translation import ugettext as _
from taggit.utils import parse_tags, edit_string_for_tags from taggit.utils import edit_string_for_tags, parse_tags
class TagWidget(forms.TextInput): class TagWidget(forms.TextInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if value is not None and not isinstance(value, six.string_types): 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")]) value = edit_string_for_tags([
o.tag for o in value.select_related("tag")])
return super(TagWidget, self).render(name, value, attrs) return super(TagWidget, self).render(name, value, attrs)
class TagField(forms.CharField): class TagField(forms.CharField):
widget = TagWidget widget = TagWidget
...@@ -21,4 +23,5 @@ class TagField(forms.CharField): ...@@ -21,4 +23,5 @@ class TagField(forms.CharField):
try: try:
return parse_tags(value) return parse_tags(value)
except ValueError: except ValueError:
raise forms.ValidationError(_("Please provide a comma-separated list of tags.")) raise forms.ValidationError(
_("Please provide a comma-separated list of tags."))
from __future__ import unicode_literals from __future__ import unicode_literals
from operator import attrgetter from operator import attrgetter
from django import VERSION from django import VERSION
try:
from django.contrib.contenttypes.fields import GenericRelation
except ImportError: # django < 1.7
from django.contrib.contenttypes.generic import GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models, router from django.db import models, router
from django.db.models.fields import Field from django.db.models.fields import Field
from django.db.models.fields.related import ManyToManyRel, RelatedField, add_lazy_relation from django.db.models.fields.related import (add_lazy_relation, ManyToManyRel,
RelatedField)
from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject
from django.utils import six
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import six
from taggit.forms import TagField
from taggit.models import GenericTaggedItemBase, TaggedItem
from taggit.utils import require_instance_manager
try:
from django.contrib.contenttypes.fields import GenericRelation
except ImportError: # django < 1.7
from django.contrib.contenttypes.generic import GenericRelation
try: try:
from django.db.models.related import PathInfo from django.db.models.related import PathInfo
except ImportError: except ImportError:
pass # PathInfo is not used on Django < 1.6 pass # PathInfo is not used on Django < 1.6
from taggit.forms import TagField
from taggit.models import TaggedItem, GenericTaggedItemBase
from taggit.utils import require_instance_manager
def _model_name(model): def _model_name(model):
...@@ -102,7 +106,7 @@ class _TaggableManager(models.Manager): ...@@ -102,7 +106,7 @@ class _TaggableManager(models.Manager):
else 'content_object') else 'content_object')
fk = self.through._meta.get_field(fieldname) fk = self.through._meta.get_field(fieldname)
query = { query = {
'%s__%s__in' % (self.through.tag_relname(), fk.name) : '%s__%s__in' % (self.through.tag_relname(), fk.name):
set(obj._get_pk_val() for obj in instances) set(obj._get_pk_val() for obj in instances)
} }
join_table = self.through._meta.db_table join_table = self.through._meta.db_table
...@@ -110,13 +114,13 @@ class _TaggableManager(models.Manager): ...@@ -110,13 +114,13 @@ class _TaggableManager(models.Manager):
connection = connections[db] connection = connections[db]
qn = connection.ops.quote_name qn = connection.ops.quote_name
qs = self.get_queryset().using(db)._next_is_sticky().filter(**query).extra( qs = self.get_queryset().using(db)._next_is_sticky().filter(**query).extra(
select = { select={
'_prefetch_related_val' : '%s.%s' % (qn(join_table), qn(source_col)) '_prefetch_related_val': '%s.%s' % (qn(join_table), qn(source_col))
} }
) )
return (qs, return (qs,
attrgetter('_prefetch_related_val'), attrgetter('_prefetch_related_val'),
attrgetter(instance._meta.pk.name), lambda obj: obj._get_pk_val(),
False, False,
self.prefetch_cache_name) self.prefetch_cache_name)
...@@ -129,12 +133,17 @@ class _TaggableManager(models.Manager): ...@@ -129,12 +133,17 @@ class _TaggableManager(models.Manager):
@require_instance_manager @require_instance_manager
def add(self, *tags): def add(self, *tags):
str_tags = set([ str_tags = set()
t tag_objs = set()
for t in tags for t in tags:
if not isinstance(t, self.through.tag_model()) if isinstance(t, self.through.tag_model()):
]) tag_objs.add(t)
tag_objs = set(tags) - str_tags elif isinstance(t, six.string_types):
str_tags.add(t)
else:
raise ValueError("Cannot add {0} ({1}). Expected {2} or str.".format(
t, type(t), type(self.through.tag_model())))
# If str_tags has 0 elements Django actually optimizes that to not do a # If str_tags has 0 elements Django actually optimizes that to not do a
# query. Malcolm is very smart. # query. Malcolm is very smart.
existing = self.through.tag_model().objects.filter( existing = self.through.tag_model().objects.filter(
...@@ -220,10 +229,12 @@ class _TaggableManager(models.Manager): ...@@ -220,10 +229,12 @@ class _TaggableManager(models.Manager):
class TaggableManager(RelatedField, Field): class TaggableManager(RelatedField, Field):
_related_name_counter = 0 _related_name_counter = 0
def __init__(self, verbose_name=_("Tags"), help_text=_("A comma-separated list of tags."), def __init__(self, verbose_name=_("Tags"),
through=None, blank=False, related_name=None, to=None, help_text=_("A comma-separated list of tags."),
manager=_TaggableManager): through=None, blank=False, related_name=None, to=None,
Field.__init__(self, verbose_name=verbose_name, help_text=help_text, blank=blank, null=True, serialize=False) manager=_TaggableManager):
Field.__init__(self, verbose_name=verbose_name, help_text=help_text,
blank=blank, null=True, serialize=False)
self.through = through or TaggedItem self.through = through or TaggedItem
self.rel = TaggableRel(self, related_name, self.through, to=to) self.rel = TaggableRel(self, related_name, self.through, to=to)
self.swappable = False self.swappable = False
...@@ -233,12 +244,12 @@ class TaggableManager(RelatedField, Field): ...@@ -233,12 +244,12 @@ class TaggableManager(RelatedField, Field):
def __get__(self, instance, model): def __get__(self, instance, model):
if instance is not None and instance.pk is None: if instance is not None and instance.pk is None:
raise ValueError("%s objects need to have a primary key value " raise ValueError("%s objects need to have a primary key value "
"before you can access their tags." % model.__name__) "before you can access their tags." % model.__name__)
manager = self.manager( manager = self.manager(
through=self.through, through=self.through,
model=model, model=model,
instance=instance, instance=instance,
prefetch_cache_name = self.name prefetch_cache_name=self.name
) )
return manager return manager
...@@ -287,7 +298,6 @@ class TaggableManager(RelatedField, Field): ...@@ -287,7 +298,6 @@ class TaggableManager(RelatedField, Field):
else: else:
self.post_through_setup(cls) self.post_through_setup(cls)
def __lt__(self, other): def __lt__(self, other):
""" """
Required contribute_to_class as Django uses bisect Required contribute_to_class as Django uses bisect
...@@ -364,7 +374,7 @@ class TaggableManager(RelatedField, Field): ...@@ -364,7 +374,7 @@ class TaggableManager(RelatedField, Field):
def extra_filters(self, pieces, pos, negate): def extra_filters(self, pieces, pos, negate):
if negate or not self.use_gfk: if negate or not self.use_gfk:
return [] return []
prefix = "__".join(["tagged_items"] + pieces[:pos-2]) prefix = "__".join(["tagged_items"] + pieces[:pos - 2])
get = ContentType.objects.get_for_model get = ContentType.objects.get_for_model
cts = [get(obj) for obj in _get_subclasses(self.model)] cts = [get(obj) for obj in _get_subclasses(self.model)]
if len(cts) == 1: if len(cts) == 1:
...@@ -378,13 +388,18 @@ class TaggableManager(RelatedField, Field): ...@@ -378,13 +388,18 @@ class TaggableManager(RelatedField, Field):
else: else:
alias_to_join = lhs_alias alias_to_join = lhs_alias
extra_col = self.through._meta.get_field_by_name('content_type')[0].column extra_col = self.through._meta.get_field_by_name('content_type')[0].column
content_type_ids = [ContentType.objects.get_for_model(subclass).pk for subclass in _get_subclasses(self.model)] content_type_ids = [ContentType.objects.get_for_model(subclass).pk for
subclass in _get_subclasses(self.model)]
if len(content_type_ids) == 1: if len(content_type_ids) == 1:
content_type_id = content_type_ids[0] content_type_id = content_type_ids[0]
extra_where = " AND %s.%s = %%s" % (qn(alias_to_join), qn(extra_col)) extra_where = " AND %s.%s = %%s" % (qn(alias_to_join),
qn(extra_col))
params = [content_type_id] params = [content_type_id]
else: else:
extra_where = " AND %s.%s IN (%s)" % (qn(alias_to_join), qn(extra_col), ','.join(['%s']*len(content_type_ids))) extra_where = " AND %s.%s IN (%s)" % (qn(alias_to_join),
qn(extra_col),
','.join(['%s'] *
len(content_type_ids)))
params = content_type_ids params = content_type_ids
return extra_where, params return extra_where, params
...@@ -461,7 +476,7 @@ def _get_subclasses(model): ...@@ -461,7 +476,7 @@ def _get_subclasses(model):
for f in model._meta.get_all_field_names(): for f in model._meta.get_all_field_names():
field = model._meta.get_field_by_name(f)[0] field = model._meta.get_field_by_name(f)[0]
if (isinstance(field, RelatedObject) and if (isinstance(field, RelatedObject) and
getattr(field.field.rel, "parent_link", None)): getattr(field.field.rel, "parent_link", None)):
subclasses.extend(_get_subclasses(field.model)) subclasses.extend(_get_subclasses(field.model))
return subclasses return subclasses
......
# encoding: utf8 # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
...@@ -27,9 +28,9 @@ class Migration(migrations.Migration): ...@@ -27,9 +28,9 @@ class Migration(migrations.Migration):
name='TaggedItem', name='TaggedItem',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('tag', models.ForeignKey(to='taggit.Tag', to_field='id')),
('object_id', models.IntegerField(verbose_name='Object id', db_index=True)), ('object_id', models.IntegerField(verbose_name='Object id', db_index=True)),
('content_type', models.ForeignKey(to='contenttypes.ContentType', to_field='id', verbose_name='Content type')), ('content_type', models.ForeignKey(related_name='taggit_taggeditem_tagged_items', verbose_name='Content type', to='contenttypes.ContentType')),
('tag', models.ForeignKey(related_name='taggit_taggeditem_items', to='taggit.Tag')),
], ],
options={ options={
'verbose_name': 'Tagged Item', 'verbose_name': 'Tagged Item',
......
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import IntegrityError, models, transaction
from django.db.models.query import QuerySet
from django.template.defaultfilters import slugify as default_slugify
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
try: try:
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
except ImportError: # django < 1.7 except ImportError: # django < 1.7
from django.contrib.contenttypes.generic import GenericForeignKey from django.contrib.contenttypes.generic import GenericForeignKey
from django.db import models, IntegrityError, transaction
from django.db.models.query import QuerySet
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
try: try:
...@@ -59,7 +61,7 @@ class TagBase(models.Model): ...@@ -59,7 +61,7 @@ class TagBase(models.Model):
except IntegrityError: except IntegrityError:
pass pass
# Now try to find existing slugs with similar names # Now try to find existing slugs with similar names
slugs = set(Tag.objects.filter(slug__startswith=self.slug)\ slugs = set(Tag.objects.filter(slug__startswith=self.slug)
.values_list('slug', flat=True)) .values_list('slug', flat=True))
i = 1 i = 1
while True: while True:
...@@ -145,7 +147,7 @@ class GenericTaggedItemBase(ItemBase): ...@@ -145,7 +147,7 @@ class GenericTaggedItemBase(ItemBase):
content_object = GenericForeignKey() content_object = GenericForeignKey()
class Meta: class Meta:
abstract=True abstract = True
@classmethod @classmethod
def lookup_kwargs(cls, instance): def lookup_kwargs(cls, instance):
......
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils import six
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.functional import wraps from django.utils.functional import wraps
from django.utils import six
def parse_tags(tagstring): def parse_tags(tagstring):
......
...@@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType ...@@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.generic.list import ListView from django.views.generic.list import ListView
from taggit.models import TaggedItem, Tag from taggit.models import Tag, TaggedItem
def tagged_object_list(request, slug, queryset, **kwargs): def tagged_object_list(request, slug, queryset, **kwargs):
...@@ -18,4 +18,3 @@ def tagged_object_list(request, slug, queryset, **kwargs): ...@@ -18,4 +18,3 @@ def tagged_object_list(request, slug, queryset, **kwargs):
kwargs["extra_context"] = {} kwargs["extra_context"] = {}
kwargs["extra_context"]["tag"] = tag kwargs["extra_context"]["tag"] = tag
return ListView.as_view(request, qs, **kwargs) return ListView.as_view(request, qs, **kwargs)
from __future__ import unicode_literals, absolute_import from __future__ import absolute_import, unicode_literals
from django import forms, VERSION from django import forms, VERSION
from .models import Food, DirectFood, CustomPKFood, OfficialFood from .models import CustomPKFood, DirectFood, Food, OfficialFood
fields = None fields = None
if VERSION >= (1,6): if VERSION >= (1, 6):
fields = '__all__' fields = '__all__'
......
...@@ -54,7 +54,7 @@ class Migration(migrations.Migration): ...@@ -54,7 +54,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='CustomPKHousePet', name='CustomPKHousePet',
fields=[ fields=[
('custompkpet_ptr', models.OneToOneField(auto_created=True, primary_key=True, serialize=False, to='tests.CustomPKPet')), ('custompkpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.CustomPKPet')),
('trained', models.BooleanField(default=False)), ('trained', models.BooleanField(default=False)),
], ],
options={ options={
...@@ -84,7 +84,7 @@ class Migration(migrations.Migration): ...@@ -84,7 +84,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='DirectHousePet', name='DirectHousePet',
fields=[ fields=[
('directpet_ptr', models.OneToOneField(auto_created=True, primary_key=True, serialize=False, to='tests.DirectPet')), ('directpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.DirectPet')),
('trained', models.BooleanField(default=False)), ('trained', models.BooleanField(default=False)),
], ],
options={ options={
...@@ -155,7 +155,7 @@ class Migration(migrations.Migration): ...@@ -155,7 +155,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='OfficialHousePet', name='OfficialHousePet',
fields=[ fields=[
('officialpet_ptr', models.OneToOneField(auto_created=True, primary_key=True, serialize=False, to='tests.OfficialPet')), ('officialpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.OfficialPet')),
('trained', models.BooleanField(default=False)), ('trained', models.BooleanField(default=False)),
], ],
options={ options={
...@@ -180,8 +180,8 @@ class Migration(migrations.Migration): ...@@ -180,8 +180,8 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('object_id', models.IntegerField(verbose_name='Object id', db_index=True)), ('object_id', models.IntegerField(verbose_name='Object id', db_index=True)),
('content_type', models.ForeignKey(verbose_name='Content type', to='contenttypes.ContentType')), ('content_type', models.ForeignKey(related_name='tests_officialthroughmodel_tagged_items', verbose_name='Content type', to='contenttypes.ContentType')),
('tag', models.ForeignKey(to='tests.OfficialTag')), ('tag', models.ForeignKey(related_name='tagged_items', to='tests.OfficialTag')),
], ],
options={ options={
'abstract': False, 'abstract': False,
...@@ -201,6 +201,30 @@ class Migration(migrations.Migration): ...@@ -201,6 +201,30 @@ class Migration(migrations.Migration):
preserve_default=True, preserve_default=True,
), ),
migrations.CreateModel( migrations.CreateModel(
name='Parent',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Child',
fields=[
('parent_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.Parent')),
],
options={
},
bases=('tests.parent',),
),
migrations.AddField(
model_name='parent',
name='tags',
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags'),
preserve_default=True,
),
migrations.CreateModel(
name='Pet', name='Pet',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
...@@ -213,7 +237,7 @@ class Migration(migrations.Migration): ...@@ -213,7 +237,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='HousePet', name='HousePet',
fields=[ fields=[
('pet_ptr', models.OneToOneField(auto_created=True, primary_key=True, serialize=False, to='tests.Pet')), ('pet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.Pet')),
('trained', models.BooleanField(default=False)), ('trained', models.BooleanField(default=False)),
], ],
options={ options={
...@@ -262,7 +286,7 @@ class Migration(migrations.Migration): ...@@ -262,7 +286,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='taggedcustompkfood', model_name='taggedcustompkfood',
name='tag', name='tag',
field=models.ForeignKey(to='taggit.Tag'), field=models.ForeignKey(related_name='tests_taggedcustompkfood_items', to='taggit.Tag'),
preserve_default=True, preserve_default=True,
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -290,7 +314,7 @@ class Migration(migrations.Migration): ...@@ -290,7 +314,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='taggedcustompkpet', model_name='taggedcustompkpet',
name='tag', name='tag',
field=models.ForeignKey(to='taggit.Tag'), field=models.ForeignKey(related_name='tests_taggedcustompkpet_items', to='taggit.Tag'),
preserve_default=True, preserve_default=True,
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -318,7 +342,7 @@ class Migration(migrations.Migration): ...@@ -318,7 +342,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='taggedfood', model_name='taggedfood',
name='tag', name='tag',
field=models.ForeignKey(to='taggit.Tag'), field=models.ForeignKey(related_name='tests_taggedfood_items', to='taggit.Tag'),
preserve_default=True, preserve_default=True,
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -346,7 +370,7 @@ class Migration(migrations.Migration): ...@@ -346,7 +370,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='taggedpet', model_name='taggedpet',
name='tag', name='tag',
field=models.ForeignKey(to='taggit.Tag'), field=models.ForeignKey(related_name='tests_taggedpet_items', to='taggit.Tag'),
preserve_default=True, preserve_default=True,
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -374,7 +398,7 @@ class Migration(migrations.Migration): ...@@ -374,7 +398,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='through1', model_name='through1',
name='tag', name='tag',
field=models.ForeignKey(to='taggit.Tag'), field=models.ForeignKey(related_name='tests_through1_items', to='taggit.Tag'),
preserve_default=True, preserve_default=True,
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -402,7 +426,7 @@ class Migration(migrations.Migration): ...@@ -402,7 +426,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='through2', model_name='through2',
name='tag', name='tag',
field=models.ForeignKey(to='taggit.Tag'), field=models.ForeignKey(related_name='tests_through2_items', to='taggit.Tag'),
preserve_default=True, preserve_default=True,
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -410,8 +434,8 @@ class Migration(migrations.Migration): ...@@ -410,8 +434,8 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('object_id', models.IntegerField(verbose_name='Object id', db_index=True)), ('object_id', models.IntegerField(verbose_name='Object id', db_index=True)),
('content_type', models.ForeignKey(verbose_name='Content type', to='contenttypes.ContentType')), ('content_type', models.ForeignKey(related_name='tests_throughgfk_tagged_items', verbose_name='Content type', to='contenttypes.ContentType')),
('tag', models.ForeignKey(to='taggit.Tag')), ('tag', models.ForeignKey(related_name='tagged_items', to='taggit.Tag')),
], ],
options={ options={
'abstract': False, 'abstract': False,
......
...@@ -4,8 +4,8 @@ from django.db import models ...@@ -4,8 +4,8 @@ from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from taggit.models import (TaggedItemBase, GenericTaggedItemBase, TaggedItem, from taggit.models import (GenericTaggedItemBase, Tag, TagBase, TaggedItem,
TagBase, Tag) TaggedItemBase)
# Ensure that two TaggableManagers with custom through model are allowed. # Ensure that two TaggableManagers with custom through model are allowed.
...@@ -192,3 +192,11 @@ class CustomManager(models.Model): ...@@ -192,3 +192,11 @@ class CustomManager(models.Model):
pass pass
tags = TaggableManager(manager=Foo) tags = TaggableManager(manager=Foo)
class Parent(models.Model):
tags = TaggableManager()
class Child(Parent):
pass
from __future__ import unicode_literals, absolute_import from __future__ import absolute_import, unicode_literals
from unittest import TestCase as UnitTestCase from unittest import TestCase as UnitTestCase
try:
from unittest import skipIf, skipUnless
except:
from django.utils.unittest import skipIf, skipUnless
import django import django
from django.conf import settings from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core import serializers from django.core import serializers
from django.db import connection from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.test import TestCase, TransactionTestCase from django.test import TestCase, TransactionTestCase
from django.utils import six
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.contrib.contenttypes.models import ContentType from .forms import CustomPKFoodForm, DirectFoodForm, FoodForm, OfficialFoodForm
from .models import (Article, Child, CustomManager, CustomPKFood,
CustomPKHousePet, CustomPKPet, DirectFood,
DirectHousePet, DirectPet, Food, HousePet, Movie,
OfficialFood, OfficialHousePet, OfficialPet,
OfficialTag, OfficialThroughModel, Pet, Photo,
TaggedCustomPKFood, TaggedCustomPKPet, TaggedFood,
TaggedPet)
from taggit.managers import TaggableManager, _TaggableManager, _model_name from taggit.managers import _model_name, _TaggableManager, TaggableManager
from taggit.models import Tag, TaggedItem from taggit.models import Tag, TaggedItem
from .forms import (FoodForm, DirectFoodForm, CustomPKFoodForm,
OfficialFoodForm) from taggit.utils import edit_string_for_tags, parse_tags
from .models import (Food, Pet, HousePet, DirectFood, DirectPet,
DirectHousePet, TaggedFood, CustomPKFood, CustomPKPet, CustomPKHousePet, try:
TaggedCustomPKFood, OfficialFood, OfficialPet, OfficialHousePet, from unittest import skipIf, skipUnless
OfficialThroughModel, OfficialTag, Photo, Movie, Article, CustomManager) except ImportError:
from taggit.utils import parse_tags, edit_string_for_tags from django.utils.unittest import skipIf, skipUnless
class BaseTaggingTest(object): class BaseTaggingTest(object):
...@@ -87,6 +88,14 @@ class TagModelTestCase(BaseTaggingTransactionTestCase): ...@@ -87,6 +88,14 @@ class TagModelTestCase(BaseTaggingTransactionTestCase):
"category-awesome-1" "category-awesome-1"
], attr="slug") ], attr="slug")
def test_integers(self):
"""Adding an integer as a tag should raise a ValueError (#237)."""
apple = self.food_model.objects.create(name="apple")
with self.assertRaisesRegexp(ValueError, (
r"Cannot add 1 \(<(type|class) 'int'>\). "
r"Expected <class 'django.db.models.base.ModelBase'> or str.")):
apple.tags.add(1)
class TagModelDirectTestCase(TagModelTestCase): class TagModelDirectTestCase(TagModelTestCase):
food_model = DirectFood food_model = DirectFood
tag_model = Tag tag_model = Tag
...@@ -109,7 +118,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase): ...@@ -109,7 +118,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
def test_add_tag(self): def test_add_tag(self):
apple = self.food_model.objects.create(name="apple") apple = self.food_model.objects.create(name="apple")
self.assertEqual(list(apple.tags.all()), []) self.assertEqual(list(apple.tags.all()), [])
self.assertEqual(list(self.food_model.tags.all()), []) self.assertEqual(list(self.food_model.tags.all()), [])
apple.tags.add('green') apple.tags.add('green')
self.assert_tags_equal(apple.tags.all(), ['green']) self.assert_tags_equal(apple.tags.all(), ['green'])
...@@ -150,7 +159,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase): ...@@ -150,7 +159,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
# make sure we don't double create. # make sure we don't double create.
# + 12 on Django 1.6 for save points. # + 12 on Django 1.6 for save points.
queries = 22 queries = 22
if django.VERSION < (1,6): if django.VERSION < (1, 6):
queries -= 12 queries -= 12
self.assertNumQueries(queries, apple.tags.add, "red", "delicious", "green") self.assertNumQueries(queries, apple.tags.add, "red", "delicious", "green")
...@@ -160,7 +169,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase): ...@@ -160,7 +169,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
# make sure we dont't double create. # make sure we dont't double create.
# + 4 on Django 1.6 for save points. # + 4 on Django 1.6 for save points.
queries = 9 queries = 9
if django.VERSION < (1,6): if django.VERSION < (1, 6):
queries -= 4 queries -= 4
self.assertNumQueries(queries, pear.tags.add, "green", "delicious") self.assertNumQueries(queries, pear.tags.add, "green", "delicious")
...@@ -181,7 +190,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase): ...@@ -181,7 +190,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
def test_delete_bulk(self): def test_delete_bulk(self):
apple = self.food_model.objects.create(name="apple") apple = self.food_model.objects.create(name="apple")
kitty = self.pet_model.objects.create(pk=apple.pk, name="kitty") kitty = self.pet_model.objects.create(pk=apple.pk, name="kitty")
apple.tags.add("red", "delicious", "fruit") apple.tags.add("red", "delicious", "fruit")
kitty.tags.add("feline") kitty.tags.add("feline")
...@@ -222,9 +231,9 @@ class TaggableManagerTestCase(BaseTaggingTestCase): ...@@ -222,9 +231,9 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
pks = self.pet_model.objects.filter(tags__name__in=["fuzzy"]) pks = self.pet_model.objects.filter(tags__name__in=["fuzzy"])
model_name = self.pet_model.__name__ model_name = self.pet_model.__name__
self.assertQuerysetEqual(pks, self.assertQuerysetEqual(pks,
['<{0}: kitty>'.format(model_name), ['<{0}: kitty>'.format(model_name),
'<{0}: cat>'.format(model_name)], '<{0}: cat>'.format(model_name)],
ordered=False) ordered=False)
def test_lookup_bulk(self): def test_lookup_bulk(self):
apple = self.food_model.objects.create(name="apple") apple = self.food_model.objects.create(name="apple")
...@@ -254,14 +263,14 @@ class TaggableManagerTestCase(BaseTaggingTestCase): ...@@ -254,14 +263,14 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
pear = self.food_model.objects.create(name="pear") pear = self.food_model.objects.create(name="pear")
pear.tags.add("green", "delicious") pear.tags.add("green", "delicious")
guava = self.food_model.objects.create(name="guava") self.food_model.objects.create(name="guava")
pks = self.food_model.objects.exclude(tags__name__in=["red"]) pks = self.food_model.objects.exclude(tags__name__in=["red"])
model_name = self.food_model.__name__ model_name = self.food_model.__name__
self.assertQuerysetEqual(pks, self.assertQuerysetEqual(pks,
['<{0}: pear>'.format(model_name), ['<{0}: pear>'.format(model_name),
'<{0}: guava>'.format(model_name)], '<{0}: guava>'.format(model_name)],
ordered=False) ordered=False)
def test_similarity_by_tag(self): def test_similarity_by_tag(self):
"""Test that pears are more similar to apples than watermelons""" """Test that pears are more similar to apples than watermelons"""
...@@ -293,8 +302,8 @@ class TaggableManagerTestCase(BaseTaggingTestCase): ...@@ -293,8 +302,8 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
'%s__name' % _model_name(self.pet_model): 'Spot' '%s__name' % _model_name(self.pet_model): 'Spot'
} }
self.assert_tags_equal( self.assert_tags_equal(
self.tag_model.objects.filter(**lookup_kwargs), self.tag_model.objects.filter(**lookup_kwargs),
['scary'] ['scary']
) )
def test_taggeditem_unicode(self): def test_taggeditem_unicode(self):
...@@ -492,7 +501,7 @@ class TagStringParseTestCase(UnitTestCase): ...@@ -492,7 +501,7 @@ class TagStringParseTestCase(UnitTestCase):
self.assertEqual(parse_tags(',one two'), ['one two']) self.assertEqual(parse_tags(',one two'), ['one two'])
self.assertEqual(parse_tags(',one two three'), ['one two three']) self.assertEqual(parse_tags(',one two three'), ['one two three'])
self.assertEqual(parse_tags('a-one, a-two and a-three'), self.assertEqual(parse_tags('a-one, a-two and a-three'),
['a-one', 'a-two and a-three']) ['a-one', 'a-two and a-three'])
def test_with_double_quoted_multiple_words(self): def test_with_double_quoted_multiple_words(self):
""" """
...@@ -504,7 +513,7 @@ class TagStringParseTestCase(UnitTestCase): ...@@ -504,7 +513,7 @@ class TagStringParseTestCase(UnitTestCase):
self.assertEqual(parse_tags('"one two three'), ['one', 'three', 'two']) self.assertEqual(parse_tags('"one two three'), ['one', 'three', 'two'])
self.assertEqual(parse_tags('"one two"'), ['one two']) self.assertEqual(parse_tags('"one two"'), ['one two'])
self.assertEqual(parse_tags('a-one "a-two and a-three"'), self.assertEqual(parse_tags('a-one "a-two and a-three"'),
['a-one', 'a-two and a-three']) ['a-one', 'a-two and a-three'])
def test_with_no_loose_commas(self): def test_with_no_loose_commas(self):
""" """
...@@ -523,9 +532,9 @@ class TagStringParseTestCase(UnitTestCase): ...@@ -523,9 +532,9 @@ class TagStringParseTestCase(UnitTestCase):
Double quotes can contain commas Double quotes can contain commas
""" """
self.assertEqual(parse_tags('a-one "a-two, and a-three"'), self.assertEqual(parse_tags('a-one "a-two, and a-three"'),
['a-one', 'a-two, and a-three']) ['a-one', 'a-two, and a-three'])
self.assertEqual(parse_tags('"two", one, one, two, "one"'), self.assertEqual(parse_tags('"two", one, one, two, "one"'),
['one', 'two']) ['one', 'two'])
def test_with_naughty_input(self): def test_with_naughty_input(self):
""" """
...@@ -540,7 +549,7 @@ class TagStringParseTestCase(UnitTestCase): ...@@ -540,7 +549,7 @@ class TagStringParseTestCase(UnitTestCase):
self.assertEqual(parse_tags(',,,,,,'), []) self.assertEqual(parse_tags(',,,,,,'), [])
self.assertEqual(parse_tags('",",",",",",","'), [',']) self.assertEqual(parse_tags('",",",",",",","'), [','])
self.assertEqual(parse_tags('a-one "a-two" and "a-three'), self.assertEqual(parse_tags('a-one "a-two" and "a-three'),
['a-one', 'a-three', 'a-two', 'and']) ['a-one', 'a-three', 'a-two', 'and'])
def test_recreation_of_tag_list_string_representations(self): def test_recreation_of_tag_list_string_representations(self):
plain = Tag.objects.create(name='plain') plain = Tag.objects.create(name='plain')
...@@ -571,3 +580,20 @@ class SouthSupportTests(TestCase): ...@@ -571,3 +580,20 @@ class SouthSupportTests(TestCase):
except ImproperlyConfigured as e: except ImproperlyConfigured as e:
exception = e exception = e
self.assertIn("SOUTH_MIGRATION_MODULES", exception.args[0]) self.assertIn("SOUTH_MIGRATION_MODULES", exception.args[0])
class InheritedPrefetchTests(TestCase):
def test_inherited_tags_with_prefetch(self):
child = Child()
child.save()
child.tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4')
child = Child.objects.get()
no_prefetch_tags = child.tags.all()
self.assertEquals(4, no_prefetch_tags.count())
child = Child.objects.prefetch_related('tags').get()
prefetch_tags = child.tags.all()
self.assertEquals(4, prefetch_tags.count())
self.assertEquals(set([t.name for t in no_prefetch_tags]),
set([t.name for t in prefetch_tags]))
...@@ -13,7 +13,7 @@ deps17 = ...@@ -13,7 +13,7 @@ deps17 =
https://github.com/django/django/archive/stable/1.7.x.zip#egg=django https://github.com/django/django/archive/stable/1.7.x.zip#egg=django
commands = commands =
python ./runtests.py python ./runtests.py {posargs}
[testenv:py26-1.4.x] [testenv:py26-1.4.x]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment