Skip to content

Commit cfff2af

Browse files
committed
Fixed #27857 -- Dropped support for Python 3.4.
1 parent a80903b commit cfff2af

File tree

20 files changed

+37
-116
lines changed

20 files changed

+37
-116
lines changed

INSTALL

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Thanks for downloading Django.
22

3-
To install it, make sure you have Python 3.4 or greater installed. Then run
3+
To install it, make sure you have Python 3.5 or greater installed. Then run
44
this command from the command prompt:
55

66
python setup.py install

django/db/models/expressions.py

-6
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,6 @@ def __init__(self, output_field=None):
150150
if output_field is not None:
151151
self.output_field = output_field
152152

153-
def __getstate__(self):
154-
# This method required only for Python 3.4.
155-
state = self.__dict__.copy()
156-
state.pop('convert_value', None)
157-
return state
158-
159153
def get_db_converters(self, connection):
160154
return (
161155
[]

django/http/cookie.py

+2-15
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,7 @@
1-
import sys
21
from http import cookies
32

4-
# Cookie pickling bug is fixed in Python 3.4.3+
5-
# http://bugs.python.org/issue22775
6-
if sys.version_info >= (3, 4, 3):
7-
SimpleCookie = cookies.SimpleCookie
8-
else:
9-
Morsel = cookies.Morsel
10-
11-
class SimpleCookie(cookies.SimpleCookie):
12-
def __setitem__(self, key, value):
13-
if isinstance(value, Morsel):
14-
# allow assignment of constructed Morsels (e.g. for pickling)
15-
dict.__setitem__(self, key, value)
16-
else:
17-
super().__setitem__(key, value)
3+
# For backwards compatibility in Django 2.1.
4+
SimpleCookie = cookies.SimpleCookie
185

196

207
def parse_cookie(cookie):

django/test/html.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"""Compare two HTML documents."""
22

33
import re
4-
5-
from django.utils.html_parser import HTMLParseError, HTMLParser
4+
from html.parser import HTMLParser
65

76
WHITESPACE = re.compile(r'\s+')
87

@@ -138,14 +137,18 @@ def __str__(self):
138137
return ''.join(str(c) for c in self.children)
139138

140139

140+
class HTMLParseError(Exception):
141+
pass
142+
143+
141144
class Parser(HTMLParser):
142145
SELF_CLOSING_TAGS = (
143146
'br', 'hr', 'input', 'img', 'meta', 'spacer', 'link', 'frame', 'base',
144147
'col',
145148
)
146149

147150
def __init__(self):
148-
HTMLParser.__init__(self)
151+
HTMLParser.__init__(self, convert_charrefs=False)
149152
self.root = RootElement()
150153
self.open_tags = []
151154
self.element_positions = {}

django/urls/converters.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ def register_converter(converter, type_name):
6060

6161
@lru_cache.lru_cache(maxsize=None)
6262
def get_converters():
63-
converters = {}
64-
converters.update(DEFAULT_CONVERTERS)
65-
converters.update(REGISTERED_CONVERTERS)
66-
return converters
63+
return {**DEFAULT_CONVERTERS, **REGISTERED_CONVERTERS}
6764

6865

6966
def get_converter(raw_converter):

django/urls/resolvers.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,7 @@ def lookup_str(self):
343343
'path.to.ClassBasedView').
344344
"""
345345
callback = self.callback
346-
# Python 3.5 collapses nested partials, so can change "while" to "if"
347-
# when it's the minimum supported version.
348-
while isinstance(callback, functools.partial):
346+
if isinstance(callback, functools.partial):
349347
callback = callback.func
350348
if not hasattr(callback, '__name__'):
351349
return callback.__module__ + "." + callback.__class__.__name__

django/utils/html.py

+5-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""HTML utilities suitable for global use."""
22

33
import re
4+
from html.parser import HTMLParser
45
from urllib.parse import (
56
parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit,
67
)
@@ -11,8 +12,6 @@
1112
from django.utils.safestring import SafeData, SafeText, mark_safe
1213
from django.utils.text import normalize_newlines
1314

