Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django 4.2 preparation #869

Merged
merged 14 commits into from
Oct 24, 2023
72 changes: 35 additions & 37 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,42 +20,40 @@ jobs:

matrix:
python-version:
- '3.7'
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- "3.8"
- "3.9"
- "3.10"
- "3.11"

steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Get pip cache dir
id: pip-cache
run: echo "::set-output name=dir::$(pip cache dir)"

- name: Cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }}
restore-keys: |
${{ matrix.python-version }}-v1-

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade tox tox-gh-actions

- name: Tox tests
run: tox -v

- name: Upload coverage
uses: codecov/codecov-action@v1
with:
name: Python ${{ matrix.python-version }}
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Get pip cache dir
id: pip-cache
run: echo "::set-output name=dir::$(pip cache dir)"

- name: Cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }}
restore-keys: |
${{ matrix.python-version }}-v1-

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade tox tox-gh-actions

- name: Tox tests
run: tox -v

- name: Upload coverage
uses: codecov/codecov-action@v1
with:
name: Python ${{ matrix.python-version }}
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ Changelog

(Unreleased)
~~~~~~~~~~~~
* **Backwards icompatible:** Rename the (``content_type``, ``object_id``) index on ``TaggedItem``.
It is very unlikely for this to affect your code itself, and a migration will rename the index. This should not cause any downtime according to my research (Postgres does not lock the table for index renames, and Oracle holds a tiny lock to do it, and the change is only to the metadata, so is not dependent on table size).

* **Backwards incompatible:** Remove the ``.indexed_together`` and ``.unique_together`` attributes on ``TaggedItem``

We are instead using ``constraints`` and ``indexes`` to set up these properties.
* Remove support for Django 3.2.
* Remove usage of deprecated APIs for Django 4.2
* Remove support for Python 3.7 (no code changes involved)
* Fix ``tag_kwargs`` and ``TAGGIT_CASE_INSENSITIVE=True`` discrepency.

4.0.0 (2023-05-04)
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ exclude = tests*
[flake8]
# E501: line too long
ignore = E501
exclude = .venv,.git,.tox

[isort]
profile = black
15 changes: 13 additions & 2 deletions taggit/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,19 @@ def get_queryset(self, extra_filters=None):
)

def get_prefetch_queryset(self, instances, queryset=None):
if queryset is not None:
raise ValueError("Custom queryset can't be used for this lookup.")
if queryset is None:
return self.get_prefetch_querysets(instances)
else:
return self.get_prefetch_querysets(instances, [queryset])

def get_prefetch_querysets(self, instances, querysets=None):
if querysets is not None:
# this queryset is meant to be used for filtering down the prefetch
# this work has not been done yet.
#
# Some hint from Django: asserting that len(querysets) == 1 if it's not None
# and then using that to filter down the qs
raise ValueError("Custom querysets can't be used for this lookup.")

instance = instances[0]
db = self._db or router.db_for_read(type(instance), instance=instance)
Expand Down
18 changes: 15 additions & 3 deletions taggit/migrations/0002_auto_20150616_2121.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
from django.db import migrations
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [("taggit", "0001_initial")]

operations = [
migrations.AlterIndexTogether(
name="taggeditem", index_together={("content_type", "object_id")}
# this migration was modified from previously being
# a ModifyIndexTogether operation.
#
# If you are a long-enough user of this library, the name
# of the index does not match what is written here. Please
# query the DB itself to find out what the name originally was.
migrations.AddIndex(
"taggeditem",
models.Index(
fields=("content_type", "object_id"),
# this is not the name of the index in previous version,
# but this is necessary to deal with index_together issues.
name="taggit_tagg_content_8fc721_idx",
),
)
]
14 changes: 10 additions & 4 deletions taggit/migrations/0003_taggeditem_add_unique_index.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.db import migrations
from django.db import migrations, models


class Migration(migrations.Migration):
Expand All @@ -8,7 +8,13 @@ class Migration(migrations.Migration):
]

