Commit 57fca9ab by Alex Gaynor

Merge branch 'master' into api-refactor

parents 6adfb70b 4b0d0dc4
...@@ -10,3 +10,5 @@ Rob Hudson <rob@cogit8.org> ...@@ -10,3 +10,5 @@ Rob Hudson <rob@cogit8.org>
Carl Meyer <carl@oddbird.net> Carl Meyer <carl@oddbird.net>
Frank Wiles Frank Wiles
Jonathan Buchanan Jonathan Buchanan
idle sign <idlesign@yandex.ru>
Charles Leifer
...@@ -4,53 +4,49 @@ The API ...@@ -4,53 +4,49 @@ The API
After you've got your ``TaggableManager`` added to your model you can start After you've got your ``TaggableManager`` added to your model you can start
playing around with the API. playing around with the API.
.. class:: TaggableManager
add(*tags) .. method:: add(*tags)
~~~~~~~~~~
This adds tags to an object. The tags can be either ``Tag`` instances, or This adds tags to an object. The tags can be either ``Tag`` instances, or
strings:: strings::
>>> apple.tags.all() >>> apple.tags.all()
[] []
>>> apple.tags.add("red", "green", "fruit") >>> apple.tags.add("red", "green", "fruit")
.. method:: remove(*tags)
remove(*tags) Removes a tag from an object. No exception is raised if the object
~~~~~~~~~~~~~ doesn't have that tag.
Removes a tag from an object. No exception is raised if the object doesn't have .. method:: clear()
that tag.
clear() Removes all tags from an object.
~~~~~~~
Removes all tags from an object. .. method:: set(*tags)
set(*tags) Removes all the current tags and then adds the specified tags to the
~~~~~~~~~~ object.
Removes all the current tags and then adds the specified tags to the object. .. method: most_common()
most_common() Returns a ``QuerySet`` of all tags, annotated with the number of times
~~~~~~~~~~~~~ they appear, available as the ``num_times`` attribute on each tag. The
``QuerySet``is ordered by ``num_times``, descending. The ``QuerySet``
is lazily evaluated, and can be sliced efficiently.
Returns a ``QuerySet`` of all tags, annotated with the number of times they .. method:: similar_objects()
appear, available as the ``num_times`` attribute on each tag. The ``QuerySet``
is ordered by ``num_times``, descending. The ``QuerySet`` is lazily evaluated,
and can be sliced efficiently.
similar_objects() Returns a list (not a lazy ``QuerySet``) of other objects tagged
~~~~~~~~~~~~~~~~~ similarly to this one, ordered with most similar first. Each object in
the list is decorated with a ``similar_tags`` attribute, the number of
tags it shares with this object.
Returns a list (not a lazy ``QuerySet``) of other objects tagged similarly to If the model is using generic tagging (the default), this method
this one, ordered with most similar first. Each object in the list is decorated searches tagged objects from all classes. If you are querying on a
with a ``similar_tags`` attribute, the number of tags it shares with this model with its own tagging through table, only other instances of the
object. same model will be returned.
If using generic tagging (the default), this method searches all tagged
objects. If querying on a model with its own tagging through table, only other
instances of the same model will be returned.
Filtering Filtering
~~~~~~~~~ ~~~~~~~~~
...@@ -62,3 +58,13 @@ like so:: ...@@ -62,3 +58,13 @@ like so::
>>> Food.objects.filter(tags__in=["delicious"]) >>> Food.objects.filter(tags__in=["delicious"])
[<Food: apple>, <Food: pear>, <Food: plum>] [<Food: apple>, <Food: pear>, <Food: plum>]
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: apple>, <Food: apple>]
>>> Food.objects.filter(tags__in=["delicious", "red"]).distinct()
[<Food: apple>]
Changelog Changelog
========= =========
0.9.0
~~~~~
Unreleased.
* Added a Hebrew locale.
* 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/>`_.
0.8.0 0.8.0
~~~~~ ~~~~~
......
...@@ -4,7 +4,7 @@ Tags in forms ...@@ -4,7 +4,7 @@ Tags in forms
============= =============
The ``TaggableManager`` will show up automatically as a field in a The ``TaggableManager`` will show up automatically as a field in a
``ModelForm`` or in the admin. Tag input via the form field is parsed ``ModelForm`` or in the admin. Tags input via the form field are parsed
as follows: as follows:
* If the input doesn't contain any commas or double quotes, it is simply * If the input doesn't contain any commas or double quotes, it is simply
...@@ -33,3 +33,19 @@ apple "ball cat" dog ``["apple", "ball cat", "dog"]`` No commas, so space del ...@@ -33,3 +33,19 @@ apple "ball cat" dog ``["apple", "ball cat", "dog"]`` No commas, so space del
"apple" "ball dog ``["apple", "ball", "dog"]`` Unclosed double quote is ignored "apple" "ball dog ``["apple", "ball", "dog"]`` Unclosed double quote is ignored
====================== ================================= ================================================ ====================== ================================= ================================================
``commit=False``
~~~~~~~~~~~~~~~~
If, when saving a form, you use the ``commit=False`` option you'll need to call
``save_m2m()`` on the form after you save the object, just as you would for a
form with normal many to many fields on it::
if request.method == "POST":
form = MyFormClass(requets.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
# Without this next line the tags won't be saved.
form.save_m2m()
...@@ -11,6 +11,7 @@ if not settings.configured: ...@@ -11,6 +11,7 @@ if not settings.configured:
INSTALLED_APPS=[ INSTALLED_APPS=[
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'taggit', 'taggit',
'taggit.contrib.suggest',
'taggit.tests', 'taggit.tests',
] ]
) )
...@@ -20,7 +21,7 @@ from django.test.simple import run_tests ...@@ -20,7 +21,7 @@ from django.test.simple import run_tests
def runtests(*test_args): def runtests(*test_args):
if not test_args: if not test_args:
test_args = ['tests'] 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, failfast=True) failures = run_tests(test_args, verbosity=1, interactive=True, failfast=True)
......
from taggit.contrib.suggest.tests.tests import SuggestCase
DATABASE_ENGINE = 'sqlite3'
INSTALLED_APPS = [
'django.contrib.contenttypes',
'taggit',
'taggit.tests',
'taggit.contrib.suggest',
]
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: Django Taggit\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-06-26 12:47-0500\n"
"PO-Revision-Date: 2010-06-26 12:54-0600\n"
"Last-Translator: Alex <alex.gaynor@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: forms.py:20
msgid "Please provide a comma-separated list of tags."
msgstr "נא לספק רשימה של תגים מופרדת עם פסיקים."
#: managers.py:41
#: managers.py:113
#: models.py:18
msgid "Tags"
msgstr "תגיות"
#: managers.py:114
msgid "A comma-separated list of tags."
msgstr "רשימה של תגים מופרדת עם פסיקים."
#: models.py:10
msgid "Name"
msgstr "שם"
#: models.py:11
msgid "Slug"
msgstr ""
#: models.py:17
msgid "Tag"
msgstr "תג"
#: models.py:56
#, python-format
msgid "%(object)s tagged with %(tag)s"
msgstr "%(object)s מתויג עם %(tag)s"
#: models.py:86
msgid "Object id"
msgstr ""
#: models.py:87
msgid "Content type"
msgstr ""
#: models.py:92
msgid "Tagged Item"
msgstr ""
#: models.py:93
msgid "Tagged Items"
msgstr ""
#: contrib/suggest/models.py:57
msgid "Enter a valid Regular Expression. To make it case-insensitive include \"(?i)\" in your expression."
msgstr ""
...@@ -83,7 +83,7 @@ class TaggedItemBase(models.Model): ...@@ -83,7 +83,7 @@ class TaggedItemBase(models.Model):
class TaggedItem(TaggedItemBase): class TaggedItem(TaggedItemBase):
object_id = models.IntegerField(verbose_name=_('Object id')) object_id = models.IntegerField(verbose_name=_('Object id'), db_index=True)
content_type = models.ForeignKey(ContentType, verbose_name=_('Content type'), content_type = models.ForeignKey(ContentType, verbose_name=_('Content type'),
related_name="tagged_items") related_name="tagged_items")
content_object = GenericForeignKey() content_object = GenericForeignKey()
......
...@@ -235,11 +235,11 @@ class TaggableFormTestCase(BaseTaggingTestCase): ...@@ -235,11 +235,11 @@ class TaggableFormTestCase(BaseTaggingTestCase):
self.assert_tags_equal(raspberry.tags.all(), []) self.assert_tags_equal(raspberry.tags.all(), [])
f = self.form_class(instance=apple) f = self.form_class(instance=apple)
self.assertEqual(str(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>\n<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value="delicious green red yummy" id="id_tags" /><br />A comma-separated list of tags.</td></tr>""") self.assertEqual(str(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>\n<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value="delicious, green, red, yummy" id="id_tags" /><br />A comma-separated list of tags.</td></tr>""")
apple.tags.add('has,comma') apple.tags.add('has,comma')
f = self.form_class(instance=apple) f = self.form_class(instance=apple)
self.assertEqual(str(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>\n<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value="&quot;has,comma&quot; delicious green red yummy" id="id_tags" /><br />A comma-separated list of tags.</td></tr>""") self.assertEqual(str(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>\n<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value="&quot;has,comma&quot;, delicious, green, red, yummy" id="id_tags" /><br />A comma-separated list of tags.</td></tr>""")
class TaggableFormDirectTestCase(TaggableFormTestCase): class TaggableFormDirectTestCase(TaggableFormTestCase):
...@@ -332,5 +332,5 @@ class TagStringParseTestCase(UnitTestCase): ...@@ -332,5 +332,5 @@ class TagStringParseTestCase(UnitTestCase):
self.assertEqual(edit_string_for_tags([plain]), u'plain') self.assertEqual(edit_string_for_tags([plain]), u'plain')
self.assertEqual(edit_string_for_tags([plain, spaces]), u'plain, spa ces') self.assertEqual(edit_string_for_tags([plain, spaces]), u'plain, spa ces')
self.assertEqual(edit_string_for_tags([plain, spaces, comma]), u'"com,ma", plain, spa ces') self.assertEqual(edit_string_for_tags([plain, spaces, comma]), u'"com,ma", plain, spa ces')
self.assertEqual(edit_string_for_tags([plain, comma]), u'"com,ma" 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([comma, spaces]), u'"com,ma", spa ces')
...@@ -108,21 +108,13 @@ def edit_string_for_tags(tags): ...@@ -108,21 +108,13 @@ def edit_string_for_tags(tags):
<http://django-tagging.googlecode.com/>`_ <http://django-tagging.googlecode.com/>`_
""" """
names = [] names = []
use_commas = False
for tag in tags: for tag in tags:
name = tag.name name = tag.name
if u',' in name: if u',' in name:
names.append('"%s"' % name) names.append('"%s"' % name)
continue
elif u' ' in name:
if not use_commas:
use_commas = True
names.append(name)
if use_commas:
glue = u', '
else: else:
glue = u' ' names.append(name)
return glue.join(sorted(names)) return u', '.join(sorted(names))
def require_instance_manager(func): def require_instance_manager(func):
......
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