Fixed #27857 -- Dropped support for Python 3.4.
This commit is contained in:
parent
a80903b711
commit
cfff2af02b
2
INSTALL
2
INSTALL
|
@ -1,6 +1,6 @@
|
|||
Thanks for downloading Django.
|
||||
|
||||
To install it, make sure you have Python 3.4 or greater installed. Then run
|
||||
To install it, make sure you have Python 3.5 or greater installed. Then run
|
||||
this command from the command prompt:
|
||||
|
||||
python setup.py install
|
||||
|
|
|
@ -150,12 +150,6 @@ class BaseExpression:
|
|||
if output_field is not None:
|
||||
self.output_field = output_field
|
||||
|
||||
def __getstate__(self):
|
||||
# This method required only for Python 3.4.
|
||||
state = self.__dict__.copy()
|
||||
state.pop('convert_value', None)
|
||||
return state
|
||||
|
||||
def get_db_converters(self, connection):
|
||||
return (
|
||||
[]
|
||||
|
|
|
@ -1,20 +1,7 @@
|
|||
import sys
|
||||
from http import cookies
|
||||
|
||||
# Cookie pickling bug is fixed in Python 3.4.3+
|
||||
# http://bugs.python.org/issue22775
|
||||
if sys.version_info >= (3, 4, 3):
|
||||
SimpleCookie = cookies.SimpleCookie
|
||||
else:
|
||||
Morsel = cookies.Morsel
|
||||
|
||||
class SimpleCookie(cookies.SimpleCookie):
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(value, Morsel):
|
||||
# allow assignment of constructed Morsels (e.g. for pickling)
|
||||
dict.__setitem__(self, key, value)
|
||||
else:
|
||||
super().__setitem__(key, value)
|
||||
# For backwards compatibility in Django 2.1.
|
||||
SimpleCookie = cookies.SimpleCookie
|
||||
|
||||
|
||||
def parse_cookie(cookie):
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
"""Compare two HTML documents."""
|
||||
|
||||
import re
|
||||
|
||||
from django.utils.html_parser import HTMLParseError, HTMLParser
|
||||
from html.parser import HTMLParser
|
||||
|
||||
WHITESPACE = re.compile(r'\s+')
|
||||
|
||||
|
@ -138,6 +137,10 @@ class RootElement(Element):
|
|||
return ''.join(str(c) for c in self.children)
|
||||
|
||||
|
||||
class HTMLParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Parser(HTMLParser):
|
||||
SELF_CLOSING_TAGS = (
|
||||
'br', 'hr', 'input', 'img', 'meta', 'spacer', 'link', 'frame', 'base',
|
||||
|
@ -145,7 +148,7 @@ class Parser(HTMLParser):
|
|||
)
|
||||
|
||||
def __init__(self):
|
||||
HTMLParser.__init__(self)
|
||||
HTMLParser.__init__(self, convert_charrefs=False)
|
||||
self.root = RootElement()
|
||||
self.open_tags = []
|
||||
self.element_positions = {}
|
||||
|
|
|
@ -60,10 +60,7 @@ def register_converter(converter, type_name):
|
|||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def get_converters():
|
||||
converters = {}
|
||||
converters.update(DEFAULT_CONVERTERS)
|
||||
converters.update(REGISTERED_CONVERTERS)
|
||||
return converters
|
||||
return {**DEFAULT_CONVERTERS, **REGISTERED_CONVERTERS}
|
||||
|
||||
|
||||
def get_converter(raw_converter):
|
||||
|
|
|
@ -343,9 +343,7 @@ class URLPattern:
|
|||
'path.to.ClassBasedView').
|
||||
"""
|
||||
callback = self.callback
|
||||
# Python 3.5 collapses nested partials, so can change "while" to "if"
|
||||
# when it's the minimum supported version.
|
||||
while isinstance(callback, functools.partial):
|
||||
if isinstance(callback, functools.partial):
|
||||
callback = callback.func
|
||||
if not hasattr(callback, '__name__'):
|
||||
return callback.__module__ + "." + callback.__class__.__name__
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""HTML utilities suitable for global use."""
|
||||
|
||||
import re
|
||||
from html.parser import HTMLParser
|
||||
from urllib.parse import (
|
||||
parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit,
|
||||
)
|
||||
|
@ -11,8 +12,6 @@ from django.utils.http import RFC3986_GENDELIMS, RFC3986_SUBDELIMS
|
|||
from django.utils.safestring import SafeData, SafeText, mark_safe
|
||||
from django.utils.text import normalize_newlines
|
||||
|
||||
from .html_parser import HTMLParseError, HTMLParser
|
||||
|
||||
# Configuration for urlize() function.
|
||||
TRAILING_PUNCTUATION_RE = re.compile(
|
||||
'^' # Beginning of word
|
||||
|
@ -132,7 +131,7 @@ def linebreaks(value, autoescape=False):
|
|||
|
||||
class MLStripper(HTMLParser):
|
||||
def __init__(self):
|
||||
HTMLParser.__init__(self)
|
||||
HTMLParser.__init__(self, convert_charrefs=False)
|
||||
self.reset()
|
||||
self.fed = []
|
||||
|
||||
|
@ -154,16 +153,9 @@ def _strip_once(value):
|
|||
Internal tag stripping utility used by strip_tags.
|
||||
"""
|
||||
s = MLStripper()
|
||||
try:
|
||||
s.feed(value)
|
||||
except HTMLParseError:
|
||||
return value
|
||||
try:
|
||||
s.close()
|
||||
except HTMLParseError:
|
||||
return s.get_data() + s.rawdata
|
||||
else:
|
||||
return s.get_data()
|
||||
s.feed(value)
|
||||
s.close()
|
||||
return s.get_data()
|
||||
|
||||
|
||||
@keep_lazy_text
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import html.parser
|
||||
|
||||
try:
|
||||
HTMLParseError = html.parser.HTMLParseError
|
||||
except AttributeError:
|
||||
# create a dummy class for Python 3.5+ where it's been removed
|
||||
class HTMLParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HTMLParser(html.parser.HTMLParser):
|
||||
"""Explicitly set convert_charrefs to be False.
|
||||
|
||||
This silences a deprecation warning on Python 3.4.
|
||||
"""
|
||||
def __init__(self, convert_charrefs=False, **kwargs):
|
||||
html.parser.HTMLParser.__init__(self, convert_charrefs=convert_charrefs, **kwargs)
|
|
@ -288,9 +288,9 @@ Once the tests complete, you should be greeted with a message informing you
|
|||
whether the test suite passed or failed. Since you haven't yet made any changes
|
||||
to Django's code, the entire test suite **should** pass. If you get failures or
|
||||
errors make sure you've followed all of the previous steps properly. See
|
||||
:ref:`running-unit-tests` for more information. If you're using Python 3.5+,
|
||||
there will be a couple failures related to deprecation warnings that you can
|
||||
ignore. These failures have since been fixed in Django.
|
||||
:ref:`running-unit-tests` for more information. There will be a couple failures
|
||||
related to deprecation warnings that you can ignore. These failures have since
|
||||
been fixed in Django.
|
||||
|
||||
Note that the latest Django trunk may not always be stable. When developing
|
||||
against trunk, you can check `Django's continuous integration builds`__ to
|
||||
|
|
|
@ -29,7 +29,7 @@ your operating system's package manager.
|
|||
You can verify that Python is installed by typing ``python`` from your shell;
|
||||
you should see something like::
|
||||
|
||||
Python 3.4.x
|
||||
Python 3.x.y
|
||||
[GCC 4.x] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>>
|
||||
|
|
|
@ -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| and Python 3.4 or later. If the
|
||||
This tutorial is written for Django |version| and Python 3.5 or 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 are still using Python
|
||||
|
|
|
@ -192,7 +192,7 @@ Configurable attributes
|
|||
.. attribute:: AppConfig.path
|
||||
|
||||
Filesystem path to the application directory, e.g.
|
||||
``'/usr/lib/python3.4/dist-packages/django/contrib/admin'``.
|
||||
``'/usr/lib/pythonX.Y/dist-packages/django/contrib/admin'``.
|
||||
|
||||
In most cases, Django can automatically detect and set this, but you can
|
||||
also provide an explicit override as a class attribute on your
|
||||
|
|
1
setup.py
1
setup.py
|
@ -62,7 +62,6 @@ setup(
|
|||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import unittest
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.handlers.wsgi import WSGIHandler, WSGIRequest, get_script_name
|
||||
from django.core.signals import request_finished, request_started
|
||||
|
@ -8,11 +6,6 @@ from django.test import (
|
|||
RequestFactory, SimpleTestCase, TransactionTestCase, override_settings,
|
||||
)
|
||||
|
||||
try:
|
||||
from http import HTTPStatus
|
||||
except ImportError: # Python < 3.5
|
||||
HTTPStatus = None
|
||||
|
||||
|
||||
class HandlerTests(SimpleTestCase):
|
||||
|
||||
|
@ -182,7 +175,6 @@ class HandlerRequestTests(SimpleTestCase):
|
|||
environ = RequestFactory().get('/%E2%A8%87%87%A5%E2%A8%A0').environ
|
||||
self.assertIsInstance(environ['PATH_INFO'], str)
|
||||
|
||||
@unittest.skipIf(HTTPStatus is None, 'HTTPStatus only exists on Python 3.5+')
|
||||
def test_handle_accepts_httpstatus_enum_value(self):
|
||||
def start_response(status, headers):
|
||||
start_response.status = status
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
from http import HTTPStatus
|
||||
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.db import connection, transaction
|
||||
from django.http import HttpResponse, StreamingHttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
try:
|
||||
from http import HTTPStatus
|
||||
except ImportError: # Python < 3.5
|
||||
pass
|
||||
|
||||
|
||||
def regular(request):
|
||||
return HttpResponse(b"regular content")
|
||||
|
|
|
@ -61,10 +61,7 @@ class MailTests(HeadersCheckMixin, SimpleTestCase):
|
|||
|
||||
def iter_attachments():
|
||||
for i in email_message.walk():
|
||||
# Once support for Python<3.5 has been dropped, we can use
|
||||
# i.get_content_disposition() here instead.
|
||||
content_disposition = i.get('content-disposition', '').split(';')[0].lower()
|
||||
if content_disposition == 'attachment':
|
||||
if i.get_content_disposition() == 'attachment':
|
||||
filename = i.get_filename()
|
||||
content = i.get_payload(decode=True)
|
||||
mimetype = i.get_content_type()
|
||||
|
@ -1161,8 +1158,8 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
|
|||
def __init__(self, *args, **kwargs):
|
||||
threading.Thread.__init__(self)
|
||||
# New kwarg added in Python 3.5; default switching to False in 3.6.
|
||||
if sys.version_info >= (3, 5):
|
||||
kwargs['decode_data'] = True
|
||||
# Setting a value only silences a deprecation warning in Python 3.5.
|
||||
kwargs['decode_data'] = True
|
||||
smtpd.SMTPServer.__init__(self, *args, **kwargs)
|
||||
self._sink = []
|
||||
self.active = False
|
||||
|
|
|
@ -5,7 +5,7 @@ import errno
|
|||
import os
|
||||
import socket
|
||||
import sys
|
||||
from http.client import HTTPConnection
|
||||
from http.client import HTTPConnection, RemoteDisconnected
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import urlopen
|
||||
|
@ -14,11 +14,6 @@ from django.test import LiveServerTestCase, override_settings
|
|||
|
||||
from .models import Person
|
||||
|
||||
try:
|
||||
from http.client import RemoteDisconnected
|
||||
except ImportError: # Python 3.4
|
||||
from http.client import BadStatusLine as RemoteDisconnected
|
||||
|
||||
TEST_ROOT = os.path.dirname(__file__)
|
||||
TEST_SETTINGS = {
|
||||
'MEDIA_URL': '/media/',
|
||||
|
|
|
@ -2,7 +2,6 @@ import base64
|
|||
import os
|
||||
import shutil
|
||||
import string
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import timedelta
|
||||
|
@ -733,10 +732,9 @@ class SessionMiddlewareTests(TestCase):
|
|||
# A deleted cookie header looks like:
|
||||
# Set-Cookie: sessionid=; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
|
||||
self.assertEqual(
|
||||
'Set-Cookie: {}={}; expires=Thu, 01-Jan-1970 00:00:00 GMT; '
|
||||
'Set-Cookie: {}=""; expires=Thu, 01-Jan-1970 00:00:00 GMT; '
|
||||
'Max-Age=0; Path=/'.format(
|
||||
settings.SESSION_COOKIE_NAME,
|
||||
'""' if sys.version_info >= (3, 5) else '',
|
||||
),
|
||||
str(response.cookies[settings.SESSION_COOKIE_NAME])
|
||||
)
|
||||
|
@ -763,10 +761,9 @@ class SessionMiddlewareTests(TestCase):
|
|||
# expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0;
|
||||
# Path=/example/
|
||||
self.assertEqual(
|
||||
'Set-Cookie: {}={}; Domain=.example.local; expires=Thu, '
|
||||
'Set-Cookie: {}=""; Domain=.example.local; expires=Thu, '
|
||||
'01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/example/'.format(
|
||||
settings.SESSION_COOKIE_NAME,
|
||||
'""' if sys.version_info >= (3, 5) else '',
|
||||
),
|
||||
str(response.cookies[settings.SESSION_COOKIE_NAME])
|
||||
)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import sys
|
||||
import unittest
|
||||
from io import StringIO
|
||||
|
||||
|
@ -94,18 +93,13 @@ class TestDebugSQL(unittest.TestCase):
|
|||
]
|
||||
|
||||
verbose_expected_outputs = [
|
||||
# Output format changed in Python 3.5+
|
||||
x.format('' if sys.version_info < (3, 5) else 'TestDebugSQL.') for x in [
|
||||
'runTest (test_runner.test_debug_sql.{}FailingTest) ... FAIL',
|
||||
'runTest (test_runner.test_debug_sql.{}ErrorTest) ... ERROR',
|
||||
'runTest (test_runner.test_debug_sql.{}PassingTest) ... ok',
|
||||
'runTest (test_runner.test_debug_sql.{}PassingSubTest) ... ok',
|
||||
# If there are errors/failures in subtests but not in test itself,
|
||||
# the status is not written. That behavior comes from Python.
|
||||
'runTest (test_runner.test_debug_sql.{}FailingSubTest) ...',
|
||||
'runTest (test_runner.test_debug_sql.{}ErrorSubTest) ...',
|
||||
]
|
||||
] + [
|
||||
'runTest (test_runner.test_debug_sql.TestDebugSQL.FailingTest) ... FAIL',
|
||||
'runTest (test_runner.test_debug_sql.TestDebugSQL.ErrorTest) ... ERROR',
|
||||
'runTest (test_runner.test_debug_sql.TestDebugSQL.PassingTest) ... ok',
|
||||
# If there are errors/failures in subtests but not in test itself,
|
||||
# the status is not written. That behavior comes from Python.
|
||||
'runTest (test_runner.test_debug_sql.TestDebugSQL.FailingSubTest) ...',
|
||||
'runTest (test_runner.test_debug_sql.TestDebugSQL.ErrorSubTest) ...',
|
||||
('''SELECT COUNT(*) AS "__count" '''
|
||||
'''FROM "test_runner_person" WHERE '''
|
||||
'''"test_runner_person"."first_name" = 'pass';'''),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
@ -684,9 +683,6 @@ class HTMLEqualTests(SimpleTestCase):
|
|||
error_msg = (
|
||||
"First argument is not valid HTML:\n"
|
||||
"('Unexpected end tag `div` (Line 1, Column 6)', (1, 6))"
|
||||
) if sys.version_info >= (3, 5) else (
|
||||
"First argument is not valid HTML:\n"
|
||||
"Unexpected end tag `div` (Line 1, Column 6), at line 1, column 7"
|
||||
)
|
||||
with self.assertRaisesMessage(AssertionError, error_msg):
|
||||
self.assertHTMLEqual('< div></ div>', '<div></div>')
|
||||
|
|
Loading…
Reference in New Issue