diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index cf2cefeea9..7a9702aebb 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -40,7 +40,7 @@ from django.conf import settings from django.core.signals import request_finished from django.utils import six from django.utils._os import npath -from django.utils.encoding import force_bytes, get_system_encoding +from django.utils.encoding import get_system_encoding from django.utils.six.moves import _thread as thread # This import does nothing, but it's necessary to avoid some race conditions @@ -290,8 +290,8 @@ def restart_with_reloader(): # Environment variables on Python 2 + Windows must be str. encoding = get_system_encoding() for key in new_environ.keys(): - str_key = force_bytes(key, encoding=encoding) - str_value = force_bytes(new_environ[key], encoding=encoding) + str_key = key.decode(encoding).encode('utf-8') + str_value = new_environ[key].decode(encoding).encode('utf-8') del new_environ[key] new_environ[str_key] = str_value new_environ["RUN_MAIN"] = 'true' diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 92fa8820a0..91620eb740 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -32,3 +32,6 @@ Bugfixes * Fixed a regression where ``SelectDateWidget`` localized the years in the select box (:ticket:`28530`). + +* Fixed a regression in 1.11.4 where ``runserver`` crashed with non-Unicode + system encodings on Python 2 + Windows (:ticket:`28487`). diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 78206135fa..cab48309fb 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -5,13 +5,14 @@ import os import shutil import sys import tempfile +import unittest from importlib import import_module from django import conf from django.contrib import admin from django.test import SimpleTestCase, mock, override_settings from django.test.utils import extend_sys_path -from django.utils import autoreload +from django.utils import autoreload, six from django.utils._os import npath, upath from django.utils.six.moves import _thread from django.utils.translation import trans_real @@ -258,6 +259,13 @@ class ResetTranslationsTests(SimpleTestCase): class TestRestartWithReloader(SimpleTestCase): + def setUp(self): + self._orig_environ = os.environ.copy() + + def tearDown(self): + os.environ.clear() + os.environ.update(self._orig_environ) + def test_environment(self): """" With Python 2 on Windows, restart_with_reloader() coerces environment @@ -268,3 +276,24 @@ class TestRestartWithReloader(SimpleTestCase): os.environ['SPAM'] = 'spam' with mock.patch.object(sys, 'argv', ['-c', 'pass']): autoreload.restart_with_reloader() + + @unittest.skipUnless(six.PY2 and sys.platform == 'win32', 'This is a Python 2 + Windows-specific issue.') + def test_environment_decoding(self): + """The system encoding is used for decoding.""" + os.environ['SPAM'] = 'spam' + os.environ['EGGS'] = b'\xc6u vi komprenas?' + with mock.patch('locale.getdefaultlocale') as default_locale: + # Latin-3 is the correct mapping. + default_locale.return_value = ('eo', 'latin3') + with mock.patch.object(sys, 'argv', ['-c', 'pass']): + autoreload.restart_with_reloader() + # CP1252 interprets latin3's C circumflex as AE ligature. + # It's incorrect but doesn't raise an error. + default_locale.return_value = ('en_US', 'cp1252') + with mock.patch.object(sys, 'argv', ['-c', 'pass']): + autoreload.restart_with_reloader() + # Interpreting the string as UTF-8 is fatal. + with self.assertRaises(UnicodeDecodeError): + default_locale.return_value = ('en_US', 'utf-8') + with mock.patch.object(sys, 'argv', ['-c', 'pass']): + autoreload.restart_with_reloader()