operations = [
migrations.AlterUniqueTogether(
name="taggeditem", unique_together={("content_type", "object_id", "tag")}
)
# this migration was modified to declare a uniqueness constraint differently
# this change was written on 2023-09-20, if any issues occurred from this please report it upstream
migrations.AddConstraint(
model_name="taggeditem",
constraint=models.UniqueConstraint(
fields=("content_type", "object_id", "tag"),
name="taggit_taggeditem_content_type_id_object_id_tag_id_4bb97a8e_uniq",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 4.2.5 on 2023-09-19 23:16
from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("taggit", "0005_auto_20220424_2025"),
]

operations = [
migrations.RenameIndex(
model_name="taggeditem",
new_name="taggit_tagg_content_8fc721_idx",
old_fields=("content_type", "object_id"),
),
]
14 changes: 12 additions & 2 deletions taggit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,15 @@ class Meta:
verbose_name = _("tagged item")
verbose_name_plural = _("tagged items")
app_label = "taggit"
index_together = [["content_type", "object_id"]]
unique_together = [["content_type", "object_id", "tag"]]
indexes = [
models.Index(
fields=["content_type", "object_id"],
)
]

constraints = [
models.UniqueConstraint(
fields=("content_type", "object_id", "tag"),
name="taggit_taggeditem_content_type_id_object_id_tag_id_4bb97a8e_uniq",
)
]
2 changes: 2 additions & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@
STATIC_URL = "/static/"

DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

USE_TZ = True
22 changes: 14 additions & 8 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
UUIDTaggedItem,
)

if DJANGO_VERSION < (4, 2):
TestCase.assertQuerySetEqual = TestCase.assertQuerysetEqual


class BaseTaggingTestCase(TestCase):
def assert_tags_equal(self, qs, tags, sort=True, attr="name"):
Expand Down Expand Up @@ -591,7 +594,7 @@ def test_lookup_by_tag(self):

pks = self.pet_model.objects.filter(tags__name__in=["fuzzy"])
model_name = self.pet_model.__name__
self.assertQuerysetEqual(
self.assertQuerySetEqual(
pks,
[f"<{model_name}: kitty>", f"<{model_name}: cat>"],
ordered=False,
Expand All @@ -609,7 +612,7 @@ def test_exclude(self):

pks = self.food_model.objects.exclude(tags__name__in=["red"])
model_name = self.food_model.__name__
self.assertQuerysetEqual(
self.assertQuerySetEqual(
pks,
[f"<{model_name}: pear>", f"<{model_name}: guava>"],
ordered=False,
Expand Down Expand Up @@ -963,20 +966,23 @@ def _get_form_str(self, form_str):
# https://github.com/django/django/commit/98756c685ee173bbd43f21ed0553f808be835ce5
# https://github.com/django/django/commit/232b60a21b951bd16b8c95b34fcbcbf3ecd89fca
form_str %= {
"help_start": '<div class="helptext">',
"help_start": '<div class="helptext" id="id_tags_helptext">',
"help_stop": "</div>",
"required": "required",
"aria": 'aria-describedby="id_tags_helptext"',
}
else:
form_str %= {
"help_start": '<span class="helptext">',
"help_stop": "</span>",
"required": "required",
"aria": "",
}
return form_str

def assertFormRenders(self, form, html):
self.assertHTMLEqual(str(form), self._get_form_str(html))
rendered_form = form.as_table() if DJANGO_VERSION < (5, 0) else form.as_div()
self.assertHTMLEqual(rendered_form, self._get_form_str(html))

def test_form(self):
self.assertEqual(list(self.form_class.base_fields), ["name", "tags"])
Expand All @@ -986,7 +992,7 @@ def test_form(self):
self.assertFormRenders(
f,
"""<div><label for="id_name">Name:</label><input id="id_name" type="text" name="name" value="apple" maxlength="50" %(required)s /></div>
<div><label for="id_tags">Tags:</label>%(help_start)sA comma-separated list of tags.%(help_stop)s<input type="text" name="tags" value="green, red, yummy" id="id_tags" %(required)s /></div>""",
<div><label for="id_tags">Tags:</label>%(help_start)sA comma-separated list of tags.%(help_stop)s<input %(aria)s type="text" name="tags" value="green, red, yummy" id="id_tags" %(required)s /></div>""",
)
else:
self.assertFormRenders(
Expand Down Expand Up @@ -1014,7 +1020,7 @@ def test_form(self):
self.assertFormRenders(
f,
"""<div><label for="id_name">Name:</label><input id="id_name" type="text" name="name" value="apple" maxlength="50" %(required)s /></div>
<div><label for="id_tags">Tags:</label>%(help_start)sA comma-separated list of tags.%(help_stop)s<input type="text" name="tags" value="delicious, green, red, yummy" id="id_tags" %(required)s /></div>""",
<div><label for="id_tags">Tags:</label>%(help_start)sA comma-separated list of tags.%(help_stop)s<input %(aria)s type="text" name="tags" value="delicious, green, red, yummy" id="id_tags" %(required)s /></div>""",
)
else:
self.assertFormRenders(
Expand All @@ -1029,7 +1035,7 @@ def test_form(self):
self.assertFormRenders(
f,
"""<div><label for="id_name">Name:</label><input id="id_name" type="text" name="name" value="apple" maxlength="50" %(required)s /></div>
<div><label for="id_tags">Tags:</label>%(help_start)sA comma-separated list of tags.%(help_stop)s<input type="text" name="tags" value="&quot;has,comma&quot;, delicious, green, red, yummy" id="id_tags" %(required)s /></div>""",
<div><label for="id_tags">Tags:</label>%(help_start)sA comma-separated list of tags.%(help_stop)s<input %(aria)s type="text" name="tags" value="&quot;has,comma&quot;, delicious, green, red, yummy" id="id_tags" %(required)s /></div>""",
)
else:
self.assertFormRenders(
Expand All @@ -1044,7 +1050,7 @@ def test_form(self):
self.assertFormRenders(
f,
"""<div><label for="id_name">Name:</label><input id="id_name" type="text" name="name" value="apple" maxlength="50" %(required)s /></div>
<div><label for="id_tags">Tags:</label>%(help_start)sA comma-separated list of tags.%(help_stop)s<input type="text" name="tags" value="&quot;has space&quot;, &quot;has,comma&quot;, delicious, green, red, yummy" id="id_tags" %(required)s /></div>""",
<div><label for="id_tags">Tags:</label>%(help_start)sA comma-separated list of tags.%(help_stop)s<input %(aria)s type="text" name="tags" value="&quot;has space&quot;, &quot;has,comma&quot;, delicious, green, red, yummy" id="id_tags" %(required)s /></div>""",
)
else:
self.assertFormRenders(
Expand Down
7 changes: 3 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,26 @@ envlist =
black
flake8
isort
py{37,38,39,310}-dj32
py{38,39,310,311}-dj{41,42}
py{310,311}-djmain
docs

[gh-actions]
python =
3.7: py37
3.8: py38, black, flake8, isort
3.9: py39
3.10: py310
3.11: py311

[testenv]
deps =
dj32: Django>=3.2,<3.3
dj41: Django>=4.1,<4.2
dj42: Django>=4.2,<5.0
djmain: https://github.com/django/django/archive/main.tar.gz
coverage
djangorestframework
setenv =
PYTHONWARNINGS=all
commands =
coverage run -m django test --settings=tests.settings {posargs}
coverage report
Expand All @@ -38,7 +37,7 @@ ignore_errors =
basepython = python3
skip_install = true
deps = black
commands = black --target-version=py37 --check --diff .
commands = black --target-version=py38 --check --diff .

[testenv:flake8]
basepython = python3
Expand Down