The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .coveragerc
├── .github
    └── workflows
    │   ├── release.yml
    │   └── test.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yml
├── AUTHORS
├── CHANGELOG.rst
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs
    ├── admin.rst
    ├── api.rst
    ├── changelog.rst
    ├── conf.py
    ├── contributing.rst
    ├── custom_tagging.rst
    ├── external_apps.rst
    ├── faq.rst
    ├── forms.rst
    ├── getting_started.rst
    ├── index.rst
    ├── serializers.rst
    └── testing.rst
├── requirements
    ├── docs.txt
    └── test.txt
├── sample_taggit
    ├── __init__.py
    ├── asgi.py
    ├── fixtures
    │   ├── 0001_users.json
    │   ├── 0002_author.json
    │   ├── 0003_book_type.json
    │   ├── 0004_books.json
    │   ├── 0005_tags.json
    │   ├── 0006_condition_tags.json
    │   ├── 0007_tags.json
    │   └── 0008_condition_tagged_item.json
    ├── library_management
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── forms.py
    │   ├── migrations
    │   │   ├── 0001_initial.py
    │   │   └── __init__.py
    │   ├── models.py
    │   ├── templates
    │   │   └── library_management
    │   │   │   ├── author_detail.html
    │   │   │   ├── author_form.html
    │   │   │   ├── author_list.html
    │   │   │   ├── base.html
    │   │   │   ├── book_detail.html
    │   │   │   ├── book_form.html
    │   │   │   ├── book_list.html
    │   │   │   ├── home_page.html
    │   │   │   ├── magazine_list.html
    │   │   │   └── physical_copy_form.html
    │   ├── templatetags
    │   │   ├── __init__.py
    │   │   └── custom_filters.py
    │   ├── urls.py
    │   └── views.py
    ├── make.bat
    ├── makefile
    ├── manage.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
├── setup.cfg
├── setup.py
├── taggit
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── forms.py
    ├── locale
    │   ├── ar
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── cs
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── da
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── de
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── el
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── en
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── eo
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── es
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── fa
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── fi
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── fr
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── he
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── it
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── ja
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── nb
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── nl
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── pt_BR
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── ru
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── tr
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   ├── uk
    │   │   └── LC_MESSAGES
    │   │   │   ├── django.mo
    │   │   │   └── django.po
    │   └── zh_Hans
    │   │   └── LC_MESSAGES
    │   │       ├── django.mo
    │   │       └── django.po
    ├── management
    │   └── commands
    │   │   ├── deduplicate_tags.py
    │   │   └── remove_orphaned_tags.py
    ├── managers.py
    ├── migrations
    │   ├── 0001_initial.py
    │   ├── 0002_auto_20150616_2121.py
    │   ├── 0003_taggeditem_add_unique_index.py
    │   ├── 0004_alter_taggeditem_content_type_alter_taggeditem_tag.py
    │   ├── 0005_auto_20220424_2025.py
    │   ├── 0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx.py
    │   └── __init__.py
    ├── models.py
    ├── serializers.py
    ├── templates
    │   └── admin
    │   │   └── taggit
    │   │       └── merge_tags_form.html
    ├── utils.py
    └── views.py
├── tests
    ├── __init__.py
    ├── admin.py
    ├── custom_parser.py
    ├── forms.py
    ├── migrations
    │   ├── 0001_initial.py
    │   ├── 0002_auto_20200214_1129.py
    │   ├── 0003_auto_20210310_0918.py
    │   ├── 0004_auto_20210619_0826.py
    │   ├── 0005_auto_20210713_2301.py
    │   ├── 0006_auto_20230622_0834.py
    │   ├── 0007_alter_multiinheritancelazyresolutionfoodtag_tag_and_more.py
    │   └── __init__.py
    ├── models.py
    ├── serializers.py
    ├── settings.py
    ├── templates
    │   └── tests
    │   │   └── food_tag_list.html
    ├── test_admin.py
    ├── test_deduplicate_tags.py
    ├── test_forms.py
    ├── test_models.py
    ├── test_remove_orphaned_tags.py
    ├── test_serializers.py
    ├── test_utils.py
    ├── tests.py
    ├── urls.py
    └── views.py
└── tox.ini


/.coveragerc:
--------------------------------------------------------------------------------
 1 | [run]
 2 | branch = True
 3 | source = taggit
 4 | 
 5 | [report]
 6 | exclude_lines =
 7 |     if self.debug:
 8 |     pragma: no cover
 9 |     raise NotImplementedError
