Browse Source

Drop support for Django 2.2

Matt Westcott 3 years ago
parent
commit
85c97fe87f

+ 8 - 14
.github/workflows/test.yml

@@ -17,9 +17,8 @@ on:
 # - test runs with USE_EMAIL_USER_MODEL=yes and DISABLE_TIMEZONE=yes
 
 # Current configuration:
-# - django 2.2, python 3.6, mysql
-# - django 3.0, python 3.7, sqlite
-# - django 3.1, python 3.8, postgres
+# - django 3.0, python 3.6, sqlite
+# - django 3.1, python 3.7, postgres
 # - django 3.2, python 3.8, postgres
 # - django 3.2, python 3.9, mysql
 # - django 3.2, python 3.9, sqlite
@@ -27,9 +26,8 @@ on:
 # - django 3.2, python 3.9, postgres, DISABLE_TIMEZONE=yes
 # - django stable/3.2.x, python 3.9, postgres (allow failures)
 # - django main, python 3.9, postgres (allow failures)
-# - elasticsearch 5, django 2.2, python 3.6, sqlite
-# - elasticsearch 6, django 3.0, python 3.7, postgres
-# - elasticsearch 7, django 3.1, python 3.8, postgres
+# - elasticsearch 5, django 3.0, python 3.6, sqlite
+# - elasticsearch 6, django 3.1, python 3.7, postgres
 # - elasticsearch 7, django 3.2, python 3.8, postgres
 # - elasticsearch 7, django 3.2, python 3.9, sqlite, USE_EMAIL_USER_MODEL=yes
 
@@ -39,7 +37,7 @@ jobs:
     strategy:
       matrix:
         include:
-          - python: 3.7
+          - python: 3.6
             django: "Django>=3.0,<3.1"
           - python: 3.9
             django: "Django>=3.1,<3.2"
@@ -67,7 +65,7 @@ jobs:
     strategy:
       matrix:
         include:
-          - python: 3.8
+          - python: 3.7
             django: "Django>=3.1,<3.2"
             experimental: false
           - python: 3.8
@@ -123,8 +121,6 @@ jobs:
     strategy:
       matrix:
         include:
-          - python: 3.6
-            django: "Django>=2.2,<3.0"
           - python: 3.9
             django: "Django>=3.2,<3.3"
 
@@ -166,7 +162,7 @@ jobs:
       matrix:
         include:
           - python: 3.6
-            django: "Django>=2.2,<3.0"
+            django: "Django>=3.0,<3.1"
     steps:
       - name: Configure sysctl limits
         run: |
@@ -250,7 +246,7 @@ jobs:
       matrix:
         include:
           - python: 3.7
-            django: "Django>=3.0,<3.1"
+            django: "Django>=3.1,<3.2"
 
     services:
       postgres:
@@ -297,8 +293,6 @@ jobs:
     strategy:
       matrix:
         include:
-          - python: 3.8
-            django: "Django>=3.1,<3.2"
           - python: 3.8
             django: "Django>=3.2,<3.3"
 

+ 1 - 0
CHANGELOG.txt

@@ -4,6 +4,7 @@ Changelog
 2.14 (xx.xx.xxxx) - IN DEVELOPMENT
 ~~~~~~~~~~~~~~~~~
 
+ * Removed support for Django 2.2
  * Added ``ancestor_of`` API filter (Jaap Roes)
  * Fix: Invalid filter values for foreign key fields in the API now give an error instead of crashing (Tidjani Dia)
  * Fix: Ordering specified in `construct_explorer_page_queryset` hook is now taken into account again by the page explorer API (Andre Fonseca)

+ 2 - 2
README.md

