summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2017-02-17 19:45:34 -0500
committerTim Graham <timograham@gmail.com>2017-09-25 17:11:03 -0400
commitcfff2af02be40106d4759cc6f8bfa476ce82421c (patch)
tree2680ff4040dd83d7b50ca2265e8268e362142476
parenta80903b7114c984b5087597e8c34750e7bb44f51 (diff)
Fixed #27857 -- Dropped support for Python 3.4.
-rw-r--r--INSTALL2
-rw-r--r--django/db/models/expressions.py6
-rw-r--r--django/http/cookie.py17
-rw-r--r--django/test/html.py9
-rw-r--r--django/urls/converters.py5
-rw-r--r--django/urls/resolvers.py4
-rw-r--r--django/utils/html.py18
-rw-r--r--django/utils/html_parser.py17
-rw-r--r--docs/intro/contributing.txt6
-rw-r--r--docs/intro/install.txt2
-rw-r--r--docs/intro/tutorial01.txt2
-rw-r--r--docs/ref/applications.txt2
-rw-r--r--setup.py1
-rw-r--r--tests/handlers/tests.py8
-rw-r--r--tests/handlers/views.py7
-rw-r--r--tests/mail/tests.py9
-rw-r--r--tests/servers/tests.py7
-rw-r--r--tests/sessions_tests/tests.py7
-rw-r--r--tests/test_runner/test_debug_sql.py20
-rw-r--r--tests/test_utils/tests.py4
20 files changed, 37 insertions, 116 deletions
diff --git a/INSTALL b/INSTALL
index be64877476..dda9b4c4e5 100644
--- a/INSTALL
+++ b/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
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index 49ca801924..def866efbd 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -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 (
[]
diff --git a/django/http/cookie.py b/django/http/cookie.py
index 52dff786c4..b94d2b0386 100644
--- a/django/http/cookie.py
+++ b/django/http/cookie.py
@@ -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):
diff --git a/django/test/html.py b/django/test/html.py
index 726beb6e93..b5ec00b16f 100644
--- a/django/test/html.py
+++ b/django/test/html.py
@@ -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 = {}
diff --git a/django/urls/converters.py b/django/urls/converters.py
index eb2a61971e..cc22f33df4 100644
--- a/django/urls/converters.py
+++ b/django/urls/converters.py
@@ -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):
diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py
index 4cd25ff075..b214264445 100644
--- a/django/urls/resolvers.py
+++ b/django/urls/resolvers.py
@@ -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__
diff --git a/django/utils/html.py b/django/utils/html.py
index 9f4f58c7a1..e365cd41f6 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -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
diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py
deleted file mode 100644
index 6b46ddc368..0000000000
--- a/django/utils/html_parser.py
+++ /dev/null
@@ -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)
diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt
index 764d166631..ceafb6b028 100644
--- a/docs/intro/contributing.txt
+++ b/docs/intro/contributing.txt
@@ -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
diff --git a/docs/intro/install.txt b/docs/intro/install.txt
index 9861923b37..a0370df3d2 100644
--- a/docs/intro/install.txt
+++ b/docs/intro/install.txt
@@ -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.
>>>
diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt
index fca0cca25b..91bf08e66b 100644
--- a/docs/intro/tutorial01.txt
+++ b/docs/intro/tutorial01.txt
@@ -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
diff --git a/docs/ref/applications.txt b/docs/ref/applications.txt
index 53d11e31e4..35d2118a27 100644
--- a/docs/ref/applications.txt
+++ b/docs/ref/applications.txt
@@ -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
diff --git a/setup.py b/setup.py
index e9cfa4f093..a9367606ff 100644
--- a/setup.py
+++ b/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',
diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py
index 167faadb02..adf34c5437 100644
--- a/tests/handlers/tests.py
+++ b/tests/handlers/tests.py
@@ -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
diff --git a/tests/handlers/views.py b/tests/handlers/views.py
index 22b94de3b9..8005cc605f 100644
--- a/tests/handlers/views.py
+++ b/tests/handlers/views.py
@@ -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")
diff --git a/tests/mail/tests.py b/tests/mail/tests.py
index 29a56d6e74..b60e7ff6c9 100644
--- a/tests/mail/tests.py
+++ b/tests/mail/tests.py
@@ -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
diff --git a/tests/servers/tests.py b/tests/servers/tests.py
index ea64c246e2..cbf477fa98 100644
--- a/tests/servers/tests.py
+++ b/tests/servers/tests.py
@@ -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/',
diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py
index bd0e3b7668..3a3af1613a 100644
--- a/tests/sessions_tests/tests.py
+++ b/tests/sessions_tests/tests.py
@@ -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])
)
diff --git a/tests/test_runner/test_debug_sql.py b/tests/test_runner/test_debug_sql.py
index 2ac7203051..8c4cb6fef6 100644
--- a/tests/test_runner/test_debug_sql.py
+++ b/tests/test_runner/test_debug_sql.py
@@ -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';'''),
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index 80ee5a27b5..ff0bda6b8d 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -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>')