Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
django-taggit
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Wiki
Members
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
31cbafbb
authored
Sep 07, 2010
by
Alex Gaynor
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'api-refactor'
parents
f3657783
17ebe7b3
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
238 additions
and
141 deletions
+238
-141
docs/api.txt
+6
-3
docs/changelog.txt
+4
-0
docs/custom_tagging.txt
+46
-0
docs/custom_through.txt
+0
-31
docs/index.txt
+1
-1
runtests.py
+1
-1
taggit/contrib/suggest/tests.py
+2
-2
taggit/contrib/suggest/utils.py
+0
-2
taggit/managers.py
+16
-48
taggit/models.py
+52
-25
taggit/tests/forms.py
+5
-1
taggit/tests/models.py
+45
-13
taggit/tests/tests.py
+60
-14
No files found.
docs/api.txt
View file @
31cbafbb
...
...
@@ -56,7 +56,7 @@ Django ORM API. For example if you had a ``Food`` model, whose
``TaggableManager`` was named ``tags``, you could find all the delicious fruit
like so::
>>> Food.objects.filter(tags__in=["delicious"])
>>> Food.objects.filter(tags__
name__
in=["delicious"])
[<Food: apple>, <Food: pear>, <Food: plum>]
...
...
@@ -64,7 +64,10 @@ If you're filtering on multiple tags, it's very common to get duplicate
results, because of the way relational databases work. Often you'll want to
make use of the ``distinct()`` method on ``QuerySets``::
>>> Food.objects.filter(tags__in=["delicious", "red"])
>>> Food.objects.filter(tags__
name__
in=["delicious", "red"])
[<Food: apple>, <Food: apple>]
>>> Food.objects.filter(tags__in=["delicious", "red"]).distinct()
>>> Food.objects.filter(tags__
name__
in=["delicious", "red"]).distinct()
[<Food: apple>]
You can also filter by the slug on tags. If you're using a custom ``Tag``
model you can use this API to filter on any fields it has.
docs/changelog.txt
View file @
31cbafbb
...
...
@@ -10,6 +10,10 @@ Unreleased.
* Added an index on the ``object_id`` field of ``TaggedItem``.
* When displaying tags always join them with commas, never spaces.
* The docs are now available `online <http://django-taggit.readthedocs.org/>`_.
* Custom ``Tag`` models are now allowed.
* *Backwards incompatible* Filtering on tags is no longer
``filter(tags__in=["foo"])``, it is written
``filter(tags__name__in=["foo"])``.
0.8.0
~~~~~
...
...
docs/custom_tagging.txt
0 → 100644
View file @
31cbafbb
Using a Custom Tag or Through Model
===================================
By default ``django-taggit`` uses a "through model" with a
``GenericForeignKey`` on it, that has another ``ForeignKey`` to an included
``Tag`` model. However, there are some cases where this isn't desirable, for
example if you want the speed and referential guarantees of a real
``ForeignKey``, if you have a model with a non-integer primary key, or if you
want to store additional data about a tag, such as whether it is official. In
these cases ``django-taggit`` makes it easy to substitute your own through
model, or ``Tag`` model.
Your intermediary model must be a subclass of
``taggit.models.TaggedItemBase`` with a foreign key to your content
model named ``content_object``. Pass this intermediary model as the
``through`` argument to ``TaggableManager``::
from django.db import models
from taggit.managers import TaggableManager
from taggit.models import TaggedItemBase
class TaggedFood(TaggedItemBase):
content_object = models.ForeignKey('Food')
class Food(models.Model):
# ... fields here
tags = TaggableManager(through=TaggedFood)
Once this is done, the API works the same as for GFK-tagged models.
To change the behavior in other ways there are a number of other classes you
can subclass to obtain different behavior:
========================= ===========================================================
Class name Behavior
========================= ===========================================================
``TaggedItemBase`` Allows custom ``ForeignKeys`` to models.
``GenericTaggedItemBase`` Allows custom ``Tag`` models.
``ItemBase`` Allows custom ``Tag`` models and ``ForeignKeys`` to models.
========================= ===========================================================
When providing a custom ``Tag`` model it should be a ``ForeignKey`` to your tag model named ``"tag"``.
docs/custom_through.txt
deleted
100644 → 0
View file @
f3657783
Using a Custom Through Model
============================
By default ``django-taggit`` uses a "through model" with a
``GenericForeignKey`` on it. However, there are some cases where this
isn't desirable, for example if you want the speed and referential
guarantees of a real ``ForeignKey``, or if you have a model with a
non-integer primary key. In these cases ``django-taggit`` makes it
easy to substitute your own through model.
Youe intermediary model must be a subclass of
``taggit.models.TaggedItemBase`` with a foreign key to your content
model named ``content_object``. Pass this intermediary model as the
``through`` argument to ``TaggableManager``::
from django.db import models
from taggit.managers import TaggableManager
from taggit.models import TaggedItemBase
class TaggedFood(TaggedItemBase):
content_object = models.ForeignKey('Food')
class Food(models.Model):
# ... fields here
tags = TaggableManager(through=TaggedFood)
Once this is done, the API works the same as for GFK-tagged models.
docs/index.txt
View file @
31cbafbb
...
...
@@ -13,7 +13,7 @@ for known issues with older versions of Django), and Python 2.4-2.X.
getting_started
forms
api
custom_t
hrough
custom_t
agging
issues
changelog
...
...
runtests.py
View file @
31cbafbb
...
...
@@ -24,7 +24,7 @@ def runtests(*test_args):
test_args
=
[
'tests'
,
'suggest'
]
parent
=
dirname
(
abspath
(
__file__
))
sys
.
path
.
insert
(
0
,
parent
)
failures
=
run_tests
(
test_args
,
verbosity
=
1
,
interactive
=
True
)
failures
=
run_tests
(
test_args
,
verbosity
=
1
,
interactive
=
True
,
failfast
=
True
)
sys
.
exit
(
failures
)
...
...
taggit/contrib/suggest/tests.py
View file @
31cbafbb
...
...
@@ -9,7 +9,7 @@ from taggit.models import Tag
class
SuggestCase
(
TestCase
):
def
test_simple_suggest
(
self
):
ku_tag
=
Tag
.
objects
.
create
(
name
=
'ku'
)
ku_keyword1
=
TagKeyword
.
objects
.
create
(
TagKeyword
.
objects
.
create
(
tag
=
ku_tag
,
keyword
=
'kansas university'
)
...
...
@@ -31,7 +31,7 @@ class SuggestCase(TestCase):
def
test_bad_regex
(
self
):
ku_tag
=
Tag
.
objects
.
create
(
name
=
'ku'
)
ku_keyword1
=
TagKeyword
.
objects
.
create
(
TagKeyword
.
objects
.
create
(
tag
=
ku_tag
,
keyword
=
'kansas university'
)
...
...
taggit/contrib/suggest/utils.py
View file @
31cbafbb
import
re
from
django.conf
import
settings
from
taggit.contrib.suggest.models
import
TagKeyword
,
TagRegex
from
taggit.models
import
Tag
...
...
taggit/managers.py
View file @
31cbafbb
import
django
from
django.contrib.contenttypes.generic
import
GenericForeignKey
from
django.contrib.contenttypes.generic
import
GenericRelation
from
django.contrib.contenttypes.models
import
ContentType
from
django.db
import
models
from
django.db.models.fields.related
import
ManyToManyRel
from
django.db.models.related
import
RelatedObject
from
django.db.models.query_utils
import
QueryWrapper
from
django.utils.translation
import
ugettext_lazy
as
_
from
taggit.forms
import
TagField
from
taggit.models
import
Tag
,
TaggedItem
from
taggit.models
import
Tag
,
TaggedItem
,
GenericTaggedItemBase
from
taggit.utils
import
require_instance_manager
...
...
@@ -39,9 +37,9 @@ class TaggableRel(ManyToManyRel):
class
TaggableManager
(
object
):
def
__init__
(
self
,
verbose_name
=
_
(
"Tags"
),
through
=
None
):
self
.
use_gfk
=
through
is
None
self
.
use_gfk
=
through
is
None
or
issubclass
(
through
,
GenericTaggedItemBase
)
self
.
through
=
through
or
TaggedItem
self
.
rel
=
TaggableRel
(
to
=
self
.
through
)
self
.
rel
=
TaggableRel
(
to
=
self
.
through
.
_meta
.
get_field
(
"tag"
)
.
rel
.
to
)
self
.
verbose_name
=
verbose_name
self
.
editable
=
True
self
.
unique
=
False
...
...
@@ -67,46 +65,18 @@ class TaggableManager(object):
self
.
model
=
cls
cls
.
_meta
.
add_field
(
self
)
setattr
(
cls
,
name
,
self
)
if
self
.
use_gfk
:
tagged_items
=
GenericRelation
(
self
.
through
)
tagged_items
.
contribute_to_class
(
cls
,
"tagged_items"
)
def
save_form_data
(
self
,
instance
,
value
):
getattr
(
instance
,
self
.
name
)
.
set
(
*
value
)
def
get_prep_lookup
(
self
,
lookup_type
,
value
):
if
lookup_type
not
in
[
"in"
,
"isnull"
]:
# Users really shouldn't do "isnull" lookups, again: the ORM can,
# you can't.
raise
ValueError
(
"You can't do lookups other than
\"
in
\"
on Tags: "
"__
%
s=
%
s"
%
(
lookup_type
,
value
))
if
hasattr
(
value
,
'prepare'
):
return
value
.
prepare
()
if
hasattr
(
value
,
'_prepare'
):
return
value
.
_prepare
()
if
lookup_type
==
"in"
:
if
all
(
isinstance
(
v
,
Tag
)
for
v
in
value
):
value
=
self
.
through
.
objects
.
filter
(
tag__in
=
value
)
elif
all
(
isinstance
(
v
,
basestring
)
for
v
in
value
):
value
=
self
.
through
.
objects
.
filter
(
tag__name__in
=
value
)
elif
all
(
isinstance
(
v
,
(
int
,
long
))
for
v
in
value
):
# This one is really ackward, just don't do it. The ORM does
# it for deletes, but no one else gets to.
return
value
else
:
# Fucking flip-floppers.
raise
ValueError
(
"You can't combine Tag objects and strings. '
%
s' "
"was provided."
%
value
)
if
hasattr
(
models
.
Field
,
"get_prep_lookup"
):
return
models
.
Field
()
.
get_prep_lookup
(
lookup_type
,
value
)
return
models
.
Field
()
.
get_db_prep_lookup
(
lookup_type
,
value
)
return
models
.
Field
()
.
get_prep_lookup
(
lookup_type
,
value
)
if
django
.
VERSION
<
(
1
,
2
):
get_db_prep_lookup
=
get_prep_lookup
else
:
def
get_db_prep_lookup
(
self
,
lookup_type
,
value
,
connection
,
prepared
=
False
):
if
not
prepared
:
return
self
.
get_prep_lookup
(
lookup_type
,
value
)
return
models
.
Field
()
.
get_db_prep_lookup
(
lookup_type
,
value
,
connection
=
connection
,
prepared
=
True
)
def
get_db_prep_lookup
(
self
,
*
args
,
**
kwargs
):
return
models
.
Field
()
.
get_db_prep_lookup
(
*
args
,
**
kwargs
)
def
formfield
(
self
,
form_class
=
TagField
,
**
kwargs
):
defaults
=
{
...
...
@@ -122,12 +92,10 @@ class TaggableManager(object):
return
self
.
through
.
objects
.
none
()
def
related_query_name
(
self
):
return
self
.
model
.
_meta
.
object_name
.
lower
()
return
self
.
model
.
_meta
.
module_name
def
m2m_reverse_name
(
self
):
if
self
.
use_gfk
:
return
"id"
return
self
.
through
.
_meta
.
pk
.
column
return
self
.
through
.
_meta
.
get_field_by_name
(
"tag"
)[
0
]
.
column
def
m2m_column_name
(
self
):
if
self
.
use_gfk
:
...
...
@@ -143,7 +111,7 @@ class TaggableManager(object):
def
extra_filters
(
self
,
pieces
,
pos
,
negate
):
if
negate
or
not
self
.
use_gfk
:
return
[]
prefix
=
"__"
.
join
(
pieces
[:
pos
+
1
])
prefix
=
"__"
.
join
(
[
"tagged_items"
]
+
pieces
[:
pos
-
2
])
cts
=
map
(
ContentType
.
objects
.
get_for_model
,
_get_subclasses
(
self
.
model
))
if
len
(
cts
)
==
1
:
return
[(
"
%
s__content_type"
%
prefix
,
cts
[
0
])]
...
...
@@ -163,8 +131,8 @@ class _TaggableManager(models.Manager):
@require_instance_manager
def
add
(
self
,
*
tags
):
for
tag
in
tags
:
if
not
isinstance
(
tag
,
Tag
):
tag
,
_
=
Tag
.
objects
.
get_or_create
(
name
=
tag
)
if
not
isinstance
(
tag
,
self
.
through
.
tag_model
()
):
tag
,
_
=
self
.
through
.
tag_model
()
.
objects
.
get_or_create
(
name
=
tag
)
self
.
through
.
objects
.
get_or_create
(
tag
=
tag
,
**
self
.
_lookup_kwargs
())
@require_instance_manager
...
...
taggit/models.py
View file @
31cbafbb
...
...
@@ -6,7 +6,7 @@ from django.template.defaultfilters import slugify
from
django.utils.translation
import
ugettext_lazy
as
_
,
ugettext
class
Tag
(
models
.
Model
):
class
Tag
Base
(
models
.
Model
):
name
=
models
.
CharField
(
verbose_name
=
_
(
'Name'
),
max_length
=
100
)
slug
=
models
.
SlugField
(
verbose_name
=
_
(
'Slug'
),
unique
=
True
,
max_length
=
100
)
...
...
@@ -14,8 +14,7 @@ class Tag(models.Model):
return
self
.
name
class
Meta
:
verbose_name
=
_
(
"Tag"
)
verbose_name_plural
=
_
(
"Tags"
)
abstract
=
True
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
pk
and
not
self
.
slug
:
...
...
@@ -35,7 +34,7 @@ class Tag(models.Model):
while
True
:
try
:
sid
=
transaction
.
savepoint
(
**
trans_kwargs
)
res
=
super
(
Tag
,
self
)
.
save
(
*
args
,
**
kwargs
)
res
=
super
(
Tag
Base
,
self
)
.
save
(
*
args
,
**
kwargs
)
transaction
.
savepoint_commit
(
sid
,
**
trans_kwargs
)
return
res
except
IntegrityError
:
...
...
@@ -43,15 +42,16 @@ class Tag(models.Model):
i
+=
1
self
.
slug
=
"
%
s_
%
d"
%
(
slug
,
i
)
else
:
return
super
(
Tag
,
self
)
.
save
(
*
args
,
**
kwargs
)
return
super
(
TagBase
,
self
)
.
save
(
*
args
,
**
kwargs
)
class
Tag
(
TagBase
):
class
Meta
:
verbose_name
=
_
(
"Tag"
)
verbose_name_plural
=
_
(
"Tags"
)
class
TaggedItemBase
(
models
.
Model
):
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"
)
class
ItemBase
(
models
.
Model
):
def
__unicode__
(
self
):
return
ugettext
(
"
%(object)
s tagged with
%(tag)
s"
)
%
{
"object"
:
self
.
content_object
,
...
...
@@ -62,6 +62,10 @@ class TaggedItemBase(models.Model):
abstract
=
True
@classmethod
def
tag_model
(
cls
):
return
cls
.
_meta
.
get_field_by_name
(
"tag"
)[
0
]
.
rel
.
to
@classmethod
def
tag_relname
(
cls
):
return
cls
.
_meta
.
get_field_by_name
(
'tag'
)[
0
]
.
rel
.
related_name
...
...
@@ -71,27 +75,46 @@ class TaggedItemBase(models.Model):
'content_object'
:
instance
}
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"
)
class
Meta
:
abstract
=
True
@classmethod
def
tags_for
(
cls
,
model
,
instance
=
None
):
if
instance
is
not
None
:
return
Tag
.
objects
.
filter
(
**
{
return
cls
.
tag_model
()
.
objects
.
filter
(
**
{
'
%
s__content_object'
%
cls
.
tag_relname
():
instance
})
return
Tag
.
objects
.
filter
(
**
{
return
cls
.
tag_model
()
.
objects
.
filter
(
**
{
'
%
s__content_object__isnull'
%
cls
.
tag_relname
():
False
})
.
distinct
()
class
TaggedItem
(
Tagged
ItemBase
):
class
GenericTaggedItemBase
(
ItemBase
):
object_id
=
models
.
IntegerField
(
verbose_name
=
_
(
'Object id'
),
db_index
=
True
)
content_type
=
models
.
ForeignKey
(
ContentType
,
verbose_name
=
_
(
'Content type'
),
related_name
=
"tagged_items"
)
if
django
.
VERSION
<
(
1
,
2
):
content_type
=
models
.
ForeignKey
(
ContentType
,
verbose_name
=
_
(
'Content type'
),
related_name
=
"
%(class)
s_tagged_items"
)
else
:
content_type
=
models
.
ForeignKey
(
ContentType
,
verbose_name
=
_
(
'Content type'
),
related_name
=
"
%(app_label)
s_
%(class)
s_tagged_items"
)
content_object
=
GenericForeignKey
()
class
Meta
:
verbose_name
=
_
(
"Tagged Item"
)
verbose_name_plural
=
_
(
"Tagged Items"
)
abstract
=
True
@classmethod
def
lookup_kwargs
(
cls
,
instance
):
return
{
...
...
@@ -102,12 +125,16 @@ class TaggedItem(TaggedItemBase):
@classmethod
def
tags_for
(
cls
,
model
,
instance
=
None
):
ct
=
ContentType
.
objects
.
get_for_model
(
model
)
kwargs
=
{
"
%
s__content_type"
%
cls
.
tag_relname
():
ct
}
if
instance
is
not
None
:
return
Tag
.
objects
.
filter
(
**
{
'
%
s__object_id'
%
cls
.
tag_relname
():
instance
.
pk
,
'
%
s__content_type'
%
cls
.
tag_relname
():
ct
})
return
Tag
.
objects
.
filter
(
**
{
'
%
s__content_type'
%
cls
.
tag_relname
():
ct
})
.
distinct
()
kwargs
[
"
%
s__object_id"
%
cls
.
tag_relname
()]
=
instance
.
pk
return
cls
.
tag_model
()
.
objects
.
filter
(
**
kwargs
)
.
distinct
()
class
TaggedItem
(
GenericTaggedItemBase
,
TaggedItemBase
):
class
Meta
:
verbose_name
=
_
(
"Tagged Item"
)
verbose_name_plural
=
_
(
"Tagged Items"
)
taggit/tests/forms.py
View file @
31cbafbb
from
django
import
forms
from
taggit.tests.models
import
Food
,
DirectFood
,
CustomPKFood
from
taggit.tests.models
import
Food
,
DirectFood
,
CustomPKFood
,
OfficialFood
class
FoodForm
(
forms
.
ModelForm
):
...
...
@@ -14,3 +14,7 @@ class DirectFoodForm(forms.ModelForm):
class
CustomPKFoodForm
(
forms
.
ModelForm
):
class
Meta
:
model
=
CustomPKFood
class
OfficialFoodForm
(
forms
.
ModelForm
):
class
Meta
:
model
=
OfficialFood
taggit/tests/models.py
View file @
31cbafbb
from
django.db
import
models
from
taggit.managers
import
TaggableManager
from
taggit.models
import
TaggedItemBase
from
taggit.models
import
TaggedItemBase
,
GenericTaggedItemBase
,
TagBase
class
Food
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
...
...
@@ -19,23 +20,23 @@ class Pet(models.Model):
def
__unicode__
(
self
):
return
self
.
name
class
HousePet
(
Pet
):
trained
=
models
.
BooleanField
()
# test direct-tagging with custom through model
# Test direct-tagging with custom through model
class
TaggedFood
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'DirectFood'
)
class
TaggedPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'DirectPet'
)
class
DirectFood
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
(
through
=
TaggedFood
)
class
TaggedPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'DirectPet'
)
class
DirectPet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
...
...
@@ -43,24 +44,27 @@ class DirectPet(models.Model):
def
__unicode__
(
self
):
return
self
.
name
class
DirectHousePet
(
DirectPet
):
trained
=
models
.
BooleanField
()
# test custom through model to model with custom PK
# Test custom through model to model with custom PK
class
TaggedCustomPKFood
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'CustomPKFood'
)
class
TaggedCustomPKPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'CustomPKPet'
)
class
CustomPKFood
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
,
primary_key
=
True
)
tags
=
TaggableManager
(
through
=
TaggedCustomPKFood
)
class
TaggedCustomPKPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'CustomPKPet'
)
def
__unicode__
(
self
):
return
self
.
name
class
CustomPKPet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
,
primary_key
=
True
)
...
...
@@ -71,3 +75,31 @@ class CustomPKPet(models.Model):
class
CustomPKHousePet
(
CustomPKPet
):
trained
=
models
.
BooleanField
()
# Test custom through model to a custom tag model
class
OfficialTag
(
TagBase
):
official
=
models
.
BooleanField
()
class
OfficialThroughModel
(
GenericTaggedItemBase
):
tag
=
models
.
ForeignKey
(
OfficialTag
,
related_name
=
"tagged_items"
)
class
OfficialFood
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
(
through
=
OfficialThroughModel
)
def
__unicode__
(
self
):
return
self
.
name
class
OfficialPet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
(
through
=
OfficialThroughModel
)
def
__unicode__
(
self
):
return
self
.
name
class
OfficialHousePet
(
OfficialPet
):
trained
=
models
.
BooleanField
()
taggit/tests/tests.py
View file @
31cbafbb
...
...
@@ -3,10 +3,12 @@ from unittest import TestCase as UnitTestCase
from
django.test
import
TestCase
,
TransactionTestCase
from
taggit.models
import
Tag
,
TaggedItem
from
taggit.tests.forms
import
FoodForm
,
DirectFoodForm
,
CustomPKFoodForm
from
taggit.tests.forms
import
(
FoodForm
,
DirectFoodForm
,
CustomPKFoodForm
,
OfficialFoodForm
)
from
taggit.tests.models
import
(
Food
,
Pet
,
HousePet
,
DirectFood
,
DirectPet
,
DirectHousePet
,
TaggedPet
,
CustomPKFood
,
CustomPKPet
,
CustomPKHousePet
,
TaggedCustomPKPet
)
TaggedCustomPKPet
,
OfficialFood
,
OfficialPet
,
OfficialHousePet
,
OfficialThroughModel
,
OfficialTag
)
from
taggit.utils
import
parse_tags
,
edit_string_for_tags
...
...
@@ -27,23 +29,39 @@ class BaseTaggingTransactionTestCase(TransactionTestCase, BaseTaggingTest):
class
TagModelTestCase
(
BaseTaggingTransactionTestCase
):
food_model
=
Food
tag_model
=
Tag
def
test_unique_slug
(
self
):
apple
=
self
.
food_model
.
objects
.
create
(
name
=
"apple"
)
apple
.
tags
.
add
(
"Red"
,
"red"
)
def
test_update
(
self
):
special
=
self
.
tag_model
.
objects
.
create
(
name
=
"special"
)
special
.
save
()
def
test_add
(
self
):
apple
=
self
.
food_model
.
objects
.
create
(
name
=
"apple"
)
yummy
=
self
.
tag_model
.
objects
.
create
(
name
=
"yummy"
)
apple
.
tags
.
add
(
yummy
)
class
TagModelDirectTestCase
(
TagModelTestCase
):
food_model
=
DirectFood
tag_model
=
Tag
class
TagModelCustomPKTestCase
(
TagModelTestCase
):
food_model
=
CustomPKFood
tag_model
=
Tag
class
TagModelOfficialTestCase
(
TagModelTestCase
):
food_model
=
OfficialFood
tag_model
=
OfficialTag
class
TaggableManagerTestCase
(
BaseTaggingTestCase
):
food_model
=
Food
pet_model
=
Pet
housepet_model
=
HousePet
taggeditem_model
=
TaggedItem
tag_model
=
Tag
def
test_add_tag
(
self
):
apple
=
self
.
food_model
.
objects
.
create
(
name
=
"apple"
)
...
...
@@ -72,7 +90,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
apple
.
tags
.
remove
(
'green'
)
self
.
assert_tags_equal
(
apple
.
tags
.
all
(),
[
'red'
])
self
.
assert_tags_equal
(
self
.
food_model
.
tags
.
all
(),
[
'green'
,
'red'
])
tag
=
Tag
.
objects
.
create
(
name
=
"delicious"
)
tag
=
self
.
tag_model
.
objects
.
create
(
name
=
"delicious"
)
apple
.
tags
.
add
(
tag
)
self
.
assert_tags_equal
(
apple
.
tags
.
all
(),
[
"red"
,
"delicious"
])
...
...
@@ -108,13 +126,13 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
apple
.
tags
.
add
(
"red"
,
"green"
)
pear
=
self
.
food_model
.
objects
.
create
(
name
=
"pear"
)
pear
.
tags
.
add
(
"green"
)
self
.
assertEqual
(
list
(
self
.
food_model
.
objects
.
filter
(
tags__in
=
[
"red"
])),
list
(
self
.
food_model
.
objects
.
filter
(
tags__
name__
in
=
[
"red"
])),
[
apple
]
)
self
.
assertEqual
(
list
(
self
.
food_model
.
objects
.
filter
(
tags__in
=
[
"green"
])),
list
(
self
.
food_model
.
objects
.
filter
(
tags__
name__
in
=
[
"green"
])),
[
apple
,
pear
]
)
...
...
@@ -123,18 +141,18 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
dog
=
self
.
pet_model
.
objects
.
create
(
name
=
"dog"
)
dog
.
tags
.
add
(
"woof"
,
"red"
)
self
.
assertEqual
(
list
(
self
.
food_model
.
objects
.
filter
(
tags__in
=
[
"red"
])
.
distinct
()),
list
(
self
.
food_model
.
objects
.
filter
(
tags__
name__
in
=
[
"red"
])
.
distinct
()),
[
apple
]
)
tag
=
Tag
.
objects
.
get
(
name
=
"woof"
)
self
.
assertEqual
(
list
(
self
.
pet_model
.
objects
.
filter
(
tags__in
=
[
tag
])),
[
dog
])
tag
=
self
.
tag_model
.
objects
.
get
(
name
=
"woof"
)
self
.
assertEqual
(
list
(
self
.
pet_model
.
objects
.
filter
(
tags__
name__
in
=
[
tag
])),
[
dog
])
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__in
=
[
"fuzzy"
])),
map
(
lambda
o
:
o
.
pk
,
self
.
pet_model
.
objects
.
filter
(
tags__
name__
in
=
[
"fuzzy"
])),
[
kitty
.
pk
,
cat
.
pk
]
)
...
...
@@ -148,7 +166,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
guava
=
self
.
food_model
.
objects
.
create
(
name
=
"guava"
)
self
.
assertEqual
(
map
(
lambda
o
:
o
.
pk
,
self
.
food_model
.
objects
.
exclude
(
tags__in
=
[
"red"
])),
map
(
lambda
o
:
o
.
pk
,
self
.
food_model
.
objects
.
exclude
(
tags__
name__
in
=
[
"red"
])),
[
pear
.
pk
,
guava
.
pk
],
)
...
...
@@ -177,9 +195,11 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
spike
=
self
.
pet_model
.
objects
.
create
(
name
=
'Spike'
)
spot
.
tags
.
add
(
'scary'
)
spike
.
tags
.
add
(
'fluffy'
)
lookup_kwargs
=
{
'
%
s__name'
%
(
self
.
pet_model
.
_meta
.
object_name
.
lower
()):
'Spot'
}
lookup_kwargs
=
{
'
%
s__name'
%
self
.
pet_model
.
_meta
.
module_name
:
'Spot'
}
self
.
assert_tags_equal
(
[
i
.
tag
for
i
in
self
.
taggeditem_model
.
objects
.
filter
(
**
lookup_kwargs
)]
,
self
.
tag_model
.
objects
.
filter
(
**
lookup_kwargs
)
,
[
'scary'
]
)
...
...
@@ -211,6 +231,28 @@ class TaggableManagerCustomPKTestCase(TaggableManagerTestCase):
# tell if the instance is saved or not
pass
class
TaggableManagerOfficialTestCase
(
TaggableManagerTestCase
):
food_model
=
OfficialFood
pet_model
=
OfficialPet
housepet_model
=
OfficialHousePet
taggeditem_model
=
OfficialThroughModel
tag_model
=
OfficialTag
def
test_extra_fields
(
self
):
self
.
tag_model
.
objects
.
create
(
name
=
"red"
)
self
.
tag_model
.
objects
.
create
(
name
=
"delicious"
,
official
=
True
)
apple
=
self
.
food_model
.
objects
.
create
(
name
=
"apple"
)
apple
.
tags
.
add
(
"delicious"
,
"red"
)
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
],
)
class
TaggableFormTestCase
(
BaseTaggingTestCase
):
form_class
=
FoodForm
food_model
=
Food
...
...
@@ -254,6 +296,10 @@ class TaggableFormCustomPKTestCase(TaggableFormTestCase):
form_class
=
CustomPKFoodForm
food_model
=
CustomPKFood
class
TaggableFormOfficialTestCase
(
TaggableFormTestCase
):
form_class
=
OfficialFoodForm
food_model
=
OfficialFood
class
TagStringParseTestCase
(
UnitTestCase
):
"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment