Fixed #34233 -- Dropped support for Python 3.8 and 3.9.

This commit is contained in:
Mariusz Felisiak 2023-01-18 09:46:01 +01:00 committed by GitHub
parent d547171183
commit 3bbe22dafc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 51 additions and 327 deletions

View File

@ -16,8 +16,6 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: python-version:
- '3.8'
- '3.9'
- '3.10' - '3.10'
- '3.11' - '3.11'
- '3.12-dev' - '3.12-dev'

View File

@ -1,6 +1,6 @@
Thanks for downloading Django. Thanks for downloading Django.
To install it, make sure you have Python 3.8 or greater installed. Then run To install it, make sure you have Python 3.10 or greater installed. Then run
this command from the command prompt: this command from the command prompt:
python -m pip install . python -m pip install .

View File

@ -14,7 +14,6 @@ from django.utils.crypto import (
RANDOM_STRING_CHARS, RANDOM_STRING_CHARS,
constant_time_compare, constant_time_compare,
get_random_string, get_random_string,
md5,
pbkdf2, pbkdf2,
) )
from django.utils.deprecation import RemovedInDjango51Warning from django.utils.deprecation import RemovedInDjango51Warning
@ -684,7 +683,7 @@ class MD5PasswordHasher(BasePasswordHasher):
def encode(self, password, salt): def encode(self, password, salt):
self._check_encode_args(password, salt) self._check_encode_args(password, salt)
hash = md5((salt + password).encode()).hexdigest() hash = hashlib.md5((salt + password).encode()).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash) return "%s$%s$%s" % (self.algorithm, salt, hash)
def decode(self, encoded): def decode(self, encoded):
@ -799,7 +798,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
def encode(self, password, salt): def encode(self, password, salt):
if salt != "": if salt != "":
raise ValueError("salt must be empty.") raise ValueError("salt must be empty.")
return md5(password.encode()).hexdigest() return hashlib.md5(password.encode()).hexdigest()
def decode(self, encoded): def decode(self, encoded):
return { return {

View File

@ -2,6 +2,7 @@ import json
import os import os
import posixpath import posixpath
import re import re
from hashlib import md5
from urllib.parse import unquote, urldefrag, urlsplit, urlunsplit from urllib.parse import unquote, urldefrag, urlsplit, urlunsplit
from django.conf import STATICFILES_STORAGE_ALIAS, settings from django.conf import STATICFILES_STORAGE_ALIAS, settings
@ -9,7 +10,6 @@ from django.contrib.staticfiles.utils import check_settings, matches_patterns
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage, storages from django.core.files.storage import FileSystemStorage, storages
from django.utils.crypto import md5
from django.utils.functional import LazyObject from django.utils.functional import LazyObject

View File

@ -6,11 +6,11 @@ import random
import tempfile import tempfile
import time import time
import zlib import zlib
from hashlib import md5
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
from django.core.files import locks from django.core.files import locks
from django.core.files.move import file_move_safe from django.core.files.move import file_move_safe
from django.utils.crypto import md5
class FileBasedCache(BaseCache): class FileBasedCache(BaseCache):

View File

@ -1,4 +1,4 @@
from django.utils.crypto import md5 from hashlib import md5
TEMPLATE_FRAGMENT_KEY_TEMPLATE = "template.cache.%s.%s" TEMPLATE_FRAGMENT_KEY_TEMPLATE = "template.cache.%s.%s"

View File

@ -2,6 +2,7 @@ import logging
import sys import sys
import tempfile import tempfile
import traceback import traceback
from contextlib import aclosing
from asgiref.sync import ThreadSensitiveContext, sync_to_async from asgiref.sync import ThreadSensitiveContext, sync_to_async
@ -19,7 +20,6 @@ from django.http import (
parse_cookie, parse_cookie,
) )
from django.urls import set_script_prefix from django.urls import set_script_prefix
from django.utils.asyncio import aclosing
from django.utils.functional import cached_property from django.utils.functional import cached_property
logger = logging.getLogger("django.request") logger = logging.getLogger("django.request")

View File

@ -275,16 +275,6 @@ def validate_ipv4_address(value):
raise ValidationError( raise ValidationError(
_("Enter a valid IPv4 address."), code="invalid", params={"value": value} _("Enter a valid IPv4 address."), code="invalid", params={"value": value}
) )
else:
# Leading zeros are forbidden to avoid ambiguity with the octal
# notation. This restriction is included in Python 3.9.5+.
# TODO: Remove when dropping support for PY39.
if any(octet != "0" and octet[0] == "0" for octet in value.split(".")):
raise ValidationError(
_("Enter a valid IPv4 address."),
code="invalid",
params={"value": value},
)
def validate_ipv6_address(value): def validate_ipv6_address(value):

View File

@ -5,22 +5,17 @@ import logging
import threading import threading
import time import time
import warnings import warnings
import zoneinfo
from collections import deque from collections import deque
from contextlib import contextmanager from contextlib import contextmanager
from django.db.backends.utils import debug_transaction
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import DEFAULT_DB_ALIAS, DatabaseError, NotSupportedError from django.db import DEFAULT_DB_ALIAS, DatabaseError, NotSupportedError
from django.db.backends import utils from django.db.backends import utils
from django.db.backends.base.validation import BaseDatabaseValidation from django.db.backends.base.validation import BaseDatabaseValidation
from django.db.backends.signals import connection_created from django.db.backends.signals import connection_created
from django.db.backends.utils import debug_transaction
from django.db.transaction import TransactionManagementError from django.db.transaction import TransactionManagementError
from django.db.utils import DatabaseErrorWrapper from django.db.utils import DatabaseErrorWrapper
from django.utils.asyncio import async_unsafe from django.utils.asyncio import async_unsafe

View File

@ -4,8 +4,9 @@ Implementations of SQL functions for SQLite.
import functools import functools
import random import random
import statistics import statistics
import zoneinfo
from datetime import timedelta from datetime import timedelta
from hashlib import sha1, sha224, sha256, sha384, sha512 from hashlib import md5, sha1, sha224, sha256, sha384, sha512
from math import ( from math import (
acos, acos,
asin, asin,
@ -32,14 +33,8 @@ from django.db.backends.utils import (
typecast_timestamp, typecast_timestamp,
) )
from django.utils import timezone from django.utils import timezone
from django.utils.crypto import md5
from django.utils.duration import duration_microseconds from django.utils.duration import duration_microseconds
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
def register(connection): def register(connection):
create_deterministic_function = functools.partial( create_deterministic_function = functools.partial(

View File

@ -4,9 +4,9 @@ import functools
import logging import logging
import time import time
from contextlib import contextmanager from contextlib import contextmanager
from hashlib import md5
from django.db import NotSupportedError from django.db import NotSupportedError
from django.utils.crypto import md5
from django.utils.dateparse import parse_time from django.utils.dateparse import parse_time
logger = logging.getLogger("django.db.backends") logger = logging.getLogger("django.db.backends")

View File

@ -1,12 +1,8 @@
import zoneinfo
from datetime import datetime from datetime import datetime
from datetime import timezone as datetime_timezone from datetime import timezone as datetime_timezone
from datetime import tzinfo from datetime import tzinfo
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.template import Library, Node, TemplateSyntaxError from django.template import Library, Node, TemplateSyntaxError
from django.utils import timezone from django.utils import timezone

View File

@ -1,6 +1,7 @@
import argparse import argparse
import ctypes import ctypes
import faulthandler import faulthandler
import hashlib
import io import io
import itertools import itertools
import logging import logging
@ -27,7 +28,6 @@ from django.test.utils import setup_databases as _setup_databases
from django.test.utils import setup_test_environment from django.test.utils import setup_test_environment
from django.test.utils import teardown_databases as _teardown_databases from django.test.utils import teardown_databases as _teardown_databases
from django.test.utils import teardown_test_environment from django.test.utils import teardown_test_environment
from django.utils.crypto import new_hash
from django.utils.datastructures import OrderedSet from django.utils.datastructures import OrderedSet
try: try:
@ -580,7 +580,7 @@ class Shuffler:
@classmethod @classmethod
def _hash_text(cls, text): def _hash_text(cls, text):
h = new_hash(cls.hash_algorithm, usedforsecurity=False) h = hashlib.new(cls.hash_algorithm, usedforsecurity=False)
h.update(text.encode("utf-8")) h.update(text.encode("utf-8"))
return h.hexdigest() return h.hexdigest()

View File

@ -53,7 +53,6 @@ from django.test.utils import (
) )
from django.utils.deprecation import RemovedInDjango51Warning from django.utils.deprecation import RemovedInDjango51Warning
from django.utils.functional import classproperty from django.utils.functional import classproperty
from django.utils.version import PY310
from django.views.static import serve from django.views.static import serve
logger = logging.getLogger("django.test") logger = logging.getLogger("django.test")
@ -795,32 +794,6 @@ class SimpleTestCase(unittest.TestCase):
**kwargs, **kwargs,
) )
# A similar method is available in Python 3.10+.
if not PY310:
@contextmanager
def assertNoLogs(self, logger, level=None):
"""
Assert no messages are logged on the logger, with at least the
given level.
"""
if isinstance(level, int):
level = logging.getLevelName(level)
elif level is None:
level = "INFO"
try:
with self.assertLogs(logger, level) as cm:
yield
except AssertionError as e:
msg = e.args[0]
expected_msg = (
f"no logs of level {level} or higher triggered on {logger}"
)
if msg != expected_msg:
raise e
else:
self.fail(f"Unexpected logs found: {cm.output!r}")
def assertFieldOutput( def assertFieldOutput(
self, self,
fieldclass, fieldclass,

View File

@ -37,28 +37,3 @@ def async_unsafe(message):
return decorator(func) return decorator(func)
else: else:
return decorator return decorator
try:
from contextlib import aclosing
except ImportError:
# TODO: Remove when dropping support for PY39.
from contextlib import AbstractAsyncContextManager
# Backport of contextlib.aclosing() from Python 3.10. Copyright (C) Python
# Software Foundation (see LICENSE.python).
class aclosing(AbstractAsyncContextManager):
"""
Async context manager for safely finalizing an asynchronously
cleaned-up resource such as an async generator, calling its
``aclose()`` method.
"""
def __init__(self, thing):
self.thing = thing
async def __aenter__(self):
return self.thing
async def __aexit__(self, *exc_info):
await self.thing.aclose()

View File

@ -16,11 +16,11 @@ An example: i18n middleware would need to distinguish caches by the
""" """
import time import time
from collections import defaultdict from collections import defaultdict
from hashlib import md5
from django.conf import settings from django.conf import settings
from django.core.cache import caches from django.core.cache import caches
from django.http import HttpResponse, HttpResponseNotModified from django.http import HttpResponse, HttpResponseNotModified
from django.utils.crypto import md5
from django.utils.http import http_date, parse_etags, parse_http_date_safe, quote_etag from django.utils.http import http_date, parse_etags, parse_http_date_safe, quote_etag
from django.utils.log import log_response from django.utils.log import log_response
from django.utils.regex_helper import _lazy_re_compile from django.utils.regex_helper import _lazy_re_compile

View File

@ -7,7 +7,6 @@ import secrets
from django.conf import settings from django.conf import settings
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.inspect import func_supports_parameter
class InvalidAlgorithm(ValueError): class InvalidAlgorithm(ValueError):
@ -75,19 +74,3 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
password = force_bytes(password) password = force_bytes(password)
salt = force_bytes(salt) salt = force_bytes(salt)
return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen) return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen)
# TODO: Remove when dropping support for PY38. inspect.signature() is used to
# detect whether the usedforsecurity argument is available as this fix may also
# have been applied by downstream package maintainers to other versions in
# their repositories.
if func_supports_parameter(hashlib.md5, "usedforsecurity"):
md5 = hashlib.md5
new_hash = hashlib.new
else:
def md5(data=b"", *, usedforsecurity=True):
return hashlib.md5(data)
def new_hash(hash_algorithm, *, usedforsecurity=True):
return hashlib.new(hash_algorithm)

View File

@ -4,18 +4,9 @@ import re
import unicodedata import unicodedata
from binascii import Error as BinasciiError from binascii import Error as BinasciiError
from email.utils import formatdate from email.utils import formatdate
from urllib.parse import ( from urllib.parse import quote, unquote
ParseResult,
SplitResult,
_coerce_args,
_splitnetloc,
_splitparams,
quote,
scheme_chars,
unquote,
)
from urllib.parse import urlencode as original_urlencode from urllib.parse import urlencode as original_urlencode
from urllib.parse import uses_params from urllib.parse import urlparse
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.regex_helper import _lazy_re_compile from django.utils.regex_helper import _lazy_re_compile
@ -47,10 +38,6 @@ ASCTIME_DATE = _lazy_re_compile(r"^\w{3} %s %s %s %s$" % (__M, __D2, __T, __Y))
RFC3986_GENDELIMS = ":/?#[]@" RFC3986_GENDELIMS = ":/?#[]@"
RFC3986_SUBDELIMS = "!$&'()*+,;=" RFC3986_SUBDELIMS = "!$&'()*+,;="
# TODO: Remove when dropping support for PY38.
# Unsafe bytes to be removed per WHATWG spec.
_UNSAFE_URL_BYTES_TO_REMOVE = ["\t", "\r", "\n"]
def urlencode(query, doseq=False): def urlencode(query, doseq=False):
""" """
@ -283,74 +270,13 @@ def url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
) )
# TODO: Remove when dropping support for PY38.
# Copied from urllib.parse.urlparse() but uses fixed urlsplit() function.
def _urlparse(url, scheme="", allow_fragments=True):
"""Parse a URL into 6 components:
<scheme>://<netloc>/<path>;<params>?<query>#<fragment>
Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
Note that we don't break the components up in smaller bits
(e.g. netloc is a single string) and we don't expand % escapes."""
url, scheme, _coerce_result = _coerce_args(url, scheme)
splitresult = _urlsplit(url, scheme, allow_fragments)
scheme, netloc, url, query, fragment = splitresult
if scheme in uses_params and ";" in url:
url, params = _splitparams(url)
else:
params = ""
result = ParseResult(scheme, netloc, url, params, query, fragment)
return _coerce_result(result)
# TODO: Remove when dropping support for PY38.
def _remove_unsafe_bytes_from_url(url):
for b in _UNSAFE_URL_BYTES_TO_REMOVE:
url = url.replace(b, "")
return url
# TODO: Remove when dropping support for PY38.
# Backport of urllib.parse.urlsplit() from Python 3.9.
def _urlsplit(url, scheme="", allow_fragments=True):
"""Parse a URL into 5 components:
<scheme>://<netloc>/<path>?<query>#<fragment>
Return a 5-tuple: (scheme, netloc, path, query, fragment).
Note that we don't break the components up in smaller bits
(e.g. netloc is a single string) and we don't expand % escapes."""
url, scheme, _coerce_result = _coerce_args(url, scheme)
url = _remove_unsafe_bytes_from_url(url)
scheme = _remove_unsafe_bytes_from_url(scheme)
netloc = query = fragment = ""
i = url.find(":")
if i > 0:
for c in url[:i]:
if c not in scheme_chars:
break
else:
scheme, url = url[:i].lower(), url[i + 1 :]
if url[:2] == "//":
netloc, url = _splitnetloc(url, 2)
if ("[" in netloc and "]" not in netloc) or (
"]" in netloc and "[" not in netloc
):
raise ValueError("Invalid IPv6 URL")
if allow_fragments and "#" in url:
url, fragment = url.split("#", 1)
if "?" in url:
url, query = url.split("?", 1)
v = SplitResult(scheme, netloc, url, query, fragment)
return _coerce_result(v)
def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False): def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
# Chrome considers any URL with more than two slashes to be absolute, but # Chrome considers any URL with more than two slashes to be absolute, but
# urlparse is not so flexible. Treat any url with three slashes as unsafe. # urlparse is not so flexible. Treat any url with three slashes as unsafe.
if url.startswith("///"): if url.startswith("///"):
return False return False
try: try:
url_info = _urlparse(url) url_info = urlparse(url)
except ValueError: # e.g. invalid IPv6 addresses except ValueError: # e.g. invalid IPv6 addresses
return False return False
# Forbid URLs like http:///example.com - with a scheme, but without a hostname. # Forbid URLs like http:///example.com - with a scheme, but without a hostname.

