mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #32355 -- Dropped support for Python 3.6 and 3.7
This commit is contained in:
		
				
					committed by
					
						
						Carlton Gibson
					
				
			
			
				
	
			
			
			
						parent
						
							9c6ba87692
						
					
				
				
					commit
					ec0ff40631
				
			
							
								
								
									
										2
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								INSTALL
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
Thanks for downloading Django.
 | 
			
		||||
 | 
			
		||||
To install it, make sure you have Python 3.6 or greater installed. Then run
 | 
			
		||||
To install it, make sure you have Python 3.8 or greater installed. Then run
 | 
			
		||||
this command from the command prompt:
 | 
			
		||||
 | 
			
		||||
    python -m pip install .
 | 
			
		||||
 
 | 
			
		||||
@@ -154,9 +154,7 @@ class Command(BaseCommand):
 | 
			
		||||
                    self.has_errors = True
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
                # PY37: Remove str() when dropping support for PY37.
 | 
			
		||||
                # https://bugs.python.org/issue31961
 | 
			
		||||
                args = [self.program, *self.program_options, '-o', str(mo_path), str(po_path)]
 | 
			
		||||
                args = [self.program, *self.program_options, '-o', mo_path, po_path]
 | 
			
		||||
                futures.append(executor.submit(popen_wrapper, args))
 | 
			
		||||
 | 
			
		||||
            for future in concurrent.futures.as_completed(futures):
 | 
			
		||||
 
 | 
			
		||||
@@ -261,12 +261,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
 | 
			
		||||
        # For now, it's here so that every use of "threading" is
 | 
			
		||||
        # also async-compatible.
 | 
			
		||||
        try:
 | 
			
		||||
            if hasattr(asyncio, 'current_task'):
 | 
			
		||||
                # Python 3.7 and up
 | 
			
		||||
            current_task = asyncio.current_task()
 | 
			
		||||
            else:
 | 
			
		||||
                # Python 3.6
 | 
			
		||||
                current_task = asyncio.Task.current_task()
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            current_task = None
 | 
			
		||||
        # Current task can be none even if the current_task call didn't error
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ from django.utils.asyncio import async_unsafe
 | 
			
		||||
from django.utils.dateparse import parse_datetime, parse_time
 | 
			
		||||
from django.utils.duration import duration_microseconds
 | 
			
		||||
from django.utils.regex_helper import _lazy_re_compile
 | 
			
		||||
from django.utils.version import PY38
 | 
			
		||||
 | 
			
		||||
from .client import DatabaseClient
 | 
			
		||||
from .creation import DatabaseCreation
 | 
			
		||||
@@ -180,9 +179,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
 | 
			
		||||
                "settings.DATABASES is improperly configured. "
 | 
			
		||||
                "Please supply the NAME value.")
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            # TODO: Remove str() when dropping support for PY36.
 | 
			
		||||
            # https://bugs.python.org/issue33496
 | 
			
		||||
            'database': str(settings_dict['NAME']),
 | 
			
		||||
            'database': settings_dict['NAME'],
 | 
			
		||||
            'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
 | 
			
		||||
            **settings_dict['OPTIONS'],
 | 
			
		||||
        }
 | 
			
		||||
@@ -206,13 +203,10 @@ class DatabaseWrapper(BaseDatabaseWrapper):
 | 
			
		||||
    @async_unsafe
 | 
			
		||||
    def get_new_connection(self, conn_params):
 | 
			
		||||
        conn = Database.connect(**conn_params)
 | 
			
		||||
        if PY38:
 | 
			
		||||
        create_deterministic_function = functools.partial(
 | 
			
		||||
            conn.create_function,
 | 
			
		||||
            deterministic=True,
 | 
			
		||||
        )
 | 
			
		||||
        else:
 | 
			
		||||
            create_deterministic_function = conn.create_function
 | 
			
		||||
        create_deterministic_function('django_date_extract', 2, _sqlite_datetime_extract)
 | 
			
		||||
        create_deterministic_function('django_date_trunc', 4, _sqlite_date_trunc)
 | 
			
		||||
        create_deterministic_function('django_datetime_cast_date', 3, _sqlite_datetime_cast_date)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,5 @@ class DatabaseClient(BaseDatabaseClient):
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def settings_to_cmd_args_env(cls, settings_dict, parameters):
 | 
			
		||||
        args = [
 | 
			
		||||
            cls.executable_name,
 | 
			
		||||
            # TODO: Remove str() when dropping support for PY37. args
 | 
			
		||||
            # parameter accepts path-like objects on Windows since Python 3.8.
 | 
			
		||||
            str(settings_dict['NAME']),
 | 
			
		||||
            *parameters,
 | 
			
		||||
        ]
 | 
			
		||||
        args = [cls.executable_name, settings_dict['NAME'], *parameters]
 | 
			
		||||
        return args, None
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,6 @@ class MigrationQuestioner:
 | 
			
		||||
        except ImportError:
 | 
			
		||||
            return self.defaults.get("ask_initial", False)
 | 
			
		||||
        else:
 | 
			
		||||
            # getattr() needed on PY36 and older (replace with attribute access).
 | 
			
		||||
            if getattr(migrations_module, "__file__", None):
 | 
			
		||||
                filenames = os.listdir(os.path.dirname(migrations_module.__file__))
 | 
			
		||||
            elif hasattr(migrations_module, "__path__"):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,6 @@ from http import cookies
 | 
			
		||||
