Browse Source

Fixed #29817 -- Deprecated settings.FILE_CHARSET.

Jon Dufresne 6 years ago
parent
commit
0cd465b63a

+ 25 - 1
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):

+ 2 - 0
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

+ 1 - 2
docs/ref/django-admin.txt

@@ -613,8 +613,7 @@ the :ref:`i18n documentation <how-to-create-language-files>` 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
 

+ 6 - 2
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`

+ 5 - 6
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.

+ 3 - 0
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.

+ 1 - 1
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

+ 40 - 0
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')