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