View File

@ -3,12 +3,7 @@ Timezone-related classes and functions.
""" """
import functools import functools
import zoneinfo
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from contextlib import ContextDecorator from contextlib import ContextDecorator
from datetime import datetime, timedelta, timezone, tzinfo from datetime import datetime, timedelta, timezone, tzinfo

View File

@ -4,7 +4,7 @@ How to install Django on Windows
.. highlight:: doscon .. highlight:: doscon
This document will guide you through installing Python 3.8 and Django on This document will guide you through installing Python 3.11 and Django on
Windows. It also provides instructions for setting up a virtual environment, Windows. It also provides instructions for setting up a virtual environment,
which makes it easier to work on Python projects. This is meant as a beginner's which makes it easier to work on Python projects. This is meant as a beginner's
guide for users working on Django projects and does not reflect how Django guide for users working on Django projects and does not reflect how Django
@ -20,7 +20,7 @@ Install Python
============== ==============
Django is a Python web framework, thus requiring Python to be installed on your Django is a Python web framework, thus requiring Python to be installed on your
machine. At the time of writing, Python 3.8 is the latest version. machine. At the time of writing, Python 3.11 is the latest version.
To install Python on your machine go to https://www.python.org/downloads/. The To install Python on your machine go to https://www.python.org/downloads/. The
website should offer you a download button for the latest Python version. website should offer you a download button for the latest Python version.

View File

@ -91,14 +91,14 @@ In addition to the default environments, ``tox`` supports running unit tests
for other versions of Python and other database backends. Since Django's test for other versions of Python and other database backends. Since Django's test
suite doesn't bundle a settings file for database backends other than SQLite, suite doesn't bundle a settings file for database backends other than SQLite,
however, you must :ref:`create and provide your own test settings however, you must :ref:`create and provide your own test settings
<running-unit-tests-settings>`. For example, to run the tests on Python 3.9 <running-unit-tests-settings>`. For example, to run the tests on Python 3.10
using PostgreSQL: using PostgreSQL:
.. console:: .. console::
$ tox -e py39-postgres -- --settings=my_postgres_settings $ tox -e py310-postgres -- --settings=my_postgres_settings
This command sets up a Python 3.9 virtual environment, installs Django's This command sets up a Python 3.10 virtual environment, installs Django's
test suite dependencies (including those for PostgreSQL), and calls test suite dependencies (including those for PostgreSQL), and calls
``runtests.py`` with the supplied arguments (in this case, ``runtests.py`` with the supplied arguments (in this case,
``--settings=my_postgres_settings``). ``--settings=my_postgres_settings``).
@ -113,14 +113,14 @@ above:
.. code-block:: console .. code-block:: console
$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py39-postgres $ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py310-postgres
Windows users should use: Windows users should use:
.. code-block:: doscon .. code-block:: doscon
...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings ...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py39-postgres ...\> tox -e py310-postgres
Running the JavaScript tests Running the JavaScript tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -220,15 +220,15 @@ this. For a small app like polls, this process isn't too difficult.
Programming Language :: Python Programming Language :: Python
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.11
Topic :: Internet :: WWW/HTTP Topic :: Internet :: WWW/HTTP
Topic :: Internet :: WWW/HTTP :: Dynamic Content Topic :: Internet :: WWW/HTTP :: Dynamic Content
[options] [options]
include_package_data = true include_package_data = true
packages = find: packages = find:
python_requires = >=3.8 python_requires = >=3.10
install_requires = install_requires =
Django >= X.Y # Replace "X.Y" as appropriate Django >= X.Y # Replace "X.Y" as appropriate