# For backwards compatibility in Django 2.1.
 | 
			
		||||
SimpleCookie = cookies.SimpleCookie
 | 
			
		||||
 | 
			
		||||
# Add support for the SameSite attribute (obsolete when PY37 is unsupported).
 | 
			
		||||
cookies.Morsel._reserved.setdefault('samesite', 'SameSite')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_cookie(cookie):
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -18,19 +18,10 @@ from django.utils.datastructures import (
 | 
			
		||||
from django.utils.encoding import escape_uri_path, iri_to_uri
 | 
			
		||||
from django.utils.functional import cached_property
 | 
			
		||||
from django.utils.http import is_same_domain
 | 
			
		||||
from django.utils.inspect import func_supports_parameter
 | 
			
		||||
from django.utils.regex_helper import _lazy_re_compile
 | 
			
		||||
 | 
			
		||||
from .multipartparser import parse_header
 | 
			
		||||
 | 
			
		||||
# TODO: Remove when dropping support for PY37. inspect.signature() is used to
 | 
			
		||||
# detect whether the max_num_fields argument is available as this security fix
 | 
			
		||||
# was backported to Python 3.6.8 and 3.7.2, and may also have been applied by
 | 
			
		||||
# downstream package maintainers to other versions in their repositories.
 | 
			
		||||
if not func_supports_parameter(parse_qsl, 'max_num_fields'):
 | 
			
		||||
    from django.utils.http import parse_qsl
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RAISE_ERROR = object()
 | 
			
		||||
host_validation_re = _lazy_re_compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9\.:]+\])(:\d+)?$")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ from django.test.utils import (
 | 
			
		||||
    teardown_test_environment,
 | 
			
		||||
)
 | 
			
		||||
from django.utils.datastructures import OrderedSet
 | 
			
		||||
from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import ipdb as pdb
 | 
			
		||||
@@ -240,8 +239,8 @@ failure and get a correct traceback.
 | 
			
		||||
        self.stop_if_failfast()
 | 
			
		||||
 | 
			
		||||
    def addSubTest(self, test, subtest, err):
 | 
			
		||||
        # Follow Python 3.5's implementation of unittest.TestResult.addSubTest()
 | 
			
		||||
        # by not doing anything when a subtest is successful.
 | 
			
		||||
        # Follow Python's implementation of unittest.TestResult.addSubTest() by
 | 
			
		||||
        # not doing anything when a subtest is successful.
 | 
			
		||||
        if err is not None:
 | 
			
		||||
            # Call check_picklable() before check_subtest_picklable() since
 | 
			
		||||
            # check_picklable() performs the tblib check.
 | 
			
		||||
