django-taggit (0.17.4-1) unstable; urgency=medium
* New upstream release. # imported from the archive
This commit is contained in:
commit
0c6b364e5e
|
@ -0,0 +1,15 @@
|
|||
django-taggit was originally created by Alex Gaynor.
|
||||
|
||||
The following is a list of much appreciated contributors:
|
||||
|
||||
Nathan Borror <nathan@playgroundblues.com>
|
||||
fakeempire <adam@fakeempire.com>
|
||||
Ben Firshman <ben@firshman.co.uk>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Rob Hudson <rob@cogit8.org>
|
||||
Carl Meyer <carl@oddbird.net>
|
||||
Frank Wiles
|
||||
Jonathan Buchanan
|
||||
idle sign <idlesign@yandex.ru>
|
||||
Charles Leifer
|
||||
Florian Apolloner <apollo13@apolloner.eu>
|
|
@ -0,0 +1,163 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
0.17.4 (2015-11-25)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Allows custom Through Model with GenericForeignKey
|
||||
* https://github.com/alex/django-taggit/pull/359
|
||||
|
||||
0.17.3 (2015-10-26)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Silence Django 1.9 warning about on_delete
|
||||
* https://github.com/alex/django-taggit/pull/338
|
||||
|
||||
0.17.2 (2015-10-25)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Django 1.9 beta compatibility
|
||||
* https://github.com/alex/django-taggit/pull/352
|
||||
|
||||
0.17.1 (2015-09-10)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Fix unknown column `object_id` issue with Django 1.6+
|
||||
* https://github.com/alex/django-taggit/pull/305
|
||||
|
||||
0.17.0 (2015-08-14)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Database index added on TaggedItem fields content_type & object_id
|
||||
* https://github.com/alex/django-taggit/pull/319
|
||||
|
||||
0.16.4 (2015-08-13)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Access default manager via class instead of instance
|
||||
* https://github.com/alex/django-taggit/pull/335
|
||||
|
||||
0.16.3 (2015-08-08)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Prevent IntegrityError with custom TagBase classes
|
||||
* https://github.com/alex/django-taggit/pull/334
|
||||
|
||||
0.16.2 (2015-07-13)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Fix an admin bug related to the `Manager` property `through_fields`
|
||||
* https://github.com/alex/django-taggit/pull/328
|
||||
|
||||
0.16.1 (2015-07-09)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Fix bug that assumed all primary keys are named 'id'
|
||||
* https://github.com/alex/django-taggit/pull/322
|
||||
|
||||
0.16.0 (2015-07-04)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Add option to allow case-insensitive tags
|
||||
* https://github.com/alex/django-taggit/pull/325
|
||||
|
||||
0.15.0 (2015-06-23)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Fix wrong slugs for non-latin chars
|
||||
* Only works if optional GPL dependency (unidecode) is installed
|
||||
* https://github.com/alex/django-taggit/pull/315
|
||||
* https://github.com/alex/django-taggit/pull/273
|
||||
|
||||
0.14.0 (2015-04-26)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Prevent extra JOIN when prefetching
|
||||
* https://github.com/alex/django-taggit/pull/275
|
||||
* Prevent _meta warnings with Django 1.8
|
||||
* https://github.com/alex/django-taggit/pull/299
|
||||
|
||||
0.13.0 (2015-04-02)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Django 1.8 support
|
||||
* https://github.com/alex/django-taggit/pull/297
|
||||
|
||||
0.12.3 (2015-03-03)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Specify that the internal type of the TaggitManager is a ManyToManyField
|
||||
|
||||
0.12.2 (2014-21-09)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Fixed 1.7 migrations.
|
||||
|
||||
0.12.1 (2014-10-08)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Final (hopefully) fixes for the upcoming Django 1.7 release.
|
||||
* Added Japanese translation.
|
||||
|
||||
0.12.0 (2014-20-04)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* **Backwards incompatible:** Support for Django 1.7 migrations. South users
|
||||
have to set ``SOUTH_MIGRATION_MODULES`` to use ``taggit.south_migrations``
|
||||
for taggit.
|
||||
* **Backwards incompatible:** Django's new transaction handling is used on
|
||||
Django 1.6 and newer.
|
||||
* **Backwards incompatible:** ``Tag.save`` got changed to opportunistically
|
||||
try to save the tag and if that fails fall back to selecting existing
|
||||
similar tags and retry -- if that fails too an ``IntegrityError`` is
|
||||
raised by the database, your app will have to handle that.
|
||||
* Added Italian and Esperanto translations.
|
||||
|
||||
0.11.2 (2013-13-12)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Forbid multiple TaggableManagers via generic foreign keys.
|
||||
|
||||
0.11.1 (2013-25-11)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Fixed support for Django 1.4 and 1.5.
|
||||
|
||||
0.11.0 (2013-25-11)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
* Added support for prefetch_related on tags fields.
|
||||
* Fixed support for Django 1.7.
|
||||
* Made the tagging relations unserializeable again.
|
||||
* Allow more than one TaggableManager on models (assuming concrete FKs are
|
||||
used for the relations).
|
||||
|
||||
0.10.0 (2013-17-08)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Support for Django 1.6 and 1.7.
|
||||
* Python3 support
|
||||
* **Backwards incompatible:** Dropped support for Django < 1.4.5.
|
||||
* Tag names are unique now, use the provided South migrations to upgrade.
|
||||
|
||||
0.9.2 (2011-01-17)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* **Backwards incompatible:** Forms containing a :class:`TaggableManager` by
|
||||
default now require tags, to change this provide ``blank=True`` to the
|
||||
:class:`TaggableManager`.
|
||||
* Now works with Django 1.3 (as of beta-1).
|
||||
|
||||
0.9.0 (2010-09-22)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* 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/>`_.
|
||||
* 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"])``.
|
||||
* Added a German locale.
|
||||
* Added a Dutch locale.
|
||||
* Removed ``taggit.contrib.suggest``, it now lives in an external application,
|
||||
see :doc:`external_apps` for more information.
|
||||
|
||||
0.8.0 (2010-06-22)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fixed querying for objects using ``exclude(tags__in=tags)``.
|
||||
* Marked strings as translatable.
|
||||
|
||||
* Added a Russian translation.
|
||||
* Created a `mailing list <http://groups.google.com/group/django-taggit>`_.
|
||||
* Smarter tagstring parsing for form field; ported from Jonathan
|
||||
Buchanan's `django-tagging
|
||||
<http://django-tagging.googlecode.com>`_. Now supports tags
|
||||
containing commas. See :ref:`tags-in-forms` for details.
|
||||
* Switched to using savepoints around the slug generation for tags. This
|
||||
ensures that it works fine on databases (such as Postgres) which dirty a
|
||||
transaction with an ``IntegrityError``.
|
||||
* Added Python 2.4 compatibility.
|
||||
* Added Django 1.1 compatibility.
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) Alex Gaynor and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of django-taggit nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,9 @@
|
|||
include AUTHORS
|
||||
include CHANGELOG.txt
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include tox.ini
|
||||
include runtests.py
|
||||
recursive-include docs *
|
||||
recursive-include taggit/locale *
|
||||
recursive-include tests *
|
|
@ -0,0 +1,62 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: django-taggit
|
||||
Version: 0.17.4
|
||||
Summary: django-taggit is a reusable Django application for simple tagging.
|
||||
Home-page: http://github.com/alex/django-taggit/tree/master
|
||||
Author: Alex Gaynor
|
||||
Author-email: alex.gaynor@gmail.com
|
||||
License: BSD
|
||||
Description: django-taggit
|
||||
=============
|
||||
|
||||
``django-taggit`` a simpler approach to tagging with Django. Add ``"taggit"`` to your
|
||||
``INSTALLED_APPS`` then just add a TaggableManager to your model and go:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from django.db import models
|
||||
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
class Food(models.Model):
|
||||
# ... fields here
|
||||
|
||||
tags = TaggableManager()
|
||||
|
||||
|
||||
Then you can use the API like so:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> apple = Food.objects.create(name="apple")
|
||||
>>> apple.tags.add("red", "green", "delicious")
|
||||
>>> apple.tags.all()
|
||||
[<Tag: red>, <Tag: green>, <Tag: delicious>]
|
||||
>>> apple.tags.remove("green")
|
||||
>>> apple.tags.all()
|
||||
[<Tag: red>, <Tag: delicious>]
|
||||
>>> Food.objects.filter(tags__name__in=["red"])
|
||||
[<Food: apple>, <Food: cherry>]
|
||||
|
||||
Tags will show up for you automatically in forms and the admin.
|
||||
|
||||
``django-taggit`` requires Django 1.4.5 or greater.
|
||||
|
||||
For more info check out the `documentation <https://django-taggit.readthedocs.org/en/latest/>`_. And for questions about usage or
|
||||
development you can contact the
|
||||
`mailinglist <http://groups.google.com/group/django-taggit>`_.
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Framework :: Django
|
|
@ -0,0 +1,39 @@
|
|||
django-taggit
|
||||
=============
|
||||
|
||||
``django-taggit`` a simpler approach to tagging with Django. Add ``"taggit"`` to your
|
||||
``INSTALLED_APPS`` then just add a TaggableManager to your model and go:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from django.db import models
|
||||
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
class Food(models.Model):
|
||||
# ... fields here
|
||||
|
||||
tags = TaggableManager()
|
||||
|
||||
|
||||
Then you can use the API like so:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> apple = Food.objects.create(name="apple")
|
||||
>>> apple.tags.add("red", "green", "delicious")
|
||||
>>> apple.tags.all()
|
||||
[<Tag: red>, <Tag: green>, <Tag: delicious>]
|
||||
>>> apple.tags.remove("green")
|
||||
>>> apple.tags.all()
|
||||
[<Tag: red>, <Tag: delicious>]
|
||||
>>> Food.objects.filter(tags__name__in=["red"])
|
||||
[<Food: apple>, <Food: cherry>]
|
||||
|
||||
Tags will show up for you automatically in forms and the admin.
|
||||
|
||||
``django-taggit`` requires Django 1.4.5 or greater.
|
||||
|
||||
For more info check out the `documentation <https://django-taggit.readthedocs.org/en/latest/>`_. And for questions about usage or
|
||||
development you can contact the
|
||||
`mailinglist <http://groups.google.com/group/django-taggit>`_.
|
|
@ -0,0 +1,97 @@
|
|||
django-taggit (0.17.4-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Fri, 27 Nov 2015 08:37:05 +0100
|
||||
|
||||
django-taggit (0.17.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
- Django 1.9 compatibility.
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Thu, 29 Oct 2015 14:44:36 +0100
|
||||
|
||||
django-taggit (0.17.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Thu, 17 Sep 2015 09:47:25 +0200
|
||||
|
||||
django-taggit (0.17.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* Update debian/watch.
|
||||
* Add python 3 package.
|
||||
* Run tests during build.
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Mon, 24 Aug 2015 13:44:42 +0200
|
||||
|
||||
django-taggit (0.12.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
- Fixed 1.7 migrations.
|
||||
* Bump standards to 3.9.6.
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Thu, 25 Sep 2014 09:20:46 +0200
|
||||
|
||||
django-taggit (0.12.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
- Complete support for Django 1.7 (Closes: #755614).
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Mon, 11 Aug 2014 09:55:45 +0200
|
||||
|
||||
django-taggit (0.12-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
- Backwards incompatible: Support for Django 1.7 migrations. South users
|
||||
have to set SOUTH_MIGRATION_MODULES to use taggit.south_migrations
|
||||
for taggit.
|
||||
- Backwards incompatible: Django's new transaction handling is used on
|
||||
Django 1.6 and newer.
|
||||
- Backwards incompatible: Tag.save got changed to opportunistically
|
||||
try to save the tag and if that fails fall back to selecting existing
|
||||
similar tags and retry -- if that fails too an IntegrityError is
|
||||
raised by the database, your app will have to handle that.
|
||||
- Added Italian and Esperanto translations.
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Tue, 29 Apr 2014 10:05:29 +0200
|
||||
|
||||
django-taggit (0.11.2-2) unstable; urgency=medium
|
||||
|
||||
* Bump standards to 3.9.5.
|
||||
* Really do not ship tests in module without namespace (Closes: #738210).
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Mon, 10 Feb 2014 09:36:44 +0100
|
||||
|
||||
django-taggit (0.11.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
* Do not ship tests in module without namespace (Closes: #733729).
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Fri, 03 Jan 2014 10:20:12 +0100
|
||||
|
||||
django-taggit (0.11.1-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
* Bump standards to 3.9.5.
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Mon, 09 Dec 2013 14:00:12 +0100
|
||||
|
||||
django-taggit (0.10a1-3) unstable; urgency=low
|
||||
|
||||
* Fixed Vcs-* fields in debian/control (Closes: #721524).
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Mon, 09 Sep 2013 09:36:36 +0200
|
||||
|
||||
django-taggit (0.10a1-2) unstable; urgency=low
|
||||
|
||||
* Fix typo in package name.
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Thu, 15 Aug 2013 16:48:21 +0200
|
||||
|
||||
django-taggit (0.10a1-1) unstable; urgency=low
|
||||
|
||||
* Initial release. (Closes: #719809)
|
||||
|
||||
-- Michal Čihař <nijel@debian.org> Thu, 15 Aug 2013 16:31:05 +0200
|
|
@ -0,0 +1 @@
|
|||
9
|
|
@ -0,0 +1,41 @@
|
|||
Source: django-taggit
|
||||
Section: python
|
||||
Priority: optional
|
||||
Maintainer: Michal Čihař <nijel@debian.org>
|
||||
Uploaders: Python Applications Packaging Team <python-apps-team@lists.alioth.debian.org>
|
||||
Build-Depends: debhelper (>= 9)
|
||||
Build-Depends-Indep: python (>= 2.6.6-3), python-setuptools, python3-setuptools, python3-all, dh-python, python-django, python3-django
|
||||
Standards-Version: 3.9.6
|
||||
Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/django-taggit.git
|
||||
Vcs-Git: git://anonscm.debian.org/collab-maint/django-taggit.git
|
||||
Homepage: https://github.com/alex/django-taggit
|
||||
X-Python-Version: >= 2.6
|
||||
X-Python3-Version: >= 3.2
|
||||
|
||||
Package: python-django-taggit
|
||||
Architecture: all
|
||||
Depends: ${python:Depends}, ${misc:Depends}, python-django
|
||||
Breaks: ${python:Breaks}
|
||||
Description: simple tagging for Django (Python 2)
|
||||
This is a generic tagging application for Django, which allows
|
||||
association of a number of tags with any Model instance and makes
|
||||
retrieval of tags simple.
|
||||
.
|
||||
django-taggit a simpler approach to tagging with Django. Add "taggit" to your
|
||||
INSTALLED_APPS then just add a TaggableManager to your model.
|
||||
.
|
||||
This package installs the library for Python 2.
|
||||
|
||||
Package: python3-django-taggit
|
||||
Architecture: all
|
||||
Depends: ${python3:Depends}, ${misc:Depends}, python3-django
|
||||
Breaks: ${python3:Breaks}
|
||||
Description: simple tagging for Django (Python 3)
|
||||
This is a generic tagging application for Django, which allows
|
||||
association of a number of tags with any Model instance and makes
|
||||
retrieval of tags simple.
|
||||
.
|
||||
django-taggit a simpler approach to tagging with Django. Add "taggit" to your
|
||||
INSTALLED_APPS then just add a TaggableManager to your model.
|
||||
.
|
||||
This package installs the library for Python 3.
|
|
@ -0,0 +1,54 @@
|
|||
This package was debianized by Michal Čihař <nijel@debian.org> on
|
||||
Thu, 15 Aug 2013 16:18:02 +0200.
|
||||
|
||||
It was downloaded from <https://pypi.python.org/pypi/django-taggit>.
|
||||
|
||||
Upstream Authors:
|
||||
|
||||
Nathan Borror <nathan@playgroundblues.com>
|
||||
fakeempire <adam@fakeempire.com>
|
||||
Ben Firshman <ben@firshman.co.uk>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Rob Hudson <rob@cogit8.org>
|
||||
Carl Meyer <carl@oddbird.net>
|
||||
Frank Wiles
|
||||
Jonathan Buchanan
|
||||
idle sign <idlesign@yandex.ru>
|
||||
Charles Leifer
|
||||
Florian Apolloner <apollo13@apolloner.eu>
|
||||
|
||||
Copyright:
|
||||
|
||||
Copyright (c) Alex Gaynor and individual contributors.
|
||||
|
||||
License:
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of django-taggit nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License can be found in `/usr/share/common-licenses/GPL-2'.
|
||||
|
||||
The Debian packaging is Copyright (C) 2013, Michal Čihař
|
||||
<nijel@debian.org> and is licensed under the GPL, see above.
|
|
@ -0,0 +1,5 @@
|
|||
# Configuration file for git-buildpackage and friends
|
||||
|
||||
[DEFAULT]
|
||||
sign-tags = True
|
||||
pristine-tar = True
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
export PYBUILD_NAME=django-taggit
|
||||
%:
|
||||
dh $@ --fail-missing --with python2,python3 --buildsystem=pybuild
|
||||
|
||||
override_dh_auto_test:
|
||||
python2 runtests.py
|
||||
python3 runtests.py
|
|
@ -0,0 +1 @@
|
|||
3.0 (quilt)
|
|
@ -0,0 +1,2 @@
|
|||
version=3
|
||||
http://pypi.debian.net/django-taggit/django-taggit-(.*)\.tar\.gz
|
|
@ -0,0 +1,62 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: django-taggit
|
||||
Version: 0.17.4
|
||||
Summary: django-taggit is a reusable Django application for simple tagging.
|
||||
Home-page: http://github.com/alex/django-taggit/tree/master
|
||||
Author: Alex Gaynor
|
||||
Author-email: alex.gaynor@gmail.com
|
||||
License: BSD
|
||||
Description: django-taggit
|
||||
=============
|
||||
|
||||
``django-taggit`` a simpler approach to tagging with Django. Add ``"taggit"`` to your
|
||||
``INSTALLED_APPS`` then just add a TaggableManager to your model and go:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from django.db import models
|
||||
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
class Food(models.Model):
|
||||
# ... fields here
|
||||
|
||||
tags = TaggableManager()
|
||||
|
||||
|
||||
Then you can use the API like so:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> apple = Food.objects.create(name="apple")
|
||||
>>> apple.tags.add("red", "green", "delicious")
|
||||
>>> apple.tags.all()
|
||||
[<Tag: red>, <Tag: green>, <Tag: delicious>]
|
||||
>>> apple.tags.remove("green")
|
||||
>>> apple.tags.all()
|
||||
[<Tag: red>, <Tag: delicious>]
|
||||
>>> Food.objects.filter(tags__name__in=["red"])
|
||||
[<Food: apple>, <Food: cherry>]
|
||||
|
||||
Tags will show up for you automatically in forms and the admin.
|
||||
|
||||
``django-taggit`` requires Django 1.4.5 or greater.
|
||||
|
||||
For more info check out the `documentation <https://django-taggit.readthedocs.org/en/latest/>`_. And for questions about usage or
|
||||
development you can contact the
|
||||
`mailinglist <http://groups.google.com/group/django-taggit>`_.
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Framework :: Django
|
|
@ -0,0 +1,64 @@
|
|||
AUTHORS
|
||||
CHANGELOG.txt
|
||||
LICENSE
|
||||
MANIFEST.in
|
||||
README.rst
|
||||
runtests.py
|
||||
setup.cfg
|
||||
setup.py
|
||||
tox.ini
|
||||
django_taggit.egg-info/PKG-INFO
|
||||
django_taggit.egg-info/SOURCES.txt
|
||||
django_taggit.egg-info/dependency_links.txt
|
||||
django_taggit.egg-info/not-zip-safe
|
||||
django_taggit.egg-info/top_level.txt
|
||||
docs/Makefile
|
||||
docs/admin.txt
|
||||
docs/api.txt
|
||||
docs/changelog.txt
|
||||
docs/conf.py
|
||||
docs/custom_tagging.txt
|
||||
docs/external_apps.txt
|
||||
docs/forms.txt
|
||||
docs/getting_started.txt
|
||||
docs/index.txt
|
||||
taggit/__init__.py
|
||||
taggit/admin.py
|
||||
taggit/forms.py
|
||||
taggit/managers.py
|
||||
taggit/models.py
|
||||
taggit/utils.py
|
||||
taggit/views.py
|
||||
taggit/locale/cs/LC_MESSAGES/django.mo
|
||||
taggit/locale/cs/LC_MESSAGES/django.po
|
||||
taggit/locale/de/LC_MESSAGES/django.mo
|
||||
taggit/locale/de/LC_MESSAGES/django.po
|
||||
taggit/locale/en/LC_MESSAGES/django.po
|
||||
taggit/locale/eo/LC_MESSAGES/django.mo
|
||||
taggit/locale/eo/LC_MESSAGES/django.po
|
||||
taggit/locale/he/LC_MESSAGES/django.mo
|
||||
taggit/locale/he/LC_MESSAGES/django.po
|
||||
taggit/locale/it/LC_MESSAGES/django.mo
|
||||
taggit/locale/it/LC_MESSAGES/django.po
|
||||
taggit/locale/ja/LC_MESSAGES/django.mo
|
||||
taggit/locale/ja/LC_MESSAGES/django.po
|
||||
taggit/locale/nb/LC_MESSAGES/django.mo
|
||||
taggit/locale/nb/LC_MESSAGES/django.po
|
||||
taggit/locale/nl/LC_MESSAGES/django.mo
|
||||
taggit/locale/nl/LC_MESSAGES/django.po
|
||||
taggit/locale/pt_BR/LC_MESSAGES/django.mo
|
||||
taggit/locale/pt_BR/LC_MESSAGES/django.po
|
||||
taggit/locale/ru/LC_MESSAGES/django.mo
|
||||
taggit/locale/ru/LC_MESSAGES/django.po
|
||||
taggit/migrations/0001_initial.py
|
||||
taggit/migrations/0002_auto_20150616_2121.py
|
||||
taggit/migrations/__init__.py
|
||||
taggit/south_migrations/0001_initial.py
|
||||
taggit/south_migrations/0002_unique_tagnames.py
|
||||
taggit/south_migrations/__init__.py
|
||||
tests/__init__.py
|
||||
tests/forms.py
|
||||
tests/models.py
|
||||
tests/tests.py
|
||||
tests/migrations/0001_initial.py
|
||||
tests/migrations/__init__.py
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
taggit
|
|
@ -0,0 +1,89 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-taggit.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-taggit.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,14 @@
|
|||
Using tags in the admin
|
||||
=======================
|
||||
|
||||
By default if you have a :class:`TaggableManager` on your model it will show up
|
||||
in the admin, just as it will in any other form. One important thing to note
|
||||
is that you *cannot* include a :class:`TaggableManager` in
|
||||
:attr:`ModelAdmin.list_display`, if you do you'll see an exception that looks
|
||||
like::
|
||||
|
||||
AttributeError: 'TaggableManager' object has no attribute 'flatchoices'
|
||||
|
||||
This is for the same reason that you cannot include a :class:`ManyToManyField`,
|
||||
it would result in an unreasonable number of queries being executed. If you really would like to add it, you can read the
|
||||
`Django documentation <http://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display>`_.
|
|
@ -0,0 +1,114 @@
|
|||
The API
|
||||
=======
|
||||
|
||||
After you've got your ``TaggableManager`` added to your model you can start
|
||||
playing around with the API.
|
||||
|
||||
.. class:: TaggableManager([verbose_name="Tags", help_text="A comma-separated list of tags.", through=None, blank=False])
|
||||
|
||||
:param verbose_name: The verbose_name for this field.
|
||||
:param help_text: The help_text to be used in forms (including the admin).
|
||||
:param through: The through model, see :doc:`custom_tagging` for more
|
||||
information.
|
||||
:param blank: Controls whether this field is required.
|
||||
|
||||
.. method:: add(*tags)
|
||||
|
||||
This adds tags to an object. The tags can be either ``Tag`` instances, or
|
||||
strings::
|
||||
|
||||
>>> apple.tags.all()
|
||||
[]
|
||||
>>> apple.tags.add("red", "green", "fruit")
|
||||
|
||||
.. method:: remove(*tags)
|
||||
|
||||
Removes a tag from an object. No exception is raised if the object
|
||||
doesn't have that tag.
|
||||
|
||||
.. method:: clear()
|
||||
|
||||
Removes all tags from an object.
|
||||
|
||||
.. method:: set(*tags)
|
||||
|
||||
Removes all the current tags and then adds the specified tags to the
|
||||
object.
|
||||
|
||||
.. method: 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.
|
||||
|
||||
.. method:: 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.
|
||||
|
||||
If the model is using generic tagging (the default), this method
|
||||
searches tagged objects from all classes. If you are querying on a
|
||||
model with its own tagging through table, only other instances of the
|
||||
same model will be returned.
|
||||
|
||||
.. method:: names()
|
||||
|
||||
Convenience method, returning a ``ValuesListQuerySet`` (basically
|
||||
just an iterable) containing the name of each tag as a string::
|
||||
|
||||
>>> apple.tags.names()
|
||||
[u'green and juicy', u'red']
|
||||
|
||||
.. method:: slugs()
|
||||
|
||||
Convenience method, returning a ``ValuesListQuerySet`` (basically
|
||||
just an iterable) containing the slug of each tag as a string::
|
||||
|
||||
>>> apple.tags.slugs()
|
||||
[u'green-and-juicy', u'red']
|
||||
|
||||
.. hint::
|
||||
|
||||
You can subclass ``_TaggableManager`` (note the underscore) to add
|
||||
methods or functionality. ``TaggableManager`` takes an optional
|
||||
manager keyword argument for your custom class, like this::
|
||||
|
||||
class Food(models.Model):
|
||||
# ... fields here
|
||||
tags = TaggableManager(manager=_CustomTaggableManager)
|
||||
|
||||
Filtering
|
||||
~~~~~~~~~
|
||||
|
||||
To find all of a model with a specific tags you can filter, using the normal
|
||||
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__name__in=["delicious"])
|
||||
[<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__name__in=["delicious", "red"])
|
||||
[<Food: apple>, <Food: apple>]
|
||||
>>> 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.
|
||||
|
||||
Aggregation
|
||||
~~~~~~~~~~~
|
||||
|
||||
Unfortunately, due to a
|
||||
`bug in Django <http://code.djangoproject.com/ticket/10870>`_, it is not
|
||||
currently (Django < 1.6) possible to use aggregation in conjunction with ``taggit``. This is
|
||||
a `documented interaction <http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#generic-relations-and-aggregation>`_
|
||||
of generic relations (which ``taggit`` uses internally) and aggregates.
|
|
@ -0,0 +1 @@
|
|||
.. include:: ../CHANGELOG.txt
|
|
@ -0,0 +1,198 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# django-taggit documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon May 3 22:22:47 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.append(os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.intersphinx']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.txt'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'django-taggit'
|
||||
copyright = u'2010-2014, Alex Gaynor and others.'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.12'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.12'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
#html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'django-taggitdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'django-taggit.tex', u'django-taggit Documentation',
|
||||
u'Alex Gaynor', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -0,0 +1,167 @@
|
|||
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.
|
||||
|
||||
To change the behavior there are a number of classes you can subclass to obtain
|
||||
different behavior:
|
||||
|
||||
=============================== =======================================================================
|
||||
Class name Behavior
|
||||
=============================== =======================================================================
|
||||
``TaggedItemBase`` Allows custom ``ForeignKeys`` to models.
|
||||
``GenericTaggedItemBase`` Allows custom ``Tag`` models. Tagged models use an integer primary key.
|
||||
``GenericUUIDTaggedItemBase`` Allows custom ``Tag`` models. Tagged models use a UUID primary key.
|
||||
``CommonGenericTaggedItemBase`` Allows custom ``Tag`` models and ``GenericForeignKeys`` to models.
|
||||
``ItemBase`` Allows custom ``Tag`` models and ``ForeignKeys`` to models.
|
||||
=============================== =======================================================================
|
||||
|
||||
Custom ForeignKeys
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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.
|
||||
|
||||
Custom GenericForeignKeys
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The default ``GenericForeignKey`` used by ``django-taggit`` assume your
|
||||
tagged object use an integer primary key. For non-integer primary key,
|
||||
your intermediary model must be a subclass of ``taggit.models.CommonGenericTaggedItemBase``
|
||||
with a field named ``"object_id"`` of the type of your primary key.
|
||||
|
||||
For example, if your primary key is a string::
|
||||
|
||||
from django.db import models
|
||||
|
||||
from taggit.managers import TaggableManager
|
||||
from taggit.models import CommonGenericTaggedItemBase, TaggedItemBase
|
||||
|
||||
class GenericStringTaggedItem(CommonGenericTaggedItemBase, TaggedItemBase):
|
||||
object_id = models.CharField(max_length=50, verbose_name=_('Object id'), db_index=True)
|
||||
|
||||
class Food(models.Model):
|
||||
food_id = models.CharField(primary_key=True)
|
||||
# ... fields here
|
||||
|
||||
tags = TaggableManager(through=GenericStringTaggedItem)
|
||||
|
||||
GenericUUIDTaggedItemBase
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
``GenericUUIDTaggedItemBase`` relies on Django UUIDField introduced with
|
||||
Django 1.8. Therefore ``GenericUUIDTaggedItemBase`` is only defined
|
||||
if you are using Django 1.8+.
|
||||
|
||||
A common use case of a non-integer primary key, is UUID primary key.
|
||||
``django-taggit`` provides a base class ``GenericUUIDTaggedItemBase`` ready
|
||||
to use with models using an UUID primary key::
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from taggit.managers import TaggableManager
|
||||
from taggit.models import GenericUUIDTaggedItemBase, TaggedItemBase
|
||||
|
||||
class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase):
|
||||
# If you only inherit GenericUUIDTaggedItemBase, you need to define
|
||||
# a tag field. e.g.
|
||||
# tag = models.ForeignKey(Tag, related_name="uuid_tagged_items", on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Tag")
|
||||
verbose_name_plural = _("Tags")
|
||||
|
||||
class Food(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
# ... fields here
|
||||
|
||||
tags = TaggableManager(through=UUIDTaggedItem)
|
||||
|
||||
Custom tag
|
||||
~~~~~~~~~~
|
||||
|
||||
When providing a custom ``Tag`` model it should be a ``ForeignKey`` to your tag
|
||||
model named ``"tag"``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from taggit.managers import TaggableManager
|
||||
from taggit.models import TagBase, GenericTaggedItemBase
|
||||
|
||||
|
||||
class MyCustomTag(TagBase):
|
||||
# ... fields here
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Tag")
|
||||
verbose_name_plural = _("Tags")
|
||||
|
||||
# ... methods (if any) here
|
||||
|
||||
|
||||
class TaggedWhatever(GenericTaggedItemBase):
|
||||
# TaggedWhatever can also extend TaggedItemBase or a combination of
|
||||
# both TaggedItemBase and GenericTaggedItemBase. GenericTaggedItemBase
|
||||
# allows using the same tag for different kinds of objects, in this
|
||||
# example Food and Drink.
|
||||
|
||||
# Here is where you provide your custom Tag class.
|
||||
tag = models.ForeignKey(MyCustomTag,
|
||||
related_name="%(app_label)s_%(class)s_items")
|
||||
|
||||
|
||||
class Food(models.Model):
|
||||
# ... fields here
|
||||
|
||||
tags = TaggableManager(through=TaggedWhatever)
|
||||
|
||||
|
||||
class Drink(models.Model):
|
||||
# ... fields here
|
||||
|
||||
tags = TaggableManager(through=TaggedWhatever)
|
||||
|
||||
|
||||
.. class:: TagBase
|
||||
|
||||
.. method:: slugify(tag, i=None)
|
||||
|
||||
By default ``taggit`` uses :func:`django.template.defaultfilters.slugify`
|
||||
to calculate a slug for a given tag. However, if you want to implement
|
||||
your own logic you can override this method, which receives the ``tag``
|
||||
(a string), and ``i``, which is either ``None`` or an integer, which
|
||||
signifies how many times the slug for this tag has been attempted to be
|
||||
calculated, it is ``None`` on the first time, and the counting begins
|
||||
at ``1`` thereafter.
|
|
@ -0,0 +1,34 @@
|
|||
External Applications
|
||||
=====================
|
||||
|
||||
In addition to the features included in ``django-taggit`` directly, there are a
|
||||
number of external applications which provide additional features that may be
|
||||
of interest.
|
||||
|
||||
.. note::
|
||||
|
||||
Despite their mention here, the following applications are in no way
|
||||
official, nor have they in any way been reviewed or tested.
|
||||
|
||||
|
||||
If you have an application that you'd like to see listed here, simply fork
|
||||
``taggit`` on `github`__, add it to this list, and send a pull request.
|
||||
|
||||
* ``django-taggit-suggest``: Provides support for defining keyword and regular
|
||||
expression rules for suggesting new tags for content. This used to be
|
||||
available at ``taggit.contrib.suggest``. Available on `github`__.
|
||||
* ``django-taggit-templatetags``: Provides several templatetags, including one
|
||||
for tag clouds, to expose various ``taggit`` APIs directly to templates.
|
||||
Available on `github`__.
|
||||
* ``django-taggit-helpers``: Makes it easier to work with admin pages of models
|
||||
associated with ``taggit`` tags by adding helper classes: ``TaggitCounter``,
|
||||
``TaggitListFilter``, ``TaggitStackedInline``, ``TaggitTabularInline``.
|
||||
Available on `github`__.
|
||||
* ``django-taggit-labels``: Provides a clickable label widget for the
|
||||
Django admin for user friendly selection from managed tag sets.
|
||||
|
||||
__ http://github.com/alex/django-taggit
|
||||
__ http://github.com/frankwiles/django-taggit-suggest
|
||||
__ http://github.com/feuervogel/django-taggit-templatetags
|
||||
__ http://github.com/mfcovington/django-taggit-helpers
|
||||
__ https://github.com/bennylope/django-taggit-labels
|
|
@ -0,0 +1,51 @@
|
|||
.. _tags-in-forms:
|
||||
|
||||
Tags in forms
|
||||
=============
|
||||
|
||||
The ``TaggableManager`` will show up automatically as a field in a
|
||||
``ModelForm`` or in the admin. Tags input via the form field are parsed
|
||||
as follows:
|
||||
|
||||
* If the input doesn't contain any commas or double quotes, it is simply
|
||||
treated as a space-delimited list of tag names.
|
||||
|
||||
* If the input does contain either of these characters:
|
||||
|
||||
* Groups of characters which appear between double quotes take
|
||||
precedence as multi-word tags (so double quoted tag names may
|
||||
contain commas). An unclosed double quote will be ignored.
|
||||
|
||||
* Otherwise, if there are any unquoted commas in the input, it will
|
||||
be treated as comma-delimited. If not, it will be treated as
|
||||
space-delimited.
|
||||
|
||||
Examples:
|
||||
|
||||
====================== ================================= ================================================
|
||||
Tag input string Resulting tags Notes
|
||||
====================== ================================= ================================================
|
||||
apple ball cat ``["apple", "ball", "cat"]`` No commas, so space delimited
|
||||
apple, ball cat ``["apple", "ball cat"]`` Comma present, so comma delimited
|
||||
"apple, ball" cat dog ``["apple, ball", "cat", "dog"]`` All commas are quoted, so space delimited
|
||||
"apple, ball", cat dog ``["apple, ball", "cat dog"]`` Contains an unquoted comma, so comma delimited
|
||||
apple "ball cat" dog ``["apple", "ball cat", "dog"]`` No commas, so space delimited
|
||||
"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(request.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()
|
|
@ -0,0 +1,39 @@
|
|||
Getting Started
|
||||
===============
|
||||
|
||||
To get started using ``django-taggit`` simply install it with
|
||||
``pip``::
|
||||
|
||||
$ pip install django-taggit
|
||||
|
||||
|
||||
Add ``"taggit"`` to your project's ``INSTALLED_APPS`` setting.
|
||||
|
||||
Run `./manage.py syncdb` or `./manage.py migrate` if using migrations.
|
||||
|
||||
.. note::
|
||||
|
||||
If you are using South you'll have to add the following setting, since
|
||||
taggit uses Django migrations by default::
|
||||
|
||||
SOUTH_MIGRATION_MODULES = {
|
||||
'taggit': 'taggit.south_migrations',
|
||||
}
|
||||
|
||||
And then to any model you want tagging on do the following::
|
||||
|
||||
from django.db import models
|
||||
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
class Food(models.Model):
|
||||
# ... fields here
|
||||
|
||||
tags = TaggableManager()
|
||||
|
||||
.. note::
|
||||
|
||||
If you want ``django-taggit`` to be **CASE INSENSITIVE** when looking up existing tags, you'll have to set to ``True`` the TAGGIT_CASE_INSENSITIVE setting (by default ``False``)::
|
||||
|
||||
TAGGIT_CASE_INSENSITIVE = True
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
Welcome to django-taggit's documentation!
|
||||
=========================================
|
||||
|
||||
``django-taggit`` is a reusable Django application designed to making adding
|
||||
tagging to your project easy and fun.
|
||||
|
||||
``django-taggit`` works with Django 1.4.5+ and Python 2.7-3.X.
|
||||
|
||||
.. warning::
|
||||
|
||||
Since version 0.10.0 taggit uses South for database migrations.
|
||||
This means that users who are upgrading to 0.10.0 and up will have to fake
|
||||
the initial migration, like this::
|
||||
|
||||
python manage.py migrate taggit --fake 0001
|
||||
python manage.py migrate
|
||||
|
||||
Since version 0.12.0 taggit uses Django migrations by default. South users
|
||||
have to adjust their settings::
|
||||
|
||||
SOUTH_MIGRATION_MODULES = {
|
||||
'taggit': 'taggit.south_migrations',
|
||||
}
|
||||
|
||||
For more information, see `south documentation`__
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
getting_started
|
||||
forms
|
||||
admin
|
||||
api
|
||||
custom_tagging
|
||||
external_apps
|
||||
changelog
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
__ http://south.readthedocs.org/en/latest/
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
|
||||
if not settings.configured:
|
||||
settings.configure(
|
||||
DATABASES={
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
}
|
||||
},
|
||||
INSTALLED_APPS=[
|
||||
'django.contrib.contenttypes',
|
||||
'taggit',
|
||||
'tests',
|
||||
],
|
||||
MIDDLEWARE_CLASSES=[],
|
||||
)
|
||||
|
||||
|
||||
def runtests():
|
||||
argv = sys.argv[:1] + ['test'] + sys.argv[1:]
|
||||
execute_from_command_line(argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
runtests()
|
|
@ -0,0 +1,18 @@
|
|||
[metadata]
|
||||
license-file = LICENSE
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[flake8]
|
||||
ignore = E501,E302
|
||||
exclude = south_migrations,migrations
|
||||
|
||||
[isort]
|
||||
forced_separate = tests,taggit
|
||||
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
import taggit
|
||||
|
||||
|
||||
with open('README.rst') as f:
|
||||
readme = f.read()
|
||||
|
||||
setup(
|
||||
name='django-taggit',
|
||||
version='.'.join(str(i) for i in taggit.VERSION),
|
||||
description='django-taggit is a reusable Django application for simple tagging.',
|
||||
long_description=readme,
|
||||
author='Alex Gaynor',
|
||||
author_email='alex.gaynor@gmail.com',
|
||||
url='http://github.com/alex/django-taggit/tree/master',
|
||||
packages=find_packages(exclude=('tests*',)),
|
||||
package_data = {
|
||||
'taggit': [
|
||||
'locale/*/LC_MESSAGES/*',
|
||||
],
|
||||
},
|
||||
license='BSD',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Framework :: Django',
|
||||
],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
VERSION = (0, 17, 4)
|
|
@ -0,0 +1,21 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from taggit.models import Tag, TaggedItem
|
||||
|
||||
|
||||
class TaggedItemInline(admin.StackedInline):
|
||||
model = TaggedItem
|
||||
|
||||
class TagAdmin(admin.ModelAdmin):
|
||||
inlines = [
|
||||
TaggedItemInline
|
||||
]
|
||||
list_display = ["name", "slug"]
|
||||
ordering = ["name", "slug"]
|
||||
search_fields = ["name"]
|
||||
prepopulated_fields = {"slug": ["name"]}
|
||||
|
||||
|
||||
admin.site.register(Tag, TagAdmin)
|
|
@ -0,0 +1,27 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.utils import six
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from taggit.utils import edit_string_for_tags, parse_tags
|
||||
|
||||
|
||||
class TagWidget(forms.TextInput):
|
||||
def render(self, name, value, attrs=None):
|
||||
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)
|
||||
|
||||
|
||||
class TagField(forms.CharField):
|
||||
widget = TagWidget
|
||||
|
||||
def clean(self, value):
|
||||
value = super(TagField, self).clean(value)
|
||||
try:
|
||||
return parse_tags(value)
|
||||
except ValueError:
|
||||
raise forms.ValidationError(
|
||||
_("Please provide a comma-separated list of tags."))
|
Binary file not shown.
|
@ -0,0 +1,64 @@
|
|||
# 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.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-08-01 16:52+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr "Vložte čárkami oddělený seznam tagů"
|
||||
|
||||
#: managers.py:59 models.py:59
|
||||
msgid "Tags"
|
||||
msgstr "Tagy"
|
||||
|
||||
#: managers.py:60
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "Čárkami oddělený seznam tagů"
|
||||
|
||||
#: models.py:15
|
||||
msgid "Name"
|
||||
msgstr "Jméno"
|
||||
|
||||
#: models.py:16
|
||||
msgid "Slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: models.py:58
|
||||
msgid "Tag"
|
||||
msgstr "Tag"
|
||||
|
||||
#: models.py:65
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "%(object)s označen tagem %(tag)s"
|
||||
|
||||
#: models.py:112
|
||||
msgid "Object id"
|
||||
msgstr "ID objektu"
|
||||
|
||||
#: models.py:115
|
||||
msgid "Content type"
|
||||
msgstr "Typ obsahu"
|
||||
|
||||
#: models.py:158
|
||||
msgid "Tagged Item"
|
||||
msgstr "Tagem označená položka"
|
||||
|
||||
#: models.py:159
|
||||
msgid "Tagged Items"
|
||||
msgstr "Tagy označené položky"
|
Binary file not shown.
|
@ -0,0 +1,67 @@
|
|||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-taggit\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-09-07 09:26-0700\n"
|
||||
"PO-Revision-Date: 2010-09-07 09:26-0700\n"
|
||||
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||
"Language-Team: German <de@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 "Bitte eine durch Komma getrennte Schlagwortliste eingeben."
|
||||
|
||||
#: managers.py:39 managers.py:83 models.py:50
|
||||
msgid "Tags"
|
||||
msgstr "Schlagwörter"
|
||||
|
||||
#: managers.py:84
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "Eine durch Komma getrennte Schlagwortliste."
|
||||
|
||||
#: models.py:10
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: models.py:11
|
||||
msgid "Slug"
|
||||
msgstr "Kürzel"
|
||||
|
||||
#: models.py:49
|
||||
msgid "Tag"
|
||||
msgstr "Schlagwort"
|
||||
|
||||
#: models.py:56
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "%(object)s verschlagwortet mit %(tag)s"
|
||||
|
||||
#: models.py:100
|
||||
msgid "Object id"
|
||||
msgstr "Objekt-ID"
|
||||
|
||||
#: models.py:104 models.py:110
|
||||
msgid "Content type"
|
||||
msgstr "Inhaltstyp"
|
||||
|
||||
#: models.py:138
|
||||
msgid "Tagged Item"
|
||||
msgstr "Verschlagwortetes Objekt"
|
||||
|
||||
#: models.py:139
|
||||
msgid "Tagged Items"
|
||||
msgstr "Verschlagwortete Objekte"
|
||||
|
||||
#: contrib/suggest/models.py:57
|
||||
msgid ""
|
||||
"Enter a valid Regular Expression. To make it case-insensitive include \"(?i)"
|
||||
"\" in your expression."
|
||||
msgstr ""
|
||||
"Bitte einen regulären Ausdruck eingeben. Fügen Sie \"(?i) \" dem "
|
||||
"Ausdruck hinzu, um nicht zwischen Groß- und Kleinschreibung zu "
|
||||
"unterscheiden."
|
|
@ -0,0 +1,68 @@
|
|||
# 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.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-09-07 09:45-0700\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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"
|
||||
|
||||
#: forms.py:20
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr ""
|
||||
|
||||
#: managers.py:39 managers.py:83 models.py:50
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#: managers.py:84
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:10
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:11
|
||||
msgid "Slug"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:49
|
||||
msgid "Tag"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:56
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:100
|
||||
msgid "Object id"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:104 models.py:110
|
||||
msgid "Content type"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:138
|
||||
msgid "Tagged Item"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:139
|
||||
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 ""
|
Binary file not shown.
|
@ -0,0 +1,67 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-taggit\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-09-07 09:26-0700\n"
|
||||
"PO-Revision-Date: 2014-03-29 18:57+0100\n"
|
||||
"Last-Translator: Baptiste Darthenay <taggit.batisteo@recursor.net>\n"
|
||||
"Language-Team: Esperanto <taggit.batisteo@recursor.net>\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"
|
||||
"Language: eo\n"
|
||||
"X-Generator: Poedit 1.5.4\n"
|
||||
|
||||
#: forms.py:20
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr "Bonvolu enmeti liston da etikedoj apartitaj per komoj."
|
||||
|
||||
#: managers.py:39 managers.py:83 models.py:50
|
||||
msgid "Tags"
|
||||
msgstr "Etikedoj"
|
||||
|
||||
#: managers.py:84
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "Listo da etikedoj apartitaj per komoj."
|
||||
|
||||
#: models.py:10
|
||||
msgid "Name"
|
||||
msgstr "Nomo"
|
||||
|
||||
#: models.py:11
|
||||
msgid "Slug"
|
||||
msgstr "Ĵetonvorto"
|
||||
|
||||
#: models.py:49
|
||||
msgid "Tag"
|
||||
msgstr "Etikedo"
|
||||
|
||||
#: models.py:56
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "%(object)s etikedita %(tag)s"
|
||||
|
||||
#: models.py:100
|
||||
msgid "Object id"
|
||||
msgstr "Objekto ID"
|
||||
|
||||
#: models.py:104 models.py:110
|
||||
msgid "Content type"
|
||||
msgstr "Enhavtipo"
|
||||
|
||||
#: models.py:138
|
||||
msgid "Tagged Item"
|
||||
msgstr "Etikedita elemento"
|
||||
|
||||
#: models.py:139
|
||||
msgid "Tagged Items"
|
||||
msgstr "Etikeditaj elementoj"
|
||||
|
||||
#: contrib/suggest/models.py:57
|
||||
msgid ""
|
||||
"Enter a valid Regular Expression. To make it case-insensitive include \"(?"
|
||||
"i)\" in your expression."
|
||||
msgstr ""
|
||||
"Entajpu validan regulan esprimon. Por ke estu usklecoblinda, enmetu \"(?i)\" "
|
||||
"en via esprimo."
|
Binary file not shown.
|
@ -0,0 +1,68 @@
|
|||
# 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 ""
|
Binary file not shown.
|
@ -0,0 +1,70 @@
|
|||
# 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.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-04-13 15:57+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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"
|
||||
|
||||
#: forms.py:20
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr "Fornire una lista di tag separati da virgola."
|
||||
|
||||
#: managers.py:39 managers.py:83 models.py:50
|
||||
msgid "Tags"
|
||||
msgstr "Tag"
|
||||
|
||||
#: managers.py:84
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "Una lista di tag separati da virgola."
|
||||
|
||||
#: models.py:10
|
||||
msgid "Name"
|
||||
msgstr "Nome"
|
||||
|
||||
#: models.py:11
|
||||
msgid "Slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: models.py:49
|
||||
msgid "Tag"
|
||||
msgstr "Tag"
|
||||
|
||||
#: models.py:56
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "%(object)s con tag %(tag)s"
|
||||
|
||||
#: models.py:100
|
||||
msgid "Object id"
|
||||
msgstr "Id Oggetto"
|
||||
|
||||
#: models.py:104 models.py:110
|
||||
msgid "Content type"
|
||||
msgstr "Tipo"
|
||||
|
||||
#: models.py:138
|
||||
msgid "Tagged Item"
|
||||
msgstr "Oggetto con tag"
|
||||
|
||||
#: models.py:139
|
||||
msgid "Tagged Items"
|
||||
msgstr "Oggetti con tag"
|
||||
|
||||
#: contrib/suggest/models.py:57
|
||||
msgid ""
|
||||
"Enter a valid Regular Expression. To make it case-insensitive include \"(?i)"
|
||||
"\" in your expression."
|
||||
msgstr ""
|
||||
"Inserire un'espressione regolare valida. Per renderla indipendente dal maiuscolo e minuscolo, includere \"(?i)"
|
||||
"\" nell'espressione."
|
Binary file not shown.
|
@ -0,0 +1,67 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-taggit\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-09-07 09:26-0700\n"
|
||||
"PO-Revision-Date: 2014-04-23 08:05+0900\n"
|
||||
"Last-Translator: Tatsuo Ikeda <jp.ne.co.jp@gmail.com>\n"
|
||||
"Language-Team: \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"
|
||||
"Language: ja\n"
|
||||
"X-Generator: Poedit 1.6.4\n"
|
||||
|
||||
#: forms.py:20
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr "複数タグはカンマ区切りのリストを入れてください。"
|
||||
|
||||
#: managers.py:39 managers.py:83 models.py:50
|
||||
msgid "Tags"
|
||||
msgstr "タグ一覧"
|
||||
|
||||
#: managers.py:84
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "複数タグはカンマ区切りのリスト。"
|
||||
|
||||
#: models.py:10
|
||||
msgid "Name"
|
||||
msgstr "名称"
|
||||
|
||||
#: models.py:11
|
||||
msgid "Slug"
|
||||
msgstr "スラッグ"
|
||||
|
||||
#: models.py:49
|
||||
msgid "Tag"
|
||||
msgstr "タグ"
|
||||
|
||||
#: models.py:56
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "%(object)s tagged with %(tag)s"
|
||||
|
||||
#: models.py:100
|
||||
msgid "Object id"
|
||||
msgstr "オブジェクト ID"
|
||||
|
||||
#: models.py:104 models.py:110
|
||||
msgid "Content type"
|
||||
msgstr "コンテンツタイプ"
|
||||
|
||||
#: models.py:138
|
||||
msgid "Tagged Item"
|
||||
msgstr "タグ付け済みのアイテム"
|
||||
|
||||
#: models.py:139
|
||||
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 ""
|
||||
"正しい正規表現を入力してください。 大文字と小文字を区別しないようにするには "
|
||||
"\"(?i)\" を正規表現に含めてください。"
|
Binary file not shown.
|
@ -0,0 +1,72 @@
|
|||
# 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: 0.9.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-09-07 09:45-0700\n"
|
||||
"PO-Revision-Date: 2012-12-08 14:42+0100\n"
|
||||
"Last-Translator: Bjørn Pettersen <bp@datakortet.no>\n"
|
||||
"Language-Team: Norwegian <bp@datakortet.no>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.5.4\n"
|
||||
"Language: Norwegian\n"
|
||||
|
||||
#: forms.py:20
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr "Vennligst oppgi en kommaseparert tagg-liste."
|
||||
|
||||
#: managers.py:39 managers.py:83 models.py:50
|
||||
msgid "Tags"
|
||||
msgstr "Tagger"
|
||||
|
||||
#: managers.py:84
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "En kommaseparert tagg-liste."
|
||||
|
||||
#: models.py:10
|
||||
msgid "Name"
|
||||
msgstr "Navn"
|
||||
|
||||
#: models.py:11
|
||||
msgid "Slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: models.py:49
|
||||
msgid "Tag"
|
||||
msgstr "Tagg"
|
||||
|
||||
#: models.py:56
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "%(object)s tagget med %(tag)s"
|
||||
|
||||
#: models.py:100
|
||||
msgid "Object id"
|
||||
msgstr "Objekt-id"
|
||||
|
||||
#: models.py:104 models.py:110
|
||||
msgid "Content type"
|
||||
msgstr "Innholdstype"
|
||||
|
||||
#: models.py:138
|
||||
msgid "Tagged Item"
|
||||
msgstr "Tagget Element"
|
||||
|
||||
#: models.py:139
|
||||
msgid "Tagged Items"
|
||||
msgstr "Taggede Elementer"
|
||||
|
||||
#: contrib/suggest/models.py:57
|
||||
msgid ""
|
||||
"Enter a valid Regular Expression. To make it case-insensitive include \"(?"
|
||||
"i)\" in your expression."
|
||||
msgstr ""
|
||||
"Skriv et gyldig regulært utrykk (regex). For å gjøre det uavhengig av "
|
||||
"forskjellen mellom store og små bokstaver må du inkludere \"(?i)\" i din "
|
||||
"regex."
|
Binary file not shown.
|
@ -0,0 +1,63 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-taggit\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-09-07 09:45-0700\n"
|
||||
"PO-Revision-Date: 2010-09-07 23:04+0100\n"
|
||||
"Last-Translator: Jeffrey Gelens <jeffrey@gelens.org>\n"
|
||||
"Language-Team: Dutch\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: forms.py:20
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr "Geef een door komma gescheiden lijst van tags."
|
||||
|
||||
#: managers.py:39
|
||||
#: managers.py:83
|
||||
#: models.py:50
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
|
||||
#: managers.py:84
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "Een door komma gescheiden lijst van tags."
|
||||
|
||||
#: models.py:10
|
||||
msgid "Name"
|
||||
msgstr "Naam"
|
||||
|
||||
#: models.py:11
|
||||
msgid "Slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: models.py:49
|
||||
msgid "Tag"
|
||||
msgstr "Tag"
|
||||
|
||||
#: models.py:56
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "%(object)s getagged met %(tag)s"
|
||||
|
||||
#: models.py:100
|
||||
msgid "Object id"
|
||||
msgstr "Object-id"
|
||||
|
||||
#: models.py:104
|
||||
#: models.py:110
|
||||
msgid "Content type"
|
||||
msgstr "Inhoudstype"
|
||||
|
||||
#: models.py:138
|
||||
msgid "Tagged Item"
|
||||
msgstr "Object getagged"
|
||||
|
||||
#: models.py:139
|
||||
msgid "Tagged Items"
|
||||
msgstr "Objecten getagged"
|
||||
|
||||
#: contrib/suggest/models.py:57
|
||||
msgid "Enter a valid Regular Expression. To make it case-insensitive include \"(?i)\" in your expression."
|
||||
msgstr "Voer een valide reguliere expressie in. Voeg \"(?i)\" aan de expressie toe om deze hoofdletter ongevoelig te maken."
|
Binary file not shown.
|
@ -0,0 +1,62 @@
|
|||
# This file is distributed under WTFPL license.
|
||||
#
|
||||
# Translators:
|
||||
# RPB <r@ifgy.co>, 2013.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-taggit\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-01-15 22:25-0200\n"
|
||||
"PO-Revision-Date: 2013-01-12 18:11-0200\n"
|
||||
"Last-Translator: RPB <r@ifgy.co>\n"
|
||||
"Language-Team: Portuguese (Brazil) <r@ifgy.co>\n"
|
||||
"Language: pt_BR\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:21
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr "Favor fornecer uma lista de marcadores separados por vírgula."
|
||||
|
||||
#: managers.py:39 models.py:57
|
||||
msgid "Tags"
|
||||
msgstr "Marcadores"
|
||||
|
||||
#: managers.py:40
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "Uma lista de marcadores separados por vírgula."
|
||||
|
||||
#: models.py:10
|
||||
msgid "Name"
|
||||
msgstr "Nome"
|
||||
|
||||
#: models.py:11
|
||||
msgid "Slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: models.py:56
|
||||
msgid "Tag"
|
||||
msgstr "Marcador"
|
||||
|
||||
#: models.py:63
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "%(object)s marcados com %(tag)s"
|
||||
|
||||
#: models.py:113
|
||||
msgid "Object id"
|
||||
msgstr "Id do objeto"
|
||||
|
||||
#: models.py:117 models.py:123
|
||||
msgid "Content type"
|
||||
msgstr "Tipo de conteúdo"
|
||||
|
||||
#: models.py:159
|
||||
msgid "Tagged Item"
|
||||
msgstr "Item marcado"
|
||||
|
||||
#: models.py:160
|
||||
msgid "Tagged Items"
|
||||
msgstr "Itens marcados"
|
Binary file not shown.
|
@ -0,0 +1,69 @@
|
|||
# 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-11 11:28+0700\n"
|
||||
"PO-Revision-Date: 2010-06-11 11:30+0700\n"
|
||||
"Last-Translator: Igor 'idle sign' Starikov <idlesign@yandex.ru>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
"X-Poedit-Language: Russian\n"
|
||||
|
||||
#: forms.py:20
|
||||
msgid "Please provide a comma-separated list of tags."
|
||||
msgstr "Укажите метки через запятую."
|
||||
|
||||
#: managers.py:41
|
||||
#: managers.py:101
|
||||
#: models.py:17
|
||||
msgid "Tags"
|
||||
msgstr "Метки"
|
||||
|
||||
#: managers.py:102
|
||||
msgid "A comma-separated list of tags."
|
||||
msgstr "Список меток через запятую."
|
||||
|
||||
#: models.py:9
|
||||
msgid "Name"
|
||||
msgstr "Название"
|
||||
|
||||
#: models.py:10
|
||||
msgid "Slug"
|
||||
msgstr "Слаг"
|
||||
|
||||
#: models.py:16
|
||||
msgid "Tag"
|
||||
msgstr "Метка"
|
||||
|
||||
#: models.py:55
|
||||
#, python-format
|
||||
msgid "%(object)s tagged with %(tag)s"
|
||||
msgstr "элемент «%(object)s» с меткой «%(tag)s»"
|
||||
|
||||
#: models.py:82
|
||||
msgid "Object id"
|
||||
msgstr "ID объекта"
|
||||
|
||||
#: models.py:83
|
||||
msgid "Content type"
|
||||
msgstr "Тип содержимого"
|
||||
|
||||
#: models.py:87
|
||||
msgid "Tagged Item"
|
||||
msgstr "Элемент с меткой"
|
||||
|
||||
#: models.py:88
|
||||
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 "Введите регулярное выражение. Чтобы сделать его чувствительным к регистру укажите \"(?i)\"."
|
|
@ -0,0 +1,592 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
from django import VERSION
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.conf import settings
|
||||
from django.db import models, router
|
||||
from django.db.models.fields import Field
|
||||
from django.db.models.fields.related import (add_lazy_relation, ManyToManyRel,
|
||||
OneToOneRel, RelatedField)
|
||||
|
||||
if VERSION < (1, 8):
|
||||
# related.py was removed in Django 1.8
|
||||
|
||||
# Depending on how Django was updated, related.py could still exist
|
||||
# on the users system even on Django 1.8+, so we check the Django
|
||||
# version before importing it to make sure this doesn't get imported
|
||||
# accidentally.
|
||||
from django.db.models.related import RelatedObject
|
||||
else:
|
||||
RelatedObject = None
|
||||
|
||||
from django.utils import six
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from taggit.forms import TagField
|
||||
from taggit.models import CommonGenericTaggedItemBase, TaggedItem
|
||||
from taggit.utils import _get_field, require_instance_manager
|
||||
|
||||
try:
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
except ImportError: # django < 1.7
|
||||
from django.contrib.contenttypes.generic import GenericRelation
|
||||
|
||||
try:
|
||||
from django.db.models.query_utils import PathInfo
|
||||
except ImportError: # Django < 1.8
|
||||
try:
|
||||
from django.db.models.related import PathInfo
|
||||
except ImportError:
|
||||
pass # PathInfo is not used on Django < 1.6
|
||||
|
||||
|
||||
def _model_name(model):
|
||||
if VERSION < (1, 7):
|
||||
return model._meta.module_name
|
||||
else:
|
||||
return model._meta.model_name
|
||||
|
||||
|
||||
class TaggableRel(ManyToManyRel):
|
||||
def __init__(self, field, related_name, through, to=None):
|
||||
# rel.to renamed to rel.model in Django 1.9
|
||||
if VERSION >= (1, 9):
|
||||
self.model = to
|
||||
else:
|
||||
self.to = to
|
||||
self.related_name = related_name
|
||||
self.limit_choices_to = {}
|
||||
self.symmetrical = True
|
||||
self.multiple = True
|
||||
self.through = None if VERSION < (1, 7) else through
|
||||
self.field = field
|
||||
self.through_fields = None
|
||||
|
||||
def get_joining_columns(self):
|
||||
return self.field.get_reverse_joining_columns()
|
||||
|
||||
def get_extra_restriction(self, where_class, alias, related_alias):
|
||||
return self.field.get_extra_restriction(where_class, related_alias, alias)
|
||||
|
||||
|
||||
class ExtraJoinRestriction(object):
|
||||
"""
|
||||
An extra restriction used for contenttype restriction in joins.
|
||||
"""
|
||||
contains_aggregate = False
|
||||
|
||||
def __init__(self, alias, col, content_types):
|
||||
self.alias = alias
|
||||
self.col = col
|
||||
self.content_types = content_types
|
||||
|
||||
def as_sql(self, qn, connection):
|
||||
if len(self.content_types) == 1:
|
||||
extra_where = "%s.%s = %%s" % (qn(self.alias), qn(self.col))
|
||||
else:
|
||||
extra_where = "%s.%s IN (%s)" % (qn(self.alias), qn(self.col),
|
||||
','.join(['%s'] * len(self.content_types)))
|
||||
return extra_where, self.content_types
|
||||
|
||||
def relabel_aliases(self, change_map):
|
||||
self.alias = change_map.get(self.alias, self.alias)
|
||||
|
||||
def clone(self):
|
||||
return self.__class__(self.alias, self.col, self.content_types[:])
|
||||
|
||||
|
||||
class _TaggableManager(models.Manager):
|
||||
def __init__(self, through, model, instance, prefetch_cache_name):
|
||||
self.through = through
|
||||
self.model = model
|
||||
self.instance = instance
|
||||
self.prefetch_cache_name = prefetch_cache_name
|
||||
self._db = None
|
||||
|
||||
def is_cached(self, instance):
|
||||
return self.prefetch_cache_name in instance._prefetched_objects_cache
|
||||
|
||||
def get_queryset(self, extra_filters=None):
|
||||
try:
|
||||
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
|
||||
except (AttributeError, KeyError):
|
||||
kwargs = extra_filters if extra_filters else {}
|
||||
return self.through.tags_for(self.model, self.instance, **kwargs)
|
||||
|
||||
def get_prefetch_queryset(self, instances, queryset=None):
|
||||
if queryset is not None:
|
||||
raise ValueError("Custom queryset can't be used for this lookup.")
|
||||
|
||||
instance = instances[0]
|
||||
from django.db import connections
|
||||
db = self._db or router.db_for_read(instance.__class__, instance=instance)
|
||||
|
||||
fieldname = ('object_id' if issubclass(self.through, CommonGenericTaggedItemBase)
|
||||
else 'content_object')
|
||||
fk = self.through._meta.get_field(fieldname)
|
||||
query = {
|
||||
'%s__%s__in' % (self.through.tag_relname(), fk.name):
|
||||
set(obj._get_pk_val() for obj in instances)
|
||||
}
|
||||
join_table = self.through._meta.db_table
|
||||
source_col = fk.column
|
||||
connection = connections[db]
|
||||
qn = connection.ops.quote_name
|
||||
qs = self.get_queryset(query).using(db).extra(
|
||||
select={
|
||||
'_prefetch_related_val': '%s.%s' % (qn(join_table), qn(source_col))
|
||||
}
|
||||
)
|
||||
return (qs,
|
||||
attrgetter('_prefetch_related_val'),
|
||||
lambda obj: obj._get_pk_val(),
|
||||
False,
|
||||
self.prefetch_cache_name)
|
||||
|
||||
# Django < 1.6 uses the previous name of query_set
|
||||
get_query_set = get_queryset
|
||||
get_prefetch_query_set = get_prefetch_queryset
|
||||
|
||||
def _lookup_kwargs(self):
|
||||
return self.through.lookup_kwargs(self.instance)
|
||||
|
||||
@require_instance_manager
|
||||
def add(self, *tags):
|
||||
str_tags = set()
|
||||
tag_objs = set()
|
||||
for t in tags:
|
||||
if isinstance(t, self.through.tag_model()):
|
||||
tag_objs.add(t)
|
||||
elif isinstance(t, six.string_types):
|
||||
str_tags.add(t)
|
||||
else:
|
||||
raise ValueError("Cannot add {0} ({1}). Expected {2} or str.".format(
|
||||
t, type(t), type(self.through.tag_model())))
|
||||
|
||||
if getattr(settings, 'TAGGIT_CASE_INSENSITIVE', False):
|
||||
# Some databases can do case-insensitive comparison with IN, which
|
||||
# would be faster, but we can't rely on it or easily detect it.
|
||||
existing = []
|
||||
tags_to_create = []
|
||||
|
||||
for name in str_tags:
|
||||
try:
|
||||
tag = self.through.tag_model().objects.get(name__iexact=name)
|
||||
existing.append(tag)
|
||||
except self.through.tag_model().DoesNotExist:
|
||||
tags_to_create.append(name)
|
||||
else:
|
||||
# If str_tags has 0 elements Django actually optimizes that to not do a
|
||||
# query. Malcolm is very smart.
|
||||
existing = self.through.tag_model().objects.filter(
|
||||
name__in=str_tags
|
||||
)
|
||||
|
||||
tags_to_create = str_tags - set(t.name for t in existing)
|
||||
|
||||
tag_objs.update(existing)
|
||||
|
||||
for new_tag in tags_to_create:
|
||||
tag_objs.add(self.through.tag_model().objects.create(name=new_tag))
|
||||
|
||||
for tag in tag_objs:
|
||||
self.through.objects.get_or_create(tag=tag, **self._lookup_kwargs())
|
||||
|
||||
@require_instance_manager
|
||||
def names(self):
|
||||
return self.get_queryset().values_list('name', flat=True)
|
||||
|
||||
@require_instance_manager
|
||||
def slugs(self):
|
||||
return self.get_queryset().values_list('slug', flat=True)
|
||||
|
||||
@require_instance_manager
|
||||
def set(self, *tags):
|
||||
self.clear()
|
||||
self.add(*tags)
|
||||
|
||||
@require_instance_manager
|
||||
def remove(self, *tags):
|
||||
self.through.objects.filter(**self._lookup_kwargs()).filter(
|
||||
tag__name__in=tags).delete()
|
||||
|
||||
@require_instance_manager
|
||||
def clear(self):
|
||||
self.through.objects.filter(**self._lookup_kwargs()).delete()
|
||||
|
||||
def most_common(self):
|
||||
return self.get_queryset().annotate(
|
||||
num_times=models.Count(self.through.tag_relname())
|
||||
).order_by('-num_times')
|
||||
|
||||
@require_instance_manager
|
||||
def similar_objects(self):
|
||||
lookup_kwargs = self._lookup_kwargs()
|
||||
lookup_keys = sorted(lookup_kwargs)
|
||||
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())
|
||||
qs = qs.order_by('-n')
|
||||
|
||||
# TODO: This all feels like a bit of a hack.
|
||||
items = {}
|
||||
if len(lookup_keys) == 1:
|
||||
# Can we do this without a second query by using a select_related()
|
||||
# somehow?
|
||||
f = _get_field(self.through, lookup_keys[0])
|
||||
rel_model = f.rel.model if VERSION >= (1, 9) else f.rel.to
|
||||
objs = rel_model._default_manager.filter(**{
|
||||
"%s__in" % f.rel.field_name: [r["content_object"] for r in qs]
|
||||
})
|
||||
for obj in objs:
|
||||
items[(getattr(obj, f.rel.field_name),)] = obj
|
||||
else:
|
||||
preload = {}
|
||||
for result in qs:
|
||||
preload.setdefault(result['content_type'], set())
|
||||
preload[result["content_type"]].add(result["object_id"])
|
||||
|
||||
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
|
||||
|
||||
results = []
|
||||
for result in qs:
|
||||
obj = items[
|
||||
tuple(result[k] for k in lookup_keys)
|
||||
]
|
||||
obj.similar_tags = result["n"]
|
||||
results.append(obj)
|
||||
return results
|
||||
|
||||
# _TaggableManager needs to be hashable but BaseManagers in Django 1.8+ overrides
|
||||
# the __eq__ method which makes the default __hash__ method disappear.
|
||||
# This checks if the __hash__ attribute is None, and if so, it reinstates the original method.
|
||||
if models.Manager.__hash__ is None:
|
||||
__hash__ = object.__hash__
|
||||
|
||||
|
||||
class TaggableManager(RelatedField, Field):
|
||||
# Field flags
|
||||
many_to_many = True
|
||||
many_to_one = False
|
||||
one_to_many = False
|
||||
one_to_one = False
|
||||
|
||||
_related_name_counter = 0
|
||||
|
||||
def __init__(self, verbose_name=_("Tags"),
|
||||
help_text=_("A comma-separated list of tags."),
|
||||
through=None, blank=False, related_name=None, to=None,
|
||||
manager=_TaggableManager):
|
||||
|
||||
self.through = through or TaggedItem
|
||||
self.swappable = False
|
||||
self.manager = manager
|
||||
|
||||
rel = TaggableRel(self, related_name, self.through, to=to)
|
||||
|
||||
Field.__init__(
|
||||
self,
|
||||
verbose_name=verbose_name,
|
||||
help_text=help_text,
|
||||
blank=blank,
|
||||
null=True,
|
||||
serialize=False,
|
||||
rel=rel,
|
||||
)
|
||||
# NOTE: `to` is ignored, only used via `deconstruct`.
|
||||
|
||||
def __get__(self, instance, model):
|
||||
if instance is not None and instance.pk is None:
|
||||
raise ValueError("%s objects need to have a primary key value "
|
||||
"before you can access their tags." % model.__name__)
|
||||
manager = self.manager(
|
||||
through=self.through,
|
||||
model=model,
|
||||
instance=instance,
|
||||
prefetch_cache_name=self.name
|
||||
)
|
||||
return manager
|
||||
|
||||
def deconstruct(self):
|
||||
"""
|
||||
Deconstruct the object, used with migrations.
|
||||
"""
|
||||
name, path, args, kwargs = super(TaggableManager, self).deconstruct()
|
||||
# Remove forced kwargs.
|
||||
for kwarg in ('serialize', 'null'):
|
||||
del kwargs[kwarg]
|
||||
# Add arguments related to relations.
|
||||
# Ref: https://github.com/alex/django-taggit/issues/206#issuecomment-37578676
|
||||
if isinstance(self.rel.through, six.string_types):
|
||||
kwargs['through'] = self.rel.through
|
||||
elif not self.rel.through._meta.auto_created:
|
||||
kwargs['through'] = "%s.%s" % (self.rel.through._meta.app_label, self.rel.through._meta.object_name)
|
||||
|
||||
# rel.to renamed to remote_field.model in Django 1.9
|
||||
if VERSION >= (1, 9):
|
||||
if isinstance(self.remote_field.model, six.string_types):
|
||||
kwargs['to'] = self.remote_field.model
|
||||
else:
|
||||
kwargs['to'] = '%s.%s' % (self.remote_field.model._meta.app_label, self.remote_field.model._meta.object_name)
|
||||
else:
|
||||
if isinstance(self.rel.to, six.string_types):
|
||||
kwargs['to'] = self.rel.to
|
||||
else:
|
||||
kwargs['to'] = '%s.%s' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name)
|
||||
|
||||
return name, path, args, kwargs
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
if VERSION < (1, 7):
|
||||
self.name = self.column = self.attname = name
|
||||
else:
|
||||
self.set_attributes_from_name(name)
|
||||
self.model = cls
|
||||
|
||||
cls._meta.add_field(self)
|
||||
setattr(cls, name, self)
|
||||
if not cls._meta.abstract:
|
||||
# rel.to renamed to remote_field.model in Django 1.9
|
||||
if VERSION >= (1, 9):
|
||||
if isinstance(self.remote_field.model, six.string_types):
|
||||
def resolve_related_class(field, model, cls):
|
||||
field.remote_field.model = model
|
||||
add_lazy_relation(cls, self, self.remote_field.model, resolve_related_class)
|
||||
else:
|
||||
if isinstance(self.rel.to, six.string_types):
|
||||
def resolve_related_class(field, model, cls):
|
||||
field.rel.to = model
|
||||
add_lazy_relation(cls, self, self.rel.to, resolve_related_class)
|
||||
|
||||
if isinstance(self.through, six.string_types):
|
||||
def resolve_related_class(field, model, cls):
|
||||
self.through = model
|
||||
self.rel.through = model
|
||||
self.post_through_setup(cls)
|
||||
add_lazy_relation(
|
||||
cls, self, self.through, resolve_related_class
|
||||
)
|
||||
else:
|
||||
self.post_through_setup(cls)
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'ManyToManyField'
|
||||
|
||||
def __lt__(self, other):
|
||||
"""
|
||||
Required contribute_to_class as Django uses bisect
|
||||
for ordered class contribution and bisect requires
|
||||
a orderable type in py3.
|
||||
"""
|
||||
return False
|
||||
|
||||
def post_through_setup(self, cls):
|
||||
if RelatedObject is not None: # Django < 1.8
|
||||
self.related = RelatedObject(cls, self.model, self)
|
||||
|
||||
self.use_gfk = (
|
||||
self.through is None or issubclass(self.through, CommonGenericTaggedItemBase)
|
||||
)
|
||||
|
||||
# rel.to renamed to remote_field.model in Django 1.9
|
||||
if VERSION >= (1, 9):
|
||||
if not self.remote_field.model:
|
||||
self.remote_field.model = self.through._meta.get_field("tag").remote_field.model
|
||||
else:
|
||||
if not self.rel.to:
|
||||
self.rel.to = self.through._meta.get_field("tag").rel.to
|
||||
|
||||
if RelatedObject is not None: # Django < 1.8
|
||||
self.related = RelatedObject(self.through, cls, self)
|
||||
|
||||
if self.use_gfk:
|
||||
tagged_items = GenericRelation(self.through)
|
||||
tagged_items.contribute_to_class(cls, 'tagged_items')
|
||||
|
||||
for rel in cls._meta.local_many_to_many:
|
||||
if rel == self or not isinstance(rel, TaggableManager):
|
||||
continue
|
||||
if rel.through == self.through:
|
||||
raise ValueError('You can\'t have two TaggableManagers with the'
|
||||
' same through model.')
|
||||
|
||||
def save_form_data(self, instance, value):
|
||||
getattr(instance, self.name).set(*value)
|
||||
|
||||
def formfield(self, form_class=TagField, **kwargs):
|
||||
defaults = {
|
||||
"label": capfirst(self.verbose_name),
|
||||
"help_text": self.help_text,
|
||||
"required": not self.blank
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return form_class(**defaults)
|
||||
|
||||
def value_from_object(self, instance):
|
||||
if instance.pk:
|
||||
return self.through.objects.filter(**self.through.lookup_kwargs(instance))
|
||||
return self.through.objects.none()
|
||||
|
||||
def related_query_name(self):
|
||||
return _model_name(self.model)
|
||||
|
||||
def m2m_reverse_name(self):
|
||||
return _get_field(self.through, 'tag').column
|
||||
|
||||
def m2m_reverse_field_name(self):
|
||||
return _get_field(self.through, 'tag').name
|
||||
|
||||
def m2m_target_field_name(self):
|
||||
return self.model._meta.pk.name
|
||||
|
||||
def m2m_reverse_target_field_name(self):
|
||||
# rel.to renamed to remote_field.model in Django 1.9
|
||||
if VERSION >= (1, 9):
|
||||
return self.remote_field.model._meta.pk.name
|
||||
else:
|
||||
return self.rel.to._meta.pk.name
|
||||
|
||||
def m2m_column_name(self):
|
||||
if self.use_gfk:
|
||||
return self.through._meta.virtual_fields[0].fk_field
|
||||
return self.through._meta.get_field('content_object').column
|
||||
|
||||
def db_type(self, connection=None):
|
||||
return None
|
||||
|
||||
def m2m_db_table(self):
|
||||
return self.through._meta.db_table
|
||||
|
||||
def bulk_related_objects(self, new_objs, using):
|
||||
return []
|
||||
|
||||
def extra_filters(self, pieces, pos, negate):
|
||||
if negate or not self.use_gfk:
|
||||
return []
|
||||
prefix = "__".join(["tagged_items"] + pieces[:pos - 2])
|
||||
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)]
|
||||
|
||||
def get_extra_join_sql(self, connection, qn, lhs_alias, rhs_alias):
|
||||
model_name = _model_name(self.through)
|
||||
if rhs_alias == '%s_%s' % (self.through._meta.app_label, model_name):
|
||||
alias_to_join = rhs_alias
|
||||
else:
|
||||
alias_to_join = lhs_alias
|
||||
extra_col = _get_field(self.through, 'content_type').column
|
||||
content_type_ids = [ContentType.objects.get_for_model(subclass).pk for
|
||||
subclass in _get_subclasses(self.model)]
|
||||
if len(content_type_ids) == 1:
|
||||
content_type_id = content_type_ids[0]
|
||||
extra_where = " AND %s.%s = %%s" % (qn(alias_to_join),
|
||||
qn(extra_col))
|
||||
params = [content_type_id]
|
||||
else:
|
||||
extra_where = " AND %s.%s IN (%s)" % (qn(alias_to_join),
|
||||
qn(extra_col),
|
||||
','.join(['%s'] *
|
||||
len(content_type_ids)))
|
||||
params = content_type_ids
|
||||
return extra_where, params
|
||||
|
||||
# This and all the methods till the end of class are only used in django >= 1.6
|
||||
def _get_mm_case_path_info(self, direct=False):
|
||||
pathinfos = []
|
||||
linkfield1 = _get_field(self.through, 'content_object')
|
||||
linkfield2 = _get_field(self.through, self.m2m_reverse_field_name())
|
||||
if direct:
|
||||
join1infos = linkfield1.get_reverse_path_info()
|
||||
join2infos = linkfield2.get_path_info()
|
||||
else:
|
||||
join1infos = linkfield2.get_reverse_path_info()
|
||||
join2infos = linkfield1.get_path_info()
|
||||
pathinfos.extend(join1infos)
|
||||
pathinfos.extend(join2infos)
|
||||
return pathinfos
|
||||
|
||||
def _get_gfk_case_path_info(self, direct=False):
|
||||
pathinfos = []
|
||||
from_field = self.model._meta.pk
|
||||
opts = self.through._meta
|
||||
linkfield = _get_field(self.through, self.m2m_reverse_field_name())
|
||||
if direct:
|
||||
join1infos = [PathInfo(self.model._meta, opts, [from_field], self.rel, True, False)]
|
||||
join2infos = linkfield.get_path_info()
|
||||
else:
|
||||
join1infos = linkfield.get_reverse_path_info()
|
||||
join2infos = [PathInfo(opts, self.model._meta, [from_field], self, True, False)]
|
||||
pathinfos.extend(join1infos)
|
||||
pathinfos.extend(join2infos)
|
||||
return pathinfos
|
||||
|
||||
def get_path_info(self):
|
||||
if self.use_gfk:
|
||||
return self._get_gfk_case_path_info(direct=True)
|
||||
else:
|
||||
return self._get_mm_case_path_info(direct=True)
|
||||
|
||||
def get_reverse_path_info(self):
|
||||
if self.use_gfk:
|
||||
return self._get_gfk_case_path_info(direct=False)
|
||||
else:
|
||||
return self._get_mm_case_path_info(direct=False)
|
||||
|
||||
def get_joining_columns(self, reverse_join=False):
|
||||
if reverse_join:
|
||||
return ((self.model._meta.pk.column, "object_id"),)
|
||||
else:
|
||||
return (("object_id", self.model._meta.pk.column),)
|
||||
|
||||
def get_extra_restriction(self, where_class, alias, related_alias):
|
||||
extra_col = _get_field(self.through, 'content_type').column
|
||||
content_type_ids = [ContentType.objects.get_for_model(subclass).pk
|
||||
for subclass in _get_subclasses(self.model)]
|
||||
return ExtraJoinRestriction(related_alias, extra_col, content_type_ids)
|
||||
|
||||
def get_reverse_joining_columns(self):
|
||||
return self.get_joining_columns(reverse_join=True)
|
||||
|
||||
@property
|
||||
def related_fields(self):
|
||||
return [(_get_field(self.through, 'object_id'), self.model._meta.pk)]
|
||||
|
||||
@property
|
||||
def foreign_related_fields(self):
|
||||
return [self.related_fields[0][1]]
|
||||
|
||||
|
||||
def _get_subclasses(model):
|
||||
subclasses = [model]
|
||||
if VERSION < (1, 8):
|
||||
all_fields = (_get_field(model, f) for f in model._meta.get_all_field_names())
|
||||
else:
|
||||
all_fields = model._meta.get_fields()
|
||||
for field in all_fields:
|
||||
# Django 1.8 +
|
||||
if (not RelatedObject and isinstance(field, OneToOneRel) and
|
||||
getattr(field.field.rel, "parent_link", None)):
|
||||
subclasses.extend(_get_subclasses(field.related_model))
|
||||
|
||||
# < Django 1.8
|
||||
if (RelatedObject and isinstance(field, RelatedObject) and
|
||||
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)
|
|
@ -0,0 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('name', models.CharField(help_text='', unique=True, max_length=100, verbose_name='Name')),
|
||||
('slug', models.SlugField(help_text='', unique=True, max_length=100, verbose_name='Slug')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Tag',
|
||||
'verbose_name_plural': 'Tags',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TaggedItem',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('object_id', models.IntegerField(help_text='', verbose_name='Object id', db_index=True)),
|
||||
('content_type', models.ForeignKey(related_name='taggit_taggeditem_tagged_items', verbose_name='Content type', to='contenttypes.ContentType', help_text='', on_delete=models.CASCADE)),
|
||||
('tag', models.ForeignKey(related_name='taggit_taggeditem_items', to='taggit.Tag', help_text='', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Tagged Item',
|
||||
'verbose_name_plural': 'Tagged Items',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('taggit', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='taggeditem',
|
||||
index_together=set([('content_type', 'object_id')]),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Django migrations for taggit app
|
||||
|
||||
This package does not contain South migrations. South migrations can be found
|
||||
in the ``south_migrations`` package.
|
||||
"""
|
||||
|
||||
SOUTH_ERROR_MESSAGE = """\n
|
||||
For South support, customize the SOUTH_MIGRATION_MODULES setting like so:
|
||||
|
||||
SOUTH_MIGRATION_MODULES = {
|
||||
'taggit': 'taggit.south_migrations',
|
||||
}
|
||||
"""
|
||||
|
||||
# Ensure the user is not using Django 1.6 or below with South
|
||||
try:
|
||||
from django.db import migrations # noqa
|
||||
except ImportError:
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured(SOUTH_ERROR_MESSAGE)
|
|
@ -0,0 +1,225 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import django
|
||||
from django import VERSION
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import IntegrityError, models, transaction
|
||||
from django.db.models.query import QuerySet
|
||||
from django.template.defaultfilters import slugify as default_slugify
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ugettext
|
||||
|
||||
from taggit.utils import _get_field
|
||||
try:
|
||||
from unidecode import unidecode
|
||||
except ImportError:
|
||||
unidecode = lambda tag: tag
|
||||
|
||||
|
||||
try:
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
except ImportError: # django < 1.7
|
||||
from django.contrib.contenttypes.generic import GenericForeignKey
|
||||
|
||||
|
||||
try:
|
||||
atomic = transaction.atomic
|
||||
except AttributeError:
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def atomic(using=None):
|
||||
sid = transaction.savepoint(using=using)
|
||||
try:
|
||||
yield
|
||||
except IntegrityError:
|
||||
transaction.savepoint_rollback(sid, using=using)
|
||||
raise
|
||||
else:
|
||||
transaction.savepoint_commit(sid, using=using)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
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 __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pk and not self.slug:
|
||||
self.slug = self.slugify(self.name)
|
||||
from django.db import router
|
||||
using = kwargs.get("using") or router.db_for_write(
|
||||
type(self), instance=self)
|
||||
# Make sure we write to the same db for all attempted writes,
|
||||
# with a multi-master setup, theoretically we could try to
|
||||
# write and rollback on different DBs
|
||||
kwargs["using"] = using
|
||||
# Be oportunistic and try to save the tag, this should work for
|
||||
# most cases ;)
|
||||
try:
|
||||
with atomic(using=using):
|
||||
res = super(TagBase, self).save(*args, **kwargs)
|
||||
return res
|
||||
except IntegrityError:
|
||||
pass
|
||||
# Now try to find existing slugs with similar names
|
||||
slugs = set(
|
||||
self.__class__._default_manager
|
||||
.filter(slug__startswith=self.slug)
|
||||
.values_list('slug', flat=True)
|
||||
)
|
||||
i = 1
|
||||
while True:
|
||||
slug = self.slugify(self.name, i)
|
||||
if slug not in slugs:
|
||||
self.slug = slug
|
||||
# We purposely ignore concurrecny issues here for now.
|
||||
# (That is, till we found a nice solution...)
|
||||
return super(TagBase, self).save(*args, **kwargs)
|
||||
i += 1
|
||||
else:
|
||||
return super(TagBase, self).save(*args, **kwargs)
|
||||
|
||||
def slugify(self, tag, i=None):
|
||||
slug = default_slugify(unidecode(tag))
|
||||
if i is not None:
|
||||
slug += "_%d" % i
|
||||
return slug
|
||||
|
||||
|
||||
class Tag(TagBase):
|
||||
class Meta:
|
||||
verbose_name = _("Tag")
|
||||
verbose_name_plural = _("Tags")
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ItemBase(models.Model):
|
||||
def __str__(self):
|
||||
return ugettext("%(object)s tagged with %(tag)s") % {
|
||||
"object": self.content_object,
|
||||
"tag": self.tag
|
||||
}
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def tag_model(cls):
|
||||
return _get_field(cls, 'tag').rel.to
|
||||
|
||||
@classmethod
|
||||
def tag_relname(cls):
|
||||
return _get_field(cls, 'tag').rel.related_name
|
||||
|
||||
@classmethod
|
||||
def lookup_kwargs(cls, instance):
|
||||
return {
|
||||
'content_object': instance
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def bulk_lookup_kwargs(cls, instances):
|
||||
return {
|
||||
"content_object__in": instances,
|
||||
}
|
||||
|
||||
|
||||
class TaggedItemBase(ItemBase):
|
||||
tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def tags_for(cls, model, instance=None, **extra_filters):
|
||||
kwargs = extra_filters or {}
|
||||
if instance is not None:
|
||||
kwargs.update({
|
||||
'%s__content_object' % cls.tag_relname(): instance
|
||||
})
|
||||
return cls.tag_model().objects.filter(**kwargs)
|
||||
kwargs.update({
|
||||
'%s__content_object__isnull' % cls.tag_relname(): False
|
||||
})
|
||||
return cls.tag_model().objects.filter(**kwargs).distinct()
|
||||
|
||||
|
||||
class CommonGenericTaggedItemBase(ItemBase):
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_('Content type'),
|
||||
related_name="%(app_label)s_%(class)s_tagged_items"
|
||||
)
|
||||
content_object = GenericForeignKey()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def lookup_kwargs(cls, instance):
|
||||
return {
|
||||
'object_id': instance.pk,
|
||||
'content_type': ContentType.objects.get_for_model(instance)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def bulk_lookup_kwargs(cls, instances):
|
||||
if isinstance(instances, QuerySet):
|
||||
# Can do a real object_id IN (SELECT ..) query.
|
||||
return {
|
||||
"object_id__in": instances,
|
||||
"content_type": ContentType.objects.get_for_model(instances.model),
|
||||
}
|
||||
else:
|
||||
# TODO: instances[0], can we assume there are instances.
|
||||
return {
|
||||
"object_id__in": [instance.pk for instance in instances],
|
||||
"content_type": ContentType.objects.get_for_model(instances[0]),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def tags_for(cls, model, instance=None, **extra_filters):
|
||||
ct = ContentType.objects.get_for_model(model)
|
||||
kwargs = {
|
||||
"%s__content_type" % cls.tag_relname(): ct
|
||||
}
|
||||
if instance is not None:
|
||||
kwargs["%s__object_id" % cls.tag_relname()] = instance.pk
|
||||
if extra_filters:
|
||||
kwargs.update(extra_filters)
|
||||
return cls.tag_model().objects.filter(**kwargs).distinct()
|
||||
|
||||
|
||||
class GenericTaggedItemBase(CommonGenericTaggedItemBase):
|
||||
object_id = models.IntegerField(verbose_name=_('Object id'), db_index=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
if VERSION >= (1, 8):
|
||||
|
||||
class GenericUUIDTaggedItemBase(CommonGenericTaggedItemBase):
|
||||
object_id = models.UUIDField(verbose_name=_('Object id'), db_index=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class TaggedItem(GenericTaggedItemBase, TaggedItemBase):
|
||||
class Meta:
|
||||
verbose_name = _("Tagged Item")
|
||||
verbose_name_plural = _("Tagged Items")
|
||||
if django.VERSION >= (1, 5):
|
||||
index_together = [
|
||||
["content_type", "object_id"],
|
||||
]
|
|
@ -0,0 +1,60 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'Tag'
|
||||
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('taggit', ['Tag'])
|
||||
|
||||
# Adding model 'TaggedItem'
|
||||
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='taggit_taggeditem_tagged_items', to=orm['contenttypes.ContentType'])),
|
||||
))
|
||||
db.send_create_signal('taggit', ['TaggedItem'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'Tag'
|
||||
db.delete_table('taggit_tag')
|
||||
|
||||
# Deleting model 'TaggedItem'
|
||||
db.delete_table('taggit_taggeditem')
|
||||
|
||||
|
||||
models = {
|
||||
'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'}),
|
||||
'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'})
|
||||
},
|
||||
'taggit.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
'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'})
|
||||
},
|
||||
'taggit.taggeditem': {
|
||||
'Meta': {'object_name': 'TaggedItem'},
|
||||
'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': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['taggit']
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding unique constraint on 'Tag', fields ['name']
|
||||
db.create_unique('taggit_tag', ['name'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'Tag', fields ['name']
|
||||
db.delete_unique('taggit_tag', ['name'])
|
||||
|
||||
|
||||
models = {
|
||||
'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'}),
|
||||
'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'})
|
||||
},
|
||||
'taggit.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
'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'})
|
||||
},
|
||||
'taggit.taggeditem': {
|
||||
'Meta': {'object_name': 'TaggedItem'},
|
||||
'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': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['taggit']
|
|
@ -0,0 +1,137 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django import VERSION
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.functional import wraps
|
||||
|
||||
|
||||
def _get_field(model, name):
|
||||
if VERSION < (1, 8):
|
||||
return model._meta.get_field_by_name(name)[0]
|
||||
else:
|
||||
return model._meta.get_field(name)
|
||||
|
||||
|
||||
def parse_tags(tagstring):
|
||||
"""
|
||||
Parses tag input, with multiple word input being activated and
|
||||
delineated by commas and double quotes. Quotes take precedence, so
|
||||
they may contain commas.
|
||||
|
||||
Returns a sorted list of unique tag names.
|
||||
|
||||
Ported from Jonathan Buchanan's `django-tagging
|
||||
<http://django-tagging.googlecode.com/>`_
|
||||
"""
|
||||
if not tagstring:
|
||||
return []
|
||||
|
||||
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 ',' not in tagstring and '"' not in tagstring:
|
||||
words = list(set(split_strip(tagstring, ' ')))
|
||||
words.sort()
|
||||
return words
|
||||
|
||||
words = []
|
||||
buffer = []
|
||||
# Defer splitting of non-quoted sections until we know if there are
|
||||
# any unquoted commas.
|
||||
to_be_split = []
|
||||
saw_loose_comma = False
|
||||
open_quote = False
|
||||
i = iter(tagstring)
|
||||
try:
|
||||
while True:
|
||||
c = six.next(i)
|
||||
if c == '"':
|
||||
if buffer:
|
||||
to_be_split.append(''.join(buffer))
|
||||
buffer = []
|
||||
# Find the matching quote
|
||||
open_quote = True
|
||||
c = six.next(i)
|
||||
while c != '"':
|
||||
buffer.append(c)
|
||||
c = six.next(i)
|
||||
if buffer:
|
||||
word = ''.join(buffer).strip()
|
||||
if word:
|
||||
words.append(word)
|
||||
buffer = []
|
||||
open_quote = False
|
||||
else:
|
||||
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 ',' in buffer:
|
||||
saw_loose_comma = True
|
||||
to_be_split.append(''.join(buffer))
|
||||
if to_be_split:
|
||||
if saw_loose_comma:
|
||||
delimiter = ','
|
||||
else:
|
||||
delimiter = ' '
|
||||
for chunk in to_be_split:
|
||||
words.extend(split_strip(chunk, delimiter))
|
||||
words = list(set(words))
|
||||
words.sort()
|
||||
return words
|
||||
|
||||
|
||||
def split_strip(string, delimiter=','):
|
||||
"""
|
||||
Splits ``string`` on ``delimiter``, stripping each resulting string
|
||||
and returning a list of non-empty strings.
|
||||
|
||||
Ported from Jonathan Buchanan's `django-tagging
|
||||
<http://django-tagging.googlecode.com/>`_
|
||||
"""
|
||||
if not string:
|
||||
return []
|
||||
|
||||
words = [w.strip() for w in string.split(delimiter)]
|
||||
return [w for w in words if w]
|
||||
|
||||
|
||||
def edit_string_for_tags(tags):
|
||||
"""
|
||||
Given list of ``Tag`` instances, creates a string representation of
|
||||
the list suitable for editing by the user, such that submitting the
|
||||
given string representation back without changing it will give the
|
||||
same list of tags.
|
||||
|
||||
Tag names which contain commas will be double quoted.
|
||||
|
||||
If any tag name which isn't being quoted contains whitespace, the
|
||||
resulting string of tag names will be comma-delimited, otherwise
|
||||
it will be space-delimited.
|
||||
|
||||
Ported from Jonathan Buchanan's `django-tagging
|
||||
<http://django-tagging.googlecode.com/>`_
|
||||
"""
|
||||
names = []
|
||||
for tag in tags:
|
||||
name = tag.name
|
||||
if ',' in name or ' ' in name:
|
||||
names.append('"%s"' % name)
|
||||
else:
|
||||
names.append(name)
|
||||
return ', '.join(sorted(names))
|
||||
|
||||
|
||||
def require_instance_manager(func):
|
||||
@wraps(func)
|
||||
def inner(self, *args, **kwargs):
|
||||
if self.instance is None:
|
||||
raise TypeError("Can't call %s with a non-instance manager" % func.__name__)
|
||||
return func(self, *args, **kwargs)
|
||||
return inner
|
|
@ -0,0 +1,20 @@
|
|||
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 import ListView
|
||||
|
||||
from taggit.models import Tag, TaggedItem
|
||||
|
||||
|
||||
def tagged_object_list(request, slug, queryset, **kwargs):
|
||||
if callable(queryset):
|
||||
queryset = queryset()
|
||||
tag = get_object_or_404(Tag, slug=slug)
|
||||
qs = queryset.filter(pk__in=TaggedItem.objects.filter(
|
||||
tag=tag, content_type=ContentType.objects.get_for_model(queryset.model)
|
||||
).values_list("object_id", flat=True))
|
||||
if "extra_context" not in kwargs:
|
||||
kwargs["extra_context"] = {}
|
||||
kwargs["extra_context"]["tag"] = tag
|
||||
return ListView.as_view(request, qs, **kwargs)
|
|
@ -0,0 +1,36 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django import forms, VERSION
|
||||
|
||||
from .models import (CustomPKFood, DirectCustomPKFood, DirectFood, Food,
|
||||
OfficialFood)
|
||||
|
||||
fields = None
|
||||
if VERSION >= (1, 6):
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FoodForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Food
|
||||
fields = fields
|
||||
|
||||
class DirectFoodForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = DirectFood
|
||||
fields = fields
|
||||
|
||||
class DirectCustomPKFoodForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = DirectCustomPKFood
|
||||
fields = fields
|
||||
|
||||
class CustomPKFoodForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = CustomPKFood
|
||||
fields = fields
|
||||
|
||||
class OfficialFoodForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = OfficialFood
|
||||
fields = fields
|
|
@ -0,0 +1,468 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import taggit.managers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('taggit', '0001_initial'),
|
||||
('contenttypes', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Article',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('title', models.CharField(help_text='', max_length=100)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomManager',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomPKFood',
|
||||
fields=[
|
||||
('name', models.CharField(help_text='', max_length=50, serialize=False, primary_key=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomPKPet',
|
||||
fields=[
|
||||
('name', models.CharField(help_text='', max_length=50, serialize=False, primary_key=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomPKHousePet',
|
||||
fields=[
|
||||
('custompkpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.CustomPKPet', help_text='')),
|
||||
('trained', models.BooleanField(default=False, help_text='')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=('tests.custompkpet',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DirectCustomPKFood',
|
||||
fields=[
|
||||
('name', models.CharField(help_text='', max_length=50, serialize=False, primary_key=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DirectCustomPKPet',
|
||||
fields=[
|
||||
('name', models.CharField(help_text='', max_length=50, serialize=False, primary_key=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DirectCustomPKHousePet',
|
||||
fields=[
|
||||
('directcustompkpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.DirectCustomPKPet', help_text='')),
|
||||
('trained', models.BooleanField(default=False, help_text='')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=('tests.directcustompkpet',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DirectFood',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('name', models.CharField(help_text='', max_length=50)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DirectPet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('name', models.CharField(help_text='', max_length=50)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DirectHousePet',
|
||||
fields=[
|
||||
('directpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.DirectPet', help_text='')),
|
||||
('trained', models.BooleanField(default=False, help_text='')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=('tests.directpet',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Food',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('name', models.CharField(help_text='', max_length=50)),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Movie',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MultipleTags',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MultipleTagsGFK',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('tags1', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OfficialFood',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('name', models.CharField(help_text='', max_length=50)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OfficialPet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('name', models.CharField(help_text='', max_length=50)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OfficialHousePet',
|
||||
fields=[
|
||||
('officialpet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.OfficialPet', help_text='')),
|
||||
('trained', models.BooleanField(default=False, help_text='')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=('tests.officialpet',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OfficialTag',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('name', models.CharField(help_text='', unique=True, max_length=100, verbose_name='Name')),
|
||||
('slug', models.SlugField(help_text='', unique=True, max_length=100, verbose_name='Slug')),
|
||||
('official', models.BooleanField(default=False, help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OfficialThroughModel',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('object_id', models.IntegerField(help_text='', verbose_name='Object id', db_index=True)),
|
||||
('content_type', models.ForeignKey(related_name='tests_officialthroughmodel_tagged_items', verbose_name='Content type', to='contenttypes.ContentType', help_text='')),
|
||||
('tag', models.ForeignKey(related_name='tagged_items', to='tests.OfficialTag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Parent',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Child',
|
||||
fields=[
|
||||
('parent_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.Parent', help_text='')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=('tests.parent',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Pet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('name', models.CharField(help_text='', max_length=50)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HousePet',
|
||||
fields=[
|
||||
('pet_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='tests.Pet', help_text='')),
|
||||
('trained', models.BooleanField(default=False, help_text='')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=('tests.pet',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Photo',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TaggedCustomPK',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('object_id', models.CharField(help_text='', max_length=50, verbose_name='Object id', db_index=True)),
|
||||
('content_type', models.ForeignKey(related_name='tests_taggedcustompk_tagged_items', verbose_name='Content type', to='contenttypes.ContentType', help_text='', on_delete=models.CASCADE)),
|
||||
('tag', models.ForeignKey(related_name='tests_taggedcustompk_items', to='taggit.Tag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TaggedCustomPKFood',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('content_object', models.ForeignKey(help_text='', to='tests.DirectCustomPKFood')),
|
||||
('tag', models.ForeignKey(related_name='tests_taggedcustompkfood_items', to='taggit.Tag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TaggedCustomPKPet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('content_object', models.ForeignKey(help_text='', to='tests.DirectCustomPKPet')),
|
||||
('tag', models.ForeignKey(related_name='tests_taggedcustompkpet_items', to='taggit.Tag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TaggedFood',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('content_object', models.ForeignKey(help_text='', to='tests.DirectFood')),
|
||||
('tag', models.ForeignKey(related_name='tests_taggedfood_items', to='taggit.Tag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TaggedPet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('content_object', models.ForeignKey(help_text='', to='tests.DirectPet')),
|
||||
('tag', models.ForeignKey(related_name='tests_taggedpet_items', to='taggit.Tag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Through1',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('content_object', models.ForeignKey(help_text='', to='tests.MultipleTags')),
|
||||
('tag', models.ForeignKey(related_name='tests_through1_items', to='taggit.Tag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Through2',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('content_object', models.ForeignKey(help_text='', to='tests.MultipleTags')),
|
||||
('tag', models.ForeignKey(related_name='tests_through2_items', to='taggit.Tag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ThroughGFK',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, help_text='', verbose_name='ID')),
|
||||
('object_id', models.IntegerField(help_text='', verbose_name='Object id', db_index=True)),
|
||||
('content_type', models.ForeignKey(related_name='tests_throughgfk_tagged_items', verbose_name='Content type', to='contenttypes.ContentType', help_text='')),
|
||||
('tag', models.ForeignKey(related_name='tagged_items', to='taggit.Tag', help_text='')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='pet',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='parent',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='officialpet',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='tests.OfficialTag', through='tests.OfficialThroughModel', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='officialfood',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='tests.OfficialTag', through='tests.OfficialThroughModel', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='multipletagsgfk',
|
||||
name='tags2',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.ThroughGFK', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='multipletags',
|
||||
name='tags1',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.Through1', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='multipletags',
|
||||
name='tags2',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.Through2', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='custompkpet',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedCustomPK', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='custompkfood',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedCustomPK', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='directpet',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedPet', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='directfood',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedFood', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='directcustompkpet',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedCustomPKPet', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='directcustompkfood',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.TaggedCustomPKFood', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ArticleTag',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
},
|
||||
bases=('taggit.tag',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ArticleTaggedItem',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
},
|
||||
bases=('taggit.taggeditem',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(to='taggit.Tag', through='tests.ArticleTaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags'),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
|
@ -0,0 +1,228 @@
|
|||
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 (CommonGenericTaggedItemBase, GenericTaggedItemBase,
|
||||
Tag, TagBase, TaggedItem, TaggedItemBase)
|
||||
|
||||
|
||||
# Ensure that two TaggableManagers with custom through model are allowed.
|
||||
class Through1(TaggedItemBase):
|
||||
content_object = models.ForeignKey('MultipleTags')
|
||||
|
||||
|
||||
class Through2(TaggedItemBase):
|
||||
content_object = models.ForeignKey('MultipleTags')
|
||||
|
||||
|
||||
class MultipleTags(models.Model):
|
||||
tags1 = TaggableManager(through=Through1, related_name='tags1')
|
||||
tags2 = TaggableManager(through=Through2, related_name='tags2')
|
||||
|
||||
# Ensure that two TaggableManagers with GFK via different through models are allowed.
|
||||
class ThroughGFK(GenericTaggedItemBase):
|
||||
tag = models.ForeignKey(Tag, related_name='tagged_items')
|
||||
|
||||
class MultipleTagsGFK(models.Model):
|
||||
tags1 = TaggableManager(related_name='tagsgfk1')
|
||||
tags2 = TaggableManager(through=ThroughGFK, related_name='tagsgfk2')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Food(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
tags = TaggableManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Pet(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
tags = TaggableManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class HousePet(Pet):
|
||||
trained = models.BooleanField(default=False)
|
||||
|
||||
|
||||
# Test direct-tagging with custom through model
|
||||
|
||||
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 __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class DirectHousePet(DirectPet):
|
||||
trained = models.BooleanField(default=False)
|
||||
|
||||
|
||||
# Test custom through model to model with custom PK
|
||||
|
||||
class TaggedCustomPKFood(TaggedItemBase):
|
||||
content_object = models.ForeignKey('DirectCustomPKFood')
|
||||
|
||||
class TaggedCustomPKPet(TaggedItemBase):
|
||||
content_object = models.ForeignKey('DirectCustomPKPet')
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DirectCustomPKFood(models.Model):
|
||||
name = models.CharField(max_length=50, primary_key=True)
|
||||
|
||||
tags = TaggableManager(through=TaggedCustomPKFood)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class DirectCustomPKPet(models.Model):
|
||||
name = models.CharField(max_length=50, primary_key=True)
|
||||
|
||||
tags = TaggableManager(through=TaggedCustomPKPet)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class DirectCustomPKHousePet(DirectCustomPKPet):
|
||||
trained = models.BooleanField(default=False)
|
||||
|
||||
# Test custom through model to model with custom PK using GenericForeignKey
|
||||
|
||||
class TaggedCustomPK(CommonGenericTaggedItemBase, TaggedItemBase):
|
||||
object_id = models.CharField(max_length=50, verbose_name='Object id', db_index=True)
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CustomPKFood(models.Model):
|
||||
name = models.CharField(max_length=50, primary_key=True)
|
||||
|
||||
tags = TaggableManager(through=TaggedCustomPK)
|
||||
|
||||
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=TaggedCustomPK)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class CustomPKHousePet(CustomPKPet):
|
||||
trained = models.BooleanField(default=False)
|
||||
|
||||
# Test custom through model to a custom tag model
|
||||
|
||||
class OfficialTag(TagBase):
|
||||
official = models.BooleanField(default=False)
|
||||
|
||||
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 __str__(self):
|
||||
return self.name
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class OfficialPet(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
tags = TaggableManager(through=OfficialThroughModel)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class OfficialHousePet(OfficialPet):
|
||||
trained = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class Media(models.Model):
|
||||
tags = TaggableManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class Photo(Media):
|
||||
pass
|
||||
|
||||
class Movie(Media):
|
||||
pass
|
||||
|
||||
|
||||
class ArticleTag(Tag):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
def slugify(self, tag, i=None):
|
||||
slug = "category-%s" % tag.lower()
|
||||
|
||||
if i is not None:
|
||||
slug += "-%d" % i
|
||||
return slug
|
||||
|
||||
|
||||
class ArticleTaggedItem(TaggedItem):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
@classmethod
|
||||
def tag_model(self):
|
||||
return ArticleTag
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
|
||||
tags = TaggableManager(through=ArticleTaggedItem)
|
||||
|
||||
|
||||
class CustomManager(models.Model):
|
||||
class Foo(object):
|
||||
def __init__(*args, **kwargs):
|
||||
pass
|
||||
|
||||
tags = TaggableManager(manager=Foo)
|
||||
|
||||
|
||||
class Parent(models.Model):
|
||||
tags = TaggableManager()
|
||||
|
||||
|
||||
class Child(Parent):
|
||||
pass
|
|
@ -0,0 +1,659 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from unittest import TestCase as UnitTestCase
|
||||
|
||||
import django
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core import serializers
|
||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||
from django.db import connection, models
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from .forms import (CustomPKFoodForm, DirectCustomPKFoodForm, DirectFoodForm,
|
||||
FoodForm, OfficialFoodForm)
|
||||
from .models import (Article, Child, CustomManager, CustomPKFood,
|
||||
CustomPKHousePet, CustomPKPet, DirectCustomPKFood,
|
||||
DirectCustomPKHousePet, DirectCustomPKPet, DirectFood,
|
||||
DirectHousePet, DirectPet, Food, HousePet, Movie,
|
||||
OfficialFood, OfficialHousePet, OfficialPet,
|
||||
OfficialTag, OfficialThroughModel, Pet, Photo,
|
||||
TaggedCustomPK, TaggedCustomPKFood, TaggedCustomPKPet,
|
||||
TaggedFood, TaggedPet)
|
||||
|
||||
from taggit.managers import _model_name, _TaggableManager, TaggableManager
|
||||
from taggit.models import Tag, TaggedItem
|
||||
|
||||
from taggit.utils import edit_string_for_tags, parse_tags
|
||||
|
||||
try:
|
||||
from unittest import skipIf, skipUnless
|
||||
except ImportError:
|
||||
from django.utils.unittest import skipIf, skipUnless
|
||||
|
||||
|
||||
class BaseTaggingTest(object):
|
||||
def assert_tags_equal(self, qs, tags, sort=True, attr="name"):
|
||||
got = [getattr(obj, attr) for obj in qs]
|
||||
if sort:
|
||||
got.sort()
|
||||
tags.sort()
|
||||
self.assertEqual(got, tags)
|
||||
|
||||
def _get_form_str(self, form_str):
|
||||
if django.VERSION >= (1, 3):
|
||||
form_str %= {
|
||||
"help_start": '<span class="helptext">',
|
||||
"help_stop": "</span>"
|
||||
}
|
||||
else:
|
||||
form_str %= {
|
||||
"help_start": "",
|
||||
"help_stop": ""
|
||||
}
|
||||
return form_str
|
||||
|
||||
def assert_form_renders(self, form, html):
|
||||
self.assertHTMLEqual(str(form), self._get_form_str(html))
|
||||
|
||||
|
||||
class BaseTaggingTestCase(TestCase, BaseTaggingTest):
|
||||
pass
|
||||
|
||||
|
||||
class BaseTaggingTransactionTestCase(TransactionTestCase, BaseTaggingTest):
|
||||
pass
|
||||
|
||||
|
||||
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)
|
||||
|
||||
def test_slugify(self):
|
||||
a = Article.objects.create(title="django-taggit 1.0 Released")
|
||||
a.tags.add("awesome", "release", "AWESOME")
|
||||
self.assert_tags_equal(a.tags.all(), [
|
||||
"category-awesome",
|
||||
"category-release",
|
||||
"category-awesome-1"
|
||||
], attr="slug")
|
||||
|
||||
def test_integers(self):
|
||||
"""Adding an integer as a tag should raise a ValueError (#237)."""
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
with self.assertRaisesRegexp(ValueError, (
|
||||
r"Cannot add 1 \(<(type|class) 'int'>\). "
|
||||
r"Expected <class 'django.db.models.base.ModelBase'> or str.")):
|
||||
apple.tags.add(1)
|
||||
|
||||
class TagModelDirectTestCase(TagModelTestCase):
|
||||
food_model = DirectFood
|
||||
tag_model = Tag
|
||||
|
||||
class TagModelDirectCustomPKTestCase(TagModelTestCase):
|
||||
food_model = DirectCustomPKFood
|
||||
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")
|
||||
self.assertEqual(list(apple.tags.all()), [])
|
||||
self.assertEqual(list(self.food_model.tags.all()), [])
|
||||
|
||||
apple.tags.add('green')
|
||||
self.assert_tags_equal(apple.tags.all(), ['green'])
|
||||
self.assert_tags_equal(self.food_model.tags.all(), ['green'])
|
||||
|
||||
pear = self.food_model.objects.create(name="pear")
|
||||
pear.tags.add('green')
|
||||
self.assert_tags_equal(pear.tags.all(), ['green'])
|
||||
self.assert_tags_equal(self.food_model.tags.all(), ['green'])
|
||||
|
||||
apple.tags.add('red')
|
||||
self.assert_tags_equal(apple.tags.all(), ['green', 'red'])
|
||||
self.assert_tags_equal(self.food_model.tags.all(), ['green', 'red'])
|
||||
|
||||
self.assert_tags_equal(
|
||||
self.food_model.tags.most_common(),
|
||||
['green', 'red'],
|
||||
sort=False
|
||||
)
|
||||
|
||||
apple.tags.remove('green')
|
||||
self.assert_tags_equal(apple.tags.all(), ['red'])
|
||||
self.assert_tags_equal(self.food_model.tags.all(), ['green', 'red'])
|
||||
tag = self.tag_model.objects.create(name="delicious")
|
||||
apple.tags.add(tag)
|
||||
self.assert_tags_equal(apple.tags.all(), ["red", "delicious"])
|
||||
|
||||
apple.delete()
|
||||
self.assert_tags_equal(self.food_model.tags.all(), ["green"])
|
||||
|
||||
def test_add_queries(self):
|
||||
# Prefill content type cache:
|
||||
ContentType.objects.get_for_model(self.food_model)
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
# 1 query to see which tags exist
|
||||
# + 3 queries to create the tags.
|
||||
# + 6 queries to create the intermediary things (including SELECTs, to
|
||||
# make sure we don't double create.
|
||||
# + 12 on Django 1.6 for save points.
|
||||
queries = 22
|
||||
if django.VERSION < (1, 6):
|
||||
queries -= 12
|
||||
self.assertNumQueries(queries, apple.tags.add, "red", "delicious", "green")
|
||||
|
||||
pear = self.food_model.objects.create(name="pear")
|
||||
# 1 query to see which tags exist
|
||||
# + 4 queries to create the intermeidary things (including SELECTs, to
|
||||
# make sure we dont't double create.
|
||||
# + 4 on Django 1.6 for save points.
|
||||
queries = 9
|
||||
if django.VERSION < (1, 6):
|
||||
queries -= 4
|
||||
self.assertNumQueries(queries, pear.tags.add, "green", "delicious")
|
||||
|
||||
self.assertNumQueries(0, pear.tags.add)
|
||||
|
||||
def test_require_pk(self):
|
||||
food_instance = self.food_model()
|
||||
self.assertRaises(ValueError, lambda: food_instance.tags.all())
|
||||
|
||||
def test_delete_obj(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add("red")
|
||||
self.assert_tags_equal(apple.tags.all(), ["red"])
|
||||
strawberry = self.food_model.objects.create(name="strawberry")
|
||||
strawberry.tags.add("red")
|
||||
apple.delete()
|
||||
self.assert_tags_equal(strawberry.tags.all(), ["red"])
|
||||
|
||||
def test_delete_bulk(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
kitty = self.pet_model.objects.create(pk=apple.pk, name="kitty")
|
||||
|
||||
apple.tags.add("red", "delicious", "fruit")
|
||||
kitty.tags.add("feline")
|
||||
|
||||
self.food_model.objects.all().delete()
|
||||
|
||||
self.assert_tags_equal(kitty.tags.all(), ["feline"])
|
||||
|
||||
def test_lookup_by_tag(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
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__name__in=["red"])),
|
||||
[apple]
|
||||
)
|
||||
self.assertEqual(
|
||||
list(self.food_model.objects.filter(tags__name__in=["green"])),
|
||||
[apple, pear]
|
||||
)
|
||||
|
||||
kitty = self.pet_model.objects.create(name="kitty")
|
||||
kitty.tags.add("fuzzy", "red")
|
||||
dog = self.pet_model.objects.create(name="dog")
|
||||
dog.tags.add("woof", "red")
|
||||
self.assertEqual(
|
||||
list(self.food_model.objects.filter(tags__name__in=["red"]).distinct()),
|
||||
[apple]
|
||||
)
|
||||
|
||||
tag = self.tag_model.objects.get(name="woof")
|
||||
self.assertEqual(list(self.pet_model.objects.filter(tags__in=[tag])), [dog])
|
||||
|
||||
cat = self.housepet_model.objects.create(name="cat", trained=True)
|
||||
cat.tags.add("fuzzy")
|
||||
|
||||
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_lookup_bulk(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
pear = self.food_model.objects.create(name="pear")
|
||||
apple.tags.add('fruit', 'green')
|
||||
pear.tags.add('fruit', 'yummie')
|
||||
|
||||
def lookup_qs():
|
||||
# New fix: directly allow WHERE object_id IN (SELECT id FROM ..)
|
||||
objects = self.food_model.objects.all()
|
||||
lookup = self.taggeditem_model.bulk_lookup_kwargs(objects)
|
||||
list(self.taggeditem_model.objects.filter(**lookup))
|
||||
|
||||
def lookup_list():
|
||||
# Simulate old situation: iterate over a list.
|
||||
objects = list(self.food_model.objects.all())
|
||||
lookup = self.taggeditem_model.bulk_lookup_kwargs(objects)
|
||||
list(self.taggeditem_model.objects.filter(**lookup))
|
||||
|
||||
self.assertNumQueries(1, lookup_qs)
|
||||
self.assertNumQueries(2, lookup_list)
|
||||
|
||||
def test_exclude(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add("red", "green", "delicious")
|
||||
|
||||
pear = self.food_model.objects.create(name="pear")
|
||||
pear.tags.add("green", "delicious")
|
||||
|
||||
self.food_model.objects.create(name="guava")
|
||||
|
||||
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"""
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add("green", "juicy", "small", "sour")
|
||||
|
||||
pear = self.food_model.objects.create(name="pear")
|
||||
pear.tags.add("green", "juicy", "small", "sweet")
|
||||
|
||||
watermelon = self.food_model.objects.create(name="watermelon")
|
||||
watermelon.tags.add("green", "juicy", "large", "sweet")
|
||||
|
||||
similar_objs = apple.tags.similar_objects()
|
||||
self.assertEqual(similar_objs, [pear, watermelon])
|
||||
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")
|
||||
apple.tags.add("juicy", "juicy")
|
||||
self.assert_tags_equal(apple.tags.all(), ['juicy'])
|
||||
|
||||
def test_query_traverse(self):
|
||||
spot = self.pet_model.objects.create(name='Spot')
|
||||
spike = self.pet_model.objects.create(name='Spike')
|
||||
spot.tags.add('scary')
|
||||
spike.tags.add('fluffy')
|
||||
lookup_kwargs = {
|
||||
'%s__name' % _model_name(self.pet_model): 'Spot'
|
||||
}
|
||||
self.assert_tags_equal(
|
||||
self.tag_model.objects.filter(**lookup_kwargs),
|
||||
['scary']
|
||||
)
|
||||
|
||||
def test_taggeditem_unicode(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add("juicy")
|
||||
|
||||
self.assertEqual(
|
||||
force_text(self.taggeditem_model.objects.all()[0]),
|
||||
"apple tagged with juicy"
|
||||
)
|
||||
|
||||
def test_abstract_subclasses(self):
|
||||
p = Photo.objects.create()
|
||||
p.tags.add("outdoors", "pretty")
|
||||
self.assert_tags_equal(
|
||||
p.tags.all(),
|
||||
["outdoors", "pretty"]
|
||||
)
|
||||
|
||||
m = Movie.objects.create()
|
||||
m.tags.add("hd")
|
||||
self.assert_tags_equal(
|
||||
m.tags.all(),
|
||||
["hd"],
|
||||
)
|
||||
|
||||
def test_field_api(self):
|
||||
# Check if tag field, which simulates m2m, has django-like api.
|
||||
field = self.food_model._meta.get_field('tags')
|
||||
self.assertTrue(hasattr(field, 'rel'))
|
||||
self.assertTrue(hasattr(field.rel, 'to'))
|
||||
self.assertTrue(hasattr(field, 'related'))
|
||||
|
||||
# This API has changed in Django 1.8
|
||||
# https://code.djangoproject.com/ticket/21414
|
||||
if django.VERSION >= (1, 8):
|
||||
self.assertEqual(self.food_model, field.model)
|
||||
self.assertEqual(self.tag_model, field.related.model)
|
||||
else:
|
||||
self.assertEqual(self.food_model, field.related.model)
|
||||
|
||||
def test_names_method(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add('green')
|
||||
apple.tags.add('red')
|
||||
self.assertEqual(list(apple.tags.names()), ['green', 'red'])
|
||||
|
||||
def test_slugs_method(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add('green and juicy')
|
||||
apple.tags.add('red')
|
||||
self.assertEqual(list(apple.tags.slugs()), ['green-and-juicy', 'red'])
|
||||
|
||||
def test_serializes(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
serializers.serialize("json", (apple,))
|
||||
|
||||
def test_prefetch_related(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add('1', '2')
|
||||
orange = self.food_model.objects.create(name="orange")
|
||||
orange.tags.add('2', '4')
|
||||
with self.assertNumQueries(2):
|
||||
l = list(self.food_model.objects.prefetch_related('tags').all())
|
||||
with self.assertNumQueries(0):
|
||||
foods = dict((f.name, set(t.name for t in f.tags.all())) for f in l)
|
||||
self.assertEqual(foods, {
|
||||
'orange': set(['2', '4']),
|
||||
'apple': set(['1', '2'])
|
||||
})
|
||||
|
||||
def test_internal_type_is_manytomany(self):
|
||||
self.assertEqual(
|
||||
TaggableManager().get_internal_type(), 'ManyToManyField'
|
||||
)
|
||||
|
||||
def test_prefetch_no_extra_join(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add('1', '2')
|
||||
with self.assertNumQueries(2):
|
||||
l = list(self.food_model.objects.prefetch_related('tags').all())
|
||||
join_clause = 'INNER JOIN "%s"' % self.taggeditem_model._meta.db_table
|
||||
self.assertEqual(connection.queries[-1]['sql'].count(join_clause), 1, connection.queries[-2:])
|
||||
|
||||
@override_settings(TAGGIT_CASE_INSENSITIVE=True)
|
||||
def test_with_case_insensitive_option(self):
|
||||
spain = self.tag_model.objects.create(name="Spain", slug="spain")
|
||||
orange = self.food_model.objects.create(name="orange")
|
||||
orange.tags.add('spain')
|
||||
self.assertEqual(list(orange.tags.all()), [spain])
|
||||
|
||||
|
||||
class TaggableManagerDirectTestCase(TaggableManagerTestCase):
|
||||
food_model = DirectFood
|
||||
pet_model = DirectPet
|
||||
housepet_model = DirectHousePet
|
||||
taggeditem_model = TaggedFood
|
||||
|
||||
class TaggableManagerDirectCustomPKTestCase(TaggableManagerTestCase):
|
||||
food_model = DirectCustomPKFood
|
||||
pet_model = DirectCustomPKPet
|
||||
housepet_model = DirectCustomPKHousePet
|
||||
taggeditem_model = TaggedCustomPKFood
|
||||
|
||||
def test_require_pk(self):
|
||||
# TODO with a charfield pk, pk is never None, so taggit has no way to
|
||||
# tell if the instance is saved or not
|
||||
pass
|
||||
|
||||
class TaggableManagerCustomPKTestCase(TaggableManagerTestCase):
|
||||
food_model = CustomPKFood
|
||||
pet_model = CustomPKPet
|
||||
housepet_model = CustomPKHousePet
|
||||
taggeditem_model = TaggedCustomPK
|
||||
|
||||
def test_require_pk(self):
|
||||
# TODO with a charfield pk, pk is never None, so taggit has no way to
|
||||
# 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(apple, self.food_model.objects.get(tags__official=False))
|
||||
|
||||
def test_get_tags_with_count(self):
|
||||
apple = self.food_model.objects.create(name="apple")
|
||||
apple.tags.add("red", "green", "delicious")
|
||||
pear = self.food_model.objects.create(name="pear")
|
||||
pear.tags.add("green", "delicious")
|
||||
|
||||
tag_info = self.tag_model.objects.filter(officialfood__in=[apple.id, pear.id],name='green').annotate(models.Count('name'))
|
||||
self.assertEqual(tag_info[0].name__count, 2)
|
||||
|
||||
class TaggableManagerInitializationTestCase(TaggableManagerTestCase):
|
||||
"""Make sure manager override defaults and sets correctly."""
|
||||
food_model = Food
|
||||
custom_manager_model = CustomManager
|
||||
|
||||
def test_default_manager(self):
|
||||
self.assertEqual(self.food_model.tags.__class__, _TaggableManager)
|
||||
|
||||
def test_custom_manager(self):
|
||||
self.assertEqual(self.custom_manager_model.tags.__class__, CustomManager.Foo)
|
||||
|
||||
class TaggableFormTestCase(BaseTaggingTestCase):
|
||||
form_class = FoodForm
|
||||
food_model = Food
|
||||
|
||||
def test_form(self):
|
||||
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>
|
||||
<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value="green, red, yummy" id="id_tags" /><br />%(help_start)sA comma-separated list of tags.%(help_stop)s</td></tr>""")
|
||||
f.save()
|
||||
apple = self.food_model.objects.get(name='apple')
|
||||
self.assert_tags_equal(apple.tags.all(), ['green', 'red', 'yummy'])
|
||||
|
||||
f = self.form_class({'name': 'apple', 'tags': 'green, red, yummy, delicious'}, instance=apple)
|
||||
f.save()
|
||||
apple = self.food_model.objects.get(name='apple')
|
||||
self.assert_tags_equal(apple.tags.all(), ['green', 'red', 'yummy', 'delicious'])
|
||||
self.assertEqual(self.food_model.objects.count(), 1)
|
||||
|
||||
f = self.form_class({"name": "raspberry"})
|
||||
self.assertFalse(f.is_valid())
|
||||
|
||||
f = self.form_class(instance=apple)
|
||||
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>
|
||||
<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value="delicious, green, red, yummy" id="id_tags" /><br />%(help_start)sA comma-separated list of tags.%(help_stop)s</td></tr>""")
|
||||
|
||||
apple.tags.add('has,comma')
|
||||
f = self.form_class(instance=apple)
|
||||
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>
|
||||
<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value=""has,comma", delicious, green, red, yummy" id="id_tags" /><br />%(help_start)sA comma-separated list of tags.%(help_stop)s</td></tr>""")
|
||||
|
||||
apple.tags.add('has space')
|
||||
f = self.form_class(instance=apple)
|
||||
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>
|
||||
<tr><th><label for="id_tags">Tags:</label></th><td><input type="text" name="tags" value=""has space", "has,comma", delicious, green, red, yummy" id="id_tags" /><br />%(help_start)sA comma-separated list of tags.%(help_stop)s</td></tr>""")
|
||||
|
||||
def test_formfield(self):
|
||||
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, 'Add some categories')
|
||||
self.assertEqual(ff.required, False)
|
||||
|
||||
self.assertEqual(ff.clean(""), [])
|
||||
|
||||
tm = TaggableManager()
|
||||
ff = tm.formfield()
|
||||
self.assertRaises(ValidationError, ff.clean, "")
|
||||
|
||||
class TaggableFormDirectTestCase(TaggableFormTestCase):
|
||||
form_class = DirectFoodForm
|
||||
food_model = DirectFood
|
||||
|
||||
class TaggableFormDirectCustomPKTestCase(TaggableFormTestCase):
|
||||
form_class = DirectCustomPKFoodForm
|
||||
food_model = DirectCustomPKFood
|
||||
|
||||
class TaggableFormCustomPKTestCase(TaggableFormTestCase):
|
||||
form_class = CustomPKFoodForm
|
||||
food_model = CustomPKFood
|
||||
|
||||
class TaggableFormOfficialTestCase(TaggableFormTestCase):
|
||||
form_class = OfficialFoodForm
|
||||
food_model = OfficialFood
|
||||
|
||||
|
||||
class TagStringParseTestCase(UnitTestCase):
|
||||
"""
|
||||
Ported from Jonathan Buchanan's `django-tagging
|
||||
<http://django-tagging.googlecode.com/>`_
|
||||
"""
|
||||
|
||||
def test_with_simple_space_delimited_tags(self):
|
||||
"""
|
||||
Test with simple space-delimited tags.
|
||||
"""
|
||||
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'), ['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'),
|
||||
['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'), ['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"'),
|
||||
['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"'), ['one', 'thr,ee', 'two'])
|
||||
|
||||
def test_with_loose_commas(self):
|
||||
"""
|
||||
Loose commas - split on commas
|
||||
"""
|
||||
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"'),
|
||||
['a-one', 'a-two, and a-three'])
|
||||
self.assertEqual(parse_tags('"two", one, one, two, "one"'),
|
||||
['one', 'two'])
|
||||
|
||||
def test_with_naughty_input(self):
|
||||
"""
|
||||
Test with naughty input.
|
||||
"""
|
||||
# Bad users! Naughty users!
|
||||
self.assertEqual(parse_tags(None), [])
|
||||
self.assertEqual(parse_tags(''), [])
|
||||
self.assertEqual(parse_tags('"'), [])
|
||||
self.assertEqual(parse_tags('""'), [])
|
||||
self.assertEqual(parse_tags('"' * 7), [])
|
||||
self.assertEqual(parse_tags(',,,,,,'), [])
|
||||
self.assertEqual(parse_tags('",",",",",",","'), [','])
|
||||
self.assertEqual(parse_tags('a-one "a-two" and "a-three'),
|
||||
['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]), '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"')
|
||||
|
||||
|
||||
@skipIf(django.VERSION < (1, 7), "not relevant for Django < 1.7")
|
||||
class DeconstructTestCase(UnitTestCase):
|
||||
def test_deconstruct_kwargs_kept(self):
|
||||
instance = TaggableManager(through=OfficialThroughModel, to='dummy.To')
|
||||
name, path, args, kwargs = instance.deconstruct()
|
||||
new_instance = TaggableManager(*args, **kwargs)
|
||||
self.assertEqual('tests.OfficialThroughModel', new_instance.rel.through)
|
||||
self.assertEqual('dummy.To', new_instance.rel.to)
|
||||
|
||||
|
||||
@skipUnless(django.VERSION < (1, 7), "test only applies to 1.6 and below")
|
||||
class SouthSupportTests(TestCase):
|
||||
def test_import_migrations_module(self):
|
||||
try:
|
||||
from taggit.migrations import __doc__ # noqa
|
||||
except ImproperlyConfigured as e:
|
||||
exception = e
|
||||
self.assertIn("SOUTH_MIGRATION_MODULES", exception.args[0])
|
||||
|
||||
|
||||
class InheritedPrefetchTests(TestCase):
|
||||
|
||||
def test_inherited_tags_with_prefetch(self):
|
||||
child = Child()
|
||||
child.save()
|
||||
child.tags.add('tag 1', 'tag 2', 'tag 3', 'tag 4')
|
||||
|
||||
child = Child.objects.get()
|
||||
no_prefetch_tags = child.tags.all()
|
||||
self.assertEquals(4, no_prefetch_tags.count())
|
||||
child = Child.objects.prefetch_related('tags').get()
|
||||
prefetch_tags = child.tags.all()
|
||||
self.assertEquals(4, prefetch_tags.count())
|
||||
self.assertEquals(set([t.name for t in no_prefetch_tags]),
|
||||
set([t.name for t in prefetch_tags]))
|
|
@ -0,0 +1,129 @@
|
|||
[testenv]
|
||||
skipsdist = True
|
||||
usedevelop = True
|
||||
deps =
|
||||
flake8
|
||||
deps14 =
|
||||
https://github.com/django/django/archive/stable/1.4.x.tar.gz#egg=django
|
||||
deps15 =
|
||||
https://github.com/django/django/archive/stable/1.5.x.tar.gz#egg=django
|
||||
deps16 =
|
||||
https://github.com/django/django/archive/stable/1.6.x.tar.gz#egg=django
|
||||
deps17 =
|
||||
https://github.com/django/django/archive/stable/1.7.x.tar.gz#egg=django
|
||||
deps18 =
|
||||
https://github.com/django/django/archive/stable/1.8.x.tar.gz#egg=django
|
||||
deps19 =
|
||||
https://github.com/django/django/archive/stable/1.9.x.tar.gz#egg=django
|
||||
|
||||
commands =
|
||||
python ./runtests.py {posargs}
|
||||
|
||||
|
||||
[testenv:py26-1.4.x]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps14}
|
||||
|
||||
[testenv:py26-1.5.x]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps15}
|
||||
|
||||
[testenv:py26-1.6.x]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps16}
|
||||
|
||||
[testenv:py27-1.4.x]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps14}
|
||||
|
||||
[testenv:py27-1.5.x]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps15}
|
||||
|
||||
[testenv:py27-1.6.x]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps16}
|
||||
|
||||
[testenv:py27-1.7.x]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps17}
|
||||
|
||||
[testenv:py27-1.8.x]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps18}
|
||||
|
||||
[testenv:py27-1.9.x]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps19}
|
||||
|
||||
[testenv:py33-1.5.x]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps15}
|
||||
|
||||
[testenv:py33-1.6.x]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps16}
|
||||
|
||||
[testenv:py33-1.7.x]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps17}
|
||||
|
||||
[testenv:py33-1.8.x]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps18}
|
||||
|
||||
[testenv:py34-1.5.x]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps15}
|
||||
|
||||
[testenv:py34-1.6.x]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps16}
|
||||
|
||||
[testenv:py34-1.7.x]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps17}
|
||||
|
||||
[testenv:py34-1.8.x]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps18}
|
||||
|
||||
[testenv:py34-1.9.x]
|
||||
basepython = python3.4
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
{[testenv]deps19}
|
Loading…
Reference in New Issue