View File

@ -23,7 +23,7 @@ in a shell prompt (indicated by the $ prefix):
If Django is installed, you should see the version of your installation. If it If Django is installed, you should see the version of your installation. If it
isn't, you'll get an error telling "No module named django". isn't, you'll get an error telling "No module named django".
This tutorial is written for Django |version|, which supports Python 3.8 and This tutorial is written for Django |version|, which supports Python 3.10 and
later. If the Django version doesn't match, you can refer to the tutorial for later. If the Django version doesn't match, you can refer to the tutorial for
your version of Django by using the version switcher at the bottom right corner your version of Django by using the version switcher at the bottom right corner
of this page, or update Django to the newest version. If you're using an older of this page, or update Django to the newest version. If you're using an older

View File

@ -21,6 +21,8 @@ Python compatibility
Django 5.0 supports Python 3.10, 3.11, and 3.12. We **highly recommend** and Django 5.0 supports Python 3.10, 3.11, and 3.12. We **highly recommend** and
only officially support the latest release of each series. only officially support the latest release of each series.
The Django 4.2.x series is the last to support Python 3.8 and 3.9.
Third-party library support for older version of Django Third-party library support for older version of Django
======================================================= =======================================================

View File

@ -32,8 +32,7 @@ False <USE_TZ>` in your settings file.
In older version, time zone support was disabled by default. In older version, time zone support was disabled by default.
Time zone support uses :mod:`zoneinfo`, which is part of the Python standard Time zone support uses :mod:`zoneinfo`, which is part of the Python standard
library from Python 3.9. The ``backports.zoneinfo`` package is automatically library from Python 3.9.
installed alongside Django if you are using Python 3.8.
If you're wrestling with a particular problem, start with the :ref:`time zone If you're wrestling with a particular problem, start with the :ref:`time zone
FAQ <time-zones-faq>`. FAQ <time-zones-faq>`.

