pax_global_header00006660000000000000000000000064122614773310014520gustar00rootroot0000000000000052 comment=650ea579bcdd4a23dd307c3940a33dbf2fd6de5a django-taggit-0.11.2/000077500000000000000000000000001226147733100143205ustar00rootroot00000000000000django-taggit-0.11.2/AUTHORS000066400000000000000000000006631226147733100153750ustar00rootroot00000000000000django-taggit was originally created by Alex Gaynor. The following is a list of much appreciated contributors: Nathan Borror fakeempire Ben Firshman Alex Gaynor Rob Hudson Carl Meyer Frank Wiles Jonathan Buchanan idle sign Charles Leifer Florian Apolloner django-taggit-0.11.2/CHANGELOG.txt000066400000000000000000000043651226147733100163600ustar00rootroot00000000000000Changelog ========= 0.11.2 (13.12.2013) ~~~~~~~~~~~~~~~~~~~ * Forbid multiple TaggableManagers via generic foreign keys. 0.11.1 (25.11.2013) ~~~~~~~~~~~~~~~~~~~ * Fixed support for Django 1.4 and 1.5. 0.11.0 (25.11.2013) ~~~~~~~~~~~~~~~~~~~ * 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 (17.08.2013) ~~~~~~~~~~~~~~~~~~~ * 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 ~~~~~ * *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 ~~~~~ * 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 `_. * 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 ~~~~~ * Fixed querying for objects using ``exclude(tags__in=tags)``. * Marked strings as translatable. * Added a Russian translation. * Created a `mailing list `_. * Smarter tagstring parsing for form field; ported from Jonathan Buchanan's `django-tagging `_. 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. django-taggit-0.11.2/LICENSE000066400000000000000000000030231226147733100153230ustar00rootroot00000000000000Copyright (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. django-taggit-0.11.2/MANIFEST.in000066400000000000000000000002361226147733100160570ustar00rootroot00000000000000include AUTHORS include CHANGELOG.txt include LICENSE include README.rst recursive-include docs * recursive-include taggit/locale * recursive-include tests * django-taggit-0.11.2/PKG-INFO000066400000000000000000000042151226147733100154170ustar00rootroot00000000000000Metadata-Version: 1.1 Name: django-taggit Version: 0.11.2 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:: 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:: >>> apple = Food.objects.create(name="apple") >>> apple.tags.add("red", "green", "delicious") >>> apple.tags.all() [, , ] >>> apple.tags.remove("green") >>> apple.tags.all() [, ] >>> Food.objects.filter(tags__name__in=["red"]) [, ] Tags will show up for you automatically in forms and the admin. ``django-taggit`` requires Django 1.4.5 or greater. For more info checkout out the documentation. And for questions about usage or development you can contact the `mailinglist `_. 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: Framework :: Django django-taggit-0.11.2/README.rst000066400000000000000000000020371226147733100160110ustar00rootroot00000000000000django-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:: 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:: >>> apple = Food.objects.create(name="apple") >>> apple.tags.add("red", "green", "delicious") >>> apple.tags.all() [, , ] >>> apple.tags.remove("green") >>> apple.tags.all() [, ] >>> Food.objects.filter(tags__name__in=["red"]) [, ] Tags will show up for you automatically in forms and the admin. ``django-taggit`` requires Django 1.4.5 or greater. For more info checkout out the documentation. And for questions about usage or development you can contact the `mailinglist `_. django-taggit-0.11.2/django_taggit.egg-info/000077500000000000000000000000001226147733100206135ustar00rootroot00000000000000django-taggit-0.11.2/django_taggit.egg-info/PKG-INFO000066400000000000000000000042151226147733100217120ustar00rootroot00000000000000Metadata-Version: 1.1 Name: django-taggit Version: 0.11.2 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:: 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:: >>> apple = Food.objects.create(name="apple") >>> apple.tags.add("red", "green", "delicious") >>> apple.tags.all() [, , ] >>> apple.tags.remove("green") >>> apple.tags.all() [, ] >>> Food.objects.filter(tags__name__in=["red"]) [, ] Tags will show up for you automatically in forms and the admin. ``django-taggit`` requires Django 1.4.5 or greater. For more info checkout out the documentation. And for questions about usage or development you can contact the `mailinglist `_. 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: Framework :: Django django-taggit-0.11.2/django_taggit.egg-info/SOURCES.txt000066400000000000000000000024371226147733100225050ustar00rootroot00000000000000AUTHORS CHANGELOG.txt LICENSE MANIFEST.in README.rst setup.cfg setup.py 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/he/LC_MESSAGES/django.mo taggit/locale/he/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_unique_tagnames.py taggit/migrations/__init__.py tests/__init__.py tests/forms.py tests/models.py tests/tests.pydjango-taggit-0.11.2/django_taggit.egg-info/dependency_links.txt000066400000000000000000000000011226147733100246610ustar00rootroot00000000000000 django-taggit-0.11.2/django_taggit.egg-info/not-zip-safe000066400000000000000000000000011226147733100230410ustar00rootroot00000000000000 django-taggit-0.11.2/django_taggit.egg-info/top_level.txt000066400000000000000000000000151226147733100233410ustar00rootroot00000000000000tests taggit django-taggit-0.11.2/docs/000077500000000000000000000000001226147733100152505ustar00rootroot00000000000000django-taggit-0.11.2/docs/Makefile000066400000000000000000000061041226147733100167110ustar00rootroot00000000000000# 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 ' where 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." django-taggit-0.11.2/docs/admin.txt000066400000000000000000000013741226147733100171060ustar00rootroot00000000000000Using 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 `_. django-taggit-0.11.2/docs/api.txt000066400000000000000000000073501226147733100165670ustar00rootroot00000000000000The 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.slugs.names() [u'green-and-juicy', u'red'] 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"]) [, , ] 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.objects.filter(tags__name__in=["delicious", "red"]).distinct() [] 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 `_, it is not currently (Django < 1.6) possible to use aggregation in conjunction with ``taggit``. This is a `documented interaction `_ of generic relations (which ``taggit`` uses internally) and aggregates. django-taggit-0.11.2/docs/changelog.txt000066400000000000000000000000361226147733100177370ustar00rootroot00000000000000.. include:: ../CHANGELOG.txt django-taggit-0.11.2/docs/conf.py000066400000000000000000000145501226147733100165540ustar00rootroot00000000000000# -*- 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-2013, 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.10.0' # The full version, including alpha/beta/rc tags. release = '0.10.0' # 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 # " v 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 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} django-taggit-0.11.2/docs/custom_tagging.txt000066400000000000000000000071471226147733100210340ustar00rootroot00000000000000Using a Custom Tag or Through Model =================================== By default ``django-taggit`` uses a "through model" with a ``GenericForeignKey`` on it, that has another ``ForeignKey`` to an included ``Tag`` model. However, there are some cases where this isn't desirable, for example if you want the speed and referential guarantees of a real ``ForeignKey``, if you have a model with a non-integer primary key, or if you want to store additional data about a tag, such as whether it is official. In these cases ``django-taggit`` makes it easy to substitute your own through model, or ``Tag`` model. Your intermediary model must be a subclass of ``taggit.models.TaggedItemBase`` with a foreign key to your content model named ``content_object``. Pass this intermediary model as the ``through`` argument to ``TaggableManager``:: from django.db import models from taggit.managers import TaggableManager from taggit.models import TaggedItemBase class TaggedFood(TaggedItemBase): content_object = models.ForeignKey('Food') class Food(models.Model): # ... fields here tags = TaggableManager(through=TaggedFood) Once this is done, the API works the same as for GFK-tagged models. To change the behavior in other ways there are a number of other classes you can subclass to obtain different behavior: ========================= =========================================================== Class name Behavior ========================= =========================================================== ``TaggedItemBase`` Allows custom ``ForeignKeys`` to models. ``GenericTaggedItemBase`` Allows custom ``Tag`` models. ``ItemBase`` Allows custom ``Tag`` models and ``ForeignKeys`` to models. ========================= =========================================================== When providing a custom ``Tag`` model it should be a ``ForeignKey`` to your tag model named ``"tag"``: .. 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. django-taggit-0.11.2/docs/external_apps.txt000066400000000000000000000020651226147733100206610ustar00rootroot00000000000000External 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`__. __ http://github.com/alex/django-taggit __ http://github.com/frankwiles/django-taggit-suggest __ http://github.com/feuervogel/django-taggit-templatetags django-taggit-0.11.2/docs/forms.txt000066400000000000000000000042561226147733100171460ustar00rootroot00000000000000.. _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() django-taggit-0.11.2/docs/getting_started.txt000066400000000000000000000010021226147733100211710ustar00rootroot00000000000000Getting 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 taggit` if using South. 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() django-taggit-0.11.2/docs/index.txt000066400000000000000000000015351226147733100171240ustar00rootroot00000000000000Welcome 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 so:: python manage.py migrate taggit --fake 0001 python manage.py migrate 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/ django-taggit-0.11.2/setup.cfg000066400000000000000000000001651226147733100161430ustar00rootroot00000000000000[metadata] license-file = LICENSE [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 django-taggit-0.11.2/setup.py000066400000000000000000000022171226147733100160340ustar00rootroot00000000000000from setuptools import setup, find_packages f = open('README.rst') readme = f.read() f.close() setup( name='django-taggit', version='0.11.2', 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(), 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', 'Framework :: Django', ], include_package_data=True, zip_safe=False, ) django-taggit-0.11.2/taggit/000077500000000000000000000000001226147733100155775ustar00rootroot00000000000000django-taggit-0.11.2/taggit/__init__.py000066400000000000000000000000411226147733100177030ustar00rootroot00000000000000VERSION = (0, 12, 0, 'alpha', 0) django-taggit-0.11.2/taggit/admin.py000066400000000000000000000007001226147733100172360ustar00rootroot00000000000000from __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) django-taggit-0.11.2/taggit/forms.py000066400000000000000000000014651226147733100173050ustar00rootroot00000000000000from __future__ import unicode_literals from django import forms from django.utils.translation import ugettext as _ from django.utils import six from taggit.utils import parse_tags, edit_string_for_tags class TagWidget(forms.TextInput): def render(self, name, value, attrs=None): if value is not None and not isinstance(value, 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.")) django-taggit-0.11.2/taggit/locale/000077500000000000000000000000001226147733100170365ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/cs/000077500000000000000000000000001226147733100174435ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/cs/LC_MESSAGES/000077500000000000000000000000001226147733100212305ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/cs/LC_MESSAGES/django.mo000066400000000000000000000020451226147733100230300ustar00rootroot00000000000000 | !@ `m r.|  !Y!{  )    %(object)s tagged with %(tag)sA comma-separated list of tags.Content typeNameObject idPlease provide a comma-separated list of tags.SlugTagTagged ItemTagged ItemsTagsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2013-08-01 16:52+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; %(object)s označen tagem %(tag)sČárkami oddělený seznam tagůTyp obsahuJménoID objektuVložte čárkami oddělený seznam tagůSlugTagTagem označená položkaTagy označené položkyTagydjango-taggit-0.11.2/taggit/locale/cs/LC_MESSAGES/django.po000066400000000000000000000026461226147733100230420ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: LANGUAGE \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" django-taggit-0.11.2/taggit/locale/de/000077500000000000000000000000001226147733100174265ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/de/LC_MESSAGES/000077500000000000000000000000001226147733100212135ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/de/LC_MESSAGES/django.mo000066400000000000000000000024631226147733100230170ustar00rootroot00000000000000 01P p`} .! % 1>dC&+  :   %   %(object)s tagged with %(tag)sA comma-separated list of tags.Content typeEnter a valid Regular Expression. To make it case-insensitive include "(?i)" in your expression.NameObject idPlease provide a comma-separated list of tags.SlugTagTagged ItemTagged ItemsTagsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: POT-Creation-Date: 2010-09-07 09:26-0700 PO-Revision-Date: 2010-09-07 09:26-0700 Last-Translator: Jannis Leidel Language-Team: German MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1) %(object)s verschlagwortet mit %(tag)sEine durch Komma getrennte Schlagwortliste.InhaltstypBitte einen regulären Ausdruck eingeben. Fügen Sie "(?i) " dem Ausdruck hinzu, um nicht zwischen Groß- und Kleinschreibung zu unterscheiden.NameObjekt-IDBitte eine durch Komma getrennte Schlagwortliste eingeben.KürzelSchlagwortVerschlagwortetes ObjektVerschlagwortete ObjekteSchlagwörterdjango-taggit-0.11.2/taggit/locale/de/LC_MESSAGES/django.po000066400000000000000000000031021226147733100230110ustar00rootroot00000000000000#, 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 \n" "Language-Team: German \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." django-taggit-0.11.2/taggit/locale/en/000077500000000000000000000000001226147733100174405ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/en/LC_MESSAGES/000077500000000000000000000000001226147733100212255ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/en/LC_MESSAGES/django.po000066400000000000000000000025051226147733100230310ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: LANGUAGE \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 "" django-taggit-0.11.2/taggit/locale/he/000077500000000000000000000000001226147733100174325ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/he/LC_MESSAGES/000077500000000000000000000000001226147733100212175ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/he/LC_MESSAGES/django.mo000066400000000000000000000015171226147733100230220ustar00rootroot00000000000000T .,0a5"8F? D%(object)s tagged with %(tag)sA comma-separated list of tags.NamePlease provide a comma-separated list of tags.TagTagsProject-Id-Version: Django Taggit Report-Msgid-Bugs-To: POT-Creation-Date: 2010-06-26 12:47-0500 PO-Revision-Date: 2010-06-26 12:54-0600 Last-Translator: Alex Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); %(object)s מתויג עם %(tag)sרשימה של תגים מופרדת עם פסיקים.שםנא לספק רשימה של תגים מופרדת עם פסיקים.תגתגיותdjango-taggit-0.11.2/taggit/locale/he/LC_MESSAGES/django.po000066400000000000000000000030171226147733100230220ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: LANGUAGE \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 "" django-taggit-0.11.2/taggit/locale/nb/000077500000000000000000000000001226147733100174355ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/nb/LC_MESSAGES/000077500000000000000000000000001226147733100212225ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/nb/LC_MESSAGES/django.mo000066400000000000000000000023761226147733100230310ustar00rootroot00000000000000 01P p`} .! % 1>kC  ,   %(object)s tagged with %(tag)sA comma-separated list of tags.Content typeEnter a valid Regular Expression. To make it case-insensitive include "(?i)" in your expression.NameObject idPlease provide a comma-separated list of tags.SlugTagTagged ItemTagged ItemsTagsProject-Id-Version: 0.9.3 Report-Msgid-Bugs-To: POT-Creation-Date: 2010-09-07 09:45-0700 PO-Revision-Date: 2012-12-08 14:42+0100 Last-Translator: Bjørn Pettersen Language-Team: Norwegian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Generator: Poedit 1.5.4 Language: Norwegian %(object)s tagget med %(tag)sEn kommaseparert tagg-liste.InnholdstypeSkriv 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.NavnObjekt-idVennligst oppgi en kommaseparert tagg-liste.SlugTaggTagget ElementTaggede ElementerTaggerdjango-taggit-0.11.2/taggit/locale/nb/LC_MESSAGES/django.po000066400000000000000000000033101226147733100230210ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: Norwegian \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." django-taggit-0.11.2/taggit/locale/nl/000077500000000000000000000000001226147733100174475ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/nl/LC_MESSAGES/000077500000000000000000000000001226147733100212345ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/nl/LC_MESSAGES/django.mo000066400000000000000000000023011226147733100230270ustar00rootroot00000000000000 01P p`} .! % 1>GC) qS X.b   %(object)s tagged with %(tag)sA comma-separated list of tags.Content typeEnter a valid Regular Expression. To make it case-insensitive include "(?i)" in your expression.NameObject idPlease provide a comma-separated list of tags.SlugTagTagged ItemTagged ItemsTagsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: POT-Creation-Date: 2010-09-07 09:45-0700 PO-Revision-Date: 2010-09-07 23:04+0100 Last-Translator: Jeffrey Gelens Language-Team: Dutch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Poedit-Language: Dutch %(object)s getagged met %(tag)sEen door komma gescheiden lijst van tags.InhoudstypeVoer een valide reguliere expressie in. Voeg "(?i)" aan de expressie toe om deze hoofdletter ongevoelig te maken.NaamObject-idGeef een door komma gescheiden lijst van tags.SlugTagObject getaggedObjecten getaggedTagsdjango-taggit-0.11.2/taggit/locale/nl/LC_MESSAGES/django.po000066400000000000000000000026451226147733100230450ustar00rootroot00000000000000msgid "" 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 \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." django-taggit-0.11.2/taggit/locale/pt_BR/000077500000000000000000000000001226147733100200445ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/pt_BR/LC_MESSAGES/000077500000000000000000000000001226147733100216315ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/pt_BR/LC_MESSAGES/django.mo000066400000000000000000000020501226147733100234250ustar00rootroot00000000000000 | !@ `m r.|  m@/` >     %(object)s tagged with %(tag)sA comma-separated list of tags.Content typeNameObject idPlease provide a comma-separated list of tags.SlugTagTagged ItemTagged ItemsTagsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: POT-Creation-Date: 2013-01-15 22:25-0200 PO-Revision-Date: 2013-01-12 18:11-0200 Last-Translator: RPB Language-Team: Portuguese (Brazil) Language: pt_BR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1) %(object)s marcados com %(tag)sUma lista de marcadores separados por vírgula.Tipo de conteúdoNomeId do objetoFavor fornecer uma lista de marcadores separados por vírgula.SlugMarcadorItem marcadoItens marcadosMarcadoresdjango-taggit-0.11.2/taggit/locale/pt_BR/LC_MESSAGES/django.po000066400000000000000000000025101226147733100234310ustar00rootroot00000000000000# This file is distributed under WTFPL license. # # Translators: # RPB , 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 \n" "Language-Team: Portuguese (Brazil) \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" django-taggit-0.11.2/taggit/locale/ru/000077500000000000000000000000001226147733100174645ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/ru/LC_MESSAGES/000077500000000000000000000000001226147733100212515ustar00rootroot00000000000000django-taggit-0.11.2/taggit/locale/ru/LC_MESSAGES/django.mo000066400000000000000000000027511226147733100230550ustar00rootroot00000000000000 01P p`} .! % 1>C92@s2C4U      %(object)s tagged with %(tag)sA comma-separated list of tags.Content typeEnter a valid Regular Expression. To make it case-insensitive include "(?i)" in your expression.NameObject idPlease provide a comma-separated list of tags.SlugTagTagged ItemTagged ItemsTagsProject-Id-Version: Django Taggit Report-Msgid-Bugs-To: POT-Creation-Date: 2010-06-11 11:28+0700 PO-Revision-Date: 2010-06-11 11:30+0700 Last-Translator: Igor 'idle sign' Starikov Language-Team: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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; X-Poedit-Language: Russian элемент «%(object)s» с меткой «%(tag)s»Список меток через запятую.Тип содержимогоВведите регулярное выражение. Чтобы сделать его чувствительным к регистру укажите "(?i)".НазваниеID объектаУкажите метки через запятую.СлагМеткаЭлемент с меткойЭлементы с меткойМеткиdjango-taggit-0.11.2/taggit/locale/ru/LC_MESSAGES/django.po000066400000000000000000000036321226147733100230570ustar00rootroot00000000000000# 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 , 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 \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)\"." django-taggit-0.11.2/taggit/managers.py000066400000000000000000000375361226147733100177640ustar00rootroot00000000000000from __future__ import unicode_literals from operator import attrgetter from django import VERSION from django.contrib.contenttypes.generic import GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models, router from django.db.models.fields import Field from django.db.models.fields.related import ManyToManyRel, RelatedField, add_lazy_relation from django.db.models.related import RelatedObject from django.utils.text import capfirst from django.utils.translation import ugettext_lazy as _ from django.utils import six try: from django.db.models.related import PathInfo except ImportError: pass # PathInfo is not used on Django < 1.6 from taggit.forms import TagField from taggit.models import TaggedItem, GenericTaggedItemBase from taggit.utils import require_instance_manager 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): self.related_name = related_name self.limit_choices_to = {} self.symmetrical = True self.multiple = True self.through = None self.field = field 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. """ 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(RelatedField, Field): _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): Field.__init__(self, verbose_name=verbose_name, help_text=help_text, blank=blank, null=True, serialize=False) self.through = through or TaggedItem self.rel = TaggableRel(self, related_name) 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 = _TaggableManager( through=self.through, model=model, instance=instance, prefetch_cache_name = self.name ) return manager def contribute_to_class(self, cls, name): if VERSION < (1, 7): self.name = self.column = 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: if isinstance(self.through, six.string_types): def resolve_related_class(field, model, cls): self.through = model self.post_through_setup(cls) add_lazy_relation( cls, self, self.through, resolve_related_class ) else: self.post_through_setup(cls) 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): self.related = RelatedObject(cls, self.model, self) self.use_gfk = ( self.through is None or issubclass(self.through, GenericTaggedItemBase) ) self.rel.to = self.through._meta.get_field("tag").rel.to 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 isinstance(rel, TaggableManager) and rel.use_gfk and rel != self: raise ValueError('You can only have one TaggableManager per model' ' using generic relations.') 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 self.through._meta.get_field_by_name("tag")[0].column def m2m_reverse_field_name(self): return self.through._meta.get_field_by_name("tag")[0].name def m2m_target_field_name(self): return self.model._meta.pk.name def m2m_reverse_target_field_name(self): 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 = self.through._meta.get_field_by_name('content_type')[0].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 = self.through._meta.get_field_by_name('content_object')[0] linkfield2 = self.through._meta.get_field_by_name(self.m2m_reverse_field_name())[0] 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 object_id_field = opts.get_field_by_name('object_id')[0] linkfield = self.through._meta.get_field_by_name(self.m2m_reverse_field_name())[0] 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, [object_id_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 (("id", "object_id"),) else: return (("object_id", "id"),) def get_extra_restriction(self, where_class, alias, related_alias): extra_col = self.through._meta.get_field_by_name('content_type')[0].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 [(self.through._meta.get_field_by_name('object_id')[0], self.model._meta.pk)] @property def foreign_related_fields(self): return [self.related_fields[0][1]] 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_query_set(self): try: return self.instance._prefetched_objects_cache[self.prefetch_cache_name] except (AttributeError, KeyError): return self.through.tags_for(self.model, self.instance) def get_prefetch_query_set(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, GenericTaggedItemBase) 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_query_set().using(db)._next_is_sticky().filter(**query).extra( select = { '_prefetch_related_val' : '%s.%s' % (qn(join_table), qn(source_col)) } ) return (qs, attrgetter('_prefetch_related_val'), attrgetter(instance._meta.pk.name), False, self.prefetch_cache_name) # Django 1.6 renamed this get_queryset = get_query_set def _lookup_kwargs(self): return self.through.lookup_kwargs(self.instance) @require_instance_manager def add(self, *tags): str_tags = set([ t for t in tags if not isinstance(t, self.through.tag_model()) ]) tag_objs = set(tags) - str_tags # 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 ) tag_objs.update(existing) for new_tag in str_tags - set(t.name for t in existing): 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_query_set().values_list('name', flat=True) @require_instance_manager def slugs(self): return self.get_query_set().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_query_set().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 = self.through._meta.get_field_by_name(lookup_keys[0])[0] objs = f.rel.to._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 def _get_subclasses(model): subclasses = [model] for f in model._meta.get_all_field_names(): field = model._meta.get_field_by_name(f)[0] if (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) django-taggit-0.11.2/taggit/migrations/000077500000000000000000000000001226147733100177535ustar00rootroot00000000000000django-taggit-0.11.2/taggit/migrations/0001_initial.py000066400000000000000000000056261226147733100224270ustar00rootroot00000000000000# -*- 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'] django-taggit-0.11.2/taggit/migrations/0002_unique_tagnames.py000066400000000000000000000037531226147733100241630ustar00rootroot00000000000000# -*- 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'] django-taggit-0.11.2/taggit/migrations/__init__.py000066400000000000000000000000001226147733100220520ustar00rootroot00000000000000django-taggit-0.11.2/taggit/models.py000066400000000000000000000117731226147733100174450ustar00rootroot00000000000000from __future__ import unicode_literals from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.generic import GenericForeignKey from django.db import models, IntegrityError, transaction from django.db.models.query import QuerySet from django.template.defaultfilters import slugify as default_slugify from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.encoding import python_2_unicode_compatible @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 trans_kwargs = {"using": using} i = 0 while True: i += 1 try: sid = transaction.savepoint(**trans_kwargs) res = super(TagBase, self).save(*args, **kwargs) transaction.savepoint_commit(sid, **trans_kwargs) return res except IntegrityError: transaction.savepoint_rollback(sid, **trans_kwargs) self.slug = self.slugify(self.name, i) else: return super(TagBase, self).save(*args, **kwargs) def slugify(self, tag, i=None): slug = default_slugify(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 cls._meta.get_field_by_name("tag")[0].rel.to @classmethod def tag_relname(cls): return cls._meta.get_field_by_name('tag')[0].rel.related_name @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") class Meta: abstract = True @classmethod def tags_for(cls, model, instance=None): if instance is not None: return cls.tag_model().objects.filter(**{ '%s__content_object' % cls.tag_relname(): instance }) return cls.tag_model().objects.filter(**{ '%s__content_object__isnull' % cls.tag_relname(): False }).distinct() class GenericTaggedItemBase(ItemBase): object_id = models.IntegerField(verbose_name=_('Object id'), db_index=True) content_type = models.ForeignKey( ContentType, verbose_name=_('Content type'), related_name="%(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): 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 return cls.tag_model().objects.filter(**kwargs).distinct() class TaggedItem(GenericTaggedItemBase, TaggedItemBase): class Meta: verbose_name = _("Tagged Item") verbose_name_plural = _("Tagged Items") django-taggit-0.11.2/taggit/utils.py000066400000000000000000000076201226147733100173160ustar00rootroot00000000000000from __future__ import unicode_literals from django.utils.encoding import force_text from django.utils.functional import wraps from django.utils import six def parse_tags(tagstring): """ 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 `_ """ 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 `_ """ 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 `_ """ 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 django-taggit-0.11.2/taggit/views.py000066400000000000000000000013551226147733100173120ustar00rootroot00000000000000from __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 TaggedItem, Tag 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) django-taggit-0.11.2/tests/000077500000000000000000000000001226147733100154625ustar00rootroot00000000000000django-taggit-0.11.2/tests/__init__.py000066400000000000000000000000001226147733100175610ustar00rootroot00000000000000django-taggit-0.11.2/tests/forms.py000066400000000000000000000012031226147733100171560ustar00rootroot00000000000000from __future__ import unicode_literals, absolute_import from django import forms, VERSION from .models import Food, DirectFood, CustomPKFood, 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 CustomPKFoodForm(forms.ModelForm): class Meta: model = CustomPKFood fields = fields class OfficialFoodForm(forms.ModelForm): class Meta: model = OfficialFood fields = fields django-taggit-0.11.2/tests/models.py000066400000000000000000000076411226147733100173270ustar00rootroot00000000000000from __future__ import unicode_literals from django.db import models from django.utils.encoding import python_2_unicode_compatible from taggit.managers import TaggableManager from taggit.models import (TaggedItemBase, GenericTaggedItemBase, TaggedItem, TagBase, Tag) # 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') @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() # 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() # Test custom through model to model with custom PK class TaggedCustomPKFood(TaggedItemBase): content_object = models.ForeignKey('CustomPKFood') class TaggedCustomPKPet(TaggedItemBase): content_object = models.ForeignKey('CustomPKPet') @python_2_unicode_compatible class CustomPKFood(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 CustomPKPet(models.Model): name = models.CharField(max_length=50, primary_key=True) tags = TaggableManager(through=TaggedCustomPKPet) def __str__(self): return self.name class CustomPKHousePet(CustomPKPet): trained = models.BooleanField() # 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() 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) django-taggit-0.11.2/tests/tests.py000066400000000000000000000513271226147733100172060ustar00rootroot00000000000000from __future__ import unicode_literals, absolute_import from unittest import TestCase as UnitTestCase import django from django.conf import settings from django.core.exceptions import ValidationError from django.core import serializers from django.db import connection from django.test import TestCase, TransactionTestCase from django.utils import six from django.utils.encoding import force_text from django.contrib.contenttypes.models import ContentType from taggit.managers import TaggableManager, _model_name from taggit.models import Tag, TaggedItem from .forms import (FoodForm, DirectFoodForm, CustomPKFoodForm, OfficialFoodForm) from .models import (Food, Pet, HousePet, DirectFood, DirectPet, DirectHousePet, TaggedPet, CustomPKFood, CustomPKPet, CustomPKHousePet, TaggedCustomPKPet, OfficialFood, OfficialPet, OfficialHousePet, OfficialThroughModel, OfficialTag, Photo, Movie, Article) from taggit.utils import parse_tags, edit_string_for_tags class BaseTaggingTest(object): def assert_tags_equal(self, qs, tags, sort=True, attr="name"): got = [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": '', "help_stop": "" } 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") class TagModelDirectTestCase(TagModelTestCase): food_model = DirectFood tag_model = Tag class TagModelCustomPKTestCase(TagModelTestCase): food_model = CustomPKFood tag_model = Tag class TagModelOfficialTestCase(TagModelTestCase): food_model = OfficialFood tag_model = OfficialTag class TaggableManagerTestCase(BaseTaggingTestCase): food_model = Food pet_model = Pet housepet_model = HousePet taggeditem_model = TaggedItem tag_model = Tag def test_add_tag(self): apple = self.food_model.objects.create(name="apple") 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") guava = 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): ross = self.pet_model.objects.create(name="ross") # I keep Ross Perot for a pet, what's it to you? ross.tags.add("president") self.assertEqual( force_text(self.taggeditem_model.objects.all()[0]), "ross tagged with president" ) 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, 'related')) 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']) }) class TaggableManagerDirectTestCase(TaggableManagerTestCase): food_model = DirectFood pet_model = DirectPet housepet_model = DirectHousePet taggeditem_model = TaggedPet class TaggableManagerCustomPKTestCase(TaggableManagerTestCase): food_model = CustomPKFood pet_model = CustomPKPet housepet_model = CustomPKHousePet taggeditem_model = TaggedCustomPKPet 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)) 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, """
%(help_start)sA comma-separated list of tags.%(help_stop)s""") 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, """
%(help_start)sA comma-separated list of tags.%(help_stop)s""") apple.tags.add('has,comma') f = self.form_class(instance=apple) self.assert_form_renders(f, """
%(help_start)sA comma-separated list of tags.%(help_stop)s""") apple.tags.add('has space') f = self.form_class(instance=apple) self.assert_form_renders(f, """
%(help_start)sA comma-separated list of tags.%(help_stop)s""") 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 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 `_ """ 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"')