10 |     if __name__ == .__main__.:
11 | ignore_errors = True
12 | omit =
13 |     tests/*
14 |     taggit/migrations/*
15 | 


--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
 1 | name: Release
 2 | 
 3 | on:
 4 |   push:
 5 |     tags:
 6 |     - '*'
 7 | 
 8 | jobs:
 9 |   build:
10 |     if: github.repository == 'jazzband/django-taggit'
11 |     runs-on: ubuntu-latest
12 | 
13 |     steps:
14 |       - uses: actions/checkout@v2
15 |         with:
16 |           fetch-depth: 0
17 | 
18 |       - name: Set up Python
19 |         uses: actions/setup-python@v2
20 |         with:
21 |           python-version: 3.9
22 | 
23 |       - name: Install dependencies
24 |         run: |
25 |           python -m pip install -U pip
26 |           python -m pip install -U setuptools twine wheel
27 | 
28 |       - name: Build package
29 |         run: |
30 |           python setup.py --version
31 |           python setup.py sdist --format=gztar bdist_wheel
32 |           twine check dist/*
33 | 
34 |       - name: Upload packages to Jazzband
35 |         if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
36 |         uses: pypa/gh-action-pypi-publish@master
37 |         with:
38 |           user: jazzband
39 |           password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
40 |           repository_url: https://jazzband.co/projects/django-taggit/upload
41 | 


--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
 1 | name: Test
 2 | 
 3 | on: [push, pull_request]
 4 | 
 5 | jobs:
 6 |   build:
 7 |     name: Python ${{ matrix.python-version }}
 8 |     runs-on: ubuntu-latest
 9 | 
10 |     # The maximum number of minutes to let a workflow run
11 |     # before GitHub automatically cancels it. Default: 360
12 |     timeout-minutes: 30
13 | 
14 |     strategy:
15 |       # When set to true, GitHub cancels
16 |       # all in-progress jobs if any matrix job fails.
17 |       fail-fast: false
18 | 
19 |       max-parallel: 5
20 | 
21 |       matrix:
22 |         python-version:
23 |           - "3.9"
24 |           - "3.10"
25 |           - "3.11"
26 |           - "3.12"
27 | 
28 |     steps:
29 |       - uses: actions/checkout@v2
30 | 
31 |       - name: Set up Python ${{ matrix.python-version }}
32 |         uses: actions/setup-python@v2
33 |         with:
34 |           python-version: ${{ matrix.python-version }}
35 | 
36 |       - name: Get pip cache dir
37 |         id: pip-cache
38 |         run: echo "::set-output name=dir::$(pip cache dir)"
39 | 
40 |       - name: Cache
41 |         uses: actions/cache@v2
42 |         with:
43 |           path: ${{ steps.pip-cache.outputs.dir }}
44 |           key: ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }}
45 |           restore-keys: |
46 |             ${{ matrix.python-version }}-v1-
47 | 
48 |       - name: Install Python dependencies
49 |         run: |
50 |           python -m pip install --upgrade pip
51 |           python -m pip install --upgrade tox tox-gh-actions
52 | 
53 |       - name: Tox tests
54 |         run: tox -v
55 | 
56 |       - name: Upload coverage
57 |         uses: codecov/codecov-action@v1
58 |         with:
59 |           name: Python ${{ matrix.python-version }}
60 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
  1 | # Byte-compiled / optimized / DLL files
  2 | __pycache__/
  3 | *.py[cod]
  4 | *$py.class
  5 | 
  6 | # C extensions
  7 | *.so
  8 | 
  9 | # Distribution / packaging
 10 | .Python
 11 | build/
 12 | develop-eggs/
 13 | dist/
 14 | downloads/
 15 | eggs/
 16 | .eggs/
 17 | lib/
 18 | lib64/
 19 | parts/
 20 | sdist/
 21 | var/
 22 | wheels/
 23 | *.egg-info/
 24 | .installed.cfg
 25 | *.egg
 26 | MANIFEST
 27 | 
 28 | # PyInstaller
 29 | #  Usually these files are written by a python script from a template
 30 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 31 | *.manifest
 32 | *.spec
 33 | 
 34 | # Installer logs
 35 | pip-log.txt
 36 | pip-delete-this-directory.txt
 37 | 
 38 | # Unit test / coverage reports
 39 | htmlcov/
 40 | .tox/
 41 | .coverage
 42 | .coverage.*
 43 | .cache
 44 | nosetests.xml
 45 | coverage.xml
 46 | *.cover
 47 | .hypothesis/
 48 | .pytest_cache/
 49 | 
 50 | # Translations
 51 | *.pot
 52 | 
 53 | # Django stuff:
 54 | *.log
 55 | local_settings.py
 56 | db.sqlite3
 57 | 
 58 | # Flask stuff:
 59 | instance/
 60 | .webassets-cache
 61 | 
 62 | # Scrapy stuff:
 63 | .scrapy
 64 | 
 65 | # Sphinx documentation
 66 | docs/_build/
 67 | 
 68 | # PyBuilder
 69 | target/
 70 | 
 71 | # Jupyter Notebook
 72 | .ipynb_checkpoints
 73 | 
 74 | # pyenv
 75 | .python-version
 76 | 
 77 | # celery beat schedule file
 78 | celerybeat-schedule
 79 | 
 80 | # SageMath parsed files
 81 | *.sage.py
 82 | 
 83 | # Environments
 84 | .env
 85 | .venv
 86 | env/
 87 | venv/
 88 | ENV/
 89 | env.bak/
 90 | venv.bak/
 91 | 
 92 | # Spyder project settings
 93 | .spyderproject
 94 | .spyproject
 95 | 
 96 | # Rope project settings
 97 | .ropeproject
 98 | 
 99 | # mkdocs documentation
100 | /site
101 | 
102 | # mypy
103 | .mypy_cache/
104 | 
105 | # PyCharm files
106 | .idea/
107 | 


--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
 1 | repos:
 2 |   - repo: https://github.com/pre-commit/pre-commit-hooks
 3 |     rev: v4.6.0
 4 |     hooks:
 5 |       - id: check-yaml
 6 |       - id: end-of-file-fixer
 7 |       - id: trailing-whitespace
 8 |   - repo: https://github.com/psf/black
 9 |     rev: 24.8.0
10 |     hooks:
11 |       - id: black
12 |   - repo: https://github.com/PyCQA/flake8
13 |     rev: 7.1.1
14 |     hooks:
15 |       - id: flake8
16 |   - repo: https://github.com/asottile/pyupgrade
17 |     rev: v3.17.0
18 |     hooks:
19 |     -   id: pyupgrade
20 |         args: [--py37-plus]
21 |   - repo: https://github.com/adamchainz/django-upgrade
22 |     rev: 1.21.0
23 |     hooks:
24 |     - id: django-upgrade
25 |       args: [--target-version, "3.2"]
26 | 


--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
 1 | version: 2
 2 | build:
 3 |   os: ubuntu-22.04
 4 |   tools:
 5 |     python: "3.9"
 6 | 
 7 | python:
 8 |   install:
 9 |     - method: pip
10 |       path: .
11 | 
12 | sphinx:
13 |   configuration: docs/conf.py
14 | 


--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
 1 | django-taggit was originally created by Alex Gaynor.
 2 | 
 3 | django-taggit-serializer was originally created by Paul Oostenrijk.
 4 | 
 5 | The following is a list of much appreciated contributors:
 6 | 
 7 | Nathan Borror <nathan@playgroundblues.com>
 8 | fakeempire <adam@fakeempire.com>
 9 | Ben Firshman <ben@firshman.co.uk>
10 | Alex Gaynor <alex.gaynor@gmail.com>
11 | Rob Hudson <rob@cogit8.org>
12 | Carl Meyer <carl@oddbird.net>
13 | Frank Wiles
14 | Jonathan Buchanan
15 | idle sign <idlesign@yandex.ru>
16 | Charles Leifer
17 | Florian Apolloner <apollo13@apolloner.eu>
18 | Andrew Pryde <andrew@rocketpod.co.uk>
19 | John Whitlock <jwhitlock@mozilla.com>
20 | Jon Dufresne <jon.dufresne@gmail.com>
21 | Pablo Olmedo Dorado <pablolmedorado@gmail.com>
22 | Steve Reciao <stevenrecio@gmail.com>
23 | 


--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
 1 | # Code of Conduct
 2 | 
 3 | As contributors and maintainers of the Jazzband projects, and in the interest of
 4 | fostering an open and welcoming community, we pledge to respect all people who
 5 | contribute through reporting issues, posting feature requests, updating documentation,
 6 | submitting pull requests or patches, and other activities.
 7 | 
 8 | We are committed to making participation in the Jazzband a harassment-free experience
 9 | for everyone, regardless of the level of experience, gender, gender identity and
10 | expression, sexual orientation, disability, personal appearance, body size, race,
11 | ethnicity, age, religion, or nationality.
12 | 
13 | Examples of unacceptable behavior by participants include:
14 | 
15 | - The use of sexualized language or imagery
16 | - Personal attacks
17 | - Trolling or insulting/derogatory comments
18 | - Public or private harassment
19 | - Publishing other's private information, such as physical or electronic addresses,
20 |   without explicit permission
21 | - Other unethical or unprofessional conduct
22 | 
23 | The Jazzband roadies have the right and responsibility to remove, edit, or reject
24 | comments, commits, code, wiki edits, issues, and other contributions that are not
25 | aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
26 | for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
27 | 
28 | By adopting this Code of Conduct, the roadies commit themselves to fairly and
29 | consistently applying these principles to every aspect of managing the jazzband
30 | projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
31 | removed from the Jazzband roadies.
32 | 
33 | This code of conduct applies both within project spaces and in public spaces when an
34 | individual is representing the project or its community.
35 | 
36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
37 | contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
38 | investigated and will result in a response that is deemed necessary and appropriate to
39 | the circumstances. Roadies are obligated to maintain confidentiality with regard to the
40 | reporter of an incident.
41 | 
42 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
43 | 1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
44 | 
45 | [homepage]: https://contributor-covenant.org
46 | [version]: https://contributor-covenant.org/version/1/3/0/
47 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
  1 | Contributing to django-taggit
  2 | =============================
  3 | 
  4 | .. image:: https://jazzband.co/static/img/jazzband.svg
  5 |    :target: https://jazzband.co/
  6 |    :alt: Jazzband
  7 | 
  8 | This is a `Jazzband <https://jazzband.co>`_ project. By contributing you agree
  9 | to abide by the `Contributor Code of Conduct
 10 | <https://jazzband.co/about/conduct>`_ and follow the `guidelines
 11 | <https://jazzband.co/about/guidelines>`_.
 12 | 
 13 | Thank you for taking the time to contribute to django-taggit.
 14 | 
 15 | Follow these guidelines to speed up the process.
 16 | 
 17 | Reach out before you start
 18 | --------------------------
 19 | 
 20 | Before opening a new issue, look if somebody else has already started working
 21 | on the same issue in the `GitHub issues
 22 | <https://github.com/jazzband/django-taggit/issues>`_ and `pull requests
 23 | <https://github.com/jazzband/django-taggit/pulls>`_.
 24 | 
 25 | Fork the repository
 26 | -------------------
 27 | 
 28 | Once you have forked this repository to your own GitHub account, install your
 29 | own fork in your development environment:
 30 | 
 31 | .. code-block:: console
 32 | 
 33 |     git clone git@github.com:<your_fork>/django-taggit.git
 34 |     cd django-taggit
 35 |     python setup.py develop
 36 | 
 37 | Running tests
 38 | -------------
 39 | 
 40 | django-taggit uses `tox <https://tox.readthedocs.io/>`_ to run tests:
 41 | 
 42 | .. code-block:: console
 43 | 
 44 |     tox
 45 | 
 46 | Running the sample application
 47 | ------------------------------
 48 | 
 49 | There is a sample application in ``sample_taggit``. You can run it by doing the following:
 50 | 
 51 | 
 52 | **Prepare the Database**
 53 | ~~~~~~~~~~~~~~~~~~~~~~~~
 54 | 
 55 | Use the `reset-db` command to prepare your database. This will remove any existing data, run migrations, and load fixtures, including creating a default admin user.
 56 | 
 57 | **On Windows:**
 58 | 
 59 | .. code-block:: console
 60 | 
 61 |      cd sample_taggit
 62 |      call make.bat reset-db
 63 | 
 64 | **On Mac/Linux:**
 65 | 
 66 | .. code-block:: console
 67 | 
 68 |      cd sample_taggit
 69 |      make reset-db
 70 | 
 71 | **Launch the Sample Project**
 72 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 73 | 
 74 | Launch the sample project itself with:
 75 | 
 76 | .. code-block:: console
 77 | 
 78 |      sample_taggit/manage.py runserver
 79 | 
 80 | **Default Admin User Login:**
 81 | 
 82 | Username: taggit
 83 | 
 84 | Password: admin
 85 | 
 86 | Follow style conventions (black, flake8, isort)
 87 | -----------------------------------------------
 88 | 
 89 | Check that your changes are not breaking the style conventions with:
 90 | 
 91 | .. code-block:: console
 92 | 
 93 |     tox -e black,flake8,isort
 94 | 
 95 | Update the documentation
 96 | ------------------------
 97 | 
 98 | If you introduce new features or change existing documented behavior, please
 99 | remember to update the documentation.
100 | 
101 | The documentation is located in the ``docs`` directory of the repository.
102 | 
103 | To do work on the docs, proceed with the following steps:
104 | 
105 | .. code-block:: console
106 | 
107 |     pip install sphinx
108 |     sphinx-build -n -W docs docs/_build
109 | 
110 | Add a changelog line
111 | --------------------
112 | 
113 | Even when the change is minor, a changelog line is helpful to both describe
114 | the intent of the change, and to give a heads up to people upgrading. You can
115 | add a line in the ``(Unreleased)`` section of ``CHANGELOG.rst``, along with
116 | any more detailed explanations for more complicated changes.
117 | 
118 | Send pull request
119 | -----------------
120 | 
121 | It is now time to push your changes to GitHub and open a `pull request
122 | <https://github.com/jazzband/django-taggit/pulls>`_!
123 | 
124 | 
125 | Release Checklist
126 | -----------------
127 | 
128 | These steps need to happen by a release maintainer.
129 | 
130 | To make a release, the following needs to happen:
131 | 
132 | - Make sure that ``setup.cfg`` is set up properly w/r/t Python and Django requirements
133 | - Make sure the documentation (``docs/index.rst``) also describes the right Python/Django versions
134 | - Bump the version number in ``taggit/__init__.py``
135 | - Update the changelog (making sure to add the (Unreleased) section to the top)
136 | - Get those changes onto the ``master`` branch
137 | - Tag the commit with the version number
138 | - CI should then upload a release to be verified through Jazzband
139 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | Copyright (c) Alex Gaynor, Paul Oostenrijk, and individual contributors.
 2 | All rights reserved.
 3 | 
 4 | Redistribution and use in source and binary forms, with or without modification,
 5 | are permitted provided that the following conditions are met:
 6 | 
 7 |     1. Redistributions of source code must retain the above copyright notice,
 8 |        this list of conditions and the following disclaimer.
 9 | 
10 |     2. Redistributions in binary form must reproduce the above copyright
11 |        notice, this list of conditions and the following disclaimer in the
12 |        documentation and/or other materials provided with the distribution.
13 | 
14 |     3. Neither the name of django-taggit nor the names of its contributors
15 |        may be used to endorse or promote products derived from this software
16 |        without specific prior written permission.
17 | 
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | 


--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
 1 | include AUTHORS
 2 | include CHANGELOG.rst
 3 | include LICENSE
 4 | include README.rst
 5 | include setup.cfg
 6 | include setup.py
 7 | recursive-include taggit *.py
 8 | recursive-include taggit/templates *.html
 9 | recursive-include taggit/locale *.mo *.po
10 | prune tests
11 | prune sample_taggit
12 | prune docs
13 | 


--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
 1 | django-taggit
 2 | =============
 3 | 
 4 | .. image:: https://jazzband.co/static/img/badge.svg
 5 |    :target: https://jazzband.co/
 6 |    :alt: Jazzband
 7 | 
 8 | .. image:: https://img.shields.io/pypi/pyversions/django-taggit.svg
 9 |    :target: https://pypi.org/project/django-taggit/
10 |    :alt: Supported Python versions
11 | 
12 | .. image:: https://img.shields.io/pypi/djversions/django-taggit.svg
13 |    :target: https://pypi.org/project/django-taggit/
14 |    :alt: Supported Django versions
15 | 
16 | .. image:: https://github.com/jazzband/django-taggit/workflows/Test/badge.svg
17 |    :target: https://github.com/jazzband/django-taggit/actions
18 |    :alt: GitHub Actions
19 | 
20 | .. image:: https://codecov.io/gh/jazzband/django-taggit/coverage.svg?branch=master
21 |     :target: https://codecov.io/gh/jazzband/django-taggit?branch=master
22 | 
23 | This is a `Jazzband <https://jazzband.co>`_ project. By contributing you agree
24 | to abide by the `Contributor Code of Conduct
25 | <https://jazzband.co/about/conduct>`_ and follow the `guidelines
26 | <https://jazzband.co/about/guidelines>`_.
27 | 
28 | ``django-taggit`` a simpler approach to tagging with Django.  Add ``"taggit"`` to your
29 | ``INSTALLED_APPS`` then just add a TaggableManager to your model and go:
30 | 
31 | .. code:: python
32 | 
33 |     from django.db import models
34 | 
35 |     from taggit.managers import TaggableManager
36 | 
37 | 
38 |     class Food(models.Model):
39 |         # ... fields here
40 | 
41 |         tags = TaggableManager()
42 | 
43 | 
44 | Then you can use the API like so:
45 | 
46 | .. code:: pycon
47 | 
48 |     >>> apple = Food.objects.create(name="apple")
49 |     >>> apple.tags.add("red", "green", "delicious")
50 |     >>> apple.tags.all()
51 |     [<Tag: red>, <Tag: green>, <Tag: delicious>]
52 |     >>> apple.tags.remove("green")
53 |     >>> apple.tags.all()
54 |     [<Tag: red>, <Tag: delicious>]
55 |     >>> Food.objects.filter(tags__name__in=["red"])
56 |     [<Food: apple>, <Food: cherry>]
57 | 
58 | Tags will show up for you automatically in forms and the admin.
59 | 
60 | ``django-taggit`` requires Django 3.2 or greater.
61 | 
62 | For more info check out the `documentation
63 | <https://django-taggit.readthedocs.io/>`_. And for questions about usage or
64 | development you can create an issue on Github (if your question is about
65 | usage please add the `question` tag).
66 | 


--------------------------------------------------------------------------------
/docs/admin.rst:
--------------------------------------------------------------------------------
 1 | Using tags in the admin
 2 | =======================
 3 | 
 4 | By default if you have a :class:`TaggableManager` on your model it will show up
 5 | in the admin, just as it will in any other form.
 6 | 
 7 | If you are specifying :attr:`ModelAdmin.fieldsets
 8 | <django.contrib.admin.ModelAdmin.fieldsets>`, include the name of the
 9 | :class:`TaggableManager` as a field::
10 | 
11 |     fieldsets = (
12 |         (None, {'fields': ('tags',)}),
13 |     )
14 | 
15 | Including tags in ``ModelAdmin.list_display``
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | 
18 | One important thing to note is that you *cannot* include a
19 | :class:`TaggableManager` in :attr:`ModelAdmin.list_display
20 | <django.contrib.admin.ModelAdmin.list_display>`. If you do you'll see an
21 | exception that looks like::
22 | 
23 |     AttributeError: '_TaggableManager' object has no attribute 'name'
24 | 
25 | This is for the same reason that you cannot include a
26 | :class:`~django.db.models.ManyToManyField`: it would result in an unreasonable
27 | number of queries being executed.
28 | 
29 | If you want to show tags in :attr:`ModelAdmin.list_display
30 | <django.contrib.admin.ModelAdmin.list_display>`, you can add a custom display
31 | method to the :class:`~django.contrib.admin.ModelAdmin`, using
32 | :meth:`~django.db.models.query.QuerySet.prefetch_related` to minimize queries::
33 | 
34 |     class MyModelAdmin(admin.ModelAdmin):
35 |         list_display = ['tag_list']
36 | 
37 |         def get_queryset(self, request):
38 |             return super().get_queryset(request).prefetch_related('tags')
39 | 
40 |         def tag_list(self, obj):
41 |             return u", ".join(o.name for o in obj.tags.all())
42 | 
43 | 
44 | Merging tags in the admin
45 | ~~~~~~~~~~~~~~~~~~~~~~~~~
46 | 
47 | Functionality has been added to the admin app to allow for tag "merging". Multiple tags can be selected, and all of their usages will be replaced by the tag that you choose to use.
48 | 
49 | To merge your tags follow these steps:
50 | 
51 | 1. Navigate to the Tags page inside of the Taggit app
52 | 2. Select the tags that you want to merge
53 | 3. Use the dropdown action list and select `Merge selected tags` and then click `Go`
54 | 4. This will redirect you onto a new page where you can insert the new tag name.
55 | 5. Click `Merge Tags`
56 | 6. This will redirect you back to the tag list
57 | 


--------------------------------------------------------------------------------
/docs/api.rst:
--------------------------------------------------------------------------------
  1 | The API
  2 | =======
  3 | 
  4 | After you've got your ``TaggableManager`` added to your model you can start
  5 | playing around with the API.
  6 | 
  7 | .. class:: TaggableManager([verbose_name="Tags", help_text="A comma-separated list of tags.", through=None, blank=False])
  8 | 
  9 |     :param verbose_name: The verbose_name for this field.
 10 |     :param help_text: The help_text to be used in forms (including the admin).
 11 |     :param through: The through model, see :doc:`custom_tagging` for more
 12 |         information.
 13 |     :param blank: Controls whether this field is required.
 14 | 
 15 |     .. method:: add(*tags, through_defaults=None, tag_kwargs=None)
 16 | 
 17 |         This adds tags to an object. The tags can be either ``Tag`` instances, or
 18 |         strings::
 19 | 
 20 |             >>> apple.tags.all()
 21 |             []
 22 |             >>> apple.tags.add("red", "green", "fruit")
 23 | 
 24 |         Use the ``through_defaults`` argument to specify values for your custom
 25 |         ``through`` model, if needed.
 26 | 
 27 |         The ``tag_kwargs`` argument allows one to specify parameters for the tags
 28 |         themselves.
 29 | 
 30 |     .. method:: remove(*tags)
 31 | 
 32 |         Removes a tag from an object. No exception is raised if the object
 33 |         doesn't have that tag.
 34 | 
 35 |     .. method:: clear()
 36 | 
 37 |         Removes all tags from an object.
 38 | 
 39 |     .. method:: set(tags, *, through_defaults=None, clear=False)
 40 | 
 41 |         If ``clear = True`` removes all the current tags and then adds the
 42 |         specified tags to the object. Otherwise sets the object's tags to those
 43 |         specified, removing only the missing tags and adding only the new tags.
 44 | 
 45 |         Use the ``through_defaults`` argument to specify values for your custom
 46 |         ``through`` model, if needed.
 47 | 
 48 |     .. method: most_common()
 49 | 
 50 |         Returns a ``QuerySet`` of all tags, annotated with the number of times
 51 |         they appear, available as the ``num_times`` attribute on each tag. The
 52 |         ``QuerySet``is ordered by ``num_times``, descending.  The ``QuerySet``
 53 |         is lazily evaluated, and can be sliced efficiently.
 54 | 
 55 |         :param min_count: Specify a min count to limit the returned queryset
 56 | 
 57 |     .. method:: similar_objects()
 58 | 
 59 |         Returns a list (not a lazy ``QuerySet``) of other objects tagged
 60 |         similarly to this one, ordered with most similar first. Each object in
 61 |         the list is decorated with a ``similar_tags`` attribute, the number of
 62 |         tags it shares with this object.
 63 | 
 64 |         If the model is using generic tagging (the default), this method
 65 |         searches tagged objects from all classes. If you are querying on a
 66 |         model with its own tagging through table, only other instances of the
 67 |         same model will be returned.
 68 | 
 69 |     .. method:: names()
 70 | 
 71 |         Convenience method, returning a ``ValuesListQuerySet`` (basically
 72 |         just an iterable) containing the name of each tag as a string::
 73 | 
 74 |             >>> apple.tags.names()
 75 |             ["green and juicy", "red"]
 76 | 
 77 |     .. method:: slugs()
 78 | 
 79 |         Convenience method, returning a ``ValuesListQuerySet`` (basically
 80 |         just an iterable) containing the slug of each tag as a string::
 81 | 
 82 |             >>> apple.tags.slugs()
 83 |             ["green-and-juicy", "red"]
 84 | 
 85 |     .. hint::
 86 | 
 87 |        You can subclass ``_TaggableManager`` (note the underscore) to add
 88 |        methods or functionality. ``TaggableManager`` takes an optional
 89 |        manager keyword argument for your custom class, like this::
 90 | 
 91 |           class Food(models.Model):
 92 |               # ... fields here
 93 |               tags = TaggableManager(manager=_CustomTaggableManager)
 94 | 
 95 | Filtering
 96 | ~~~~~~~~~
 97 | 
 98 | To find all of a model with a specific tags you can filter, using the normal
 99 | Django ORM API.  For example if you had a ``Food`` model, whose
100 | ``TaggableManager`` was named ``tags``, you could find all the delicious fruit
101 | like so::
102 | 
103 |     >>> Food.objects.filter(tags__name__in=["delicious"])
104 |     [<Food: apple>, <Food: pear>, <Food: plum>]
105 | 
106 | 
107 | If you're filtering on multiple tags, it's very common to get duplicate
108 | results, because of the way relational databases work.  Often you'll want to
109 | make use of the ``distinct()`` method on ``QuerySets``::
110 | 
111 |     >>> Food.objects.filter(tags__name__in=["delicious", "red"])
112 |     [<Food: apple>, <Food: apple>]
113 |     >>> Food.objects.filter(tags__name__in=["delicious", "red"]).distinct()
114 |     [<Food: apple>]
115 | 
116 | You can also filter by the slug on tags.  If you're using a custom ``Tag``
117 | model you can use this API to filter on any fields it has.
118 | 


--------------------------------------------------------------------------------
/docs/changelog.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../CHANGELOG.rst
2 | 


--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
 1 | import taggit
 2 | 
 3 | extensions = ["sphinx.ext.intersphinx"]
 4 | 
 5 | master_doc = "index"
 6 | 
 7 | project = "django-taggit"
 8 | copyright = "Alex Gaynor and individual contributors."
 9 | 
10 | # The short X.Y version.
11 | version = taggit.__version__
12 | # The full version, including alpha/beta/rc tags.
13 | release = taggit.__version__
14 | 
15 | intersphinx_mapping = {
16 |     "django": (
17 |         "https://docs.djangoproject.com/en/stable",
18 |         "https://docs.djangoproject.com/en/stable/_objects/",
19 |     ),
20 |     "python": ("https://docs.python.org/3", None),
21 | }
22 | 


--------------------------------------------------------------------------------
/docs/contributing.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../CONTRIBUTING.rst
2 | 


--------------------------------------------------------------------------------
/docs/external_apps.rst:
--------------------------------------------------------------------------------
 1 | External Applications
 2 | =====================
 3 | 
 4 | In addition to the features included in ``django-taggit`` directly, there are a
 5 | number of external applications which provide additional features that may be
 6 | of interest.
 7 | 
 8 | .. note::
 9 | 
10 |     Despite their mention here, the following applications are in no way
11 |     official, nor have they in any way been reviewed or tested.
12 | 
13 | If you have an application that you'd like to see listed here, simply fork
14 | `django-taggit on github <https://github.com/jazzband/django-taggit>`_,
15 | add it to this list, and send a pull request.
16 | 
17 |  * `django-taggit-anywhere <https://github.com/bashu/django-taggit-anywhere>`_:
18 |    Simpler approach to tagging with ``taggit``. Additionally this
19 |    project provides easy-to-use integration with ``django-taggit-helpers`` and
20 |    ``django-taggit-labels``.
21 |  * `django-taggit-helpers <https://github.com/mfcovington/django-taggit-helpers>`_:
22 |    Makes it easier to work with admin pages of models
23 |    associated with ``taggit`` tags by adding helper classes: ``TaggitCounter``,
24 |    ``TaggitListFilter``, ``TaggitStackedInline``, ``TaggitTabularInline``.
25 |  * `django-taggit-labels <https://github.com/bennylope/django-taggit-labels>`_:
26 |    Provides a clickable label widget for the
27 |    Django admin for user friendly selection from managed tag sets.
28 |  * `django-taggit-serializer <https://github.com/glemmaPaul/django-taggit-serializer>`_:
29 |    Adds functionality for using ``taggit`` with
30 |    ``django-rest-framework``.
31 |  * `django-taggit-suggest <https://github.com/frankwiles/django-taggit-suggest>`_:
32 |    Provides support for defining keyword and regular
33 |    expression rules for suggesting new tags for content.  This used to be
34 |    available at ``taggit.contrib.suggest``.
35 |  * `django-taggit-templatetags <https://github.com/feuervogel/django-taggit-templatetags>`_:
36 |    Provides several templatetags, including one
37 |    for tag clouds, to expose various ``taggit`` APIs directly to templates.
38 |  * `django-taggit-bulk <https://github.com/nnseva/django-taggit-bulk>`_:
39 |    An admin action for the bulk tagging from the model  admin instance list view.
40 | 


--------------------------------------------------------------------------------
/docs/faq.rst:
--------------------------------------------------------------------------------
 1 | Frequently Asked Questions
 2 | ==========================
 3 | 
 4 | - How can I get all my tags?
 5 | 
 6 |  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.
 7 | 
 8 |  So if you are using the standard setup, ``Tag.objects.all()`` will give you the tags.
 9 | 
10 | - How can I use this with factory_boy?
11 | 
12 |  Since these are all built off of many-to-many relationships, you can check out `factory_boy's documentation on this topic <https://factoryboy.readthedocs.io/en/stable/recipes.html#simple-many-to-many-relationship>`_ and get some ideas on how to deal with tags.
13 | 
14 | 
15 |  One way to handle this is with post-generation hooks::
16 | 
17 |    class ProductFactory(DjangoModelFactory):
18 |         # Rest of the stuff
19 | 
20 |         @post_generation
21 |         def tags(self, create, extracted, **kwargs):
22 |             if not create:
23 |                 return
24 | 
25 |             if extracted:
26 |                 self.tags.add(*extracted)
27 | 


--------------------------------------------------------------------------------
/docs/forms.rst:
--------------------------------------------------------------------------------
 1 | .. _tags-in-forms:
 2 | 
 3 | Tags in forms
 4 | =============
 5 | 
 6 | The ``TaggableManager`` will show up automatically as a field in a
 7 | ``ModelForm`` or in the admin. Tags input via the form field are parsed
 8 | as follows:
 9 | 
10 | * If the input doesn't contain any commas or double quotes, it is simply
11 |   treated as a space-delimited list of tag names.
12 | 
13 | * If the input does contain either of these characters:
14 | 
15 |   * Groups of characters which appear between double quotes take
16 |     precedence as multi-word tags (so double quoted tag names may
17 |     contain commas). An unclosed double quote will be ignored.
18 | 
19 |   * Otherwise, if there are any unquoted commas in the input, it will
20 |     be treated as comma-delimited. If not, it will be treated as
21 |     space-delimited.
22 | 
23 | Examples:
24 | 
25 | ====================== ================================= ================================================
26 | Tag input string       Resulting tags                    Notes
27 | ====================== ================================= ================================================
28 | apple ball cat         ``["apple", "ball", "cat"]``      No commas, so space delimited
29 | apple, ball cat        ``["apple", "ball cat"]``         Comma present, so comma delimited
30 | "apple, ball" cat dog  ``["apple, ball", "cat", "dog"]`` All commas are quoted, so space delimited
31 | "apple, ball", cat dog ``["apple, ball", "cat dog"]``    Contains an unquoted comma, so comma delimited
32 | apple "ball cat" dog   ``["apple", "ball cat", "dog"]``  No commas, so space delimited
33 | "apple" "ball dog      ``["apple", "ball", "dog"]``      Unclosed double quote is ignored
34 | ====================== ================================= ================================================
35 | 
36 | 
37 | ``commit=False``
38 | ~~~~~~~~~~~~~~~~
39 | 
40 | If, when saving a form, you use the ``commit=False`` option you'll need to call
41 | ``save_m2m()`` on the form after you save the object, just as you would for a
42 | form with normal many to many fields on it::
43 | 
44 |     if request.method == "POST":
45 |         form = MyFormClass(request.POST)
46 |         if form.is_valid():
47 |             obj = form.save(commit=False)
48 |             obj.user = request.user
49 |             obj.save()
50 |             # Without this next line the tags won't be saved.
51 |             form.save_m2m()
52 | 
53 | You can check the details over in the `Django documentation on form saving <https://docs.djangoproject.com/en/3.2/topics/forms/modelforms/#the-save-method>`_.
54 | 


--------------------------------------------------------------------------------
/docs/getting_started.rst:
--------------------------------------------------------------------------------
 1 | Getting Started
 2 | ===============
 3 | 
 4 | To get started using ``django-taggit`` simply install it with
 5 | ``pip``::
 6 | 
 7 |     $ pip install django-taggit
 8 | 
 9 | 
10 | Add ``"taggit"`` to your project's ``INSTALLED_APPS`` setting.
11 | 
12 | Run `./manage.py migrate`.
13 | 
14 | And then to any model you want tagging on do the following::
15 | 
16 |     from django.db import models
17 | 
18 |     from taggit.managers import TaggableManager
19 | 
20 |     class Food(models.Model):
21 |         # ... fields here
22 | 
23 |         tags = TaggableManager()
24 | 
25 | .. note::
26 | 
27 |     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)::
28 | 
29 |       TAGGIT_CASE_INSENSITIVE = True
30 | 
31 | 
32 | Settings
33 | --------
34 | 
35 | The following Django-level settings affect the behavior of the library
36 | 
37 | * ``TAGGIT_CASE_INSENSITIVE``
38 | 
39 |   When set to ``True``, tag lookups will be case insensitive. This defaults to ``False``.
40 | 
41 | * ``TAGGIT_STRIP_UNICODE_WHEN_SLUGIFYING``
42 |   When this is set to ``True``, tag slugs will be limited to ASCII characters. In this case, if you also have ``unidecode`` installed,
43 |   then tag sluggification will transform a tag like ``あい うえお`` to ``ai-ueo``.
44 |   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``.
45 | 
46 |   This value defaults to ``False``, meaning that unicode is preserved in slugification.
47 | 
48 |   Because the behavior when ``True`` is set leads to situations where
49 |   slugs can be entirely stripped to an empty string, we recommend not activating this.
50 | 


--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
 1 | Welcome to django-taggit's documentation!
 2 | =========================================
 3 | 
 4 | ``django-taggit`` is a reusable Django application designed to make adding
 5 | tagging to your project easy and fun.
 6 | 
 7 | ``django-taggit`` works with Django 4.1+ and Python 3.9+.
 8 | 
 9 | .. toctree::
10 |    :maxdepth: 2
11 | 
12 |    getting_started
13 |    forms
14 |    admin
15 |    serializers
16 |    testing
17 |    api
18 |    faq
19 |    custom_tagging
20 |    contributing
21 |    external_apps
22 |    changelog
23 | 
24 | Indices and tables
25 | ==================
26 | 
27 | * :ref:`genindex`
28 | * :ref:`modindex`
29 | * :ref:`search`
30 | 


--------------------------------------------------------------------------------
/docs/serializers.rst:
--------------------------------------------------------------------------------
 1 | Usage With Django Rest Framework
 2 | ================================
 3 | 
 4 | 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.
 5 | 
 6 | To accept tags through a `REST` API call we need to add the following to our `Serializer`::
 7 | 
 8 | 
 9 |     from taggit.serializers import (TagListSerializerField,
10 |                                     TaggitSerializer)
11 | 
12 | 
13 |     class YourSerializer(TaggitSerializer, serializers.ModelSerializer):
14 | 
15 |         tags = TagListSerializerField()
16 | 
17 |         class Meta:
18 |             model = YourModel
19 |             fields = '__all__'
20 | 
21 | And you're done, so now you can add tags to your model.
22 | 


--------------------------------------------------------------------------------
/docs/testing.rst:
--------------------------------------------------------------------------------
 1 | Testing
 2 | =======
 3 | 
 4 | Natural Key Support
 5 | -------------------
 6 | We have added `natural key support <https://docs.djangoproject.com/en/5.0/topics/serialization/#natural-keys>`_ to the Tag model in the Django taggit library. This allows you to identify objects by human-readable identifiers rather than by their database ID::
 7 | 
 8 |     python manage.py dumpdata taggit.Tag --natural-foreign --natural-primary > tags.json
 9 | 
10 |     python manage.py loaddata tags.json
11 | 
12 | By default tags use the name field as the natural key.
13 | 
14 | You can customize this in your own custom tag model by setting the ``natural_key_fields`` property on your model the required fields.
15 | 


--------------------------------------------------------------------------------
/requirements/docs.txt:
--------------------------------------------------------------------------------
1 | Sphinx
2 | docutils<0.18
3 | 


--------------------------------------------------------------------------------
/requirements/test.txt:
--------------------------------------------------------------------------------
1 | Django>=3.2
2 | coverage
3 | 


--------------------------------------------------------------------------------
/sample_taggit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/sample_taggit/__init__.py


--------------------------------------------------------------------------------
/sample_taggit/asgi.py:
--------------------------------------------------------------------------------
 1 | """
 2 | ASGI config for sample_taggit project.
 3 | 
 4 | It exposes the ASGI callable as a module-level variable named ``application``.
 5 | 
 6 | For more information on this file, see
 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
 8 | """
 9 | 
10 | import os
11 | 
12 | from django.core.asgi import get_asgi_application
13 | 
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_taggit.settings")
15 | 
16 | application = get_asgi_application()
17 | 


--------------------------------------------------------------------------------
/sample_taggit/fixtures/0001_users.json:
--------------------------------------------------------------------------------
 1 | [
 2 | {
 3 |   "model": "auth.user",
 4 |   "pk": 1,
 5 |   "fields": {
 6 |     "password": "pbkdf2_sha256$720000$KnQUl5xpmqQF2aWaANeHIG$kO4C7iRjRpDfUNwWgvXyCtmfoCQqOHUTHCO2IvrH58U=",
 7 |     "last_login": "2024-07-28T13:26:52.011Z",
 8 |     "is_superuser": true,
 9 |     "username": "taggit",
10 |     "first_name": "",
11 |     "last_name": "",
12 |     "email": "",
13 |     "is_staff": true,
14 |     "is_active": true,
15 |     "date_joined": "2024-07-27T17:02:40.676Z",
16 |     "groups": [],
17 |     "user_permissions": []
18 |   }
19 | }
20 | ]
21 | 


--------------------------------------------------------------------------------
/sample_taggit/fixtures/0003_book_type.json:
--------------------------------------------------------------------------------
 1 | [
 2 | {
 3 |   "model": "library_management.booktype",
 4 |   "pk": 1,
 5 |   "fields": {
 6 |     "slug": "hardcover",
 7 |     "name": "Hardcover"
 8 |   }
 9 | },
10 | {
11 |   "model": "library_management.booktype",
12 |   "pk": 2,
13 |   "fields": {
14 |     "slug": "paperback",
15 |     "name": "Paperback"
16 |   }
17 | },
18 | {
19 |   "model": "library_management.booktype",
20 |   "pk": 3,
21 |   "fields": {
22 |     "slug": "e-book",
23 |     "name": "E-book"
24 |   }
25 | },
26 | {
27 |   "model": "library_management.booktype",
28 |   "pk": 4,
29 |   "fields": {
30 |     "slug": "audiobook",
31 |     "name": "Audiobook"
32 |   }
33 | },
34 | {
35 |   "model": "library_management.booktype",
36 |   "pk": 5,
37 |   "fields": {
38 |     "slug": "magazine",
39 |     "name": "Magazine"
40 |   }
41 | }
42 | ]
43 | 


--------------------------------------------------------------------------------
/sample_taggit/fixtures/0006_condition_tags.json:
--------------------------------------------------------------------------------
  1 | [
  2 | {
  3 |   "model": "library_management.conditiontag",
  4 |   "pk": 1,
  5 |   "fields": {
  6 |     "name": "ripped",
  7 |     "slug": "ripped"
  8 |   }
  9 | },
 10 | {
 11 |   "model": "library_management.conditiontag",
 12 |   "pk": 2,
 13 |   "fields": {
 14 |     "name": "signed",
 15 |     "slug": "signed"
 16 |   }
 17 | },
 18 | {
 19 |   "model": "library_management.conditiontag",
 20 |   "pk": 3,
 21 |   "fields": {
 22 |     "name": "burnt",
 23 |     "slug": "burnt"
 24 |   }
 25 | },
 26 | {
 27 |   "model": "library_management.conditiontag",
 28 |   "pk": 4,
 29 |   "fields": {
 30 |     "name": "Burned",
 31 |     "slug": "burned"
 32 |   }
 33 | },
 34 | {
 35 |   "model": "library_management.conditiontag",
 36 |   "pk": 5,
 37 |   "fields": {
 38 |     "name": "Dog-eared",
 39 |     "slug": "dog-eared"
 40 |   }
 41 | },
 42 | {
 43 |   "model": "library_management.conditiontag",
 44 |   "pk": 6,
 45 |   "fields": {
 46 |     "name": "Fair",
 47 |     "slug": "fair"
 48 |   }
 49 | },
 50 | {
 51 |   "model": "library_management.conditiontag",
 52 |   "pk": 7,
 53 |   "fields": {
 54 |     "name": "Good",
 55 |     "slug": "good"
 56 |   }
 57 | },
 58 | {
 59 |   "model": "library_management.conditiontag",
 60 |   "pk": 8,
 61 |   "fields": {
 62 |     "name": "Highlighted",
 63 |     "slug": "highlighted"
 64 |   }
 65 | },
 66 | {
 67 |   "model": "library_management.conditiontag",
 68 |   "pk": 9,
 69 |   "fields": {
 70 |     "name": "Like New",
 71 |     "slug": "like-new"
 72 |   }
 73 | },
 74 | {
 75 |   "model": "library_management.conditiontag",
 76 |   "pk": 10,
 77 |   "fields": {
 78 |     "name": "Loose Binding",
 79 |     "slug": "loose-binding"
 80 |   }
 81 | },
 82 | {
 83 |   "model": "library_management.conditiontag",
 84 |   "pk": 11,
 85 |   "fields": {
 86 |     "name": "Missing Pages",
 87 |     "slug": "missing-pages"
 88 |   }
 89 | },
 90 | {
 91 |   "model": "library_management.conditiontag",
 92 |   "pk": 12,
 93 |   "fields": {
 94 |     "name": "New",
 95 |     "slug": "new"
 96 |   }
 97 | },
 98 | {
 99 |   "model": "library_management.conditiontag",
100 |   "pk": 13,
101 |   "fields": {
102 |     "name": "Poor",
103 |     "slug": "poor"
104 |   }
105 | },
106 | {
107 |   "model": "library_management.conditiontag",
108 |   "pk": 14,
109 |   "fields": {
110 |     "name": "Repaired",
111 |     "slug": "repaired"
112 |   }
113 | },
114 | {
115 |   "model": "library_management.conditiontag",
116 |   "pk": 15,
117 |   "fields": {
118 |     "name": "Signed",
119 |     "slug": "signed_1"
120 |   }
121 | },
122 | {
123 |   "model": "library_management.conditiontag",
124 |   "pk": 16,
125 |   "fields": {
126 |     "name": "Stained",
127 |     "slug": "stained"
128 |   }
129 | },
130 | {
131 |   "model": "library_management.conditiontag",
132 |   "pk": 17,
133 |   "fields": {
134 |     "name": "Torn",
135 |     "slug": "torn"
136 |   }
137 | },
138 | {
139 |   "model": "library_management.conditiontag",
140 |   "pk": 18,
141 |   "fields": {
142 |     "name": "Water Damaged",
143 |     "slug": "water-damaged"
144 |   }
145 | },
146 | {
147 |   "model": "library_management.conditiontag",
148 |   "pk": 20,
149 |   "fields": {
150 |     "name": "First Edition",
151 |     "slug": "first-edition"
152 |   }
153 | }
154 | ]
155 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/sample_taggit/library_management/__init__.py


--------------------------------------------------------------------------------
/sample_taggit/library_management/admin.py:
--------------------------------------------------------------------------------
 1 | from django.contrib import admin
 2 | from library_management.models import (
 3 |     Author,
 4 |     Book,
 5 |     BookType,
 6 |     ConditionTag,
 7 |     Magazine,
 8 |     PhysicalCopy,
 9 | )
10 | 
11 | admin.site.register(Book)
12 | admin.site.register(Author)
13 | admin.site.register(BookType)
14 | admin.site.register(Magazine)
15 | admin.site.register(PhysicalCopy)
16 | admin.site.register(ConditionTag)
17 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 | 
3 | 
4 | class LibraryManagementConfig(AppConfig):
5 |     default_auto_field = "django.db.models.BigAutoField"
6 |     name = "library_management"
7 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/forms.py:
--------------------------------------------------------------------------------
 1 | from django import forms
 2 | 
 3 | from taggit.models import Tag
 4 | 
 5 | from .models import Author, Book, ConditionTag, PhysicalCopy
 6 | 
 7 | 
 8 | class PhysicalCopyForm(forms.ModelForm):
 9 |     condition_tags = forms.ModelMultipleChoiceField(
10 |         queryset=ConditionTag.objects.all(),
11 |         widget=forms.CheckboxSelectMultiple,
12 |         required=False,
13 |     )
14 | 
15 |     class Meta:
16 |         model = PhysicalCopy
17 |         fields = ["condition_tags"]
18 | 
19 | 
20 | class AuthorForm(forms.ModelForm):
21 |     tags = forms.ModelMultipleChoiceField(
22 |         queryset=Tag.objects.all(), widget=forms.CheckboxSelectMultiple, required=False
23 |     )
24 | 
25 |     def __init__(self, *args, **kwargs):
26 |         super().__init__(*args, **kwargs)
27 |         self.fields["tags"].queryset = self.fields["tags"].queryset.order_by("name")
28 | 
29 |     class Meta:
30 |         model = Author
31 |         fields = [
32 |             "first_name",
33 |             "last_name",
34 |             "middle_name",
35 |             "birth_date",
36 |             "biography",
37 |             "tags",
38 |         ]
39 | 
40 | 
41 | class BookForm(forms.ModelForm):
42 |     tags = forms.ModelMultipleChoiceField(
43 |         queryset=Tag.objects.all(), widget=forms.CheckboxSelectMultiple, required=False
44 |     )
45 | 
46 |     def __init__(self, *args, **kwargs):
47 |         super().__init__(*args, **kwargs)
48 |         self.fields["tags"].queryset = self.fields["tags"].queryset.order_by("name")
49 | 
50 |     class Meta:
51 |         model = Book
52 |         fields = ["name", "author", "published_date", "isbn", "summary", "tags"]
53 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/sample_taggit/library_management/migrations/__init__.py


--------------------------------------------------------------------------------
/sample_taggit/library_management/models.py:
--------------------------------------------------------------------------------
  1 | import uuid
  2 | 
  3 | from django.db import models
  4 | from django.urls import reverse
  5 | 
  6 | from taggit.managers import TaggableManager
  7 | from taggit.models import TagBase, TaggedItemBase
  8 | 
  9 | 
 10 | class BookTypeChoices(models.TextChoices):
 11 |     HARDCOVER = "HC", "Hardcover"
 12 |     PAPERBACK = "PB", "Paperback"
 13 |     EBOOK = "EB", "E-book"
 14 |     AUDIOBOOK = "AB", "Audiobook"
 15 |     MAGAZINE = "MG", "Magazine"
 16 | 
 17 | 
 18 | class BookType(TagBase):
 19 |     name = models.CharField(max_length=255, unique=True)
 20 | 
 21 |     class Meta:
 22 |         verbose_name = "Book Type"
 23 |         verbose_name_plural = "Book Types"
 24 | 
 25 | 
 26 | class Book(models.Model):
 27 |     name = models.CharField(max_length=255)
 28 |     author = models.ForeignKey("Author", on_delete=models.CASCADE)
 29 |     published_date = models.DateField(null=True)
 30 |     isbn = models.CharField(max_length=17, unique=True)
 31 |     summary = models.TextField()
 32 |     tags = TaggableManager()
 33 | 
 34 |     @property
 35 |     def title(self):
 36 |         return self.name
 37 | 
 38 |     def __str__(self):
 39 |         return self.title
 40 | 
 41 |     class Meta:
 42 |         verbose_name = "Book"
 43 |         verbose_name_plural = "Books"
 44 | 
 45 | 
 46 | class MagazineManager(models.Manager):
 47 |     def get_queryset(self):
 48 |         return (
 49 |             super()
 50 |             .get_queryset()
 51 |             .filter(
 52 |                 physical_copies__book_type__name="Magazine",
 53 |                 physical_copies__isnull=False,
 54 |             )
 55 |             .distinct()
 56 |         )
 57 | 
 58 | 
 59 | class Magazine(Book):
 60 |     objects = MagazineManager()
 61 | 
 62 |     class Meta:
 63 |         proxy = True
 64 | 
 65 |     @property
 66 |     def title(self):
 67 |         return f"{self.name} Edition: {self.published_date.strftime('%B %Y')}"
 68 | 
 69 | 
 70 | class Author(models.Model):
 71 |     first_name = models.CharField(max_length=255, blank=True)
 72 |     last_name = models.CharField(max_length=255, blank=True)
 73 |     middle_name = models.CharField(max_length=255, blank=True)
 74 |     birth_date = models.DateField()
 75 |     biography = models.TextField()
 76 |     tags = TaggableManager()
 77 | 
 78 |     @property
 79 |     def full_name(self):
 80 |         return f"{self.first_name} {self.last_name}"
 81 | 
 82 |     def get_absolute_url(self):
 83 |         return reverse("author-detail", kwargs={"pk": self.pk})
 84 | 
 85 |     def __str__(self):
 86 |         return self.full_name
 87 | 
 88 | 
 89 | class ConditionTag(TagBase):
 90 |     class Meta:
 91 |         verbose_name = "Condition Tag"
 92 |         verbose_name_plural = "Condition Tags"
 93 |         ordering = ["name"]
 94 | 
 95 | 
 96 | class ConditionTaggedItem(TaggedItemBase):
 97 |     tag = models.ForeignKey(
 98 |         ConditionTag, related_name="tagged_items", on_delete=models.CASCADE
 99 |     )
100 |     content_object = models.ForeignKey("PhysicalCopy", on_delete=models.CASCADE)
101 | 
102 |     class Meta:
103 |         verbose_name = "Condition Tagged Item"
104 |         verbose_name_plural = "Condition Tagged Items"
105 | 
106 | 
107 | class PhysicalCopy(models.Model):
108 |     book = models.ForeignKey(
109 |         Book, on_delete=models.CASCADE, related_name="physical_copies"
110 |     )
111 |     barcode = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
112 |     book_type = models.ForeignKey(BookType, on_delete=models.CASCADE)
113 |     condition_tags = TaggableManager(through=ConditionTaggedItem, blank=True)
114 | 
115 |     def __str__(self):
116 |         return f"{self.book.name} - {self.barcode}"
117 | 
118 |     class Meta:
119 |         verbose_name = "Physical Copy"
120 |         verbose_name_plural = "Physical Copies"
121 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/author_detail.html:
--------------------------------------------------------------------------------
 1 | {% extends "library_management/base.html" %}
 2 | {% load custom_filters %}
 3 | 
 4 | {% block title %}Author Detail{% endblock %}
 5 | 
 6 | {% block content %}
 7 | <h1 class="my-4">{{ author.full_name }}</h1>
 8 | <p><strong>Birth Date:</strong> {{ author.birth_date }}</p>
 9 | <p>{{ author.biography }}</p>
10 | <h2>Tags</h2>
11 | <ul>
12 |     {% for tag in author.tags.all|order_tags %}
13 |     <li>{{ tag.name }}</li>
14 |     {% endfor %}
15 | </ul>
16 | <h2>Books</h2>
17 | <ul>
18 |     {% for book in author.book_set.all %}
19 |     <li><a href="{% url 'book-detail' book.pk %}">{{ book.name }}</a></li>
20 |     {% endfor %}
21 | </ul>
22 | <a href="{% url 'author-list' %}" class="btn btn-secondary">Back to list</a>
23 | <a href="{% url 'author-update' author.pk %}" class="btn btn-primary">Edit Author</a>
24 | {% endblock %}
25 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/author_form.html:
--------------------------------------------------------------------------------
 1 | {% extends "library_management/base.html" %}
 2 | 
 3 | {% block title %}Edit Author{% endblock %}
 4 | 
 5 | {% block content %}
 6 | <h1 class="my-4">Edit Author</h1>
 7 | <form method="post">
 8 |     {% csrf_token %}
 9 |     {{ form.as_p }}
10 |     <button type="submit" class="btn btn-primary">Save changes</button>
11 |     <a href="{% url 'author-detail' author.pk %}" class="btn btn-secondary">Cancel</a>
12 | </form>
13 | {% endblock %}
14 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/author_list.html:
--------------------------------------------------------------------------------
 1 | {% extends "library_management/base.html" %}
 2 | 
 3 | {% block title %}Authors{% endblock %}
 4 | 
 5 | {% block content %}
 6 | <h1 class="my-4">Authors</h1>
 7 | <ul>
 8 |     {% for author in authors %}
 9 |     <li><a href="{{ author.get_absolute_url }}">{{ author.full_name }}</a></li>
10 |     {% endfor %}
11 | </ul>
12 | {% endblock %}
13 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/base.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 | <head>
 4 |     <meta charset="UTF-8">
 5 |     <title>{% block title %}Library Management{% endblock %}</title>
 6 |     <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
 7 |     {% block extra_css %}{% endblock %}
 8 |     <style>
 9 |         .navbar {
10 |             background-color: #343a40;
11 |         }
12 |         .navbar-brand, .nav-link {
13 |             color: #ffffff !important;
14 |         }
15 |         .nav-link:hover {
16 |             color: #d4d4d4 !important;
17 |         }
18 |     </style>
19 | 
20 | 
21 | </head>
22 | <body>
23 |     <nav class="navbar navbar-expand-lg">
24 |     <a class="navbar-brand" href="{% url 'home_page' %}">Library Management</a>
25 |     <div class="collapse navbar-collapse">
26 |         <ul class="navbar-nav mr-auto">
27 |             <li class="nav-item">
28 |                 <a class="nav-link" href="{% url 'book-list' %}">Books</a>
29 |             </li>
30 |             <li class="nav-item">
31 |                 <a class="nav-link" href="{% url 'author-list' %}">Authors</a>
32 |             </li>
33 |             <li class="nav-item">
34 |                 <a class="nav-link" href="{% url 'magazine-list' %}">Magazines</a>
35 |             </li>
36 |             <li class="nav-item">
37 |                 <a class="nav-link" href="{% url 'admin:index' %}">Admin</a>
38 |             </li>
39 |         </ul>
40 |     </div>
41 | </nav>
42 |     <div class="container mt-4">
43 |         {% block content %}{% endblock %}
44 |     </div>
45 |     {% block extra_js %}{% endblock %}
46 | </body>
47 | </html>
48 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/book_detail.html:
--------------------------------------------------------------------------------
 1 | {% extends "library_management/base.html" %}
 2 | {% load custom_filters %}
 3 | 
 4 | 
 5 | {% block title %}Book Detail{% endblock %}
 6 | 
 7 | {% block content %}
 8 | <h1 class="my-4">{{ book }}</h1>
 9 | <p><strong>Author:</strong> <a href="{{ book.author.get_absolute_url }}">{{ book.author.full_name }}</a></p>
10 | <p><strong>Published Date:</strong> {{ book.published_date }}</p>
11 | <p><strong>ISBN:</strong> {{ book.isbn }}</p>
12 | <p><strong>Summary:</strong> {{ book.summary }}</p>
13 | <p><strong>Tags:</strong> {{ book.tags.all|join:", " }}</p>
14 | 
15 | <h2 class="my-4">Physical Copies</h2>
16 | <ul class="list-group">
17 |     {% for copy in book.physical_copies.all %}
18 |     <li class="list-group-item">
19 |         <strong>Type:</strong> {{ copy.book_type.name }}<br>
20 |         <strong>Condition:</strong> {{ copy.condition_tags.all|join:", " }}<br>
21 |         <a href="{% url 'physical-copy-update' copy.pk %}" class="btn btn-primary">Update Condition</a>
22 |     </li>
23 |     {% endfor %}
24 | </ul>
25 | 
26 | <a href="{% url 'book-list' %}" class="btn btn-secondary">Back to list</a>
27 | {% if book|classname == 'Book' %}
28 | <a href="{% url 'book-update' book.pk %}" class="btn btn-primary">Edit</a>
29 | {% elif book|classname == 'Magazine' %}
30 | <a href="{% url 'magazine-update' book.pk %}" class="btn btn-primary">Edit</a>
31 | {% endif %}
32 | {% endblock %}
33 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/book_form.html:
--------------------------------------------------------------------------------
 1 | {% extends "library_management/base.html" %}
 2 | 
 3 | {% block title %}Edit Book{% endblock %}
 4 | 
 5 | {% block content %}
 6 | <h1 class="my-4">Edit Book</h1>
 7 | <form method="post">
 8 |     {% csrf_token %}
 9 |     {{ form.as_p }}
10 |     <button type="submit" class="btn btn-primary">Save changes</button>
11 |     <a href="{% url 'book-detail' book.pk %}" class="btn btn-secondary">Cancel</a>
12 | </form>
13 | {% endblock %}
14 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/book_list.html:
--------------------------------------------------------------------------------
 1 | {% extends "library_management/base.html" %}
 2 | 
 3 | {% block title %}Book List{% endblock %}
 4 | 
 5 | {% block content %}
 6 | <h1 class="my-4">Book List</h1>
 7 | <ul class="list-group">
 8 |     {% for book in page_obj %}
 9 |         <li class="list-group-item">
10 |             <a href="{% url 'book-detail' book.pk %}">{{ book.name }}</a> by {{ book.author }}
11 |         </li>
12 |     {% endfor %}
13 | </ul>
14 | 
15 | <nav aria-label="Page navigation">
16 |     <ul class="pagination">
17 |         {% if page_obj.has_previous %}
18 |             <li class="page-item">
19 |                 <a class="page-link" href="?page=1" aria-label="First">
20 |                     <span aria-hidden="true">&laquo;&laquo;</span>
21 |                 </a>
22 |             </li>
23 |             <li class="page-item">
24 |                 <a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
25 |                     <span aria-hidden="true">&laquo;</span>
26 |                 </a>
27 |             </li>
28 |         {% endif %}
29 |         {% for num in page_obj.paginator.page_range %}
30 |             {% if page_obj.number == num %}
31 |                 <li class="page-item active"><span class="page-link">{{ num }}</span></li>
32 |             {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
33 |                 <li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
34 |             {% endif %}
35 |         {% endfor %}
36 |         {% if page_obj.has_next %}
37 |             <li class="page-item">
38 |                 <a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
39 |                     <span aria-hidden="true">&raquo;</span>
40 |                 </a>
41 |             </li>
42 |             <li class="page-item">
43 |                 <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
44 |                     <span aria-hidden="true">&raquo;&raquo;</span>
45 |                 </a>
46 |             </li>
47 |         {% endif %}
48 |     </ul>
49 | </nav>
50 | {% endblock %}
51 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/home_page.html:
--------------------------------------------------------------------------------
 1 | {% extends 'library_management/base.html' %}
 2 | 
 3 | {% block content %}
 4 | <h1 class="my-4">Library Management Home</h1>
 5 | 
 6 | <div class="row">
 7 |     <div class="col-md-4">
 8 |         <div class="card mb-4">
 9 |             <div class="card-body">
10 |                 <h5 class="card-title">Number of Books</h5>
11 |                 <p class="card-text"><strong>{{ num_books }}</strong></p>
12 |             </div>
13 |         </div>
14 |     </div>
15 |     <div class="col-md-4">
16 |         <div class="card mb-4">
17 |             <div class="card-body">
18 |                 <h5 class="card-title">Genres Represented</h5>
19 |                 <ul class="list-group list-group-flush">
20 |                     {% for genre in genres %}
21 |                         <li class="list-group-item">{{ genre.name }}: <strong>{{ genre.count }}</strong></li>
22 |                     {% endfor %}
23 |                 </ul>
24 |             </div>
25 |         </div>
26 |     </div>
27 |     <div class="col-md-4">
28 |         <div class="card mb-4">
29 |             <div class="card-body">
30 |                 <h5 class="card-title">Condition of Physical Books</h5>
31 |                 <ul class="list-group list-group-flush">
32 |                     {% for condition in condition_stats %}
33 |                         <li class="list-group-item">{{ condition.condition_tags__name }}: <strong>{{ condition.count }}</strong></li>
34 |                     {% endfor %}
35 |                 </ul>
36 |             </div>
37 |         </div>
38 |     </div>
39 | </div>
40 | 
41 | 
42 | {% endblock %}
43 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/magazine_list.html:
--------------------------------------------------------------------------------
 1 | {% extends "library_management/base.html" %}
 2 | 
 3 | {% block title %}Magazine List{% endblock %}
 4 | 
 5 | {% block content %}
 6 | <h1 class="my-4">Magazines</h1>
 7 | <ul class="list-group">
 8 |     {% for magazine in magazines %}
 9 |         <li class="list-group-item">
10 |             <a href="{% url 'magazine-detail' magazine.pk %}">{{ magazine.name }}</a>
11 |         </li>
12 |     {% endfor %}
13 | </ul>
14 | {% endblock %}
15 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templates/library_management/physical_copy_form.html:
--------------------------------------------------------------------------------
 1 | {% extends "library_management/base.html" %}
 2 | 
 3 | {% block title %}Update Physical Copy Condition{% endblock %}
 4 | 
 5 | {% block content %}
 6 | <h1>Update Physical Copy Condition</h1>
 7 | <form method="post">
 8 |     {% csrf_token %}
 9 |     {{ form.as_p }}
10 |     <div class="button-group">
11 |         <button type="submit" class="btn btn-primary">Save</button>
12 |         <a href="{% url 'book-detail' object.book.pk %}" class="btn btn-secondary">Cancel</a>
13 |     </div>
14 | </form>
15 | {% endblock %}
16 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/sample_taggit/library_management/templatetags/__init__.py


--------------------------------------------------------------------------------
/sample_taggit/library_management/templatetags/custom_filters.py:
--------------------------------------------------------------------------------
 1 | from django import template
 2 | 
 3 | register = template.Library()
 4 | 
 5 | 
 6 | @register.filter
 7 | def classname(obj):
 8 |     return obj.__class__.__name__
 9 | 
10 | 
11 | @register.filter
12 | def order_tags(tags):
13 |     return tags.order_by("name")
14 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/urls.py:
--------------------------------------------------------------------------------
 1 | from django.urls import path
 2 | 
 3 | from .views import (
 4 |     AuthorDetailView,
 5 |     AuthorListView,
 6 |     AuthorUpdateView,
 7 |     BookCreateView,
 8 |     BookDetailView,
 9 |     BookListView,
10 |     BookUpdateView,
11 |     MagazineCreateView,
12 |     MagazineDetailView,
13 |     MagazineListView,
14 |     MagazineUpdateView,
15 |     PhysicalCopyUpdateView,
16 |     home_page,
17 | )
18 | 
19 | urlpatterns = [
20 |     path("", home_page, name="home_page"),
21 |     path("book_list", BookListView.as_view(), name="book-list"),
22 |     path("book/<int:pk>/", BookDetailView.as_view(), name="book-detail"),
23 |     path("book/new/", BookCreateView.as_view(), name="book-create"),
24 |     path("book/<int:pk>/edit/", BookUpdateView.as_view(), name="book-update"),
25 |     path("authors/", AuthorListView.as_view(), name="author-list"),
26 |     path("authors/<int:pk>/", AuthorDetailView.as_view(), name="author-detail"),
27 |     path("authors/<int:pk>/edit/", AuthorUpdateView.as_view(), name="author-update"),
28 |     path("magazines/", MagazineListView.as_view(), name="magazine-list"),
29 |     path("magazine/<int:pk>/", MagazineDetailView.as_view(), name="magazine-detail"),
30 |     path("magazine/new/", MagazineCreateView.as_view(), name="magazine-create"),
31 |     path(
32 |         "magazine/<int:pk>/edit/", MagazineUpdateView.as_view(), name="magazine-update"
33 |     ),
34 |     path(
35 |         "physical_copy/<int:pk>/edit/",
36 |         PhysicalCopyUpdateView.as_view(),
37 |         name="physical-copy-update",
38 |     ),
39 | ]
40 | 


--------------------------------------------------------------------------------
/sample_taggit/library_management/views.py:
--------------------------------------------------------------------------------
  1 | from django.db.models import Count
  2 | from django.db.models.functions import Lower
  3 | from django.shortcuts import render
  4 | from django.urls import reverse, reverse_lazy
  5 | from django.views.generic import CreateView, DetailView, ListView, UpdateView
  6 | 
  7 | from .forms import AuthorForm, BookForm, PhysicalCopyForm
  8 | from .models import Author, Book, Magazine, PhysicalCopy
  9 | 
 10 | 
 11 | def home_page(request):
 12 |     # Number of books
 13 |     num_books = Book.objects.count()
 14 | 
 15 |     # Different genres represented
 16 |     genres = Book.tags.values("name").annotate(count=Count("name")).order_by("-count")
 17 | 
 18 |     # Condition of physical books
 19 |     condition_stats = (
 20 |         PhysicalCopy.objects.values("condition_tags__name")
 21 |         .annotate(count=Count("condition_tags"))
 22 |         .order_by("-count")
 23 |     )
 24 | 
 25 |     context = {
 26 |         "num_books": num_books,
 27 |         "genres": genres,
 28 |         "condition_stats": condition_stats,
 29 |     }
 30 | 
 31 |     return render(request, "library_management/home_page.html", context)
 32 | 
 33 | 
 34 | class BookListView(ListView):
 35 |     model = Book
 36 |     template_name = "library_management/book_list.html"
 37 |     context_object_name = "books"
 38 |     paginate_by = 20
 39 | 
 40 |     def get_ordering(self):
 41 |         ordering = self.request.GET.get("ordering", "name")
 42 |         if ordering == "name":
 43 |             return [Lower("name")]
 44 |         return [ordering]
 45 | 
 46 | 
 47 | class BookDetailView(DetailView):
 48 |     model = Book
 49 |     template_name = "library_management/book_detail.html"
 50 |     context_object_name = "book"
 51 | 
 52 | 
 53 | class BookCreateView(CreateView):
 54 |     model = Book
 55 |     template_name = "library_management/book_form.html"
 56 |     fields = ["name", "author", "published_date", "isbn", "summary", "tags"]
 57 |     success_url = reverse_lazy("book-list")
 58 | 
 59 | 
 60 | class BookUpdateView(UpdateView):
 61 |     model = Book
 62 |     form_class = BookForm
 63 |     template_name = "library_management/book_form.html"
 64 | 
 65 |     def get_success_url(self):
 66 |         return reverse("book-detail", kwargs={"pk": self.object.pk})
 67 | 
 68 | 
 69 | class AuthorListView(ListView):
 70 |     model = Author
 71 |     template_name = "library_management/author_list.html"
 72 |     context_object_name = "authors"
 73 |     ordering = ["last_name", "first_name"]
 74 | 
 75 | 
 76 | class AuthorDetailView(DetailView):
 77 |     model = Author
 78 |     template_name = "library_management/author_detail.html"
 79 |     context_object_name = "author"
 80 | 
 81 | 
 82 | class AuthorUpdateView(UpdateView):
 83 |     model = Author
 84 |     form_class = AuthorForm
 85 |     template_name = "library_management/author_form.html"
 86 |     context_object_name = "author"
 87 | 
 88 | 
 89 | class MagazineListView(BookListView):
 90 |     model = Magazine
 91 |     template_name = "library_management/magazine_list.html"
 92 |     context_object_name = "magazines"
 93 | 
 94 | 
 95 | class MagazineDetailView(BookDetailView):
 96 |     model = Magazine
 97 |     template_name = "library_management/book_detail.html"
 98 |     context_object_name = "book"
 99 | 
100 | 
101 | class MagazineCreateView(BookCreateView):
102 |     model = Magazine
103 |     template_name = "library_management/book_form.html"
104 |     success_url = reverse_lazy("magazine-list")
105 | 
106 | 
107 | class MagazineUpdateView(BookUpdateView):
108 |     model = Magazine
109 |     form_class = BookForm
110 |     template_name = "library_management/book_form.html"
111 |     context_object_name = "book"
112 | 
113 |     def get_success_url(self):
114 |         return reverse("magazine-detail", kwargs={"pk": self.object.pk})
115 | 
116 | 
117 | class PhysicalCopyUpdateView(UpdateView):
118 |     model = PhysicalCopy
119 |     form_class = PhysicalCopyForm
120 |     template_name = "library_management/physical_copy_form.html"
121 | 
122 |     def get_success_url(self):
123 |         return reverse("book-detail", kwargs={"pk": self.object.book.pk})
124 | 


--------------------------------------------------------------------------------
/sample_taggit/make.bat:
--------------------------------------------------------------------------------
 1 | :: make.bat
 2 | 
 3 | @echo off
 4 | 
 5 | set PYTHON=python
 6 | if not "%2" == "" (
 7 |     set PYTHON=%2
 8 | )
 9 | 
10 | if "%1" == "reset-db" (
11 |     %PYTHON% manage.py migrate library_management zero
12 |     %PYTHON% manage.py migrate taggit zero
13 |     %PYTHON% manage.py migrate
14 |     for %%f in (fixtures\*.json) do (
15 |         %PYTHON% manage.py loaddata %%f
16 |     )
17 | ) else if "%1" == "export-data" (
18 |     %PYTHON% manage.py dumpdata --indent 2 auth.User --output=fixtures\0001_users.json
19 |     %PYTHON% manage.py dumpdata --indent 2 library_management.Author --output=fixtures\0002_author.json
20 |     %PYTHON% manage.py dumpdata --indent 2 library_management.BookType --output=fixtures\0003_book_type.json
21 |     %PYTHON% manage.py dumpdata --indent 2 library_management.Book --output=fixtures\0004_books.json
22 |     %PYTHON% manage.py dumpdata --indent 2 taggit.Tag --output=fixtures\0005_tags.json
23 |     %PYTHON% manage.py dumpdata --indent 2 library_management.ConditionTag --output=fixtures\0006_condition_tags.json
24 |     %PYTHON% manage.py dumpdata --indent 2 library_management.PhysicalCopy --output=fixtures\0007_tags.json
25 |     %PYTHON% manage.py dumpdata --indent 2 library_management.ConditionTaggedItem --output=fixtures\0008_condition_tagged_item.json
26 | ) else if "%1" == "loaddata" (
27 |     for %%f in (fixtures\*.json) do (
28 |         %PYTHON% manage.py loaddata %%f
29 |     )
30 | ) else (
31 |     echo Invalid command. Use 'reset-db', 'export', or 'loaddata'.
32 | )
33 | 


--------------------------------------------------------------------------------
/sample_taggit/makefile:
--------------------------------------------------------------------------------
 1 | # Makefile
 2 | 
 3 | PYTHON ?= python3
 4 | 
 5 | reset-db:
 6 | 	$(PYTHON) manage.py migrate library_management zero;
 7 | 	$(PYTHON) manage.py migrate taggit zero;
 8 | 	$(PYTHON) manage.py migrate;
 9 | 	$(PYTHON) manage.py loaddata fixtures/*.json
10 | 
11 | export-data:
12 | 	$(PYTHON) manage.py dumpdata --indent 2 auth.User --output=sample_taggit/fixtures/0001_users.json;
13 | 	$(PYTHON) manage.py dumpdata --indent 2 library_management.Author --output=sample_taggit/fixtures/0002_author.json;
14 | 	$(PYTHON) manage.py dumpdata --indent 2 library_management.BookType --output=sample_taggit/fixtures/0003_book_type.json;
15 | 	$(PYTHON) manage.py dumpdata --indent 2 library_management.Book --output=sample_taggit/fixtures/0004_books.json;
16 | 	$(PYTHON) manage.py dumpdata --indent 2 taggit.Tag --output=sample_taggit/fixtures/0005_tags.json;
17 | 	$(PYTHON) manage.py dumpdata --indent 2 library_management.ConditionTag --output=sample_taggit/fixtures/0006_condition_tags.json;
18 | 	$(PYTHON) manage.py dumpdata --indent 2 library_management.PhysicalCopy --output=sample_taggit/fixtures/0007_tags.json;
19 | 	$(PYTHON) manage.py dumpdata --indent 2 library_management.ConditionTaggedItem --output=sample_taggit/fixtures/0008_condition_tagged_item.json;
20 | 
21 | loaddata:
22 | 	$(PYTHON) manage.py loaddata fixtures/*.json
23 | 


--------------------------------------------------------------------------------
/sample_taggit/manage.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python
 2 | """Django's command-line utility for administrative tasks."""
 3 | import os
 4 | import sys
 5 | from pathlib import Path
 6 | 
 7 | 
 8 | def main():
 9 |     """Run administrative tasks."""
10 |     # add this so that we can import from sample_taggit
11 |     sys.path.append(str(Path(__file__).resolve().parent.parent))
12 |     os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_taggit.settings")
13 |     try:
14 |         from django.core.management import execute_from_command_line
15 |     except ImportError as exc:
16 |         raise ImportError(
17 |             "Couldn't import Django. Are you sure it's installed and "
18 |             "available on your PYTHONPATH environment variable? Did you "
19 |             "forget to activate a virtual environment?"
20 |         ) from exc
21 |     execute_from_command_line(sys.argv)
22 | 
23 | 
24 | if __name__ == "__main__":
25 |     main()
26 | 


--------------------------------------------------------------------------------
/sample_taggit/settings.py:
--------------------------------------------------------------------------------
  1 | """
  2 | Django settings for sample_taggit project.
  3 | 
  4 | Generated by 'django-admin startproject' using Django 5.0.6.
  5 | 
  6 | For more information on this file, see
  7 | https://docs.djangoproject.com/en/5.0/topics/settings/
  8 | 
  9 | For the full list of settings and their values, see
 10 | https://docs.djangoproject.com/en/5.0/ref/settings/
 11 | """
 12 | 
 13 | import os
 14 | from pathlib import Path
 15 | 
 16 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
 17 | BASE_DIR = Path(__file__).resolve().parent.parent
 18 | 
 19 | 
 20 | # Quick-start development settings - unsuitable for production
 21 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
 22 | 
 23 | # SECURITY WARNING: keep the secret key used in production secret!
 24 | SECRET_KEY = "django-insecure-1$#3+wir_0n0&d#_f$35%b-fb_!f(8vzh8*a2x%ih*+j6*gih_"
 25 | 
 26 | # SECURITY WARNING: don't run with debug turned on in production!
 27 | DEBUG = True
 28 | 
 29 | ALLOWED_HOSTS = []
 30 | 
 31 | 
 32 | # Application definition
 33 | 
 34 | INSTALLED_APPS = [
 35 |     "django.contrib.admin",
 36 |     "django.contrib.auth",
 37 |     "django.contrib.contenttypes",
 38 |     "django.contrib.sessions",
 39 |     "django.contrib.messages",
 40 |     "django.contrib.staticfiles",
 41 |     "taggit",
 42 |     "library_management",
 43 | ]
 44 | 
 45 | MIDDLEWARE = [
 46 |     "django.middleware.security.SecurityMiddleware",
 47 |     "django.contrib.sessions.middleware.SessionMiddleware",
 48 |     "django.middleware.common.CommonMiddleware",
 49 |     "django.middleware.csrf.CsrfViewMiddleware",
 50 |     "django.contrib.auth.middleware.AuthenticationMiddleware",
 51 |     "django.contrib.messages.middleware.MessageMiddleware",
 52 |     "django.middleware.clickjacking.XFrameOptionsMiddleware",
 53 | ]
 54 | 
 55 | ROOT_URLCONF = "sample_taggit.urls"
 56 | 
 57 | TEMPLATES = [
 58 |     {
 59 |         "BACKEND": "django.template.backends.django.DjangoTemplates",
 60 |         "DIRS": [os.path.join(BASE_DIR, "templates")],
 61 |         "APP_DIRS": True,
 62 |         "OPTIONS": {
 63 |             "context_processors": [
 64 |                 "django.template.context_processors.debug",
 65 |                 "django.template.context_processors.request",
 66 |                 "django.contrib.auth.context_processors.auth",
 67 |                 "django.contrib.messages.context_processors.messages",
 68 |             ],
 69 |         },
 70 |     },
 71 | ]
 72 | 
 73 | WSGI_APPLICATION = "sample_taggit.wsgi.application"
 74 | 
 75 | 
 76 | # Database
 77 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
 78 | 
 79 | DATABASES = {
 80 |     "default": {
 81 |         "ENGINE": "django.db.backends.sqlite3",
 82 |         "NAME": BASE_DIR / "db.sqlite3",
 83 |     }
 84 | }
 85 | 
 86 | 
 87 | # Password validation
 88 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
 89 | 
 90 | AUTH_PASSWORD_VALIDATORS = [
 91 |     {
 92 |         "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
 93 |     },
 94 |     {
 95 |         "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
 96 |     },
 97 |     {
 98 |         "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
 99 |     },
100 |     {
101 |         "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
102 |     },
103 | ]
104 | 
105 | 
106 | # Internationalization
107 | # https://docs.djangoproject.com/en/5.0/topics/i18n/
108 | 
109 | LANGUAGE_CODE = "en-us"
110 | 
111 | TIME_ZONE = "UTC"
112 | 
113 | USE_I18N = True
114 | 
115 | USE_TZ = True
116 | 
117 | 
118 | # Static files (CSS, JavaScript, Images)
119 | # https://docs.djangoproject.com/en/5.0/howto/static-files/
120 | 
121 | STATIC_URL = "static/"
122 | 
123 | # Default primary key field type
124 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
125 | 
126 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
127 | 


--------------------------------------------------------------------------------
/sample_taggit/urls.py:
--------------------------------------------------------------------------------
 1 | """
 2 | URL configuration for sample_taggit project.
 3 | 
 4 | The `urlpatterns` list routes URLs to views. For more information please see:
 5 |     https://docs.djangoproject.com/en/5.0/topics/http/urls/
 6 | Examples:
 7 | Function views
 8 |     1. Add an import:  from my_app import views
 9 |     2. Add a URL to urlpatterns:  path('', views.home, name='home')
10 | Class-based views
11 |     1. Add an import:  from other_app.views import Home
12 |     2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
13 | Including another URLconf
14 |     1. Import the include() function: from django.urls import include, path
15 |     2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
16 | """
17 | 
18 | from django.contrib import admin
19 | from django.urls import include, path
20 | from library_management import urls as library_management_urls
21 | 
22 | urlpatterns = [
23 |     path("admin/", admin.site.urls),
24 |     path("", include(library_management_urls)),
25 | ]
26 | 


--------------------------------------------------------------------------------
/sample_taggit/wsgi.py:
--------------------------------------------------------------------------------
 1 | """
 2 | WSGI config for sample_taggit project.
 3 | 
 4 | It exposes the WSGI callable as a module-level variable named ``application``.
 5 | 
 6 | For more information on this file, see
 7 | https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
 8 | """
 9 | 
10 | import os
11 | 
12 | from django.core.wsgi import get_wsgi_application
13 | 
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_taggit.settings")
15 | 
16 | application = get_wsgi_application()
17 | 


--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
 1 | [metadata]
 2 | name = django-taggit
 3 | version = attr: taggit.__version__
 4 | description = django-taggit is a reusable Django application for simple tagging.
 5 | long_description = file: README.rst
 6 | author = Alex Gaynor
 7 | author_email = alex.gaynor@gmail.com
 8 | url = https://github.com/jazzband/django-taggit
 9 | license = BSD
10 | classifiers =
11 |     Development Status :: 5 - Production/Stable
12 |     Environment :: Web Environment
13 |     Framework :: Django
14 |     Framework :: Django :: 4.1
15 |     Framework :: Django :: 4.2
16 |     Framework :: Django :: 5.0
17 |     Intended Audience :: Developers
18 |     License :: OSI Approved :: BSD License
19 |     Operating System :: OS Independent
20 |     Programming Language :: Python
21 |     Programming Language :: Python :: 3
22 |     Programming Language :: Python :: 3 :: Only
23 |     Programming Language :: Python :: 3.9
24 |     Programming Language :: Python :: 3.10
25 |     Programming Language :: Python :: 3.11
26 |     Programming Language :: Python :: 3.12
27 | project_urls =
28 |     Documentation = https://django-taggit.readthedocs.io
29 |     Source = https://github.com/jazzband/django-taggit
30 |     Tracker = https://github.com/jazzband/django-taggit/issues
31 | 
32 | [options]
33 | python_requires = >=3.9
34 | packages = find:
35 | install_requires = Django>=4.1
36 | include_package_data = true
37 | zip_safe = false
38 | 
39 | [options.packages.find]
40 | exclude = tests*
41 | 
42 | [flake8]
43 | # E501: line too long
44 | ignore = E501
45 | exclude = .venv,.git,.tox,.direnv
46 | 
47 | [isort]
48 | profile = black
49 | 


--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | 
3 | setup()
4 | 


--------------------------------------------------------------------------------
/taggit/__init__.py:
--------------------------------------------------------------------------------
1 | VERSION = (6, 1, 0)
2 | __version__ = ".".join(str(i) for i in VERSION)
3 | 


--------------------------------------------------------------------------------
/taggit/admin.py:
--------------------------------------------------------------------------------
 1 | from django.contrib import admin
 2 | from django.db import transaction
 3 | from django.shortcuts import redirect, render
 4 | from django.urls import path
 5 | 
 6 | from taggit.models import Tag, TaggedItem
 7 | 
 8 | from .forms import MergeTagsForm
 9 | 
10 | 
11 | class TaggedItemInline(admin.StackedInline):
12 |     model = TaggedItem
13 | 
14 | 
15 | @admin.register(Tag)
16 | class TagAdmin(admin.ModelAdmin):
17 |     inlines = [TaggedItemInline]
18 |     list_display = ["name", "slug"]
19 |     ordering = ["name", "slug"]
20 |     search_fields = ["name"]
21 |     prepopulated_fields = {"slug": ["name"]}
22 |     actions = ["render_tag_form", "remove_orphaned_tags_action"]
23 | 
24 |     def get_urls(self):
25 |         urls = super().get_urls()
26 |         custom_urls = [
27 |             path(
28 |                 "merge-tags/",
29 |                 self.admin_site.admin_view(self.merge_tags_view),
30 |                 name="taggit_tag_merge_tags",
31 |             ),
32 |         ]
33 |         return custom_urls + urls
34 | 
35 |     @admin.action(description="Merge selected tags")
36 |     def render_tag_form(self, request, queryset):
37 |         selected = request.POST.getlist(admin.helpers.ACTION_CHECKBOX_NAME)
38 |         if not selected:
39 |             self.message_user(request, "Please select at least one tag.")
40 |             return redirect(request.get_full_path())
41 | 
42 |         # set the selected tags into the session, to be used later
43 |         selected_tag_ids = ",".join(selected)
44 |         request.session["selected_tag_ids"] = selected_tag_ids
45 | 
46 |         return redirect("admin:taggit_tag_merge_tags")
47 | 
48 |     def merge_tags_view(self, request):
49 |         selected_tag_ids = request.session.get("selected_tag_ids", "").split(",")
50 |         if request.method == "POST":
51 |             form = MergeTagsForm(request.POST)
52 |             if form.is_valid():
53 |                 new_tag_name = form.cleaned_data["new_tag_name"]
54 |                 new_tag, created = Tag.objects.get_or_create(name=new_tag_name)
55 |                 with transaction.atomic():
56 |                     for tag_id in selected_tag_ids:
57 |                         tag = Tag.objects.get(id=tag_id)
58 |                         tagged_items = TaggedItem.objects.filter(tag=tag)
59 |                         for tagged_item in tagged_items:
60 |                             if TaggedItem.objects.filter(
61 |                                 tag=new_tag,
62 |                                 content_type=tagged_item.content_type,
63 |                                 object_id=tagged_item.object_id,
64 |                             ).exists():
65 |                                 # we have the new tag as well, so we can just
66 |                                 # remove the tag association
67 |                                 tagged_item.delete()
68 |                             else:
69 |                                 # point this taggedItem to the new one
70 |                                 tagged_item.tag = new_tag
71 |                                 tagged_item.save()
72 | 
73 |                 self.message_user(request, "Tags have been merged", level="success")
74 |                 # clear the selected_tag_ids from session after merge is complete
75 |                 request.session.pop("selected_tag_ids", None)
76 | 
77 |                 return redirect("..")
78 |             else:
79 |                 self.message_user(request, "Form is invalid.", level="error")
80 | 
81 |         context = {
82 |             "form": MergeTagsForm(),
83 |             "selected_tag_ids": selected_tag_ids,
84 |         }
85 |         return render(request, "admin/taggit/merge_tags_form.html", context)
86 | 
87 |     @admin.action(description="Remove orphaned tags")
88 |     def remove_orphaned_tags_action(self, request, queryset):
89 |         try:
90 |             orphaned_tags = queryset.objects.orphaned()
91 |             count, _ = orphaned_tags.delete()
92 |             self.message_user(
93 |                 request, f"Successfully removed {count} orphaned tags.", level="success"
94 |             )
95 |         except Exception as e:
96 |             self.message_user(request, f"An error occurred: {e}", level="error")
97 | 


--------------------------------------------------------------------------------
/taggit/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig as BaseConfig
2 | from django.utils.translation import gettext_lazy as _
3 | 
4 | 
5 | class TaggitAppConfig(BaseConfig):
6 |     name = "taggit"
7 |     verbose_name = _("Taggit")
8 |     default_auto_field = "django.db.models.AutoField"
9 | 


--------------------------------------------------------------------------------
/taggit/forms.py:
--------------------------------------------------------------------------------
 1 | from django import forms
 2 | from django.utils.translation import gettext as _
 3 | 
 4 | from taggit.utils import edit_string_for_tags, parse_tags
 5 | 
 6 | 
 7 | class TagWidgetMixin:
 8 |     def format_value(self, value):
 9 |         if value is not None and not isinstance(value, str):
10 |             value = edit_string_for_tags(value)
11 |         return super().format_value(value)
12 | 
13 | 
14 | class TagWidget(TagWidgetMixin, forms.TextInput):
15 |     pass
16 | 
17 | 
18 | class TextareaTagWidget(TagWidgetMixin, forms.Textarea):
19 |     pass
20 | 
21 | 
22 | class TagField(forms.CharField):
23 |     widget = TagWidget
24 | 
25 |     def clean(self, value):
26 |         value = super().clean(value)
27 |         try:
28 |             return parse_tags(value)
29 |         except ValueError:
30 |             raise forms.ValidationError(
31 |                 _("Please provide a comma-separated list of tags.")
32 |             )
33 | 
34 |     def has_changed(self, initial_value, data_value):
35 |         # Always return False if the field is disabled since self.bound_data
36 |         # always uses the initial value in this case.
37 |         if self.disabled:
38 |             return False
39 | 
40 |         try:
41 |             data_value = self.clean(data_value)
42 |         except forms.ValidationError:
43 |             pass
44 | 
45 |         # normalize "empty values"
46 |         if not data_value:
47 |             data_value = []
48 |         if not initial_value:
49 |             initial_value = []
50 | 
51 |         initial_value = [tag.name for tag in initial_value]
52 |         initial_value.sort()
53 | 
54 |         return initial_value != data_value
55 | 
56 | 
57 | class MergeTagsForm(forms.Form):
58 |     new_tag_name = forms.CharField(
59 |         label="New Tag Name",
60 |         max_length=100,
61 |         widget=forms.TextInput(attrs={"id": "id_new_tag_name"}),
62 |         help_text="Enter new or existing tag name",
63 |     )
64 | 


--------------------------------------------------------------------------------
/taggit/locale/ar/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/ar/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/ar/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | msgid ""
 7 | msgstr ""
 8 | "Project-Id-Version: \n"
 9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2021-11-14 11:27+0900\n"
11 | "PO-Revision-Date: 2021-04-19 23:30+0100\n"
12 | "Last-Translator: Soufyane Hedidi <hesoka05@gmail.com>\n"
13 | "Language-Team: \n"
14 | "Language: ar_DZ\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "X-Generator: Poedit 2.3\n"
19 | "Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
20 | "&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
21 | 
22 | #: taggit/apps.py:7
23 | msgid "Taggit"
24 | msgstr "أوْسِمه"
25 | 
26 | #: taggit/forms.py:31
27 | msgid "Please provide a comma-separated list of tags."
28 | msgstr "يرجى توفير قائمة وسوم مفصولة بفاصلة."
29 | 
30 | #: taggit/managers.py:432
31 | msgid "Tags"
32 | msgstr "الوسوم"
33 | 
34 | #: taggit/managers.py:433
35 | msgid "A comma-separated list of tags."
36 | msgstr "قائمة وسوم مفصولة بفاصلة."
37 | 
38 | #: taggit/models.py:19
39 | msgctxt "A tag name"
40 | msgid "name"
41 | msgstr "الإسم"
42 | 
43 | #: taggit/models.py:22
44 | msgctxt "A tag slug"
45 | msgid "slug"
46 | msgstr "سبيكة الوسم"
47 | 
48 | #: taggit/models.py:82
49 | msgid "tag"
50 | msgstr "الوسم"
51 | 
52 | #: taggit/models.py:83
53 | msgid "tags"
54 | msgstr "الوسوم"
55 | 
56 | #: taggit/models.py:89
57 | #, python-format
58 | msgid "%(object)s tagged with %(tag)s"
59 | msgstr "%(object)s الموسوم بـ %(tag)s"
60 | 
61 | #: taggit/models.py:134
62 | msgid "content type"
63 | msgstr "نوع المحتوى"
64 | 
65 | #: taggit/models.py:165 taggit/models.py:172
66 | msgid "object ID"
67 | msgstr "معرِّف الكائن"
68 | 
69 | #: taggit/models.py:180
70 | msgid "tagged item"
71 | msgstr "العنصر الموسوم"
72 | 
73 | #: taggit/models.py:181
74 | msgid "tagged items"
75 | msgstr "العناصر الموسومة"
76 | 
77 | #: taggit/serializers.py:40
78 | #, python-brace-format
79 | msgid "Expected a list of items but got type \"{input_type}\"."
80 | msgstr ""
81 | 
82 | #: taggit/serializers.py:43
83 | msgid ""
84 | "Invalid json list. A tag list submitted in string form must be valid json."
85 | msgstr ""
86 | 
87 | #: taggit/serializers.py:46
88 | msgid "All list items must be of string type."
89 | msgstr ""
90 | 


--------------------------------------------------------------------------------
/taggit/locale/cs/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/cs/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/cs/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | #, fuzzy
 7 | msgid ""
 8 | msgstr ""
 9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2021-11-14 11:28+0900\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 | "Language-Team: LANGUAGE <LL@li.org>\n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
20 | 
21 | #: taggit/apps.py:7
22 | msgid "Taggit"
23 | msgstr ""
24 | 
25 | #: taggit/forms.py:31
26 | msgid "Please provide a comma-separated list of tags."
27 | msgstr "Vložte čárkami oddělený seznam tagů"
28 | 
29 | #: taggit/managers.py:432
30 | msgid "Tags"
31 | msgstr "Tagy"
32 | 
33 | #: taggit/managers.py:433
34 | msgid "A comma-separated list of tags."
35 | msgstr "Čárkami oddělený seznam tagů"
36 | 
37 | #: taggit/models.py:19
38 | msgctxt "A tag name"
39 | msgid "name"
40 | msgstr "Jméno"
41 | 
42 | #: taggit/models.py:22
43 | msgctxt "A tag slug"
44 | msgid "slug"
45 | msgstr "Slug"
46 | 
47 | #: taggit/models.py:82
48 | msgid "tag"
49 | msgstr "Tag"
50 | 
51 | #: taggit/models.py:83
52 | msgid "tags"
53 | msgstr ""
54 | 
55 | #: taggit/models.py:89
56 | #, python-format
57 | msgid "%(object)s tagged with %(tag)s"
58 | msgstr "%(object)s označen tagem %(tag)s"
59 | 
60 | #: taggit/models.py:134
61 | msgid "content type"
62 | msgstr "Typ obsahu"
63 | 
64 | #: taggit/models.py:165 taggit/models.py:172
65 | msgid "object ID"
66 | msgstr "ID objektu"
67 | 
68 | #: taggit/models.py:180
69 | msgid "tagged item"
70 | msgstr "Tagem označená položka"
71 | 
72 | #: taggit/models.py:181
73 | msgid "tagged items"
74 | msgstr "Tagy označené položky"
75 | 
76 | #: taggit/serializers.py:40
77 | #, python-brace-format
78 | msgid "Expected a list of items but got type \"{input_type}\"."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:43
82 | msgid ""
83 | "Invalid json list. A tag list submitted in string form must be valid json."
84 | msgstr ""
85 | 
86 | #: taggit/serializers.py:46
87 | msgid "All list items must be of string type."
88 | msgstr ""
89 | 


--------------------------------------------------------------------------------
/taggit/locale/da/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/da/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/da/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <jakobdo@gmail.com>, 2020.
 5 | #
 6 | msgid ""
 7 | msgstr ""
 8 | "Project-Id-Version: \n"
 9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2021-11-14 11:29+0900\n"
11 | "PO-Revision-Date: 2020-10-25 14:29+0100\n"
12 | "Last-Translator: \n"
13 | "Language-Team: \n"
14 | "Language: da\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 | "X-Generator: Poedit 2.4.1\n"
20 | 
21 | #: taggit/apps.py:7
22 | msgid "Taggit"
23 | msgstr "Taggit"
24 | 
25 | #: taggit/forms.py:31
26 | msgid "Please provide a comma-separated list of tags."
27 | msgstr "Venligts angiv mærker adskilt af et komma."
28 | 
29 | #: taggit/managers.py:432
30 | msgid "Tags"
31 | msgstr "Mærker"
32 | 
33 | #: taggit/managers.py:433
34 | msgid "A comma-separated list of tags."
35 | msgstr "Adskil mærker med et komma."
36 | 
37 | #: taggit/models.py:19
38 | msgctxt "A tag name"
39 | msgid "name"
40 | msgstr "navn"
41 | 
42 | #: taggit/models.py:22
43 | msgctxt "A tag slug"
44 | msgid "slug"
45 | msgstr "slug"
46 | 
47 | #: taggit/models.py:82
48 | msgid "tag"
49 | msgstr "mærke"
50 | 
51 | #: taggit/models.py:83
52 | msgid "tags"
53 | msgstr "mærker"
54 | 
55 | #: taggit/models.py:89
56 | #, python-format
57 | msgid "%(object)s tagged with %(tag)s"
58 | msgstr "%(object)s mærket med %(tag)s"
59 | 
60 | #: taggit/models.py:134
61 | msgid "content type"
62 | msgstr "indholdstype"
63 | 
64 | #: taggit/models.py:165 taggit/models.py:172
65 | msgid "object ID"
66 | msgstr "objekt ID"
67 | 
68 | #: taggit/models.py:180
69 | msgid "tagged item"
70 | msgstr "mærket element"
71 | 
72 | #: taggit/models.py:181
73 | msgid "tagged items"
74 | msgstr "mærkede elementer"
75 | 
76 | #: taggit/serializers.py:40
77 | #, python-brace-format
78 | msgid "Expected a list of items but got type \"{input_type}\"."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:43
82 | msgid ""
83 | "Invalid json list. A tag list submitted in string form must be valid json."
84 | msgstr ""
85 | 
86 | #: taggit/serializers.py:46
87 | msgid "All list items must be of string type."
88 | msgstr ""
89 | 


--------------------------------------------------------------------------------
/taggit/locale/de/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/de/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/de/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | #, fuzzy
 2 | msgid ""
 3 | msgstr ""
 4 | "Project-Id-Version: django-taggit\n"
 5 | "Report-Msgid-Bugs-To: \n"
 6 | "POT-Creation-Date: 2021-11-14 11:29+0900\n"
 7 | "PO-Revision-Date: 2010-09-07 09:26-0700\n"
 8 | "Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
 9 | "Language-Team: German <de@li.org>\n"
10 | "Language: de\n"
11 | "MIME-Version: 1.0\n"
12 | "Content-Type: text/plain; charset=UTF-8\n"
13 | "Content-Transfer-Encoding: 8bit\n"
14 | "Plural-Forms: nplurals=2; plural=(n != 1)\n"
15 | 
16 | #: taggit/apps.py:7
17 | msgid "Taggit"
18 | msgstr ""
19 | 
20 | #: taggit/forms.py:31
21 | msgid "Please provide a comma-separated list of tags."
22 | msgstr "Bitte eine durch Komma getrennte Schlagwortliste eingeben."
23 | 
24 | #: taggit/managers.py:432
25 | msgid "Tags"
26 | msgstr "Schlagwörter"
27 | 
28 | #: taggit/managers.py:433
29 | msgid "A comma-separated list of tags."
30 | msgstr "Eine durch Komma getrennte Schlagwortliste."
31 | 
32 | #: taggit/models.py:19
33 | msgctxt "A tag name"
34 | msgid "name"
35 | msgstr "Name"
36 | 
37 | #: taggit/models.py:22
38 | msgctxt "A tag slug"
39 | msgid "slug"
40 | msgstr "Kürzel"
41 | 
42 | #: taggit/models.py:82
43 | msgid "tag"
44 | msgstr "Schlagwort"
45 | 
46 | #: taggit/models.py:83
47 | msgid "tags"
48 | msgstr ""
49 | 
50 | #: taggit/models.py:89
51 | #, python-format
52 | msgid "%(object)s tagged with %(tag)s"
53 | msgstr "%(object)s verschlagwortet mit %(tag)s"
54 | 
55 | #: taggit/models.py:134
56 | msgid "content type"
57 | msgstr "Inhaltstyp"
58 | 
59 | #: taggit/models.py:165 taggit/models.py:172
60 | msgid "object ID"
61 | msgstr "Objekt-ID"
62 | 
63 | #: taggit/models.py:180
64 | msgid "tagged item"
65 | msgstr "Verschlagwortetes Objekt"
66 | 
67 | #: taggit/models.py:181
68 | msgid "tagged items"
69 | msgstr "Verschlagwortete Objekte"
70 | 
71 | #: taggit/serializers.py:40
72 | #, python-brace-format
73 | msgid "Expected a list of items but got type \"{input_type}\"."
74 | msgstr ""
75 | 
76 | #: taggit/serializers.py:43
77 | msgid ""
78 | "Invalid json list. A tag list submitted in string form must be valid json."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:46
82 | msgid "All list items must be of string type."
83 | msgstr ""
84 | 


--------------------------------------------------------------------------------
/taggit/locale/el/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/el/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/el/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | #, fuzzy
 2 | msgid ""
 3 | msgstr ""
 4 | "Project-Id-Version: django-taggit\n"
 5 | "Report-Msgid-Bugs-To: \n"
 6 | "POT-Creation-Date: 2021-11-14 11:30+0900\n"
 7 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 8 | "Last-Translator: Serafeim Papastefanos <spapas@gmail.com>\n"
 9 | "Language-Team: Greek <el@li.org>\n"
10 | "Language: el\n"
11 | "MIME-Version: 1.0\n"
12 | "Content-Type: text/plain; charset=UTF-8\n"
13 | "Content-Transfer-Encoding: 8bit\n"
14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
15 | 
16 | #: taggit/apps.py:7
17 | msgid "Taggit"
18 | msgstr ""
19 | 
20 | #: taggit/forms.py:31
21 | msgid "Please provide a comma-separated list of tags."
22 | msgstr "Παρακαλούμε συπληρώστε μια λίστα από ετικέτες χωρισμένη με κόμματα"
23 | 
24 | #: taggit/managers.py:432
25 | msgid "Tags"
26 | msgstr "Ετικέτες"
27 | 
28 | #: taggit/managers.py:433
29 | msgid "A comma-separated list of tags."
30 | msgstr "Μια χωρισμένη με κόμματα λίστα από ετικέτες"
31 | 
32 | #: taggit/models.py:19
33 | msgctxt "A tag name"
34 | msgid "name"
35 | msgstr "Όνομα"
36 | 
37 | #: taggit/models.py:22
38 | msgctxt "A tag slug"
39 | msgid "slug"
40 | msgstr "Sluig"
41 | 
42 | #: taggit/models.py:82
43 | msgid "tag"
44 | msgstr "Ετικέτα"
45 | 
46 | #: taggit/models.py:83
47 | msgid "tags"
48 | msgstr ""
49 | 
50 | #: taggit/models.py:89
51 | #, python-format
52 | msgid "%(object)s tagged with %(tag)s"
53 | msgstr "%(object)s μαρκαρισμένα με %(tag)s"
54 | 
55 | #: taggit/models.py:134
56 | #, fuzzy
57 | #| msgid "Content type"
58 | msgid "content type"
59 | msgstr "Είδος περιεχομένου"
60 | 
61 | #: taggit/models.py:165 taggit/models.py:172
62 | #, fuzzy
63 | #| msgid "Object id"
64 | msgid "object ID"
65 | msgstr "Κωδικός αντικειμένου"
66 | 
67 | #: taggit/models.py:180
68 | #, fuzzy
69 | #| msgid "Tagged Item"
70 | msgid "tagged item"
71 | msgstr "Αντικείμενο με ετικέτα"
72 | 
73 | #: taggit/models.py:181
74 | #, fuzzy
75 | #| msgid "Tagged Items"
76 | msgid "tagged items"
77 | msgstr "Αντικείμενα με ετικέτα"
78 | 
79 | #: taggit/serializers.py:40
80 | #, python-brace-format
81 | msgid "Expected a list of items but got type \"{input_type}\"."
82 | msgstr ""
83 | 
84 | #: taggit/serializers.py:43
85 | msgid ""
86 | "Invalid json list. A tag list submitted in string form must be valid json."
87 | msgstr ""
88 | 
89 | #: taggit/serializers.py:46
90 | msgid "All list items must be of string type."
91 | msgstr ""
92 | 


--------------------------------------------------------------------------------
/taggit/locale/en/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/en/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/en/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | #, fuzzy
 7 | msgid ""
 8 | msgstr ""
 9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2021-11-14 11:31+0900\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 | "Language-Team: LANGUAGE <LL@li.org>\n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | 
20 | #: taggit/apps.py:7
21 | msgid "Taggit"
22 | msgstr ""
23 | 
24 | #: taggit/forms.py:31
25 | msgid "Please provide a comma-separated list of tags."
26 | msgstr ""
27 | 
28 | #: taggit/managers.py:432
29 | msgid "Tags"
30 | msgstr ""
31 | 
32 | #: taggit/managers.py:433
33 | msgid "A comma-separated list of tags."
34 | msgstr ""
35 | 
36 | #: taggit/models.py:19
37 | msgctxt "A tag name"
38 | msgid "name"
39 | msgstr ""
40 | 
41 | #: taggit/models.py:22
42 | msgctxt "A tag slug"
43 | msgid "slug"
44 | msgstr ""
45 | 
46 | #: taggit/models.py:82
47 | msgid "tag"
48 | msgstr ""
49 | 
50 | #: taggit/models.py:83
51 | msgid "tags"
52 | msgstr ""
53 | 
54 | #: taggit/models.py:89
55 | #, python-format
56 | msgid "%(object)s tagged with %(tag)s"
57 | msgstr ""
58 | 
59 | #: taggit/models.py:134
60 | msgid "content type"
61 | msgstr ""
62 | 
63 | #: taggit/models.py:165 taggit/models.py:172
64 | msgid "object ID"
65 | msgstr ""
66 | 
67 | #: taggit/models.py:180
68 | msgid "tagged item"
69 | msgstr ""
70 | 
71 | #: taggit/models.py:181
72 | msgid "tagged items"
73 | msgstr ""
74 | 
75 | #: taggit/serializers.py:40
76 | #, python-brace-format
77 | msgid "Expected a list of items but got type \"{input_type}\"."
78 | msgstr ""
79 | 
80 | #: taggit/serializers.py:43
81 | msgid ""
82 | "Invalid json list. A tag list submitted in string form must be valid json."
83 | msgstr ""
84 | 
85 | #: taggit/serializers.py:46
86 | msgid "All list items must be of string type."
87 | msgstr ""
88 | 


--------------------------------------------------------------------------------
/taggit/locale/eo/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/eo/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/eo/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | msgid ""
 2 | msgstr ""
 3 | "Project-Id-Version: django-taggit\n"
 4 | "Report-Msgid-Bugs-To: \n"
 5 | "POT-Creation-Date: 2021-11-14 11:31+0900\n"
 6 | "PO-Revision-Date: 2014-03-29 18:57+0100\n"
 7 | "Last-Translator: Baptiste Darthenay <taggit.batisteo@recursor.net>\n"
 8 | "Language-Team: Esperanto <taggit.batisteo@recursor.net>\n"
 9 | "Language: eo\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Generator: Poedit 1.5.4\n"
15 | 
16 | #: taggit/apps.py:7
17 | msgid "Taggit"
18 | msgstr "Etikedoj"
19 | 
20 | #: taggit/forms.py:31
21 | msgid "Please provide a comma-separated list of tags."
22 | msgstr "Bonvolu enmeti liston da etikedoj apartitaj per komoj."
23 | 
24 | #: taggit/managers.py:432
25 | msgid "Tags"
26 | msgstr "Etikedoj"
27 | 
28 | #: taggit/managers.py:433
29 | msgid "A comma-separated list of tags."
30 | msgstr "Listo da etikedoj apartitaj per komoj."
31 | 
32 | #: taggit/models.py:19
33 | msgctxt "A tag name"
34 | msgid "name"
35 | msgstr "Nomo"
36 | 
37 | #: taggit/models.py:22
38 | msgctxt "A tag slug"
39 | msgid "slug"
40 | msgstr "Ĵetonvorto"
41 | 
42 | #: taggit/models.py:82
43 | msgid "tag"
44 | msgstr "Etikedo"
45 | 
46 | #: taggit/models.py:83
47 | msgid "tags"
48 | msgstr ""
49 | 
50 | #: taggit/models.py:89
51 | #, python-format
52 | msgid "%(object)s tagged with %(tag)s"
53 | msgstr "%(object)s etikedita %(tag)s"
54 | 
55 | #: taggit/models.py:134
56 | msgid "content type"
57 | msgstr "Enhavtipo"
58 | 
59 | #: taggit/models.py:165 taggit/models.py:172
60 | msgid "object ID"
61 | msgstr "Objekto ID"
62 | 
63 | #: taggit/models.py:180
64 | msgid "tagged item"
65 | msgstr "Etikedita elemento"
66 | 
67 | #: taggit/models.py:181
68 | msgid "tagged items"
69 | msgstr "Etikeditaj elementoj"
70 | 
71 | #: taggit/serializers.py:40
72 | #, python-brace-format
73 | msgid "Expected a list of items but got type \"{input_type}\"."
74 | msgstr ""
75 | 
76 | #: taggit/serializers.py:43
77 | msgid ""
78 | "Invalid json list. A tag list submitted in string form must be valid json."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:46
82 | msgid "All list items must be of string type."
83 | msgstr ""
84 | 


--------------------------------------------------------------------------------
/taggit/locale/es/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/es/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/es/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | #, fuzzy
 7 | msgid ""
 8 | msgstr ""
 9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2021-11-14 11:32+0900\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 | "Language-Team: LANGUAGE <LL@li.org>\n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 | 
21 | #: taggit/apps.py:7
22 | msgid "Taggit"
23 | msgstr ""
24 | 
25 | #: taggit/forms.py:31
26 | msgid "Please provide a comma-separated list of tags."
27 | msgstr "Por favor introduzca una lista de etiquetas separadas por coma."
28 | 
29 | #: taggit/managers.py:432
30 | msgid "Tags"
31 | msgstr "Etiquetas"
32 | 
33 | #: taggit/managers.py:433
34 | msgid "A comma-separated list of tags."
35 | msgstr "Una lista de etiquetas separadas por coma."
36 | 
37 | #: taggit/models.py:19
38 | msgctxt "A tag name"
39 | msgid "name"
40 | msgstr "Nombre"
41 | 
42 | #: taggit/models.py:22
43 | msgctxt "A tag slug"
44 | msgid "slug"
45 | msgstr "Slug"
46 | 
47 | #: taggit/models.py:82
48 | msgid "tag"
49 | msgstr "Etiqueta"
50 | 
51 | #: taggit/models.py:83
52 | msgid "tags"
53 | msgstr ""
54 | 
55 | #: taggit/models.py:89
56 | #, python-format
57 | msgid "%(object)s tagged with %(tag)s"
58 | msgstr "%(object)s etiquetados con %(tag)s"
59 | 
60 | #: taggit/models.py:134
61 | msgid "content type"
62 | msgstr "Tipo de contenido"
63 | 
64 | #: taggit/models.py:165 taggit/models.py:172
65 | msgid "object ID"
66 | msgstr "Id del objeto"
67 | 
68 | #: taggit/models.py:180
69 | msgid "tagged item"
70 | msgstr "Elemento etiquetado"
71 | 
72 | #: taggit/models.py:181
73 | msgid "tagged items"
74 | msgstr "Elementos etiquetados"
75 | 
76 | #: taggit/serializers.py:40
77 | #, python-brace-format
78 | msgid "Expected a list of items but got type \"{input_type}\"."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:43
82 | msgid ""
83 | "Invalid json list. A tag list submitted in string form must be valid json."
84 | msgstr ""
85 | 
86 | #: taggit/serializers.py:46
87 | msgid "All list items must be of string type."
88 | msgstr ""
89 | 


--------------------------------------------------------------------------------
/taggit/locale/fa/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/fa/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/fa/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # Mohammad Hossein Yazdani <m.hossein95th@gmail.com>, 2021.
 5 | #
 6 | #, fuzzy
 7 | msgid ""
 8 | msgstr ""
 9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2021-11-14 11:32+0900\n"
12 | "PO-Revision-Date: 2021-07-27 23:15+0430\n"
13 | "Last-Translator: Mohammad Hossein Yazdani <m.hossein95th@gmail.com>\n"
14 | "Language-Team: LANGUAGE <LL@li.org>\n"
15 | "Language: fa\n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | 
20 | #: taggit/apps.py:7
21 | msgid "Taggit"
22 | msgstr ""
23 | 
24 | #: taggit/forms.py:31
25 | msgid "Please provide a comma-separated list of tags."
26 | msgstr "لطفا لیستی از برچسب های جدا شده توسط کاما بسازید"
27 | 
28 | #: taggit/managers.py:432
29 | msgid "Tags"
30 | msgstr "برچسب ها"
31 | 
32 | #: taggit/managers.py:433
33 | msgid "A comma-separated list of tags."
34 | msgstr "یک لیست از برچسب های جدا شده توسط کاما "
35 | 
36 | #: taggit/models.py:19
37 | msgctxt "A tag name"
38 | msgid "name"
39 | msgstr "نام"
40 | 
41 | #: taggit/models.py:22
42 | msgctxt "A tag slug"
43 | msgid "slug"
44 | msgstr "نامک"
45 | 
46 | #: taggit/models.py:82
47 | msgid "tag"
48 | msgstr "برچسب"
49 | 
50 | #: taggit/models.py:83
51 | msgid "tags"
52 | msgstr "برچسب ها"
53 | 
54 | #: taggit/models.py:89
55 | #, python-format
56 | msgid "%(object)s tagged with %(tag)s"
57 | msgstr "%(object)s برچسب گزاری شده با %(tag)s"
58 | 
59 | #: taggit/models.py:134
60 | msgid "content type"
61 | msgstr "نوع محتوا"
62 | 
63 | #: taggit/models.py:165 taggit/models.py:172
64 | msgid "object ID"
65 | msgstr "شناسه شی"
66 | 
67 | #: taggit/models.py:180
68 | msgid "tagged item"
69 | msgstr "آیتم برچسب گزاری شده"
70 | 
71 | #: taggit/models.py:181
72 | msgid "tagged items"
73 | msgstr "آیتم های برچسب گزاری شده"
74 | 
75 | #: taggit/serializers.py:40
76 | #, python-brace-format
77 | msgid "Expected a list of items but got type \"{input_type}\"."
78 | msgstr ""
79 | 
80 | #: taggit/serializers.py:43
81 | msgid ""
82 | "Invalid json list. A tag list submitted in string form must be valid json."
83 | msgstr ""
84 | 
85 | #: taggit/serializers.py:46
86 | msgid "All list items must be of string type."
87 | msgstr ""
88 | 


--------------------------------------------------------------------------------
/taggit/locale/fi/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/fi/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/fi/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # This file is distributed under the same license as the django-taggit package.
 2 | #
 3 | # Translators:
 4 | # Nikolay Korotkiy <sikmir@gmail.com>, 2018
 5 | #
 6 | msgid ""
 7 | msgstr ""
 8 | "Project-Id-Version: django-taggit\n"
 9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2021-11-14 11:32+0900\n"
11 | "PO-Revision-Date: 2018-01-06 17:27-0600\n"
12 | "Last-Translator: Nikolay Korotkiy <sikmir@gmail.com>\n"
13 | "Language-Team: Finnish\n"
14 | "Language: fi\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 | 
20 | #: taggit/apps.py:7
21 | msgid "Taggit"
22 | msgstr "Tagit"
23 | 
24 | #: taggit/forms.py:31
25 | msgid "Please provide a comma-separated list of tags."
26 | msgstr "Ole hyvä ja anna pilkulla erotettu lista tageista."
27 | 
28 | #: taggit/managers.py:432
29 | msgid "Tags"
30 | msgstr "Tagit"
31 | 
32 | #: taggit/managers.py:433
33 | msgid "A comma-separated list of tags."
34 | msgstr "Pilkulla erotettu lista tageista."
35 | 
36 | #: taggit/models.py:19
37 | msgctxt "A tag name"
38 | msgid "name"
39 | msgstr "Nimi"
40 | 
41 | #: taggit/models.py:22
42 | msgctxt "A tag slug"
43 | msgid "slug"
44 | msgstr "Lyhytnimi"
45 | 
46 | #: taggit/models.py:82
47 | msgid "tag"
48 | msgstr "Tagi"
49 | 
50 | #: taggit/models.py:83
51 | msgid "tags"
52 | msgstr ""
53 | 
54 | #: taggit/models.py:89
55 | #, python-format
56 | msgid "%(object)s tagged with %(tag)s"
57 | msgstr "%(object)s on merkitty %(tag)s:lla"
58 | 
59 | #: taggit/models.py:134
60 | msgid "content type"
61 | msgstr "Sisältötyyppi"
62 | 
63 | #: taggit/models.py:165 taggit/models.py:172
64 | msgid "object ID"
65 | msgstr "Kohteen ID"
66 | 
67 | #: taggit/models.py:180
68 | msgid "tagged item"
69 | msgstr "Merkitty kohde"
70 | 
71 | #: taggit/models.py:181
72 | msgid "tagged items"
73 | msgstr "Merkittyjä kohteita"
74 | 
75 | #: taggit/serializers.py:40
76 | #, python-brace-format
77 | msgid "Expected a list of items but got type \"{input_type}\"."
78 | msgstr ""
79 | 
80 | #: taggit/serializers.py:43
81 | msgid ""
82 | "Invalid json list. A tag list submitted in string form must be valid json."
83 | msgstr ""
84 | 
85 | #: taggit/serializers.py:46
86 | msgid "All list items must be of string type."
87 | msgstr ""
88 | 


--------------------------------------------------------------------------------
/taggit/locale/fr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/fr/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/fr/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # French translation for django-taggit
 2 | # Copyright (C) Jazz Band
 3 | # This file is distributed under the same license as the taggit package.
 4 | # Guillaume Bernard <associations@guillaume-bernard.fr>, 2019
 5 | #
 6 | msgid ""
 7 | msgstr ""
 8 | "Project-Id-Version: \n"
 9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2021-11-14 11:33+0900\n"
11 | "PO-Revision-Date: 2019-04-09 10:57+0200\n"
12 | "Last-Translator: Guillaume Bernard <associations@guillaume-bernard.fr>\n"
13 | "Language-Team: \n"
14 | "Language: fr\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n"
19 | "X-Generator: Poedit 2.2.1\n"
20 | 
21 | #: taggit/apps.py:7
22 | msgid "Taggit"
23 | msgstr ""
24 | 
25 | #: taggit/forms.py:31
26 | msgid "Please provide a comma-separated list of tags."
27 | msgstr "Renseignez une liste d’étiquettes séparées par des virgules."
28 | 
29 | #: taggit/managers.py:432
30 | msgid "Tags"
31 | msgstr "Étiquettes"
32 | 
33 | #: taggit/managers.py:433
34 | msgid "A comma-separated list of tags."
35 | msgstr "Une liste d’étiquettes séparées par des virgules."
36 | 
37 | #: taggit/models.py:19
38 | msgctxt "A tag name"
39 | msgid "name"
40 | msgstr "nom"
41 | 
42 | #: taggit/models.py:22
43 | msgctxt "A tag slug"
44 | msgid "slug"
45 | msgstr "slug"
46 | 
47 | #: taggit/models.py:82
48 | msgid "tag"
49 | msgstr "Étiquette"
50 | 
51 | #: taggit/models.py:83
52 | msgid "tags"
53 | msgstr ""
54 | 
55 | #: taggit/models.py:89
56 | #, python-format
57 | msgid "%(object)s tagged with %(tag)s"
58 | msgstr "%(object)s étiquetés avec %(tag)s"
59 | 
60 | #: taggit/models.py:134
61 | msgid "content type"
62 | msgstr "Type de contenu"
63 | 
64 | #: taggit/models.py:165 taggit/models.py:172
65 | msgid "object ID"
66 | msgstr "Identifiant de l’objet"
67 | 
68 | #: taggit/models.py:180
69 | msgid "tagged item"
70 | msgstr "Élément étiqueté"
71 | 
72 | #: taggit/models.py:181
73 | msgid "tagged items"
74 | msgstr "Éléments étiquetés"
75 | 
76 | #: taggit/serializers.py:40
77 | #, python-brace-format
78 | msgid "Expected a list of items but got type \"{input_type}\"."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:43
82 | msgid ""
83 | "Invalid json list. A tag list submitted in string form must be valid json."
84 | msgstr ""
85 | 
86 | #: taggit/serializers.py:46
87 | msgid "All list items must be of string type."
88 | msgstr ""
89 | 


--------------------------------------------------------------------------------
/taggit/locale/he/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/he/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/he/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | msgid ""
 7 | msgstr ""
 8 | "Project-Id-Version: Django Taggit\n"
 9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2021-11-14 11:34+0900\n"
11 | "PO-Revision-Date: 2010-06-26 12:54-0600\n"
12 | "Last-Translator: Alex <alex.gaynor@gmail.com>\n"
13 | "Language-Team: LANGUAGE <LL@li.org>\n"
14 | "Language: \n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 | 
20 | #: taggit/apps.py:7
21 | msgid "Taggit"
22 | msgstr ""
23 | 
24 | #: taggit/forms.py:31
25 | msgid "Please provide a comma-separated list of tags."
26 | msgstr "נא לספק רשימה של תגים מופרדת עם פסיקים."
27 | 
28 | #: taggit/managers.py:432
29 | msgid "Tags"
30 | msgstr "תגיות"
31 | 
32 | #: taggit/managers.py:433
33 | msgid "A comma-separated list of tags."
34 | msgstr "רשימה של תגים מופרדת עם פסיקים."
35 | 
36 | #: taggit/models.py:19
37 | msgctxt "A tag name"
38 | msgid "name"
39 | msgstr "שם"
40 | 
41 | #: taggit/models.py:22
42 | msgctxt "A tag slug"
43 | msgid "slug"
44 | msgstr ""
45 | 
46 | #: taggit/models.py:82
47 | msgid "tag"
48 | msgstr "תג"
49 | 
50 | #: taggit/models.py:83
51 | msgid "tags"
52 | msgstr ""
53 | 
54 | #: taggit/models.py:89
55 | #, python-format
56 | msgid "%(object)s tagged with %(tag)s"
57 | msgstr "%(object)s מתויג עם %(tag)s"
58 | 
59 | #: taggit/models.py:134
60 | msgid "content type"
61 | msgstr ""
62 | 
63 | #: taggit/models.py:165 taggit/models.py:172
64 | msgid "object ID"
65 | msgstr ""
66 | 
67 | #: taggit/models.py:180
68 | msgid "tagged item"
69 | msgstr ""
70 | 
71 | #: taggit/models.py:181
72 | msgid "tagged items"
73 | msgstr ""
74 | 
75 | #: taggit/serializers.py:40
76 | #, python-brace-format
77 | msgid "Expected a list of items but got type \"{input_type}\"."
78 | msgstr ""
79 | 
80 | #: taggit/serializers.py:43
81 | msgid ""
82 | "Invalid json list. A tag list submitted in string form must be valid json."
83 | msgstr ""
84 | 
85 | #: taggit/serializers.py:46
86 | msgid "All list items must be of string type."
87 | msgstr ""
88 | 


--------------------------------------------------------------------------------
/taggit/locale/it/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/it/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/it/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | #, fuzzy
 7 | msgid ""
 8 | msgstr ""
 9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2021-11-14 11:35+0900\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 | "Language-Team: LANGUAGE <LL@li.org>\n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | 
20 | #: taggit/apps.py:7
21 | msgid "Taggit"
22 | msgstr ""
23 | 
24 | #: taggit/forms.py:31
25 | msgid "Please provide a comma-separated list of tags."
26 | msgstr "Fornire una lista di tag separati da virgola."
27 | 
28 | #: taggit/managers.py:432
29 | msgid "Tags"
30 | msgstr "Tag"
31 | 
32 | #: taggit/managers.py:433
33 | msgid "A comma-separated list of tags."
34 | msgstr "Una lista di tag separati da virgola."
35 | 
36 | #: taggit/models.py:19
37 | msgctxt "A tag name"
38 | msgid "name"
39 | msgstr "Nome"
40 | 
41 | #: taggit/models.py:22
42 | msgctxt "A tag slug"
43 | msgid "slug"
44 | msgstr "Slug"
45 | 
46 | #: taggit/models.py:82
47 | msgid "tag"
48 | msgstr "Tag"
49 | 
50 | #: taggit/models.py:83
51 | msgid "tags"
52 | msgstr ""
53 | 
54 | #: taggit/models.py:89
55 | #, python-format
56 | msgid "%(object)s tagged with %(tag)s"
57 | msgstr "%(object)s con tag %(tag)s"
58 | 
59 | #: taggit/models.py:134
60 | msgid "content type"
61 | msgstr "Tipo"
62 | 
63 | #: taggit/models.py:165 taggit/models.py:172
64 | msgid "object ID"
65 | msgstr "Id Oggetto"
66 | 
67 | #: taggit/models.py:180
68 | msgid "tagged item"
69 | msgstr "Oggetto con tag"
70 | 
71 | #: taggit/models.py:181
72 | msgid "tagged items"
73 | msgstr "Oggetti con tag"
74 | 
75 | #: taggit/serializers.py:40
76 | #, python-brace-format
77 | msgid "Expected a list of items but got type \"{input_type}\"."
78 | msgstr ""
79 | 
80 | #: taggit/serializers.py:43
81 | msgid ""
82 | "Invalid json list. A tag list submitted in string form must be valid json."
83 | msgstr ""
84 | 
85 | #: taggit/serializers.py:46
86 | msgid "All list items must be of string type."
87 | msgstr ""
88 | 


--------------------------------------------------------------------------------
/taggit/locale/ja/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/ja/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/ja/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | msgid ""
 2 | msgstr ""
 3 | "Project-Id-Version: django-taggit\n"
 4 | "Report-Msgid-Bugs-To: \n"
 5 | "POT-Creation-Date: 2021-11-14 11:39+0900\n"
 6 | "PO-Revision-Date: 2014-04-23 08:05+0900\n"
 7 | "Last-Translator: Tatsuo Ikeda <jp.ne.co.jp@gmail.com>\n"
 8 | "Language-Team: \n"
 9 | "Language: ja\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Generator: Poedit 1.6.4\n"
15 | 
16 | #: taggit/apps.py:7
17 | msgid "Taggit"
18 | msgstr ""
19 | 
20 | #: taggit/forms.py:31
21 | msgid "Please provide a comma-separated list of tags."
22 | msgstr "複数タグはカンマ区切りのリストを入れてください。"
23 | 
24 | #: taggit/managers.py:432
25 | msgid "Tags"
26 | msgstr "タグ一覧"
27 | 
28 | #: taggit/managers.py:433
29 | msgid "A comma-separated list of tags."
30 | msgstr "複数タグはカンマ区切りのリスト。"
31 | 
32 | #: taggit/models.py:19
33 | msgctxt "A tag name"
34 | msgid "name"
35 | msgstr "名称"
36 | 
37 | #: taggit/models.py:22
38 | msgctxt "A tag slug"
39 | msgid "slug"
40 | msgstr "スラッグ"
41 | 
42 | #: taggit/models.py:82
43 | msgid "tag"
44 | msgstr "タグ"
45 | 
46 | #: taggit/models.py:83
47 | msgid "tags"
48 | msgstr ""
49 | 
50 | #: taggit/models.py:89
51 | #, python-format
52 | msgid "%(object)s tagged with %(tag)s"
53 | msgstr "%(object)s tagged with %(tag)s"
54 | 
55 | #: taggit/models.py:134
56 | msgid "content type"
57 | msgstr "コンテンツタイプ"
58 | 
59 | #: taggit/models.py:165 taggit/models.py:172
60 | msgid "object ID"
61 | msgstr "オブジェクト ID"
62 | 
63 | #: taggit/models.py:180
64 | msgid "tagged item"
65 | msgstr "タグ付け済みのアイテム"
66 | 
67 | #: taggit/models.py:181
68 | msgid "tagged items"
69 | msgstr "タグ付け済みのアイテム一覧"
70 | 
71 | #: taggit/serializers.py:40
72 | #, python-brace-format
73 | msgid "Expected a list of items but got type \"{input_type}\"."
74 | msgstr ""
75 | 
76 | #: taggit/serializers.py:43
77 | msgid ""
78 | "Invalid json list. A tag list submitted in string form must be valid json."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:46
82 | msgid "All list items must be of string type."
83 | msgstr ""
84 | 


--------------------------------------------------------------------------------
/taggit/locale/nb/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/nb/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/nb/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | msgid ""
 7 | msgstr ""
 8 | "Project-Id-Version: 0.9.3\n"
 9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2021-11-14 11:41+0900\n"
11 | "PO-Revision-Date: 2012-12-08 14:42+0100\n"
12 | "Last-Translator: Bjørn Pettersen <bp@datakortet.no>\n"
13 | "Language-Team: Norwegian <bp@datakortet.no>\n"
14 | "Language: Norwegian\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "X-Generator: Poedit 1.5.4\n"
19 | 
20 | #: taggit/apps.py:7
21 | msgid "Taggit"
22 | msgstr ""
23 | 
24 | #: taggit/forms.py:31
25 | msgid "Please provide a comma-separated list of tags."
26 | msgstr "Vennligst oppgi en kommaseparert tagg-liste."
27 | 
28 | #: taggit/managers.py:432
29 | msgid "Tags"
30 | msgstr "Tagger"
31 | 
32 | #: taggit/managers.py:433
33 | msgid "A comma-separated list of tags."
34 | msgstr "En kommaseparert tagg-liste."
35 | 
36 | #: taggit/models.py:19
37 | msgctxt "A tag name"
38 | msgid "name"
39 | msgstr "Navn"
40 | 
41 | #: taggit/models.py:22
42 | msgctxt "A tag slug"
43 | msgid "slug"
44 | msgstr "Slug"
45 | 
46 | #: taggit/models.py:82
47 | msgid "tag"
48 | msgstr "Tagg"
49 | 
50 | #: taggit/models.py:83
51 | msgid "tags"
52 | msgstr ""
53 | 
54 | #: taggit/models.py:89
55 | #, python-format
56 | msgid "%(object)s tagged with %(tag)s"
57 | msgstr "%(object)s tagget med %(tag)s"
58 | 
59 | #: taggit/models.py:134
60 | msgid "content type"
61 | msgstr "Innholdstype"
62 | 
63 | #: taggit/models.py:165 taggit/models.py:172
64 | msgid "object ID"
65 | msgstr "Objekt-id"
66 | 
67 | #: taggit/models.py:180
68 | msgid "tagged item"
69 | msgstr "Tagget Element"
70 | 
71 | #: taggit/models.py:181
72 | msgid "tagged items"
73 | msgstr "Taggede Elementer"
74 | 
75 | #: taggit/serializers.py:40
76 | #, python-brace-format
77 | msgid "Expected a list of items but got type \"{input_type}\"."
78 | msgstr ""
79 | 
80 | #: taggit/serializers.py:43
81 | msgid ""
82 | "Invalid json list. A tag list submitted in string form must be valid json."
83 | msgstr ""
84 | 
85 | #: taggit/serializers.py:46
86 | msgid "All list items must be of string type."
87 | msgstr ""
88 | 


--------------------------------------------------------------------------------
/taggit/locale/nl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/nl/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/nl/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | msgid ""
 2 | msgstr ""
 3 | "Project-Id-Version: django-taggit\n"
 4 | "Report-Msgid-Bugs-To: \n"
 5 | "POT-Creation-Date: 2021-11-14 11:41+0900\n"
 6 | "PO-Revision-Date: 2010-09-07 23:04+0100\n"
 7 | "Last-Translator: Jeffrey Gelens <jeffrey@gelens.org>\n"
 8 | "Language-Team: Dutch\n"
 9 | "Language: \n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | 
14 | #: taggit/apps.py:7
15 | msgid "Taggit"
16 | msgstr ""
17 | 
18 | #: taggit/forms.py:31
19 | msgid "Please provide a comma-separated list of tags."
20 | msgstr "Geef een door komma gescheiden lijst van tags."
21 | 
22 | #: taggit/managers.py:432
23 | msgid "Tags"
24 | msgstr "Tags"
25 | 
26 | #: taggit/managers.py:433
27 | msgid "A comma-separated list of tags."
28 | msgstr "Een door komma gescheiden lijst van tags."
29 | 
30 | #: taggit/models.py:19
31 | msgctxt "A tag name"
32 | msgid "name"
33 | msgstr "Naam"
34 | 
35 | #: taggit/models.py:22
36 | msgctxt "A tag slug"
37 | msgid "slug"
38 | msgstr "Slug"
39 | 
40 | #: taggit/models.py:82
41 | #, fuzzy
42 | #| msgid "Tag"
43 | msgid "tag"
44 | msgstr "Tag"
45 | 
46 | #: taggit/models.py:83
47 | msgid "tags"
48 | msgstr ""
49 | 
50 | #: taggit/models.py:89
51 | #, python-format
52 | msgid "%(object)s tagged with %(tag)s"
53 | msgstr "%(object)s getagged met %(tag)s"
54 | 
55 | #: taggit/models.py:134
56 | msgid "content type"
57 | msgstr "Inhoudstype"
58 | 
59 | #: taggit/models.py:165 taggit/models.py:172
60 | msgid "object ID"
61 | msgstr "Object-id"
62 | 
63 | #: taggit/models.py:180
64 | msgid "tagged item"
65 | msgstr "Object getagged"
66 | 
67 | #: taggit/models.py:181
68 | msgid "tagged items"
69 | msgstr "Objecten getagged"
70 | 
71 | #: taggit/serializers.py:40
72 | #, python-brace-format
73 | msgid "Expected a list of items but got type \"{input_type}\"."
74 | msgstr ""
75 | 
76 | #: taggit/serializers.py:43
77 | msgid ""
78 | "Invalid json list. A tag list submitted in string form must be valid json."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:46
82 | msgid "All list items must be of string type."
83 | msgstr ""
84 | 


--------------------------------------------------------------------------------
/taggit/locale/pt_BR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/pt_BR/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/pt_BR/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # This file is distributed under WTFPL license.
 2 | #
 3 | # Translators:
 4 | # RPB <r@ifgy.co>, 2013.
 5 | msgid ""
 6 | msgstr ""
 7 | "Project-Id-Version: django-taggit\n"
 8 | "Report-Msgid-Bugs-To: \n"
 9 | "POT-Creation-Date: 2021-11-14 11:42+0900\n"
10 | "PO-Revision-Date: 2013-01-12 18:11-0200\n"
11 | "Last-Translator: RPB <r@ifgy.co>\n"
12 | "Language-Team: Portuguese (Brazil) <r@ifgy.co>\n"
13 | "Language: pt_BR\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Plural-Forms: nplurals=2; plural=(n > 1)\n"
18 | 
19 | #: taggit/apps.py:7
20 | msgid "Taggit"
21 | msgstr ""
22 | 
23 | #: taggit/forms.py:31
24 | msgid "Please provide a comma-separated list of tags."
25 | msgstr "Favor fornecer uma lista de marcadores separados por vírgula."
26 | 
27 | #: taggit/managers.py:432
28 | msgid "Tags"
29 | msgstr "Marcadores"
30 | 
31 | #: taggit/managers.py:433
32 | msgid "A comma-separated list of tags."
33 | msgstr "Uma lista de marcadores separados por vírgula."
34 | 
35 | #: taggit/models.py:19
36 | msgctxt "A tag name"
37 | msgid "name"
38 | msgstr "Nome"
39 | 
40 | #: taggit/models.py:22
41 | msgctxt "A tag slug"
42 | msgid "slug"
43 | msgstr "Slug"
44 | 
45 | #: taggit/models.py:82
46 | msgid "tag"
47 | msgstr "Marcador"
48 | 
49 | #: taggit/models.py:83
50 | msgid "tags"
51 | msgstr ""
52 | 
53 | #: taggit/models.py:89
54 | #, python-format
55 | msgid "%(object)s tagged with %(tag)s"
56 | msgstr "%(object)s marcados com %(tag)s"
57 | 
58 | #: taggit/models.py:134
59 | msgid "content type"
60 | msgstr "Tipo de conteúdo"
61 | 
62 | #: taggit/models.py:165 taggit/models.py:172
63 | msgid "object ID"
64 | msgstr "Id do objeto"
65 | 
66 | #: taggit/models.py:180
67 | msgid "tagged item"
68 | msgstr "Item marcado"
69 | 
70 | #: taggit/models.py:181
71 | msgid "tagged items"
72 | msgstr "Itens marcados"
73 | 
74 | #: taggit/serializers.py:40
75 | #, python-brace-format
76 | msgid "Expected a list of items but got type \"{input_type}\"."
77 | msgstr ""
78 | 
79 | #: taggit/serializers.py:43
80 | msgid ""
81 | "Invalid json list. A tag list submitted in string form must be valid json."
82 | msgstr ""
83 | 
84 | #: taggit/serializers.py:46
85 | msgid "All list items must be of string type."
86 | msgstr ""
87 | 


--------------------------------------------------------------------------------
/taggit/locale/ru/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/ru/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/ru/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | msgid ""
 7 | msgstr ""
 8 | "Project-Id-Version: Django Taggit\n"
 9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2021-11-14 11:43+0900\n"
11 | "PO-Revision-Date: 2021-09-26 00:51+0300\n"
12 | "Last-Translator: Serghei Iakovlev <egrep@protonmail.ch>\n"
13 | "Language-Team: \n"
14 | "Language: ru\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
20 | 
21 | #: taggit/apps.py:7
22 | msgid "Taggit"
23 | msgstr "Теги"
24 | 
25 | #: taggit/forms.py:31
26 | msgid "Please provide a comma-separated list of tags."
27 | msgstr "Укажите теги через запятую."
28 | 
29 | #: taggit/managers.py:432
30 | msgid "Tags"
31 | msgstr "Теги"
32 | 
33 | #: taggit/managers.py:433
34 | msgid "A comma-separated list of tags."
35 | msgstr "Список тегов через запятую."
36 | 
37 | #: taggit/models.py:19
38 | msgctxt "A tag name"
39 | msgid "name"
40 | msgstr "название"
41 | 
42 | #: taggit/models.py:22
43 | msgctxt "A tag slug"
44 | msgid "slug"
45 | msgstr "слаг"
46 | 
47 | #: taggit/models.py:82
48 | msgid "tag"
49 | msgstr "тег"
50 | 
51 | #: taggit/models.py:83
52 | msgid "tags"
53 | msgstr "теги"
54 | 
55 | #: taggit/models.py:89
56 | #, python-format
57 | msgid "%(object)s tagged with %(tag)s"
58 | msgstr "элемент «%(object)s» с тегом «%(tag)s»"
59 | 
60 | #: taggit/models.py:134
61 | msgid "content type"
62 | msgstr "тип содержимого"
63 | 
64 | #: taggit/models.py:165 taggit/models.py:172
65 | msgid "object ID"
66 | msgstr "идентификатор объекта"
67 | 
68 | #: taggit/models.py:180
69 | msgid "tagged item"
70 | msgstr "элемент с меткой"
71 | 
72 | #: taggit/models.py:181
73 | msgid "tagged items"
74 | msgstr "элементы с тегом"
75 | 
76 | #: taggit/serializers.py:40
77 | #, python-brace-format
78 | msgid "Expected a list of items but got type \"{input_type}\"."
79 | msgstr "Ожидался список элементов, но получен тип «{input_type}»."
80 | 
81 | #: taggit/serializers.py:43
82 | msgid ""
83 | "Invalid json list. A tag list submitted in string form must be valid json."
84 | msgstr ""
85 | "Неверный список json. Список тегов, представленный в строковой форме, должен "
86 | "быть корректным json."
87 | 
88 | #: taggit/serializers.py:46
89 | msgid "All list items must be of string type."
90 | msgstr "Все элементы списка должны быть строкового типа."
91 | 


--------------------------------------------------------------------------------
/taggit/locale/tr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/tr/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/tr/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | #, fuzzy
 7 | msgid ""
 8 | msgstr ""
 9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2021-11-14 11:44+0900\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 | "Language-Team: LANGUAGE <LL@li.org>\n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=2; plural=(n > 1);\n"
20 | 
21 | #: taggit/apps.py:7
22 | msgid "Taggit"
23 | msgstr ""
24 | 
25 | #: taggit/forms.py:31
26 | msgid "Please provide a comma-separated list of tags."
27 | msgstr "Etiketleri bir virgülle ayrılmış listesini veriniz."
28 | 
29 | #: taggit/managers.py:432
30 | msgid "Tags"
31 | msgstr "Etiketler"
32 | 
33 | #: taggit/managers.py:433
34 | msgid "A comma-separated list of tags."
35 | msgstr "Etiketlerin virgülle ayrılmış listesi."
36 | 
37 | #: taggit/models.py:19
38 | msgctxt "A tag name"
39 | msgid "name"
40 | msgstr "Adı"
41 | 
42 | #: taggit/models.py:22
43 | msgctxt "A tag slug"
44 | msgid "slug"
45 | msgstr "Kısaltma"
46 | 
47 | #: taggit/models.py:82
48 | msgid "tag"
49 | msgstr "Etiketler"
50 | 
51 | #: taggit/models.py:83
52 | msgid "tags"
53 | msgstr ""
54 | 
55 | #: taggit/models.py:89
56 | #, python-format
57 | msgid "%(object)s tagged with %(tag)s"
58 | msgstr "%(object)s %(tag)s ile etiketlendi"
59 | 
60 | #: taggit/models.py:134
61 | msgid "content type"
62 | msgstr "İçerik türü"
63 | 
64 | #: taggit/models.py:165 taggit/models.py:172
65 | msgid "object ID"
66 | msgstr "Nesne kimliği"
67 | 
68 | #: taggit/models.py:180
69 | msgid "tagged item"
70 | msgstr "Takip edilen Öğe"
71 | 
72 | #: taggit/models.py:181
73 | msgid "tagged items"
74 | msgstr "Takip edilen Öğeler"
75 | 
76 | #: taggit/serializers.py:40
77 | #, python-brace-format
78 | msgid "Expected a list of items but got type \"{input_type}\"."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:43
82 | msgid ""
83 | "Invalid json list. A tag list submitted in string form must be valid json."
84 | msgstr ""
85 | 
86 | #: taggit/serializers.py:46
87 | msgid "All list items must be of string type."
88 | msgstr ""
89 | 


--------------------------------------------------------------------------------
/taggit/locale/uk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/uk/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/uk/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | msgid ""
 7 | msgstr ""
 8 | "Project-Id-Version: Django Taggit\n"
 9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2021-11-14 11:44+0900\n"
11 | "PO-Revision-Date: 2010-06-11 11:30+0700\n"
12 | "Last-Translator: Igor 'idle sign' Starikov <idlesign@yandex.ru>\n"
13 | "Language-Team: \n"
14 | "Language: uk\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
20 | 
21 | #: taggit/apps.py:7
22 | msgid "Taggit"
23 | msgstr "Мітка"
24 | 
25 | #: taggit/forms.py:31
26 | msgid "Please provide a comma-separated list of tags."
27 | msgstr "Вкажіть мітки через кому."
28 | 
29 | #: taggit/managers.py:432
30 | msgid "Tags"
31 | msgstr "Мітка"
32 | 
33 | #: taggit/managers.py:433
34 | msgid "A comma-separated list of tags."
35 | msgstr "Список міток через кому."
36 | 
37 | #: taggit/models.py:19
38 | msgctxt "A tag name"
39 | msgid "name"
40 | msgstr "Назва"
41 | 
42 | #: taggit/models.py:22
43 | msgctxt "A tag slug"
44 | msgid "slug"
45 | msgstr "Слаг"
46 | 
47 | #: taggit/models.py:82
48 | msgid "tag"
49 | msgstr "Мітка"
50 | 
51 | #: taggit/models.py:83
52 | msgid "tags"
53 | msgstr ""
54 | 
55 | #: taggit/models.py:89
56 | #, python-format
57 | msgid "%(object)s tagged with %(tag)s"
58 | msgstr "елемент «%(object)s» з міткою «%(tag)s»"
59 | 
60 | #: taggit/models.py:134
61 | msgid "content type"
62 | msgstr "Тип вмісту"
63 | 
64 | #: taggit/models.py:165 taggit/models.py:172
65 | msgid "object ID"
66 | msgstr "ID об'єкта"
67 | 
68 | #: taggit/models.py:180
69 | msgid "tagged item"
70 | msgstr "Елемент з міткою"
71 | 
72 | #: taggit/models.py:181
73 | msgid "tagged items"
74 | msgstr "Елементи з міткою"
75 | 
76 | #: taggit/serializers.py:40
77 | #, python-brace-format
78 | msgid "Expected a list of items but got type \"{input_type}\"."
79 | msgstr ""
80 | 
81 | #: taggit/serializers.py:43
82 | msgid ""
83 | "Invalid json list. A tag list submitted in string form must be valid json."
84 | msgstr ""
85 | 
86 | #: taggit/serializers.py:46
87 | msgid "All list items must be of string type."
88 | msgstr ""
89 | 


--------------------------------------------------------------------------------
/taggit/locale/zh_Hans/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/locale/zh_Hans/LC_MESSAGES/django.mo


--------------------------------------------------------------------------------
/taggit/locale/zh_Hans/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
 1 | # SOME DESCRIPTIVE TITLE.
 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 3 | # This file is distributed under the same license as the PACKAGE package.
 4 | # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 5 | #
 6 | #, fuzzy
 7 | msgid ""
 8 | msgstr ""
 9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2024-01-30 16:06+0800\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 | "Language-Team: LANGUAGE <LL@li.org>\n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=1; plural=0;\n"
20 | 
21 | #: taggit/apps.py:7
22 | msgid "Taggit"
23 | msgstr "标签项"
24 | 
25 | #: taggit/forms.py:31
26 | msgid "Please provide a comma-separated list of tags."
27 | msgstr "请提供逗号分隔的标签列表。"
28 | 
29 | #: taggit/managers.py:442
30 | msgid "Tags"
31 | msgstr "标签"
32 | 
33 | #: taggit/managers.py:443
34 | msgid "A comma-separated list of tags."
35 | msgstr "逗号分隔的标签列表。"
36 | 
37 | #: taggit/models.py:20
38 | msgctxt "A tag name"
39 | msgid "name"
40 | msgstr "名称"
41 | 
42 | #: taggit/models.py:23
43 | msgctxt "A tag slug"
44 | msgid "slug"
45 | msgstr "唯一标识"
46 | 
47 | #: taggit/models.py:89
48 | msgid "tag"
49 | msgstr "标签"
50 | 
51 | #: taggit/models.py:90
52 | msgid "tags"
53 | msgstr "标签"
54 | 
55 | #: taggit/models.py:96
56 | #, python-format
57 | msgid "%(object)s tagged with %(tag)s"
58 | msgstr "%(object)s 使用了标签 %(tag)s"
59 | 
60 | #: taggit/models.py:141
61 | msgid "content type"
62 | msgstr "内容类型"
63 | 
64 | #: taggit/models.py:172 taggit/models.py:179
65 | msgid "object ID"
66 | msgstr "对象ID"
67 | 
68 | #: taggit/models.py:187
69 | msgid "tagged item"
70 | msgstr "标签项"
71 | 
72 | #: taggit/models.py:188
73 | msgid "tagged items"
74 | msgstr "标签项"
75 | 
76 | #: taggit/serializers.py:53
77 | #, python-brace-format
78 | msgid "Expected a list of items but got type \"{input_type}\"."
79 | msgstr "期望得到一个项目列表,但得到的是类型 “{input_type}”。"
80 | 
81 | #: taggit/serializers.py:56
82 | msgid ""
83 | "Invalid json list. A tag list submitted in string form must be valid json."
84 | msgstr "无效的 JSON 列表。以字符串形式提交的标签列表必须是有效的 JSON。"
85 | 
86 | #: taggit/serializers.py:59
87 | msgid "All list items must be of string type."
88 | msgstr "所有列表项必须是字符串类型。"
89 | 


--------------------------------------------------------------------------------
/taggit/management/commands/deduplicate_tags.py:
--------------------------------------------------------------------------------
 1 | from django.conf import settings
 2 | from django.core.management.base import BaseCommand
 3 | from django.db import transaction
 4 | 
 5 | from taggit.models import Tag, TaggedItem
 6 | 
 7 | 
 8 | class Command(BaseCommand):
 9 |     help = "Identify and remove duplicate tags based on case insensitivity"
10 | 
11 |     def handle(self, *args, **kwargs):
12 |         if not getattr(settings, "TAGGIT_CASE_INSENSITIVE", False):
13 |             self.stdout.write(
14 |                 self.style.ERROR("TAGGIT_CASE_INSENSITIVE is not enabled.")
15 |             )
16 |             return
17 | 
18 |         tags = Tag.objects.all()
19 |         tag_dict = {}
20 | 
21 |         for tag in tags:
22 |             lower_name = tag.name.lower()
23 |             if lower_name in tag_dict:
24 |                 existing_tag = tag_dict[lower_name]
25 |                 self._deduplicate_tags(existing_tag=existing_tag, tag_to_remove=tag)
26 |             else:
27 |                 tag_dict[lower_name] = tag
28 | 
29 |         self.stdout.write(self.style.SUCCESS("Tag deduplication complete."))
30 | 
31 |     @transaction.atomic
32 |     def _deduplicate_tags(self, existing_tag, tag_to_remove):
33 |         """
34 |         Remove a tag by merging it into an existing tag
35 |         """
36 |         # If this ends up very slow for you, please file a ticket!
37 |         # This isn't trying to be performant, in order to keep the code simple.
38 |         for item in TaggedItem.objects.filter(tag=tag_to_remove):
39 |             # if we already have the same association on the model
40 |             # (via the existing tag), then we can just remove the
41 |             # tagged item.
42 |             tag_exists_other = TaggedItem.objects.filter(
43 |                 tag=existing_tag,
44 |                 content_type_id=item.content_type_id,
45 |                 object_id=item.object_id,
46 |             ).exists()
47 |             if tag_exists_other:
48 |                 item.delete()
49 |             else:
50 |                 item.tag = existing_tag
51 |                 item.save()
52 | 
53 |         # this should never trigger, but can never be too sure
54 |         assert not TaggedItem.objects.filter(
55 |             tag=tag_to_remove
56 |         ).exists(), "Tags were not all cleaned up!"
57 | 
58 |         tag_to_remove.delete()
59 | 
60 |     def _collect_tagged_items(self, tag, existing_tag, tagged_items_to_update):
61 |         for item in TaggedItem.objects.filter(tag=tag):
62 |             tagged_items_to_update[(item.content_type_id, item.object_id)].append(
63 |                 existing_tag.id
64 |             )
65 | 
66 |     def _remove_duplicates_and_update(self, tagged_items_to_update):
67 |         with transaction.atomic():
68 |             for (content_type_id, object_id), tag_ids in tagged_items_to_update.items():
69 |                 unique_tag_ids = set(tag_ids)
70 |                 if len(unique_tag_ids) > 1:
71 |                     first_tag_id = unique_tag_ids.pop()
72 |                     for duplicate_tag_id in unique_tag_ids:
73 |                         TaggedItem.objects.filter(
74 |                             content_type_id=content_type_id,
75 |                             object_id=object_id,
76 |                             tag_id=duplicate_tag_id,
77 |                         ).delete()
78 | 
79 |                     TaggedItem.objects.filter(
80 |                         content_type_id=content_type_id,
81 |                         object_id=object_id,
82 |                         tag_id=first_tag_id,
83 |                     ).update(tag_id=first_tag_id)
84 | 


--------------------------------------------------------------------------------
/taggit/management/commands/remove_orphaned_tags.py:
--------------------------------------------------------------------------------
 1 | from django.core.management.base import BaseCommand
 2 | 
 3 | from taggit.models import Tag
 4 | 
 5 | 
 6 | class Command(BaseCommand):
 7 |     help = "Remove orphaned tags"
 8 | 
 9 |     def handle(self, *args, **options):
10 |         orphaned_tags = Tag.objects.orphaned()
11 |         count = orphaned_tags.delete()
12 |         self.stdout.write(f"Successfully removed {count} orphaned tags")
13 | 


--------------------------------------------------------------------------------
/taggit/migrations/0001_initial.py:
--------------------------------------------------------------------------------
 1 | from django.db import migrations, models
 2 | 
 3 | 
 4 | class Migration(migrations.Migration):
 5 |     dependencies = [("contenttypes", "0001_initial")]
 6 | 
 7 |     operations = [
 8 |         migrations.CreateModel(
 9 |             name="Tag",
10 |             fields=[
11 |                 (
12 |                     "id",
13 |                     models.AutoField(
14 |                         auto_created=True,
15 |                         primary_key=True,
16 |                         serialize=False,
17 |                         help_text="",
18 |                         verbose_name="ID",
19 |                     ),
20 |                 ),
21 |                 (
22 |                     "name",
23 |                     models.CharField(
24 |                         help_text="", unique=True, max_length=100, verbose_name="name"
25 |                     ),
26 |                 ),
27 |                 (
28 |                     "slug",
29 |                     models.SlugField(
30 |                         help_text="", unique=True, max_length=100, verbose_name="slug"
31 |                     ),
32 |                 ),
33 |             ],
34 |             options={"verbose_name": "tag", "verbose_name_plural": "tags"},
35 |             bases=(models.Model,),
36 |         ),
37 |         migrations.CreateModel(
38 |             name="TaggedItem",
39 |             fields=[
40 |                 (
41 |                     "id",
42 |                     models.AutoField(
43 |                         auto_created=True,
44 |                         primary_key=True,
45 |                         serialize=False,
46 |                         help_text="",
47 |                         verbose_name="ID",
48 |                     ),
49 |                 ),
50 |                 (
51 |                     "object_id",
52 |                     models.IntegerField(
53 |                         help_text="", verbose_name="object ID", db_index=True
54 |                     ),
55 |                 ),
56 |                 (
57 |                     "content_type",
58 |                     models.ForeignKey(
59 |                         related_name="taggit_taggeditem_tagged_items",
60 |                         verbose_name="content type",
61 |                         to="contenttypes.ContentType",
62 |                         help_text="",
63 |                         on_delete=models.CASCADE,
64 |                     ),
65 |                 ),
66 |                 (
67 |                     "tag",
68 |                     models.ForeignKey(
69 |                         related_name="taggit_taggeditem_items",
70 |                         to="taggit.Tag",
71 |                         help_text="",
72 |                         on_delete=models.CASCADE,
73 |                     ),
74 |                 ),
75 |             ],
76 |             options={
77 |                 "verbose_name": "tagged item",
78 |                 "verbose_name_plural": "tagged items",
79 |             },
80 |             bases=(models.Model,),
81 |         ),
82 |     ]
83 | 


--------------------------------------------------------------------------------
/taggit/migrations/0002_auto_20150616_2121.py:
--------------------------------------------------------------------------------
 1 | from django.db import migrations, models
 2 | 
 3 | 
 4 | class Migration(migrations.Migration):
 5 |     dependencies = [("taggit", "0001_initial")]
 6 | 
 7 |     operations = [
 8 |         # this migration was modified from previously being
 9 |         # a ModifyIndexTogether operation.
10 |         #
11 |         # If you are a long-enough user of this library, the name
12 |         # of the index does not match what is written here. Please
13 |         # query the DB itself to find out what the name originally was.
14 |         migrations.AddIndex(
15 |             "taggeditem",
16 |             models.Index(
17 |                 fields=("content_type", "object_id"),
18 |                 # this is not the name of the index in previous version,
19 |                 # but this is necessary to deal with index_together issues.
20 |                 name="taggit_tagg_content_8fc721_idx",
21 |             ),
22 |         )
23 |     ]
24 | 


--------------------------------------------------------------------------------
/taggit/migrations/0003_taggeditem_add_unique_index.py:
--------------------------------------------------------------------------------
 1 | from django.db import migrations, models
 2 | 
 3 | 
 4 | class Migration(migrations.Migration):
 5 |     dependencies = [
 6 |         ("contenttypes", "0002_remove_content_type_name"),
 7 |         ("taggit", "0002_auto_20150616_2121"),
 8 |     ]
 9 | 
10 |     operations = [
11 |         # this migration was modified to declare a uniqueness constraint differently
12 |         # this change was written on 2023-09-20, if any issues occurred from this please report it upstream
13 |         migrations.AddConstraint(
14 |             model_name="taggeditem",
15 |             constraint=models.UniqueConstraint(
16 |                 fields=("content_type", "object_id", "tag"),
17 |                 name="taggit_taggeditem_content_type_id_object_id_tag_id_4bb97a8e_uniq",
18 |             ),
19 |         ),
20 |     ]
21 | 


--------------------------------------------------------------------------------
/taggit/migrations/0004_alter_taggeditem_content_type_alter_taggeditem_tag.py:
--------------------------------------------------------------------------------
 1 | # This migration has no effect in practice. It exists to stop
 2 | # Django from autodetecting migrations in taggit when users
 3 | # update to Django 4.0.
 4 | # See https://docs.djangoproject.com/en/stable/releases/4.0/#migrations-autodetector-changes
 5 | import django.db.models.deletion
 6 | from django.db import migrations, models
 7 | 
 8 | 
 9 | class Migration(migrations.Migration):
10 |     dependencies = [
11 |         ("contenttypes", "0002_remove_content_type_name"),
12 |         ("taggit", "0003_taggeditem_add_unique_index"),
13 |     ]
14 | 
15 |     operations = [
16 |         migrations.AlterField(
17 |             model_name="taggeditem",
18 |             name="content_type",
19 |             field=models.ForeignKey(
20 |                 on_delete=django.db.models.deletion.CASCADE,
21 |                 related_name="%(app_label)s_%(class)s_tagged_items",
22 |                 to="contenttypes.contenttype",
23 |                 verbose_name="content type",
24 |             ),
25 |         ),
26 |         migrations.AlterField(
27 |             model_name="taggeditem",
28 |             name="tag",
29 |             field=models.ForeignKey(
30 |                 on_delete=django.db.models.deletion.CASCADE,
31 |                 related_name="%(app_label)s_%(class)s_items",
32 |                 to="taggit.tag",
33 |             ),
34 |         ),
35 |     ]
36 | 


--------------------------------------------------------------------------------
/taggit/migrations/0005_auto_20220424_2025.py:
--------------------------------------------------------------------------------
 1 | # Generated by Django 2.2.26 on 2022-04-24 20:25
 2 | 
 3 | from django.db import migrations, models
 4 | 
 5 | 
 6 | class Migration(migrations.Migration):
 7 |     dependencies = [
 8 |         ("taggit", "0004_alter_taggeditem_content_type_alter_taggeditem_tag"),
 9 |     ]
10 | 
11 |     operations = [
12 |         migrations.AlterField(
13 |             model_name="tag",
14 |             name="slug",
15 |             field=models.SlugField(
16 |                 allow_unicode=True, max_length=100, unique=True, verbose_name="slug"
17 |             ),
18 |         ),
19 |     ]
20 | 


--------------------------------------------------------------------------------
/taggit/migrations/0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx.py:
--------------------------------------------------------------------------------
 1 | # Generated by Django 4.2.5 on 2023-09-19 23:16
 2 | from django.db import migrations
 3 | 
 4 | 
 5 | class Migration(migrations.Migration):
 6 |     dependencies = [
 7 |         ("taggit", "0005_auto_20220424_2025"),
 8 |     ]
 9 | 
10 |     operations = [
11 |         migrations.RenameIndex(
12 |             model_name="taggeditem",
13 |             new_name="taggit_tagg_content_8fc721_idx",
14 |             old_fields=("content_type", "object_id"),
15 |         ),
16 |     ]
17 | 


--------------------------------------------------------------------------------
/taggit/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/taggit/migrations/__init__.py


--------------------------------------------------------------------------------
/taggit/serializers.py:
--------------------------------------------------------------------------------
  1 | """
  2 | Django-taggit serializer support
  3 | 
  4 | Originally vendored from https://github.com/glemmaPaul/django-taggit-serializer
  5 | """
  6 | 
  7 | import json
  8 | 
  9 | # Third party
 10 | from django.utils.translation import gettext_lazy
 11 | from rest_framework import serializers
 12 | 
 13 | 
 14 | class TagList(list):
 15 |     """
 16 |     This tag list subclass adds pretty printing support to the tag list
 17 |     serializer
 18 |     """
 19 | 
 20 |     def __init__(self, *args, **kwargs):
 21 |         pretty_print = kwargs.pop("pretty_print", True)
 22 |         super().__init__(*args, **kwargs)
 23 |         self.pretty_print = pretty_print
 24 | 
 25 |     def __add__(self, rhs):
 26 |         return TagList(super().__add__(rhs))
 27 | 
 28 |     def __getitem__(self, item):
 29 |         result = super().__getitem__(item)
 30 |         try:
 31 |             return TagList(result)
 32 |         except TypeError:
 33 |             return result
 34 | 
 35 |     def __str__(self):
 36 |         if self.pretty_print:
 37 |             return json.dumps(self, sort_keys=True, indent=4, separators=(",", ": "))
 38 |         else:
 39 |             return json.dumps(self)
 40 | 
 41 | 
 42 | class TagListSerializerField(serializers.ListField):
 43 |     """
 44 |     A serializer field that can write out a tag list
 45 | 
 46 |     This serializer field has some odd qualities compared to just using a ListField.
 47 |     If this field poses problems, we should introduce a new field that is a simpler
 48 |     ListField implementation with less features.
 49 |     """
 50 | 
 51 |     child = serializers.CharField()
 52 |     default_error_messages = {
 53 |         "not_a_list": gettext_lazy(
 54 |             'Expected a list of items but got type "{input_type}".'
 55 |         ),
 56 |         "invalid_json": gettext_lazy(
 57 |             "Invalid json list. A tag list submitted in string"
 58 |             " form must be valid json."
 59 |         ),
 60 |         "not_a_str": gettext_lazy("All list items must be of string type."),
 61 |     }
 62 |     order_by = None
 63 | 
 64 |     def __init__(self, **kwargs):
 65 |         pretty_print = kwargs.pop("pretty_print", True)
 66 | 
 67 |         style = kwargs.pop("style", {})
 68 |         kwargs["style"] = {"base_template": "textarea.html"}
 69 |         kwargs["style"].update(style)
 70 | 
 71 |         super().__init__(**kwargs)
 72 | 
 73 |         self.pretty_print = pretty_print
 74 | 
 75 |     def to_internal_value(self, value):
 76 |         # note to future maintainers: this field used to not be a ListField
 77 |         # and has extra behavior to support string-based input.
 78 |         #
 79 |         # In the future we should look at removing this feature so we can
 80 |         # make this a simple ListField (if feasible)
 81 |         if isinstance(value, str):
 82 |             if not value:
 83 |                 value = "[]"
 84 |             try:
 85 |                 value = json.loads(value)
 86 |             except ValueError:
 87 |                 self.fail("invalid_json")
 88 | 
 89 |         if not isinstance(value, list):
 90 |             self.fail("not_a_list", input_type=type(value).__name__)
 91 | 
 92 |         for s in value:
 93 |             if not isinstance(s, str):
 94 |                 self.fail("not_a_str")
 95 | 
 96 |             self.child.run_validation(s)
 97 | 
 98 |         return value
 99 | 
100 |     def to_representation(self, value):
101 |         if not isinstance(value, TagList):
102 |             if not isinstance(value, list):
103 |                 if self.order_by:
104 |                     tags = value.all().order_by(*self.order_by)
105 |                 else:
106 |                     tags = value.all()
107 |                 value = [tag.name for tag in tags]
108 |             value = TagList(value, pretty_print=self.pretty_print)
109 | 
110 |         return value
111 | 
112 | 
113 | class TaggitSerializer(serializers.Serializer):
114 |     def create(self, validated_data):
115 |         to_be_tagged, validated_data = self._pop_tags(validated_data)
116 | 
117 |         tag_object = super().create(validated_data)
118 | 
119 |         return self._save_tags(tag_object, to_be_tagged)
120 | 
121 |     def update(self, instance, validated_data):
122 |         to_be_tagged, validated_data = self._pop_tags(validated_data)
123 | 
124 |         tag_object = super().update(instance, validated_data)
125 | 
126 |         return self._save_tags(tag_object, to_be_tagged)
127 | 
128 |     def _save_tags(self, tag_object, tags):
129 |         for key in tags.keys():
130 |             tag_values = tags.get(key)
131 |             getattr(tag_object, key).set(tag_values)
132 | 
133 |         return tag_object
134 | 
135 |     def _pop_tags(self, validated_data):
136 |         to_be_tagged = {}
137 | 
138 |         for key in self.fields.keys():
139 |             field = self.fields[key]
140 |             if isinstance(field, TagListSerializerField):
141 |                 if key in validated_data:
142 |                     to_be_tagged[key] = validated_data.pop(key)
143 | 
144 |         return (to_be_tagged, validated_data)
145 | 


--------------------------------------------------------------------------------
/taggit/templates/admin/taggit/merge_tags_form.html:
--------------------------------------------------------------------------------
 1 | {% extends "admin/base.html" %} {% block content %}
 2 | <div id="mergeTagsModal">
 3 |   <div>
 4 |     <div>
 5 |       <div>
 6 |         <form
 7 |           id="merge-tags-form"
 8 |           method="post"
 9 |           action="{% url 'admin:taggit_tag_merge_tags' %}"
10 |         >
11 |           {% csrf_token %} {% for field in form %}
12 |           <div>
13 |             {{ field.label_tag }} {{ field }} {% if field.errors %}
14 |             <ul class="errorlist">
15 |               {% for error in field.errors %}
16 |               <li>{{ error }}</li>
17 |               {% endfor %}
18 |             </ul>
19 |             {% endif %}
20 |           </div>
21 |           {% endfor %}
22 |           <p><i><strong>Enter new or existing tag name</strong></strong></i></p>
23 |           <div>
24 |             <input type="submit" class="btn btn-primary"></input>
25 |           </div>
26 |         </form>
27 |       </div>
28 |     </div>
29 |   </div>
30 | </div>
31 | {% endblock %}
32 | 


--------------------------------------------------------------------------------
/taggit/utils.py:
--------------------------------------------------------------------------------
  1 | from django.conf import settings
  2 | from django.utils.functional import wraps
  3 | from django.utils.module_loading import import_string
  4 | 
  5 | 
  6 | def _parse_tags(tagstring):
  7 |     """
  8 |     Parses tag input, with multiple word input being activated and
  9 |     delineated by commas and double quotes. Quotes take precedence, so
 10 |     they may contain commas.
 11 | 
 12 |     Returns a sorted list of unique tag names.
 13 | 
 14 |     Ported from Jonathan Buchanan's `django-tagging
 15 |     <http://django-tagging.googlecode.com/>`_
 16 |     """
 17 |     if not tagstring:
 18 |         return []
 19 | 
 20 |     # Special case - if there are no commas or double quotes in the
 21 |     # input, we don't *do* a recall... I mean, we know we only need to
 22 |     # split on spaces.
 23 |     if "," not in tagstring and '"' not in tagstring:
 24 |         words = list(set(split_strip(tagstring, " ")))
 25 |         words.sort()
 26 |         return words
 27 | 
 28 |     words = []
 29 |     buffer = []
 30 |     # Defer splitting of non-quoted sections until we know if there are
 31 |     # any unquoted commas.
 32 |     to_be_split = []
 33 |     saw_loose_comma = False
 34 |     open_quote = False
 35 |     i = iter(tagstring)
 36 |     try:
 37 |         while True:
 38 |             c = next(i)
 39 |             if c == '"':
 40 |                 if buffer:
 41 |                     to_be_split.append("".join(buffer))
 42 |                     buffer = []
 43 |                 # Find the matching quote
 44 |                 open_quote = True
 45 |                 c = next(i)
 46 |                 while c != '"':
 47 |                     buffer.append(c)
 48 |                     c = next(i)
 49 |                 if buffer:
 50 |                     word = "".join(buffer).strip()
 51 |                     if word:
 52 |                         words.append(word)
 53 |                     buffer = []
 54 |                 open_quote = False
 55 |             else:
 56 |                 if not saw_loose_comma and c == ",":
 57 |                     saw_loose_comma = True
 58 |                 buffer.append(c)
 59 |     except StopIteration:
 60 |         # If we were parsing an open quote which was never closed treat
 61 |         # the buffer as unquoted.
 62 |         if buffer:
 63 |             if open_quote and "," in buffer:
 64 |                 saw_loose_comma = True
 65 |             to_be_split.append("".join(buffer))
 66 |     if to_be_split:
 67 |         if saw_loose_comma:
 68 |             delimiter = ","
 69 |         else:
 70 |             delimiter = " "
 71 |         for chunk in to_be_split:
 72 |             words.extend(split_strip(chunk, delimiter))
 73 |     words = list(set(words))
 74 |     words.sort()
 75 |     return words
 76 | 
 77 | 
 78 | def split_strip(string, delimiter=","):
 79 |     """
 80 |     Splits ``string`` on ``delimiter``, stripping each resulting string
 81 |     and returning a list of non-empty strings.
 82 | 
 83 |     Ported from Jonathan Buchanan's `django-tagging
 84 |     <http://django-tagging.googlecode.com/>`_
 85 |     """
 86 |     if not string:
 87 |         return []
 88 | 
 89 |     words = [w.strip() for w in string.split(delimiter)]
 90 |     return [w for w in words if w]
 91 | 
 92 | 
 93 | def _edit_string_for_tags(tags):
 94 |     """
 95 |     Given list of ``Tag`` instances, creates a string representation of
 96 |     the list suitable for editing by the user, such that submitting the
 97 |     given string representation back without changing it will give the
 98 |     same list of tags.
 99 | 
100 |     Tag names which contain commas will be double quoted.
101 | 
102 |     If any tag name which isn't being quoted contains whitespace, the
103 |     resulting string of tag names will be comma-delimited, otherwise
104 |     it will be space-delimited.
105 | 
106 |     Ported from Jonathan Buchanan's `django-tagging
107 |     <http://django-tagging.googlecode.com/>`_
108 |     """
109 |     names = []
110 |     for tag in tags:
111 |         name = tag.name
112 |         if "," in name or " " in name:
113 |             names.append('"%s"' % name)
114 |         else:
115 |             names.append(name)
116 |     return ", ".join(sorted(names))
117 | 
118 | 
119 | def require_instance_manager(func):
120 |     @wraps(func)
121 |     def inner(self, *args, **kwargs):
122 |         if self.instance is None:
123 |             raise TypeError("Can't call %s with a non-instance manager" % func.__name__)
124 |         return func(self, *args, **kwargs)
125 | 
126 |     return inner
127 | 
128 | 
129 | def get_func(key, default):
130 |     func_path = getattr(settings, key, None)
131 |     return default if func_path is None else import_string(func_path)
132 | 
133 | 
134 | def parse_tags(tagstring):
135 |     func = get_func("TAGGIT_TAGS_FROM_STRING", _parse_tags)
136 |     return func(tagstring)
137 | 
138 | 
139 | def edit_string_for_tags(tags):
140 |     func = get_func("TAGGIT_STRING_FROM_TAGS", _edit_string_for_tags)
141 |     return func(tags)
142 | 


--------------------------------------------------------------------------------
/taggit/views.py:
--------------------------------------------------------------------------------
 1 | from django.contrib.contenttypes.models import ContentType
 2 | from django.shortcuts import get_object_or_404
 3 | from django.views.generic.list import ListView
 4 | 
 5 | from taggit.models import Tag, TaggedItem
 6 | 
 7 | 
 8 | def tagged_object_list(request, slug, queryset, **kwargs):
 9 |     if callable(queryset):
10 |         queryset = queryset()
11 |     kwargs["slug"] = slug
12 |     tag_list_view = type(
13 |         "TagListView",
14 |         (TagListMixin, ListView),
15 |         {"model": queryset.model, "queryset": queryset},
16 |     )
17 |     return tag_list_view.as_view()(request, **kwargs)
18 | 
19 | 
20 | class TagListMixin:
21 |     tag_suffix = "_tag"
22 | 
23 |     def dispatch(self, request, *args, **kwargs):
24 |         slug = kwargs.pop("slug")
25 |         self.tag = get_object_or_404(Tag, slug=slug)
26 |         return super().dispatch(request, *args, **kwargs)
27 | 
28 |     def get_queryset(self, **kwargs):
29 |         qs = super().get_queryset(**kwargs)
30 |         return qs.filter(
31 |             pk__in=TaggedItem.objects.filter(
32 |                 tag=self.tag, content_type=ContentType.objects.get_for_model(qs.model)
33 |             ).values_list("object_id", flat=True)
34 |         )
35 | 
36 |     def get_template_names(self):
37 |         if self.tag_suffix:
38 |             self.template_name_suffix = self.tag_suffix + self.template_name_suffix
39 |         return super().get_template_names()
40 | 
41 |     def get_context_data(self, **kwargs):
42 |         context = super().get_context_data(**kwargs)
43 |         if "extra_context" not in context:
44 |             context["extra_context"] = {}
45 |         context["extra_context"]["tag"] = self.tag
46 |         return context
47 | 


--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/tests/__init__.py


--------------------------------------------------------------------------------
/tests/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | 
3 | from . import models
4 | 
5 | admin.site.register(models.Food)
6 | 


--------------------------------------------------------------------------------
/tests/custom_parser.py:
--------------------------------------------------------------------------------
1 | def comma_splitter(tag_string):
2 |     return [t.strip() for t in tag_string.split(",") if t.strip()]
3 | 
4 | 
5 | def comma_joiner(tags):
6 |     return ", ".join(t.name for t in tags)
7 | 


--------------------------------------------------------------------------------
/tests/forms.py:
--------------------------------------------------------------------------------
 1 | from django import forms
 2 | 
 3 | from .models import (
 4 |     BlankTagModel,
 5 |     CustomPKFood,
 6 |     DirectCustomPKFood,
 7 |     DirectFood,
 8 |     Food,
 9 |     OfficialFood,
10 | )
11 | 
12 | 
13 | class BlankTagForm(forms.ModelForm):
14 |     class Meta:
15 |         model = BlankTagModel
16 |         fields = "__all__"
17 | 
18 | 
19 | class FoodForm(forms.ModelForm):
20 |     class Meta:
21 |         model = Food
22 |         fields = "__all__"
23 | 
24 | 
25 | class DirectFoodForm(forms.ModelForm):
26 |     class Meta:
27 |         model = DirectFood
28 |         fields = "__all__"
29 | 
30 | 
31 | class DirectCustomPKFoodForm(forms.ModelForm):
32 |     class Meta:
33 |         model = DirectCustomPKFood
34 |         fields = "__all__"
35 | 
36 | 
37 | class CustomPKFoodForm(forms.ModelForm):
38 |     class Meta:
39 |         model = CustomPKFood
40 |         fields = "__all__"
41 | 
42 | 
43 | class OfficialFoodForm(forms.ModelForm):
44 |     class Meta:
45 |         model = OfficialFood
46 |         fields = "__all__"
47 | 


--------------------------------------------------------------------------------
/tests/migrations/0002_auto_20200214_1129.py:
--------------------------------------------------------------------------------
  1 | # Generated by Django 3.0.3 on 2020-02-14 11:29
  2 | 
  3 | import uuid
  4 | 
  5 | import django.utils.timezone
  6 | from django.db import migrations, models
  7 | 
  8 | import taggit.managers
  9 | 
 10 | 
 11 | class Migration(migrations.Migration):
 12 |     dependencies = [
 13 |         ("taggit", "0003_taggeditem_add_unique_index"),
 14 |         ("contenttypes", "0002_remove_content_type_name"),
 15 |         ("tests", "0001_initial"),
 16 |     ]
 17 | 
 18 |     operations = [
 19 |         migrations.CreateModel(
 20 |             name="UUIDPet",
 21 |             fields=[
 22 |                 (
 23 |                     "id",
 24 |                     models.UUIDField(
 25 |                         default=uuid.uuid4,
 26 |                         editable=False,
 27 |                         primary_key=True,
 28 |                         serialize=False,
 29 |                     ),
 30 |                 ),
 31 |                 ("name", models.CharField(max_length=50)),
 32 |                 ("created_at", models.DateTimeField(auto_now_add=True)),
 33 |             ],
 34 |             options={"ordering": ["created_at"]},
 35 |         ),
 36 |         migrations.AlterModelOptions(
 37 |             name="uuidfood", options={"ordering": ["created_at"]}
 38 |         ),
 39 |         migrations.AddField(
 40 |             model_name="blanktagmodel",
 41 |             name="tags",
 42 |             field=taggit.managers.TaggableManager(
 43 |                 blank=True,
 44 |                 help_text="A comma-separated list of tags.",
 45 |                 through="taggit.TaggedItem",
 46 |                 to="taggit.Tag",
 47 |                 verbose_name="Tags",
 48 |             ),
 49 |         ),
 50 |         migrations.AddField(
 51 |             model_name="uuidfood",
 52 |             name="created_at",
 53 |             field=models.DateTimeField(
 54 |                 auto_now_add=True, default=django.utils.timezone.now
 55 |             ),
 56 |             preserve_default=False,
 57 |         ),
 58 |         migrations.AlterField(
 59 |             model_name="taggedtrackedfood",
 60 |             name="tag",
 61 |             field=models.ForeignKey(
 62 |                 on_delete=django.db.models.deletion.CASCADE,
 63 |                 related_name="taggedtrackedfood_items",
 64 |                 to="tests.TrackedTag",
 65 |             ),
 66 |         ),
 67 |         migrations.AlterField(
 68 |             model_name="taggedtrackedpet",
 69 |             name="tag",
 70 |             field=models.ForeignKey(
 71 |                 on_delete=django.db.models.deletion.CASCADE,
 72 |                 related_name="taggedtrackedpet_items",
 73 |                 to="tests.TrackedTag",
 74 |             ),
 75 |         ),
 76 |         migrations.AlterUniqueTogether(
 77 |             name="uuidtaggeditem",
 78 |             unique_together={("content_type", "object_id", "tag")},
 79 |         ),
 80 |         migrations.CreateModel(
 81 |             name="UUIDHousePet",
 82 |             fields=[
 83 |                 (
 84 |                     "uuidpet_ptr",
 85 |                     models.OneToOneField(
 86 |                         auto_created=True,
 87 |                         on_delete=django.db.models.deletion.CASCADE,
 88 |                         parent_link=True,
 89 |                         primary_key=True,
 90 |                         serialize=False,
 91 |                         to="tests.UUIDPet",
 92 |                     ),
 93 |                 ),
 94 |                 ("trained", models.BooleanField(default=False)),
 95 |             ],
 96 |             bases=("tests.uuidpet",),
 97 |         ),
 98 |         migrations.AddField(
 99 |             model_name="uuidpet",
100 |             name="tags",
101 |             field=taggit.managers.TaggableManager(
102 |                 help_text="A comma-separated list of tags.",
103 |                 through="tests.UUIDTaggedItem",
104 |                 to="tests.UUIDTag",
105 |                 verbose_name="Tags",
106 |             ),
107 |         ),
108 |     ]
109 | 


--------------------------------------------------------------------------------
/tests/migrations/0003_auto_20210310_0918.py:
--------------------------------------------------------------------------------
 1 | # Generated by Django 3.1.7 on 2021-03-10 09:18
 2 | 
 3 | import django.db.models.deletion
 4 | from django.db import migrations, models
 5 | 
 6 | import taggit.managers
 7 | 
 8 | 
 9 | class Migration(migrations.Migration):
10 |     dependencies = [
11 |         ("taggit", "0003_taggeditem_add_unique_index"),
12 |         ("contenttypes", "0002_remove_content_type_name"),
13 |         ("tests", "0002_auto_20200214_1129"),
14 |     ]
15 | 
16 |     operations = [
17 |         migrations.CreateModel(
18 |             name="BaseFood",
19 |             fields=[
20 |                 (
21 |                     "id",
22 |                     models.AutoField(
23 |                         auto_created=True,
24 |                         primary_key=True,
25 |                         serialize=False,
26 |                         verbose_name="ID",
27 |                     ),
28 |                 ),
29 |                 ("name", models.CharField(max_length=50)),
30 |             ],
31 |         ),
32 |         migrations.CreateModel(
33 |             name="MultiInheritanceFood",
34 |             fields=[
35 |                 (
36 |                     "basefood_ptr",
37 |                     models.OneToOneField(
38 |                         auto_created=True,
39 |                         on_delete=django.db.models.deletion.CASCADE,
40 |                         parent_link=True,
41 |                         primary_key=True,
42 |                         serialize=False,
43 |                         to="tests.basefood",
44 |                     ),
45 |                 ),
46 |             ],
47 |             bases=("tests.basefood",),
48 |         ),
49 |         migrations.CreateModel(
50 |             name="MultiInheritanceLazyResolutionFoodTag",
51 |             fields=[
52 |                 (
53 |                     "id",
54 |                     models.AutoField(
55 |                         auto_created=True,
56 |                         primary_key=True,
57 |                         serialize=False,
58 |                         verbose_name="ID",
59 |                     ),
60 |                 ),
61 |                 (
62 |                     "tag",
63 |                     models.ForeignKey(
64 |                         on_delete=django.db.models.deletion.CASCADE,
65 |                         related_name="tests_multiinheritancelazyresolutionfoodtag_items",
66 |                         to="taggit.tag",
67 |                     ),
68 |                 ),
69 |                 (
70 |                     "content_object",
71 |                     models.ForeignKey(
72 |                         on_delete=django.db.models.deletion.CASCADE,
73 |                         related_name="tagged_items",
74 |                         to="tests.multiinheritancefood",
75 |                     ),
76 |                 ),
77 |             ],
78 |             options={
79 |                 "unique_together": {("content_object", "tag")},
80 |             },
81 |         ),
82 |         migrations.AddField(
83 |             model_name="multiinheritancefood",
84 |             name="tags",
85 |             field=taggit.managers.TaggableManager(
86 |                 help_text="A comma-separated list of tags.",
87 |                 through="tests.MultiInheritanceLazyResolutionFoodTag",
88 |                 to="taggit.Tag",
89 |                 verbose_name="Tags",
90 |             ),
91 |         ),
92 |     ]
93 | 


--------------------------------------------------------------------------------
/tests/migrations/0004_auto_20210619_0826.py:
--------------------------------------------------------------------------------
  1 | # Generated by Django 3.2.4 on 2021-06-19 08:26
  2 | 
  3 | import django.db.models.deletion
  4 | from django.db import migrations, models
  5 | 
  6 | import taggit.managers
  7 | 
  8 | 
  9 | class Migration(migrations.Migration):
 10 |     dependencies = [
 11 |         ("contenttypes", "0002_remove_content_type_name"),
 12 |         ("taggit", "0003_taggeditem_add_unique_index"),
 13 |         ("tests", "0003_auto_20210310_0918"),
 14 |     ]
 15 | 
 16 |     operations = [
 17 |         migrations.AlterField(
 18 |             model_name="officialtag",
 19 |             name="name",
 20 |             field=models.CharField(max_length=100, unique=True, verbose_name="name"),
 21 |         ),
 22 |         migrations.AlterField(
 23 |             model_name="officialtag",
 24 |             name="slug",
 25 |             field=models.SlugField(max_length=100, unique=True, verbose_name="slug"),
 26 |         ),
 27 |         migrations.AlterField(
 28 |             model_name="officialthroughmodel",
 29 |             name="content_type",
 30 |             field=models.ForeignKey(
 31 |                 on_delete=django.db.models.deletion.CASCADE,
 32 |                 related_name="tests_officialthroughmodel_tagged_items",
 33 |                 to="contenttypes.contenttype",
 34 |                 verbose_name="content type",
 35 |             ),
 36 |         ),
 37 |         migrations.AlterField(
 38 |             model_name="officialthroughmodel",
 39 |             name="object_id",
 40 |             field=models.IntegerField(db_index=True, verbose_name="object ID"),
 41 |         ),
 42 |         migrations.AlterField(
 43 |             model_name="taggedcustompk",
 44 |             name="content_type",
 45 |             field=models.ForeignKey(
 46 |                 on_delete=django.db.models.deletion.CASCADE,
 47 |                 related_name="tests_taggedcustompk_tagged_items",
 48 |                 to="contenttypes.contenttype",
 49 |                 verbose_name="content type",
 50 |             ),
 51 |         ),
 52 |         migrations.AlterField(
 53 |             model_name="throughgfk",
 54 |             name="content_type",
 55 |             field=models.ForeignKey(
 56 |                 on_delete=django.db.models.deletion.CASCADE,
 57 |                 related_name="tests_throughgfk_tagged_items",
 58 |                 to="contenttypes.contenttype",
 59 |                 verbose_name="content type",
 60 |             ),
 61 |         ),
 62 |         migrations.AlterField(
 63 |             model_name="throughgfk",
 64 |             name="object_id",
 65 |             field=models.IntegerField(db_index=True, verbose_name="object ID"),
 66 |         ),
 67 |         migrations.AlterField(
 68 |             model_name="trackedtag",
 69 |             name="name",
 70 |             field=models.CharField(max_length=100, unique=True, verbose_name="name"),
 71 |         ),
 72 |         migrations.AlterField(
 73 |             model_name="trackedtag",
 74 |             name="slug",
 75 |             field=models.SlugField(max_length=100, unique=True, verbose_name="slug"),
 76 |         ),
 77 |         migrations.AlterField(
 78 |             model_name="uuidtag",
 79 |             name="name",
 80 |             field=models.CharField(max_length=100, unique=True, verbose_name="name"),
 81 |         ),
 82 |         migrations.AlterField(
 83 |             model_name="uuidtag",
 84 |             name="slug",
 85 |             field=models.SlugField(max_length=100, unique=True, verbose_name="slug"),
 86 |         ),
 87 |         migrations.AlterField(
 88 |             model_name="uuidtaggeditem",
 89 |             name="content_type",
 90 |             field=models.ForeignKey(
 91 |                 on_delete=django.db.models.deletion.CASCADE,
 92 |                 related_name="tests_uuidtaggeditem_tagged_items",
 93 |                 to="contenttypes.contenttype",
 94 |                 verbose_name="content type",
 95 |             ),
 96 |         ),
 97 |         migrations.AlterField(
 98 |             model_name="uuidtaggeditem",
 99 |             name="object_id",
100 |             field=models.UUIDField(db_index=True, verbose_name="object ID"),
101 |         ),
102 |         migrations.CreateModel(
103 |             name="TestModel",
104 |             fields=[
105 |                 (
106 |                     "id",
107 |                     models.AutoField(
108 |                         auto_created=True,
109 |                         primary_key=True,
110 |                         serialize=False,
111 |                         verbose_name="ID",
112 |                     ),
113 |                 ),
114 |                 (
115 |                     "tags",
116 |                     taggit.managers.TaggableManager(
117 |                         help_text="A comma-separated list of tags.",
118 |                         through="taggit.TaggedItem",
119 |                         to="taggit.Tag",
120 |                         verbose_name="Tags",
121 |                     ),
122 |                 ),
123 |             ],
124 |         ),
125 |     ]
126 | 


--------------------------------------------------------------------------------
/tests/migrations/0005_auto_20210713_2301.py:
--------------------------------------------------------------------------------
 1 | # Generated by Django 3.2.4 on 2021-06-19 08:26
 2 | 
 3 | from django.db import migrations, models
 4 | 
 5 | import taggit.managers
 6 | 
 7 | 
 8 | class Migration(migrations.Migration):
 9 |     dependencies = [
10 |         ("tests", "0004_auto_20210619_0826"),
11 |     ]
12 | 
13 |     operations = [
14 |         migrations.CreateModel(
15 |             name="OrderedModel",
16 |             fields=[
17 |                 (
18 |                     "id",
19 |                     models.AutoField(
20 |                         auto_created=True,
21 |                         primary_key=True,
22 |                         serialize=False,
23 |                         verbose_name="ID",
24 |                     ),
25 |                 ),
26 |                 (
27 |                     "tags",
28 |                     taggit.managers.TaggableManager(
29 |                         help_text="A comma-separated list of tags.",
30 |                         through="taggit.TaggedItem",
31 |                         to="taggit.Tag",
32 |                         verbose_name="Tags",
33 |                     ),
34 |                 ),
35 |             ],
36 |         ),
37 |     ]
38 | 


--------------------------------------------------------------------------------
/tests/migrations/0006_auto_20230622_0834.py:
--------------------------------------------------------------------------------
  1 | # Generated by Django 3.2.19 on 2023-06-22 08:34
  2 | 
  3 | import django.db.models.deletion
  4 | from django.db import migrations, models
  5 | 
  6 | import taggit.managers
  7 | 
  8 | 
  9 | class Migration(migrations.Migration):
 10 |     dependencies = [
 11 |         ("contenttypes", "0002_remove_content_type_name"),
 12 |         ("tests", "0005_auto_20210713_2301"),
 13 |     ]
 14 | 
 15 |     operations = [
 16 |         migrations.CreateModel(
 17 |             name="TenantTag",
 18 |             fields=[
 19 |                 (
 20 |                     "id",
 21 |                     models.AutoField(
 22 |                         auto_created=True,
 23 |                         primary_key=True,
 24 |                         serialize=False,
 25 |                         verbose_name="ID",
 26 |                     ),
 27 |                 ),
 28 |                 ("name", models.CharField(max_length=100)),
 29 |                 ("slug", models.SlugField(allow_unicode=True, max_length=100)),
 30 |                 ("tenant_id", models.IntegerField()),
 31 |             ],
 32 |             options={
 33 |                 "unique_together": {("name", "tenant_id"), ("slug", "tenant_id")},
 34 |             },
 35 |         ),
 36 |         migrations.AlterField(
 37 |             model_name="officialtag",
 38 |             name="slug",
 39 |             field=models.SlugField(
 40 |                 allow_unicode=True, max_length=100, unique=True, verbose_name="slug"
 41 |             ),
 42 |         ),
 43 |         migrations.AlterField(
 44 |             model_name="trackedtag",
 45 |             name="slug",
 46 |             field=models.SlugField(
 47 |                 allow_unicode=True, max_length=100, unique=True, verbose_name="slug"
 48 |             ),
 49 |         ),
 50 |         migrations.AlterField(
 51 |             model_name="uuidtag",
 52 |             name="slug",
 53 |             field=models.SlugField(
 54 |                 allow_unicode=True, max_length=100, unique=True, verbose_name="slug"
 55 |             ),
 56 |         ),
 57 |         migrations.CreateModel(
 58 |             name="TenantTaggedItem",
 59 |             fields=[
 60 |                 (
 61 |                     "id",
 62 |                     models.AutoField(
 63 |                         auto_created=True,
 64 |                         primary_key=True,
 65 |                         serialize=False,
 66 |                         verbose_name="ID",
 67 |                     ),
 68 |                 ),
 69 |                 (
 70 |                     "object_id",
 71 |                     models.IntegerField(db_index=True, verbose_name="object ID"),
 72 |                 ),
 73 |                 (
 74 |                     "content_type",
 75 |                     models.ForeignKey(
 76 |                         on_delete=django.db.models.deletion.CASCADE,
 77 |                         related_name="tests_tenanttaggeditem_tagged_items",
 78 |                         to="contenttypes.contenttype",
 79 |                         verbose_name="content type",
 80 |                     ),
 81 |                 ),
 82 |                 (
 83 |                     "tag",
 84 |                     models.ForeignKey(
 85 |                         on_delete=django.db.models.deletion.CASCADE,
 86 |                         related_name="tests_tenanttaggeditem_items",
 87 |                         to="tests.tenanttag",
 88 |                     ),
 89 |                 ),
 90 |             ],
 91 |             options={
 92 |                 "abstract": False,
 93 |             },
 94 |         ),
 95 |         migrations.CreateModel(
 96 |             name="TenantModel",
 97 |             fields=[
 98 |                 (
 99 |                     "id",
100 |                     models.AutoField(
101 |                         auto_created=True,
102 |                         primary_key=True,
103 |                         serialize=False,
104 |                         verbose_name="ID",
105 |                     ),
106 |                 ),
107 |                 ("name", models.CharField(max_length=50)),
108 |                 (
109 |                     "tags",
110 |                     taggit.managers.TaggableManager(
111 |                         help_text="A comma-separated list of tags.",
112 |                         through="tests.TenantTaggedItem",
113 |                         to="tests.TenantTag",
114 |                         verbose_name="Tags",
115 |                     ),
116 |                 ),
117 |             ],
118 |         ),
119 |     ]
120 | 


--------------------------------------------------------------------------------
/tests/migrations/0007_alter_multiinheritancelazyresolutionfoodtag_tag_and_more.py:
--------------------------------------------------------------------------------
  1 | # Generated by Django 4.2 on 2023-07-24 00:05
  2 | 
  3 | import django.db.models.deletion
  4 | from django.db import migrations, models
  5 | 
  6 | 
  7 | class Migration(migrations.Migration):
  8 |     dependencies = [
  9 |         ("contenttypes", "0002_remove_content_type_name"),
 10 |         ("taggit", "0005_auto_20220424_2025"),
 11 |         ("tests", "0006_auto_20230622_0834"),
 12 |     ]
 13 | 
 14 |     operations = [
 15 |         migrations.AlterField(
 16 |             model_name="multiinheritancelazyresolutionfoodtag",
 17 |             name="tag",
 18 |             field=models.ForeignKey(
 19 |                 on_delete=django.db.models.deletion.CASCADE,
 20 |                 related_name="%(app_label)s_%(class)s_items",
 21 |                 to="taggit.tag",
 22 |             ),
 23 |         ),
 24 |         migrations.AlterField(
 25 |             model_name="officialthroughmodel",
 26 |             name="content_type",
 27 |             field=models.ForeignKey(
 28 |                 on_delete=django.db.models.deletion.CASCADE,
 29 |                 related_name="%(app_label)s_%(class)s_tagged_items",
 30 |                 to="contenttypes.contenttype",
 31 |                 verbose_name="content type",
 32 |             ),
 33 |         ),
 34 |         migrations.AlterField(
 35 |             model_name="taggedcustompk",
 36 |             name="content_type",
 37 |             field=models.ForeignKey(
 38 |                 on_delete=django.db.models.deletion.CASCADE,
 39 |                 related_name="%(app_label)s_%(class)s_tagged_items",
 40 |                 to="contenttypes.contenttype",
 41 |                 verbose_name="content type",
 42 |             ),
 43 |         ),
 44 |         migrations.AlterField(
 45 |             model_name="taggedcustompk",
 46 |             name="tag",
 47 |             field=models.ForeignKey(
 48 |                 on_delete=django.db.models.deletion.CASCADE,
 49 |                 related_name="%(app_label)s_%(class)s_items",
 50 |                 to="taggit.tag",
 51 |             ),
 52 |         ),
 53 |         migrations.AlterField(
 54 |             model_name="taggedcustompkfood",
 55 |             name="tag",
 56 |             field=models.ForeignKey(
 57 |                 on_delete=django.db.models.deletion.CASCADE,
 58 |                 related_name="%(app_label)s_%(class)s_items",
 59 |                 to="taggit.tag",
 60 |             ),
 61 |         ),
 62 |         migrations.AlterField(
 63 |             model_name="taggedcustompkpet",
 64 |             name="tag",
 65 |             field=models.ForeignKey(
 66 |                 on_delete=django.db.models.deletion.CASCADE,
 67 |                 related_name="%(app_label)s_%(class)s_items",
 68 |                 to="taggit.tag",
 69 |             ),
 70 |         ),
 71 |         migrations.AlterField(
 72 |             model_name="taggedfood",
 73 |             name="tag",
 74 |             field=models.ForeignKey(
 75 |                 on_delete=django.db.models.deletion.CASCADE,
 76 |                 related_name="%(app_label)s_%(class)s_items",
 77 |                 to="taggit.tag",
 78 |             ),
 79 |         ),
 80 |         migrations.AlterField(
 81 |             model_name="taggedpet",
 82 |             name="tag",
 83 |             field=models.ForeignKey(
 84 |                 on_delete=django.db.models.deletion.CASCADE,
 85 |                 related_name="%(app_label)s_%(class)s_items",
 86 |                 to="taggit.tag",
 87 |             ),
 88 |         ),
 89 |         migrations.AlterField(
 90 |             model_name="taggedtrackedfood",
 91 |             name="tag",
 92 |             field=models.ForeignKey(
 93 |                 on_delete=django.db.models.deletion.CASCADE,
 94 |                 related_name="%(class)s_items",
 95 |                 to="tests.trackedtag",
 96 |             ),
 97 |         ),
 98 |         migrations.AlterField(
 99 |             model_name="taggedtrackedpet",
100 |             name="tag",
101 |             field=models.ForeignKey(
102 |                 on_delete=django.db.models.deletion.CASCADE,
103 |                 related_name="%(class)s_items",
104 |                 to="tests.trackedtag",
105 |             ),
106 |         ),
107 |         migrations.AlterField(
108 |             model_name="tenanttaggeditem",
109 |             name="content_type",
110 |             field=models.ForeignKey(
111 |                 on_delete=django.db.models.deletion.CASCADE,
112 |                 related_name="%(app_label)s_%(class)s_tagged_items",
113 |                 to="contenttypes.contenttype",
114 |                 verbose_name="content type",
115 |             ),
116 |         ),
117 |         migrations.AlterField(
118 |             model_name="tenanttaggeditem",
119 |             name="tag",
120 |             field=models.ForeignKey(
121 |                 on_delete=django.db.models.deletion.CASCADE,
122 |                 related_name="%(app_label)s_%(class)s_items",
123 |                 to="tests.tenanttag",
124 |             ),
125 |         ),
126 |         migrations.AlterField(
127 |             model_name="through1",
128 |             name="tag",
129 |             field=models.ForeignKey(
130 |                 on_delete=django.db.models.deletion.CASCADE,
131 |                 related_name="%(app_label)s_%(class)s_items",
132 |                 to="taggit.tag",
133 |             ),
134 |         ),
135 |         migrations.AlterField(
136 |             model_name="through2",
137 |             name="tag",
138 |             field=models.ForeignKey(
139 |                 on_delete=django.db.models.deletion.CASCADE,
140 |                 related_name="%(app_label)s_%(class)s_items",
141 |                 to="taggit.tag",
142 |             ),
143 |         ),
144 |         migrations.AlterField(
145 |             model_name="throughgfk",
146 |             name="content_type",
147 |             field=models.ForeignKey(
148 |                 on_delete=django.db.models.deletion.CASCADE,
149 |                 related_name="%(app_label)s_%(class)s_tagged_items",
150 |                 to="contenttypes.contenttype",
151 |                 verbose_name="content type",
152 |             ),
153 |         ),
154 |         migrations.AlterField(
155 |             model_name="uuidtaggeditem",
156 |             name="content_type",
157 |             field=models.ForeignKey(
158 |                 on_delete=django.db.models.deletion.CASCADE,
159 |                 related_name="%(app_label)s_%(class)s_tagged_items",
160 |                 to="contenttypes.contenttype",
161 |                 verbose_name="content type",
162 |             ),
163 |         ),
164 |         migrations.AlterField(
165 |             model_name="uuidtaggeditem",
166 |             name="tag",
167 |             field=models.ForeignKey(
168 |                 on_delete=django.db.models.deletion.CASCADE,
169 |                 related_name="%(app_label)s_%(class)s_items",
170 |                 to="tests.uuidtag",
171 |             ),
172 |         ),
173 |     ]
174 | 


--------------------------------------------------------------------------------
/tests/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzband/django-taggit/7af1e7415225ef00c801a7e687137a2a0eb9f323/tests/migrations/__init__.py


--------------------------------------------------------------------------------
/tests/serializers.py:
--------------------------------------------------------------------------------
 1 | from rest_framework import serializers
 2 | 
 3 | from taggit.serializers import TaggitSerializer, TagListSerializerField
 4 | 
 5 | from .models import TestModel
 6 | 
 7 | 
 8 | class TestModelSerializer(TaggitSerializer, serializers.ModelSerializer):
 9 |     tags = TagListSerializerField()
10 | 
11 |     class Meta:
12 |         model = TestModel
13 |         fields = "__all__"
14 | 


--------------------------------------------------------------------------------
/tests/settings.py:
--------------------------------------------------------------------------------
 1 | SECRET_KEY = "secretkey"
 2 | 
 3 | INSTALLED_APPS = [
 4 |     "django.contrib.admin",
 5 |     "django.contrib.auth",
 6 |     "django.contrib.contenttypes",
 7 |     "django.contrib.sessions",
 8 |     "django.contrib.messages",
 9 |     "django.contrib.staticfiles",
10 |     "taggit",
11 |     "tests",
12 | ]
13 | 
14 | MIDDLEWARE = [
15 |     "django.middleware.security.SecurityMiddleware",
16 |     "django.contrib.sessions.middleware.SessionMiddleware",
17 |     "django.middleware.common.CommonMiddleware",
18 |     "django.middleware.csrf.CsrfViewMiddleware",
19 |     "django.contrib.auth.middleware.AuthenticationMiddleware",
20 |     "django.contrib.messages.middleware.MessageMiddleware",
21 |     "django.middleware.clickjacking.XFrameOptionsMiddleware",
22 | ]
23 | 
24 | ROOT_URLCONF = "tests.urls"
25 | 
26 | TEMPLATES = [
27 |     {
28 |         "BACKEND": "django.template.backends.django.DjangoTemplates",
29 |         "APP_DIRS": True,
30 |         "OPTIONS": {
31 |             "context_processors": [
32 |                 "django.template.context_processors.debug",
33 |                 "django.template.context_processors.request",
34 |                 "django.contrib.auth.context_processors.auth",
35 |                 "django.contrib.messages.context_processors.messages",
36 |             ]
37 |         },
38 |     }
39 | ]
40 | 
41 | DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}
42 | 
43 | STATIC_URL = "/static/"
44 | 
45 | DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
46 | 
47 | USE_TZ = True
48 | 


--------------------------------------------------------------------------------
/tests/templates/tests/food_tag_list.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 | <head>
 4 |     <meta charset="UTF-8">
 5 |     <title>Title</title>
 6 | </head>
 7 | <body>
 8 | 
 9 | </body>
10 | </html>
11 | 


--------------------------------------------------------------------------------
/tests/test_admin.py:
--------------------------------------------------------------------------------
 1 | from django.contrib.auth.models import User
 2 | from django.test import TestCase
 3 | from django.urls import reverse
 4 | 
 5 | from taggit.models import Tag
 6 | 
 7 | from .models import Food
 8 | 
 9 | 
10 | class AdminTest(TestCase):
11 |     def setUp(self):
12 |         super().setUp()
13 |         self.apple = Food.objects.create(name="apple")
14 |         self.apple.tags.add("Red", "red")
15 |         self.pear = Food.objects.create(name="pear")
16 |         self.pear.tags.add("red", "RED")
17 |         self.peach = Food.objects.create(name="peach")
18 |         self.peach.tags.add("red", "Yellow")
19 | 
20 |         user = User.objects.create_superuser(
21 |             username="admin", email="admin@mailinator.com", password="password"
22 |         )
23 |         self.client.force_login(user)
24 | 
25 |     def test_get_changelist(self):
26 |         response = self.client.get(reverse("admin:tests_food_changelist"))
27 |         self.assertEqual(response.status_code, 200)
28 | 
29 |     def test_get_add(self):
30 |         response = self.client.get(reverse("admin:tests_food_add"))
31 |         self.assertEqual(response.status_code, 200)
32 | 
33 |     def test_get_history(self):
34 |         response = self.client.get(
35 |             reverse("admin:tests_food_history", args=(self.apple.pk,))
36 |         )
37 |         self.assertEqual(response.status_code, 200)
38 | 
39 |     def test_get_delete(self):
40 |         response = self.client.get(
41 |             reverse("admin:tests_food_delete", args=(self.apple.pk,))
42 |         )
43 |         self.assertEqual(response.status_code, 200)
44 | 
45 |     def test_get_change(self):
46 |         response = self.client.get(
47 |             reverse("admin:tests_food_change", args=(self.apple.pk,))
48 |         )
49 |         self.assertEqual(response.status_code, 200)
50 | 
51 |     def test_tag_merging(self):
52 |         response = self.client.get(reverse("admin:taggit_tag_changelist"))
53 | 
54 |         # merging red and RED into Red
55 |         pks_to_select = [Tag.objects.get(name="red").pk, Tag.objects.get(name="RED").pk]
56 |         response = self.client.post(
57 |             reverse("admin:taggit_tag_changelist"),
58 |             data={"action": "render_tag_form", "_selected_action": pks_to_select},
59 |         )
60 |         # we're redirecting
61 |         self.assertEqual(response.status_code, 302)
62 |         self.assertEqual(response.url, reverse("admin:taggit_tag_merge_tags"))
63 |         # make sure what we expected got into the session keys
64 |         assert "selected_tag_ids" in self.client.session.keys()
65 |         self.assertEqual(
66 |             self.client.session["selected_tag_ids"], ",".join(map(str, pks_to_select))
67 |         )
68 | 
69 |         # let's do the actual merge operation
70 |         response = self.client.post(
71 |             reverse("admin:taggit_tag_merge_tags"), {"new_tag_name": "Red"}
72 |         )
73 |         self.assertEqual(response.status_code, 302)
74 | 
75 |         # time to check the result of the merges
76 |         self.assertSetEqual({tag.name for tag in self.apple.tags.all()}, {"Red"})
77 |         self.assertSetEqual({tag.name for tag in self.pear.tags.all()}, {"Red"})
78 |         self.assertSetEqual(
79 |             {tag.name for tag in self.peach.tags.all()}, {"Yellow", "Red"}
80 |         )
81 | 


--------------------------------------------------------------------------------
/tests/test_deduplicate_tags.py:
--------------------------------------------------------------------------------
 1 | from io import StringIO
 2 | 
 3 | from django.conf import settings
 4 | from django.core.management import call_command
 5 | from django.test import TestCase
 6 | 
 7 | from taggit.models import Tag, TaggedItem
 8 | from tests.models import Food, HousePet
 9 | 
10 | 
11 | class DeduplicateTagsTests(TestCase):
12 |     def setUp(self):
13 |         settings.TAGGIT_CASE_INSENSITIVE = True
14 | 
15 |         self.tag1 = Tag.objects.create(name="Python")
16 |         self.tag2 = Tag.objects.create(name="python")
17 |         self.tag3 = Tag.objects.create(name="PYTHON")
18 | 
19 |         self.food_item = Food.objects.create(name="Apple")
20 |         self.pet_item = HousePet.objects.create(name="Fido")
21 | 
22 |         self.food_item.tags.add(self.tag1)
23 |         self.pet_item.tags.add(self.tag2)
24 |         self.pet_item.tags.add(self.tag3)
25 | 
26 |     def test_deduplicate_tags(self):
27 |         self.assertEqual(Tag.objects.count(), 3)
28 |         self.assertEqual(TaggedItem.objects.count(), 3)
29 | 
30 |         out = StringIO()
31 |         call_command("deduplicate_tags", stdout=out)
32 | 
33 |         self.assertEqual(Tag.objects.count(), 1)
34 |         self.assertEqual(TaggedItem.objects.count(), 2)
35 | 
36 |         self.assertTrue(Tag.objects.filter(name__iexact="python").exists())
37 |         self.assertEqual(
38 |             TaggedItem.objects.filter(tag__name__iexact="python").count(), 2
39 |         )
40 | 
41 |         self.assertIn("Tag deduplication complete.", out.getvalue())
42 | 
43 |     def test_no_duplicates(self):
44 |         self.assertEqual(Tag.objects.count(), 3)
45 |         self.assertEqual(TaggedItem.objects.count(), 3)
46 | 
47 |         out = StringIO()
48 |         call_command("deduplicate_tags", stdout=out)
49 | 
50 |         self.assertEqual(Tag.objects.count(), 1)
51 |         self.assertEqual(TaggedItem.objects.count(), 2)
52 | 
53 |         self.assertTrue(Tag.objects.filter(name__iexact="python").exists())
54 |         self.assertEqual(
55 |             TaggedItem.objects.filter(tag__name__iexact="python").count(), 2
56 |         )
57 | 
58 |         self.assertIn("Tag deduplication complete.", out.getvalue())
59 | 
60 |     def test_taggit_case_insensitive_not_enabled(self):
61 |         settings.TAGGIT_CASE_INSENSITIVE = False
62 | 
63 |         out = StringIO()
64 |         call_command("deduplicate_tags", stdout=out)
65 | 
66 |         self.assertIn("TAGGIT_CASE_INSENSITIVE is not enabled.", out.getvalue())
67 | 
68 |         self.assertEqual(Tag.objects.count(), 3)
69 |         self.assertEqual(TaggedItem.objects.count(), 3)
70 | 


--------------------------------------------------------------------------------
/tests/test_forms.py:
--------------------------------------------------------------------------------
 1 | from django import forms
 2 | from django.test import TestCase
 3 | from django.test.utils import override_settings
 4 | 
 5 | from taggit.forms import TagField
 6 | from taggit.models import Tag
 7 | 
 8 | 
 9 | def _test_parse_tags(tagstring):
10 |     if "," in tagstring:
11 |         return tagstring.split(",")
12 |     else:
13 |         raise ValueError()
14 | 
15 | 
16 | @override_settings(TAGGIT_TAGS_FROM_STRING="tests.test_forms._test_parse_tags")
17 | class TagFieldTests(TestCase):
18 |     def test_should_return_error_on_clean_if_not_comma_separated(self):
19 |         class TestForm(forms.Form):
20 |             tag = TagField()
21 | 
22 |         excpected_error = "Please provide a comma-separated list of tags."
23 | 
24 |         form = TestForm({"tag": "not-comma-separated"})
25 | 
26 |         self.assertFalse(form.is_valid())
27 |         self.assertIn(excpected_error, form.errors["tag"])
28 | 
29 |     def test_should_always_return_False_on_has_change_if_disabled(self):
30 |         class TestForm(forms.Form):
31 |             tag = TagField(disabled=True)
32 | 
33 |         form = TestForm(initial={"tag": "foo,bar"}, data={"tag": ["a,b,c"]})
34 | 
35 |         self.assertTrue(form.is_valid())
36 |         self.assertFalse(form.has_changed())
37 | 
38 |     def test_should_return_True_if_form_has_changed(self):
39 |         class TestForm(forms.Form):
40 |             tag = TagField()
41 | 
42 |         form = TestForm(initial={"tag": [Tag(name="a")]}, data={"tag": ["b"]})
43 | 
44 |         self.assertTrue(form.has_changed())
45 | 
46 |     def test_should_return_False_if_form_has_not_changed(self):
47 |         class TestForm(forms.Form):
48 |             tag = TagField()
49 | 
50 |         form = TestForm(
51 |             initial={"tag": [Tag(name="foo-bar")]}, data={"tag": ["foo-bar"]}
52 |         )
53 | 
54 |         self.assertFalse(form.has_changed())
55 | 
56 |     def test_should_return_False_if_not_provided(self):
57 |         class TestForm(forms.Form):
58 |             tag = TagField()
59 | 
60 |         form = TestForm()
61 | 
62 |         self.assertFalse(form.has_changed())
63 | 


--------------------------------------------------------------------------------
/tests/test_models.py:
--------------------------------------------------------------------------------
  1 | from contextlib import contextmanager
  2 | from unittest import skipIf
  3 | 
  4 | from django.test import TestCase, override_settings
  5 | 
  6 | from taggit import models as taggit_models
  7 | from tests.models import TestModel
  8 | 
  9 | 
 10 | @contextmanager
 11 | def disable_unidecode():
 12 |     """
 13 |     Disable unidecode temporarily
 14 |     """
 15 |     old_installed_value = taggit_models.unidecode_installed
 16 |     taggit_models.unidecode_installed = False
 17 |     try:
 18 |         yield
 19 |     finally:
 20 |         taggit_models.unidecode_installed = old_installed_value
 21 | 
 22 | 
 23 | class TestTaggableManager(TestCase):
 24 |     def test_duplicates(self):
 25 |         sample_obj = TestModel.objects.create()
 26 |         sample_obj.tags.set(["green", "green"])
 27 |         desired_result = ["green"]
 28 |         self.assertEqual(desired_result, [tag.name for tag in sample_obj.tags.all()])
 29 | 
 30 | 
 31 | class TestSlugification(TestCase):
 32 |     def test_unicode_slugs(self):
 33 |         """
 34 |         Confirm the preservation of unicode in slugification by default
 35 |         """
 36 |         sample_obj = TestModel.objects.create()
 37 |         # a unicode tag will be slugified for space reasons but
 38 |         # unicode-ness will be kept by default
 39 |         sample_obj.tags.add("あい うえお")
 40 |         self.assertEqual([tag.slug for tag in sample_obj.tags.all()], ["あい-うえお"])
 41 | 
 42 |     def test_old_slugs_wo_unidecode(self):
 43 |         """
 44 |         Test that the setting that gives us the old slugification behavior
 45 |         is in place
 46 |         """
 47 |         with (
 48 |             disable_unidecode(),
 49 |             override_settings(TAGGIT_STRIP_UNICODE_WHEN_SLUGIFYING=True),
 50 |         ):
 51 |             sample_obj = TestModel.objects.create()
 52 |             sample_obj.tags.add("aあい うえおb")
 53 |             # when unidecode is not installed, the unicode ends up being passed directly
 54 |             # to slugify, and will get "wiped"
 55 |             self.assertEqual([tag.slug for tag in sample_obj.tags.all()], ["a-b"])
 56 | 
 57 |     @skipIf(
 58 |         not taggit_models.unidecode_installed,
 59 |         "This test requires unidecode to be installed",
 60 |     )
 61 |     def test_old_slugs_with_unidecode(self):
 62 |         """
 63 |         Test that the setting that gives us the old slugification behavior
 64 |         is in place
 65 |         """
 66 |         with override_settings(TAGGIT_STRIP_UNICODE_WHEN_SLUGIFYING=True):
 67 |             sample_obj = TestModel.objects.create()
 68 |             # unidecode will transform the tag on top of slugification
 69 |             sample_obj.tags.add("あい うえお")
 70 |             self.assertEqual([tag.slug for tag in sample_obj.tags.all()], ["ai-ueo"])
 71 | 
 72 | 
 73 | class TestPrefetchCache(TestCase):
 74 |     def setUp(self) -> None:
 75 |         sample_obj = TestModel.objects.create()
 76 |         sample_obj.tags.set(["1", "2", "3"])
 77 | 
 78 |     def test_cache_clears_on_add(self):
 79 |         """
 80 |         Test that the prefetch cache gets cleared on tag addition
 81 |         """
 82 |         sample_obj = TestModel.objects.prefetch_related("tags").get()
 83 |         self.assertTrue(sample_obj.tags.is_cached(sample_obj))
 84 | 
 85 |         sample_obj.tags.add("4")
 86 |         self.assertFalse(sample_obj.tags.is_cached(sample_obj))
 87 | 
 88 |     def test_cache_clears_on_remove(self):
 89 |         """
 90 |         Test that the prefetch cache gets cleared on tag removal
 91 |         """
 92 |         sample_obj = TestModel.objects.prefetch_related("tags").get()
 93 |         self.assertTrue(sample_obj.tags.is_cached(sample_obj))
 94 | 
 95 |         sample_obj.tags.remove("3")
 96 |         self.assertFalse(sample_obj.tags.is_cached(sample_obj))
 97 | 
 98 |     def test_cache_clears_on_clear(self):
 99 |         """
100 |         Test that the prefetch cache gets cleared when tags are cleared
101 |         """
102 |         sample_obj = TestModel.objects.prefetch_related("tags").get()
103 |         self.assertTrue(sample_obj.tags.is_cached(sample_obj))
104 | 
105 |         sample_obj.tags.clear()
106 |         self.assertFalse(sample_obj.tags.is_cached(sample_obj))
107 | 


--------------------------------------------------------------------------------
/tests/test_remove_orphaned_tags.py:
--------------------------------------------------------------------------------
 1 | from django.core.management import call_command
 2 | from django.test import TestCase
 3 | 
 4 | from taggit.models import Tag
 5 | from tests.models import Food, HousePet
 6 | 
 7 | 
 8 | class RemoveOrphanedTagsTests(TestCase):
 9 |     def setUp(self):
10 |         # Create some tags, some orphaned and some not
11 |         self.orphan_tag1 = Tag.objects.create(name="Orphan1")
12 |         self.orphan_tag2 = Tag.objects.create(name="Orphan2")
13 |         self.used_tag = Tag.objects.create(name="Used")
14 | 
15 |         # Create instances of Food and HousePet and tag them
16 |         self.food_item = Food.objects.create(name="Apple")
17 |         self.pet_item = HousePet.objects.create(name="Fido")
18 | 
19 |         self.food_item.tags.add(self.used_tag)
20 |         self.pet_item.tags.add(self.used_tag)
21 | 
22 |     def test_remove_orphaned_tags(self):
23 |         # Ensure the setup is correct
24 |         self.assertEqual(Tag.objects.count(), 3)
25 |         self.assertEqual(Tag.objects.filter(taggit_taggeditem_items=None).count(), 2)
26 | 
27 |         # Call the management command to remove orphaned tags
28 |         call_command("remove_orphaned_tags")
29 | 
30 |         # Check the count of tags after running the command
31 |         self.assertEqual(Tag.objects.count(), 1)
32 |         self.assertEqual(Tag.objects.filter(taggit_taggeditem_items=None).count(), 0)
33 | 
34 |         # Ensure that the used tag still exists
35 |         self.assertTrue(Tag.objects.filter(name="Used").exists())
36 |         self.assertFalse(Tag.objects.filter(name="Orphan1").exists())
37 |         self.assertFalse(Tag.objects.filter(name="Orphan2").exists())
38 | 
39 |     def test_no_orphaned_tags(self):
40 |         # Ensure the setup is correct
41 |         self.assertEqual(Tag.objects.count(), 3)
42 |         self.assertEqual(Tag.objects.filter(taggit_taggeditem_items=None).count(), 2)
43 | 
44 |         # Add used_tag to food_item to make no tags orphaned
45 |         self.food_item.tags.add(self.orphan_tag1)
46 |         self.food_item.tags.add(self.orphan_tag2)
47 | 
48 |         # Call the management command to remove orphaned tags
49 |         call_command("remove_orphaned_tags")
50 | 
51 |         # Check the count of tags after running the command
52 |         self.assertEqual(Tag.objects.count(), 3)
53 |         self.assertEqual(Tag.objects.filter(taggit_taggeditem_items=None).count(), 0)
54 | 
55 |         # Ensure all tags still exist
56 |         self.assertTrue(Tag.objects.filter(name="Used").exists())
57 |         self.assertTrue(Tag.objects.filter(name="Orphan1").exists())
58 |         self.assertTrue(Tag.objects.filter(name="Orphan2").exists())
59 | 


--------------------------------------------------------------------------------
/tests/test_serializers.py:
--------------------------------------------------------------------------------
  1 | """
  2 | test_django-taggit-serializer
  3 | ------------
  4 | 
  5 | Tests for `django-taggit-serializer` models module.
  6 | """
  7 | 
  8 | from django.test import TestCase
  9 | from rest_framework.exceptions import ValidationError
 10 | 
 11 | from taggit import serializers
 12 | 
 13 | from .models import TestModel
 14 | from .serializers import TestModelSerializer
 15 | 
 16 | 
 17 | class TestTaggit_serializer(TestCase):
 18 |     def test_taggit_serializer_field(self):
 19 |         correct_value = ["1", "2", "3"]
 20 |         serializer_field = serializers.TagListSerializerField()
 21 | 
 22 |         correct_value = serializer_field.to_internal_value(correct_value)
 23 | 
 24 |         assert type(correct_value) is list
 25 | 
 26 |         incorrect_value = "123"
 27 | 
 28 |         with self.assertRaises(ValidationError):
 29 |             incorrect_value = serializer_field.to_internal_value(incorrect_value)
 30 | 
 31 |         representation = serializer_field.to_representation(correct_value)
 32 |         self.assertIsInstance(representation, serializers.TagList)
 33 | 
 34 |     def test_taggit_serializer_update(self):
 35 |         """Test if serializer class is working properly on updating object"""
 36 |         request_data = {"tags": ["1", "2", "3"]}
 37 | 
 38 |         test_model = TestModel.objects.create()
 39 | 
 40 |         serializer = TestModelSerializer(test_model, data=request_data)
 41 |         serializer.is_valid()
 42 |         serializer.save()
 43 | 
 44 |         assert len(test_model.tags.all()) == len(request_data.get("tags"))
 45 | 
 46 |     def test_taggit_serializer_create(self):
 47 |         """
 48 |         Test if serializer class is working
 49 |         properly on creating a object
 50 |         """
 51 |         request_data = {"tags": ["1", "2", "3"]}
 52 | 
 53 |         serializer = TestModelSerializer(data=request_data)
 54 |         assert serializer.is_valid()
 55 |         test_model = serializer.save()
 56 | 
 57 |         assert len(test_model.tags.all()) == len(request_data.get("tags"))
 58 | 
 59 |     def test_taggit_serializer_create_with_string(self):
 60 |         """
 61 |         Test that we can pass in a string instead of an array for
 62 |         a tag list without issues
 63 |         """
 64 |         request_data = {"tags": '["1", "2", "3"]'}
 65 | 
 66 |         serializer = TestModelSerializer(data=request_data)
 67 |         assert serializer.is_valid(), serializer.errors
 68 |         test_model = serializer.save()
 69 | 
 70 |         assert {tag.name for tag in test_model.tags.all()} == {"1", "2", "3"}
 71 | 
 72 |     def test_taggit_removes_tags(self):
 73 |         """
 74 |         Test if the old assigned tags are removed
 75 |         """
 76 |         test_model = TestModel.objects.create()
 77 |         test_model.tags.add("1")
 78 | 
 79 |         request_data = {"tags": ["2", "3"]}
 80 | 
 81 |         serializer = TestModelSerializer(test_model, data=request_data)
 82 |         serializer.is_valid()
 83 |         test_model = serializer.save()
 84 | 
 85 |         assert TestModel.objects.filter(tags__name__in=["1"]).count() == 0
 86 |         assert TestModel.objects.filter(tags__name__in=["1", "2"]).count() == 1
 87 | 
 88 |     def test_returns_new_data_after_update(self):
 89 |         """
 90 |         Test if the serializer uses fresh data after updating prefetched fields
 91 |         """
 92 |         TestModel.objects.create().tags.add("1")
 93 | 
 94 |         test_model = TestModel.objects.prefetch_related("tags").get()
 95 | 
 96 |         assert TestModelSerializer(test_model).data["tags"] == ["1"]
 97 | 
 98 |         request_data = {"tags": ["2", "3"]}
 99 |         serializer = TestModelSerializer(test_model, data=request_data)
100 |         serializer.is_valid()
101 |         test_model = serializer.save()
102 | 
103 |         assert set(serializer.data["tags"]) == {"2", "3"}
104 | 


--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | import os.path
 3 | 
 4 | from django.test import TestCase
 5 | from django.utils import translation
 6 | 
 7 | from taggit.utils import split_strip
 8 | 
 9 | 
10 | class SplitStripTests(TestCase):
11 |     def test_should_return_empty_list_if_not_string(self):
12 |         result = split_strip(None)
13 | 
14 |         self.assertListEqual(result, [])
15 | 
16 |     def test_should_return_list_of_non_empty_words(self):
17 |         expected_result = ["foo", "bar"]
18 | 
19 |         result = split_strip("foo|bar||", delimiter="|")
20 | 
21 |         self.assertListEqual(result, expected_result)
22 | 
23 | 
24 | class TestLanguages(TestCase):
25 |     maxDiff = None
26 | 
27 |     def get_locale_dir(self):
28 |         return os.path.join(os.path.dirname(__file__), "..", "taggit", "locale")
29 | 
30 |     def test_language_file_integrity(self):
31 |         locale_dir = self.get_locale_dir()
32 |         for locale in os.listdir(locale_dir):
33 |             # attempt translation activation to confirm that the language files are working
34 |             with translation.override(locale):
35 |                 pass
36 | 


--------------------------------------------------------------------------------
/tests/urls.py:
--------------------------------------------------------------------------------
 1 | from django.contrib import admin
 2 | from django.urls import re_path
 3 | 
 4 | from .views import FoodTagListView
 5 | 
 6 | urlpatterns = [
 7 |     re_path(
 8 |         r"^food/tags/(?P<slug>[a-z0-9_-]+)/
quot;,
 9 |         FoodTagListView.as_view(),
10 |         name="food-tag-list",
11 |     ),
12 |     re_path(r"^admin/", admin.site.urls),
13 | ]
14 | 


--------------------------------------------------------------------------------
/tests/views.py:
--------------------------------------------------------------------------------
 1 | from django.views.generic.list import ListView
 2 | 
 3 | from taggit.views import TagListMixin
 4 | 
 5 | from .models import Food
 6 | 
 7 | 
 8 | class FoodTagListView(TagListMixin, ListView):
 9 |     model = Food
10 | 


--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
 1 | [tox]
 2 | minversion = 1.9
 3 | envlist =
 4 |     black
 5 |     flake8
 6 |     isort
 7 |     py{39,310,311,312}-dj{41,42}
 8 |     py{310,311,312}-dj{50}
 9 |     py{310,311,312}-djmain
10 |     docs
11 | 
12 | [gh-actions]
13 | python =
14 |     3.9: py39, black, flake8, isort
15 |     3.10: py310
16 |     3.11: py311
17 |     3.12: py312
18 | 
19 | [testenv]
20 | deps =
21 |     dj41: Django>=4.1,<4.2
22 |     dj42: Django>=4.2,<5.0
23 |     dj50: Django>=5.0,<5.1
24 |     djmain: https://github.com/django/django/archive/main.tar.gz
25 |     coverage
26 |     djangorestframework
27 |     unidecode
28 | setenv =
29 |     PYTHONWARNINGS=all
30 | commands =
31 |     coverage run -m django test --settings=tests.settings {posargs}
32 |     coverage report
33 |     coverage xml
34 | ignore_outcome =
35 |     djmain: True
36 | ignore_errors =
37 |     djmain: True
38 | 
39 | [testenv:black]
40 | basepython = python3
41 | skip_install = true
42 | deps = black
43 | commands = black --target-version=py38 --check --diff .
44 | 
45 | [testenv:flake8]
46 | basepython = python3
47 | skip_install = true
48 | deps = flake8
49 | commands = flake8
50 | 
51 | [testenv:isort]
52 | basepython = python3
53 | skip_install = true
54 | deps = isort>=5.0.2
55 | commands = isort --check-only --diff .
56 | 
57 | [testenv:docs]
58 | deps = sphinx
59 | commands = sphinx-build -n -W docs docs/_build
60 | 


--------------------------------------------------------------------------------