mirror of https://github.com/django/django.git
Fixed #34233 -- Dropped support for Python 3.8 and 3.9.
This commit is contained in:
parent
d547171183
commit
3bbe22dafc
|
@ -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'
|
||||||
|
|
2
INSTALL
2
INSTALL
|
@ -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 .
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
=======================================================
|
=======================================================
|
||||||
|
|
||||||
|
|
|
@ -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>`.
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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."]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue