Skip to content

Commit

Permalink
Merge pull request #869 from jazzband/django-upgrade
Browse files Browse the repository at this point in the history
Django 4.2 preparation
  • Loading branch information
rtpg authored Oct 24, 2023
2 parents 32939cd + 60064a7 commit 7feee51
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 60 deletions.
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

0 comments on commit 7feee51

Please sign in to comment.