There are two issues to be addressed: RemovedInDjango50Warning: Passing response to assertFormError() is deprecated. Use the form object directly:
RemovedInDjango50Warning: The "default.html" templates for forms and formsets will be removed. These were proxies to the equivalent "table.html" templates, but the new "div.html" templates will be the default from Django 5.0. Transitional renderers are provided to allow you to opt-in to the new output style now. See https://docs.djangoproject.com/en/4.1/releases/4.1/ for more details Nothing complicated in fixing either of these. For the former, we must do as we're told and use the form object directly. For the latter, we need to configure our own form renderer so we can continue using the table form renderer for now. Signed-off-by: Stephen Finucane <step...@that.guru> --- patchwork/forms.py | 8 ++ patchwork/settings/base.py | 2 + patchwork/tests/views/test_mail.py | 91 +++++++++++++--- patchwork/tests/views/test_patch.py | 23 ++-- patchwork/tests/views/test_user.py | 102 ++++++++++++++---- .../django-4-1-support-bcbe65a71d235b43.yaml | 5 + tox.ini | 9 +- 7 files changed, 197 insertions(+), 43 deletions(-) create mode 100644 releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml diff --git patchwork/forms.py patchwork/forms.py index 84448586..1c5a29be 100644 --- patchwork/forms.py +++ patchwork/forms.py @@ -5,8 +5,10 @@ from django.contrib.auth.models import User from django import forms +from django.forms import renderers from django.db.models import Q from django.db.utils import ProgrammingError +from django.template.backends import django as django_template_backend from patchwork.models import Bundle from patchwork.models import Patch @@ -14,6 +16,12 @@ from patchwork.models import State from patchwork.models import UserProfile +class PatchworkTableRenderer(renderers.EngineMixin, renderers.BaseRenderer): + backend = django_template_backend.DjangoTemplates + form_template_name = 'django/forms/table.html' + formset_template_name = 'django/forms/formsets/table.html' + + class RegistrationForm(forms.Form): first_name = forms.CharField(max_length=30, required=False) last_name = forms.CharField(max_length=30, required=False) diff --git patchwork/settings/base.py patchwork/settings/base.py index 045f262f..965c949f 100644 --- patchwork/settings/base.py +++ patchwork/settings/base.py @@ -71,6 +71,8 @@ TEMPLATES = [ }, ] +FORM_RENDERER = 'patchwork.forms.PatchworkTableRenderer' + # TODO(stephenfin): Consider changing to BigAutoField when we drop support for # Django < 3.2 DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' diff --git patchwork/tests/views/test_mail.py patchwork/tests/views/test_mail.py index de9df3d2..ae0b2c38 100644 --- patchwork/tests/views/test_mail.py +++ patchwork/tests/views/test_mail.py @@ -5,6 +5,7 @@ import re +import django from django.core import mail from django.test import TestCase from django.urls import reverse @@ -33,15 +34,37 @@ class MailSettingsTest(TestCase): response = self.client.post(reverse('mail-settings'), {'email': ''}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'patchwork/mail.html') - self.assertFormError( - response, 'form', 'email', 'This field is required.' - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + 'This field is required.', + ) + else: + self.assertFormError( + response, + 'form', + 'email', + 'This field is required.', + ) def test_post_invalid(self): response = self.client.post(reverse('mail-settings'), {'email': 'foo'}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'patchwork/mail.html') - self.assertFormError(response, 'form', 'email', error_strings['email']) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + error_strings['email'], + ) + else: + self.assertFormError( + response, + 'form', + 'email', + error_strings['email'], + ) def test_post_optin(self): email = 'f...@example.com' @@ -91,9 +114,19 @@ class OptoutRequestTest(TestCase): def test_post_empty(self): response = self.client.post(reverse('mail-optout'), {'email': ''}) self.assertEqual(response.status_code, 200) - self.assertFormError( - response, 'form', 'email', 'This field is required.' - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + 'This field is required.', + ) + else: + self.assertFormError( + response, + 'form', + 'email', + 'This field is required.', + ) self.assertTrue(response.context['error']) self.assertNotIn('confirmation', response.context) self.assertEqual(len(mail.outbox), 0) @@ -101,7 +134,19 @@ class OptoutRequestTest(TestCase): def test_post_non_email(self): response = self.client.post(reverse('mail-optout'), {'email': 'foo'}) self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', 'email', error_strings['email']) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + error_strings['email'], + ) + else: + self.assertFormError( + response, + 'form', + 'email', + error_strings['email'], + ) self.assertTrue(response.context['error']) self.assertNotIn('confirmation', response.context) self.assertEqual(len(mail.outbox), 0) @@ -172,9 +217,19 @@ class OptinRequestTest(TestCase): def test_post_empty(self): response = self.client.post(reverse('mail-optin'), {'email': ''}) self.assertEqual(response.status_code, 200) - self.assertFormError( - response, 'form', 'email', 'This field is required.' - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + 'This field is required.', + ) + else: + self.assertFormError( + response, + 'form', + 'email', + 'This field is required.', + ) self.assertTrue(response.context['error']) self.assertNotIn('confirmation', response.context) self.assertEqual(len(mail.outbox), 0) @@ -182,7 +237,19 @@ class OptinRequestTest(TestCase): def test_post_non_email(self): response = self.client.post(reverse('mail-optin'), {'email': 'foo'}) self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', 'email', error_strings['email']) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + error_strings['email'], + ) + else: + self.assertFormError( + response, + 'form', + 'email', + error_strings['email'], + ) self.assertTrue(response.context['error']) self.assertNotIn('confirmation', response.context) self.assertEqual(len(mail.outbox), 0) diff --git patchwork/tests/views/test_patch.py patchwork/tests/views/test_patch.py index 9cea8a0b..d1de8ec9 100644 --- patchwork/tests/views/test_patch.py +++ patchwork/tests/views/test_patch.py @@ -8,6 +8,7 @@ from datetime import timedelta import re import unittest +import django from django.conf import settings from django.test import TestCase from django.urls import reverse @@ -460,13 +461,21 @@ class PatchUpdateTest(TestCase): new_states = [Patch.objects.get(pk=p.pk).state for p in self.patches] self.assertEqual(new_states, orig_states) - self.assertFormError( - response, - 'patchform', - 'state', - 'Select a valid choice. That choice is not one ' - 'of the available choices.', - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['patchform'], + 'state', + 'Select a valid choice. That choice is not one ' + 'of the available choices.', + ) + else: + self.assertFormError( + response, + 'patchform', + 'state', + 'Select a valid choice. That choice is not one ' + 'of the available choices.', + ) def _test_delegate_change(self, delegate_str): data = self.base_data.copy() diff --git patchwork/tests/views/test_user.py patchwork/tests/views/test_user.py index 1f74ad50..8ab91670 100644 --- patchwork/tests/views/test_user.py +++ patchwork/tests/views/test_user.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: GPL-2.0-or-later +import django from django.contrib.auth.models import User from django.core import mail from django.test.client import Client @@ -70,14 +71,38 @@ class RegistrationTest(TestCase): del data[field] response = self.client.post('/register/', data) self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', field, self.required_error) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + field, + self.required_error, + ) + else: + self.assertFormError( + response, + 'form', + field, + self.required_error, + ) def test_invalid_username(self): data = self.default_data.copy() data['username'] = 'invalid user' response = self.client.post('/register/', data) self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', 'username', self.invalid_error) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'username', + self.invalid_error, + ) + else: + self.assertFormError( + response, + 'form', + 'username', + self.invalid_error, + ) def test_existing_username(self): user = create_user() @@ -85,12 +110,19 @@ class RegistrationTest(TestCase): data['username'] = user.username response = self.client.post('/register/', data) self.assertEqual(response.status_code, 200) - self.assertFormError( - response, - 'form', - 'username', - 'This username is already taken. Please choose another.', - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'username', + 'This username is already taken. Please choose another.', + ) + else: + self.assertFormError( + response, + 'form', + 'username', + 'This username is already taken. Please choose another.', + ) def test_existing_email(self): user = create_user() @@ -98,13 +130,21 @@ class RegistrationTest(TestCase): data['email'] = user.email response = self.client.post('/register/', data) self.assertEqual(response.status_code, 200) - self.assertFormError( - response, - 'form', - 'email', - 'This email address is already in use for the account ' - '"%s".\n' % user.username, - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + 'This email address is already in use for the account ' + '"%s".\n' % user.username, + ) + else: + self.assertFormError( + response, + 'form', + 'email', + 'This email address is already in use for the account ' + '"%s".\n' % user.username, + ) def test_valid_registration(self): response = self.client.post('/register/', self.default_data) @@ -255,17 +295,37 @@ class UserLinkTest(_UserTestCase): response = self.client.post(reverse('user-link'), {'email': ''}) self.assertEqual(response.status_code, 200) self.assertTrue(response.context['linkform']) - self.assertFormError( - response, 'linkform', 'email', 'This field is required.' - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['linkform'], + 'email', + 'This field is required.', + ) + else: + self.assertFormError( + response, + 'linkform', + 'email', + 'This field is required.', + ) def test_user_person_request_invalid(self): response = self.client.post(reverse('user-link'), {'email': 'foo'}) self.assertEqual(response.status_code, 200) self.assertTrue(response.context['linkform']) - self.assertFormError( - response, 'linkform', 'email', error_strings['email'] - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['linkform'], + 'email', + error_strings['email'], + ) + else: + self.assertFormError( + response, + 'linkform', + 'email', + error_strings['email'], + ) def test_user_person_request_valid(self): response = self.client.post( diff --git releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml new file mode 100644 index 00000000..3dcab1bd --- /dev/null +++ releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + `Django 4.1 <https://docs.djangoproject.com/en/dev/releases/4.1/>`_ is + now supported. diff --git tox.ini tox.ini index aa434a72..f243faca 100644 --- tox.ini +++ tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.2 -envlist = pep8,docs,py{37,38,39}-django32,py{38,39,310}-django{40} +envlist = pep8,docs,py{37,38,39}-django32,py{38,39,310}-django{40,41} skipsdist = true ignore_basepython_conflict = true @@ -9,11 +9,14 @@ basepython = python3 deps = -r{toxinidir}/requirements-test.txt django32: django~=3.2.0 - django32: djangorestframework~=3.13.0 + django32: djangorestframework~=3.14.0 django32: django-filter~=22.1.0 django40: django~=4.0.0 - django40: djangorestframework~=3.13.0 + django40: djangorestframework~=3.14.0 django40: django-filter~=22.1.0 + django41: django~=4.1.0 + django41: djangorestframework~=3.14.0 + django41: django-filter~=22.1.0 setenv = DJANGO_SETTINGS_MODULE = patchwork.settings.dev PYTHONDONTWRITEBYTECODE = 1 -- 2.37.3 _______________________________________________ Patchwork mailing list Patchwork@lists.ozlabs.org https://lists.ozlabs.org/listinfo/patchwork