From 0cd465b63aa7c03a3d14bd5fd6543628d585f8da Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 2 Oct 2018 20:45:19 -0700 Subject: [PATCH] Fixed #29817 -- Deprecated settings.FILE_CHARSET. --- django/conf/__init__.py | 26 ++++++++++++++- docs/internals/deprecation.txt | 2 ++ docs/ref/django-admin.txt | 3 +- docs/ref/settings.txt | 8 +++-- docs/ref/unicode.txt | 11 +++---- docs/releases/2.2.txt | 3 ++ docs/topics/templates.txt | 2 +- tests/settings_tests/test_file_charset.py | 40 +++++++++++++++++++++++ 8 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 tests/settings_tests/test_file_charset.py diff --git a/django/conf/__init__.py b/django/conf/__init__.py index a380806485..73d943d26e 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -16,12 +16,18 @@ from pathlib import Path import django from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured -from django.utils.deprecation import RemovedInDjango30Warning +from django.utils.deprecation import ( + RemovedInDjango30Warning, RemovedInDjango31Warning, +) from django.utils.functional import LazyObject, empty ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" DEFAULT_CONTENT_TYPE_DEPRECATED_MSG = 'The DEFAULT_CONTENT_TYPE setting is deprecated.' +FILE_CHARSET_DEPRECATED_MSG = ( + 'The FILE_CHARSET setting is deprecated. Starting with Django 3.1, all ' + 'files read from disk must be UTF-8 encoded.' +) class LazySettings(LazyObject): @@ -111,6 +117,20 @@ class LazySettings(LazyObject): ) return self.__getattr__('DEFAULT_CONTENT_TYPE') + @property + def FILE_CHARSET(self): + stack = traceback.extract_stack() + # Show a warning if the setting is used outside of Django. + # Stack index: -1 this line, -2 the caller. + filename, _line_number, _function_name, _text = stack[-2] + if not filename.startswith(os.path.dirname(django.__file__)): + warnings.warn( + FILE_CHARSET_DEPRECATED_MSG, + RemovedInDjango31Warning, + stacklevel=2, + ) + return self.__getattr__('FILE_CHARSET') + class Settings: def __init__(self, settings_module): @@ -145,6 +165,8 @@ class Settings: if self.is_overridden('DEFAULT_CONTENT_TYPE'): warnings.warn(DEFAULT_CONTENT_TYPE_DEPRECATED_MSG, RemovedInDjango30Warning) + if self.is_overridden('FILE_CHARSET'): + warnings.warn(FILE_CHARSET_DEPRECATED_MSG, RemovedInDjango31Warning) if hasattr(time, 'tzset') and self.TIME_ZONE: # When we can, attempt to validate the timezone. If we can't find @@ -191,6 +213,8 @@ class UserSettingsHolder: self._deleted.discard(name) if name == 'DEFAULT_CONTENT_TYPE': warnings.warn(DEFAULT_CONTENT_TYPE_DEPRECATED_MSG, RemovedInDjango30Warning) + elif name == 'FILE_CHARSET': + warnings.warn(FILE_CHARSET_DEPRECATED_MSG, RemovedInDjango31Warning) super().__setattr__(name, value) def __delattr__(self, name): diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 1d03648ab1..4a40300442 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -24,6 +24,8 @@ details on these changes. * ``django.contrib.postgres.fields.FloatRangeField`` and ``django.contrib.postgres.forms.FloatRangeField`` will be removed. +* The ``FILE_CHARSET`` setting will be removed. + .. _deprecation-removed-in-3.0: 3.0 diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 1a3baecfda..1323d1292c 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -613,8 +613,7 @@ the :ref:`i18n documentation ` for details. This command doesn't require configured settings. However, when settings aren't configured, the command can't ignore the :setting:`MEDIA_ROOT` and -:setting:`STATIC_ROOT` directories or include :setting:`LOCALE_PATHS`. It will -also write files in UTF-8 rather than in :setting:`FILE_CHARSET`. +:setting:`STATIC_ROOT` directories or include :setting:`LOCALE_PATHS`. .. django-admin-option:: --all, -a diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index ef00d19b1b..cc2892077b 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1424,7 +1424,12 @@ attempt. Default: ``'utf-8'`` The character encoding used to decode any files read from disk. This includes -template files and initial SQL data files. +template files, static files, and translation catalogs. + +.. deprecated:: 2.2 + + This setting is deprecated. Starting with Django 3.1, files read from disk + must be UTF-8 encoded. .. setting:: FILE_UPLOAD_HANDLERS @@ -3374,7 +3379,6 @@ Error reporting File uploads ------------ * :setting:`DEFAULT_FILE_STORAGE` -* :setting:`FILE_CHARSET` * :setting:`FILE_UPLOAD_HANDLERS` * :setting:`FILE_UPLOAD_MAX_MEMORY_SIZE` * :setting:`FILE_UPLOAD_PERMISSIONS` diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index 0b0282b1cc..8617442318 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -265,12 +265,11 @@ Use strings when creating templates manually:: from django.template import Template t2 = Template('This is a string template.') -But the common case is to read templates from the filesystem, and this creates -a slight complication: not all filesystems store their data encoded as UTF-8. -If your template files are not stored with a UTF-8 encoding, set the :setting:`FILE_CHARSET` -setting to the encoding of the files on disk. When Django reads in a template -file, it will convert the data from this encoding to Unicode. (:setting:`FILE_CHARSET` -is set to ``'utf-8'`` by default.) +But the common case is to read templates from the filesystem. If your template +files are not stored with a UTF-8 encoding, adjust the :setting:`TEMPLATES` +setting. The built-in :py:mod:`~django.template.backends.django` backend +provides the ``'file_charset'`` option to change the encoding used to read +files from disk. The :setting:`DEFAULT_CHARSET` setting controls the encoding of rendered templates. This is set to UTF-8 by default. diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index 22d7eee0c1..e8f20f6836 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -332,3 +332,6 @@ Miscellaneous * The ``FloatRangeField`` model and form fields in ``django.contrib.postgres`` are deprecated in favor of a new name, ``DecimalRangeField``, to match the underlying ``numrange`` data type used in the database. + +* The ``FILE_CHARSET`` setting is deprecated. Starting with Django 3.1, files + read from disk must be UTF-8 encoded. diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 297008668f..c3f9a6b5eb 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -351,7 +351,7 @@ applications. This generic name was kept for backwards-compatibility. * ``'file_charset'``: the charset used to read template files on disk. - It defaults to the value of :setting:`FILE_CHARSET`. + It defaults to ``'utf-8'``. * ``'libraries'``: A dictionary of labels and dotted Python paths of template tag modules to register with the template engine. This can be used to add diff --git a/tests/settings_tests/test_file_charset.py b/tests/settings_tests/test_file_charset.py new file mode 100644 index 0000000000..1be96a26d2 --- /dev/null +++ b/tests/settings_tests/test_file_charset.py @@ -0,0 +1,40 @@ +import sys +from types import ModuleType + +from django.conf import FILE_CHARSET_DEPRECATED_MSG, Settings, settings +from django.test import SimpleTestCase, ignore_warnings +from django.utils.deprecation import RemovedInDjango31Warning + + +class DeprecationTests(SimpleTestCase): + msg = FILE_CHARSET_DEPRECATED_MSG + + def test_override_settings_warning(self): + with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg): + with self.settings(FILE_CHARSET='latin1'): + pass + + def test_settings_init_warning(self): + settings_module = ModuleType('fake_settings_module') + settings_module.FILE_CHARSET = 'latin1' + settings_module.SECRET_KEY = 'ABC' + sys.modules['fake_settings_module'] = settings_module + try: + with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg): + Settings('fake_settings_module') + finally: + del sys.modules['fake_settings_module'] + + def test_access_warning(self): + with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg): + settings.FILE_CHARSET + # Works a second time. + with self.assertRaisesMessage(RemovedInDjango31Warning, self.msg): + settings.FILE_CHARSET + + @ignore_warnings(category=RemovedInDjango31Warning) + def test_access(self): + with self.settings(FILE_CHARSET='latin1'): + self.assertEqual(settings.FILE_CHARSET, 'latin1') + # Works a second time. + self.assertEqual(settings.FILE_CHARSET, 'latin1')