Drop support for Django <3.2

pull/7568/head
Matt Westcott 2022-01-06 11:38:52 +00:00 zatwierdzone przez Matt Westcott
rodzic e27af42fdf
commit d6d43338ef
17 zmienionych plików z 71 dodań i 196 usunięć

Wyświetl plik

@ -17,18 +17,17 @@ on:
# - test runs with USE_EMAIL_USER_MODEL=yes and DISABLE_TIMEZONE=yes
# Current configuration:
# - django 3.0, python 3.7, 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.10, sqlite
# - django 3.2, python 3.9, postgres, USE_EMAIL_USER_MODEL=yes
# - django 3.2, python 3.7, postgres
# - django 3.2, python 3.8, mysql
# - django 3.2, python 3.9, sqlite
# - django 4.0, python 3.9, mysql (allow failures)
# - django 3.2, python 3.10, postgres, USE_EMAIL_USER_MODEL=yes
# - django 3.2, python 3.10, postgres, DISABLE_TIMEZONE=yes
# - django stable/4.0.x, python 3.9, postgres (allow failures)
# - django stable/4.0.x, python 3.10, postgres (allow failures)
# - django main, python 3.10, postgres (allow failures)
# - elasticsearch 5, django 3.0, python 3.7, sqlite
# - elasticsearch 6, django 3.1, python 3.7, postgres
# - elasticsearch 7, django 3.2, python 3.8, postgres
# - elasticsearch 5, django 3.2, python 3.7, sqlite
# - elasticsearch 6, django 3.2, python 3.7, postgres
# - elasticsearch 7, django 4.0, python 3.8, postgres (allow failures)
# - elasticsearch 7, django 3.2, python 3.9, sqlite, USE_EMAIL_USER_MODEL=yes
jobs:
@ -37,10 +36,8 @@ jobs:
strategy:
matrix:
include:
- python: "3.7"
django: "Django>=3.0,<3.1"
- python: "3.10"
django: "Django>=3.1,<3.2"
- python: "3.9"
django: "Django>=3.2,<3.3"
steps:
- uses: actions/checkout@v2
@ -66,20 +63,17 @@ jobs:
matrix:
include:
- python: "3.7"
django: "Django>=3.1,<3.2"
experimental: false
- python: "3.8"
django: "Django>=3.2,<3.3"
experimental: false
- python: "3.9"
django: "Django>=3.2,<3.3"
emailuser: emailuser
experimental: false
- python: "3.10"
django: "Django>=3.2,<3.3"
notz: notz
experimental: false
- python: "3.9"
- python: "3.10"
django: "Django>=3.2,<3.3"
experimental: false
emailuser: emailuser
- python: "3.10"
django: "git+https://github.com/django/django.git@stable/4.0.x#egg=Django"
experimental: true
- python: "3.10"
@ -118,11 +112,16 @@ jobs:
test-mysql:
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
strategy:
matrix:
include:
- python: "3.9"
- python: "3.8"
django: "Django>=3.2,<3.3"
experimental: false
- python: "3.9"
django: "Django>=4.0,<4.1"
experimental: true
services:
mysql:
@ -162,7 +161,7 @@ jobs:
matrix:
include:
- python: "3.7"
django: "Django>=3.0,<3.1"
django: "Django>=3.2,<3.3"
steps:
- name: Configure sysctl limits
run: |
@ -246,7 +245,7 @@ jobs:
matrix:
include:
- python: "3.7"
django: "Django>=3.1,<3.2"
django: "Django>=3.2,<3.3"
services:
postgres:
@ -290,11 +289,13 @@ jobs:
test-postgres-elasticsearch7:
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
strategy:
matrix:
include:
- python: "3.8"
django: "Django>=3.2,<3.3"
django: "Django>=4.0,<4.1"
experimental: true
services:
postgres:

Wyświetl plik

@ -4,6 +4,7 @@ Changelog
2.16 (xx.xx.xxxx) - IN DEVELOPMENT
~~~~~~~~~~~~~~~~~
* Removed support for Django 3.0 and 3.1
* Removed support for Python 3.6
* Added persistent IDs for ListBlock items, allowing commenting and improvements to revision comparisons (Matt Westcott, Tidjani Dia)
* Added Aging Pages report (Tidjani Dia)

Wyświetl plik