@@ -540,7 +539,6 @@ class DiscoverRunner:
 | 
			
		||||
                'Output timings, including database set up and total run time.'
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
        if PY37:
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '-k', action='append', dest='test_name_patterns',
 | 
			
		||||
            help=(
 | 
			
		||||
 
 | 
			
		||||
@@ -231,15 +231,11 @@ def get_child_arguments():
 | 
			
		||||
        exe_entrypoint = py_script.with_suffix('.exe')
 | 
			
		||||
        if exe_entrypoint.exists():
 | 
			
		||||
            # Should be executed directly, ignoring sys.executable.
 | 
			
		||||
            # TODO: Remove str() when dropping support for PY37.
 | 
			
		||||
            # args parameter accepts path-like on Windows from Python 3.8.
 | 
			
		||||
            return [str(exe_entrypoint), *sys.argv[1:]]
 | 
			
		||||
            return [exe_entrypoint, *sys.argv[1:]]
 | 
			
		||||
        script_entrypoint = py_script.with_name('%s-script.py' % py_script.name)
 | 
			
		||||
        if script_entrypoint.exists():
 | 
			
		||||
            # Should be executed as usual.
 | 
			
		||||
            # TODO: Remove str() when dropping support for PY37.
 | 
			
		||||
            # args parameter accepts path-like on Windows from Python 3.8.
 | 
			
		||||
            return [*args, str(script_entrypoint), *sys.argv[1:]]
 | 
			
		||||
            return [*args, script_entrypoint, *sys.argv[1:]]
 | 
			
		||||
        raise RuntimeError('Script %s does not exist.' % py_script)
 | 
			
		||||
    else:
 | 
			
		||||
        args += sys.argv
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ from binascii import Error as BinasciiError
 | 
			
		||||
from email.utils import formatdate
 | 
			
		||||
from urllib.parse import (
 | 
			
		||||
    ParseResult, SplitResult, _coerce_args, _splitnetloc, _splitparams,
 | 
			
		||||
    scheme_chars, unquote, urlencode as original_urlencode, uses_params,
 | 
			
		||||
    scheme_chars, urlencode as original_urlencode, uses_params,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from django.utils.datastructures import MultiValueDict
 | 
			
		||||
@@ -343,78 +343,6 @@ def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
 | 
			
		||||
            (not scheme or scheme in valid_schemes))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: Remove when dropping support for PY37.
 | 
			
		||||
def parse_qsl(
 | 
			
		||||
    qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8',
 | 
			
		||||
    errors='replace', max_num_fields=None,
 | 
			
		||||
):
 | 
			
		||||
    """
 | 
			
		||||
    Return a list of key/value tuples parsed from query string.
 | 
			
		||||
 | 
			
		||||
    Backport of urllib.parse.parse_qsl() from Python 3.8.
 | 
			
		||||
    Copyright (C) 2020 Python Software Foundation (see LICENSE.python).
 | 
			
		||||
 | 
			
		||||
    ----
 | 
			
		||||
 | 
			
		||||
    Parse a query given as a string argument.
 | 
			
		||||
 | 
			
		||||
    Arguments:
 | 
			
		||||
 | 
			
		||||
    qs: percent-encoded query string to be parsed
 | 
			
		||||
 | 
			
		||||
    keep_blank_values: flag indicating whether blank values in
 | 
			
		||||
        percent-encoded queries should be treated as blank strings. A
 | 
			
		||||
        true value indicates that blanks should be retained as blank
 | 
			
		||||
        strings. The default false value indicates that blank values
 | 
			
		||||
        are to be ignored and treated as if they were  not included.
 | 
			
		||||
 | 
			
		||||
    strict_parsing: flag indicating what to do with parsing errors. If false
 | 
			
		||||
        (the default), errors are silently ignored. If true, errors raise a
 | 
			
		||||
        ValueError exception.
 | 
			
		||||
 | 
			
		||||
    encoding and errors: specify how to decode percent-encoded sequences
 | 
			
		||||
        into Unicode characters, as accepted by the bytes.decode() method.
 | 
			
		||||
 | 
			
		||||
    max_num_fields: int. If set, then throws a ValueError if there are more
 | 
			
		||||
        than n fields read by parse_qsl().
 | 
			
		||||
 | 
			
		||||
    Returns a list, as G-d intended.
 | 
			
		||||
    """
 | 
			
		||||
    qs, _coerce_result = _coerce_args(qs)
 | 
			
		||||
 | 
			
		||||
    # If max_num_fields is defined then check that the number of fields is less
 | 
			
		||||
    # than max_num_fields. This prevents a memory exhaustion DOS attack via
 | 
			
		||||
    # post bodies with many fields.
 | 
			
		||||
    if max_num_fields is not None:
 | 
			
		||||
        num_fields = 1 + qs.count('&') + qs.count(';')
 | 
			
		||||
        if max_num_fields < num_fields:
 | 
			
		||||
            raise ValueError('Max number of fields exceeded')
 | 
			
		||||
 | 
			
		||||
    pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
 | 
			
		||||
    r = []
 | 
			
		||||
    for name_value in pairs:
 | 
			
		||||
        if not name_value and not strict_parsing:
 | 
			
		||||
            continue
 | 
			
		||||
        nv = name_value.split('=', 1)
 | 
			
		||||
        if len(nv) != 2:
 | 
			
		||||
            if strict_parsing:
 | 
			
		||||
                raise ValueError("bad query field: %r" % (name_value,))
 | 
			
		||||
            # Handle case of a control-name with no equal sign.
 | 
			
		||||
            if keep_blank_values:
 | 
			
		||||
                nv.append('')
 | 
			
		||||
            else:
 | 
			
		||||
                continue
 | 
			
		||||
        if len(nv[1]) or keep_blank_values:
 | 
			
		||||
            name = nv[0].replace('+', ' ')
 | 
			
		||||
            name = unquote(name, encoding=encoding, errors=errors)
 | 
			
		||||
            name = _coerce_result(name)
 | 
			
		||||
            value = nv[1].replace('+', ' ')
 | 
			
		||||
            value = unquote(value, encoding=encoding, errors=errors)
 | 
			
		||||
            value = _coerce_result(value)
 | 
			
		||||
            r.append((name, value))
 | 
			
		||||
    return r
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def escape_leading_slashes(url):
 | 
			
		||||
    """
 | 
			
		||||
    If redirecting to an absolute path (two leading slashes), a slash must be
 | 
			
		||||
 
 | 
			
		||||
@@ -72,10 +72,9 @@ def module_has_submodule(package, module_name):
 | 
			
		||||
    full_module_name = package_name + '.' + module_name
 | 
			
		||||
    try:
 | 
			
		||||
        return importlib_find(full_module_name, package_path) is not None
 | 
			
		||||
    except (ModuleNotFoundError, AttributeError):
 | 
			
		||||
    except ModuleNotFoundError:
 | 
			
		||||
        # When module_name is an invalid dotted path, Python raises
 | 
			
		||||
        # ModuleNotFoundError. AttributeError is raised on PY36 (fixed in PY37)
 | 
			
		||||
        # if the penultimate part of the path is not a package.
 | 
			
		||||
        # ModuleNotFoundError.
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,6 @@ from distutils.version import LooseVersion
 | 
			
		||||
# or later". So that third-party apps can use these values, each constant
 | 
			
		||||
# should remain as long as the oldest supported Django version supports that
 | 
			
		||||
# Python version.
 | 
			
		||||
PY36 = sys.version_info >= (3, 6)
 | 
			
		||||
PY37 = sys.version_info >= (3, 7)
 | 
			
		||||
PY38 = sys.version_info >= (3, 8)
 | 
			
		||||
PY39 = sys.version_info >= (3, 9)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -89,14 +89,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
 | 
			
		||||
suite doesn't bundle a settings file for database backends other than SQLite,
 | 
			
		||||
however, you must :ref:`create and provide your own test settings
 | 
			
		||||
<running-unit-tests-settings>`. For example, to run the tests on Python 3.7
 | 
			
		||||
<running-unit-tests-settings>`. For example, to run the tests on Python 3.9
 | 
			
		||||
using PostgreSQL:
 | 
			
		||||
 | 
			
		||||
.. console::
 | 
			
		||||
 | 
			
		||||
    $ tox -e py37-postgres -- --settings=my_postgres_settings
 | 
			
		||||
    $ tox -e py39-postgres -- --settings=my_postgres_settings
 | 
			
		||||
 | 
			
		||||
This command sets up a Python 3.7 virtual environment, installs Django's
 | 
			
		||||
This command sets up a Python 3.9 virtual environment, installs Django's
 | 
			
		||||
test suite dependencies (including those for PostgreSQL), and calls
 | 
			
		||||
``runtests.py`` with the supplied arguments (in this case,
 | 
			
		||||
``--settings=my_postgres_settings``).
 | 
			
		||||
@@ -110,14 +110,14 @@ set. For example, the following is equivalent to the command above:
 | 
			
		||||
 | 
			
		||||
.. code-block:: console
 | 
			
		||||
 | 
			
		||||
    $ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py35-postgres
 | 
			
		||||
    $ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py39-postgres
 | 
			
		||||
 | 
			
		||||
Windows users should use:
 | 
			
		||||
 | 
			
		||||
.. code-block:: doscon
 | 
			
		||||
 | 
			
		||||
    ...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
 | 
			
		||||
    ...\> tox -e py35-postgres
 | 
			
		||||
    ...\> tox -e py39-postgres
 | 
			
		||||
 | 
			
		||||
Running the JavaScript tests
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 
 | 
			
		||||
@@ -212,16 +212,15 @@ this. For a small app like polls, this process isn't too difficult.
 | 
			
		||||
            Programming Language :: Python
 | 
			
		||||
            Programming Language :: Python :: 3
 | 
			
		||||
            Programming Language :: Python :: 3 :: Only
 | 
			
		||||
            Programming Language :: Python :: 3.6
 | 
			
		||||
            Programming Language :: Python :: 3.7
 | 
			
		||||
            Programming Language :: Python :: 3.8
 | 
			
		||||
            Programming Language :: Python :: 3.9
 | 
			
		||||
            Topic :: Internet :: WWW/HTTP
 | 
			
		||||
            Topic :: Internet :: WWW/HTTP :: Dynamic Content
 | 
			
		||||
 | 
			
		||||
        [options]
 | 
			
		||||
        include_package_data = true
 | 
			
		||||
        packages = find:
 | 
			
		||||
        python_requires = >=3.6
 | 
			
		||||
        python_requires = >=3.8
 | 
			
		||||
        install_requires =
 | 
			
		||||
            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
 | 
			
		||||
isn't, you'll get an error telling "No module named django".
 | 
			
		||||
 | 
			
		||||
This tutorial is written for Django |version|, which supports Python 3.6 and
 | 
			
		||||
This tutorial is written for Django |version|, which supports Python 3.8 and
 | 
			
		||||
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
 | 
			
		||||
of this page, or update Django to the newest version. If you're using an older
 | 
			
		||||
 
 | 
			
		||||
@@ -1507,10 +1507,6 @@ May be specified multiple times and combined with :option:`test --tag`.
 | 
			
		||||
Runs test methods and classes matching test name patterns, in the same way as
 | 
			
		||||
:option:`unittest's -k option<unittest.-k>`. Can be specified multiple times.
 | 
			
		||||
 | 
			
		||||
.. admonition:: Python 3.7 and later
 | 
			
		||||
 | 
			
		||||
    This feature is only available for Python 3.7 and later.
 | 
			
		||||
 | 
			
		||||
.. django-admin-option:: --pdb
 | 
			
		||||
 | 
			
		||||
Spawns a ``pdb`` debugger at each test error or failure. If you have it
 | 
			
		||||
 
 | 
			
		||||
@@ -493,9 +493,7 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004
 | 
			
		||||
    expensive ``get_friends()`` method and wanted to allow calling it without
 | 
			
		||||
    retrieving the cached value, you could write::
 | 
			
		||||
 | 
			
		||||
        friends = cached_property(get_friends, name='friends')
 | 
			
		||||
 | 
			
		||||
    You only need the ``name`` argument for Python < 3.6 support.
 | 
			
		||||
        friends = cached_property(get_friends)
 | 
			
		||||
 | 
			
		||||
    While ``person.get_friends()`` will recompute the friends on each call, the
 | 
			
		||||
    value of the cached property will persist until you delete it as described
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,8 @@ Python compatibility
 | 
			
		||||
Django 4.0 supports Python 3.8, 3.9, and 3.10. We **highly recommend** and only
 | 
			
		||||
officially support the latest release of each series.
 | 
			
		||||
 | 
			
		||||
The Django 3.2.x series is the last to support Python 3.6 and 3.7.
 | 
			
		||||
 | 
			
		||||
.. _whats-new-4.0:
 | 
			
		||||
 | 
			
		||||
What's new in Django 4.0
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,6 @@ classifiers =
 | 
			
		||||
    Programming Language :: Python
 | 
			
		||||
    Programming Language :: Python :: 3
 | 
			
		||||
    Programming Language :: Python :: 3 :: Only
 | 
			
		||||
    Programming Language :: Python :: 3.6
 | 
			
		||||
    Programming Language :: Python :: 3.7
 | 
			
		||||
    Programming Language :: Python :: 3.8
 | 
			
		||||
    Programming Language :: Python :: 3.9
 | 
			
		||||
    Topic :: Internet :: WWW/HTTP
 | 
			
		||||
@@ -34,7 +32,7 @@ project_urls =
 | 
			
		||||
    Tracker = https://code.djangoproject.com/
 | 
			
		||||
 | 
			
		||||
[options]
 | 
			
		||||
python_requires = >=3.6
 | 
			
		||||
python_requires = >=3.8
 | 
			
		||||
packages = find:
 | 
			
		||||
include_package_data = true
 | 
			
		||||
zip_safe = false
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ from distutils.sysconfig import get_python_lib
 | 
			
		||||
from setuptools import setup
 | 
			
		||||
 | 
			
		||||
CURRENT_PYTHON = sys.version_info[:2]
 | 
			
		||||
REQUIRED_PYTHON = (3, 6)
 | 
			
		||||
REQUIRED_PYTHON = (3, 8)
 | 
			
		||||
 | 
			
		||||
# This check and everything above must remain compatible with Python 2.7.
 | 
			
		||||
if CURRENT_PYTHON < REQUIRED_PYTHON:
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ class SqliteDbshellCommandTestCase(SimpleTestCase):
 | 
			
		||||
    def test_path_name(self):
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            self.settings_to_cmd_args_env({'NAME': Path('test.db.sqlite3')}),
 | 
			
		||||
            (['sqlite3', 'test.db.sqlite3'], None),
 | 
			
		||||
            (['sqlite3', Path('test.db.sqlite3')], None),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_parameters(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ from django.db import close_old_connections, connection
 | 
			
		||||
from django.test import (
 | 
			
		||||
    RequestFactory, SimpleTestCase, TransactionTestCase, override_settings,
 | 
			
		||||
)
 | 
			
		||||
from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HandlerTests(SimpleTestCase):
 | 
			
		||||
@@ -183,7 +182,7 @@ class HandlerRequestTests(SimpleTestCase):
 | 
			
		||||
    def test_invalid_urls(self):
 | 
			
		||||
        response = self.client.get('~%A9helloworld')
 | 
			
		||||
        self.assertEqual(response.status_code, 404)
 | 
			
		||||
        self.assertEqual(response.context['request_path'], '/~%25A9helloworld' if PY37 else '/%7E%25A9helloworld')
 | 
			
		||||
        self.assertEqual(response.context['request_path'], '/~%25A9helloworld')
 | 
			
		||||
 | 
			
		||||
        response = self.client.get('d%aao%aaw%aan%aal%aao%aaa%aad%aa/')
 | 
			
		||||
        self.assertEqual(response.context['request_path'], '/d%25AAo%25AAw%25AAn%25AAl%25AAo%25AAa%25AAd%25AA')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,7 @@
 | 
			
		||||
from unittest import skipUnless
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.template import Context, Template
 | 
			
		||||
from django.test import SimpleTestCase, TestCase, override_settings
 | 
			
		||||
from django.test.utils import isolate_apps
 | 
			
		||||
from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
from .models import (
 | 
			
		||||
    AbstractBase1, AbstractBase2, AbstractBase3, Child1, Child2, Child3,
 | 
			
		||||
@@ -287,6 +284,5 @@ class TestManagerInheritance(SimpleTestCase):
 | 
			
		||||
        self.assertEqual(TestModel._meta.managers, (TestModel.custom_manager,))
 | 
			
		||||
        self.assertEqual(TestModel._meta.managers_map, {'custom_manager': TestModel.custom_manager})
 | 
			
		||||
 | 
			
		||||
    @skipUnless(PY37, '__class_getitem__() was added in Python 3.7')
 | 
			
		||||
    def test_manager_class_getitem(self):
 | 
			
		||||
        self.assertIs(models.Manager[Child1], models.Manager)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,9 @@
 | 
			
		||||
from operator import attrgetter
 | 
			
		||||
from unittest import skipUnless
 | 
			
		||||
 | 
			
		||||
from django.core.exceptions import FieldError, ValidationError
 | 
			
		||||
from django.db import connection, models
 | 
			
		||||
from django.test import SimpleTestCase, TestCase
 | 
			
		||||
from django.test.utils import CaptureQueriesContext, isolate_apps
 | 
			
		||||
from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
from .models import (
 | 
			
		||||
    Base, Chef, CommonInfo, GrandChild, GrandParent, ItalianRestaurant,
 | 
			
		||||
@@ -219,7 +217,6 @@ class ModelInheritanceTests(TestCase):
 | 
			
		||||
        self.assertSequenceEqual(qs, [p2, p1])
 | 
			
		||||
        self.assertIn(expected_order_by_sql, str(qs.query))
 | 
			
		||||
 | 
			
		||||
    @skipUnless(PY37, '__class_getitem__() was added in Python 3.7')
 | 
			
		||||
    def test_queryset_class_getitem(self):
 | 
			
		||||
        self.assertIs(models.QuerySet[Post], models.QuerySet)
 | 
			
		||||
        self.assertIs(models.QuerySet[Post, Post], models.QuerySet)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,6 @@ else:
 | 
			
		||||
        RemovedInDjango41Warning, RemovedInDjango50Warning,
 | 
			
		||||
    )
 | 
			
		||||
    from django.utils.log import DEFAULT_LOGGING
 | 
			
		||||
    from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import MySQLdb
 | 
			
		||||
@@ -521,7 +520,6 @@ if __name__ == "__main__":
 | 
			
		||||
        '--timing', action='store_true',
 | 
			
		||||
        help='Output timings, including database set up and total run time.',
 | 
			
		||||
    )
 | 
			
		||||
    if PY37:
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-k', dest='test_name_patterns', action='append',
 | 
			
		||||
        help=(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,7 @@
 | 
			
		||||
import os
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
from unittest import (
 | 
			
		||||
    TestSuite, TextTestRunner, defaultTestLoader, mock, skipUnless,
 | 
			
		||||
)
 | 
			
		||||
from unittest import TestSuite, TextTestRunner, defaultTestLoader, mock
 | 
			
		||||
 | 
			
		||||
from django.db import connections
 | 
			
		||||
from django.test import SimpleTestCase
 | 
			
		||||
@@ -11,7 +9,6 @@ from django.test.runner import DiscoverRunner
 | 
			
		||||
from django.test.utils import (
 | 
			
		||||
    NullTimeKeeper, TimeKeeper, captured_stderr, captured_stdout,
 | 
			
		||||
)
 | 
			
		||||
from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@contextmanager
 | 
			
		||||
@@ -83,7 +80,6 @@ class DiscoverRunnerTests(SimpleTestCase):
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(count, 1)
 | 
			
		||||
 | 
			
		||||
    @skipUnless(PY37, 'unittest -k option requires Python 3.7 and later')
 | 
			
		||||
    def test_name_patterns(self):
 | 
			
		||||
        all_test_1 = [
 | 
			
		||||
            'DjangoCase1.test_1', 'DjangoCase2.test_1',
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import unittest
 | 
			
		||||
 | 
			
		||||
from django.test import SimpleTestCase
 | 
			
		||||
from django.test.runner import RemoteTestResult
 | 
			
		||||
from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import tblib
 | 
			
		||||
@@ -80,8 +79,7 @@ class RemoteTestResultTest(SimpleTestCase):
 | 
			
		||||
        event = events[1]
 | 
			
		||||
        self.assertEqual(event[0], 'addSubTest')
 | 
			
		||||
        self.assertEqual(str(event[2]), 'dummy_test (test_runner.test_parallel.SampleFailingSubtest) (index=0)')
 | 
			
		||||
        trailing_comma = '' if PY37 else ','
 | 
			
		||||
        self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1'%s)" % trailing_comma)
 | 
			
		||||
        self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1')")
 | 
			
		||||
 | 
			
		||||
        event = events[2]
 | 
			
		||||
        self.assertEqual(repr(event[3][1]), "AssertionError('2 != 1'%s)" % trailing_comma)
 | 
			
		||||
        self.assertEqual(repr(event[3][1]), "AssertionError('2 != 1')")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,9 @@
 | 
			
		||||
from django.core.management.base import BaseCommand
 | 
			
		||||
from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Command(BaseCommand):
 | 
			
		||||
    def add_arguments(self, parser):
 | 
			
		||||
        kwargs = {'required': True} if PY37 else {}
 | 
			
		||||
        subparsers = parser.add_subparsers(dest='subcommand', **kwargs)
 | 
			
		||||
        subparsers = parser.add_subparsers(dest='subcommand', required=True)
 | 
			
		||||
        parser_foo = subparsers.add_parser('foo')
 | 
			
		||||
        parser_foo.add_argument('--bar')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ from django.test import SimpleTestCase, override_settings
 | 
			
		||||
from django.test.utils import captured_stderr, extend_sys_path, ignore_warnings
 | 
			
		||||
from django.utils import translation
 | 
			
		||||
from django.utils.deprecation import RemovedInDjango41Warning
 | 
			
		||||
from django.utils.version import PY37
 | 
			
		||||
 | 
			
		||||
from .management.commands import dance
 | 
			
		||||
 | 
			
		||||
@@ -337,20 +336,9 @@ class CommandTests(SimpleTestCase):
 | 
			
		||||
        msg = "Error: invalid choice: 'test' (choose from 'foo')"
 | 
			
		||||
        with self.assertRaisesMessage(CommandError, msg):
 | 
			
		||||
            management.call_command('subparser', 'test', 12)
 | 
			
		||||
        if PY37:
 | 
			
		||||
            # "required" option requires Python 3.7 and later.
 | 
			
		||||
        msg = 'Error: the following arguments are required: subcommand'
 | 
			
		||||
        with self.assertRaisesMessage(CommandError, msg):
 | 
			
		||||
            management.call_command('subparser_dest', subcommand='foo', bar=12)
 | 
			
		||||
        else:
 | 
			
		||||
            msg = (
 | 
			
		||||
                'Unknown option(s) for subparser_dest command: subcommand. '
 | 
			
		||||
                'Valid options are: bar, force_color, help, no_color, '
 | 
			
		||||
                'pythonpath, settings, skip_checks, stderr, stdout, '
 | 
			
		||||
                'traceback, verbosity, version.'
 | 
			
		||||
            )
 | 
			
		||||
            with self.assertRaisesMessage(TypeError, msg):
 | 
			
		||||
                management.call_command('subparser_dest', subcommand='foo', bar=12)
 | 
			
		||||
 | 
			
		||||
    def test_create_parser_kwargs(self):
 | 
			
		||||
        """BaseCommand.create_parser() passes kwargs to CommandParser."""
 | 
			
		||||
 
 | 
			
		||||
@@ -195,10 +195,10 @@ class TestChildArguments(SimpleTestCase):
 | 
			
		||||
        with tempfile.TemporaryDirectory() as tmpdir:
 | 
			
		||||
            exe_path = Path(tmpdir) / 'django-admin.exe'
 | 
			
		||||
            exe_path.touch()
 | 
			
		||||
            with mock.patch('sys.argv', [str(exe_path.with_suffix('')), 'runserver']):
 | 
			
		||||
            with mock.patch('sys.argv', [exe_path.with_suffix(''), 'runserver']):
 | 
			
		||||
                self.assertEqual(
 | 
			
		||||
                    autoreload.get_child_arguments(),
 | 
			
		||||
                    [str(exe_path), 'runserver']
 | 
			
		||||
                    [exe_path, 'runserver']
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    @mock.patch('sys.warnoptions', [])
 | 
			
		||||
@@ -206,10 +206,10 @@ class TestChildArguments(SimpleTestCase):
 | 
			
		||||
        with tempfile.TemporaryDirectory() as tmpdir:
 | 
			
		||||
            script_path = Path(tmpdir) / 'django-admin-script.py'
 | 
			
		||||
            script_path.touch()
 | 
			
		||||
            with mock.patch('sys.argv', [str(script_path.with_name('django-admin')), 'runserver']):
 | 
			
		||||
            with mock.patch('sys.argv', [script_path.with_name('django-admin'), 'runserver']):
 | 
			
		||||
                self.assertEqual(
 | 
			
		||||
                    autoreload.get_child_arguments(),
 | 
			
		||||
                    [sys.executable, str(script_path), 'runserver']
 | 
			
		||||
                    [sys.executable, script_path, 'runserver']
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    @mock.patch('sys.argv', ['does-not-exist', 'runserver'])
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ from django.test import SimpleTestCase
 | 
			
		||||
from django.utils.datastructures import MultiValueDict
 | 
			
		||||
from django.utils.http import (
 | 
			
		||||
    base36_to_int, escape_leading_slashes, http_date, int_to_base36,
 | 
			
		||||
    is_same_domain, parse_etags, parse_http_date, parse_qsl, quote_etag,
 | 
			
		||||
    is_same_domain, parse_etags, parse_http_date, quote_etag,
 | 
			
		||||
    url_has_allowed_host_and_scheme, urlencode, urlsafe_base64_decode,
 | 
			
		||||
    urlsafe_base64_encode,
 | 
			
		||||
)
 | 
			
		||||
@@ -331,68 +331,3 @@ class EscapeLeadingSlashesTests(unittest.TestCase):
 | 
			
		||||
        for url, expected in tests:
 | 
			
		||||
            with self.subTest(url=url):
 | 
			
		||||
                self.assertEqual(escape_leading_slashes(url), expected)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: Remove when dropping support for PY37. Backport of unit tests for
 | 
			
		||||
# urllib.parse.parse_qsl() from Python 3.8. Copyright (C) 2020 Python Software
 | 
			
		||||
# Foundation (see LICENSE.python).
 | 
			
		||||
class ParseQSLBackportTests(unittest.TestCase):
 | 
			
		||||
    def test_parse_qsl(self):
 | 
			
		||||
        tests = [
 | 
			
		||||
            ('', []),
 | 
			
		||||
            ('&', []),
 | 
			
		||||
            ('&&', []),
 | 
			
		||||
            ('=', [('', '')]),
 | 
			
		||||
            ('=a', [('', 'a')]),
 | 
			
		||||
            ('a', [('a', '')]),
 | 
			
		||||
            ('a=', [('a', '')]),
 | 
			
		||||
            ('&a=b', [('a', 'b')]),
 | 
			
		||||
            ('a=a+b&b=b+c', [('a', 'a b'), ('b', 'b c')]),
 | 
			
		||||
            ('a=1&a=2', [('a', '1'), ('a', '2')]),
 | 
			
		||||
            (b'', []),
 | 
			
		||||
            (b'&', []),
 | 
			
		||||
            (b'&&', []),
 | 
			
		||||
            (b'=', [(b'', b'')]),
 | 
			
		||||
            (b'=a', [(b'', b'a')]),
 | 
			
		||||
            (b'a', [(b'a', b'')]),
 | 
			
		||||
            (b'a=', [(b'a', b'')]),
 | 
			
		||||
            (b'&a=b', [(b'a', b'b')]),
 | 
			
		||||
            (b'a=a+b&b=b+c', [(b'a', b'a b'), (b'b', b'b c')]),
 | 
			
		||||
            (b'a=1&a=2', [(b'a', b'1'), (b'a', b'2')]),
 | 
			
		||||
            (';', []),
 | 
			
		||||
            (';;', []),
 | 
			
		||||
            (';a=b', [('a', 'b')]),
 | 
			
		||||
            ('a=a+b;b=b+c', [('a', 'a b'), ('b', 'b c')]),
 | 
			
		||||
            ('a=1;a=2', [('a', '1'), ('a', '2')]),
 | 
			
		||||
            (b';', []),
 | 
			
		||||
            (b';;', []),
 | 
			
		||||
            (b';a=b', [(b'a', b'b')]),
 | 
			
		||||
            (b'a=a+b;b=b+c', [(b'a', b'a b'), (b'b', b'b c')]),
 | 
			
		||||
            (b'a=1;a=2', [(b'a', b'1'), (b'a', b'2')]),
 | 
			
		||||
        ]
 | 
			
		||||
        for original, expected in tests:
 | 
			
		||||
            with self.subTest(original):
 | 
			
		||||
                result = parse_qsl(original, keep_blank_values=True)
 | 
			
		||||
                self.assertEqual(result, expected, 'Error parsing %r' % original)
 | 
			
		||||
                expect_without_blanks = [v for v in expected if len(v[1])]
 | 
			
		||||
                result = parse_qsl(original, keep_blank_values=False)
 | 
			
		||||
                self.assertEqual(result, expect_without_blanks, 'Error parsing %r' % original)
 | 
			
		||||
 | 
			
		||||
    def test_parse_qsl_encoding(self):
 | 
			
		||||
        result = parse_qsl('key=\u0141%E9', encoding='latin-1')
 | 
			
		||||
        self.assertEqual(result, [('key', '\u0141\xE9')])
 | 
			
		||||
        result = parse_qsl('key=\u0141%C3%A9', encoding='utf-8')
 | 
			
		||||
        self.assertEqual(result, [('key', '\u0141\xE9')])
 | 
			
		||||
        result = parse_qsl('key=\u0141%C3%A9', encoding='ascii')
 | 
			
		||||
        self.assertEqual(result, [('key', '\u0141\ufffd\ufffd')])
 | 
			
		||||
        result = parse_qsl('key=\u0141%E9-', encoding='ascii')
 | 
			
		||||
        self.assertEqual(result, [('key', '\u0141\ufffd-')])
 | 
			
		||||
        result = parse_qsl('key=\u0141%E9-', encoding='ascii', errors='ignore')
 | 
			
		||||
        self.assertEqual(result, [('key', '\u0141-')])
 | 
			
		||||
 | 
			
		||||
    def test_parse_qsl_max_num_fields(self):
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            parse_qsl('&'.join(['a=a'] * 11), max_num_fields=10)
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            parse_qsl(';'.join(['a=a'] * 11), max_num_fields=10)
 | 
			
		||||
        parse_qsl('&'.join(['a=a'] * 10), max_num_fields=10)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							@@ -23,7 +23,7 @@ passenv = DJANGO_SETTINGS_MODULE PYTHONPATH HOME DISPLAY OBJC_DISABLE_INITIALIZE
 | 
			
		||||
setenv =
 | 
			
		||||
    PYTHONDONTWRITEBYTECODE=1
 | 
			
		||||
deps =
 | 
			
		||||
    py{3,36,37,38,39}: -rtests/requirements/py3.txt
 | 
			
		||||
    py{3,38,39}: -rtests/requirements/py3.txt
 | 
			
		||||
    postgres: -rtests/requirements/postgres.txt
 | 
			
		||||
    mysql: -rtests/requirements/mysql.txt
 | 
			
		||||
    oracle: -rtests/requirements/oracle.txt
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user