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
fb2d1cc4
authored
Mar 23, 2013
by
Christopher Grebs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
We now run on py26, py27, pypy, py32, py33 using django14 and django15.
parent
20a1b6bc
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
169 additions
and
128 deletions
+169
-128
taggit/admin.py
+2
-0
taggit/forms.py
+4
-1
taggit/managers.py
+20
-19
taggit/migrations/0001_initial.py
+21
-20
taggit/migrations/0002_unique_tagnames.py
+13
-12
taggit/models.py
+5
-7
taggit/tests/forms.py
+2
-0
taggit/tests/models.py
+30
-8
taggit/tests/tests.py
+48
-42
taggit/utils.py
+22
-19
taggit/views.py
+2
-0
No files found.
taggit/admin.py
View file @
fb2d1cc4
from
__future__
import
unicode_literals
from
django.contrib
import
admin
from
taggit.models
import
Tag
,
TaggedItem
...
...
taggit/forms.py
View file @
fb2d1cc4
from
__future__
import
unicode_literals
from
django
import
forms
from
django.utils.translation
import
ugettext
as
_
from
django.utils
import
six
from
taggit.utils
import
parse_tags
,
edit_string_for_tags
class
TagWidget
(
forms
.
TextInput
):
def
render
(
self
,
name
,
value
,
attrs
=
None
):
if
value
is
not
None
and
not
isinstance
(
value
,
basestring
):
if
value
is
not
None
and
not
isinstance
(
value
,
six
.
string_types
):
value
=
edit_string_for_tags
([
o
.
tag
for
o
in
value
.
select_related
(
"tag"
)])
return
super
(
TagWidget
,
self
)
.
render
(
name
,
value
,
attrs
)
...
...
taggit/managers.py
View file @
fb2d1cc4
from
__future__
import
unicode_literals
from
django.contrib.contenttypes.generic
import
GenericRelation
from
django.contrib.contenttypes.models
import
ContentType
from
django.db
import
models
...
...
@@ -5,27 +7,13 @@ from django.db.models.fields.related import ManyToManyRel, RelatedField, add_laz
from
django.db.models.related
import
RelatedObject
from
django.utils.text
import
capfirst
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils
import
six
from
taggit.forms
import
TagField
from
taggit.models
import
TaggedItem
,
GenericTaggedItemBase
from
taggit.utils
import
require_instance_manager
try
:
all
except
NameError
:
# 2.4 compat
try
:
from
django.utils.itercompat
import
all
except
ImportError
:
# 1.1.X compat
def
all
(
iterable
):
for
item
in
iterable
:
if
not
item
:
return
False
return
True
class
TaggableRel
(
ManyToManyRel
):
def
__init__
(
self
):
self
.
related_name
=
None
...
...
@@ -68,7 +56,7 @@ class TaggableManager(RelatedField):
cls
.
_meta
.
add_field
(
self
)
setattr
(
cls
,
name
,
self
)
if
not
cls
.
_meta
.
abstract
:
if
isinstance
(
self
.
through
,
basestring
):
if
isinstance
(
self
.
through
,
six
.
string_types
):
def
resolve_related_class
(
field
,
model
,
cls
):
self
.
through
=
model
self
.
post_through_setup
(
cls
)
...
...
@@ -78,6 +66,9 @@ class TaggableManager(RelatedField):
else
:
self
.
post_through_setup
(
cls
)
def
__lt__
(
self
,
other
):
return
False
def
post_through_setup
(
self
,
cls
):
self
.
use_gfk
=
(
self
.
through
is
None
or
issubclass
(
self
.
through
,
GenericTaggedItemBase
)
...
...
@@ -132,7 +123,8 @@ class TaggableManager(RelatedField):
if
negate
or
not
self
.
use_gfk
:
return
[]
prefix
=
"__"
.
join
([
"tagged_items"
]
+
pieces
[:
pos
-
2
])
cts
=
map
(
ContentType
.
objects
.
get_for_model
,
_get_subclasses
(
self
.
model
))
get
=
ContentType
.
objects
.
get_for_model
cts
=
[
get
(
obj
)
for
obj
in
_get_subclasses
(
self
.
model
)]
if
len
(
cts
)
==
1
:
return
[(
"
%
s__content_type"
%
prefix
,
cts
[
0
])]
return
[(
"
%
s__content_type__in"
%
prefix
,
cts
)]
...
...
@@ -197,7 +189,7 @@ class _TaggableManager(models.Manager):
def
similar_objects
(
self
):
lookup_kwargs
=
self
.
_lookup_kwargs
()
lookup_keys
=
sorted
(
lookup_kwargs
)
qs
=
self
.
through
.
objects
.
values
(
*
lookup_kwargs
.
keys
(
))
qs
=
self
.
through
.
objects
.
values
(
*
six
.
iterkeys
(
lookup_kwargs
))
qs
=
qs
.
annotate
(
n
=
models
.
Count
(
'pk'
))
qs
=
qs
.
exclude
(
**
lookup_kwargs
)
qs
=
qs
.
filter
(
tag__in
=
self
.
all
())
...
...
@@ -220,7 +212,7 @@ class _TaggableManager(models.Manager):
preload
.
setdefault
(
result
[
'content_type'
],
set
())
preload
[
result
[
"content_type"
]]
.
add
(
result
[
"object_id"
])
for
ct
,
obj_ids
in
preload
.
ite
rite
ms
():
for
ct
,
obj_ids
in
preload
.
items
():
ct
=
ContentType
.
objects
.
get_for_id
(
ct
)
for
obj
in
ct
.
model_class
()
.
_default_manager
.
filter
(
pk__in
=
obj_ids
):
items
[(
ct
.
pk
,
obj
.
pk
)]
=
obj
...
...
@@ -243,3 +235,11 @@ def _get_subclasses(model):
getattr
(
field
.
field
.
rel
,
"parent_link"
,
None
)):
subclasses
.
extend
(
_get_subclasses
(
field
.
model
))
return
subclasses
# `total_ordering` does not exist in Django 1.4, as such
# we special case this import to be py3k specific which
# is not supported by Django 1.4
if
six
.
PY3
:
from
django.utils.functional
import
total_ordering
TaggableManager
=
total_ordering
(
TaggableManager
)
\ No newline at end of file
taggit/migrations/0001_initial.py
View file @
fb2d1cc4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
...
...
@@ -9,52 +11,52 @@ class Migration(SchemaMigration):
def
forwards
(
self
,
orm
):
# Adding model 'Tag'
db
.
create_table
(
u
'taggit_tag'
,
(
(
u
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
db
.
create_table
(
'taggit_tag'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
100
)),
(
'slug'
,
self
.
gf
(
'django.db.models.fields.SlugField'
)(
unique
=
True
,
max_length
=
100
)),
))
db
.
send_create_signal
(
u
'taggit'
,
[
'Tag'
])
db
.
send_create_signal
(
'taggit'
,
[
'Tag'
])
# Adding model 'TaggedItem'
db
.
create_table
(
u
'taggit_taggeditem'
,
(
(
u
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'tag'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
u
'taggit_taggeditem_items'
,
to
=
orm
[
'taggit.Tag'
])),
db
.
create_table
(
'taggit_taggeditem'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'tag'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'taggit_taggeditem_items'
,
to
=
orm
[
'taggit.Tag'
])),
(
'object_id'
,
self
.
gf
(
'django.db.models.fields.IntegerField'
)(
db_index
=
True
)),
(
'content_type'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
u
'taggit_taggeditem_tagged_items'
,
to
=
orm
[
'contenttypes.ContentType'
])),
(
'content_type'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'taggit_taggeditem_tagged_items'
,
to
=
orm
[
'contenttypes.ContentType'
])),
))
db
.
send_create_signal
(
u
'taggit'
,
[
'TaggedItem'
])
db
.
send_create_signal
(
'taggit'
,
[
'TaggedItem'
])
def
backwards
(
self
,
orm
):
# Deleting model 'Tag'
db
.
delete_table
(
u
'taggit_tag'
)
db
.
delete_table
(
'taggit_tag'
)
# Deleting model 'TaggedItem'
db
.
delete_table
(
u
'taggit_taggeditem'
)
db
.
delete_table
(
'taggit_taggeditem'
)
models
=
{
u
'contenttypes.contenttype'
:
{
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
u
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
u
'taggit.tag'
:
{
'taggit.tag'
:
{
'Meta'
:
{
'object_name'
:
'Tag'
},
u
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'slug'
:
(
'django.db.models.fields.SlugField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
})
},
u
'taggit.taggeditem'
:
{
'taggit.taggeditem'
:
{
'Meta'
:
{
'object_name'
:
'TaggedItem'
},
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"
u'taggit_taggeditem_tagged_items'"
,
'to'
:
u
"orm['contenttypes.ContentType']"
}),
u
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"
'taggit_taggeditem_tagged_items'"
,
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'object_id'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'db_index'
:
'True'
}),
'tag'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"
u'taggit_taggeditem_items'"
,
'to'
:
u
"orm['taggit.Tag']"
})
'tag'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"
'taggit_taggeditem_items'"
,
'to'
:
"orm['taggit.Tag']"
})
}
}
complete_apps
=
[
'taggit'
]
\ No newline at end of file
complete_apps
=
[
'taggit'
]
taggit/migrations/0002_unique_tagnames.py
View file @
fb2d1cc4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
...
...
@@ -9,35 +11,35 @@ class Migration(SchemaMigration):
def
forwards
(
self
,
orm
):
# Adding unique constraint on 'Tag', fields ['name']
db
.
create_unique
(
u
'taggit_tag'
,
[
'name'
])
db
.
create_unique
(
'taggit_tag'
,
[
'name'
])
def
backwards
(
self
,
orm
):
# Removing unique constraint on 'Tag', fields ['name']
db
.
delete_unique
(
u
'taggit_tag'
,
[
'name'
])
db
.
delete_unique
(
'taggit_tag'
,
[
'name'
])
models
=
{
u
'contenttypes.contenttype'
:
{
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
u
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
u
'taggit.tag'
:
{
'taggit.tag'
:
{
'Meta'
:
{
'object_name'
:
'Tag'
},
u
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
}),
'slug'
:
(
'django.db.models.fields.SlugField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'100'
})
},
u
'taggit.taggeditem'
:
{
'taggit.taggeditem'
:
{
'Meta'
:
{
'object_name'
:
'TaggedItem'
},
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"
u'taggit_taggeditem_tagged_items'"
,
'to'
:
u
"orm['contenttypes.ContentType']"
}),
u
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"
'taggit_taggeditem_tagged_items'"
,
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'object_id'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'db_index'
:
'True'
}),
'tag'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"
u'taggit_taggeditem_items'"
,
'to'
:
u
"orm['taggit.Tag']"
})
'tag'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"
'taggit_taggeditem_items'"
,
'to'
:
"orm['taggit.Tag']"
})
}
}
complete_apps
=
[
'taggit'
]
\ No newline at end of file
complete_apps
=
[
'taggit'
]
taggit/models.py
View file @
fb2d1cc4
from
__future__
import
unicode_literals
import
django
from
django.contrib.contenttypes.models
import
ContentType
from
django.contrib.contenttypes.generic
import
GenericForeignKey
...
...
@@ -10,7 +12,7 @@ class TagBase(models.Model):
name
=
models
.
CharField
(
verbose_name
=
_
(
'Name'
),
unique
=
True
,
max_length
=
100
)
slug
=
models
.
SlugField
(
verbose_name
=
_
(
'Slug'
),
unique
=
True
,
max_length
=
100
)
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
self
.
name
class
Meta
:
...
...
@@ -57,9 +59,8 @@ class Tag(TagBase):
verbose_name_plural
=
_
(
"Tags"
)
class
ItemBase
(
models
.
Model
):
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
ugettext
(
"
%(object)
s tagged with
%(tag)
s"
)
%
{
"object"
:
self
.
content_object
,
"tag"
:
self
.
tag
...
...
@@ -90,10 +91,7 @@ class ItemBase(models.Model):
class
TaggedItemBase
(
ItemBase
):
if
django
.
VERSION
<
(
1
,
2
):
tag
=
models
.
ForeignKey
(
Tag
,
related_name
=
"
%(class)
s_items"
)
else
:
tag
=
models
.
ForeignKey
(
Tag
,
related_name
=
"
%(app_label)
s_
%(class)
s_items"
)
tag
=
models
.
ForeignKey
(
Tag
,
related_name
=
"
%(app_label)
s_
%(class)
s_items"
)
class
Meta
:
abstract
=
True
...
...
taggit/tests/forms.py
View file @
fb2d1cc4
from
__future__
import
unicode_literals
from
django
import
forms
from
taggit.tests.models
import
Food
,
DirectFood
,
CustomPKFood
,
OfficialFood
...
...
taggit/tests/models.py
View file @
fb2d1cc4
from
__future__
import
unicode_literals
from
django.db
import
models
from
django.utils.encoding
import
python_2_unicode_compatible
from
taggit.managers
import
TaggableManager
from
taggit.models
import
(
TaggedItemBase
,
GenericTaggedItemBase
,
TaggedItem
,
TagBase
,
Tag
)
@python_2_unicode_compatible
class
Food
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
()
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
self
.
name
@python_2_unicode_compatible
class
Pet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
()
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
self
.
name
class
HousePet
(
Pet
):
trained
=
models
.
BooleanField
()
...
...
@@ -30,22 +36,31 @@ class HousePet(Pet):
class
TaggedFood
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'DirectFood'
)
class
TaggedPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'DirectPet'
)
@python_2_unicode_compatible
class
DirectFood
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
(
through
=
"TaggedFood"
)
def
__str__
(
self
):
return
self
.
name
@python_2_unicode_compatible
class
DirectPet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
(
through
=
TaggedPet
)
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
self
.
name
class
DirectHousePet
(
DirectPet
):
trained
=
models
.
BooleanField
()
...
...
@@ -58,20 +73,22 @@ class TaggedCustomPKFood(TaggedItemBase):
class
TaggedCustomPKPet
(
TaggedItemBase
):
content_object
=
models
.
ForeignKey
(
'CustomPKPet'
)
@python_2_unicode_compatible
class
CustomPKFood
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
,
primary_key
=
True
)
tags
=
TaggableManager
(
through
=
TaggedCustomPKFood
)
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
self
.
name
@python_2_unicode_compatible
class
CustomPKPet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
,
primary_key
=
True
)
tags
=
TaggableManager
(
through
=
TaggedCustomPKPet
)
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
self
.
name
class
CustomPKHousePet
(
CustomPKPet
):
...
...
@@ -85,20 +102,22 @@ class OfficialTag(TagBase):
class
OfficialThroughModel
(
GenericTaggedItemBase
):
tag
=
models
.
ForeignKey
(
OfficialTag
,
related_name
=
"tagged_items"
)
@python_2_unicode_compatible
class
OfficialFood
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
(
through
=
OfficialThroughModel
)
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
self
.
name
@python_2_unicode_compatible
class
OfficialPet
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
50
)
tags
=
TaggableManager
(
through
=
OfficialThroughModel
)
def
__
unicode
__
(
self
):
def
__
str
__
(
self
):
return
self
.
name
class
OfficialHousePet
(
OfficialPet
):
...
...
@@ -129,6 +148,7 @@ class ArticleTag(Tag):
slug
+=
"-
%
d"
%
i
return
slug
class
ArticleTaggedItem
(
TaggedItem
):
class
Meta
:
proxy
=
True
...
...
@@ -137,7 +157,8 @@ class ArticleTaggedItem(TaggedItem):
def
tag_model
(
self
):
return
ArticleTag
class
Article
(
models
.
Model
):
title
=
models
.
CharField
(
max_length
=
100
)
tags
=
TaggableManager
(
through
=
ArticleTaggedItem
)
tags
=
TaggableManager
(
through
=
ArticleTaggedItem
)
\ No newline at end of file
taggit/tests/tests.py
View file @
fb2d1cc4
from
__future__
import
unicode_literals
from
unittest
import
TestCase
as
UnitTestCase
import
django
...
...
@@ -5,6 +7,8 @@ from django.conf import settings
from
django.core.exceptions
import
ValidationError
from
django.db
import
connection
from
django.test
import
TestCase
,
TransactionTestCase
from
django.utils
import
six
from
django.utils.encoding
import
force_text
from
taggit.managers
import
TaggableManager
from
taggit.models
import
Tag
,
TaggedItem
...
...
@@ -19,7 +23,7 @@ from taggit.utils import parse_tags, edit_string_for_tags
class
BaseTaggingTest
(
object
):
def
assert_tags_equal
(
self
,
qs
,
tags
,
sort
=
True
,
attr
=
"name"
):
got
=
map
(
lambda
tag
:
getattr
(
tag
,
attr
),
qs
)
got
=
[
getattr
(
obj
,
attr
)
for
obj
in
qs
]
if
sort
:
got
.
sort
()
tags
.
sort
()
...
...
@@ -52,7 +56,7 @@ class BaseTaggingTest(object):
return
form_str
def
assert_form_renders
(
self
,
form
,
html
):
self
.
assertEqual
(
str
(
form
),
self
.
_get_form_str
(
html
))
self
.
assert
HTML
Equal
(
str
(
form
),
self
.
_get_form_str
(
html
))
class
BaseTaggingTestCase
(
TestCase
,
BaseTaggingTest
):
pass
...
...
@@ -210,10 +214,12 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
cat
=
self
.
housepet_model
.
objects
.
create
(
name
=
"cat"
,
trained
=
True
)
cat
.
tags
.
add
(
"fuzzy"
)
self
.
assertEqual
(
map
(
lambda
o
:
o
.
pk
,
self
.
pet_model
.
objects
.
filter
(
tags__name__in
=
[
"fuzzy"
])),
[
kitty
.
pk
,
cat
.
pk
]
)
pks
=
self
.
pet_model
.
objects
.
filter
(
tags__name__in
=
[
"fuzzy"
])
model_name
=
self
.
pet_model
.
__name__
self
.
assertQuerysetEqual
(
pks
,
[
'<{0}: kitty>'
.
format
(
model_name
),
'<{0}: cat>'
.
format
(
model_name
)],
ordered
=
False
)
def
test_exclude
(
self
):
apple
=
self
.
food_model
.
objects
.
create
(
name
=
"apple"
)
...
...
@@ -224,10 +230,12 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
guava
=
self
.
food_model
.
objects
.
create
(
name
=
"guava"
)
self
.
assertEqual
(
map
(
lambda
o
:
o
.
pk
,
self
.
food_model
.
objects
.
exclude
(
tags__name__in
=
[
"red"
])),
[
pear
.
pk
,
guava
.
pk
],
)
pks
=
self
.
food_model
.
objects
.
exclude
(
tags__name__in
=
[
"red"
])
model_name
=
self
.
food_model
.
__name__
self
.
assertQuerysetEqual
(
pks
,
[
'<{0}: pear>'
.
format
(
model_name
),
'<{0}: guava>'
.
format
(
model_name
)],
ordered
=
False
)
def
test_similarity_by_tag
(
self
):
"""Test that pears are more similar to apples than watermelons"""
...
...
@@ -242,7 +250,8 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
similar_objs
=
apple
.
tags
.
similar_objects
()
self
.
assertEqual
(
similar_objs
,
[
pear
,
watermelon
])
self
.
assertEqual
(
map
(
lambda
x
:
x
.
similar_tags
,
similar_objs
),
[
3
,
2
])
self
.
assertEqual
([
obj
.
similar_tags
for
obj
in
similar_objs
],
[
3
,
2
])
def
test_tag_reuse
(
self
):
apple
=
self
.
food_model
.
objects
.
create
(
name
=
"apple"
)
...
...
@@ -268,7 +277,7 @@ class TaggableManagerTestCase(BaseTaggingTestCase):
ross
.
tags
.
add
(
"president"
)
self
.
assertEqual
(
unicode
(
self
.
taggeditem_model
.
objects
.
all
()[
0
]),
force_text
(
self
.
taggeditem_model
.
objects
.
all
()[
0
]),
"ross tagged with president"
)
...
...
@@ -328,10 +337,7 @@ class TaggableManagerOfficialTestCase(TaggableManagerTestCase):
pear
=
self
.
food_model
.
objects
.
create
(
name
=
"Pear"
)
pear
.
tags
.
add
(
"delicious"
)
self
.
assertEqual
(
map
(
lambda
o
:
o
.
pk
,
self
.
food_model
.
objects
.
filter
(
tags__official
=
False
)),
[
apple
.
pk
],
)
self
.
assertEqual
(
apple
,
self
.
food_model
.
objects
.
get
(
tags__official
=
False
))
class
TaggableFormTestCase
(
BaseTaggingTestCase
):
...
...
@@ -339,7 +345,7 @@ class TaggableFormTestCase(BaseTaggingTestCase):
food_model
=
Food
def
test_form
(
self
):
self
.
assertEqual
(
self
.
form_class
.
base_fields
.
keys
(
),
[
'name'
,
'tags'
])
self
.
assertEqual
(
list
(
self
.
form_class
.
base_fields
),
[
'name'
,
'tags'
])
f
=
self
.
form_class
({
'name'
:
'apple'
,
'tags'
:
'green, red, yummy'
})
self
.
assert_form_renders
(
f
,
"""<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" value="apple" maxlength="50" /></td></tr>
...
...
@@ -375,7 +381,7 @@ class TaggableFormTestCase(BaseTaggingTestCase):
tm
=
TaggableManager
(
verbose_name
=
'categories'
,
help_text
=
'Add some categories'
,
blank
=
True
)
ff
=
tm
.
formfield
()
self
.
assertEqual
(
ff
.
label
,
'Categories'
)
self
.
assertEqual
(
ff
.
help_text
,
u
'Add some categories'
)
self
.
assertEqual
(
ff
.
help_text
,
'Add some categories'
)
self
.
assertEqual
(
ff
.
required
,
False
)
self
.
assertEqual
(
ff
.
clean
(
""
),
[])
...
...
@@ -407,54 +413,54 @@ class TagStringParseTestCase(UnitTestCase):
"""
Test with simple space-delimited tags.
"""
self
.
assertEqual
(
parse_tags
(
'one'
),
[
u
'one'
])
self
.
assertEqual
(
parse_tags
(
'one two'
),
[
u'one'
,
u
'two'
])
self
.
assertEqual
(
parse_tags
(
'one two three'
),
[
u'one'
,
u'three'
,
u
'two'
])
self
.
assertEqual
(
parse_tags
(
'one one two two'
),
[
u'one'
,
u
'two'
])
self
.
assertEqual
(
parse_tags
(
'one'
),
[
'one'
])
self
.
assertEqual
(
parse_tags
(
'one two'
),
[
'one'
,
'two'
])
self
.
assertEqual
(
parse_tags
(
'one two three'
),
[
'one'
,
'three'
,
'two'
])
self
.
assertEqual
(
parse_tags
(
'one one two two'
),
[
'one'
,
'two'
])
def
test_with_comma_delimited_multiple_words
(
self
):
"""
Test with comma-delimited multiple words.
An unquoted comma in the input will trigger this.
"""
self
.
assertEqual
(
parse_tags
(
',one'
),
[
u
'one'
])
self
.
assertEqual
(
parse_tags
(
',one two'
),
[
u
'one two'
])
self
.
assertEqual
(
parse_tags
(
',one two three'
),
[
u
'one two three'
])
self
.
assertEqual
(
parse_tags
(
',one'
),
[
'one'
])
self
.
assertEqual
(
parse_tags
(
',one two'
),
[
'one two'
])
self
.
assertEqual
(
parse_tags
(
',one two three'
),
[
'one two three'
])
self
.
assertEqual
(
parse_tags
(
'a-one, a-two and a-three'
),
[
u'a-one'
,
u
'a-two and a-three'
])
[
'a-one'
,
'a-two and a-three'
])
def
test_with_double_quoted_multiple_words
(
self
):
"""
Test with double-quoted multiple words.
A completed quote will trigger this. Unclosed quotes are ignored.
"""
self
.
assertEqual
(
parse_tags
(
'"one'
),
[
u
'one'
])
self
.
assertEqual
(
parse_tags
(
'"one two'
),
[
u'one'
,
u
'two'
])
self
.
assertEqual
(
parse_tags
(
'"one two three'
),
[
u'one'
,
u'three'
,
u
'two'
])
self
.
assertEqual
(
parse_tags
(
'"one two"'
),
[
u
'one two'
])
self
.
assertEqual
(
parse_tags
(
'"one'
),
[
'one'
])
self
.
assertEqual
(
parse_tags
(
'"one two'
),
[
'one'
,
'two'
])
self
.
assertEqual
(
parse_tags
(
'"one two three'
),
[
'one'
,
'three'
,
'two'
])
self
.
assertEqual
(
parse_tags
(
'"one two"'
),
[
'one two'
])
self
.
assertEqual
(
parse_tags
(
'a-one "a-two and a-three"'
),
[
u'a-one'
,
u
'a-two and a-three'
])
[
'a-one'
,
'a-two and a-three'
])
def
test_with_no_loose_commas
(
self
):
"""
Test with no loose commas -- split on spaces.
"""
self
.
assertEqual
(
parse_tags
(
'one two "thr,ee"'
),
[
u'one'
,
u'thr,ee'
,
u
'two'
])
self
.
assertEqual
(
parse_tags
(
'one two "thr,ee"'
),
[
'one'
,
'thr,ee'
,
'two'
])
def
test_with_loose_commas
(
self
):
"""
Loose commas - split on commas
"""
self
.
assertEqual
(
parse_tags
(
'"one", two three'
),
[
u'one'
,
u
'two three'
])
self
.
assertEqual
(
parse_tags
(
'"one", two three'
),
[
'one'
,
'two three'
])
def
test_tags_with_double_quotes_can_contain_commas
(
self
):
"""
Double quotes can contain commas
"""
self
.
assertEqual
(
parse_tags
(
'a-one "a-two, and a-three"'
),
[
u'a-one'
,
u
'a-two, and a-three'
])
[
'a-one'
,
'a-two, and a-three'
])
self
.
assertEqual
(
parse_tags
(
'"two", one, one, two, "one"'
),
[
u'one'
,
u
'two'
])
[
'one'
,
'two'
])
def
test_with_naughty_input
(
self
):
"""
...
...
@@ -467,16 +473,16 @@ class TagStringParseTestCase(UnitTestCase):
self
.
assertEqual
(
parse_tags
(
'""'
),
[])
self
.
assertEqual
(
parse_tags
(
'"'
*
7
),
[])
self
.
assertEqual
(
parse_tags
(
',,,,,,'
),
[])
self
.
assertEqual
(
parse_tags
(
'",",",",",",","'
),
[
u
','
])
self
.
assertEqual
(
parse_tags
(
'",",",",",",","'
),
[
','
])
self
.
assertEqual
(
parse_tags
(
'a-one "a-two" and "a-three'
),
[
u'a-one'
,
u'a-three'
,
u'a-two'
,
u
'and'
])
[
'a-one'
,
'a-three'
,
'a-two'
,
'and'
])
def
test_recreation_of_tag_list_string_representations
(
self
):
plain
=
Tag
.
objects
.
create
(
name
=
'plain'
)
spaces
=
Tag
.
objects
.
create
(
name
=
'spa ces'
)
comma
=
Tag
.
objects
.
create
(
name
=
'com,ma'
)
self
.
assertEqual
(
edit_string_for_tags
([
plain
]),
u
'plain'
)
self
.
assertEqual
(
edit_string_for_tags
([
plain
,
spaces
]),
u
'"spa ces", plain'
)
self
.
assertEqual
(
edit_string_for_tags
([
plain
,
spaces
,
comma
]),
u
'"com,ma", "spa ces", plain'
)
self
.
assertEqual
(
edit_string_for_tags
([
plain
,
comma
]),
u
'"com,ma", plain'
)
self
.
assertEqual
(
edit_string_for_tags
([
comma
,
spaces
]),
u
'"com,ma", "spa ces"'
)
self
.
assertEqual
(
edit_string_for_tags
([
plain
]),
'plain'
)
self
.
assertEqual
(
edit_string_for_tags
([
plain
,
spaces
]),
'"spa ces", plain'
)
self
.
assertEqual
(
edit_string_for_tags
([
plain
,
spaces
,
comma
]),
'"com,ma", "spa ces", plain'
)
self
.
assertEqual
(
edit_string_for_tags
([
plain
,
comma
]),
'"com,ma", plain'
)
self
.
assertEqual
(
edit_string_for_tags
([
comma
,
spaces
]),
'"com,ma", "spa ces"'
)
taggit/utils.py
View file @
fb2d1cc4
from
django.utils.encoding
import
force_unicode
from
__future__
import
unicode_literals
from
django.utils.encoding
import
force_text
from
django.utils.functional
import
wraps
from
django.utils
import
six
def
parse_tags
(
tagstring
):
...
...
@@ -16,13 +19,13 @@ def parse_tags(tagstring):
if
not
tagstring
:
return
[]
tagstring
=
force_
unicode
(
tagstring
)
tagstring
=
force_
text
(
tagstring
)
# Special case - if there are no commas or double quotes in the
# input, we don't *do* a recall... I mean, we know we only need to
# split on spaces.
if
u','
not
in
tagstring
and
u
'"'
not
in
tagstring
:
words
=
list
(
set
(
split_strip
(
tagstring
,
u
' '
)))
if
','
not
in
tagstring
and
'"'
not
in
tagstring
:
words
=
list
(
set
(
split_strip
(
tagstring
,
' '
)))
words
.
sort
()
return
words
...
...
@@ -36,39 +39,39 @@ def parse_tags(tagstring):
i
=
iter
(
tagstring
)
try
:
while
True
:
c
=
i
.
next
(
)
if
c
==
u
'"'
:
c
=
six
.
next
(
i
)
if
c
==
'"'
:
if
buffer
:
to_be_split
.
append
(
u
''
.
join
(
buffer
))
to_be_split
.
append
(
''
.
join
(
buffer
))
buffer
=
[]
# Find the matching quote
open_quote
=
True
c
=
i
.
next
(
)
while
c
!=
u
'"'
:
c
=
six
.
next
(
i
)
while
c
!=
'"'
:
buffer
.
append
(
c
)
c
=
i
.
next
(
)
c
=
six
.
next
(
i
)
if
buffer
:
word
=
u
''
.
join
(
buffer
)
.
strip
()
word
=
''
.
join
(
buffer
)
.
strip
()
if
word
:
words
.
append
(
word
)
buffer
=
[]
open_quote
=
False
else
:
if
not
saw_loose_comma
and
c
==
u
','
:
if
not
saw_loose_comma
and
c
==
','
:
saw_loose_comma
=
True
buffer
.
append
(
c
)
except
StopIteration
:
# If we were parsing an open quote which was never closed treat
# the buffer as unquoted.
if
buffer
:
if
open_quote
and
u
','
in
buffer
:
if
open_quote
and
','
in
buffer
:
saw_loose_comma
=
True
to_be_split
.
append
(
u
''
.
join
(
buffer
))
to_be_split
.
append
(
''
.
join
(
buffer
))
if
to_be_split
:
if
saw_loose_comma
:
delimiter
=
u
','
delimiter
=
','
else
:
delimiter
=
u
' '
delimiter
=
' '
for
chunk
in
to_be_split
:
words
.
extend
(
split_strip
(
chunk
,
delimiter
))
words
=
list
(
set
(
words
))
...
...
@@ -76,7 +79,7 @@ def parse_tags(tagstring):
return
words
def
split_strip
(
string
,
delimiter
=
u
','
):
def
split_strip
(
string
,
delimiter
=
','
):
"""
Splits ``string`` on ``delimiter``, stripping each resulting string
and returning a list of non-empty strings.
...
...
@@ -110,11 +113,11 @@ def edit_string_for_tags(tags):
names
=
[]
for
tag
in
tags
:
name
=
tag
.
name
if
u','
in
name
or
u
' '
in
name
:
if
','
in
name
or
' '
in
name
:
names
.
append
(
'"
%
s"'
%
name
)
else
:
names
.
append
(
name
)
return
u
', '
.
join
(
sorted
(
names
))
return
', '
.
join
(
sorted
(
names
))
def
require_instance_manager
(
func
):
...
...
taggit/views.py
View file @
fb2d1cc4
from
__future__
import
unicode_literals
from
django.contrib.contenttypes.models
import
ContentType
from
django.shortcuts
import
get_object_or_404
from
django.views.generic.list_detail
import
object_list
...
...
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