@ -56,11 +56,11 @@ _(If you are reading this on GitHub, the details here may not be indicative of t
Wagtail supports:
* Django 3.0.x, 3.1.x and 3.2.x
* Django 3.2.x
* Python 3.7, 3.8, 3.9 and 3.10
* 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 - 2.x.
[Previous versions of Wagtail](https://docs.wagtail.io/en/stable/releases/upgrading.html#compatible-django-python-versions) additionally supported Python 2.7 and earlier Django versions.
---

Wyświetl plik

@ -71,10 +71,11 @@ set a thread-local to indicate to all downstream code that AMP mode is active.
Django uses thread-locals internally to track the currently active language
for the request.
Please be aware though: In Django 3.x and above, you will need to use an
``asgiref.Local`` instead.
This is because Django 3.x handles multiple requests in a single thread
so thread-locals will no longer be unique to a single request.
Python implements thread-local data through the ``threading.local`` class,
but as of Django 3.x, multiple requests can be handled in a single thread
and so thread-locals will no longer be unique to a single request. Django
therefore provides ``asgiref.Local`` as a drop-in replacement.
Now let's create that thread-local and some utility functions to interact with it,
save this module as ``amp_utils.py`` in an app in your project:
@ -84,10 +85,9 @@ save this module as ``amp_utils.py`` in an app in your project:
# <app>/amp_utils.py
from contextlib import contextmanager
from threading import local
from asgiref.local import Local
# FIXME: For Django 3.0 support, replace this with asgiref.Local
_amp_mode_active = local()
_amp_mode_active = Local()
@contextmanager
def activate_amp_mode():

Wyświetl plik

@ -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 3.0, 3.1 and 3.2. First, install the `wagtail` package from PyPI:
Wagtail is currently compatible with Django 3.2. First, install the `wagtail` package from PyPI:
```sh
$ pip install wagtail

Wyświetl plik

@ -43,6 +43,11 @@
## Upgrade considerations
### Removed support for Django 3.0 and 3.1
Django 3.0 and 3.1 are no longer supported as of this release; please upgrade to Django 3.2 or above before upgrading Wagtail.
### Removed support for Python 3.6
Python 3.6 is no longer supported as of this release; please upgrade to Python 3.7 or above before upgrading Wagtail.

Wyświetl plik

@ -147,5 +147,5 @@ The compatible versions of Django and Python for each Wagtail release are:
+-------------------+------------------------------+-----------------------------+
| 2.15 LTS | 3.0, 3.1, 3.2 | 3.6, 3.7, 3.8, 3.9, 3.10 |
+-------------------+------------------------------+-----------------------------+
| 2.16 | 3.0, 3.1, 3.2 | 3.7, 3.8, 3.9, 3.10 |
| 2.16 | 3.2 | 3.7, 3.8, 3.9, 3.10 |
+-------------------+------------------------------+-----------------------------+

Wyświetl plik

@ -20,7 +20,7 @@ except ImportError:
install_requires = [
"Django>=3.0,<3.3",
"Django>=3.2,<4.0",
"django-modelcluster>=5.2,<6.0",
"django-taggit>=1.0,<2.0",
"django-treebeard>=4.2.0,<5.0,!=4.5",
@ -114,8 +114,6 @@ https://github.com/wagtail/wagtail/.",
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Framework :: Django',
'Framework :: Django :: 3.0',
'Framework :: Django :: 3.1',
'Framework :: Django :: 3.2',
'Framework :: Wagtail',
'Topic :: Internet :: WWW/HTTP :: Site Management',

Wyświetl plik

@ -2,7 +2,7 @@
skipsdist = True
usedevelop = True
envlist = py{37,38,39,310}-dj{30,31,32,32stable,main}-{sqlite,postgres,mysql,mssql}-{elasticsearch7,elasticsearch6,elasticsearch5,noelasticsearch}-{customuser,emailuser}-{tz,notz},
envlist = py{37,38,39,310}-dj{32,40stable,main}-{sqlite,postgres,mysql,mssql}-{elasticsearch7,elasticsearch6,elasticsearch5,noelasticsearch}-{customuser,emailuser}-{tz,notz},
[testenv]
install_command = pip install -e ".[testing]" -U {opts} {packages}
@ -22,10 +22,8 @@ deps =
django-sendfile==0.3.6
Embedly
dj30: Django~=3.0.0
dj31: Django~=3.1.0
dj32: Django~=3.2.0
dj32stable: git+https://github.com/django/django.git@stable/3.2.x#egg=Django
dj40stable: git+https://github.com/django/django.git@stable/4.0.x#egg=Django
djmain: git+https://github.com/django/django.git@main#egg=Django
postgres: psycopg2>=2.6

Wyświetl plik

@ -139,14 +139,8 @@ class WagtailAdminPageForm(WagtailAdminModelForm):
def is_valid(self):
comments = self.formsets.get('comments')
# Remove the comments formset if the management form is invalid
if comments:
try:
# As of Django 3.2, this no longer raises an error
has_form = comments.management_form.is_valid()
except forms.ValidationError:
has_form = False
if not has_form:
del self.formsets['comments']
if comments and not comments.management_form.is_valid():
del self.formsets['comments']
return super().is_valid()
def clean(self):

Wyświetl plik

@ -3,7 +3,6 @@
import json
import unittest
from django import VERSION as DJANGO_VERSION
from django.conf import settings
from django.contrib.auth.models import Group, Permission
from django.core import mail
@ -35,18 +34,11 @@ class TestHome(TestCase, WagtailTestUtils):
response = self.client.get(reverse('wagtailadmin_home'))
self.assertEqual(response.status_code, 200)
# check that media attached to menu items is correctly pulled in
if DJANGO_VERSION >= (3, 1):
self.assertContains(
response,
'<script src="/static/testapp/js/kittens.js"></script>',
html=True
)
else:
self.assertContains(
response,
'<script type="text/javascript" src="/static/testapp/js/kittens.js"></script>',
html=True
)
self.assertContains(
response,
'<script src="/static/testapp/js/kittens.js"></script>',
html=True
)
# check that custom menu items (including classname / attrs parameters) are pulled in
self.assertContains(
@ -74,18 +66,11 @@ class TestHome(TestCase, WagtailTestUtils):
self.assertContains(response, "<p>It looks like you're making a website. Would you like some help?</p>")
# check that media attached to dashboard panels is correctly pulled in
if DJANGO_VERSION >= (3, 1):
self.assertContains(
response,
'<script src="/static/testapp/js/clippy.js"></script>',
html=True
)
else:
self.assertContains(
response,
'<script type="text/javascript" src="/static/testapp/js/clippy.js"></script>',
html=True
)
self.assertContains(
response,
'<script src="/static/testapp/js/clippy.js"></script>',
html=True
)
def test_summary_items(self):
response = self.client.get(reverse('wagtailadmin_home'))

Wyświetl plik

@ -3,7 +3,6 @@ import warnings
from collections import OrderedDict
from functools import reduce
from django import VERSION as DJANGO_VERSION
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
from django.db import DEFAULT_DB_ALIAS, NotSupportedError, connections, transaction
from django.db.models import Avg, Count, F, Manager, Q, TextField, Value
@ -21,7 +20,7 @@ from wagtail.search.utils import ADD, MUL, OR
from wagtail.utils.deprecation import RemovedInWagtail217Warning
from .models import IndexEntry
from .query import Lexeme, RawSearchQuery
from .query import Lexeme
from .utils import (
get_content_type_pk, get_descendants_content_types_pks, get_postgresql_connections,
get_sql_weights, get_weight)
@ -387,10 +386,7 @@ class PostgresSearchQueryCompiler(BaseSearchQueryCompiler):
else:
lexemes |= new_lexeme
if DJANGO_VERSION >= (3, 1):
return SearchQuery(lexemes, search_type='raw', config=config)
else:
return RawSearchQuery(lexemes, config=config)
return SearchQuery(lexemes, search_type='raw', config=config)
elif isinstance(query, Phrase):
return SearchQuery(query.query_string, search_type='phrase')

Wyświetl plik

@ -3,7 +3,7 @@
# If that PR gets merged, we should be able to replace this with the version in Django.
from django.contrib.postgres.search import SearchQueryCombinable, SearchQueryField
from django.contrib.postgres.search import SearchQueryField
from django.db.models.expressions import Expression, Value
@ -85,46 +85,3 @@ class CombinedLexeme(LexemeCombinable):
combined_sql = '({} {} {})'.format(lsql, self.connector, rsql)
combined_value = combined_sql % tuple(value_params)
return '%s', [combined_value]
# This class is required for Django 3.0 support and below
# In Django 3.1 onwards, we can replace this with SearchQuery(expression, search_type='raw')
# The PR for the functionality we need is here: https://github.com/django/django/pull/12525
class RawSearchQuery(SearchQueryCombinable, Expression):
_output_field = SearchQueryField()
def __init__(self, expressions, output_field=None, *, config=None, invert=False):
self.config = config
self.invert = invert
self.expressions = expressions
super().__init__(output_field=output_field)
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
resolved = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
if self.config:
if not hasattr(self.config, 'resolve_expression'):
resolved.config = Value(self.config).resolve_expression(query, allow_joins, reuse, summarize, for_save)
else:
resolved.config = self.config.resolve_expression(query, allow_joins, reuse, summarize, for_save)
return resolved
def as_sql(self, compiler, connection):
sql, params, = compiler.compile(self.expressions)
if self.config:
config_sql, config_params = compiler.compile(self.config)
template = 'to_tsquery({}::regconfig, {})'.format(config_sql, sql)
params = config_params + params
else:
template = 'to_tsquery({})'.format(sql)
if self.invert:
template = '!!({})'.format(template)
return template, params
def _combine(self, *args, **kwargs):
combined = super()._combine(*args, **kwargs)
combined.output_field = SearchQueryField()
return combined
def __invert__(self):
return type(self)(self.lexeme, config=self.config, invert=not self.invert)

Wyświetl plik

@ -2,12 +2,7 @@ import uuid
from warnings import warn
try:
from asgiref.local import Local
except ImportError: # fallback for Django <3.0
from threading import local as Local
from asgiref.local import Local
from django.utils.functional import LazyObject
from wagtail.core import hooks

Wyświetl plik

@ -3,7 +3,6 @@ import warnings
from collections import OrderedDict
from functools import reduce
from django import VERSION as DJANGO_VERSION
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
from django.db import DEFAULT_DB_ALIAS, NotSupportedError, connections, transaction
from django.db.models import Avg, Count, F, Manager, Q, TextField, Value
@ -18,7 +17,7 @@ from ....models import IndexEntry
from ....query import And, Boost, MatchAll, Not, Or, Phrase, PlainText
from ....utils import ADD, MUL, OR, get_content_type_pk, get_descendants_content_types_pks
from ...base import BaseSearchBackend, BaseSearchQueryCompiler, BaseSearchResults, FilterFieldError
from .query import Lexeme, RawSearchQuery
from .query import Lexeme
from .weights import get_sql_weights, get_weight
@ -375,10 +374,7 @@ class PostgresSearchQueryCompiler(BaseSearchQueryCompiler):
else:
lexemes |= new_lexeme
if DJANGO_VERSION >= (3, 1):
return SearchQuery(lexemes, search_type='raw', config=config)
else:
return RawSearchQuery(lexemes, config=config)
return SearchQuery(lexemes, search_type='raw', config=config)
elif isinstance(query, Phrase):
return SearchQuery(query.query_string, search_type='phrase')

Wyświetl plik

@ -1,4 +1,4 @@
from django.contrib.postgres.search import SearchQueryCombinable, SearchQueryField
from django.contrib.postgres.search import SearchQueryField
from django.db.models.expressions import Expression, Value
@ -80,46 +80,3 @@ class CombinedLexeme(LexemeCombinable):
combined_sql = '({} {} {})'.format(lsql, self.connector, rsql)
combined_value = combined_sql % tuple(value_params)
return '%s', [combined_value]
# This class is required for Django 3.0 support and below
# In Django 3.1 onwards, we can replace this with SearchQuery(expression, search_type='raw')
# The PR for the functionality we need is here: https://github.com/django/django/pull/12525
class RawSearchQuery(SearchQueryCombinable, Expression):
_output_field = SearchQueryField()
def __init__(self, expressions, output_field=None, *, config=None, invert=False):
self.config = config
self.invert = invert
self.expressions = expressions
super().__init__(output_field=output_field)
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
resolved = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
if self.config:
if not hasattr(self.config, 'resolve_expression'):
resolved.config = Value(self.config).resolve_expression(query, allow_joins, reuse, summarize, for_save)
else:
resolved.config = self.config.resolve_expression(query, allow_joins, reuse, summarize, for_save)
return resolved
def as_sql(self, compiler, connection):
sql, params, = compiler.compile(self.expressions)
if self.config:
config_sql, config_params = compiler.compile(self.config)
template = 'to_tsquery({}::regconfig, {})'.format(config_sql, sql)
params = config_params + params
else:
template = 'to_tsquery({})'.format(sql)
if self.invert:
template = '!!({})'.format(template)
return template, params
def _combine(self, *args, **kwargs):
combined = super()._combine(*args, **kwargs)
combined.output_field = SearchQueryField()
return combined
def __invert__(self):
return type(self)(self.lexeme, config=self.config, invert=not self.invert)

Wyświetl plik

@ -1,7 +1,6 @@
import itertools
import re
from django import VERSION as DJANGO_VERSION
from django import template
from wagtail.core import hooks
@ -44,18 +43,11 @@ def format_permissions(permission_bound_field):
# iterate over permission_bound_field to build a lookup of individual renderable
# checkbox objects
if DJANGO_VERSION < (3, 1):
# checkbox.data['value'] gives the ID
checkboxes_by_id = {
int(checkbox.data['value']): checkbox
for checkbox in permission_bound_field
}
else:
# checkbox.data['value'] gives a ModelChoiceIteratorValue
checkboxes_by_id = {
int(checkbox.data['value'].value): checkbox
for checkbox in permission_bound_field
}
# checkbox.data['value'] gives a ModelChoiceIteratorValue
checkboxes_by_id = {
int(checkbox.data['value'].value): checkbox
for checkbox in permission_bound_field
}
object_perms = []
other_perms = []