View File

@ -17,8 +17,6 @@ classifiers =
Programming Language :: Python Programming Language :: Python
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.11
Topic :: Internet :: WWW/HTTP Topic :: Internet :: WWW/HTTP
@ -34,13 +32,12 @@ project_urls =
Tracker = https://code.djangoproject.com/ Tracker = https://code.djangoproject.com/
[options] [options]
python_requires = >=3.8 python_requires = >=3.10
packages = find: packages = find:
include_package_data = true include_package_data = true
zip_safe = false zip_safe = false
install_requires = install_requires =
asgiref >= 3.6.0 asgiref >= 3.6.0
backports.zoneinfo; python_version<"3.9"
sqlparse >= 0.2.2 sqlparse >= 0.2.2
tzdata; sys_platform == 'win32' tzdata; sys_platform == 'win32'

View File

@ -32,7 +32,6 @@ from django.db.migrations.recorder import MigrationRecorder
from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings
from django.test.utils import captured_stderr, captured_stdout from django.test.utils import captured_stderr, captured_stdout
from django.urls import path from django.urls import path
from django.utils.version import PY39
from django.views.static import serve from django.views.static import serve
from . import urls from . import urls
@ -107,7 +106,7 @@ class AdminScriptTestCase(SimpleTestCase):
paths.append(os.path.dirname(backend_dir)) paths.append(os.path.dirname(backend_dir))
return paths return paths
def run_test(self, args, settings_file=None, apps=None, umask=None): def run_test(self, args, settings_file=None, apps=None, umask=-1):
base_dir = os.path.dirname(self.test_dir) base_dir = os.path.dirname(self.test_dir)
# The base dir for Django's tests is one level up. # The base dir for Django's tests is one level up.
tests_dir = os.path.dirname(os.path.dirname(__file__)) tests_dir = os.path.dirname(os.path.dirname(__file__))
@ -136,12 +135,11 @@ class AdminScriptTestCase(SimpleTestCase):
cwd=self.test_dir, cwd=self.test_dir,
env=test_environ, env=test_environ,
text=True, text=True,
# subprocess.run()'s umask was added in Python 3.9. umask=umask,
**({"umask": umask} if umask and PY39 else {}),
) )
return p.stdout, p.stderr return p.stdout, p.stderr
def run_django_admin(self, args, settings_file=None, umask=None): def run_django_admin(self, args, settings_file=None, umask=-1):
return self.run_test(["-m", "django", *args], settings_file, umask=umask) return self.run_test(["-m", "django", *args], settings_file, umask=umask)
def run_manage(self, args, settings_file=None, manage_py=None): def run_manage(self, args, settings_file=None, manage_py=None):
@ -2812,7 +2810,6 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
sys.platform == "win32", sys.platform == "win32",
"Windows only partially supports umasks and chmod.", "Windows only partially supports umasks and chmod.",
) )
@unittest.skipUnless(PY39, "subprocess.run()'s umask was added in Python 3.9.")
def test_honor_umask(self): def test_honor_umask(self):
_, err = self.run_django_admin(["startproject", "testproject"], umask=0o077) _, err = self.run_django_admin(["startproject", "testproject"], umask=0o077)
self.assertNoOutput(err) self.assertNoOutput(err)

