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
...
@@ -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
``TaggableManager`` was named ``tags``, you could find all the delicious fruit
like so::
like so::
>>> Food.objects.filter(tags__in=["delicious"])
>>> Food.objects.filter(tags__
name__
in=["delicious"])
[<Food: apple>, <Food: pear>, <Food: plum>]
[<Food: apple>, <Food: pear>, <Food: plum>]
...
@@ -64,7 +64,10 @@ If you're filtering on multiple tags, it's very common to get duplicate
...
@@ -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
results, because of the way relational databases work. Often you'll want to
make use of the ``distinct()`` method on ``QuerySets``::
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: apple>, <Food: apple>]
>>> Food.objects.filter(tags__in=["delicious", "red"]).distinct()
>>> Food.objects.filter(tags__
name__
in=["delicious", "red"]).distinct()
[<Food: apple>]
[<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.
...
@@ -10,6 +10,10 @@ Unreleased.
* Added an index on the ``object_id`` field of ``TaggedItem``.
* Added an index on the ``object_id`` field of ``TaggedItem``.
* When displaying tags always join them with commas, never spaces.
* When displaying tags always join them with commas, never spaces.
* The docs are now available `online <http://django-taggit.readthedocs.org/>`_.
* 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
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.
...
@@ -13,7 +13,7 @@ for known issues with older versions of Django), and Python 2.4-2.X.
getting_started
getting_started
forms
forms
api
api
custom_t
hrough
custom_t
agging
issues
issues
changelog
changelog
...
...
runtests.py
View file @
31cbafbb
...
@@ -24,7 +24,7 @@ def runtests(*test_args):
...
@@ -24,7 +24,7 @@ def runtests(*test_args):
test_args
=
[
'tests'
,
'suggest'
]
test_args
=
[
'tests'
,
'suggest'
]
parent
=
dirname
(
abspath
(
__file__
))
parent
=
dirname
(
abspath
(
__file__
))
sys
.
path
.
insert
(
0
,
parent
)
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
)
sys
.
exit
(
failures
)
...
...
taggit/contrib/suggest/tests.py
View file @
31cbafbb
...
@@ -9,7 +9,7 @@ from taggit.models import Tag
...
@@ -9,7 +9,7 @@ from taggit.models import Tag
class
SuggestCase
(
TestCase
):
class
SuggestCase
(
TestCase
):
def
test_simple_suggest
(
self
):
def
test_simple_suggest
(
self
):
ku_tag
=
Tag
.
objects
.
create
(
name
=
'ku'
)
ku_tag
=
Tag
.
objects
.
create
(
name
=
'ku'
)
ku_keyword1
=
TagKeyword
.
objects
.
create
(
TagKeyword
.
objects
.
create
(
tag
=
ku_tag
,
tag
=
ku_tag
,
keyword
=
'kansas university'
keyword
=
'kansas university'
)
)
...
@@ -31,7 +31,7 @@ class SuggestCase(TestCase):
...
@@ -31,7 +31,7 @@ class SuggestCase(TestCase):
def
test_bad_regex
(
self
):
def
test_bad_regex
(
self
):
ku_tag
=
Tag
.
objects
.
create
(
name
=
'ku'
)
ku_tag
=
Tag
.
objects
.
create
(
name
=
'ku'
)
ku_keyword1
=
TagKeyword
.
objects
.
create
(
TagKeyword
.
objects
.
create
(
tag
=
ku_tag
,
tag
=
ku_tag
,
keyword
=
'kansas university'
keyword
=
'kansas university'
)
)
...
...
taggit/contrib/suggest/utils.py
View file @
31cbafbb
import
re
import
re
from
django.conf
import
settings
from
taggit.contrib.suggest.models
import
TagKeyword
,
TagRegex
from
taggit.contrib.suggest.models
import
TagKeyword
,
TagRegex
from
taggit.models
import
Tag
from
taggit.models
import
Tag
...
...
taggit/managers.py
View file @
31cbafbb
import
django
from
django.contrib.contenttypes.generic
import
GenericRelation
from
django.contrib.contenttypes.generic
import
GenericForeignKey
from
django.contrib.contenttypes.models
import
ContentType
from
django.contrib.contenttypes.models
import
ContentType
from
django.db
import
models
from
django.db
import
models
from
django.db.models.fields.related
import
ManyToManyRel
from
django.db.models.fields.related
import
ManyToManyRel
from
django.db.models.related
import
RelatedObject
from
django.db.models.related
import
RelatedObject
from
django.db.models.query_utils
import
QueryWrapper
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
taggit.forms
import
TagField
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
from
taggit.utils
import
require_instance_manager
...
@@ -39,9 +37,9 @@ class TaggableRel(ManyToManyRel):
...
@@ -39,9 +37,9 @@ class TaggableRel(ManyToManyRel):
class
TaggableManager
(
object
):
class
TaggableManager
(
object
):
def
__init__
(
self
,
verbose_name
=
_
(
"Tags"
),
through
=
None
):
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
.
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
.
verbose_name
=
verbose_name
self
.
editable
=
True
self
.
editable
=
True
self
.
unique
=
False
self
.
unique
=
False
...
@@ -67,46 +65,18 @@ class TaggableManager(object):
...
@@ -67,46 +65,18 @@ class TaggableManager(object):
self
.
model
=
cls
self
.
model
=
cls
cls
.
_meta
.
add_field
(
self
)
cls
.
_meta
.
add_field
(
self
)
setattr
(
cls
,
name
,
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
):
def
save_form_data
(
self
,
instance
,
value
):
getattr
(
instance
,
self
.
name
)
.
set
(
*
value
)
getattr
(
instance
,
self
.
name
)
.
set
(
*
value
)
def
get_prep_lookup
(
self
,
lookup_type
,
value
):
def
get_prep_lookup
(
self
,
lookup_type
,
value
):
if
lookup_type
not
in
[
"in"
,
"isnull"
]:
return
models
.
Field
()
.
get_prep_lookup
(
lookup_type
,
value
)
# 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
)
if
django
.
VERSION
<
(
1
,
2
):
def
get_db_prep_lookup
(
self
,
*
args
,
**
kwargs
):
get_db_prep_lookup
=
get_prep_lookup
return
models
.
Field
()
.
get_db_prep_lookup
(
*
args
,
**
kwargs
)
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
formfield
(
self
,
form_class
=
TagField
,
**
kwargs
):
def
formfield
(
self
,
form_class
=
TagField
,
**
kwargs
):
defaults
=
{
defaults
=
{
...
@@ -122,12 +92,10 @@ class TaggableManager(object):
...
@@ -122,12 +92,10 @@ class TaggableManager(object):
return
self
.
through
.
objects
.
none
()
return
self
.
through
.
objects
.
none
()
def
related_query_name
(
self
):
def
related_query_name
(
self
):
return
self
.
model
.
_meta
.
object_name
.
lower
()
return
self
.
model
.
_meta
.
module_name
def
m2m_reverse_name
(
self
):
def
m2m_reverse_name
(
self
):
if
self
.
use_gfk
:
return
self
.
through
.
_meta
.
get_field_by_name
(
"tag"
)[
0
]
.
column
return
"id"
return
self
.
through
.
_meta
.
pk
.
column
def
m2m_column_name
(
self
):
def
m2m_column_name
(
self
):
if
self
.
use_gfk
:
if
self
.
use_gfk
:
...
@@ -143,7 +111,7 @@ class TaggableManager(object):
...
@@ -143,7 +111,7 @@ class TaggableManager(object):
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
(
pieces
[:
pos
+
1
])
prefix
=
"__"
.
join
(
[
"tagged_items"
]
+
pieces
[:
pos
-
2
])
cts
=
map
(
ContentType
.
objects
.
get_for_model
,
_get_subclasses
(
self
.
model
))
cts
=
map
(
ContentType
.
objects
.
get_for_model
,
_get_subclasses
(
self
.
model
))
if
len
(
cts
)
==
1
:
if
len
(
cts
)
==
1
:
return
[(
"
%
s__content_type"
%
prefix
,
cts
[
0
])]
return
[(
"
%
s__content_type"
%
prefix
,
cts
[
0
])]
...
@@ -163,8 +131,8 @@ class _TaggableManager(models.Manager):
...
@@ -163,8 +131,8 @@ class _TaggableManager(models.Manager):
@require_instance_manager
@require_instance_manager
def
add
(
self
,
*
tags
):
def
add
(
self
,
*
tags
):
for
tag
in
tags
:
for
tag
in
tags
:
if
not
isinstance
(
tag
,
Tag
):
if
not
isinstance
(
tag
,
self
.
through
.
tag_model
()
):
tag
,
_
=
Tag
.
objects
.
get_or_create
(
name
=
tag
)
tag
,
_
=
self
.
through
.
tag_model
()
.
objects
.
get_or_create
(
name
=
tag
)
self
.
through
.
objects
.
get_or_create
(
tag
=
tag
,
**
self
.
_lookup_kwargs
())
self
.
through
.
objects
.
get_or_create
(
tag
=
tag
,
**
self
.
_lookup_kwargs
())
@require_instance_manager
@require_instance_manager
...
...
taggit/models.py
View file @
31cbafbb
...
@@ -6,7 +6,7 @@ from django.template.defaultfilters import slugify
...
@@ -6,7 +6,7 @@ from django.template.defaultfilters import slugify
from
django.utils.translation
import
ugettext_lazy
as
_
,
ugettext
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
)
name
=
models
.
CharField
(
verbose_name
=
_
(
'Name'
),
max_length
=
100
)
slug
=
models
.
SlugField
(
verbose_name
=
_
(
'Slug'
),
unique
=
True
,
max_length
=
100
)
slug
=
models
.
SlugField
(
verbose_name
=
_
(
'Slug'
),
unique
=
True
,
max_length
=
100
)
...
@@ -14,8 +14,7 @@ class Tag(models.Model):
...
@@ -14,8 +14,7 @@ class Tag(models.Model):
return
self
.
name
return
self
.
name
class
Meta
:
class
Meta
:
verbose_name
=
_
(
"Tag"
)
abstract
=
True
verbose_name_plural
=
_
(
"Tags"
)
def
save
(
self
,
*
args
,
**
kwargs
):
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
pk
and
not
self
.
slug
:
if
not
self
.
pk
and
not
self
.
slug
:
...
@@ -35,7 +34,7 @@ class Tag(models.Model):
...
@@ -35,7 +34,7 @@ class Tag(models.Model):
while
True
:
while
True
:
try
:
try
:
sid
=
transaction
.
savepoint
(
**
trans_kwargs
)
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
)
transaction
.
savepoint_commit
(
sid
,
**
trans_kwargs
)
return
res
return
res
except
IntegrityError
:
except
IntegrityError
:
...
@@ -43,15 +42,16 @@ class Tag(models.Model):
...
@@ -43,15 +42,16 @@ class Tag(models.Model):
i
+=
1
i
+=
1
self
.
slug
=
"
%
s_
%
d"
%
(
slug
,
i
)
self
.
slug
=
"
%
s_
%
d"
%
(
slug
,
i
)
else
:
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
):
def
__unicode__
(
self
):
return
ugettext
(
"
%(object)
s tagged with
%(tag)
s"
)
%
{
return
ugettext
(
"
%(object)
s tagged with
%(tag)
s"
)
%
{
"object"
:
self
.
content_object
,
"object"
:
self
.
content_object
,
...
@@ -62,6 +62,10 @@ class TaggedItemBase(models.Model):
...
@@ -62,6 +62,10 @@ class TaggedItemBase(models.Model):
abstract
=
True
abstract
=
True
@classmethod
@classmethod
def
tag_model
(
cls
):
return
cls
.
_meta
.
get_field_by_name
(
"tag"
)[
0
]
.
rel
.
to
@classmethod
def
tag_relname
(
cls
):
def
tag_relname
(
cls
):
return
cls
.
_meta
.
get_field_by_name
(
'tag'
)[
0
]
.
rel
.
related_name
return
cls
.
_meta
.
get_field_by_name
(
'tag'
)[
0
]
.
rel
.
related_name
...
@@ -71,27 +75,46 @@ class TaggedItemBase(models.Model):
...
@@ -71,27 +75,46 @@ class TaggedItemBase(models.Model):
'content_object'
:
instance
'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
@classmethod
def
tags_for
(
cls
,
model
,
instance
=
None
):
def
tags_for
(
cls
,
model
,
instance
=
None
):
if
instance
is
not
None
:
if
instance
is
not
None
:
return
Tag
.
objects
.
filter
(
**
{
return
cls
.
tag_model
()
.
objects
.
filter
(
**
{
'
%
s__content_object'
%
cls
.
tag_relname
():
instance
'
%
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
'
%
s__content_object__isnull'
%
cls
.
tag_relname
():
False
})
.
distinct
()
})
.
distinct
()
class
TaggedItem
(
Tagged
ItemBase
):
class
GenericTaggedItemBase
(
ItemBase
):
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
(
ContentType
,
verbose_name
=
_
(
'Content type'
),
if
django
.
VERSION
<
(
1
,
2
):
related_name
=
"tagged_items"
)
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
()
content_object
=
GenericForeignKey
()
class
Meta
:
class
Meta
:
verbose_name
=
_
(
"Tagged Item"
)
abstract
=
True
verbose_name_plural
=
_
(
"Tagged Items"
)
@classmethod
@classmethod
def
lookup_kwargs
(
cls
,
instance
):
def
lookup_kwargs
(
cls
,
instance
):
return
{
return
{
...
@@ -102,12 +125,16 @@ class TaggedItem(TaggedItemBase):
...
@@ -102,12 +125,16 @@ class TaggedItem(TaggedItemBase):
@classmethod
@classmethod
def
tags_for
(
cls
,
model
,
instance
=
None
):
def
tags_for
(
cls
,
model
,
instance
=
None
):
ct
=
ContentType
.
objects
.
get_for_model
(
model
)
ct
=
ContentType
.
objects
.
get_for_model
(
model
)
kwargs
=
{
"
%
s__content_type"
%
cls
.
tag_relname
():
ct
}
if
instance
is
not
None
:
if
instance
is
not
None
:
return
Tag
.
objects
.
filter
(
**
{
kwargs
[
"
%
s__object_id"
%
cls
.
tag_relname
()]
=
instance
.
pk
'
%
s__object_id'
%
cls
.
tag_relname
():
instance
.
pk
,
return
cls
.
tag_model
()
.
objects
.
filter
(
**
kwargs
)
.
distinct
()
'
%
s__content_type'
%
cls
.
tag_relname
():
ct
})
return
Tag
.
objects
.
filter
(
**
{
class
TaggedItem
(
GenericTaggedItemBase
,
TaggedItemBase
):
'
%
s__content_type'
%
cls
.
tag_relname
():
ct
class
Meta
:
})
.
distinct
()
verbose_name
=
_
(
"Tagged Item"
)
verbose_name_plural
=
_
(
"Tagged Items"
)
taggit/tests/forms.py
View file @
31cbafbb
from
django
import
forms
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
):
class
FoodForm
(
forms
.
ModelForm
):
...
@@ -14,3 +14,7 @@ class DirectFoodForm(forms.ModelForm):
...
@@ -14,3 +14,7 @@ class DirectFoodForm(forms.ModelForm):
class
CustomPKFoodForm
(
forms
.
ModelForm
):
class
CustomPKFoodForm
(
forms
.
ModelForm
):
class
Meta
:
class
Meta
:
model
=
CustomPKFood
model
=
CustomPKFood
class
OfficialFoodForm
(
forms
.
ModelForm
):
class
Meta
:
model
=
OfficialFood
taggit/tests/models.py
View file @
31cbafbb
from
django.db
import
models
from
django.db
import
models
from
taggit.managers
import
TaggableManager
from
taggit.managers
import
TaggableManager
from
taggit.models
import
TaggedItemBase
from
taggit.models
import
TaggedItemBase
,
GenericTaggedItemBase
,
TagBase
class
Food
(
models
.
Model
):
class
Food
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
name
=
models
.
CharField
(
max_length
=
50
)
...
@@ -19,23 +20,23 @@ class Pet(models.Model):
...
@@ -19,23 +20,23 @@ class Pet(models.Model):
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
self
.
name
return
self
.
name
class
HousePet
(
Pet
):
class
HousePet
(
Pet
):
trained
=
models
.
BooleanField
()
trained
=
models
.
BooleanField
()
# test direct-tagging with custom through model
# Test direct-tagging with custom through model
class
TaggedFood
(
TaggedItemBase
):
class
TaggedFood
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'DirectFood'
)
content_object
=
models
.
ForeignKey
(
'DirectFood'
)
class
TaggedPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'DirectPet'
)
class
DirectFood
(
models
.
Model
):
class
DirectFood
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
(
through
=
TaggedFood
)
tags
=
TaggableManager
(
through
=
TaggedFood
)
class
TaggedPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'DirectPet'
)
class
DirectPet
(
models
.
Model
):
class
DirectPet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
name
=
models
.
CharField
(
max_length
=
50
)
...
@@ -43,24 +44,27 @@ class DirectPet(models.Model):
...
@@ -43,24 +44,27 @@ class DirectPet(models.Model):
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
self
.
name
return
self
.
name
class
DirectHousePet
(
DirectPet
):
class
DirectHousePet
(
DirectPet
):
trained
=
models
.
BooleanField
()
trained
=
models
.
BooleanField
()
# test custom through model to model with custom PK
# Test custom through model to model with custom PK
class
TaggedCustomPKFood
(
TaggedItemBase
):
class
TaggedCustomPKFood
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'CustomPKFood'
)
content_object
=
models
.
ForeignKey
(
'CustomPKFood'
)
class
TaggedCustomPKPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'CustomPKPet'
)
class
CustomPKFood
(
models
.
Model
):
class
CustomPKFood
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
,
primary_key
=
True
)
name
=
models
.
CharField
(
max_length
=
50
,
primary_key
=
True
)
tags
=
TaggableManager
(
through
=
TaggedCustomPKFood
)
tags
=
TaggableManager
(
through
=
TaggedCustomPKFood
)
class
TaggedCustomPKPet
(
TaggedItemBase
):
def
__unicode__
(
self
):
content_object
=
models
.
ForeignKey
(
'CustomPKPet'
)
return
self
.
name
class
CustomPKPet
(
models
.
Model
):
class
CustomPKPet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
,
primary_key
=
True
)
name
=
models
.
CharField
(
max_length
=
50
,
primary_key
=
True
)
...
@@ -71,3 +75,31 @@ class CustomPKPet(models.Model):
...
@@ -71,3 +75,31 @@ class CustomPKPet(models.Model):
class
CustomPKHousePet
(
CustomPKPet
):
class
CustomPKHousePet
(
CustomPKPet
):
trained
=
models
.
BooleanField
()
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
...
@@ -3,10 +3,12 @@ from unittest import TestCase as UnitTestCase
from
django.test
import
TestCase
,
TransactionTestCase
from
django.test
import
TestCase
,
TransactionTestCase
from
taggit.models
import
Tag
,
TaggedItem
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
,
from
taggit.tests.models
import
(
Food
,
Pet
,
HousePet
,
DirectFood
,
DirectPet
,
DirectHousePet
,
TaggedPet
,
CustomPKFood
,
CustomPKPet
,
CustomPKHousePet
,
DirectHousePet
,
TaggedPet
,
CustomPKFood
,
CustomPKPet
,
CustomPKHousePet
,
TaggedCustomPKPet
)
TaggedCustomPKPet
,
OfficialFood
,
OfficialPet
,
OfficialHousePet
,
OfficialThroughModel
,
OfficialTag
)
from
taggit.utils
import
parse_tags
,
edit_string_for_tags
from
taggit.utils
import
parse_tags
,
edit_string_for_tags
...
@@ -27,23 +29,39 @@ class BaseTaggingTransactionTestCase(TransactionTestCase, BaseTaggingTest):
...
@@ -27,23 +29,39 @@ class BaseTaggingTransactionTestCase(TransactionTestCase, BaseTaggingTest):
class
TagModelTestCase
(
BaseTaggingTransactionTestCase
):
class
TagModelTestCase
(
BaseTaggingTransactionTestCase
):
food_model
=
Food
food_model
=
Food
tag_model
=
Tag
def
test_unique_slug
(
self
):
def
test_unique_slug
(
self
):
apple
=
self
.
food_model
.
objects
.
create
(
name
=
"apple"
)
apple
=
self
.
food_model
.
objects
.
create
(
name
=
"apple"
)
apple
.
tags
.
add
(
"Red"
,
"red"
)
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
):
class
TagModelDirectTestCase
(
TagModelTestCase
):
food_model
=
DirectFood
food_model
=
DirectFood
tag_model
=
Tag
class
TagModelCustomPKTestCase
(
TagModelTestCase
):
class
TagModelCustomPKTestCase
(
TagModelTestCase
):
food_model
=
CustomPKFood
food_model
=
CustomPKFood
tag_model
=
Tag
class
TagModelOfficialTestCase
(
TagModelTestCase
):
food_model
=
OfficialFood
tag_model
=
OfficialTag
class
TaggableManagerTestCase
(
BaseTaggingTestCase
):
class
TaggableManagerTestCase
(
BaseTaggingTestCase
):
food_model
=
Food
food_model
=
Food
pet_model
=
Pet
pet_model
=
Pet
housepet_model
=
HousePet
housepet_model
=
HousePet
taggeditem_model
=
TaggedItem
taggeditem_model
=
TaggedItem
tag_model
=
Tag
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"
)
...
@@ -72,7 +90,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
...
@@ -72,7 +90,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
apple
.
tags
.
remove
(
'green'
)
apple
.
tags
.
remove
(
'green'
)
self
.
assert_tags_equal
(
apple
.
tags
.
all
(),
[
'red'
])
self
.
assert_tags_equal
(
apple
.
tags
.
all
(),
[
'red'
])
self
.
assert_tags_equal
(
self
.
food_model
.
tags
.
all
(),
[
'green'
,
'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
)
apple
.
tags
.
add
(
tag
)
self
.
assert_tags_equal
(
apple
.
tags
.
all
(),
[
"red"
,
"delicious"
])
self
.
assert_tags_equal
(
apple
.
tags
.
all
(),
[
"red"
,
"delicious"
])
...
@@ -108,13 +126,13 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
...
@@ -108,13 +126,13 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
apple
.
tags
.
add
(
"red"
,
"green"
)
apple
.
tags
.
add
(
"red"
,
"green"
)
pear
=
self
.
food_model
.
objects
.
create
(
name
=
"pear"
)
pear
=
self
.
food_model
.
objects
.
create
(
name
=
"pear"
)
pear
.
tags
.
add
(
"green"
)
pear
.
tags
.
add
(
"green"
)
self
.
assertEqual
(
self
.
assertEqual
(
list
(
self
.
food_model
.
objects
.
filter
(
tags__in
=
[
"red"
])),
list
(
self
.
food_model
.
objects
.
filter
(
tags__
name__
in
=
[
"red"
])),
[
apple
]
[
apple
]
)
)
self
.
assertEqual
(
self
.
assertEqual
(
list
(
self
.
food_model
.
objects
.
filter
(
tags__in
=
[
"green"
])),
list
(
self
.
food_model
.
objects
.
filter
(
tags__
name__
in
=
[
"green"
])),
[
apple
,
pear
]
[
apple
,
pear
]
)
)
...
@@ -123,18 +141,18 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
...
@@ -123,18 +141,18 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
dog
=
self
.
pet_model
.
objects
.
create
(
name
=
"dog"
)
dog
=
self
.
pet_model
.
objects
.
create
(
name
=
"dog"
)
dog
.
tags
.
add
(
"woof"
,
"red"
)
dog
.
tags
.
add
(
"woof"
,
"red"
)
self
.
assertEqual
(
self
.
assertEqual
(
list
(
self
.
food_model
.
objects
.
filter
(
tags__in
=
[
"red"
])
.
distinct
()),
list
(
self
.
food_model
.
objects
.
filter
(
tags__
name__
in
=
[
"red"
])
.
distinct
()),
[
apple
]
[
apple
]
)
)
tag
=
Tag
.
objects
.
get
(
name
=
"woof"
)
tag
=
self
.
tag_model
.
objects
.
get
(
name
=
"woof"
)
self
.
assertEqual
(
list
(
self
.
pet_model
.
objects
.
filter
(
tags__in
=
[
tag
])),
[
dog
])
self
.
assertEqual
(
list
(
self
.
pet_model
.
objects
.
filter
(
tags__
name__
in
=
[
tag
])),
[
dog
])
cat
=
self
.
housepet_model
.
objects
.
create
(
name
=
"cat"
,
trained
=
True
)
cat
=
self
.
housepet_model
.
objects
.
create
(
name
=
"cat"
,
trained
=
True
)
cat
.
tags
.
add
(
"fuzzy"
)
cat
.
tags
.
add
(
"fuzzy"
)
self
.
assertEqual
(
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
]
[
kitty
.
pk
,
cat
.
pk
]
)
)
...
@@ -148,7 +166,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
...
@@ -148,7 +166,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
guava
=
self
.
food_model
.
objects
.
create
(
name
=
"guava"
)
guava
=
self
.
food_model
.
objects
.
create
(
name
=
"guava"
)
self
.
assertEqual
(
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
],
[
pear
.
pk
,
guava
.
pk
],
)
)
...
@@ -177,9 +195,11 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
...
@@ -177,9 +195,11 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
spike
=
self
.
pet_model
.
objects
.
create
(
name
=
'Spike'
)
spike
=
self
.
pet_model
.
objects
.
create
(
name
=
'Spike'
)
spot
.
tags
.
add
(
'scary'
)
spot
.
tags
.
add
(
'scary'
)
spike
.
tags
.
add
(
'fluffy'
)
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
(
self
.
assert_tags_equal
(
[
i
.
tag
for
i
in
self
.
taggeditem_model
.
objects
.
filter
(
**
lookup_kwargs
)]
,
self
.
tag_model
.
objects
.
filter
(
**
lookup_kwargs
)
,
[
'scary'
]
[
'scary'
]
)
)
...
@@ -211,6 +231,28 @@ class TaggableManagerCustomPKTestCase(TaggableManagerTestCase):
...
@@ -211,6 +231,28 @@ class TaggableManagerCustomPKTestCase(TaggableManagerTestCase):
# tell if the instance is saved or not
# tell if the instance is saved or not
pass
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
):
class
TaggableFormTestCase
(
BaseTaggingTestCase
):
form_class
=
FoodForm
form_class
=
FoodForm
food_model
=
Food
food_model
=
Food
...
@@ -254,6 +296,10 @@ class TaggableFormCustomPKTestCase(TaggableFormTestCase):
...
@@ -254,6 +296,10 @@ class TaggableFormCustomPKTestCase(TaggableFormTestCase):
form_class
=
CustomPKFoodForm
form_class
=
CustomPKFoodForm
food_model
=
CustomPKFood
food_model
=
CustomPKFood
class
TaggableFormOfficialTestCase
(
TaggableFormTestCase
):
form_class
=
OfficialFoodForm
food_model
=
OfficialFood
class
TagStringParseTestCase
(
UnitTestCase
):
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