pax_global_header00006660000000000000000000000064145163454510014522gustar00rootroot0000000000000052 comment=9aaac8ffffc9ac2030c503955e7d35ccc24ab26e django-taggit-5.0.1/000077500000000000000000000000001451634545100142445ustar00rootroot00000000000000django-taggit-5.0.1/.coveragerc000066400000000000000000000003431451634545100163650ustar00rootroot00000000000000[run] branch = True source = taggit [report] exclude_lines = if self.debug: pragma: no cover raise NotImplementedError if __name__ == .__main__.: ignore_errors = True omit = tests/* taggit/migrations/* django-taggit-5.0.1/.github/000077500000000000000000000000001451634545100156045ustar00rootroot00000000000000django-taggit-5.0.1/.github/workflows/000077500000000000000000000000001451634545100176415ustar00rootroot00000000000000django-taggit-5.0.1/.github/workflows/release.yml000066400000000000000000000017461451634545100220140ustar00rootroot00000000000000name: Release on: push: tags: - '*' jobs: build: if: github.repository == 'jazzband/django-taggit' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v2 with: python-version: 3.8 - name: Install dependencies run: | python -m pip install -U pip python -m pip install -U setuptools twine wheel - name: Build package run: | python setup.py --version python setup.py sdist --format=gztar bdist_wheel twine check dist/* - name: Upload packages to Jazzband if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@master with: user: jazzband password: ${{ secrets.JAZZBAND_RELEASE_KEY }} repository_url: https://jazzband.co/projects/django-taggit/upload django-taggit-5.0.1/.github/workflows/test.yml000066400000000000000000000027441451634545100213520ustar00rootroot00000000000000name: Test on: [push, pull_request] jobs: build: name: Python ${{ matrix.python-version }} runs-on: ubuntu-latest # The maximum number of minutes to let a workflow run # before GitHub automatically cancels it. Default: 360 timeout-minutes: 30 strategy: # When set to true, GitHub cancels # all in-progress jobs if any matrix job fails. fail-fast: false max-parallel: 5 matrix: python-version: - "3.8" - "3.9" - "3.10" - "3.11" steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Get pip cache dir id: pip-cache run: echo "::set-output name=dir::$(pip cache dir)" - name: Cache uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} key: ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }} restore-keys: | ${{ matrix.python-version }}-v1- - name: Install Python dependencies run: | python -m pip install --upgrade pip python -m pip install --upgrade tox tox-gh-actions - name: Tox tests run: tox -v - name: Upload coverage uses: codecov/codecov-action@v1 with: name: Python ${{ matrix.python-version }} django-taggit-5.0.1/.gitignore000066400000000000000000000022561451634545100162410ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ django-taggit-5.0.1/.pre-commit-config.yaml000066400000000000000000000011511451634545100205230ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black rev: 23.7.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/asottile/pyupgrade rev: v3.9.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/adamchainz/django-upgrade rev: 1.14.0 hooks: - id: django-upgrade args: [--target-version, "3.2"] django-taggit-5.0.1/.readthedocs.yml000066400000000000000000000002331451634545100173300ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.9" python: install: - method: pip path: . sphinx: configuration: docs/conf.py django-taggit-5.0.1/AUTHORS000066400000000000000000000012311451634545100153110ustar00rootroot00000000000000django-taggit was originally created by Alex Gaynor. django-taggit-serializer was originally created by Paul Oostenrijk. 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 Andrew Pryde John Whitlock Jon Dufresne Pablo Olmedo Dorado django-taggit-5.0.1/CHANGELOG.rst000066400000000000000000000337321451634545100162750ustar00rootroot00000000000000Changelog ========= (Unreleased) ~~~~~~~~~~~~ 5.0.1 (2023-10-26) ~~~~~~~~~~~~~~~~~~ * Fix the package metadata to properly reflect the right Django and Python version requirements Release 5.0.0 improperly stated its Django bounds as >=3.2, so people installing without bounds will end up on a version that won't work. 5.0.0 (2023-10-24) ~~~~~~~~~~~~~~~~~~ * **Backwards icompatible:** Rename the (``content_type``, ``object_id``) index on ``TaggedItem``. It is very unlikely for this to affect your code itself, and a migration will rename the index. This should not cause any downtime according to my research (Postgres does not lock the table for index renames, and Oracle holds a tiny lock to do it, and the change is only to the metadata, so is not dependent on table size). * **Backwards incompatible:** Remove the ``.indexed_together`` and ``.unique_together`` attributes on ``TaggedItem`` We are instead using ``constraints`` and ``indexes`` to set up these properties. * Remove support for Django 3.2. * Remove usage of deprecated APIs for Django 4.2 * Remove support for Python 3.7 (no code changes involved) * Fix ``tag_kwargs`` and ``TAGGIT_CASE_INSENSITIVE=True`` discrepency. 4.0.0 (2023-05-04) ~~~~~~~~~~~~~~~~~~ * Remove Python 3.6 support (no code changes occurred, but we no longer test this release). * Remove Django 4.0 support (no code changes occurred, but we no longer test this release). * Add Django 4.2 support. 3.1.0 (2022-11-08) ~~~~~~~~~~~~~~~~~~ * Add Python 3.11 support (no code changes were needed, but now we test this release). * Add Django 4.1 support (no code changes were needed, but now we test this release). * Fixed an issue where object caches would not be properly cleared after updating tags, leading to stale reads in cases where ``prefetch_related`` is used. * Change ``TagListSerializerField`` to be a subclass of ``ListField``. This should improve support for API document generation. This change should not affect API behavior, but might affect metaprogramming code, so please procede carefully during this update. 3.0.0 (2022-05-02) ~~~~~~~~~~~~~~~~~~ * **Backwards incompatible:** Tag slugification used to silently strip non-ASCII characters from the tag name to make the slug. This leads to a lot of confusion for anyone using languages with non-latin alphabets, as well as weird performance issues. Tag slugification will now, by default, maintain unicode characters as-is during slugification. This will lead to less surprises, but might cause issues for you if you are expecting all of your tag slugs to fit within a regex like ``[a-zA-Z0-9]`` (for example in URL routing configurations). Generally speaking, this should not require action on your part as a library user, as existing tag slugs are persisted in the database, and only new tags will receive the enhanced unicode-compatible slug. If you wish to maintain the old stripping behavior, set the setting ``TAGGIT_STRIP_UNICODE_WHEN_SLUGIFYING`` to ``True``. As a reminder, custom tag models can easily customize slugification behavior by overriding the ``slugify`` method to your business needs. `` Drop Django 2.2 support. 2.1.0 (2022-01-24) ~~~~~~~~~~~~~~~~~~ * Add Python 3.10 support. * Add Django 4.0 support. * Drop Django 3.1 support. 2.0.0 (2021-11-14) ~~~~~~~~~~~~~~~~~~ * **Backwards incompatible:** ``TaggableManager.set`` now takes a list of tags (instead of varargs) so that its API matches Django's ``RelatedManager.set``. Example: - previously: ``item.tags.set("red", "blue")`` - now: ``item.tags.set(["red", "blue"])`` * Fix issue where ``TagField`` would incorrectly report that a field has changed on empty values. * Update Russian translation. * Add Persian translation * Fix issue for many languages where content types were not being properly translated. * Provide translators additional context regarding strings in TagBase model. 1.5.1 (2021-07-01) ~~~~~~~~~~~~~~~~~~ * Fix compiled Ukranian translation (which would cause a failure on load for this locale). * Update compiled Danish translation. 1.5.0 (2021-06-30) ~~~~~~~~~~~~~~~~~~ * Vendor in the `django-taggit-serializer` project (under `taggit.serializers`). * Add Arabic translation. * Add Ukranian translation. 1.4.0 (2021-04-19) ~~~~~~~~~~~~~~~~~~ * Add Python 3.9 support. * Remove Python 3.5 support. * Add Django 3.2 support. * Remove Django 1.11 and 3.0 support. * Add Danish translation. * Fix crashing that could occur with ``similar_objects`` in multi-inheritance contexts. * Add support for custom fields on through table models with `through_defaults` for ``TaggedManager.add`` and ``TaggedManager.set``. 1.3.0 (2020-05-19) ~~~~~~~~~~~~~~~~~~ * Model and field ``verbose_name`` and ``verbose_name_plural`` attributes are now lowercase. This simplifies using the name in the middle of a sentence. When used as a header, title, or at the beginning of a sentence, a text transformed can be used to adjust the case. * Fix prefetch_related when using UUIDTaggedItem. * Allow for passing in extra constructor parameters when using ``TaggableManager.add``. This is especially useful when using custom tag models. 1.2.0 (2019-12-03) ~~~~~~~~~~~~~~~~~~ * **Removed** support for end-of-life Django 2.0 and 2.1. * Added support for Django 3.0. * Added support for Python 3.8. * Moved ``TaggedItemBase.tags_for()`` to ItemBase. * Replaced reference to removed Django's ``.virtual_fields`` with ``.private_field``. * Added ``TextareaTagWidget``. 1.1.0 (2019-03-22) ~~~~~~~~~~~~~~~~~~ * Added Finnish translation. * Updated Chinese translation. * Updated Esperanto translation. * Fix ``form.changed_data`` to allow early access for a tags defined with ``blank=True``. 1.0.0 (2019-03-17) ~~~~~~~~~~~~~~~~~~ * **Backwards incompatible:** Remove support for Python 2. * Added ``has_changed()`` method to ``taggit.forms.TagField``. * Added multi-column unique constraint to model ``TaggedItem`` on fields ``content_type``, ``object_id``, and ``tag``. Databases that contain duplicates will need to add a data migration to resolve these duplicates. * Fixed ``TaggableManager.most_common()`` to always evaluate lazily. Allows placing a ``.most_common()`` query at the top level of a module. * Fixed setting the ``related_name`` on a tags manager that exists on a model named ``Name``. 0.24.0 (2019-02-19) ~~~~~~~~~~~~~~~~~~~ * The project has moved to `Jazzband `_. This is the first release under the new organization. The new repository URL is ``_. * Added support for Django 2.2. * Fixed a race condition in ``TaggableManager``. * Removed method ``ItemBase.bulk_lookup_kwargs()``. * Fixed view ``tagged_object_list`` to set ``queryset.model`` as ``ListView.model`` (was previously set as a ``ContentType`` instance). * ``_TaggableManager`` and ``TaggableManager`` now always call the parent class ``__init__``. * Removed ``TaggableRel`` and replaced uses with ``ManyToManyRel``. 0.23.0 (2018-08-07) ~~~~~~~~~~~~~~~~~~~ * **Backwards incompatible:** Remove support for Django < 1.11 * Added support for Django 2.1 and Python 3.7 * Moved TagWidget value conversion from TagWidget.render() to TagWidget.format_value() 0.22.2 (2017-12-27) ~~~~~~~~~~~~~~~~~~~ * Added support for Django 2.0 * **Backwards incompatible:** Dropped support for EOL Python 3.3 0.22.1 (2017-04-22) ~~~~~~~~~~~~~~~~~~~ * Update spanish translation * Add testing for Django 1.11 and Python 3.6 * introduce isort and flake8 in the CI * [docs] Fixed links to external apps * Improved auto-slug in TagBase to support UUID pk * [docs] Added contribution guidelines 0.22.0 (2017-01-29) ~~~~~~~~~~~~~~~~~~~ * **Backwards incompatible:** Drop support for Django 1.7 0.21.6 (2017-01-25) ~~~~~~~~~~~~~~~~~~~ * Fix case-insensitive tag creation when setting to a mix of new and existing tags are used 0.21.5 (2017-01-21) ~~~~~~~~~~~~~~~~~~~ * Check for case-insensitive duplicates when creating new tags 0.21.4 (2017-01-10) ~~~~~~~~~~~~~~~~~~~ * Support __gt__ and __lt__ ordering on Tags 0.21.3 (2016-10-07) ~~~~~~~~~~~~~~~~~~~ * Fix list view 0.21.2 (2016-08-31) ~~~~~~~~~~~~~~~~~~~ * Update Python version classifiers in setup.py * Add Greek translation 0.21.1 (2016-08-25) ~~~~~~~~~~~~~~~~~~~ * Document supported versions of Django; fix Travis to test these versions. 0.21.0 (2016-08-22) ~~~~~~~~~~~~~~~~~~~ * Fix form tests on Django 1.10 * Address list_display and fieldsets in admin docs * external_apps.txt improvements * Remove support for Django 1.4-1.6, again. 0.20.2 (2016-07-11) ~~~~~~~~~~~~~~~~~~~ * Add extra_filters argument to the manager's most_common method 0.20.1 (2016-06-23) ~~~~~~~~~~~~~~~~~~~ * Specify `app_label` for `Tag` and `TaggedItem` 0.20.0 (2016-06-19) ~~~~~~~~~~~~~~~~~~~ * Fix UnboundLocalError in _TaggableManager.set(..) * Update doc links to reflect RTD domain changes * Improve Russian translations 0.19.1 (2016-05-25) ~~~~~~~~~~~~~~~~~~~ * Add app config, add simplified Chinese translation file 0.19.0 (2016-05-23) ~~~~~~~~~~~~~~~~~~~ * Implementation of m2m_changed signal sending * Code and tooling improvements 0.18.3 (2016-05-12) ~~~~~~~~~~~~~~~~~~~ * Added Spanish and Turkish translations 0.18.2 (2016-05-08) ~~~~~~~~~~~~~~~~~~~ * Add the min_count parameter to managers.most_common function 0.18.1 (2016-03-30) ~~~~~~~~~~~~~~~~~~~ * Address deprecation warnings 0.18.0 (2016-01-18) ~~~~~~~~~~~~~~~~~~~ * Add option to override default tag string parsing * Drop support for Python 2.6 0.17.6 (2015-12-09) ~~~~~~~~~~~~~~~~~~~ * Silence Django 1.9 warning 0.17.5 (2015-11-27) ~~~~~~~~~~~~~~~~~~~ * Django 1.9 compatibility fix 0.17.4 (2015-11-25) ~~~~~~~~~~~~~~~~~~~ * Allows custom Through Model with GenericForeignKey 0.17.3 (2015-10-26) ~~~~~~~~~~~~~~~~~~~ * Silence Django 1.9 warning about on_delete 0.17.2 (2015-10-25) ~~~~~~~~~~~~~~~~~~~ * Django 1.9 beta compatibility 0.17.1 (2015-09-10) ~~~~~~~~~~~~~~~~~~~ * Fix unknown column `object_id` issue with Django 1.6+ 0.17.0 (2015-08-14) ~~~~~~~~~~~~~~~~~~~ * Database index added on TaggedItem fields content_type & object_id 0.16.4 (2015-08-13) ~~~~~~~~~~~~~~~~~~~ * Access default manager via class instead of instance 0.16.3 (2015-08-08) ~~~~~~~~~~~~~~~~~~~ * Prevent IntegrityError with custom TagBase classes 0.16.2 (2015-07-13) ~~~~~~~~~~~~~~~~~~~ * Fix an admin bug related to the `Manager` property `through_fields` 0.16.1 (2015-07-09) ~~~~~~~~~~~~~~~~~~~ * Fix bug that assumed all primary keys are named 'id' 0.16.0 (2015-07-04) ~~~~~~~~~~~~~~~~~~~ * Add option to allow case-insensitive tags 0.15.0 (2015-06-23) ~~~~~~~~~~~~~~~~~~~ * Fix wrong slugs for non-latin chars. Only works if optional GPL dependency (unidecode) is installed. 0.14.0 (2015-04-26) ~~~~~~~~~~~~~~~~~~~ * Prevent extra JOIN when prefetching * Prevent _meta warnings with Django 1.8 0.13.0 (2015-04-02) ~~~~~~~~~~~~~~~~~~~ * Django 1.8 support 0.12.3 (2015-03-03) ~~~~~~~~~~~~~~~~~~~ * Specify that the internal type of the TaggitManager is a ManyToManyField 0.12.2 (2014-21-09) ~~~~~~~~~~~~~~~~~~~ * Fixed 1.7 migrations. 0.12.1 (2014-10-08) ~~~~~~~~~~~~~~~~~~~ * Final (hopefully) fixes for the upcoming Django 1.7 release. * Added Japanese translation. 0.12.0 (2014-20-04) ~~~~~~~~~~~~~~~~~~~ * **Backwards incompatible:** Support for Django 1.7 migrations. South users have to set ``SOUTH_MIGRATION_MODULES`` to use ``taggit.south_migrations`` for taggit. * **Backwards incompatible:** Django's new transaction handling is used on Django 1.6 and newer. * **Backwards incompatible:** ``Tag.save`` got changed to opportunistically try to save the tag and if that fails fall back to selecting existing similar tags and retry -- if that fails too an ``IntegrityError`` is raised by the database, your app will have to handle that. * Added Italian and Esperanto translations. 0.11.2 (2013-13-12) ~~~~~~~~~~~~~~~~~~~ * Forbid multiple TaggableManagers via generic foreign keys. 0.11.1 (2013-25-11) ~~~~~~~~~~~~~~~~~~~ * Fixed support for Django 1.4 and 1.5. 0.11.0 (2013-25-11) ~~~~~~~~~~~~~~~~~~~ * Added support for prefetch_related on tags fields. * Fixed support for Django 1.7. * Made the tagging relations unserializeable again. * Allow more than one TaggableManager on models (assuming concrete FKs are used for the relations). 0.10.0 (2013-17-08) ~~~~~~~~~~~~~~~~~~~ * Support for Django 1.6 and 1.7. * Python3 support * **Backwards incompatible:** Dropped support for Django < 1.4.5. * Tag names are unique now, use the provided South migrations to upgrade. 0.9.2 (2011-01-17) ~~~~~~~~~~~~~~~~~~ * **Backwards incompatible:** Forms containing a :class:`TaggableManager` by default now require tags, to change this provide ``blank=True`` to the :class:`TaggableManager`. * Now works with Django 1.3 (as of beta-1). 0.9.0 (2010-09-22) ~~~~~~~~~~~~~~~~~~ * Added a Hebrew locale. * Added an index on the ``object_id`` field of ``TaggedItem``. * When displaying tags always join them with commas, never spaces. * The docs are now available `online `_. * Custom ``Tag`` models are now allowed. * **Backwards incompatible:** Filtering on tags is no longer ``filter(tags__in=["foo"])``, it is written ``filter(tags__name__in=["foo"])``. * Added a German locale. * Added a Dutch locale. * Removed ``taggit.contrib.suggest``, it now lives in an external application, see :doc:`external_apps` for more information. 0.8.0 (2010-06-22) ~~~~~~~~~~~~~~~~~~ * Fixed querying for objects using ``exclude(tags__in=tags)``. * Marked strings as translatable. * Added a Russian translation. * Created a `mailing list `_. * 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-5.0.1/CODE_OF_CONDUCT.md000066400000000000000000000045071451634545100170510ustar00rootroot00000000000000# Code of Conduct As contributors and maintainers of the Jazzband projects, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in the Jazzband a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery - Personal attacks - Trolling or insulting/derogatory comments - Public or private harassment - Publishing other's private information, such as physical or electronic addresses, without explicit permission - Other unethical or unprofessional conduct The Jazzband roadies have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, the roadies commit themselves to fairly and consistently applying these principles to every aspect of managing the jazzband projects. Roadies who do not follow or enforce the Code of Conduct may be permanently removed from the Jazzband roadies. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Roadies are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version] [homepage]: https://contributor-covenant.org [version]: https://contributor-covenant.org/version/1/3/0/ django-taggit-5.0.1/CONTRIBUTING.rst000066400000000000000000000056261451634545100167160ustar00rootroot00000000000000Contributing to django-taggit ============================= .. image:: https://jazzband.co/static/img/jazzband.svg :target: https://jazzband.co/ :alt: Jazzband This is a `Jazzband `_ project. By contributing you agree to abide by the `Contributor Code of Conduct `_ and follow the `guidelines `_. Thank you for taking the time to contribute to django-taggit. Follow these guidelines to speed up the process. Reach out before you start -------------------------- Before opening a new issue, look if somebody else has already started working on the same issue in the `GitHub issues `_ and `pull requests `_. Fork the repository ------------------- Once you have forked this repository to your own GitHub account, install your own fork in your development environment: .. code-block:: console git clone git@github.com:/django-taggit.git cd django-taggit python setup.py develop Running tests ------------- django-taggit uses `tox `_ to run tests: .. code-block:: console tox Follow style conventions (black, flake8, isort) ----------------------------------------------- Check that your changes are not breaking the style conventions with: .. code-block:: console tox -e black,flake8,isort Update the documentation ------------------------ If you introduce new features or change existing documented behavior, please remember to update the documentation. The documentation is located in the ``docs`` directory of the repository. To do work on the docs, proceed with the following steps: .. code-block:: console pip install sphinx sphinx-build -n -W docs docs/_build Add a changelog line -------------------- Even when the change is minor, a changelog line is helpful to both describe the intent of the change, and to give a heads up to people upgrading. You can add a line in the ``(Unreleased)`` section of ``CHANGELOG.rst``, along with any more detailed explanations for more complicated changes. Send pull request ----------------- It is now time to push your changes to GitHub and open a `pull request `_! Release Checklist ----------------- These steps need to happen by a release maintainer. To make a release, the following needs to happen: - Make sure that ``setup.cfg`` is set up properly w/r/t Python and Django requirements - Make sure the documentation (``docs/index.rst``) also describes the right Python/Django versions - Bump the version number in ``taggit/__init__.py`` - Update the changelog (making sure to add the (Unreleased) section to the top) - Get those changes onto the ``master`` branch - Tag the commit with the version number - CI should then upload a release to be verified through Jazzband django-taggit-5.0.1/LICENSE000066400000000000000000000030321451634545100152470ustar00rootroot00000000000000Copyright (c) Alex Gaynor, Paul Oostenrijk, 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-5.0.1/MANIFEST.in000066400000000000000000000003111451634545100157750ustar00rootroot00000000000000include AUTHORS include CHANGELOG.rst include LICENSE include README.rst include tox.ini recursive-include docs *.py *.rst recursive-include taggit/locale *.mo *.po recursive-include tests *.html *.py django-taggit-5.0.1/README.rst000066400000000000000000000040771451634545100157430ustar00rootroot00000000000000django-taggit ============= .. image:: https://jazzband.co/static/img/badge.svg :target: https://jazzband.co/ :alt: Jazzband .. image:: https://img.shields.io/pypi/pyversions/django-taggit.svg :target: https://pypi.org/project/django-taggit/ :alt: Supported Python versions .. image:: https://img.shields.io/pypi/djversions/django-taggit.svg :target: https://pypi.org/project/django-taggit/ :alt: Supported Django versions .. image:: https://github.com/jazzband/django-taggit/workflows/Test/badge.svg :target: https://github.com/jazzband/django-taggit/actions :alt: GitHub Actions .. image:: https://codecov.io/gh/jazzband/django-taggit/coverage.svg?branch=master :target: https://codecov.io/gh/jazzband/django-taggit?branch=master This is a `Jazzband `_ project. By contributing you agree to abide by the `Contributor Code of Conduct `_ and follow the `guidelines `_. ``django-taggit`` a simpler approach to tagging with Django. Add ``"taggit"`` to your ``INSTALLED_APPS`` then just add a TaggableManager to your model and go: .. code:: python from django.db import models from taggit.managers import TaggableManager class Food(models.Model): # ... fields here tags = TaggableManager() Then you can use the API like so: .. code:: pycon >>> 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 3.2 or greater. For more info check out the `documentation `_. And for questions about usage or development you can create an issue on Github (if your question is about usage please add the `question` tag). django-taggit-5.0.1/docs/000077500000000000000000000000001451634545100151745ustar00rootroot00000000000000django-taggit-5.0.1/docs/admin.rst000066400000000000000000000027651451634545100170300ustar00rootroot00000000000000Using 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. If you are specifying :attr:`ModelAdmin.fieldsets `, include the name of the :class:`TaggableManager` as a field:: fieldsets = ( (None, {'fields': ('tags',)}), ) Including tags in ``ModelAdmin.list_display`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 'name' This is for the same reason that you cannot include a :class:`~django.db.models.ManyToManyField`: it would result in an unreasonable number of queries being executed. If you want to show tags in :attr:`ModelAdmin.list_display `, you can add a custom display method to the :class:`~django.contrib.admin.ModelAdmin`, using :meth:`~django.db.models.query.QuerySet.prefetch_related` to minimize queries:: class MyModelAdmin(admin.ModelAdmin): list_display = ['tag_list'] def get_queryset(self, request): return super().get_queryset(request).prefetch_related('tags') def tag_list(self, obj): return u", ".join(o.name for o in obj.tags.all()) django-taggit-5.0.1/docs/api.rst000066400000000000000000000104541451634545100165030ustar00rootroot00000000000000The 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, through_defaults=None, tag_kwargs=None) This adds tags to an object. The tags can be either ``Tag`` instances, or strings:: >>> apple.tags.all() [] >>> apple.tags.add("red", "green", "fruit") Use the ``through_defaults`` argument to specify values for your custom ``through`` model, if needed. The ``tag_kwargs`` argument allows one to specify parameters for the tags themselves. .. 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, *, through_defaults=None, clear=False) If ``clear = True`` removes all the current tags and then adds the specified tags to the object. Otherwise sets the object's tags to those specified, removing only the missing tags and adding only the new tags. Use the ``through_defaults`` argument to specify values for your custom ``through`` model, if needed. .. 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. :param min_count: Specify a min count to limit the returned queryset .. 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() ["green and juicy", "red"] .. method:: slugs() Convenience method, returning a ``ValuesListQuerySet`` (basically just an iterable) containing the slug of each tag as a string:: >>> apple.tags.slugs() ["green-and-juicy", "red"] .. hint:: You can subclass ``_TaggableManager`` (note the underscore) to add methods or functionality. ``TaggableManager`` takes an optional manager keyword argument for your custom class, like this:: class Food(models.Model): # ... fields here tags = TaggableManager(manager=_CustomTaggableManager) Filtering ~~~~~~~~~ To find all of a model with a specific tags you can filter, using the normal Django ORM API. For example if you had a ``Food`` model, whose ``TaggableManager`` was named ``tags``, you could find all the delicious fruit like so:: >>> Food.objects.filter(tags__name__in=["delicious"]) [, , ] 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. django-taggit-5.0.1/docs/changelog.rst000066400000000000000000000000361451634545100176540ustar00rootroot00000000000000.. include:: ../CHANGELOG.rst django-taggit-5.0.1/docs/conf.py000066400000000000000000000007741451634545100165030ustar00rootroot00000000000000import taggit extensions = ["sphinx.ext.intersphinx"] master_doc = "index" project = "django-taggit" copyright = "Alex Gaynor and individual contributors." # The short X.Y version. version = taggit.__version__ # The full version, including alpha/beta/rc tags. release = taggit.__version__ intersphinx_mapping = { "django": ( "https://docs.djangoproject.com/en/stable", "https://docs.djangoproject.com/en/stable/_objects/", ), "python": ("https://docs.python.org/3", None), } django-taggit-5.0.1/docs/contributing.rst000066400000000000000000000000411451634545100204300ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst django-taggit-5.0.1/docs/custom_tagging.rst000066400000000000000000000202361451634545100207430ustar00rootroot00000000000000Customizing taggit ================== Using a Custom Tag or Through Model ----------------------------------- By default ``django-taggit`` uses a "through model" with a ``GenericForeignKey`` on it, that has another ``ForeignKey`` to an included ``Tag`` model. However, there are some cases where this isn't desirable, for example if you want the speed and referential guarantees of a real ``ForeignKey``, if you have a model with a non-integer primary key, or if you want to store additional data about a tag, such as whether it is official. In these cases ``django-taggit`` makes it easy to substitute your own through model, or ``Tag`` model. Note: Including 'taggit' in ``settings.py`` INSTALLED_APPS list will create the default ``django-taggit`` and "through model" models. If you would like to use your own models, you will need to remove 'taggit' from ``settings.py``'s INSTALLED_APPS list. To change the behavior there are a number of classes you can subclass to obtain different behavior: =============================== ======================================================================= Class name Behavior =============================== ======================================================================= ``TaggedItemBase`` Allows custom ``ForeignKeys`` to models. ``GenericTaggedItemBase`` Allows custom ``Tag`` models. Tagged models use an integer primary key. ``GenericUUIDTaggedItemBase`` Allows custom ``Tag`` models. Tagged models use a UUID primary key. ``CommonGenericTaggedItemBase`` Allows custom ``Tag`` models and ``GenericForeignKeys`` to models. ``ItemBase`` Allows custom ``Tag`` models and ``ForeignKeys`` to models. =============================== ======================================================================= Custom ForeignKeys ~~~~~~~~~~~~~~~~~~ Your intermediary model must be a subclass of ``taggit.models.TaggedItemBase`` with a foreign key to your content model named ``content_object``. Pass this intermediary model as the ``through`` argument to ``TaggableManager``: .. code-block:: python from django.db import models from taggit.managers import TaggableManager from taggit.models import TaggedItemBase class TaggedFood(TaggedItemBase): content_object = models.ForeignKey('Food', on_delete=models.CASCADE) class Food(models.Model): # ... fields here tags = TaggableManager(through=TaggedFood) Once this is done, the API works the same as for GFK-tagged models. Custom GenericForeignKeys ~~~~~~~~~~~~~~~~~~~~~~~~~ The default ``GenericForeignKey`` used by ``django-taggit`` assume your tagged object use an integer primary key. For non-integer primary key, your intermediary model must be a subclass of ``taggit.models.CommonGenericTaggedItemBase`` with a field named ``"object_id"`` of the type of your primary key. For example, if your primary key is a string: .. code-block:: python from django.db import models from taggit.managers import TaggableManager from taggit.models import CommonGenericTaggedItemBase, TaggedItemBase class GenericStringTaggedItem(CommonGenericTaggedItemBase, TaggedItemBase): object_id = models.CharField(max_length=50, verbose_name=_('Object id'), db_index=True) class Food(models.Model): food_id = models.CharField(primary_key=True) # ... fields here tags = TaggableManager(through=GenericStringTaggedItem) GenericUUIDTaggedItemBase ~~~~~~~~~~~~~~~~~~~~~~~~~ A common use case of a non-integer primary key, is UUID primary key. ``django-taggit`` provides a base class ``GenericUUIDTaggedItemBase`` ready to use with models using an UUID primary key: .. code-block:: python from django.db import models from django.utils.translation import gettext_lazy as _ from taggit.managers import TaggableManager from taggit.models import GenericUUIDTaggedItemBase, TaggedItemBase class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase): # If you only inherit GenericUUIDTaggedItemBase, you need to define # a tag field. e.g. # tag = models.ForeignKey(Tag, related_name="uuid_tagged_items", on_delete=models.CASCADE) class Meta: verbose_name = _("Tag") verbose_name_plural = _("Tags") class Food(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # ... fields here tags = TaggableManager(through=UUIDTaggedItem) Custom tag ~~~~~~~~~~ When providing a custom ``Tag`` model it should be a ``ForeignKey`` to your tag model named ``"tag"``. If your custom ``Tag`` model has extra parameters you want to initialize during setup, you can do so by passing it along via the ``tag_kwargs`` parameter of ``TaggableManager.add``. For example ``my_food.tags.add("tag_name1", "tag_name2", tag_kwargs={"my_field":3})``: .. code-block:: python from django.db import models from django.utils.translation import gettext_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, on_delete=models.CASCADE, 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.utils.text.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. Using a custom tag string parser -------------------------------- By default ``django-taggit`` uses ``taggit.utils._parse_tags`` which accepts a string which may contain one or more tags and returns a list of tag names. This parser is quite intelligent and can handle a number of edge cases; however, you may wish to provide your own parser for various reasons (e.g. you can do some preprocessing on the tags so that they are converted to lowercase, reject certain tags, disallow certain characters, split only on commas rather than commas and whitespace, etc.). To provide your own parser, write a function that takes a tag string and returns a list of tag names. For example, a simple function to split on comma and convert to lowercase: .. code-block:: python def comma_splitter(tag_string): return [t.strip().lower() for t in tag_string.split(',') if t.strip()] You need to tell ``taggit`` to use this function instead of the default by adding a new setting, ``TAGGIT_TAGS_FROM_STRING`` and providing it with the dotted path to your function. Likewise, you can provide a function to convert a list of tags to a string representation and use the setting ``TAGGIT_STRING_FROM_TAGS`` to override the default value (which is ``taggit.utils._edit_string_for_tags``): .. code-block:: python def comma_joiner(tags): return ', '.join(t.name for t in tags) If the functions above were defined in a module, ``appname.utils``, then your project settings.py file should contain the following: .. code-block:: python TAGGIT_TAGS_FROM_STRING = 'appname.utils.comma_splitter' TAGGIT_STRING_FROM_TAGS = 'appname.utils.comma_joiner' django-taggit-5.0.1/docs/external_apps.rst000066400000000000000000000040541451634545100205760ustar00rootroot00000000000000External 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 `django-taggit on github `_, add it to this list, and send a pull request. * `django-taggit-anywhere `_: Simpler approach to tagging with ``taggit``. Additionally this project provides easy-to-use integration with ``django-taggit-helpers`` and ``django-taggit-labels``. * `django-taggit-helpers `_: Makes it easier to work with admin pages of models associated with ``taggit`` tags by adding helper classes: ``TaggitCounter``, ``TaggitListFilter``, ``TaggitStackedInline``, ``TaggitTabularInline``. * `django-taggit-labels `_: Provides a clickable label widget for the Django admin for user friendly selection from managed tag sets. * `django-taggit-serializer `_: Adds functionality for using ``taggit`` with ``django-rest-framework``. * `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``. * `django-taggit-templatetags `_: Provides several templatetags, including one for tag clouds, to expose various ``taggit`` APIs directly to templates. * `django-taggit-bulk `_: An admin action for the bulk tagging from the model admin instance list view. django-taggit-5.0.1/docs/faq.rst000066400000000000000000000020401451634545100164710ustar00rootroot00000000000000Frequently Asked Questions ========================== - How can I get all my tags? If you are using just an out-of-the-box setup, your tags are stored in the `Tag` model (found in `taggit.models`). If this is a custom model (for example you have your own models derived from `ItemBase`), then you'll need to query that one instead. So if you are using the standard setup, ``Tag.objects.all()`` will give you the tags. - How can I use this with factory_boy? Since these are all built off of many-to-many relationships, you can check out `factory_boy's documentation on this topic `_ and get some ideas on how to deal with tags. One way to handle this is with post-generation hooks:: class ProductFactory(DjangoModelFactory): # Rest of the stuff @post_generation def tags(self, create, extracted, **kwargs): if not create: return if extracted: self.tags.add(*extracted) django-taggit-5.0.1/docs/forms.rst000066400000000000000000000045121451634545100170560ustar00rootroot00000000000000.. _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() You can check the details over in the `Django documentation on form saving `_. django-taggit-5.0.1/docs/getting_started.rst000066400000000000000000000031751451634545100211230ustar00rootroot00000000000000Getting 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 migrate`. And then to any model you want tagging on do the following:: from django.db import models from taggit.managers import TaggableManager class Food(models.Model): # ... fields here tags = TaggableManager() .. note:: If you want ``django-taggit`` to be **CASE-INSENSITIVE** when looking up existing tags, you'll have to set ``TAGGIT_CASE_INSENSITIVE`` (in ``settings.py`` or wherever you have your Django settings) to ``True`` (``False`` by default):: TAGGIT_CASE_INSENSITIVE = True Settings -------- The following Django-level settings affect the behavior of the library * ``TAGGIT_CASE_INSENSITIVE`` When set to ``True``, tag lookups will be case insensitive. This defaults to ``False``. * ``TAGGIT_STRIP_UNICODE_WHEN_SLUGIFYING`` When this is set to ``True``, tag slugs will be limited to ASCII characters. In this case, if you also have ``unidecode`` installed, then tag sluggification will transform a tag like ``ã‚ã„ ã†ãˆãŠ`` to ``ai-ueo``. If you do not have ``unidecode`` installed, then you will usually be outright stripping unicode, meaning that something like ``helloã‚ã„`` will be slugified as ``hello``. This value defaults to ``False``, meaning that unicode is preserved in slugification. Because the behavior when ``True`` is set leads to situations where slugs can be entirely stripped to an empty string, we recommend not activating this. django-taggit-5.0.1/docs/index.rst000066400000000000000000000007761451634545100170470ustar00rootroot00000000000000Welcome to django-taggit's documentation! ========================================= ``django-taggit`` is a reusable Django application designed to make adding tagging to your project easy and fun. ``django-taggit`` works with Django 4.1+ and Python 3.8+. .. toctree:: :maxdepth: 2 getting_started forms admin serializers api faq custom_tagging contributing external_apps changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` django-taggit-5.0.1/docs/serializers.rst000066400000000000000000000014061451634545100202630ustar00rootroot00000000000000Usage With Django Rest Framework ================================ Because the tags in `django-taggit` need to be added into a `TaggableManager()` we cannot use the usual `Serializer` that we get from Django REST Framework. Because this is trying to save the tags into a `list`, which will throw an exception. To accept tags through a `REST` API call we need to add the following to our `Serializer`:: from taggit.serializers import (TagListSerializerField, TaggitSerializer) class YourSerializer(TaggitSerializer, serializers.ModelSerializer): tags = TagListSerializerField() class Meta: model = YourModel fields = '__all__' And you're done, so now you can add tags to your model. django-taggit-5.0.1/requirements/000077500000000000000000000000001451634545100167675ustar00rootroot00000000000000django-taggit-5.0.1/requirements/docs.txt000066400000000000000000000000251451634545100204550ustar00rootroot00000000000000Sphinx docutils<0.18 django-taggit-5.0.1/requirements/test.txt000066400000000000000000000000251451634545100205040ustar00rootroot00000000000000Django>=3.2 coverage django-taggit-5.0.1/setup.cfg000066400000000000000000000025131451634545100160660ustar00rootroot00000000000000[metadata] name = django-taggit version = attr: taggit.__version__ description = django-taggit is a reusable Django application for simple tagging. long_description = file: README.rst author = Alex Gaynor author_email = alex.gaynor@gmail.com url = https://github.com/jazzband/django-taggit license = BSD classifiers = Development Status :: 5 - Production/Stable Environment :: Web Environment Framework :: Django Framework :: Django :: 4.1 Framework :: Django :: 4.2 Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 project_urls = Documentation = https://django-taggit.readthedocs.io Source = https://github.com/jazzband/django-taggit Tracker = https://github.com/jazzband/django-taggit/issues [options] python_requires = >=3.8 packages = find: install_requires = Django>=4.1 include_package_data = true zip_safe = false [options.packages.find] exclude = tests* [flake8] # E501: line too long ignore = E501 exclude = .venv,.git,.tox [isort] profile = black django-taggit-5.0.1/setup.py000066400000000000000000000000461451634545100157560ustar00rootroot00000000000000from setuptools import setup setup() django-taggit-5.0.1/taggit/000077500000000000000000000000001451634545100155235ustar00rootroot00000000000000django-taggit-5.0.1/taggit/__init__.py000066400000000000000000000001041451634545100176270ustar00rootroot00000000000000VERSION = (5, 0, 1) __version__ = ".".join(str(i) for i in VERSION) django-taggit-5.0.1/taggit/admin.py000066400000000000000000000005721451634545100171710ustar00rootroot00000000000000from django.contrib import admin from taggit.models import Tag, TaggedItem class TaggedItemInline(admin.StackedInline): model = TaggedItem @admin.register(Tag) class TagAdmin(admin.ModelAdmin): inlines = [TaggedItemInline] list_display = ["name", "slug"] ordering = ["name", "slug"] search_fields = ["name"] prepopulated_fields = {"slug": ["name"]} django-taggit-5.0.1/taggit/apps.py000066400000000000000000000003651451634545100170440ustar00rootroot00000000000000from django.apps import AppConfig as BaseConfig from django.utils.translation import gettext_lazy as _ class TaggitAppConfig(BaseConfig): name = "taggit" verbose_name = _("Taggit") default_auto_field = "django.db.models.AutoField" django-taggit-5.0.1/taggit/forms.py000066400000000000000000000026361451634545100172320ustar00rootroot00000000000000from django import forms from django.utils.translation import gettext as _ from taggit.utils import edit_string_for_tags, parse_tags class TagWidgetMixin: def format_value(self, value): if value is not None and not isinstance(value, str): value = edit_string_for_tags(value) return super().format_value(value) class TagWidget(TagWidgetMixin, forms.TextInput): pass class TextareaTagWidget(TagWidgetMixin, forms.Textarea): pass class TagField(forms.CharField): widget = TagWidget def clean(self, value): value = super().clean(value) try: return parse_tags(value) except ValueError: raise forms.ValidationError( _("Please provide a comma-separated list of tags.") ) def has_changed(self, initial_value, data_value): # Always return False if the field is disabled since self.bound_data # always uses the initial value in this case. if self.disabled: return False try: data_value = self.clean(data_value) except forms.ValidationError: pass # normalize "empty values" if not data_value: data_value = [] if not initial_value: initial_value = [] initial_value = [tag.name for tag in initial_value] initial_value.sort() return initial_value != data_value django-taggit-5.0.1/taggit/locale/000077500000000000000000000000001451634545100167625ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/ar/000077500000000000000000000000001451634545100173645ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/ar/LC_MESSAGES/000077500000000000000000000000001451634545100211515ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/ar/LC_MESSAGES/django.mo000066400000000000000000000024231451634545100227510ustar00rootroot00000000000000Þ•ŒüHIhˆ˜.¨×Þ ã ðú þ œ&¹.à B0s ‚¥ ¿Êæ    %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.TaggitTagscontent typeobject IDtagtagged itemtagged itemstagsProject-Id-Version: Report-Msgid-Bugs-To: PO-Revision-Date: 2021-04-19 23:30+0100 Last-Translator: Soufyane Hedidi Language-Team: Language: ar_DZ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Generator: Poedit 2.3 Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5); %(object)s الموسوم بـ %(tag)sقائمة وسوم Ù…ÙØµÙˆÙ„Ø© Ø¨ÙØ§ØµÙ„Ø©.الإسمسبيكة الوسميرجى توÙير قائمة وسوم Ù…ÙØµÙˆÙ„Ø© Ø¨ÙØ§ØµÙ„Ø©.أوْسÙمهالوسومنوع المحتوىمعرّÙ٠الكائنالوسمالعنصر الموسومالعناصر الموسومةالوسومdjango-taggit-5.0.1/taggit/locale/ar/LC_MESSAGES/django.po000066400000000000000000000042361451634545100227600ustar00rootroot00000000000000# 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: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:27+0900\n" "PO-Revision-Date: 2021-04-19 23:30+0100\n" "Last-Translator: Soufyane Hedidi \n" "Language-Team: \n" "Language: ar_DZ\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.3\n" "Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "أوْسÙمه" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "يرجى توÙير قائمة وسوم Ù…ÙØµÙˆÙ„Ø© Ø¨ÙØ§ØµÙ„Ø©." #: taggit/managers.py:432 msgid "Tags" msgstr "الوسوم" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "قائمة وسوم Ù…ÙØµÙˆÙ„Ø© Ø¨ÙØ§ØµÙ„Ø©." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "الإسم" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "سبيكة الوسم" #: taggit/models.py:82 msgid "tag" msgstr "الوسم" #: taggit/models.py:83 msgid "tags" msgstr "الوسوم" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s الموسوم بـ %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "نوع المحتوى" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "معرّÙ٠الكائن" #: taggit/models.py:180 msgid "tagged item" msgstr "العنصر الموسوم" #: taggit/models.py:181 msgid "tagged items" msgstr "العناصر الموسومة" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/cs/000077500000000000000000000000001451634545100173675ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/cs/LC_MESSAGES/000077500000000000000000000000001451634545100211545ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/cs/LC_MESSAGES/django.mo000066400000000000000000000020221451634545100227470ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï Û]è!F!hŠ‘)–À Å ÐÛßù   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: 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ůJménoSlugVložte Äárkami oddÄ›lený seznam tagůTagyTyp obsahuID objektuTagTagem oznaÄená položkaTagy oznaÄené položkydjango-taggit-5.0.1/taggit/locale/cs/LC_MESSAGES/django.po000066400000000000000000000037251451634545100227650ustar00rootroot00000000000000# 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: 2021-11-14 11:28+0900\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Vložte Äárkami oddÄ›lený seznam tagů" #: taggit/managers.py:432 msgid "Tags" msgstr "Tagy" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Čárkami oddÄ›lený seznam tagů" #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Jméno" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Slug" #: taggit/models.py:82 msgid "tag" msgstr "Tag" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s oznaÄen tagem %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Typ obsahu" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "ID objektu" #: taggit/models.py:180 msgid "tagged item" msgstr "Tagem oznaÄená položka" #: taggit/models.py:181 msgid "tagged items" msgstr "Tagy oznaÄené položky" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/da/000077500000000000000000000000001451634545100173465ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/da/LC_MESSAGES/000077500000000000000000000000001451634545100211335ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/da/LC_MESSAGES/django.mo000066400000000000000000000020111451634545100227240ustar00rootroot00000000000000Þ•ŒüHIhˆ˜.¨×Þ ã ðú þ "?^{€+…±¸ À Í×Þî   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.TaggitTagscontent typeobject IDtagtagged itemtagged itemstagsProject-Id-Version: Report-Msgid-Bugs-To: PO-Revision-Date: 2020-10-25 14:29+0100 Last-Translator: Language-Team: Language: da MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 2.4.1 %(object)s mærket med %(tag)sAdskil mærker med et komma.navnslugVenligts angiv mærker adskilt af et komma.TaggitMærkerindholdstypeobjekt IDmærkemærket elementmærkede elementermærkerdjango-taggit-5.0.1/taggit/locale/da/LC_MESSAGES/django.po000066400000000000000000000036251451634545100227430ustar00rootroot00000000000000# 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 , 2020. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:29+0900\n" "PO-Revision-Date: 2020-10-25 14:29+0100\n" "Last-Translator: \n" "Language-Team: \n" "Language: da\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" "X-Generator: Poedit 2.4.1\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "Taggit" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Venligts angiv mærker adskilt af et komma." #: taggit/managers.py:432 msgid "Tags" msgstr "Mærker" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Adskil mærker med et komma." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "navn" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "slug" #: taggit/models.py:82 msgid "tag" msgstr "mærke" #: taggit/models.py:83 msgid "tags" msgstr "mærker" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s mærket med %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "indholdstype" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "objekt ID" #: taggit/models.py:180 msgid "tagged item" msgstr "mærket element" #: taggit/models.py:181 msgid "tagged items" msgstr "mærkede elementer" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/de/000077500000000000000000000000001451634545100173525ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/de/LC_MESSAGES/000077500000000000000000000000001451634545100211375ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/de/LC_MESSAGES/django.mo000066400000000000000000000020541451634545100227370ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï ÛHè&1+X„‰:‘ Ì Ú å ïú   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: PO-Revision-Date: 2010-09-07 09:26-0700 Last-Translator: Jannis Leidel Language-Team: German Language: de 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.NameKürzelBitte eine durch Komma getrennte Schlagwortliste eingeben.SchlagwörterInhaltstypObjekt-IDSchlagwortVerschlagwortetes ObjektVerschlagwortete Objektedjango-taggit-5.0.1/taggit/locale/de/LC_MESSAGES/django.po000066400000000000000000000034571451634545100227520ustar00rootroot00000000000000#, fuzzy msgid "" msgstr "" "Project-Id-Version: django-taggit\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:29+0900\n" "PO-Revision-Date: 2010-09-07 09:26-0700\n" "Last-Translator: Jannis Leidel \n" "Language-Team: German \n" "Language: de\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Bitte eine durch Komma getrennte Schlagwortliste eingeben." #: taggit/managers.py:432 msgid "Tags" msgstr "Schlagwörter" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Eine durch Komma getrennte Schlagwortliste." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Name" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Kürzel" #: taggit/models.py:82 msgid "tag" msgstr "Schlagwort" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s verschlagwortet mit %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Inhaltstyp" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Objekt-ID" #: taggit/models.py:180 msgid "tagged item" msgstr "Verschlagwortetes Objekt" #: taggit/models.py:181 msgid "tagged items" msgstr "Verschlagwortete Objekte" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/el/000077500000000000000000000000001451634545100173625ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/el/LC_MESSAGES/000077500000000000000000000000001451634545100211475ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/el/LC_MESSAGES/django.mo000066400000000000000000000017371451634545100227560ustar00rootroot00000000000000Þ•\ œÈÉè.(W\N`0¯Pà 1<|B¿Ð%(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.TagstagProject-Id-Version: django-taggit Report-Msgid-Bugs-To: PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: Serafeim Papastefanos Language-Team: Greek Language: el 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Μια χωÏισμένη με κόμματα λίστα από ετικέτεςΌνομαSluigΠαÏακαλοÏμε συπληÏώστε μια λίστα από ετικέτες χωÏισμένη με κόμματαΕτικέτεςΕτικέταdjango-taggit-5.0.1/taggit/locale/el/LC_MESSAGES/django.po000066400000000000000000000042141451634545100227520ustar00rootroot00000000000000#, fuzzy msgid "" msgstr "" "Project-Id-Version: django-taggit\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:30+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Serafeim Papastefanos \n" "Language-Team: Greek \n" "Language: el\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "ΠαÏακαλοÏμε συπληÏώστε μια λίστα από ετικέτες χωÏισμένη με κόμματα" #: taggit/managers.py:432 msgid "Tags" msgstr "Ετικέτες" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Μια χωÏισμένη με κόμματα λίστα από ετικέτες" #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Όνομα" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Sluig" #: taggit/models.py:82 msgid "tag" msgstr "Ετικέτα" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s μαÏκαÏισμένα με %(tag)s" #: taggit/models.py:134 #, fuzzy #| msgid "Content type" msgid "content type" msgstr "Είδος πεÏιεχομένου" #: taggit/models.py:165 taggit/models.py:172 #, fuzzy #| msgid "Object id" msgid "object ID" msgstr "Κωδικός αντικειμένου" #: taggit/models.py:180 #, fuzzy #| msgid "Tagged Item" msgid "tagged item" msgstr "Αντικείμενο με ετικέτα" #: taggit/models.py:181 #, fuzzy #| msgid "Tagged Items" msgid "tagged items" msgstr "Αντικείμενα με ετικέτα" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/en/000077500000000000000000000000001451634545100173645ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/en/LC_MESSAGES/000077500000000000000000000000001451634545100211515ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/en/LC_MESSAGES/django.mo000066400000000000000000000005211451634545100227460ustar00rootroot00000000000000Þ•$,89Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: 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 django-taggit-5.0.1/taggit/locale/en/LC_MESSAGES/django.po000066400000000000000000000033121451634545100227520ustar00rootroot00000000000000# 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: 2021-11-14 11:31+0900\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "" #: taggit/managers.py:432 msgid "Tags" msgstr "" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "" #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "" #: taggit/models.py:82 msgid "tag" msgstr "" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "" #: taggit/models.py:134 msgid "content type" msgstr "" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "" #: taggit/models.py:180 msgid "tagged item" msgstr "" #: taggit/models.py:181 msgid "tagged items" msgstr "" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/eo/000077500000000000000000000000001451634545100173655ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/eo/LC_MESSAGES/000077500000000000000000000000001451634545100211525ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/eo/LC_MESSAGES/django.mo000066400000000000000000000021531451634545100227520ustar00rootroot00000000000000Þ• „ì01Pp€.¿Æ Ë Øâ æ òˆÿˆ&¥Ì Ñ6Ý & 0;CV   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.TaggitTagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: PO-Revision-Date: 2014-03-29 18:57+0100 Last-Translator: Baptiste Darthenay Language-Team: Esperanto Language: eo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.5.4 %(object)s etikedita %(tag)sListo da etikedoj apartitaj per komoj.NomoÄ´etonvortoBonvolu enmeti liston da etikedoj apartitaj per komoj.EtikedojEtikedojEnhavtipoObjekto IDEtikedoEtikedita elementoEtikeditaj elementojdjango-taggit-5.0.1/taggit/locale/eo/LC_MESSAGES/django.po000066400000000000000000000035211451634545100227550ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: django-taggit\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:31+0900\n" "PO-Revision-Date: 2014-03-29 18:57+0100\n" "Last-Translator: Baptiste Darthenay \n" "Language-Team: Esperanto \n" "Language: eo\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" "X-Generator: Poedit 1.5.4\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "Etikedoj" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Bonvolu enmeti liston da etikedoj apartitaj per komoj." #: taggit/managers.py:432 msgid "Tags" msgstr "Etikedoj" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Listo da etikedoj apartitaj per komoj." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Nomo" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Ä´etonvorto" #: taggit/models.py:82 msgid "tag" msgstr "Etikedo" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s etikedita %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Enhavtipo" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Objekto ID" #: taggit/models.py:180 msgid "tagged item" msgstr "Etikedita elemento" #: taggit/models.py:181 msgid "tagged items" msgstr "Etikeditaj elementoj" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/es/000077500000000000000000000000001451634545100173715ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/es/LC_MESSAGES/000077500000000000000000000000001451634545100211565ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/es/LC_MESSAGES/django.mo000066400000000000000000000020421451634545100227530ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï ÛBè"+*Ny€?… ÅÏ áïø    %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: 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=2; plural=(n != 1); %(object)s etiquetados con %(tag)sUna lista de etiquetas separadas por coma.NombreSlugPor favor introduzca una lista de etiquetas separadas por coma.EtiquetasTipo de contenidoId del objetoEtiquetaElemento etiquetadoElementos etiquetadosdjango-taggit-5.0.1/taggit/locale/es/LC_MESSAGES/django.po000066400000000000000000000037451451634545100227710ustar00rootroot00000000000000# 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: 2021-11-14 11:32+0900\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=2; plural=(n != 1);\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Por favor introduzca una lista de etiquetas separadas por coma." #: taggit/managers.py:432 msgid "Tags" msgstr "Etiquetas" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Una lista de etiquetas separadas por coma." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Nombre" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Slug" #: taggit/models.py:82 msgid "tag" msgstr "Etiqueta" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s etiquetados con %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Tipo de contenido" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Id del objeto" #: taggit/models.py:180 msgid "tagged item" msgstr "Elemento etiquetado" #: taggit/models.py:181 msgid "tagged items" msgstr "Elementos etiquetados" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/fa/000077500000000000000000000000001451634545100173505ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/fa/LC_MESSAGES/000077500000000000000000000000001451634545100211355ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/fa/LC_MESSAGES/django.mo000066400000000000000000000022631451634545100227370ustar00rootroot00000000000000Þ• „ì01Pp€.¿ Ä ÑÛ ß ëø2ý40Ee«²W»#5 E%P,v£   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemstagsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: PO-Revision-Date: 2021-07-27 23:15+0430 Last-Translator: Mohammad Hossein Yazdani Language-Team: LANGUAGE Language: fa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit %(object)s برچسب گزاری شده با %(tag)sیک لیست از برچسب های جدا شده توسط کاما Ù†Ø§Ù…Ù†Ø§Ù…Ú©Ù„Ø·ÙØ§ لیستی از برچسب های جدا شده توسط کاما بسازیدبرچسب هانوع محتواشناسه شیبرچسبآیتم برچسب گزاری شدهآیتم های برچسب گزاری شدهبرچسب هاdjango-taggit-5.0.1/taggit/locale/fa/LC_MESSAGES/django.po000066400000000000000000000041621451634545100227420ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Mohammad Hossein Yazdani , 2021. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:32+0900\n" "PO-Revision-Date: 2021-07-27 23:15+0430\n" "Last-Translator: Mohammad Hossein Yazdani \n" "Language-Team: LANGUAGE \n" "Language: fa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Ù„Ø·ÙØ§ لیستی از برچسب های جدا شده توسط کاما بسازید" #: taggit/managers.py:432 msgid "Tags" msgstr "برچسب ها" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "یک لیست از برچسب های جدا شده توسط کاما " #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "نام" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "نامک" #: taggit/models.py:82 msgid "tag" msgstr "برچسب" #: taggit/models.py:83 msgid "tags" msgstr "برچسب ها" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s برچسب گزاری شده با %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "نوع محتوا" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "شناسه Ø´ÛŒ" #: taggit/models.py:180 msgid "tagged item" msgstr "آیتم برچسب گزاری شده" #: taggit/models.py:181 msgid "tagged items" msgstr "آیتم های برچسب گزاری شده" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/fi/000077500000000000000000000000001451634545100173605ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/fi/LC_MESSAGES/000077500000000000000000000000001451634545100211455ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/fi/LC_MESSAGES/django.mo000066400000000000000000000020271451634545100227450ustar00rootroot00000000000000Þ• „ì01Pp€.¿Æ Ë Øâ æ ò?ÿ"?!b„ ‰3“ÇÍÓ ãîó   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.TaggitTagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: PO-Revision-Date: 2018-01-06 17:27-0600 Last-Translator: Nikolay Korotkiy Language-Team: Finnish Language: fi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); %(object)s on merkitty %(tag)s:llaPilkulla erotettu lista tageista.NimiLyhytnimiOle hyvä ja anna pilkulla erotettu lista tageista.TagitTagitSisältötyyppiKohteen IDTagiMerkitty kohdeMerkittyjä kohteitadjango-taggit-5.0.1/taggit/locale/fi/LC_MESSAGES/django.po000066400000000000000000000036101451634545100227470ustar00rootroot00000000000000# This file is distributed under the same license as the django-taggit package. # # Translators: # Nikolay Korotkiy , 2018 # msgid "" msgstr "" "Project-Id-Version: django-taggit\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:32+0900\n" "PO-Revision-Date: 2018-01-06 17:27-0600\n" "Last-Translator: Nikolay Korotkiy \n" "Language-Team: Finnish\n" "Language: fi\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "Tagit" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Ole hyvä ja anna pilkulla erotettu lista tageista." #: taggit/managers.py:432 msgid "Tags" msgstr "Tagit" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Pilkulla erotettu lista tageista." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Nimi" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Lyhytnimi" #: taggit/models.py:82 msgid "tag" msgstr "Tagi" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s on merkitty %(tag)s:lla" #: taggit/models.py:134 msgid "content type" msgstr "Sisältötyyppi" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Kohteen ID" #: taggit/models.py:180 msgid "tagged item" msgstr "Merkitty kohde" #: taggit/models.py:181 msgid "tagged items" msgstr "Merkittyjä kohteita" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/fr/000077500000000000000000000000001451634545100173715ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/fr/LC_MESSAGES/000077500000000000000000000000001451634545100211565ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/fr/LC_MESSAGES/django.mo000066400000000000000000000021211451634545100227510ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï ÛVè#?6cšžA£ åñ %:   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: Report-Msgid-Bugs-To: PO-Revision-Date: 2019-04-09 10:57+0200 Last-Translator: Guillaume Bernard Language-Team: Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Generator: Poedit 2.2.1 %(object)s étiquetés avec %(tag)sUne liste d’étiquettes séparées par des virgules.nomslugRenseignez une liste d’étiquettes séparées par des virgules.ÉtiquettesType de contenuIdentifiant de l’objetÉtiquetteÉlément étiquetéÉléments étiquetésdjango-taggit-5.0.1/taggit/locale/fr/LC_MESSAGES/django.po000066400000000000000000000040311451634545100227560ustar00rootroot00000000000000# French translation for django-taggit # Copyright (C) Jazz Band # This file is distributed under the same license as the taggit package. # Guillaume Bernard , 2019 # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:33+0900\n" "PO-Revision-Date: 2019-04-09 10:57+0200\n" "Last-Translator: Guillaume Bernard \n" "Language-Team: \n" "Language: fr\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" "X-Generator: Poedit 2.2.1\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Renseignez une liste d’étiquettes séparées par des virgules." #: taggit/managers.py:432 msgid "Tags" msgstr "Étiquettes" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Une liste d’étiquettes séparées par des virgules." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "nom" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "slug" #: taggit/models.py:82 msgid "tag" msgstr "Étiquette" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s étiquetés avec %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Type de contenu" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Identifiant de l’objet" #: taggit/models.py:180 msgid "tagged item" msgstr "Élément étiqueté" #: taggit/models.py:181 msgid "tagged items" msgstr "Éléments étiquetés" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/he/000077500000000000000000000000001451634545100173565ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/he/LC_MESSAGES/000077500000000000000000000000001451634545100211435ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/he/LC_MESSAGES/django.mo000066400000000000000000000014741451634545100227500ustar00rootroot00000000000000Þ•T Œ¸¹Øø.7<C@"„8§àFå ,7%(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenamePlease provide a comma-separated list of tags.TagstagProject-Id-Version: Django Taggit Report-Msgid-Bugs-To: PO-Revision-Date: 2010-06-26 12:54-0600 Last-Translator: Alex Language-Team: LANGUAGE 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-5.0.1/taggit/locale/he/LC_MESSAGES/django.po000066400000000000000000000036431451634545100227530ustar00rootroot00000000000000# 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: 2021-11-14 11:34+0900\n" "PO-Revision-Date: 2010-06-26 12:54-0600\n" "Last-Translator: Alex \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=2; plural=(n != 1);\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "× × ×œ×¡×¤×§ רשימה של ×ª×’×™× ×ž×•×¤×¨×“×ª ×¢× ×¤×¡×™×§×™×." #: taggit/managers.py:432 msgid "Tags" msgstr "תגיות" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "רשימה של ×ª×’×™× ×ž×•×¤×¨×“×ª ×¢× ×¤×¡×™×§×™×." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "ש×" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "" #: taggit/models.py:82 msgid "tag" msgstr "תג" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s מתויג ×¢× %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "" #: taggit/models.py:180 msgid "tagged item" msgstr "" #: taggit/models.py:181 msgid "tagged items" msgstr "" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/it/000077500000000000000000000000001451634545100173765ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/it/LC_MESSAGES/000077500000000000000000000000001451634545100211635ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/it/LC_MESSAGES/django.mo000066400000000000000000000016611451634545100227660ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï Ûè%AF-Ky} ‚‘¡   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: 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 %(object)s con tag %(tag)sUna lista di tag separati da virgola.NomeSlugFornire una lista di tag separati da virgola.TagTipoId OggettoTagOggetto con tagOggetti con tagdjango-taggit-5.0.1/taggit/locale/it/LC_MESSAGES/django.po000066400000000000000000000035601451634545100227710ustar00rootroot00000000000000# 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: 2021-11-14 11:35+0900\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Fornire una lista di tag separati da virgola." #: taggit/managers.py:432 msgid "Tags" msgstr "Tag" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Una lista di tag separati da virgola." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Nome" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Slug" #: taggit/models.py:82 msgid "tag" msgstr "Tag" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s con tag %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Tipo" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Id Oggetto" #: taggit/models.py:180 msgid "tagged item" msgstr "Oggetto con tag" #: taggit/models.py:181 msgid "tagged items" msgstr "Oggetti con tag" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/ja/000077500000000000000000000000001451634545100173545ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/ja/LC_MESSAGES/000077500000000000000000000000001451634545100211415ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/ja/LC_MESSAGES/django.mo000066400000000000000000000021661451634545100227450ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï ÛSè<0[Œ “H  éö%!,'N   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: PO-Revision-Date: 2014-04-23 08:05+0900 Last-Translator: Tatsuo Ikeda Language-Team: Language: ja MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.6.4 %(object)s tagged with %(tag)s複数タグã¯ã‚«ãƒ³ãƒžåŒºåˆ‡ã‚Šã®ãƒªã‚¹ãƒˆã€‚å称スラッグ複数タグã¯ã‚«ãƒ³ãƒžåŒºåˆ‡ã‚Šã®ãƒªã‚¹ãƒˆã‚’入れã¦ãã ã•ã„。タグ一覧コンテンツタイプオブジェクト IDã‚¿ã‚°ã‚¿ã‚°ä»˜ã‘æ¸ˆã¿ã®ã‚¢ã‚¤ãƒ†ãƒ ã‚¿ã‚°ä»˜ã‘済ã¿ã®ã‚¢ã‚¤ãƒ†ãƒ ä¸€è¦§django-taggit-5.0.1/taggit/locale/ja/LC_MESSAGES/django.po000066400000000000000000000035641451634545100227530ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: django-taggit\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:39+0900\n" "PO-Revision-Date: 2014-04-23 08:05+0900\n" "Last-Translator: Tatsuo Ikeda \n" "Language-Team: \n" "Language: ja\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" "X-Generator: Poedit 1.6.4\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "複数タグã¯ã‚«ãƒ³ãƒžåŒºåˆ‡ã‚Šã®ãƒªã‚¹ãƒˆã‚’入れã¦ãã ã•ã„。" #: taggit/managers.py:432 msgid "Tags" msgstr "タグ一覧" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "複数タグã¯ã‚«ãƒ³ãƒžåŒºåˆ‡ã‚Šã®ãƒªã‚¹ãƒˆã€‚" #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "åç§°" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "スラッグ" #: taggit/models.py:82 msgid "tag" msgstr "ã‚¿ã‚°" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s tagged with %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "コンテンツタイプ" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "オブジェクト ID" #: taggit/models.py:180 msgid "tagged item" msgstr "ã‚¿ã‚°ä»˜ã‘æ¸ˆã¿ã®ã‚¢ã‚¤ãƒ†ãƒ " #: taggit/models.py:181 msgid "tagged items" msgstr "ã‚¿ã‚°ä»˜ã‘æ¸ˆã¿ã®ã‚¢ã‚¤ãƒ†ãƒ ä¸€è¦§" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/nb/000077500000000000000000000000001451634545100173615ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/nb/LC_MESSAGES/000077500000000000000000000000001451634545100211465ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/nb/LC_MESSAGES/django.mo000066400000000000000000000017411451634545100227500ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï ÛBè+Ifk,p ¤ ±»ÀÏ   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: 0.9.3 Report-Msgid-Bugs-To: PO-Revision-Date: 2012-12-08 14:42+0100 Last-Translator: Bjørn Pettersen Language-Team: Norwegian Language: Norwegian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Generator: Poedit 1.5.4 %(object)s tagget med %(tag)sEn kommaseparert tagg-liste.NavnSlugVennligst oppgi en kommaseparert tagg-liste.TaggerInnholdstypeObjekt-idTaggTagget ElementTaggede Elementerdjango-taggit-5.0.1/taggit/locale/nb/LC_MESSAGES/django.po000066400000000000000000000036331451634545100227550ustar00rootroot00000000000000# 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: 2021-11-14 11:41+0900\n" "PO-Revision-Date: 2012-12-08 14:42+0100\n" "Last-Translator: Bjørn Pettersen \n" "Language-Team: Norwegian \n" "Language: 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" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Vennligst oppgi en kommaseparert tagg-liste." #: taggit/managers.py:432 msgid "Tags" msgstr "Tagger" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "En kommaseparert tagg-liste." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Navn" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Slug" #: taggit/models.py:82 msgid "tag" msgstr "Tagg" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s tagget med %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Innholdstype" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Objekt-id" #: taggit/models.py:180 msgid "tagged item" msgstr "Tagget Element" #: taggit/models.py:181 msgid "tagged items" msgstr "Taggede Elementer" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/nl/000077500000000000000000000000001451634545100173735ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/nl/LC_MESSAGES/000077500000000000000000000000001451634545100211605ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/nl/LC_MESSAGES/django.mo000066400000000000000000000016451451634545100227650ustar00rootroot00000000000000Þ• tÌ0P`.pŸ ¤ ± » ÇÔå)/4.9h m yƒ“  %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagged itemtagged itemsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: PO-Revision-Date: 2010-09-07 23:04+0100 Last-Translator: Jeffrey Gelens Language-Team: Dutch Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit %(object)s getagged met %(tag)sEen door komma gescheiden lijst van tags.NaamSlugGeef een door komma gescheiden lijst van tags.TagsInhoudstypeObject-idObject getaggedObjecten getaggeddjango-taggit-5.0.1/taggit/locale/nl/LC_MESSAGES/django.po000066400000000000000000000033131451634545100227620ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: django-taggit\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-14 11:41+0900\n" "PO-Revision-Date: 2010-09-07 23:04+0100\n" "Last-Translator: Jeffrey Gelens \n" "Language-Team: Dutch\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Geef een door komma gescheiden lijst van tags." #: taggit/managers.py:432 msgid "Tags" msgstr "Tags" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Een door komma gescheiden lijst van tags." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Naam" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Slug" #: taggit/models.py:82 #, fuzzy #| msgid "Tag" msgid "tag" msgstr "Tag" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s getagged met %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Inhoudstype" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Object-id" #: taggit/models.py:180 msgid "tagged item" msgstr "Object getagged" #: taggit/models.py:181 msgid "tagged items" msgstr "Objecten getagged" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/pt_BR/000077500000000000000000000000001451634545100177705ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/pt_BR/LC_MESSAGES/000077500000000000000000000000001451634545100215555ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/pt_BR/LC_MESSAGES/django.mo000066400000000000000000000020251451634545100233530ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï ÛDè-/M}‚>‡ ÆÑ ãð ù   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: django-taggit Report-Msgid-Bugs-To: 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.NomeSlugFavor fornecer uma lista de marcadores separados por vírgula.MarcadoresTipo de conteúdoId do objetoMarcadorItem marcadoItens marcadosdjango-taggit-5.0.1/taggit/locale/pt_BR/LC_MESSAGES/django.po000066400000000000000000000035511451634545100233630ustar00rootroot00000000000000# 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: 2021-11-14 11:42+0900\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Favor fornecer uma lista de marcadores separados por vírgula." #: taggit/managers.py:432 msgid "Tags" msgstr "Marcadores" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Uma lista de marcadores separados por vírgula." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Nome" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Slug" #: taggit/models.py:82 msgid "tag" msgstr "Marcador" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s marcados com %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "Tipo de conteúdo" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Id do objeto" #: taggit/models.py:180 msgid "tagged item" msgstr "Item marcado" #: taggit/models.py:181 msgid "tagged items" msgstr "Itens marcados" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/ru/000077500000000000000000000000001451634545100174105ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/ru/LC_MESSAGES/000077500000000000000000000000001451634545100211755ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/ru/LC_MESSAGES/django.mo000066400000000000000000000035161451634545100230010ustar00rootroot00000000000000Þ•¤,ˆ‰¨ÈØ&è5JE.¿Æ Ë Øâ æ òÿƒ7ˆ2ÀóY ^g¬Æ2s¦¯¸)Ö&E   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugAll list items must be of string type.Expected a list of items but got type "{input_type}".Invalid json list. A tag list submitted in string form must be valid json.Please provide a comma-separated list of tags.TaggitTagscontent typeobject IDtagtagged itemtagged itemstagsProject-Id-Version: Django Taggit Report-Msgid-Bugs-To: PO-Revision-Date: 2021-09-26 00:51+0300 Last-Translator: Serghei Iakovlev Language-Team: Language: ru 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; Ñлемент «%(object)s» Ñ Ñ‚ÐµÐ³Ð¾Ð¼ «%(tag)s»СпиÑок тегов через запÑтую.названиеÑлагВÑе Ñлементы ÑпиÑка должны быть Ñтрокового типа.ОжидалÑÑ ÑпиÑок Ñлементов, но получен тип «{input_type}».Ðеверный ÑпиÑок json. СпиÑок тегов, предÑтавленный в Ñтроковой форме, должен быть корректным json.Укажите теги через запÑтую.ТегиТегитип Ñодержимогоидентификатор объектатегÑлемент Ñ Ð¼ÐµÑ‚ÐºÐ¾Ð¹Ñлементы Ñ Ñ‚ÐµÐ³Ð¾Ð¼Ñ‚ÐµÐ³Ð¸django-taggit-5.0.1/taggit/locale/ru/LC_MESSAGES/django.po000066400000000000000000000047601451634545100230060ustar00rootroot00000000000000# 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: 2021-11-14 11:43+0900\n" "PO-Revision-Date: 2021-09-26 00:51+0300\n" "Last-Translator: Serghei Iakovlev \n" "Language-Team: \n" "Language: ru\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "Теги" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Укажите теги через запÑтую." #: taggit/managers.py:432 msgid "Tags" msgstr "Теги" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "СпиÑок тегов через запÑтую." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "название" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Ñлаг" #: taggit/models.py:82 msgid "tag" msgstr "тег" #: taggit/models.py:83 msgid "tags" msgstr "теги" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "Ñлемент «%(object)s» Ñ Ñ‚ÐµÐ³Ð¾Ð¼ «%(tag)s»" #: taggit/models.py:134 msgid "content type" msgstr "тип Ñодержимого" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "идентификатор объекта" #: taggit/models.py:180 msgid "tagged item" msgstr "Ñлемент Ñ Ð¼ÐµÑ‚ÐºÐ¾Ð¹" #: taggit/models.py:181 msgid "tagged items" msgstr "Ñлементы Ñ Ñ‚ÐµÐ³Ð¾Ð¼" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "ОжидалÑÑ ÑпиÑок Ñлементов, но получен тип «{input_type}»." #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" "Ðеверный ÑпиÑок json. СпиÑок тегов, предÑтавленный в Ñтроковой форме, должен " "быть корректным json." #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "Ð’Ñе Ñлементы ÑпиÑка должны быть Ñтрокового типа." django-taggit-5.0.1/taggit/locale/tr/000077500000000000000000000000001451634545100174075ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/tr/LC_MESSAGES/000077500000000000000000000000001451634545100211745ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/tr/LC_MESSAGES/django.mo000066400000000000000000000020331451634545100227710ustar00rootroot00000000000000Þ• |Ü !@`p.€¯ ´ ÁË Ï ÛAè"**Mx }7‡ ¿ÉÙ èò   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.Tagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: 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=2; plural=(n > 1); %(object)s %(tag)s ile etiketlendiEtiketlerin virgülle ayrılmış listesi.AdıKısaltmaEtiketleri bir virgülle ayrılmış listesini veriniz.Etiketlerİçerik türüNesne kimliÄŸiEtiketlerTakip edilen ÖğeTakip edilen Öğelerdjango-taggit-5.0.1/taggit/locale/tr/LC_MESSAGES/django.po000066400000000000000000000037361451634545100230070ustar00rootroot00000000000000# 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: 2021-11-14 11:44+0900\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=2; plural=(n > 1);\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Etiketleri bir virgülle ayrılmış listesini veriniz." #: taggit/managers.py:432 msgid "Tags" msgstr "Etiketler" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "Etiketlerin virgülle ayrılmış listesi." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Adı" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Kısaltma" #: taggit/models.py:82 msgid "tag" msgstr "Etiketler" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s %(tag)s ile etiketlendi" #: taggit/models.py:134 msgid "content type" msgstr "İçerik türü" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "Nesne kimliÄŸi" #: taggit/models.py:180 msgid "tagged item" msgstr "Takip edilen Öğe" #: taggit/models.py:181 msgid "tagged items" msgstr "Takip edilen Öğeler" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/uk/000077500000000000000000000000001451634545100174015ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/uk/LC_MESSAGES/000077500000000000000000000000001451634545100211665ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/uk/LC_MESSAGES/django.mo000066400000000000000000000022731451634545100227710ustar00rootroot00000000000000Þ• „ì01Pp€.¿Æ Ë Øâ æ ò‹ÿ9‹,Å òý. 5 @K_ p{ š   %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.TaggitTagscontent typeobject IDtagtagged itemtagged itemsProject-Id-Version: Django Taggit Report-Msgid-Bugs-To: PO-Revision-Date: 2010-06-11 11:30+0700 Last-Translator: Igor 'idle sign' Starikov Language-Team: Language: uk 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; елемент «%(object)s» з міткою «%(tag)s»СпиÑок міток через кому.ÐазваСлагВкажіть мітки через кому.МіткаМіткаТип вміÑтуID об'єктаМіткаЕлемент з міткоюЕлементи з міткоюdjango-taggit-5.0.1/taggit/locale/uk/LC_MESSAGES/django.po000066400000000000000000000041401451634545100227670ustar00rootroot00000000000000# 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: 2021-11-14 11:44+0900\n" "PO-Revision-Date: 2010-06-11 11:30+0700\n" "Last-Translator: Igor 'idle sign' Starikov \n" "Language-Team: \n" "Language: uk\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" #: taggit/apps.py:7 msgid "Taggit" msgstr "Мітка" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "Вкажіть мітки через кому." #: taggit/managers.py:432 msgid "Tags" msgstr "Мітка" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "СпиÑок міток через кому." #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "Ðазва" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "Слаг" #: taggit/models.py:82 msgid "tag" msgstr "Мітка" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "елемент «%(object)s» з міткою «%(tag)s»" #: taggit/models.py:134 msgid "content type" msgstr "Тип вміÑту" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "ID об'єкта" #: taggit/models.py:180 msgid "tagged item" msgstr "Елемент з міткою" #: taggit/models.py:181 msgid "tagged items" msgstr "Елементи з міткою" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/locale/zh_Hans/000077500000000000000000000000001451634545100203545ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/zh_Hans/LC_MESSAGES/000077500000000000000000000000001451634545100221415ustar00rootroot00000000000000django-taggit-5.0.1/taggit/locale/zh_Hans/LC_MESSAGES/django.mo000066400000000000000000000017401451634545100237420ustar00rootroot00000000000000Þ• |Ü !@`p.€¯¶ » È Ò Þ;ë"'Ji p'} ¥¯ ¶Ã Ì Ö  %(object)s tagged with %(tag)sA comma-separated list of tags.A tag namenameA tag slugslugPlease provide a comma-separated list of tags.TaggitTagscontent typeobject IDtagged itemtagged itemsProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: 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=1; plural=0; %(object)s 使用了标签 %(tag)s逗å·åˆ†éš”的标签列表。å称唯一标识请æä¾›é€—å·åˆ†éš”的标签列表。标签项标签内容类型对象ID标签项标签项django-taggit-5.0.1/taggit/locale/zh_Hans/LC_MESSAGES/django.po000066400000000000000000000036761451634545100237570ustar00rootroot00000000000000# 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: 2021-11-14 11:45+0900\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=1; plural=0;\n" #: taggit/apps.py:7 msgid "Taggit" msgstr "标签项" #: taggit/forms.py:31 msgid "Please provide a comma-separated list of tags." msgstr "请æä¾›é€—å·åˆ†éš”的标签列表。" #: taggit/managers.py:432 msgid "Tags" msgstr "标签" #: taggit/managers.py:433 msgid "A comma-separated list of tags." msgstr "逗å·åˆ†éš”的标签列表。" #: taggit/models.py:19 msgctxt "A tag name" msgid "name" msgstr "åç§°" #: taggit/models.py:22 msgctxt "A tag slug" msgid "slug" msgstr "唯一标识" #: taggit/models.py:82 #, fuzzy #| msgid "Tag" msgid "tag" msgstr "标签" #: taggit/models.py:83 msgid "tags" msgstr "" #: taggit/models.py:89 #, python-format msgid "%(object)s tagged with %(tag)s" msgstr "%(object)s 使用了标签 %(tag)s" #: taggit/models.py:134 msgid "content type" msgstr "内容类型" #: taggit/models.py:165 taggit/models.py:172 msgid "object ID" msgstr "对象ID" #: taggit/models.py:180 msgid "tagged item" msgstr "标签项" #: taggit/models.py:181 msgid "tagged items" msgstr "标签项" #: taggit/serializers.py:40 #, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" #: taggit/serializers.py:43 msgid "" "Invalid json list. A tag list submitted in string form must be valid json." msgstr "" #: taggit/serializers.py:46 msgid "All list items must be of string type." msgstr "" django-taggit-5.0.1/taggit/managers.py000066400000000000000000000627271451634545100177100ustar00rootroot00000000000000import uuid from operator import attrgetter from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import connections, models, router from django.db.models import signals from django.db.models.fields.related import ( ManyToManyRel, OneToOneRel, RelatedField, lazy_related_operation, ) from django.db.models.query_utils import PathInfo from django.utils.functional import cached_property from django.utils.text import capfirst from django.utils.translation import gettext_lazy as _ from taggit.forms import TagField from taggit.models import ( CommonGenericTaggedItemBase, GenericUUIDTaggedItemBase, TaggedItem, ) from taggit.utils import require_instance_manager class ExtraJoinRestriction: """ An extra restriction used for contenttype restriction in joins. """ contains_aggregate = False contains_over_clause = False def __init__(self, alias, col, content_types): self.alias = alias self.col = col self.content_types = content_types def as_sql(self, compiler, connection): qn = compiler.quote_name_unless_alias if len(self.content_types) == 1: extra_where = f"{qn(self.alias)}.{qn(self.col)} = %s" else: extra_where = "{}.{} IN ({})".format( 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 type(self)(self.alias, self.col, self.content_types[:]) class _TaggableManager(models.Manager): # TODO investigate whether we can use a RelatedManager instead of all this stuff # to take advantage of all the Django goodness def __init__(self, through, model, instance, prefetch_cache_name, ordering=None): super().__init__() self.through = through self.model = model self.instance = instance self.prefetch_cache_name = prefetch_cache_name if ordering: self.ordering = ordering else: self.ordering = [] def is_cached(self, instance): return self.prefetch_cache_name in instance._prefetched_objects_cache def get_queryset(self, extra_filters=None): try: return self.instance._prefetched_objects_cache[self.prefetch_cache_name] except (AttributeError, KeyError): kwargs = extra_filters if extra_filters else {} return self.through.tags_for(self.model, self.instance, **kwargs).order_by( *self.ordering ) def get_prefetch_queryset(self, instances, queryset=None): if queryset is None: return self.get_prefetch_querysets(instances) else: return self.get_prefetch_querysets(instances, [queryset]) def get_prefetch_querysets(self, instances, querysets=None): if querysets is not None: # this queryset is meant to be used for filtering down the prefetch # this work has not been done yet. # # Some hint from Django: asserting that len(querysets) == 1 if it's not None # and then using that to filter down the qs raise ValueError("Custom querysets can't be used for this lookup.") instance = instances[0] db = self._db or router.db_for_read(type(instance), instance=instance) fieldname = ( "object_id" if issubclass(self.through, CommonGenericTaggedItemBase) else "content_object" ) fk = self.through._meta.get_field(fieldname) query = { "%s__%s__in" % (self.through.tag_relname(), fk.name): { obj._get_pk_val() for obj in instances } } join_table = self.through._meta.db_table source_col = fk.column connection = connections[db] qn = connection.ops.quote_name qs = ( self.get_queryset(query) .using(db) .extra( select={ "_prefetch_related_val": "{}.{}".format( qn(join_table), qn(source_col) ) } ) ) if issubclass(self.through, GenericUUIDTaggedItemBase): def uuid_rel_obj_attr(v): value = attrgetter("_prefetch_related_val")(v) if value is not None and not isinstance(value, uuid.UUID): input_form = "int" if isinstance(value, int) else "hex" value = uuid.UUID(**{input_form: value}) return value rel_obj_attr = uuid_rel_obj_attr else: rel_obj_attr = attrgetter("_prefetch_related_val") return ( qs, rel_obj_attr, lambda obj: obj._get_pk_val(), False, self.prefetch_cache_name, False, ) def _lookup_kwargs(self): return self.through.lookup_kwargs(self.instance) def _remove_prefetched_objects(self): prefetch_cache = getattr(self.instance, "_prefetched_objects_cache", None) if prefetch_cache: prefetch_cache.pop(self.prefetch_cache_name, None) @require_instance_manager def add(self, *tags, through_defaults=None, tag_kwargs=None, **kwargs): self._remove_prefetched_objects() if tag_kwargs is None: tag_kwargs = {} db = router.db_for_write(self.through, instance=self.instance) tag_objs = self._to_tag_model_instances(tags, tag_kwargs) new_ids = {t.pk for t in tag_objs} # NOTE: can we hardcode 'tag_id' here or should the column name be got # dynamically from somewhere? vals = ( self.through._default_manager.using(db) .values_list("tag_id", flat=True) .filter(**self._lookup_kwargs()) ) new_ids = new_ids - set(vals) signals.m2m_changed.send( sender=self.through, action="pre_add", instance=self.instance, reverse=False, model=self.through.tag_model(), pk_set=new_ids, using=db, ) for tag in tag_objs: self.through._default_manager.using(db).get_or_create( tag=tag, **self._lookup_kwargs(), defaults=through_defaults ) signals.m2m_changed.send( sender=self.through, action="post_add", instance=self.instance, reverse=False, model=self.through.tag_model(), pk_set=new_ids, using=db, ) def _to_tag_model_instances(self, tags, tag_kwargs): """ Takes an iterable containing either strings, tag objects, or a mixture of both and returns set of tag objects. """ db = router.db_for_write(self.through, instance=self.instance) str_tags = set() tag_objs = set() for t in tags: if isinstance(t, self.through.tag_model()): tag_objs.add(t) elif isinstance(t, str): str_tags.add(t) else: raise ValueError( "Cannot add {} ({}). Expected {} or str.".format( t, type(t), type(self.through.tag_model()) ) ) case_insensitive = getattr(settings, "TAGGIT_CASE_INSENSITIVE", False) manager = self.through.tag_model()._default_manager.using(db) if case_insensitive: # Some databases can do case-insensitive comparison with IN, which # would be faster, but we can't rely on it or easily detect it. existing = [] tags_to_create = [] for name in str_tags: try: tag = manager.get(name__iexact=name, **tag_kwargs) existing.append(tag) except self.through.tag_model().DoesNotExist: tags_to_create.append(name) else: # If str_tags has 0 elements Django actually optimizes that to not # do a query. Malcolm is very smart. existing = manager.filter(name__in=str_tags, **tag_kwargs) tags_to_create = str_tags - {t.name for t in existing} tag_objs.update(existing) for new_tag in tags_to_create: if case_insensitive: lookup = {"name__iexact": new_tag, **tag_kwargs} else: lookup = {"name": new_tag, **tag_kwargs} tag, create = manager.get_or_create(**lookup, defaults={"name": new_tag}) tag_objs.add(tag) return tag_objs @require_instance_manager def names(self): return self.get_queryset().values_list("name", flat=True) @require_instance_manager def slugs(self): return self.get_queryset().values_list("slug", flat=True) @require_instance_manager def set(self, tags, *, through_defaults=None, **kwargs): """ Set the object's tags to the given n tags. If the clear kwarg is True then all existing tags are removed (using `.clear()`) and the new tags added. Otherwise, only those tags that are not present in the args are removed and any new tags added. Any kwarg apart from 'clear' will be passed when adding tags. """ db = router.db_for_write(self.through, instance=self.instance) clear = kwargs.pop("clear", False) tag_kwargs = kwargs.pop("tag_kwargs", {}) if clear: self.clear() self.add(*tags, **kwargs) else: # make sure we're working with a collection of a uniform type objs = self._to_tag_model_instances(tags, tag_kwargs) # get the existing tag strings old_tag_strs = set( self.through._default_manager.using(db) .filter(**self._lookup_kwargs()) .values_list("tag__name", flat=True) ) new_objs = [] for obj in objs: if obj.name in old_tag_strs: old_tag_strs.remove(obj.name) else: new_objs.append(obj) self.remove(*old_tag_strs) self.add(*new_objs, through_defaults=through_defaults, **kwargs) @require_instance_manager def remove(self, *tags): if not tags: return self._remove_prefetched_objects() db = router.db_for_write(self.through, instance=self.instance) qs = ( self.through._default_manager.using(db) .filter(**self._lookup_kwargs()) .filter(tag__name__in=tags) ) old_ids = set(qs.values_list("tag_id", flat=True)) signals.m2m_changed.send( sender=self.through, action="pre_remove", instance=self.instance, reverse=False, model=self.through.tag_model(), pk_set=old_ids, using=db, ) qs.delete() signals.m2m_changed.send( sender=self.through, action="post_remove", instance=self.instance, reverse=False, model=self.through.tag_model(), pk_set=old_ids, using=db, ) @require_instance_manager def clear(self): self._remove_prefetched_objects() db = router.db_for_write(self.through, instance=self.instance) signals.m2m_changed.send( sender=self.through, action="pre_clear", instance=self.instance, reverse=False, model=self.through.tag_model(), pk_set=None, using=db, ) self.through._default_manager.using(db).filter(**self._lookup_kwargs()).delete() signals.m2m_changed.send( sender=self.through, action="post_clear", instance=self.instance, reverse=False, model=self.through.tag_model(), pk_set=None, using=db, ) def most_common(self, min_count=None, extra_filters=None): queryset = ( self.get_queryset(extra_filters) .annotate(num_times=models.Count(self.through.tag_relname())) .order_by("-num_times") ) if min_count: queryset = queryset.filter(num_times__gte=min_count) return queryset @require_instance_manager def similar_objects(self): lookup_kwargs = self._lookup_kwargs() lookup_keys = sorted(lookup_kwargs) qs = self.through.objects.values(*lookup_kwargs.keys()) 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(lookup_keys[0]) remote_field = f.remote_field rel_model = remote_field.model objs = rel_model._default_manager.filter( **{ "%s__in" % remote_field.field_name: [r["content_object"] for r in qs] } ) actual_remote_field_name = f.target_field.get_attname() for obj in objs: items[(getattr(obj, actual_remote_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 class TaggableManager(RelatedField): # Field flags many_to_many = True many_to_one = False one_to_many = False one_to_one = False _related_name_counter = 0 def __init__( self, verbose_name=_("Tags"), help_text=_("A comma-separated list of tags."), through=None, blank=False, related_name=None, to=None, ordering=None, manager=_TaggableManager, ): self.through = through or TaggedItem rel = ManyToManyRel(self, to, related_name=related_name, through=self.through) super().__init__( verbose_name=verbose_name, help_text=help_text, blank=blank, null=True, serialize=False, rel=rel, ) self.ordering = ordering self.swappable = False self.manager = manager 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__ ) return self.manager( through=self.through, model=model, instance=instance, prefetch_cache_name=self.name, ordering=self.ordering, ) def deconstruct(self): """ Deconstruct the object, used with migrations. """ name, path, args, kwargs = super().deconstruct() # Remove forced kwargs. for kwarg in ("serialize", "null"): del kwargs[kwarg] # Add arguments related to relations. # Ref: https://github.com/jazzband/django-taggit/issues/206#issuecomment-37578676 rel = self.remote_field if isinstance(rel.through, str): kwargs["through"] = rel.through elif not rel.through._meta.auto_created: kwargs["through"] = "{}.{}".format( rel.through._meta.app_label, rel.through._meta.object_name ) related_model = rel.model if isinstance(related_model, str): kwargs["to"] = related_model else: kwargs["to"] = "{}.{}".format( related_model._meta.app_label, related_model._meta.object_name ) return name, path, args, kwargs def contribute_to_class(self, cls, name): self.set_attributes_from_name(name) self.model = cls self.opts = cls._meta cls._meta.add_field(self) setattr(cls, name, self) if not cls._meta.abstract: if isinstance(self.remote_field.model, str): def resolve_related_class(cls, model, field): field.remote_field.model = model lazy_related_operation( resolve_related_class, cls, self.remote_field.model, field=self ) if isinstance(self.through, str): def resolve_related_class(cls, model, field): self.through = model self.remote_field.through = model self.post_through_setup(cls) lazy_related_operation( resolve_related_class, cls, self.through, field=self ) else: self.post_through_setup(cls) def get_internal_type(self): return "ManyToManyField" def post_through_setup(self, cls): self.use_gfk = self.through is None or issubclass( self.through, CommonGenericTaggedItemBase ) if not self.remote_field.model: self.remote_field.model = self.through._meta.get_field( "tag" ).remote_field.model if self.use_gfk: tagged_items = GenericRelation(self.through) tagged_items.contribute_to_class(cls, "tagged_items") for rel in cls._meta.local_many_to_many: if rel == self or not isinstance(rel, TaggableManager): continue if rel.through == self.through: raise ValueError( "You can't have two TaggableManagers with the" " same through model." ) def save_form_data(self, instance, value): getattr(instance, self.name).set(value) def formfield(self, form_class=TagField, **kwargs): defaults = { "label": capfirst(self.verbose_name), "help_text": self.help_text, "required": not self.blank, } defaults.update(kwargs) return form_class(**defaults) def value_from_object(self, obj): if obj.pk is None: return [] qs = self.through.objects.select_related("tag").filter( **self.through.lookup_kwargs(obj) ) return [ti.tag for ti in qs] def m2m_reverse_name(self): return self.through._meta.get_field("tag").column def m2m_reverse_field_name(self): return self.through._meta.get_field("tag").name def m2m_target_field_name(self): return self.model._meta.pk.name def m2m_reverse_target_field_name(self): return self.remote_field.model._meta.pk.name def m2m_column_name(self): if self.use_gfk: return self.through._meta.private_fields[0].fk_field return self.through._meta.get_field("content_object").column def m2m_db_table(self): return self.through._meta.db_table def bulk_related_objects(self, new_objs, using): return [] def _get_mm_case_path_info(self, direct=False, filtered_relation=None): pathinfos = [] linkfield1 = self.through._meta.get_field("content_object") linkfield2 = self.through._meta.get_field(self.m2m_reverse_field_name()) if not filtered_relation: # Django >= 4.1 provides cached path_infos and reverse_path_infos properties # to use in preference to get_path_info / get_reverse_path_info when not # passing a filtered_relation if direct: join1infos = linkfield1.reverse_path_infos join2infos = linkfield2.path_infos else: join1infos = linkfield2.reverse_path_infos join2infos = linkfield1.path_infos else: if direct: join1infos = linkfield1.get_reverse_path_info( filtered_relation=filtered_relation ) join2infos = linkfield2.get_path_info( filtered_relation=filtered_relation ) else: join1infos = linkfield2.get_reverse_path_info( filtered_relation=filtered_relation ) join2infos = linkfield1.get_path_info( filtered_relation=filtered_relation ) pathinfos.extend(join1infos) pathinfos.extend(join2infos) return pathinfos def _get_gfk_case_path_info(self, direct=False, filtered_relation=None): pathinfos = [] from_field = self.model._meta.pk opts = self.through._meta linkfield = self.through._meta.get_field(self.m2m_reverse_field_name()) if direct: join1infos = [ PathInfo( self.model._meta, opts, [from_field], self.remote_field, True, False, filtered_relation, ) ] if not filtered_relation: join2infos = linkfield.path_infos else: join2infos = linkfield.get_path_info( filtered_relation=filtered_relation ) else: if not filtered_relation: join1infos = linkfield.reverse_path_infos else: join1infos = linkfield.get_reverse_path_info( filtered_relation=filtered_relation ) join2infos = [ PathInfo( opts, self.model._meta, [from_field], self, True, False, filtered_relation, ) ] pathinfos.extend(join1infos) pathinfos.extend(join2infos) return pathinfos def get_path_info(self, filtered_relation=None): if self.use_gfk: return self._get_gfk_case_path_info( direct=True, filtered_relation=filtered_relation ) else: return self._get_mm_case_path_info( direct=True, filtered_relation=filtered_relation ) @cached_property def path_infos(self): return self.get_path_info() def get_reverse_path_info(self, filtered_relation=None): if self.use_gfk: return self._get_gfk_case_path_info( direct=False, filtered_relation=filtered_relation ) else: return self._get_mm_case_path_info( direct=False, filtered_relation=filtered_relation ) @cached_property def reverse_path_infos(self): return self.get_reverse_path_info() def get_joining_columns(self, reverse_join=False): # RemovedInDjango60Warning # https://github.com/django/django/commit/8b1ff0da4b162e87edebd94e61f2cd153e9e159d if reverse_join: return ((self.model._meta.pk.column, "object_id"),) else: return (("object_id", self.model._meta.pk.column),) def get_joining_fields(self, reverse_join=False): if reverse_join: return ( ( self.model._meta.pk, self.remote_field.through._meta.get_field("object_id"), ), ) else: return ( ( self.remote_field.through._meta.get_field("object_id"), self.model._meta.pk, ), ) def get_extra_restriction(self, alias, related_alias): extra_col = self.through._meta.get_field("content_type").column content_type_ids = [ ContentType.objects.get_for_model(subclass).pk for subclass in _get_subclasses(self.model) ] return ExtraJoinRestriction(related_alias, extra_col, content_type_ids) def get_reverse_joining_columns(self): # RemovedInDjango60Warning # https://github.com/django/django/commit/8b1ff0da4b162e87edebd94e61f2cd153e9e159d return self.get_joining_columns(reverse_join=True) def get_reverse_joining_fields(self): return self.get_joining_fields(reverse_join=True) @property def related_fields(self): return [(self.through._meta.get_field("object_id"), self.model._meta.pk)] @property def foreign_related_fields(self): return [self.related_fields[0][1]] def _get_subclasses(model): subclasses = [model] for field in model._meta.get_fields(): if isinstance(field, OneToOneRel) and getattr( field.field.remote_field, "parent_link", None ): subclasses.extend(_get_subclasses(field.related_model)) return subclasses django-taggit-5.0.1/taggit/migrations/000077500000000000000000000000001451634545100176775ustar00rootroot00000000000000django-taggit-5.0.1/taggit/migrations/0001_initial.py000066400000000000000000000051711451634545100223460ustar00rootroot00000000000000from django.db import migrations, models class Migration(migrations.Migration): dependencies = [("contenttypes", "0001_initial")] operations = [ migrations.CreateModel( name="Tag", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, help_text="", verbose_name="ID", ), ), ( "name", models.CharField( help_text="", unique=True, max_length=100, verbose_name="name" ), ), ( "slug", models.SlugField( help_text="", unique=True, max_length=100, verbose_name="slug" ), ), ], options={"verbose_name": "tag", "verbose_name_plural": "tags"}, bases=(models.Model,), ), migrations.CreateModel( name="TaggedItem", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, help_text="", verbose_name="ID", ), ), ( "object_id", models.IntegerField( help_text="", verbose_name="object ID", db_index=True ), ), ( "content_type", models.ForeignKey( related_name="taggit_taggeditem_tagged_items", verbose_name="content type", to="contenttypes.ContentType", help_text="", on_delete=models.CASCADE, ), ), ( "tag", models.ForeignKey( related_name="taggit_taggeditem_items", to="taggit.Tag", help_text="", on_delete=models.CASCADE, ), ), ], options={ "verbose_name": "tagged item", "verbose_name_plural": "tagged items", }, bases=(models.Model,), ), ] django-taggit-5.0.1/taggit/migrations/0002_auto_20150616_2121.py000066400000000000000000000015061451634545100233150ustar00rootroot00000000000000from django.db import migrations, models class Migration(migrations.Migration): dependencies = [("taggit", "0001_initial")] operations = [ # this migration was modified from previously being # a ModifyIndexTogether operation. # # If you are a long-enough user of this library, the name # of the index does not match what is written here. Please # query the DB itself to find out what the name originally was. migrations.AddIndex( "taggeditem", models.Index( fields=("content_type", "object_id"), # this is not the name of the index in previous version, # but this is necessary to deal with index_together issues. name="taggit_tagg_content_8fc721_idx", ), ) ] django-taggit-5.0.1/taggit/migrations/0003_taggeditem_add_unique_index.py000066400000000000000000000013311451634545100264100ustar00rootroot00000000000000from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("taggit", "0002_auto_20150616_2121"), ] operations = [ # this migration was modified to declare a uniqueness constraint differently # this change was written on 2023-09-20, if any issues occurred from this please report it upstream migrations.AddConstraint( model_name="taggeditem", constraint=models.UniqueConstraint( fields=("content_type", "object_id", "tag"), name="taggit_taggeditem_content_type_id_object_id_tag_id_4bb97a8e_uniq", ), ), ] django-taggit-5.0.1/taggit/migrations/0004_alter_taggeditem_content_type_alter_taggeditem_tag.py000066400000000000000000000023031451634545100332220ustar00rootroot00000000000000# This migration has no effect in practice. It exists to stop # Django from autodetecting migrations in taggit when users # update to Django 4.0. # See https://docs.djangoproject.com/en/stable/releases/4.0/#migrations-autodetector-changes import django.db.models.deletion from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("taggit", "0003_taggeditem_add_unique_index"), ] operations = [ migrations.AlterField( model_name="taggeditem", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="taggeditem", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), ] django-taggit-5.0.1/taggit/migrations/0005_auto_20220424_2025.py000066400000000000000000000007611451634545100233200ustar00rootroot00000000000000# Generated by Django 2.2.26 on 2022-04-24 20:25 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("taggit", "0004_alter_taggeditem_content_type_alter_taggeditem_tag"), ] operations = [ migrations.AlterField( model_name="tag", name="slug", field=models.SlugField( allow_unicode=True, max_length=100, unique=True, verbose_name="slug" ), ), ] 0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx.py000066400000000000000000000006331451634545100370300ustar00rootroot00000000000000django-taggit-5.0.1/taggit/migrations# Generated by Django 4.2.5 on 2023-09-19 23:16 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ("taggit", "0005_auto_20220424_2025"), ] operations = [ migrations.RenameIndex( model_name="taggeditem", new_name="taggit_tagg_content_8fc721_idx", old_fields=("content_type", "object_id"), ), ] django-taggit-5.0.1/taggit/migrations/__init__.py000066400000000000000000000000001451634545100217760ustar00rootroot00000000000000django-taggit-5.0.1/taggit/models.py000066400000000000000000000142071451634545100173640ustar00rootroot00000000000000from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import IntegrityError, models, router, transaction from django.utils.text import slugify from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ from django.utils.translation import pgettext_lazy try: from unidecode import unidecode except ImportError: def unidecode(tag): return tag class TagBase(models.Model): name = models.CharField( verbose_name=pgettext_lazy("A tag name", "name"), unique=True, max_length=100 ) slug = models.SlugField( verbose_name=pgettext_lazy("A tag slug", "slug"), unique=True, max_length=100, allow_unicode=True, ) def __str__(self): return self.name def __gt__(self, other): return self.name.lower() > other.name.lower() def __lt__(self, other): return self.name.lower() < other.name.lower() class Meta: abstract = True def save(self, *args, **kwargs): if self._state.adding and not self.slug: self.slug = self.slugify(self.name) using = kwargs.get("using") or router.db_for_write( type(self), instance=self ) # Make sure we write to the same db for all attempted writes, # with a multi-master setup, theoretically we could try to # write and rollback on different DBs kwargs["using"] = using # Be opportunistic and try to save the tag, this should work for # most cases ;) try: with transaction.atomic(using=using): res = super().save(*args, **kwargs) return res except IntegrityError: pass # Now try to find existing slugs with similar names slugs = set( type(self) ._default_manager.filter(slug__startswith=self.slug) .values_list("slug", flat=True) ) i = 1 while True: slug = self.slugify(self.name, i) if slug not in slugs: self.slug = slug # We purposely ignore concurrency issues here for now. # (That is, till we found a nice solution...) return super().save(*args, **kwargs) i += 1 else: return super().save(*args, **kwargs) def slugify(self, tag, i=None): if getattr(settings, "TAGGIT_STRIP_UNICODE_WHEN_SLUGIFYING", False): slug = slugify(unidecode(tag)) else: slug = slugify(tag, allow_unicode=True) if i is not None: slug += "_%d" % i return slug class Tag(TagBase): class Meta: verbose_name = _("tag") verbose_name_plural = _("tags") app_label = "taggit" class ItemBase(models.Model): def __str__(self): return gettext("%(object)s tagged with %(tag)s") % { "object": self.content_object, "tag": self.tag, } class Meta: abstract = True @classmethod def tag_model(cls): field = cls._meta.get_field("tag") return field.remote_field.model @classmethod def tag_relname(cls): field = cls._meta.get_field("tag") return field.remote_field.related_name @classmethod def lookup_kwargs(cls, instance): return {"content_object": instance} @classmethod def tags_for(cls, model, instance=None, **extra_filters): kwargs = extra_filters or {} if instance is not None: kwargs.update({"%s__content_object" % cls.tag_relname(): instance}) return cls.tag_model().objects.filter(**kwargs) kwargs.update({"%s__content_object__isnull" % cls.tag_relname(): False}) return cls.tag_model().objects.filter(**kwargs).distinct() class TaggedItemBase(ItemBase): tag = models.ForeignKey( Tag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE ) class Meta: abstract = True class CommonGenericTaggedItemBase(ItemBase): content_type = models.ForeignKey( ContentType, on_delete=models.CASCADE, verbose_name=_("content type"), related_name="%(app_label)s_%(class)s_tagged_items", ) content_object = GenericForeignKey() class Meta: abstract = True @classmethod def lookup_kwargs(cls, instance): return { "object_id": instance.pk, "content_type": ContentType.objects.get_for_model(instance), } @classmethod def tags_for(cls, model, instance=None, **extra_filters): tag_relname = cls.tag_relname() model = model._meta.concrete_model kwargs = { "%s__content_type__app_label" % tag_relname: model._meta.app_label, "%s__content_type__model" % tag_relname: model._meta.model_name, } if instance is not None: kwargs["%s__object_id" % tag_relname] = instance.pk if extra_filters: kwargs.update(extra_filters) return cls.tag_model().objects.filter(**kwargs).distinct() class GenericTaggedItemBase(CommonGenericTaggedItemBase): object_id = models.IntegerField(verbose_name=_("object ID"), db_index=True) class Meta: abstract = True class GenericUUIDTaggedItemBase(CommonGenericTaggedItemBase): object_id = models.UUIDField(verbose_name=_("object ID"), db_index=True) class Meta: abstract = True class TaggedItem(GenericTaggedItemBase, TaggedItemBase): class Meta: verbose_name = _("tagged item") verbose_name_plural = _("tagged items") app_label = "taggit" indexes = [ models.Index( fields=["content_type", "object_id"], ) ] constraints = [ models.UniqueConstraint( fields=("content_type", "object_id", "tag"), name="taggit_taggeditem_content_type_id_object_id_tag_id_4bb97a8e_uniq", ) ] django-taggit-5.0.1/taggit/serializers.py000066400000000000000000000104361451634545100204350ustar00rootroot00000000000000""" Django-taggit serializer support Originally vendored from https://github.com/glemmaPaul/django-taggit-serializer """ import json # Third party from django.utils.translation import gettext_lazy from rest_framework import serializers class TagList(list): """ This tag list subclass adds pretty printing support to the tag list serializer """ def __init__(self, *args, **kwargs): pretty_print = kwargs.pop("pretty_print", True) super().__init__(*args, **kwargs) self.pretty_print = pretty_print def __add__(self, rhs): return TagList(super().__add__(rhs)) def __getitem__(self, item): result = super().__getitem__(item) try: return TagList(result) except TypeError: return result def __str__(self): if self.pretty_print: return json.dumps(self, sort_keys=True, indent=4, separators=(",", ": ")) else: return json.dumps(self) class TagListSerializerField(serializers.ListField): """ A serializer field that can write out a tag list This serializer field has some odd qualities compared to just using a ListField. If this field poses problems, we should introduce a new field that is a simpler ListField implementation with less features. """ child = serializers.CharField() default_error_messages = { "not_a_list": gettext_lazy( 'Expected a list of items but got type "{input_type}".' ), "invalid_json": gettext_lazy( "Invalid json list. A tag list submitted in string" " form must be valid json." ), "not_a_str": gettext_lazy("All list items must be of string type."), } order_by = None def __init__(self, **kwargs): pretty_print = kwargs.pop("pretty_print", True) style = kwargs.pop("style", {}) kwargs["style"] = {"base_template": "textarea.html"} kwargs["style"].update(style) super().__init__(**kwargs) self.pretty_print = pretty_print def to_internal_value(self, value): # note to future maintainers: this field used to not be a ListField # and has extra behavior to support string-based input. # # In the future we should look at removing this feature so we can # make this a simple ListField (if feasible) if isinstance(value, str): if not value: value = "[]" try: value = json.loads(value) except ValueError: self.fail("invalid_json") if not isinstance(value, list): self.fail("not_a_list", input_type=type(value).__name__) for s in value: if not isinstance(s, str): self.fail("not_a_str") self.child.run_validation(s) return value def to_representation(self, value): if not isinstance(value, TagList): if not isinstance(value, list): if self.order_by: tags = value.all().order_by(*self.order_by) else: tags = value.all() value = [tag.name for tag in tags] value = TagList(value, pretty_print=self.pretty_print) return value class TaggitSerializer(serializers.Serializer): def create(self, validated_data): to_be_tagged, validated_data = self._pop_tags(validated_data) tag_object = super().create(validated_data) return self._save_tags(tag_object, to_be_tagged) def update(self, instance, validated_data): to_be_tagged, validated_data = self._pop_tags(validated_data) tag_object = super().update(instance, validated_data) return self._save_tags(tag_object, to_be_tagged) def _save_tags(self, tag_object, tags): for key in tags.keys(): tag_values = tags.get(key) getattr(tag_object, key).set(tag_values) return tag_object def _pop_tags(self, validated_data): to_be_tagged = {} for key in self.fields.keys(): field = self.fields[key] if isinstance(field, TagListSerializerField): if key in validated_data: to_be_tagged[key] = validated_data.pop(key) return (to_be_tagged, validated_data) django-taggit-5.0.1/taggit/utils.py000066400000000000000000000103071451634545100172360ustar00rootroot00000000000000from django.conf import settings from django.utils.functional import wraps from django.utils.module_loading import import_string 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 [] # 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 = next(i) if c == '"': if buffer: to_be_split.append("".join(buffer)) buffer = [] # Find the matching quote open_quote = True c = next(i) while c != '"': buffer.append(c) c = 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 def get_func(key, default): func_path = getattr(settings, key, None) return default if func_path is None else import_string(func_path) def parse_tags(tagstring): func = get_func("TAGGIT_TAGS_FROM_STRING", _parse_tags) return func(tagstring) def edit_string_for_tags(tags): func = get_func("TAGGIT_STRING_FROM_TAGS", _edit_string_for_tags) return func(tags) django-taggit-5.0.1/taggit/views.py000066400000000000000000000027721451634545100172420ustar00rootroot00000000000000from django.contrib.contenttypes.models import ContentType from django.shortcuts import get_object_or_404 from django.views.generic.list import ListView from taggit.models import Tag, TaggedItem def tagged_object_list(request, slug, queryset, **kwargs): if callable(queryset): queryset = queryset() kwargs["slug"] = slug tag_list_view = type( "TagListView", (TagListMixin, ListView), {"model": queryset.model, "queryset": queryset}, ) return tag_list_view.as_view()(request, **kwargs) class TagListMixin: tag_suffix = "_tag" def dispatch(self, request, *args, **kwargs): slug = kwargs.pop("slug") self.tag = get_object_or_404(Tag, slug=slug) return super().dispatch(request, *args, **kwargs) def get_queryset(self, **kwargs): qs = super().get_queryset(**kwargs) return qs.filter( pk__in=TaggedItem.objects.filter( tag=self.tag, content_type=ContentType.objects.get_for_model(qs.model) ).values_list("object_id", flat=True) ) def get_template_names(self): if self.tag_suffix: self.template_name_suffix = self.tag_suffix + self.template_name_suffix return super().get_template_names() def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if "extra_context" not in context: context["extra_context"] = {} context["extra_context"]["tag"] = self.tag return context django-taggit-5.0.1/tests/000077500000000000000000000000001451634545100154065ustar00rootroot00000000000000django-taggit-5.0.1/tests/__init__.py000066400000000000000000000000001451634545100175050ustar00rootroot00000000000000django-taggit-5.0.1/tests/admin.py000066400000000000000000000001311451634545100170430ustar00rootroot00000000000000from django.contrib import admin from . import models admin.site.register(models.Food) django-taggit-5.0.1/tests/custom_parser.py000066400000000000000000000002501451634545100206430ustar00rootroot00000000000000def comma_splitter(tag_string): return [t.strip() for t in tag_string.split(",") if t.strip()] def comma_joiner(tags): return ", ".join(t.name for t in tags) django-taggit-5.0.1/tests/forms.py000066400000000000000000000015021451634545100171040ustar00rootroot00000000000000from django import forms from .models import ( BlankTagModel, CustomPKFood, DirectCustomPKFood, DirectFood, Food, OfficialFood, ) class BlankTagForm(forms.ModelForm): class Meta: model = BlankTagModel fields = "__all__" class FoodForm(forms.ModelForm): class Meta: model = Food fields = "__all__" class DirectFoodForm(forms.ModelForm): class Meta: model = DirectFood fields = "__all__" class DirectCustomPKFoodForm(forms.ModelForm): class Meta: model = DirectCustomPKFood fields = "__all__" class CustomPKFoodForm(forms.ModelForm): class Meta: model = CustomPKFood fields = "__all__" class OfficialFoodForm(forms.ModelForm): class Meta: model = OfficialFood fields = "__all__" django-taggit-5.0.1/tests/migrations/000077500000000000000000000000001451634545100175625ustar00rootroot00000000000000django-taggit-5.0.1/tests/migrations/0001_initial.py000066400000000000000000001150161451634545100222310ustar00rootroot00000000000000import uuid import django.db.models.deletion from django.db import migrations, models import taggit.managers class Migration(migrations.Migration): initial = True dependencies = [ ("taggit", "0003_taggeditem_add_unique_index"), ("contenttypes", "0002_remove_content_type_name"), ] operations = [ migrations.CreateModel( name="Article", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("title", models.CharField(max_length=100)), ], ), migrations.CreateModel( name="CustomManager", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="CustomPKFood", fields=[ ( "name", models.CharField(max_length=50, primary_key=True, serialize=False), ) ], ), migrations.CreateModel( name="CustomPKPet", fields=[ ( "name", models.CharField(max_length=50, primary_key=True, serialize=False), ) ], ), migrations.CreateModel( name="DirectCustomPKFood", fields=[ ( "name", models.CharField(max_length=50, primary_key=True, serialize=False), ) ], ), migrations.CreateModel( name="DirectCustomPKPet", fields=[ ( "name", models.CharField(max_length=50, primary_key=True, serialize=False), ) ], ), migrations.CreateModel( name="DirectFood", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="DirectPet", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="DirectTrackedFood", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="DirectTrackedPet", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="BlankTagModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="Food", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="Movie", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], options={"abstract": False}, ), migrations.CreateModel( name="MultipleTags", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="MultipleTagsGFK", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="Name", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="OfficialFood", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="OfficialPet", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="OfficialTag", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "name", models.CharField(max_length=100, unique=True, verbose_name="Name"), ), ( "slug", models.SlugField(max_length=100, unique=True, verbose_name="Slug"), ), ("official", models.BooleanField(default=False)), ], options={"abstract": False}, ), migrations.CreateModel( name="OfficialThroughModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "object_id", models.IntegerField(db_index=True, verbose_name="Object id"), ), ( "content_type", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_officialthroughmodel_tagged_items", to="contenttypes.ContentType", verbose_name="Content type", ), ), ( "tag", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tagged_items", to="tests.OfficialTag", ), ), ("extra_field", models.CharField(max_length=10)), ], ), migrations.CreateModel( name="TrackedTag", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "name", models.CharField(max_length=100, unique=True, verbose_name="Name"), ), ( "slug", models.SlugField(max_length=100, unique=True, verbose_name="Slug"), ), ("created_by", models.CharField(max_length=50)), ("created_dt", models.DateTimeField(auto_now_add=True)), ( "description", models.TextField(blank=True, max_length=255, null=True), ), ], options={"abstract": False}, ), migrations.CreateModel( name="Parent", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="Pet", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="Photo", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], options={"abstract": False}, ), migrations.CreateModel( name="ProxyPhoto", fields=[], options={"proxy": True, "indexes": []}, bases=("tests.photo",), ), migrations.CreateModel( name="TaggedCustomPK", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "object_id", models.CharField( db_index=True, max_length=50, verbose_name="Object id" ), ), ( "content_type", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedcustompk_tagged_items", to="contenttypes.ContentType", verbose_name="Content type", ), ), ], ), migrations.CreateModel( name="TaggedCustomPKFood", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "content_object", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.DirectCustomPKFood", ), ), ], ), migrations.CreateModel( name="TaggedCustomPKPet", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="TaggedFood", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "content_object", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.DirectFood", ), ), ], ), migrations.CreateModel( name="TaggedPet", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], options={"abstract": False}, ), migrations.CreateModel( name="TaggedTrackedFood", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "content_object", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.DirectTrackedFood", ), ), ("created_by", models.CharField(max_length=50)), ("created_dt", models.DateTimeField(auto_now_add=True)), ], ), migrations.CreateModel( name="TaggedTrackedPet", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("created_by", models.CharField(max_length=50)), ("created_dt", models.DateTimeField(auto_now_add=True)), ], options={"abstract": False}, ), migrations.CreateModel( name="Through1", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "content_object", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.MultipleTags", ), ), ], options={"abstract": False}, ), migrations.CreateModel( name="Through2", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "content_object", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.MultipleTags", ), ), ], options={"abstract": False}, ), migrations.CreateModel( name="ThroughGFK", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "object_id", models.IntegerField(db_index=True, verbose_name="Object id"), ), ( "content_type", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_throughgfk_tagged_items", to="contenttypes.ContentType", verbose_name="Content type", ), ), ], options={"abstract": False}, ), migrations.CreateModel( name="UUIDFood", fields=[ ( "id", models.UUIDField( default=uuid.uuid4, editable=False, primary_key=True, serialize=False, ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="UUIDTag", fields=[ ( "name", models.CharField(max_length=100, unique=True, verbose_name="Name"), ), ( "slug", models.SlugField(max_length=100, unique=True, verbose_name="Slug"), ), ( "id", models.UUIDField( default=uuid.uuid4, editable=False, primary_key=True, serialize=False, ), ), ], options={"abstract": False}, ), migrations.CreateModel( name="UUIDTaggedItem", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "object_id", models.UUIDField(db_index=True, verbose_name="Object id"), ), ( "content_type", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_uuidtaggeditem_tagged_items", to="contenttypes.ContentType", verbose_name="Content type", ), ), ( "tag", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_uuidtaggeditem_items", to="tests.UUIDTag", ), ), ], options={"abstract": False}, ), migrations.CreateModel( name="ArticleTag", fields=[], options={"proxy": True, "indexes": []}, bases=("taggit.tag",), ), migrations.CreateModel( name="ArticleTaggedItem", fields=[], options={"proxy": True, "indexes": []}, bases=("taggit.taggeditem",), ), migrations.CreateModel( name="Child", fields=[ ( "parent_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Parent", ), ) ], bases=("tests.parent",), ), migrations.CreateModel( name="CustomPKHousePet", fields=[ ( "custompkpet_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.CustomPKPet", ), ), ("trained", models.BooleanField(default=False)), ], bases=("tests.custompkpet",), ), migrations.CreateModel( name="DirectCustomPKHousePet", fields=[ ( "directcustompkpet_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.DirectCustomPKPet", ), ), ("trained", models.BooleanField(default=False)), ], bases=("tests.directcustompkpet",), ), migrations.CreateModel( name="DirectHousePet", fields=[ ( "directpet_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.DirectPet", ), ), ("trained", models.BooleanField(default=False)), ], bases=("tests.directpet",), ), migrations.CreateModel( name="HousePet", fields=[ ( "pet_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Pet", ), ), ("trained", models.BooleanField(default=False)), ], bases=("tests.pet",), ), migrations.CreateModel( name="OfficialHousePet", fields=[ ( "officialpet_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.OfficialPet", ), ), ("trained", models.BooleanField(default=False)), ], bases=("tests.officialpet",), ), migrations.CreateModel( name="DirectTrackedHousePet", fields=[ ( "directtrackedpet_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.DirectTrackedPet", ), ), ("trained", models.BooleanField(default=False)), ], bases=("tests.directtrackedpet",), ), migrations.AddField( model_name="uuidfood", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.UUIDTaggedItem", to="tests.UUIDTag", verbose_name="Tags", ), ), migrations.AddField( model_name="throughgfk", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tagged_items", to="taggit.Tag", ), ), migrations.AddField( model_name="through2", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_through2_items", to="taggit.Tag", ), ), migrations.AddField( model_name="through1", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_through1_items", to="taggit.Tag", ), ), migrations.AddField( model_name="taggedpet", name="content_object", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.DirectPet" ), ), migrations.AddField( model_name="taggedpet", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedpet_items", to="taggit.Tag", ), ), migrations.AddField( model_name="taggedfood", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedfood_items", to="taggit.Tag", ), ), migrations.AddField( model_name="taggedtrackedpet", name="content_object", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.DirectTrackedPet" ), ), migrations.AddField( model_name="taggedtrackedpet", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedtrackedpet_items", to="tests.TrackedTag", ), ), migrations.AddField( model_name="taggedtrackedfood", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedtrackedfood_items", to="tests.TrackedTag", ), ), migrations.AddField( model_name="taggedcustompkpet", name="content_object", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.DirectCustomPKPet", ), ), migrations.AddField( model_name="taggedcustompkpet", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedcustompkpet_items", to="taggit.Tag", ), ), migrations.AddField( model_name="taggedcustompkfood", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedcustompkfood_items", to="taggit.Tag", ), ), migrations.AddField( model_name="taggedcustompk", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedcustompk_items", to="taggit.Tag", ), ), migrations.AddField( model_name="photo", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="pet", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="parent", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="officialpet", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.OfficialThroughModel", to="tests.OfficialTag", verbose_name="Tags", ), ), migrations.AddField( model_name="officialfood", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.OfficialThroughModel", to="tests.OfficialTag", verbose_name="Tags", ), ), migrations.AddField( model_name="name", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", related_name="a_unique_related_name", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="multipletagsgfk", name="tags1", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", related_name="tagsgfk1", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="multipletagsgfk", name="tags2", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", related_name="tagsgfk2", through="tests.ThroughGFK", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="multipletags", name="tags1", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", related_name="tags1", through="tests.Through1", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="multipletags", name="tags2", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", related_name="tags2", through="tests.Through2", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="movie", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="food", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="directpet", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TaggedPet", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="directfood", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TaggedFood", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="directtrackedpet", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TaggedTrackedPet", to="tests.TrackedTag", verbose_name="Tags", ), ), migrations.AddField( model_name="directtrackedfood", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TaggedTrackedFood", to="tests.TrackedTag", verbose_name="Tags", ), ), migrations.AddField( model_name="directcustompkpet", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TaggedCustomPKPet", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="directcustompkfood", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TaggedCustomPKFood", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="custompkpet", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TaggedCustomPK", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="custompkfood", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TaggedCustomPK", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="custommanager", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="article", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.ArticleTaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AlterUniqueTogether( name="taggedfood", unique_together={("content_object", "tag")} ), migrations.AlterUniqueTogether( name="taggedtrackedfood", unique_together={("content_object", "tag")} ), migrations.AlterUniqueTogether( name="taggedcustompkpet", unique_together={("content_object", "tag")} ), migrations.AlterUniqueTogether( name="taggedcustompkfood", unique_together={("content_object", "tag")} ), migrations.AlterUniqueTogether( name="taggedcustompk", unique_together={("object_id", "tag")} ), migrations.AlterUniqueTogether( name="officialthroughmodel", unique_together={("content_type", "object_id", "tag")}, ), ] django-taggit-5.0.1/tests/migrations/0002_auto_20200214_1129.py000066400000000000000000000066601451634545100232030ustar00rootroot00000000000000# Generated by Django 3.0.3 on 2020-02-14 11:29 import uuid import django.utils.timezone from django.db import migrations, models import taggit.managers class Migration(migrations.Migration): dependencies = [ ("taggit", "0003_taggeditem_add_unique_index"), ("contenttypes", "0002_remove_content_type_name"), ("tests", "0001_initial"), ] operations = [ migrations.CreateModel( name="UUIDPet", fields=[ ( "id", models.UUIDField( default=uuid.uuid4, editable=False, primary_key=True, serialize=False, ), ), ("name", models.CharField(max_length=50)), ("created_at", models.DateTimeField(auto_now_add=True)), ], options={"ordering": ["created_at"]}, ), migrations.AlterModelOptions( name="uuidfood", options={"ordering": ["created_at"]} ), migrations.AddField( model_name="blanktagmodel", name="tags", field=taggit.managers.TaggableManager( blank=True, help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), migrations.AddField( model_name="uuidfood", name="created_at", field=models.DateTimeField( auto_now_add=True, default=django.utils.timezone.now ), preserve_default=False, ), migrations.AlterField( model_name="taggedtrackedfood", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="taggedtrackedfood_items", to="tests.TrackedTag", ), ), migrations.AlterField( model_name="taggedtrackedpet", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="taggedtrackedpet_items", to="tests.TrackedTag", ), ), migrations.AlterUniqueTogether( name="uuidtaggeditem", unique_together={("content_type", "object_id", "tag")}, ), migrations.CreateModel( name="UUIDHousePet", fields=[ ( "uuidpet_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.UUIDPet", ), ), ("trained", models.BooleanField(default=False)), ], bases=("tests.uuidpet",), ), migrations.AddField( model_name="uuidpet", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.UUIDTaggedItem", to="tests.UUIDTag", verbose_name="Tags", ), ), ] django-taggit-5.0.1/tests/migrations/0003_auto_20210310_0918.py000066400000000000000000000056621451634545100232100ustar00rootroot00000000000000# Generated by Django 3.1.7 on 2021-03-10 09:18 import django.db.models.deletion from django.db import migrations, models import taggit.managers class Migration(migrations.Migration): dependencies = [ ("taggit", "0003_taggeditem_add_unique_index"), ("contenttypes", "0002_remove_content_type_name"), ("tests", "0002_auto_20200214_1129"), ] operations = [ migrations.CreateModel( name="BaseFood", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], ), migrations.CreateModel( name="MultiInheritanceFood", fields=[ ( "basefood_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.basefood", ), ), ], bases=("tests.basefood",), ), migrations.CreateModel( name="MultiInheritanceLazyResolutionFoodTag", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "tag", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_multiinheritancelazyresolutionfoodtag_items", to="taggit.tag", ), ), ( "content_object", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tagged_items", to="tests.multiinheritancefood", ), ), ], options={ "unique_together": {("content_object", "tag")}, }, ), migrations.AddField( model_name="multiinheritancefood", name="tags", field=taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.MultiInheritanceLazyResolutionFoodTag", to="taggit.Tag", verbose_name="Tags", ), ), ] django-taggit-5.0.1/tests/migrations/0004_auto_20210619_0826.py000066400000000000000000000105031451634545100232110ustar00rootroot00000000000000# Generated by Django 3.2.4 on 2021-06-19 08:26 import django.db.models.deletion from django.db import migrations, models import taggit.managers class Migration(migrations.Migration): dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("taggit", "0003_taggeditem_add_unique_index"), ("tests", "0003_auto_20210310_0918"), ] operations = [ migrations.AlterField( model_name="officialtag", name="name", field=models.CharField(max_length=100, unique=True, verbose_name="name"), ), migrations.AlterField( model_name="officialtag", name="slug", field=models.SlugField(max_length=100, unique=True, verbose_name="slug"), ), migrations.AlterField( model_name="officialthroughmodel", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_officialthroughmodel_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="officialthroughmodel", name="object_id", field=models.IntegerField(db_index=True, verbose_name="object ID"), ), migrations.AlterField( model_name="taggedcustompk", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_taggedcustompk_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="throughgfk", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_throughgfk_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="throughgfk", name="object_id", field=models.IntegerField(db_index=True, verbose_name="object ID"), ), migrations.AlterField( model_name="trackedtag", name="name", field=models.CharField(max_length=100, unique=True, verbose_name="name"), ), migrations.AlterField( model_name="trackedtag", name="slug", field=models.SlugField(max_length=100, unique=True, verbose_name="slug"), ), migrations.AlterField( model_name="uuidtag", name="name", field=models.CharField(max_length=100, unique=True, verbose_name="name"), ), migrations.AlterField( model_name="uuidtag", name="slug", field=models.SlugField(max_length=100, unique=True, verbose_name="slug"), ), migrations.AlterField( model_name="uuidtaggeditem", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_uuidtaggeditem_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="uuidtaggeditem", name="object_id", field=models.UUIDField(db_index=True, verbose_name="object ID"), ), migrations.CreateModel( name="TestModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "tags", taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), ], ), ] django-taggit-5.0.1/tests/migrations/0005_auto_20210713_2301.py000066400000000000000000000017601451634545100232000ustar00rootroot00000000000000# Generated by Django 3.2.4 on 2021-06-19 08:26 from django.db import migrations, models import taggit.managers class Migration(migrations.Migration): dependencies = [ ("tests", "0004_auto_20210619_0826"), ] operations = [ migrations.CreateModel( name="OrderedModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "tags", taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="taggit.TaggedItem", to="taggit.Tag", verbose_name="Tags", ), ), ], ), ] django-taggit-5.0.1/tests/migrations/0006_auto_20230622_0834.py000066400000000000000000000075361451634545100232220ustar00rootroot00000000000000# Generated by Django 3.2.19 on 2023-06-22 08:34 import django.db.models.deletion from django.db import migrations, models import taggit.managers class Migration(migrations.Migration): dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("tests", "0005_auto_20210713_2301"), ] operations = [ migrations.CreateModel( name="TenantTag", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=100)), ("slug", models.SlugField(allow_unicode=True, max_length=100)), ("tenant_id", models.IntegerField()), ], options={ "unique_together": {("name", "tenant_id"), ("slug", "tenant_id")}, }, ), migrations.AlterField( model_name="officialtag", name="slug", field=models.SlugField( allow_unicode=True, max_length=100, unique=True, verbose_name="slug" ), ), migrations.AlterField( model_name="trackedtag", name="slug", field=models.SlugField( allow_unicode=True, max_length=100, unique=True, verbose_name="slug" ), ), migrations.AlterField( model_name="uuidtag", name="slug", field=models.SlugField( allow_unicode=True, max_length=100, unique=True, verbose_name="slug" ), ), migrations.CreateModel( name="TenantTaggedItem", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "object_id", models.IntegerField(db_index=True, verbose_name="object ID"), ), ( "content_type", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_tenanttaggeditem_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), ( "tag", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="tests_tenanttaggeditem_items", to="tests.tenanttag", ), ), ], options={ "abstract": False, }, ), migrations.CreateModel( name="TenantModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ( "tags", taggit.managers.TaggableManager( help_text="A comma-separated list of tags.", through="tests.TenantTaggedItem", to="tests.TenantTag", verbose_name="Tags", ), ), ], ), ] 0007_alter_multiinheritancelazyresolutionfoodtag_tag_and_more.py000066400000000000000000000137351451634545100343560ustar00rootroot00000000000000django-taggit-5.0.1/tests/migrations# Generated by Django 4.2 on 2023-07-24 00:05 import django.db.models.deletion from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("taggit", "0005_auto_20220424_2025"), ("tests", "0006_auto_20230622_0834"), ] operations = [ migrations.AlterField( model_name="multiinheritancelazyresolutionfoodtag", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), migrations.AlterField( model_name="officialthroughmodel", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="taggedcustompk", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="taggedcustompk", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), migrations.AlterField( model_name="taggedcustompkfood", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), migrations.AlterField( model_name="taggedcustompkpet", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), migrations.AlterField( model_name="taggedfood", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), migrations.AlterField( model_name="taggedpet", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), migrations.AlterField( model_name="taggedtrackedfood", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(class)s_items", to="tests.trackedtag", ), ), migrations.AlterField( model_name="taggedtrackedpet", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(class)s_items", to="tests.trackedtag", ), ), migrations.AlterField( model_name="tenanttaggeditem", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="tenanttaggeditem", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="tests.tenanttag", ), ), migrations.AlterField( model_name="through1", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), migrations.AlterField( model_name="through2", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="taggit.tag", ), ), migrations.AlterField( model_name="throughgfk", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="uuidtaggeditem", name="content_type", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_tagged_items", to="contenttypes.contenttype", verbose_name="content type", ), ), migrations.AlterField( model_name="uuidtaggeditem", name="tag", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="%(app_label)s_%(class)s_items", to="tests.uuidtag", ), ), ] django-taggit-5.0.1/tests/migrations/__init__.py000066400000000000000000000000001451634545100216610ustar00rootroot00000000000000django-taggit-5.0.1/tests/models.py000066400000000000000000000245411451634545100172510ustar00rootroot00000000000000import uuid from django.db import models from taggit.managers import TaggableManager from taggit.models import ( CommonGenericTaggedItemBase, GenericTaggedItemBase, GenericUUIDTaggedItemBase, ItemBase, Tag, TagBase, TaggedItem, TaggedItemBase, ) # base test model class TestModel(models.Model): tags = TaggableManager() # Ensure that two TaggableManagers with custom through model are allowed. class Through1(TaggedItemBase): content_object = models.ForeignKey("MultipleTags", on_delete=models.CASCADE) class Through2(TaggedItemBase): content_object = models.ForeignKey("MultipleTags", on_delete=models.CASCADE) class MultipleTags(models.Model): tags1 = TaggableManager(through=Through1, related_name="tags1") tags2 = TaggableManager(through=Through2, related_name="tags2") # Ensure that two TaggableManagers with GFK via different through models are allowed. class ThroughGFK(GenericTaggedItemBase): tag = models.ForeignKey(Tag, related_name="tagged_items", on_delete=models.CASCADE) class MultipleTagsGFK(models.Model): tags1 = TaggableManager(related_name="tagsgfk1") tags2 = TaggableManager(through=ThroughGFK, related_name="tagsgfk2") class BlankTagModel(models.Model): name = models.CharField(max_length=50) tags = TaggableManager(blank=True) def __str__(self): return self.name class Food(models.Model): name = models.CharField(max_length=50) tags = TaggableManager() def __str__(self): return self.name class BaseFood(models.Model): name = models.CharField(max_length=50) def __str__(self): return self.name class MultiInheritanceLazyResolutionFoodTag(TaggedItemBase): content_object = models.ForeignKey( "MultiInheritanceFood", related_name="tagged_items", on_delete=models.CASCADE ) class Meta: unique_together = [["content_object", "tag"]] class MultiInheritanceFood(BaseFood): tags = TaggableManager(through=MultiInheritanceLazyResolutionFoodTag) def __str__(self): return self.name class Pet(models.Model): name = models.CharField(max_length=50) tags = TaggableManager() def __str__(self): return self.name class HousePet(Pet): trained = models.BooleanField(default=False) # Test direct-tagging with custom through model class TaggedFood(TaggedItemBase): content_object = models.ForeignKey("DirectFood", on_delete=models.CASCADE) class Meta: unique_together = [["content_object", "tag"]] class TaggedPet(TaggedItemBase): content_object = models.ForeignKey("DirectPet", on_delete=models.CASCADE) class DirectFood(models.Model): name = models.CharField(max_length=50) tags = TaggableManager(through="TaggedFood") def __str__(self): return self.name class DirectPet(models.Model): name = models.CharField(max_length=50) tags = TaggableManager(through=TaggedPet) def __str__(self): return self.name class DirectHousePet(DirectPet): trained = models.BooleanField(default=False) # Test direct-tagging with custom through model and custom tag class TrackedTag(TagBase): created_by = models.CharField(max_length=50) created_dt = models.DateTimeField(auto_now_add=True) description = models.TextField(blank=True, max_length=255, null=True) class TaggedTrackedFood(ItemBase): content_object = models.ForeignKey("DirectTrackedFood", on_delete=models.CASCADE) tag = models.ForeignKey( TrackedTag, on_delete=models.CASCADE, related_name="%(class)s_items" ) created_by = models.CharField(max_length=50) created_dt = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ["content_object", "tag"] class TaggedTrackedPet(ItemBase): content_object = models.ForeignKey("DirectTrackedPet", on_delete=models.CASCADE) tag = models.ForeignKey( TrackedTag, on_delete=models.CASCADE, related_name="%(class)s_items" ) created_by = models.CharField(max_length=50) created_dt = models.DateTimeField(auto_now_add=True) class DirectTrackedFood(models.Model): name = models.CharField(max_length=50) tags = TaggableManager(through=TaggedTrackedFood) def __str__(self): return self.name class DirectTrackedPet(models.Model): name = models.CharField(max_length=50) tags = TaggableManager(through=TaggedTrackedPet) def __str__(self): return self.name class DirectTrackedHousePet(DirectTrackedPet): trained = models.BooleanField(default=False) # Test custom through model to model with custom PK class TaggedCustomPKFood(TaggedItemBase): content_object = models.ForeignKey("DirectCustomPKFood", on_delete=models.CASCADE) class Meta: unique_together = [["content_object", "tag"]] class TaggedCustomPKPet(TaggedItemBase): content_object = models.ForeignKey("DirectCustomPKPet", on_delete=models.CASCADE) class Meta: unique_together = [["content_object", "tag"]] class DirectCustomPKFood(models.Model): name = models.CharField(max_length=50, primary_key=True) tags = TaggableManager(through=TaggedCustomPKFood) def __str__(self): return self.name class DirectCustomPKPet(models.Model): name = models.CharField(max_length=50, primary_key=True) tags = TaggableManager(through=TaggedCustomPKPet) def __str__(self): return self.name class DirectCustomPKHousePet(DirectCustomPKPet): trained = models.BooleanField(default=False) # Test custom through model to model with custom PK using GenericForeignKey class TaggedCustomPK(CommonGenericTaggedItemBase, TaggedItemBase): object_id = models.CharField(max_length=50, verbose_name="Object id", db_index=True) class Meta: unique_together = [["object_id", "tag"]] class CustomPKFood(models.Model): name = models.CharField(max_length=50, primary_key=True) tags = TaggableManager(through=TaggedCustomPK) def __str__(self): return self.name class CustomPKPet(models.Model): name = models.CharField(max_length=50, primary_key=True) tags = TaggableManager(through=TaggedCustomPK) def __str__(self): return self.name class CustomPKHousePet(CustomPKPet): trained = models.BooleanField(default=False) # Test custom through model to a custom tag model class OfficialTag(TagBase): official = models.BooleanField(default=False) class OfficialThroughModel(GenericTaggedItemBase): tag = models.ForeignKey( OfficialTag, related_name="tagged_items", on_delete=models.CASCADE ) extra_field = models.CharField(max_length=10) class Meta: unique_together = [["content_type", "object_id", "tag"]] class OfficialFood(models.Model): name = models.CharField(max_length=50) tags = TaggableManager(through=OfficialThroughModel) def __str__(self): return self.name class OfficialPet(models.Model): name = models.CharField(max_length=50) tags = TaggableManager(through=OfficialThroughModel) def __str__(self): return self.name class OfficialHousePet(OfficialPet): trained = models.BooleanField(default=False) class Media(models.Model): tags = TaggableManager() class Meta: abstract = True class Photo(Media): pass class Movie(Media): pass class ProxyPhoto(Photo): class Meta: proxy = True class ArticleTag(Tag): class Meta: proxy = True def slugify(self, tag, i=None): slug = "category-%s" % tag.lower() if i is not None: slug += "-%d" % i return slug class ArticleTaggedItem(TaggedItem): class Meta: proxy = True @classmethod def tag_model(self): return ArticleTag class Article(models.Model): title = models.CharField(max_length=100) tags = TaggableManager(through=ArticleTaggedItem) class CustomManager(models.Model): class Foo: def __init__(*args, **kwargs): pass tags = TaggableManager(manager=Foo) class Parent(models.Model): tags = TaggableManager() class Child(Parent): pass class UUIDFood(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=50) tags = TaggableManager(through="UUIDTaggedItem") created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class Meta: # With a UUIDField pk, objects are not always ordered by creation time. So explicitly set ordering. ordering = ["created_at"] class UUIDPet(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=50) tags = TaggableManager(through="UUIDTaggedItem") created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class Meta: # With a UUIDField pk, objects are not always ordered by creation time. So explicitly set ordering. ordering = ["created_at"] class UUIDHousePet(UUIDPet): trained = models.BooleanField(default=False) class UUIDTag(TagBase): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) class UUIDTaggedItem(GenericUUIDTaggedItemBase): tag = models.ForeignKey( UUIDTag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE ) class Meta: unique_together = [["content_type", "object_id", "tag"]] class TenantTag(TagBase): name = models.CharField(max_length=100) slug = models.SlugField(max_length=100, allow_unicode=True) tenant_id = models.IntegerField() class Meta: unique_together = [["name", "tenant_id"], ["slug", "tenant_id"]] class TenantTaggedItem(GenericTaggedItemBase): tag = models.ForeignKey( TenantTag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE, ) class TenantModel(models.Model): name = models.CharField(max_length=50) tags = TaggableManager(through=TenantTaggedItem) # Exists to verify system check failure. # tests.Name.tags: (fields.E303) Reverse query name for 'Name.tags' clashes with field name 'Tag.name'. # HINT: Rename field 'Tag.name', or add/change a related_name argument to the definition for field 'Name.tags'. class Name(models.Model): tags = TaggableManager(related_name="a_unique_related_name") class OrderedModel(models.Model): tags = TaggableManager(ordering=["name"]) django-taggit-5.0.1/tests/serializers.py000066400000000000000000000005051451634545100203140ustar00rootroot00000000000000from rest_framework import serializers from taggit.serializers import TaggitSerializer, TagListSerializerField from .models import TestModel class TestModelSerializer(TaggitSerializer, serializers.ModelSerializer): tags = TagListSerializerField() class Meta: model = TestModel fields = "__all__" django-taggit-5.0.1/tests/settings.py000066400000000000000000000025001451634545100176150ustar00rootroot00000000000000SECRET_KEY = "secretkey" INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "taggit", "tests", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ROOT_URLCONF = "tests.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ] }, } ] DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}} STATIC_URL = "/static/" DEFAULT_AUTO_FIELD = "django.db.models.AutoField" USE_TZ = True django-taggit-5.0.1/tests/templates/000077500000000000000000000000001451634545100174045ustar00rootroot00000000000000django-taggit-5.0.1/tests/templates/tests/000077500000000000000000000000001451634545100205465ustar00rootroot00000000000000django-taggit-5.0.1/tests/templates/tests/food_tag_list.html000066400000000000000000000001741451634545100242530ustar00rootroot00000000000000 Title django-taggit-5.0.1/tests/test_admin.py000066400000000000000000000025621451634545100201140ustar00rootroot00000000000000from django.contrib.auth.models import User from django.test import TestCase from django.urls import reverse from .models import Food class AdminTest(TestCase): def setUp(self): super().setUp() self.apple = Food.objects.create(name="apple") self.apple.tags.add("Red", "red") user = User.objects.create_superuser( username="admin", email="admin@mailinator.com", password="password" ) self.client.force_login(user) def test_get_changelist(self): response = self.client.get(reverse("admin:tests_food_changelist")) self.assertEqual(response.status_code, 200) def test_get_add(self): response = self.client.get(reverse("admin:tests_food_add")) self.assertEqual(response.status_code, 200) def test_get_history(self): response = self.client.get( reverse("admin:tests_food_history", args=(self.apple.pk,)) ) self.assertEqual(response.status_code, 200) def test_get_delete(self): response = self.client.get( reverse("admin:tests_food_delete", args=(self.apple.pk,)) ) self.assertEqual(response.status_code, 200) def test_get_change(self): response = self.client.get( reverse("admin:tests_food_change", args=(self.apple.pk,)) ) self.assertEqual(response.status_code, 200) django-taggit-5.0.1/tests/test_forms.py000066400000000000000000000034611451634545100201510ustar00rootroot00000000000000from django import forms from django.test import TestCase from django.test.utils import override_settings from taggit.forms import TagField from taggit.models import Tag def _test_parse_tags(tagstring): if "," in tagstring: return tagstring.split(",") else: raise ValueError() @override_settings(TAGGIT_TAGS_FROM_STRING="tests.test_forms._test_parse_tags") class TagFieldTests(TestCase): def test_should_return_error_on_clean_if_not_comma_separated(self): class TestForm(forms.Form): tag = TagField() excpected_error = "Please provide a comma-separated list of tags." form = TestForm({"tag": "not-comma-separated"}) self.assertFalse(form.is_valid()) self.assertIn(excpected_error, form.errors["tag"]) def test_should_always_return_False_on_has_change_if_disabled(self): class TestForm(forms.Form): tag = TagField(disabled=True) form = TestForm(initial={"tag": "foo,bar"}, data={"tag": ["a,b,c"]}) self.assertTrue(form.is_valid()) self.assertFalse(form.has_changed()) def test_should_return_True_if_form_has_changed(self): class TestForm(forms.Form): tag = TagField() form = TestForm(initial={"tag": [Tag(name="a")]}, data={"tag": ["b"]}) self.assertTrue(form.has_changed()) def test_should_return_False_if_form_has_not_changed(self): class TestForm(forms.Form): tag = TagField() form = TestForm( initial={"tag": [Tag(name="foo-bar")]}, data={"tag": ["foo-bar"]} ) self.assertFalse(form.has_changed()) def test_should_return_False_if_not_provided(self): class TestForm(forms.Form): tag = TagField() form = TestForm() self.assertFalse(form.has_changed()) django-taggit-5.0.1/tests/test_models.py000066400000000000000000000044431451634545100203070ustar00rootroot00000000000000from django.test import TestCase, override_settings from tests.models import TestModel class TestSlugification(TestCase): def test_unicode_slugs(self): """ Confirm the preservation of unicode in slugification by default """ sample_obj = TestModel.objects.create() # a unicode tag will be slugified for space reasons but # unicode-ness will be kept by default sample_obj.tags.add("ã‚ã„ ã†ãˆãŠ") self.assertEqual([tag.slug for tag in sample_obj.tags.all()], ["ã‚ã„-ã†ãˆãŠ"]) def test_old_slugs(self): """ Test that the setting that gives us the old slugification behavior is in place """ with override_settings(TAGGIT_STRIP_UNICODE_WHEN_SLUGIFYING=True): sample_obj = TestModel.objects.create() # a unicode tag will be slugified for space reasons but # unicode-ness will be kept by default sample_obj.tags.add("ã‚ã„ ã†ãˆãŠ") self.assertEqual([tag.slug for tag in sample_obj.tags.all()], [""]) class TestPrefetchCache(TestCase): def setUp(self) -> None: sample_obj = TestModel.objects.create() sample_obj.tags.set(["1", "2", "3"]) def test_cache_clears_on_add(self): """ Test that the prefetch cache gets cleared on tag addition """ sample_obj = TestModel.objects.prefetch_related("tags").get() self.assertTrue(sample_obj.tags.is_cached(sample_obj)) sample_obj.tags.add("4") self.assertFalse(sample_obj.tags.is_cached(sample_obj)) def test_cache_clears_on_remove(self): """ Test that the prefetch cache gets cleared on tag removal """ sample_obj = TestModel.objects.prefetch_related("tags").get() self.assertTrue(sample_obj.tags.is_cached(sample_obj)) sample_obj.tags.remove("3") self.assertFalse(sample_obj.tags.is_cached(sample_obj)) def test_cache_clears_on_clear(self): """ Test that the prefetch cache gets cleared when tags are cleared """ sample_obj = TestModel.objects.prefetch_related("tags").get() self.assertTrue(sample_obj.tags.is_cached(sample_obj)) sample_obj.tags.clear() self.assertFalse(sample_obj.tags.is_cached(sample_obj)) django-taggit-5.0.1/tests/test_serializers.py000066400000000000000000000064321451634545100213600ustar00rootroot00000000000000""" test_django-taggit-serializer ------------ Tests for `django-taggit-serializer` models module. """ from django.test import TestCase from rest_framework.exceptions import ValidationError from taggit import serializers from .models import TestModel from .serializers import TestModelSerializer class TestTaggit_serializer(TestCase): def test_taggit_serializer_field(self): correct_value = ["1", "2", "3"] serializer_field = serializers.TagListSerializerField() correct_value = serializer_field.to_internal_value(correct_value) assert type(correct_value) is list incorrect_value = "123" with self.assertRaises(ValidationError): incorrect_value = serializer_field.to_internal_value(incorrect_value) representation = serializer_field.to_representation(correct_value) self.assertIsInstance(representation, serializers.TagList) def test_taggit_serializer_update(self): """Test if serializer class is working properly on updating object""" request_data = {"tags": ["1", "2", "3"]} test_model = TestModel.objects.create() serializer = TestModelSerializer(test_model, data=request_data) serializer.is_valid() serializer.save() assert len(test_model.tags.all()) == len(request_data.get("tags")) def test_taggit_serializer_create(self): """ Test if serializer class is working properly on creating a object """ request_data = {"tags": ["1", "2", "3"]} serializer = TestModelSerializer(data=request_data) assert serializer.is_valid() test_model = serializer.save() assert len(test_model.tags.all()) == len(request_data.get("tags")) def test_taggit_serializer_create_with_string(self): """ Test that we can pass in a string instead of an array for a tag list without issues """ request_data = {"tags": '["1", "2", "3"]'} serializer = TestModelSerializer(data=request_data) assert serializer.is_valid(), serializer.errors test_model = serializer.save() assert {tag.name for tag in test_model.tags.all()} == {"1", "2", "3"} def test_taggit_removes_tags(self): """ Test if the old assigned tags are removed """ test_model = TestModel.objects.create() test_model.tags.add("1") request_data = {"tags": ["2", "3"]} serializer = TestModelSerializer(test_model, data=request_data) serializer.is_valid() test_model = serializer.save() assert TestModel.objects.filter(tags__name__in=["1"]).count() == 0 assert TestModel.objects.filter(tags__name__in=["1", "2"]).count() == 1 def test_returns_new_data_after_update(self): """ Test if the serializer uses fresh data after updating prefetched fields """ TestModel.objects.create().tags.add("1") test_model = TestModel.objects.prefetch_related("tags").get() assert TestModelSerializer(test_model).data["tags"] == ["1"] request_data = {"tags": ["2", "3"]} serializer = TestModelSerializer(test_model, data=request_data) serializer.is_valid() test_model = serializer.save() assert set(serializer.data["tags"]) == {"2", "3"} django-taggit-5.0.1/tests/test_utils.py000066400000000000000000000017201451634545100201570ustar00rootroot00000000000000import os import os.path from django.test import TestCase from django.utils import translation from taggit.utils import split_strip class SplitStripTests(TestCase): def test_should_return_empty_list_if_not_string(self): result = split_strip(None) self.assertListEqual(result, []) def test_should_return_list_of_non_empty_words(self): expected_result = ["foo", "bar"] result = split_strip("foo|bar||", delimiter="|") self.assertListEqual(result, expected_result) class TestLanguages(TestCase): maxDiff = None def get_locale_dir(self): return os.path.join(os.path.dirname(__file__), "..", "taggit", "locale") def test_language_file_integrity(self): locale_dir = self.get_locale_dir() for locale in os.listdir(locale_dir): # attempt translation activation to confirm that the language files are working with translation.override(locale): pass django-taggit-5.0.1/tests/tests.py000066400000000000000000001473141451634545100171340ustar00rootroot00000000000000from io import StringIO from unittest import mock from django import VERSION as DJANGO_VERSION from django.contrib.contenttypes.models import ContentType from django.core import serializers from django.core.exceptions import ValidationError from django.core.management import call_command from django.db import IntegrityError, connection, models from django.test import RequestFactory, SimpleTestCase, TestCase from django.test.utils import override_settings from taggit.managers import TaggableManager, _TaggableManager from taggit.models import Tag, TaggedItem from taggit.utils import edit_string_for_tags, parse_tags from taggit.views import tagged_object_list from .forms import ( BlankTagForm, CustomPKFoodForm, DirectCustomPKFoodForm, DirectFoodForm, FoodForm, OfficialFoodForm, ) from .models import ( Article, Child, CustomManager, CustomPKFood, CustomPKHousePet, CustomPKPet, DirectCustomPKFood, DirectCustomPKHousePet, DirectCustomPKPet, DirectFood, DirectHousePet, DirectPet, DirectTrackedFood, DirectTrackedHousePet, DirectTrackedPet, Food, HousePet, Movie, MultiInheritanceFood, Name, OfficialFood, OfficialHousePet, OfficialPet, OfficialTag, OfficialThroughModel, OrderedModel, Pet, Photo, ProxyPhoto, TaggedCustomPK, TaggedCustomPKFood, TaggedFood, TaggedTrackedFood, TenantModel, TenantTag, TrackedTag, UUIDFood, UUIDHousePet, UUIDPet, UUIDTag, UUIDTaggedItem, ) if DJANGO_VERSION < (4, 2): TestCase.assertQuerySetEqual = TestCase.assertQuerysetEqual class BaseTaggingTestCase(TestCase): 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) class TagModelTestCase(BaseTaggingTestCase): food_model = Food tag_model = Tag def test_unique_slug(self): apple = self.food_model.objects.create(name="apple") apple.tags.add("Red", "red") def test_update(self): special = self.tag_model.objects.create(name="special") special.save() def test_add(self): apple = self.food_model.objects.create(name="apple") yummy = self.tag_model.objects.create(name="yummy") apple.tags.add(yummy) def test_slugify(self): a = Article.objects.create(title="django-taggit 1.0 Released") a.tags.add("awesome", "release", "AWESOME") self.assert_tags_equal( a.tags.all(), ["category-awesome", "category-release", "category-awesome-1"], attr="slug", ) def test_integers(self): """Adding an integer as a tag should raise a ValueError (#237).""" apple = self.food_model.objects.create(name="apple") with self.assertRaisesRegex( ValueError, ( r"Cannot add 1 \(<(type|class) 'int'>\). " r"Expected or str." ), ): apple.tags.add(1) def test_gt(self): high = self.tag_model.objects.create(name="high") low = self.tag_model.objects.create(name="Low") self.assertIs(low > high, True) self.assertIs(high > low, False) def test_lt(self): high = self.tag_model.objects.create(name="high") low = self.tag_model.objects.create(name="Low") self.assertIs(high < low, True) self.assertIs(low < high, False) class CustomTagCreationTestCase(TestCase): def test_model_manager_add(self): apple = OfficialFood.objects.create(name="apple") # let's add two official tags apple.tags.add("foo", "bar", tag_kwargs={"official": True}) # and two unofficial ones apple.tags.add("baz", "wow", tag_kwargs={"official": False}) # We should end up with 4 tags self.assertEqual(apple.tags.count(), 4) self.assertEqual(apple.tags.filter(official=True).count(), 2) self.assertEqual(apple.tags.filter(official=False).count(), 2) class TagModelDirectTestCase(TagModelTestCase): food_model = DirectFood tag_model = Tag class TagModelDirectCustomPKTestCase(TagModelTestCase): food_model = DirectCustomPKFood tag_model = Tag class TagModelCustomPKTestCase(TagModelTestCase): food_model = CustomPKFood tag_model = Tag class TagModelOfficialTestCase(TagModelTestCase): food_model = OfficialFood tag_model = OfficialTag class TagUUIDModelTestCase(TagModelTestCase): food_model = UUIDFood tag_model = UUIDTag class TaggableManagerTestCase(BaseTaggingTestCase): food_model = Food multi_inheritance_food_model = MultiInheritanceFood pet_model = Pet housepet_model = HousePet taggeditem_model = TaggedItem tag_model = Tag def setUp(self): super().setUp() ContentType.objects.clear_cache() 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 ) self.assert_tags_equal( self.food_model.tags.most_common(min_count=2), ["green"], 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"]) @mock.patch("django.db.models.signals.m2m_changed.send") def test_add_new_tag_sends_m2m_changed_signals(self, send_mock): apple = self.food_model.objects.create(name="apple") apple.tags.add("green") green_pk = self.tag_model.objects.get(name="green").pk self.assertEqual(send_mock.call_count, 2) send_mock.assert_has_calls( [ mock.call( action="pre_add", instance=apple, model=self.tag_model, pk_set={green_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_add", instance=apple, model=self.tag_model, pk_set={green_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), ] ) @mock.patch("django.db.models.signals.m2m_changed.send") def test_add_existing_tag_sends_m2m_changed_signals(self, send_mock): apple = self.food_model.objects.create(name="apple") green = self.tag_model.objects.create(name="green") apple.tags.add("green") self.assertEqual(send_mock.call_count, 2) send_mock.assert_has_calls( [ mock.call( action="pre_add", instance=apple, model=self.tag_model, pk_set={green.pk}, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_add", instance=apple, model=self.tag_model, pk_set={green.pk}, reverse=False, sender=self.taggeditem_model, using="default", ), ] ) @mock.patch("django.db.models.signals.m2m_changed.send") def test_add_second_tag_sends_m2m_changed_signals_with_correct_new_pks( self, send_mock ): apple = self.food_model.objects.create(name="apple") green = self.tag_model.objects.create(name="green") apple.tags.add("red") send_mock.reset_mock() apple.tags.add("green", "red") self.assertEqual(send_mock.call_count, 2) send_mock.assert_has_calls( [ mock.call( action="pre_add", instance=apple, model=self.tag_model, pk_set={green.pk}, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_add", instance=apple, model=self.tag_model, pk_set={green.pk}, reverse=False, sender=self.taggeditem_model, using="default", ), ] ) @mock.patch("django.db.models.signals.m2m_changed.send") def test_remove_tag_sends_m2m_changed_signals(self, send_mock): apple = self.food_model.objects.create(name="apple") apple.tags.add("green") green_pk = self.tag_model.objects.get(name="green").pk send_mock.reset_mock() apple.tags.remove("green") self.assertEqual(send_mock.call_count, 2) send_mock.assert_has_calls( [ mock.call( action="pre_remove", instance=apple, model=self.tag_model, pk_set={green_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_remove", instance=apple, model=self.tag_model, pk_set={green_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), ] ) @mock.patch("django.db.models.signals.m2m_changed.send") def test_clear_sends_m2m_changed_signal(self, send_mock): apple = self.food_model.objects.create(name="apple") apple.tags.add("red") send_mock.reset_mock() apple.tags.clear() self.assertEqual(send_mock.call_count, 2) send_mock.assert_has_calls( [ mock.call( action="pre_clear", instance=apple, model=self.tag_model, pk_set=None, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_clear", instance=apple, model=self.tag_model, pk_set=None, reverse=False, sender=self.taggeditem_model, using="default", ), ] ) @mock.patch("django.db.models.signals.m2m_changed.send") def test_set_with_clear_true_sends_m2m_changed_signal(self, send_mock): apple = self.food_model.objects.create(name="apple") apple.tags.add("green") apple.tags.add("red") send_mock.reset_mock() apple.tags.set(["red"], clear=True) red_pk = self.tag_model.objects.get(name="red").pk self.assertEqual(send_mock.call_count, 4) send_mock.assert_has_calls( [ mock.call( action="pre_clear", instance=apple, model=self.tag_model, pk_set=None, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_clear", instance=apple, model=self.tag_model, pk_set=None, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="pre_add", instance=apple, model=self.tag_model, pk_set={red_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_add", instance=apple, model=self.tag_model, pk_set={red_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), ] ) @mock.patch("django.db.models.signals.m2m_changed.send") def test_set_sends_m2m_changed_signal(self, send_mock): apple = self.food_model.objects.create(name="apple") apple.tags.add("green") send_mock.reset_mock() apple.tags.set(["red"]) green_pk = self.tag_model.objects.get(name="green").pk red_pk = self.tag_model.objects.get(name="red").pk self.assertEqual(send_mock.call_count, 4) send_mock.assert_has_calls( [ mock.call( action="pre_remove", instance=apple, model=self.tag_model, pk_set={green_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_remove", instance=apple, model=self.tag_model, pk_set={green_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="pre_add", instance=apple, model=self.tag_model, pk_set={red_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), mock.call( action="post_add", instance=apple, model=self.tag_model, pk_set={red_pk}, reverse=False, sender=self.taggeditem_model, using="default", ), ] ) 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. SELECT "taggit_tag"."id", "taggit_tag"."name", "taggit_tag"."slug" FROM "taggit_tag" WHERE "taggit_tag"."name" IN ('green', 'red', 'delicious') # 2. SELECT "taggit_tag"."id", "taggit_tag"."name", "taggit_tag"."slug" FROM "taggit_tag" WHERE "taggit_tag"."name" = 'green' # 3. SAVEPOINT # 4. SAVEPOINT # 5. INSERT INTO "taggit_tag" ("name", "slug") VALUES ('green', 'green') # 6. RELEASE SAVEPOINT # 7. RELEASE SAVEPOINT # 8. SELECT "taggit_tag"."id", "taggit_tag"."name", "taggit_tag"."slug" FROM "taggit_tag" WHERE "taggit_tag"."name" = 'red' # 9. SAVEPOINT # 10. SAVEPOINT # 11. INSERT INTO "taggit_tag" ("name", "slug") VALUES ('red', 'red') # 12. RELEASE SAVEPOINT # 13. RELEASE SAVEPOINT # 14. SELECT "taggit_tag"."id", "taggit_tag"."name", "taggit_tag"."slug" FROM "taggit_tag" WHERE "taggit_tag"."name" = 'delicious' # 15. SAVEPOINT # 16. SAVEPOINT # 17. INSERT INTO "taggit_tag" ("name", "slug") VALUES ('delicious', 'delicious') # 18. RELEASE SAVEPOINT # 19. RELEASE SAVEPOINT # 20. SELECT "taggit_taggeditem"."tag_id" FROM "taggit_taggeditem" WHERE ("taggit_taggeditem"."content_type_id" = 20 AND "taggit_taggeditem"."object_id" = 1) # 21. SELECT "taggit_taggeditem"."id", "taggit_taggeditem"."tag_id", "taggit_taggeditem"."content_type_id", "taggit_taggeditem"."object_id" FROM "taggit_taggeditem" WHERE ("taggit_taggeditem"."content_type_id" = 20 AND "taggit_taggeditem"."object_id" = 1 AND "taggit_taggeditem"."tag_id" = 1) # 22. SAVEPOINT # 23. INSERT INTO "taggit_taggeditem" ("tag_id", "content_type_id", "object_id") VALUES (1, 20, 1) # 24. RELEASE SAVEPOINT # 25. SELECT "taggit_taggeditem"."id", "taggit_taggeditem"."tag_id", "taggit_taggeditem"."content_type_id", "taggit_taggeditem"."object_id" FROM "taggit_taggeditem" WHERE ("taggit_taggeditem"."content_type_id" = 20 AND "taggit_taggeditem"."object_id" = 1 AND "taggit_taggeditem"."tag_id" = 2) # 26. SAVEPOINT # 27. INSERT INTO "taggit_taggeditem" ("tag_id", "content_type_id", "object_id") VALUES (2, 20, 1) # 28. RELEASE SAVEPOINT # 29. SELECT "taggit_taggeditem"."id", "taggit_taggeditem"."tag_id", "taggit_taggeditem"."content_type_id", "taggit_taggeditem"."object_id" FROM "taggit_taggeditem" WHERE ("taggit_taggeditem"."content_type_id" = 20 AND "taggit_taggeditem"."object_id" = 1 AND "taggit_taggeditem"."tag_id" = 3) # 30. SAVEPOINT # 31. INSERT INTO "taggit_taggeditem" ("tag_id", "content_type_id", "object_id") VALUES (3, 20, 1) # 32. RELEASE SAVEPOINT queries = 32 self.assertNumQueries(queries, apple.tags.add, "red", "delicious", "green") pear = self.food_model.objects.create(name="pear") # 1 query to see which tags exist # 1 query to check existing ids for sending m2m_changed signal # + 4 queries to create the intermediary things (including SELECTs, to # make sure we dont't double create. # + 4 for save points. queries = 10 self.assertNumQueries(queries, pear.tags.add, "green", "delicious") # 1 query to check existing ids for sending m2m_changed signal self.assertNumQueries(1, pear.tags.add) def test_require_pk(self): food_instance = self.food_model() msg = ( "%s objects need to have a primary key value before you can access " "their tags." % type(self.food_model()).__name__ ) with self.assertRaisesMessage(ValueError, msg): 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, [f"<{model_name}: kitty>", f"<{model_name}: cat>"], ordered=False, transform=repr, ) def test_exclude(self): apple = self.food_model.objects.create(name="apple") apple.tags.add("red", "green", "delicious") pear = self.food_model.objects.create(name="pear") pear.tags.add("green", "delicious") self.food_model.objects.create(name="guava") pks = self.food_model.objects.exclude(tags__name__in=["red"]) model_name = self.food_model.__name__ self.assertQuerySetEqual( pks, [f"<{model_name}: pear>", f"<{model_name}: guava>"], ordered=False, transform=repr, ) def test_multi_inheritance_similarity_by_tag(self): """Test that pears are more similar to apples than watermelons using multi_inheritance""" apple = self.multi_inheritance_food_model.objects.create(name="apple") apple.tags.add("green", "juicy", "small", "sour") pear = self.multi_inheritance_food_model.objects.create(name="pear") pear.tags.add("green", "juicy", "small", "sweet") watermelon = self.multi_inheritance_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_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" % self.pet_model._meta.model_name: "Spot"} self.assert_tags_equal( self.tag_model.objects.filter(**lookup_kwargs), ["scary"] ) def test_taggeditem_str(self): apple = self.food_model.objects.create(name="apple") apple.tags.add("juicy") self.assertEqual( str(self.taggeditem_model.objects.first()), "apple tagged with juicy" ) def test_taggeditem_through_defaults(self): if self.taggeditem_model != OfficialThroughModel: self.skipTest( "Through default tests are only run when the tagged item model has extra_field" ) apple = self.food_model.objects.create(name="kiwi") apple.tags.add("juicy", through_defaults={"extra_field": "green"}) self.assertEqual( str(self.taggeditem_model.objects.first()), "kiwi tagged with juicy" ) self.assertEqual(self.taggeditem_model.objects.first().extra_field, "green") 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_proxy_subclasses(self): p = Photo.objects.create() proxy_p = ProxyPhoto.objects.create() p.tags.add("outdoors", "pretty") self.assert_tags_equal(p.tags.all(), ["outdoors", "pretty"]) self.assert_tags_equal(proxy_p.tags.all(), []) proxy_p.tags.add("hd") self.assert_tags_equal(proxy_p.tags.all(), ["hd"]) self.assert_tags_equal(p.tags.all(), ["outdoors", "pretty"]) 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, "remote_field")) self.assertTrue(hasattr(field.remote_field, "model")) self.assertEqual(self.food_model, field.model) self.assertEqual(self.tag_model, field.remote_field.model) def test_names_method(self): apple = self.food_model.objects.create(name="apple") apple.tags.add("green") apple.tags.add("red") self.assertEqual(sorted(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(sorted(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): list_prefetched = list( self.food_model.objects.prefetch_related("tags").all() ) with self.assertNumQueries(0): foods = {f.name: {t.name for t in f.tags.all()} for f in list_prefetched} self.assertEqual(foods, {"orange": {"2", "4"}, "apple": {"1", "2"}}) def test_internal_type_is_manytomany(self): self.assertEqual(TaggableManager().get_internal_type(), "ManyToManyField") def test_prefetch_no_extra_join(self): apple = self.food_model.objects.create(name="apple") apple.tags.add("1", "2") with self.assertNumQueries(2): list(self.food_model.objects.prefetch_related("tags").all()) join_clause = 'INNER JOIN "%s"' % self.taggeditem_model._meta.db_table self.assertEqual( connection.queries[-1]["sql"].count(join_clause), 1, connection.queries[-2:], ) @override_settings(TAGGIT_CASE_INSENSITIVE=True) def test_with_case_insensitive_option(self): spain = self.tag_model.objects.create(name="Spain", slug="spain") orange = self.food_model.objects.create(name="orange") orange.tags.add("spain") self.assertEqual(list(orange.tags.all()), [spain]) @override_settings(TAGGIT_CASE_INSENSITIVE=True) def test_with_case_insensitive_option_and_creation(self): orange = self.food_model.objects.create(name="orange") orange.tags.add("spain", "Spain") tag_names = list(orange.tags.names()) self.assertEqual(len(tag_names), 1, tag_names) @override_settings(TAGGIT_CASE_INSENSITIVE=True) def test_with_case_insensitive_option_new_and_old(self): orange = self.food_model.objects.create(name="orange") orange.tags.add("Spain") tag_names = list(orange.tags.names()) self.assertEqual(len(tag_names), 1, tag_names) orange.tags.add("spain", "Valencia") tag_names = sorted(orange.tags.names()) self.assertEqual(tag_names, ["Spain", "Valencia"]) def test_tag_uniqueness(self): apple = self.food_model.objects.create(name="apple") tag = self.tag_model.objects.create(name="juice", slug="juicy") self.taggeditem_model.objects.create(tag=tag, content_object=apple) with self.assertRaises(IntegrityError): self.taggeditem_model.objects.create(tag=tag, content_object=apple) def test_most_common_lazy(self): with self.assertNumQueries(0): qs = self.food_model.tags.most_common() with self.assertNumQueries(1): list(qs) class TaggableManagerDirectTestCase(TaggableManagerTestCase): food_model = DirectFood pet_model = DirectPet housepet_model = DirectHousePet taggeditem_model = TaggedFood class TaggableManagerDirectTrackedTestCase(TaggableManagerTestCase): food_model = DirectTrackedFood pet_model = DirectTrackedPet housepet_model = DirectTrackedHousePet taggeditem_model = TaggedTrackedFood tag_model = TrackedTag class TaggableManagerDirectCustomPKTestCase(TaggableManagerTestCase): food_model = DirectCustomPKFood pet_model = DirectCustomPKPet housepet_model = DirectCustomPKHousePet taggeditem_model = TaggedCustomPKFood def test_require_pk(self): # With a CharField pk, pk is never None. So taggit has no way to tell # if the instance is saved or not. pass class TaggableManagerCustomPKTestCase(TaggableManagerTestCase): food_model = CustomPKFood pet_model = CustomPKPet housepet_model = CustomPKHousePet taggeditem_model = TaggedCustomPK def test_require_pk(self): # With a CharField pk, pk is never None. So taggit has no way to tell # if the instance is saved or not. pass class TaggableManagerUUIDTestCase(TaggableManagerTestCase): food_model = UUIDFood pet_model = UUIDPet housepet_model = UUIDHousePet taggeditem_model = UUIDTaggedItem tag_model = UUIDTag def test_require_pk(self): # With a UUIDField pk, pk is never None. So taggit has no way to tell # if the instance is saved or not. pass class TenantTagTestCase(TestCase): model = TenantModel tag_model = TenantTag def test_tenant_tag(self): tenant_1 = self.model.objects.create(name="tenant 1") tenant_2 = self.model.objects.create(name="tenant 2") # tenant 1 tags tenant_1.tags.add("foo", "bar", tag_kwargs={"tenant_id": 1}) # tenant 2 tags tenant_2.tags.add("foo", "baz", tag_kwargs={"tenant_id": 2}) # We should end up with 4 tags self.assertEqual(self.tag_model.objects.all().count(), 4) self.assertEqual(tenant_1.tags.count(), 2) self.assertEqual(tenant_2.tags.count(), 2) @override_settings(TAGGIT_CASE_INSENSITIVE=True) def test_tenant_tag_insensitive(self): tenant_1 = self.model.objects.create(name="tenant 1") tenant_2 = self.model.objects.create(name="tenant 2") # tenant 1 tags tenant_1.tags.add("foo", "bar", tag_kwargs={"tenant_id": 1}) # tenant 2 tags tenant_2.tags.add("foo", "baz", tag_kwargs={"tenant_id": 2}) # We should end up with 4 tags self.assertEqual(self.tag_model.objects.all().count(), 4) self.assertEqual(tenant_1.tags.count(), 2) self.assertEqual(tenant_2.tags.count(), 2) class TaggableManagerOfficialTestCase(TaggableManagerTestCase): food_model = OfficialFood pet_model = OfficialPet housepet_model = OfficialHousePet taggeditem_model = OfficialThroughModel tag_model = OfficialTag def test_extra_fields(self): self.tag_model.objects.create(name="red") self.tag_model.objects.create(name="delicious", official=True) apple = self.food_model.objects.create(name="apple") apple.tags.add("delicious", "red") pear = self.food_model.objects.create(name="Pear") pear.tags.add("delicious") self.assertEqual(apple, self.food_model.objects.get(tags__official=False)) def test_get_tags_with_count(self): apple = self.food_model.objects.create(name="apple") apple.tags.add("red", "green", "delicious") pear = self.food_model.objects.create(name="pear") pear.tags.add("green", "delicious") tag_info = self.tag_model.objects.filter( officialfood__in=[apple.id, pear.id], name="green" ).annotate(models.Count("name")) self.assertEqual(tag_info[0].name__count, 2) def test_most_common_extra_filters(self): apple = self.food_model.objects.create(name="apple") apple.tags.add("red") apple.tags.add("green") orange = self.food_model.objects.create(name="orange") orange.tags.add("orange") orange.tags.add("red") pear = self.food_model.objects.create(name="pear") pear.tags.add("green") pear.tags.add("yellow") self.assert_tags_equal( self.food_model.tags.most_common( min_count=2, extra_filters={"officialfood__name__in": ["pear", "apple"]} )[:1], ["green"], sort=False, ) self.assert_tags_equal( self.food_model.tags.most_common( min_count=2, extra_filters={"officialfood__name__in": ["orange", "apple"]}, )[:1], ["red"], sort=False, ) class TaggableManagerInitializationTestCase(TaggableManagerTestCase): """Make sure manager override defaults and sets correctly.""" food_model = Food custom_manager_model = CustomManager def test_default_manager(self): self.assertIs(type(self.food_model.tags), _TaggableManager) def test_custom_manager(self): self.assertIs(type(self.custom_manager_model.tags), CustomManager.Foo) class TaggableFormTestCase(BaseTaggingTestCase): form_class = FoodForm food_model = Food def _get_form_str(self, form_str): if DJANGO_VERSION >= (5, 0): # Django defaults to div-based form rendering in 5.0 # https://github.com/django/django/commit/98756c685ee173bbd43f21ed0553f808be835ce5 # https://github.com/django/django/commit/232b60a21b951bd16b8c95b34fcbcbf3ecd89fca form_str %= { "help_start": '
', "help_stop": "
", "required": "required", "aria": 'aria-describedby="id_tags_helptext"', } else: form_str %= { "help_start": '', "help_stop": "", "required": "required", "aria": "", } return form_str def assertFormRenders(self, form, html): rendered_form = form.as_table() if DJANGO_VERSION < (5, 0) else form.as_div() self.assertHTMLEqual(rendered_form, self._get_form_str(html)) def test_form(self): self.assertEqual(list(self.form_class.base_fields), ["name", "tags"]) f = self.form_class({"name": "apple", "tags": "green, red, yummy"}) if DJANGO_VERSION >= (5, 0): self.assertFormRenders( f, """
%(help_start)sA comma-separated list of tags.%(help_stop)s
""", ) else: self.assertFormRenders( 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) if DJANGO_VERSION >= (5, 0): self.assertFormRenders( f, """
%(help_start)sA comma-separated list of tags.%(help_stop)s
""", ) else: self.assertFormRenders( f, """
%(help_start)sA comma-separated list of tags.%(help_stop)s""", ) apple.tags.add("has,comma") f = self.form_class(instance=apple) if DJANGO_VERSION >= (5, 0): self.assertFormRenders( f, """
%(help_start)sA comma-separated list of tags.%(help_stop)s
""", ) else: self.assertFormRenders( f, """
%(help_start)sA comma-separated list of tags.%(help_stop)s""", ) apple.tags.add("has space") f = self.form_class(instance=apple) if DJANGO_VERSION >= (5, 0): self.assertFormRenders( f, """
%(help_start)sA comma-separated list of tags.%(help_stop)s
""", ) else: self.assertFormRenders( 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, "") def test_form_changed_data(self): # new food, blank tag pear = self.food_model() request = {"name": "pear", "tags": ""} fff = self.form_class(request, instance=pear) self.assertFalse(fff.is_valid()) pear = self.food_model() request = {"name": "pear", "tags": "sweat"} fff = self.form_class(request, instance=pear) self.assertTrue(fff.is_valid()) self.assertIn("tags", fff.changed_data) self.assertIn("name", fff.changed_data) fff.save() request = {"name": "pear", "tags": "yellow"} fff = self.form_class(request, instance=pear) self.assertTrue(fff.is_valid()) self.assertIn("tags", fff.changed_data) self.assertNotIn("name", fff.changed_data) fff.save() # same object nothing changed fff = self.form_class(request, instance=pear) self.assertTrue(fff.is_valid()) self.assertFalse(fff.changed_data) # delete tag request = {"name": "pear", "tags": ""} fff = self.form_class(request, instance=pear) self.assertFalse(fff.is_valid()) # tag not blank # change name, tags are the same request = {"name": "apple", "tags": "yellow"} fff = self.form_class(request, instance=pear) self.assertTrue(fff.is_valid()) self.assertIn("name", fff.changed_data) self.assertNotIn("tags", fff.changed_data) fff.save() # tags changed apple = self.food_model.objects.get(name="apple") request = {"name": "apple", "tags": "yellow, delicious"} fff = self.form_class(request, instance=apple) self.assertTrue(fff.is_valid()) self.assertNotIn("name", fff.changed_data) self.assertIn("tags", fff.changed_data) fff.save() # only tags order changed apple = self.food_model.objects.get(name="apple") request = {"name": "apple", "tags": "delicious, yellow"} fff = self.form_class(request, instance=apple) self.assertTrue(fff.is_valid()) self.assertFalse(fff.changed_data) # and nothing changed fff = self.form_class(request, instance=apple) self.assertTrue(fff.is_valid()) self.assertFalse(fff.changed_data) class TaggableFormDirectTestCase(TaggableFormTestCase): form_class = DirectFoodForm food_model = DirectFood class TaggableFormDirectCustomPKTestCase(TaggableFormTestCase): form_class = DirectCustomPKFoodForm food_model = DirectCustomPKFood class TaggableFormCustomPKTestCase(TaggableFormTestCase): form_class = CustomPKFoodForm food_model = CustomPKFood class TaggableFormOfficialTestCase(TaggableFormTestCase): form_class = OfficialFoodForm food_model = OfficialFood class BlankFormTestCase(SimpleTestCase): form_class = BlankTagForm def test_early_access_to_changed_data_with(self): # For example in ModelForm.validate_unique(). request = {"name": "pear"} form = self.form_class(request) self.assertNotIn("tags", form.changed_data) request = {"name": "pear", "tags": ""} form = self.form_class(request) self.assertNotIn("tags", form.changed_data) request = {"name": "pear", "tags": "tag1"} form = self.form_class(request) self.assertIn("tags", form.changed_data) class TagStringParseTestCase(SimpleTestCase): """ 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(name="plain") spaces = Tag(name="spa ces") comma = Tag(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"') @override_settings(TAGGIT_TAGS_FROM_STRING="tests.custom_parser.comma_splitter") def test_custom_comma_splitter(self): self.assertEqual(parse_tags(" Cued Speech "), ["Cued Speech"]) self.assertEqual(parse_tags(" ,Cued Speech, "), ["Cued Speech"]) self.assertEqual(parse_tags("Cued Speech"), ["Cued Speech"]) self.assertEqual( parse_tags("Cued Speech, dictionary"), ["Cued Speech", "dictionary"] ) @override_settings(TAGGIT_STRING_FROM_TAGS="tests.custom_parser.comma_joiner") def test_custom_comma_joiner(self): a = Tag(name="Cued Speech") b = Tag(name="transliterator") self.assertEqual(edit_string_for_tags([a, b]), "Cued Speech, transliterator") class DeconstructTestCase(SimpleTestCase): def test_deconstruct_kwargs_kept(self): instance = TaggableManager(through=OfficialThroughModel, to="dummy.To") name, path, args, kwargs = instance.deconstruct() new_instance = TaggableManager(*args, **kwargs) self.assertEqual( "tests.OfficialThroughModel", new_instance.remote_field.through ) self.assertEqual("dummy.To", new_instance.remote_field.model) class InheritedPrefetchTests(TestCase): def test_inherited_tags_with_prefetch(self): child = Child() child.save() child.tags.add("tag 1", "tag 2", "tag 3", "tag 4") child = Child.objects.get() no_prefetch_tags = child.tags.all() self.assertEqual(4, no_prefetch_tags.count()) child = Child.objects.prefetch_related("tags").get() prefetch_tags = child.tags.all() self.assertEqual(4, prefetch_tags.count()) self.assertEqual( {t.name for t in no_prefetch_tags}, {t.name for t in prefetch_tags} ) class TagListViewTests(TestCase): model = Food def setUp(self): super().setUp() self.factory = RequestFactory() self.slug = "green" self.apple = self.model.objects.create(name="apple") self.apple.tags.add(self.slug) self.strawberry = self.model.objects.create(name="strawberry") self.strawberry.tags.add("red") def test_url_request_returns_view(self): request = self.factory.get(f"/food/tags/{self.slug}/") queryset = self.model.objects.all() response = tagged_object_list(request, self.slug, queryset) self.assertEqual(response.status_code, 200) self.assertIn(self.apple, response.context_data["object_list"]) self.assertNotIn(self.strawberry, response.context_data["object_list"]) self.assertEqual( self.apple.tags.first(), response.context_data["extra_context"]["tag"] ) def test_list_view_returns_single(self): response = self.client.get(f"/food/tags/{self.slug}/") self.assertEqual(response.status_code, 200) self.assertIn(self.apple, response.context_data["object_list"]) self.assertNotIn(self.strawberry, response.context_data["object_list"]) class RelatedNameTests(TestCase): def test_default_related_name(self): food = Food.objects.create(name="apple") food.tags.add("green") tag = Tag.objects.get(food=food.pk) self.assertEqual(tag.name, "green") def test_custom_related_name(self): name = Name.objects.create() name.tags.add("foo") tag = Tag.objects.get(a_unique_related_name=name.pk) self.assertEqual(tag.name, "foo") class OrderedTagsTest(TestCase): def test_added_tags_are_returned_ordered(self): obj = OrderedModel.objects.create() obj.tags.add("green", "red", "orange", "yellow", "blue") self.assertListEqual( ["blue", "green", "orange", "red", "yellow"], list(obj.tags.values_list("name", flat=True)), ) class PendingMigrationsTests(TestCase): def test_taggit_has_no_pending_migrations(self): out = StringIO() call_command("makemigrations", "taggit", dry_run=True, stdout=out) self.assertEqual(out.getvalue().strip(), "No changes detected in app 'taggit'") def test_tests_have_no_pending_migrations(self): out = StringIO() call_command("makemigrations", "tests", dry_run=True, stdout=out) self.assertEqual(out.getvalue().strip(), "No changes detected in app 'tests'") django-taggit-5.0.1/tests/urls.py000066400000000000000000000004461451634545100167510ustar00rootroot00000000000000from django.contrib import admin from django.urls import re_path from .views import FoodTagListView urlpatterns = [ re_path( r"^food/tags/(?P[a-z0-9_-]+)/$", FoodTagListView.as_view(), name="food-tag-list", ), re_path(r"^admin/", admin.site.urls), ] django-taggit-5.0.1/tests/views.py000066400000000000000000000002621451634545100171150ustar00rootroot00000000000000from django.views.generic.list import ListView from taggit.views import TagListMixin from .models import Food class FoodTagListView(TagListMixin, ListView): model = Food django-taggit-5.0.1/tox.ini000066400000000000000000000020121451634545100155520ustar00rootroot00000000000000[tox] minversion = 1.9 envlist = black flake8 isort py{38,39,310,311}-dj{41,42} py{310,311}-djmain docs [gh-actions] python = 3.8: py38, black, flake8, isort 3.9: py39 3.10: py310 3.11: py311 [testenv] deps = dj41: Django>=4.1,<4.2 dj42: Django>=4.2,<5.0 djmain: https://github.com/django/django/archive/main.tar.gz coverage djangorestframework setenv = PYTHONWARNINGS=all commands = coverage run -m django test --settings=tests.settings {posargs} coverage report coverage xml ignore_outcome = djmain: True ignore_errors = djmain: True [testenv:black] basepython = python3 skip_install = true deps = black commands = black --target-version=py38 --check --diff . [testenv:flake8] basepython = python3 skip_install = true deps = flake8 commands = flake8 [testenv:isort] basepython = python3 skip_install = true deps = isort>=5.0.2 commands = isort --check-only --diff . [testenv:docs] deps = sphinx commands = sphinx-build -n -W docs docs/_build