@@ -56,11 +56,11 @@ _(If you are reading this on GitHub, the details here may not be indicative of t
 
 Wagtail supports:
 
-* Django 2.2.x, 3.0.x, 3.1.x and 3.2.x
+* Django 3.0.x, 3.1.x and 3.2.x
 * Python 3.6, 3.7, 3.8 and 3.9
 * PostgreSQL, MySQL and SQLite as database backends
 
-[Previous versions of Wagtail](https://docs.wagtail.io/en/stable/releases/upgrading.html#compatible-django-python-versions) additionally supported Python 2.7 and Django 1.x.
+[Previous versions of Wagtail](https://docs.wagtail.io/en/stable/releases/upgrading.html#compatible-django-python-versions) additionally supported Python 2.7 and Django 1.x - 2.x.
 
 ---
 

+ 1 - 1
docs/getting_started/integrating_into_django.md

@@ -5,7 +5,7 @@
 
 Wagtail provides the `wagtail start` command and project template to get you started with a new Wagtail project as quickly as possible, but it's easy to integrate Wagtail into an existing Django project too.
 
-Wagtail is currently compatible with Django 2.2, 3.0, 3.1 and 3.2. First, install the `wagtail` package from PyPI:
+Wagtail is currently compatible with Django 3.0, 3.1 and 3.2. First, install the `wagtail` package from PyPI:
 
 ```sh
 $ pip install wagtail

+ 5 - 0
docs/releases/2.14.rst

@@ -24,3 +24,8 @@ Bug fixes
 
 Upgrade considerations
 ======================
+
+Removed support for Django 2.2
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Django 2.2 is no longer supported as of this release; please upgrade to Django 3.0 or above before upgrading Wagtail.

+ 1 - 2
setup.py

@@ -20,7 +20,7 @@ except ImportError:
 
 
 install_requires = [
-    "Django>=2.2,<3.3",
+    "Django>=3.0,<3.3",
     "django-modelcluster>=5.1,<6.0",
     "django-taggit>=1.0,<2.0",
     "django-treebeard>=4.2.0,<5.0,!=4.5",
@@ -109,7 +109,6 @@ https://github.com/wagtail/wagtail/.",
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
         'Framework :: Django',
-        'Framework :: Django :: 2.2',
         'Framework :: Django :: 3.0',
         'Framework :: Django :: 3.1',
         'Framework :: Django :: 3.2',

+ 1 - 2
tox.ini

@@ -2,7 +2,7 @@
 skipsdist = True
 usedevelop = True
 
-envlist = py{36,37,38,39}-dj{22,30,31,32,32stable,main}-{sqlite,postgres,mysql,mssql}-{elasticsearch7,elasticsearch6,elasticsearch5,noelasticsearch}-{customuser,emailuser}-{tz,notz},
+envlist = py{36,37,38,39}-dj{30,31,32,32stable,main}-{sqlite,postgres,mysql,mssql}-{elasticsearch7,elasticsearch6,elasticsearch5,noelasticsearch}-{customuser,emailuser}-{tz,notz},
 
 [testenv]
 install_command = pip install -e ".[testing]" -U {opts} {packages}
@@ -22,7 +22,6 @@ deps =
     django-sendfile==0.3.6
     Embedly
 
-    dj22: Django~=2.2.0
     dj30: Django~=3.0.0
     dj31: Django~=3.1.0
     dj32: Django~=3.2.0

+ 3 - 9
wagtail/admin/tests/pages/test_copy_page.py

@@ -1,4 +1,3 @@
-from django import VERSION as DJANGO_VERSION
 from django.contrib.auth.models import Group, Permission
 from django.http import HttpRequest, HttpResponse
 from django.test import TestCase
@@ -319,14 +318,9 @@ class TestPageCopy(TestCase, WagtailTestUtils):
         self.assertEqual(response.status_code, 200)
 
         # Check that a form error was raised
-        if DJANGO_VERSION >= (3, 0):
-            self.assertFormError(
-                response, 'form', 'new_slug', "Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens."
-            )
-        else:
-            self.assertFormError(
-                response, 'form', 'new_slug', "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or hyphens."
-            )
+        self.assertFormError(
+            response, 'form', 'new_slug', "Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens."
+        )
 
     def test_page_copy_post_valid_unicode_slug(self):
         post_data = {

+ 3 - 14
wagtail/admin/tests/test_account_management.py

@@ -2,7 +2,6 @@ import unittest
 
 import pytz
 
-from django import VERSION as DJANGO_VERSION
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.auth import views as auth_views
@@ -344,10 +343,7 @@ class TestAccountSection(TestCase, WagtailTestUtils, TestAccountSectionUtilsMixi
         # Check that a validation error was raised
         password_form = password_panel.get_form()
         self.assertTrue('new_password2' in password_form.errors.keys())
-        if DJANGO_VERSION >= (3, 0):
-            self.assertTrue("The two password fields didn’t match." in password_form.errors['new_password2'])
-        else:
-            self.assertTrue("The two password fields didn't match." in password_form.errors['new_password2'])
+        self.assertTrue("The two password fields didn’t match." in password_form.errors['new_password2'])
 
         # Check that the password was not changed
         self.user.refresh_from_db()
@@ -722,10 +718,7 @@ class TestPasswordReset(TestCase, WagtailTestUtils):
         self.password_reset_uid = force_str(urlsafe_base64_encode(force_bytes(self.user.pk)))
 
         # Create url_args
-        if DJANGO_VERSION >= (3, 0):
-            token = auth_views.PasswordResetConfirmView.reset_url_token
-        else:
-            token = auth_views.INTERNAL_RESET_URL_TOKEN
+        token = auth_views.PasswordResetConfirmView.reset_url_token
 
         self.url_kwargs = dict(uidb64=self.password_reset_uid, token=token)
 
@@ -807,11 +800,7 @@ class TestPasswordReset(TestCase, WagtailTestUtils):
 
         # Check that a validation error was raised
         self.assertTrue('new_password2' in response.context['form'].errors.keys())
-
-        if DJANGO_VERSION >= (3, 0):
-            self.assertTrue("The two password fields didn’t match." in response.context['form'].errors['new_password2'])
-        else:
-            self.assertTrue("The two password fields didn't match." in response.context['form'].errors['new_password2'])
+        self.assertTrue("The two password fields didn’t match." in response.context['form'].errors['new_password2'])
 
         # Check that the password was not changed
         self.assertTrue(get_user_model().objects.get(email='test@email.com').check_password('password'))

+ 11 - 25
wagtail/admin/tests/test_audit_log.py

@@ -1,6 +1,5 @@
 from datetime import timedelta
 
-from django import VERSION as DJANGO_VERSION
 from django.contrib.auth.models import Group, Permission
 from django.test import TestCase
 from django.urls import reverse
@@ -85,30 +84,17 @@ class TestAuditLogAdmin(TestCase, WagtailTestUtils):
         self.assertContains(response, "Page scheduled for publishing", 1)
         self.assertContains(response, "Published", 1)
 
-        if DJANGO_VERSION >= (3, 0):
-            self.assertContains(
-                response, "Added the &#x27;Private, accessible to logged-in users&#x27; view restriction"
-            )
-            self.assertContains(
-                response,
-                "Updated the view restriction to &#x27;Private, accessible with the following password&#x27;"
-            )
-            self.assertContains(
-                response,
-                "Removed the &#x27;Private, accessible with the following password&#x27; view restriction"
-            )
-        else:
-            self.assertContains(
-                response, "Added the &#39;Private, accessible to logged-in users&#39; view restriction"
-            )
-            self.assertContains(
-                response,
-                "Updated the view restriction to &#39;Private, accessible with the following password&#39;"
-            )
-            self.assertContains(
-                response,
-                "Removed the &#39;Private, accessible with the following password&#39; view restriction"
-            )
+        self.assertContains(
+            response, "Added the &#x27;Private, accessible to logged-in users&#x27; view restriction"
+        )
+        self.assertContains(
+            response,
+            "Updated the view restriction to &#x27;Private, accessible with the following password&#x27;"
+        )
+        self.assertContains(
+            response,
+            "Removed the &#x27;Private, accessible with the following password&#x27; view restriction"
+        )
 
         self.assertContains(response, 'system', 2)  # create without a user + remove restriction
         self.assertContains(response, 'the_editor', 9)  # 7 entries by editor + 1 in sidebar menu + 1 in filter

+ 1 - 1
wagtail/contrib/modeladmin/options.py

@@ -126,7 +126,7 @@ class ModelAdmin(WagtailRegisterable):
             self.model, self.inspect_view_enabled)
         self.url_helper = self.get_url_helper_class()(self.model)
 
-        # Needed to support RelatedFieldListFilter in Django 2.2+
+        # Needed to support RelatedFieldListFilter
         # See: https://github.com/wagtail/wagtail/issues/5105
         self.admin_site = default_django_admin_site
 

+ 4 - 13
wagtail/contrib/modeladmin/tests/test_page_modeladmin.py

@@ -1,4 +1,3 @@
-from django import VERSION as DJANGO_VERSION
 from django.contrib.auth.models import Group, Permission
 from django.test import TestCase
 
@@ -65,21 +64,13 @@ class TestExcludeFromExplorer(TestCase, WagtailTestUtils):
     def test_attribute_effects_explorer(self):
         # The two VenuePages should appear in the venuepage list
         response = self.client.get('/admin/modeladmintest/venuepage/')
-        if DJANGO_VERSION >= (3, 0):
-            self.assertContains(response, "Santa&#x27;s Grotto")
-            self.assertContains(response, "Santa&#x27;s Workshop")
-        else:
-            self.assertContains(response, "Santa&#39;s Grotto")
-            self.assertContains(response, "Santa&#39;s Workshop")
+        self.assertContains(response, "Santa&#x27;s Grotto")
+        self.assertContains(response, "Santa&#x27;s Workshop")
 
         # But when viewing the children of 'Christmas' event in explorer
         response = self.client.get('/admin/pages/4/')
-        if DJANGO_VERSION >= (3, 0):
-            self.assertNotContains(response, "Santa&#x27;s Grotto")
-            self.assertNotContains(response, "Santa&#x27;s Workshop")
-        else:
-            self.assertNotContains(response, "Santa&#39;s Grotto")
-            self.assertNotContains(response, "Santa&#39;s Workshop")
+        self.assertNotContains(response, "Santa&#x27;s Grotto")
+        self.assertNotContains(response, "Santa&#x27;s Workshop")
 
         # But the other test page should...
         self.assertContains(response, "Claim your free present!")

+ 7 - 11
wagtail/contrib/modeladmin/views.py

@@ -33,16 +33,12 @@ from wagtail.admin.views.mixins import SpreadsheetExportMixin
 from .forms import ParentChooserForm
 
 
-try:
-    from django.db.models.sql.constants import QUERY_TERMS
-except ImportError:
-    # Django 2.1+ does not have QUERY_TERMS anymore
-    QUERY_TERMS = {
-        'contains', 'day', 'endswith', 'exact', 'gt', 'gte', 'hour',
-        'icontains', 'iendswith', 'iexact', 'in', 'iregex', 'isnull',
-        'istartswith', 'lt', 'lte', 'minute', 'month', 'range', 'regex',
-        'search', 'second', 'startswith', 'week_day', 'year',
-    }
+QUERY_TERMS = {
+    'contains', 'day', 'endswith', 'exact', 'gt', 'gte', 'hour',
+    'icontains', 'iendswith', 'iexact', 'in', 'iregex', 'isnull',
+    'istartswith', 'lt', 'lte', 'minute', 'month', 'range', 'regex',
+    'search', 'second', 'startswith', 'week_day', 'year',
+}
 
 
 class WMABaseView(TemplateView):
@@ -255,7 +251,7 @@ class IndexView(SpreadsheetExportMixin, WMABaseView):
     IGNORED_PARAMS = (ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, EXPORT_VAR)
 
     # sortable_by is required by the django.contrib.admin.templatetags.admin_list.result_headers
-    # template tag as of Django 2.1 - see https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.sortable_by
+    # template tag - see https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.sortable_by
     sortable_by = None
 
     @method_decorator(login_required)

+ 1 - 6
wagtail/core/models.py

@@ -7,7 +7,6 @@ from collections import namedtuple
 from io import StringIO
 from urllib.parse import urlparse
 
-from django import VERSION as DJANGO_VERSION
 from django import forms
 from django.apps import apps
 from django.conf import settings
@@ -96,17 +95,13 @@ def _extract_field_data(source, exclude_fields=None):
         if isinstance(field, models.OneToOneField) and field.remote_field.parent_link:
             continue
 
-        if DJANGO_VERSION >= (3, 0) and isinstance(field, models.ForeignKey):
+        if isinstance(field, models.ForeignKey):
             # Use attname to copy the ID instead of retrieving the instance
 
             # Note: We first need to set the field to None to unset any object
             # that's there already just setting _id on its own won't change the
             # field until its saved.
 
-            # Before Django 3.0, Django won't find the new object if the field
-            # was set to None in this way, so this optimisation isn't available
-            # for Django 2.x.
-
             data_dict[field.name] = None
             data_dict[field.attname] = getattr(source, field.attname)
 

+ 1 - 6
wagtail/core/views.py

@@ -2,18 +2,13 @@ from django.conf import settings
 from django.http import Http404, HttpResponse
 from django.shortcuts import get_object_or_404, redirect
 from django.urls import reverse
+from django.utils.http import url_has_allowed_host_and_scheme
 
 from wagtail.core import hooks
 from wagtail.core.forms import PasswordViewRestrictionForm
 from wagtail.core.models import Page, PageViewRestriction, Site
 
 
-try:
-    from django.utils.http import url_has_allowed_host_and_scheme
-except ImportError:  # fallback for Django 2.2
-    from django.utils.http import is_safe_url as url_has_allowed_host_and_scheme
-
-
 def serve(request, path):
     # we need a valid Site object corresponding to this request in order to proceed
     site = Site.find_for_request(request)

+ 1 - 6
wagtail/documents/views/serve.py

@@ -5,6 +5,7 @@ from django.http import Http404, HttpResponse, StreamingHttpResponse
 from django.shortcuts import get_object_or_404, redirect
 from django.template.response import TemplateResponse
 from django.urls import reverse
+from django.utils.http import url_has_allowed_host_and_scheme
 from django.views.decorators.cache import cache_control
 from django.views.decorators.http import etag
 
@@ -17,12 +18,6 @@ from wagtail.utils import sendfile_streaming_backend
 from wagtail.utils.sendfile import sendfile
 
 
-try:
-    from django.utils.http import url_has_allowed_host_and_scheme
-except ImportError:  # fallback for Django 2.2
-    from django.utils.http import is_safe_url as url_has_allowed_host_and_scheme
-
-
 def document_etag(request, document_id, document_filename):
     Document = get_document_model()
     if hasattr(Document, 'file_hash'):