View File

@ -2,14 +2,10 @@ import datetime
import os import os
import re import re
import unittest import unittest
import zoneinfo
from unittest import mock from unittest import mock
from urllib.parse import parse_qsl, urljoin, urlparse from urllib.parse import parse_qsl, urljoin, urlparse
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import AdminSite, ModelAdmin from django.contrib.admin import AdminSite, ModelAdmin
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME

View File

@ -1,15 +1,11 @@
import gettext import gettext
import os import os
import re import re
import zoneinfo
from datetime import datetime, timedelta from datetime import datetime, timedelta
from importlib import import_module from importlib import import_module
from unittest import skipUnless from unittest import skipUnless
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin

View File

@ -1,11 +1,7 @@
import zoneinfo
from datetime import datetime, timedelta from datetime import datetime, timedelta
from datetime import timezone as datetime_timezone from datetime import timezone as datetime_timezone
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.conf import settings from django.conf import settings
from django.db import DataError, OperationalError from django.db import DataError, OperationalError
from django.db.models import ( from django.db.models import (

View File

@ -8,13 +8,9 @@ import pathlib
import re import re
import sys import sys
import uuid import uuid
import zoneinfo
from unittest import mock from unittest import mock
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
import custom_migration_operations.more_operations import custom_migration_operations.more_operations
import custom_migration_operations.operations import custom_migration_operations.operations

View File

@ -1,7 +1,6 @@
aiosmtpd aiosmtpd
asgiref >= 3.6.0 asgiref >= 3.6.0
argon2-cffi >= 19.2.0 argon2-cffi >= 19.2.0
backports.zoneinfo; python_version < '3.9'
bcrypt bcrypt
black black
docutils docutils

View File

@ -1,4 +1,3 @@
import logging
import os import os
import unittest import unittest
import warnings import warnings
@ -47,7 +46,6 @@ from django.test.utils import (
) )
from django.urls import NoReverseMatch, path, reverse, reverse_lazy from django.urls import NoReverseMatch, path, reverse, reverse_lazy
from django.utils.deprecation import RemovedInDjango51Warning from django.utils.deprecation import RemovedInDjango51Warning
from django.utils.log import DEFAULT_LOGGING
from django.utils.version import PY311 from django.utils.version import PY311
from .models import Car, Person, PossessedCar from .models import Car, Person, PossessedCar
@ -1198,47 +1196,6 @@ class AssertWarnsMessageTests(SimpleTestCase):
func1() func1()
# TODO: Remove when dropping support for PY39.
class AssertNoLogsTest(SimpleTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
logging.config.dictConfig(DEFAULT_LOGGING)
cls.addClassCleanup(logging.config.dictConfig, settings.LOGGING)
def setUp(self):
self.logger = logging.getLogger("django")
@override_settings(DEBUG=True)
def test_fails_when_log_emitted(self):
msg = "Unexpected logs found: ['INFO:django:FAIL!']"
with self.assertRaisesMessage(AssertionError, msg):
with self.assertNoLogs("django", "INFO"):
self.logger.info("FAIL!")
@override_settings(DEBUG=True)
def test_text_level(self):
with self.assertNoLogs("django", "INFO"):
self.logger.debug("DEBUG logs are ignored.")
@override_settings(DEBUG=True)
def test_int_level(self):
with self.assertNoLogs("django", logging.INFO):
self.logger.debug("DEBUG logs are ignored.")
@override_settings(DEBUG=True)
def test_default_level(self):
with self.assertNoLogs("django"):
self.logger.debug("DEBUG logs are ignored.")
@override_settings(DEBUG=True)
def test_does_not_hide_other_failures(self):
msg = "1 != 2"
with self.assertRaisesMessage(AssertionError, msg):
with self.assertNoLogs("django"):
self.assertEqual(1, 2)
class AssertFieldOutputTests(SimpleTestCase): class AssertFieldOutputTests(SimpleTestCase):
def test_assert_field_output(self): def test_assert_field_output(self):
error_invalid = ["Enter a valid email address."] error_invalid = ["Enter a valid email address."]

View File

@ -1,15 +1,11 @@
import datetime import datetime
import re import re
import sys import sys
import zoneinfo
from contextlib import contextmanager from contextlib import contextmanager
from unittest import SkipTest, skipIf from unittest import SkipTest, skipIf
from xml.dom.minidom import parseString from xml.dom.minidom import parseString
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core import serializers from django.core import serializers
from django.db import connection from django.db import connection

View File

@ -9,16 +9,12 @@ import time
import types import types
import weakref import weakref
import zipfile import zipfile
import zoneinfo
from importlib import import_module from importlib import import_module
from pathlib import Path from pathlib import Path
from subprocess import CompletedProcess from subprocess import CompletedProcess
from unittest import mock, skip, skipIf from unittest import mock, skip, skipIf
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
import django.__main__ import django.__main__
from django.apps.registry import Apps from django.apps.registry import Apps
from django.test import SimpleTestCase from django.test import SimpleTestCase

View File

@ -11,7 +11,6 @@ from django.utils.module_loading import (
import_string, import_string,
module_has_submodule, module_has_submodule,
) )
from django.utils.version import PY310
class DefaultLoader(unittest.TestCase): class DefaultLoader(unittest.TestCase):
@ -205,35 +204,12 @@ class AutodiscoverModulesTestCase(SimpleTestCase):
self.assertEqual(site._registry, {"lorem": "ipsum"}) self.assertEqual(site._registry, {"lorem": "ipsum"})
if PY310: class TestFinder:
def __init__(self, *args, **kwargs):
self.importer = zipimporter(*args, **kwargs)
class TestFinder: def find_spec(self, path, target=None):
def __init__(self, *args, **kwargs): return self.importer.find_spec(path, target)
self.importer = zipimporter(*args, **kwargs)
def find_spec(self, path, target=None):
return self.importer.find_spec(path, target)
else:
class TestFinder:
def __init__(self, *args, **kwargs):
self.importer = zipimporter(*args, **kwargs)
def find_module(self, path):
importer = self.importer.find_module(path)
if importer is None:
return
return TestLoader(importer)
class TestLoader:
def __init__(self, importer):
self.importer = importer
def load_module(self, name):
mod = self.importer.load_module(name)
mod.__loader__ = self
return mod
class CustomLoader(EggLoader): class CustomLoader(EggLoader):

View File

@ -1,11 +1,7 @@
import datetime import datetime
import zoneinfo
from unittest import mock from unittest import mock
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.test import SimpleTestCase, override_settings from django.test import SimpleTestCase, override_settings
from django.utils import timezone from django.utils import timezone

View File

@ -24,7 +24,7 @@ passenv = DJANGO_SETTINGS_MODULE PYTHONPATH HOME DISPLAY OBJC_DISABLE_INITIALIZE
setenv = setenv =
PYTHONDONTWRITEBYTECODE=1 PYTHONDONTWRITEBYTECODE=1
deps = deps =
py{3,38,39,310,311}: -rtests/requirements/py3.txt py{3,310,311}: -rtests/requirements/py3.txt
postgres: -rtests/requirements/postgres.txt postgres: -rtests/requirements/postgres.txt
mysql: -rtests/requirements/mysql.txt mysql: -rtests/requirements/mysql.txt
oracle: -rtests/requirements/oracle.txt oracle: -rtests/requirements/oracle.txt