14-
from .html_parser import HTMLParseError, HTMLParser
15-
1615
# Configuration for urlize() function.
1716
TRAILING_PUNCTUATION_RE = re.compile(
1817
'^' # Beginning of word
@@ -132,7 +131,7 @@ def linebreaks(value, autoescape=False):
132131

133132
class MLStripper(HTMLParser):
134133
def __init__(self):
135-
HTMLParser.__init__(self)
134+
HTMLParser.__init__(self, convert_charrefs=False)
136135
self.reset()
137136
self.fed = []
138137

@@ -154,16 +153,9 @@ def _strip_once(value):
154153
Internal tag stripping utility used by strip_tags.
155154
"""
156155
s = MLStripper()
157-
try:
158-
s.feed(value)
159-
except HTMLParseError:
160-
return value
161-
try:
162-
s.close()
163-
except HTMLParseError:
164-
return s.get_data() + s.rawdata
165-
else:
166-
return s.get_data()
156+
s.feed(value)
157+
s.close()
158+
return s.get_data()
167159

168160

169161
@keep_lazy_text

django/utils/html_parser.py

-17
This file was deleted.

docs/intro/contributing.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,9 @@ Once the tests complete, you should be greeted with a message informing you
288288
whether the test suite passed or failed. Since you haven't yet made any changes
289289
to Django's code, the entire test suite **should** pass. If you get failures or
290290
errors make sure you've followed all of the previous steps properly. See
291-
:ref:`running-unit-tests` for more information. If you're using Python 3.5+,
292-
there will be a couple failures related to deprecation warnings that you can
293-
ignore. These failures have since been fixed in Django.
291+
:ref:`running-unit-tests` for more information. There will be a couple failures
292+
related to deprecation warnings that you can ignore. These failures have since
293+
been fixed in Django.
294294

295295
Note that the latest Django trunk may not always be stable. When developing
296296
against trunk, you can check `Django's continuous integration builds`__ to

docs/intro/install.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ your operating system's package manager.
2929
You can verify that Python is installed by typing ``python`` from your shell;
3030
you should see something like::
3131

32-
Python 3.4.x
32+
Python 3.x.y
3333
[GCC 4.x] on linux
3434
Type "help", "copyright", "credits" or "license" for more information.
3535
>>>

docs/intro/tutorial01.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ in a shell prompt (indicated by the $ prefix):
2323
If Django is installed, you should see the version of your installation. If it
2424
isn't, you'll get an error telling "No module named django".
2525

26-
This tutorial is written for Django |version| and Python 3.4 or later. If the
26+
This tutorial is written for Django |version| and Python 3.5 or later. If the
2727
Django version doesn't match, you can refer to the tutorial for your version
2828
of Django by using the version switcher at the bottom right corner of this
2929
page, or update Django to the newest version. If you are still using Python

docs/ref/applications.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ Configurable attributes
192192
.. attribute:: AppConfig.path
193193

194194
Filesystem path to the application directory, e.g.
195-
``'/usr/lib/python3.4/dist-packages/django/contrib/admin'``.
195+
``'/usr/lib/pythonX.Y/dist-packages/django/contrib/admin'``.
196196

197197
In most cases, Django can automatically detect and set this, but you can
198198
also provide an explicit override as a class attribute on your

setup.py

-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
'Operating System :: OS Independent',
6363
'Programming Language :: Python',
6464
'Programming Language :: Python :: 3',
65-
'Programming Language :: Python :: 3.4',
6665
'Programming Language :: Python :: 3.5',
6766
'Programming Language :: Python :: 3.6',
6867
'Topic :: Internet :: WWW/HTTP',

tests/handlers/tests.py

-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import unittest
2-
31
from django.core.exceptions import ImproperlyConfigured
42
from django.core.handlers.wsgi import WSGIHandler, WSGIRequest, get_script_name
53
from django.core.signals import request_finished, request_started
@@ -8,11 +6,6 @@
86
RequestFactory, SimpleTestCase, TransactionTestCase, override_settings,
97
)
108

11-
try:
12-
from http import HTTPStatus
13-
except ImportError: # Python < 3.5
14-
HTTPStatus = None
15-
169

1710
class HandlerTests(SimpleTestCase):
1811

@@ -182,7 +175,6 @@ def test_environ_path_info_type(self):
182175
environ = RequestFactory().get('/%E2%A8%87%87%A5%E2%A8%A0').environ
183176
self.assertIsInstance(environ['PATH_INFO'], str)
184177

185-
@unittest.skipIf(HTTPStatus is None, 'HTTPStatus only exists on Python 3.5+')
186178
def test_handle_accepts_httpstatus_enum_value(self):
187179
def start_response(status, headers):
188180
start_response.status = status

tests/handlers/views.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1+
from http import HTTPStatus
2+
13
from django.core.exceptions import SuspiciousOperation
24
from django.db import connection, transaction
35
from django.http import HttpResponse, StreamingHttpResponse
46
from django.views.decorators.csrf import csrf_exempt
57

6-
try:
7-
from http import HTTPStatus
8-
except ImportError: # Python < 3.5
9-
pass
10-
118

129
def regular(request):
1310
return HttpResponse(b"regular content")

tests/mail/tests.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,7 @@ def get_decoded_attachments(self, django_message):
6161

6262
def iter_attachments():
6363
for i in email_message.walk():
64-
# Once support for Python<3.5 has been dropped, we can use
65-
# i.get_content_disposition() here instead.
66-
content_disposition = i.get('content-disposition', '').split(';')[0].lower()
67-
if content_disposition == 'attachment':
64+
if i.get_content_disposition() == 'attachment':
6865
filename = i.get_filename()
6966
content = i.get_payload(decode=True)
7067
mimetype = i.get_content_type()
@@ -1161,8 +1158,8 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
11611158
def __init__(self, *args, **kwargs):
11621159
threading.Thread.__init__(self)
11631160
# New kwarg added in Python 3.5; default switching to False in 3.6.
1164-
if sys.version_info >= (3, 5):
1165-
kwargs['decode_data'] = True
1161+
# Setting a value only silences a deprecation warning in Python 3.5.
1162+
kwargs['decode_data'] = True
11661163
smtpd.SMTPServer.__init__(self, *args, **kwargs)
11671164
self._sink = []
11681165
self.active = False

tests/servers/tests.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import socket
77
import sys
8-
from http.client import HTTPConnection
8+
from http.client import HTTPConnection, RemoteDisconnected
99
from urllib.error import HTTPError
1010
from urllib.parse import urlencode
1111
from urllib.request import urlopen
@@ -14,11 +14,6 @@
1414

1515
from .models import Person
1616

17-
try:
18-
from http.client import RemoteDisconnected
19-
except ImportError: # Python 3.4
20-
from http.client import BadStatusLine as RemoteDisconnected
21-
2217
TEST_ROOT = os.path.dirname(__file__)
2318
TEST_SETTINGS = {
2419
'MEDIA_URL': '/media/',

tests/sessions_tests/tests.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import os
33
import shutil
44
import string
5-
import sys
65
import tempfile
76
import unittest
87
from datetime import timedelta
@@ -733,10 +732,9 @@ def test_session_delete_on_end(self):
733732
# A deleted cookie header looks like:
734733
# Set-Cookie: sessionid=; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
735734
self.assertEqual(
736-
'Set-Cookie: {}={}; expires=Thu, 01-Jan-1970 00:00:00 GMT; '
735+
'Set-Cookie: {}=""; expires=Thu, 01-Jan-1970 00:00:00 GMT; '
737736
'Max-Age=0; Path=/'.format(
738737
settings.SESSION_COOKIE_NAME,
739-
'""' if sys.version_info >= (3, 5) else '',
740738
),
741739
str(response.cookies[settings.SESSION_COOKIE_NAME])
742740
)
@@ -763,10 +761,9 @@ def test_session_delete_on_end_with_custom_domain_and_path(self):
763761
# expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0;
764762
# Path=/example/
765763
self.assertEqual(
766-
'Set-Cookie: {}={}; Domain=.example.local; expires=Thu, '
764+
'Set-Cookie: {}=""; Domain=.example.local; expires=Thu, '
767765
'01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/example/'.format(
768766
settings.SESSION_COOKIE_NAME,
769-
'""' if sys.version_info >= (3, 5) else '',
770767
),
771768
str(response.cookies[settings.SESSION_COOKIE_NAME])
772769
)

tests/test_runner/test_debug_sql.py

+7-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import sys
21
import unittest
32
from io import StringIO
43

@@ -94,18 +93,13 @@ def test_output_verbose(self):
9493
]
9594

9695
verbose_expected_outputs = [
97-
# Output format changed in Python 3.5+
98-
x.format('' if sys.version_info < (3, 5) else 'TestDebugSQL.') for x in [
99-
'runTest (test_runner.test_debug_sql.{}FailingTest) ... FAIL',
100-
'runTest (test_runner.test_debug_sql.{}ErrorTest) ... ERROR',
101-
'runTest (test_runner.test_debug_sql.{}PassingTest) ... ok',
102-
'runTest (test_runner.test_debug_sql.{}PassingSubTest) ... ok',
103-
# If there are errors/failures in subtests but not in test itself,
104-
# the status is not written. That behavior comes from Python.
105-
'runTest (test_runner.test_debug_sql.{}FailingSubTest) ...',
106-
'runTest (test_runner.test_debug_sql.{}ErrorSubTest) ...',
107-
]
108-
] + [
96+
'runTest (test_runner.test_debug_sql.TestDebugSQL.FailingTest) ... FAIL',
97+
'runTest (test_runner.test_debug_sql.TestDebugSQL.ErrorTest) ... ERROR',
98+
'runTest (test_runner.test_debug_sql.TestDebugSQL.PassingTest) ... ok',
99+
# If there are errors/failures in subtests but not in test itself,
100+
# the status is not written. That behavior comes from Python.
101+
'runTest (test_runner.test_debug_sql.TestDebugSQL.FailingSubTest) ...',
102+
'runTest (test_runner.test_debug_sql.TestDebugSQL.ErrorSubTest) ...',
109103
('''SELECT COUNT(*) AS "__count" '''
110104
'''FROM "test_runner_person" WHERE '''
111105
'''"test_runner_person"."first_name" = 'pass';'''),

tests/test_utils/tests.py

-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import sys
32
import unittest
43
from io import StringIO
54
from unittest import mock
@@ -684,9 +683,6 @@ def test_parsing_errors(self):
684683
error_msg = (
685684
"First argument is not valid HTML:\n"
686685
"('Unexpected end tag `div` (Line 1, Column 6)', (1, 6))"
687-
) if sys.version_info >= (3, 5) else (
688-
"First argument is not valid HTML:\n"
689-
"Unexpected end tag `div` (Line 1, Column 6), at line 1, column 7"
690686
)
691687
with self.assertRaisesMessage(AssertionError, error_msg):
692688
self.assertHTMLEqual('< div></ div>', '<div></div>')

0 commit comments

Comments
 (0)