Ver Fonte

Refs #29983 -- Added support for using pathlib.Path in all settings.

Jon Dufresne há 5 anos atrás
pai
commit
77aa74cb70

+ 1 - 1
django/contrib/staticfiles/management/commands/findstatic.py

@@ -21,7 +21,7 @@ class Command(LabelCommand):
         if verbosity >= 2:
             searched_locations = (
                 "\nLooking in the following locations:\n  %s" %
-                "\n  ".join(finders.searched_locations)
+                "\n  ".join([str(loc) for loc in finders.searched_locations])
             )
         else:
             searched_locations = ''

+ 3 - 1
django/db/backends/sqlite3/base.py

@@ -174,7 +174,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
                 "settings.DATABASES is improperly configured. "
                 "Please supply the NAME value.")
         kwargs = {
-            'database': settings_dict['NAME'],
+            # TODO: Remove str() when dropping support for PY36.
+            # https://bugs.python.org/issue33496
+            'database': str(settings_dict['NAME']),
             'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
             **settings_dict['OPTIONS'],
         }

+ 4 - 1
django/db/backends/sqlite3/creation.py

@@ -1,6 +1,7 @@
 import os
 import shutil
 import sys
+from pathlib import Path
 
 from django.db.backends.base.creation import BaseDatabaseCreation
 
@@ -9,7 +10,9 @@ class DatabaseCreation(BaseDatabaseCreation):
 
     @staticmethod
     def is_in_memory_db(database_name):
-        return database_name == ':memory:' or 'mode=memory' in database_name
+        return not isinstance(database_name, Path) and (
+            database_name == ':memory:' or 'mode=memory' in database_name
+        )
 
     def _get_test_db_name(self):
         test_database_name = self.connection.settings_dict['TEST']['NAME'] or ':memory:'

+ 1 - 1
django/forms/renderers.py

@@ -39,7 +39,7 @@ class EngineMixin:
     def engine(self):
         return self.backend({
             'APP_DIRS': True,
-            'DIRS': [str(ROOT / self.backend.app_dirname)],
+            'DIRS': [ROOT / self.backend.app_dirname],
             'NAME': 'djangoforms',
             'OPTIONS': {},
         })

+ 1 - 1
django/template/utils.py

@@ -99,7 +99,7 @@ def get_app_template_dirs(dirname):
     installed applications.
     """
     template_dirs = [
-        str(Path(app_config.path) / dirname)
+        Path(app_config.path) / dirname
         for app_config in apps.get_app_configs()
         if app_config.path and (Path(app_config.path) / dirname).is_dir()
     ]

+ 7 - 1
docs/releases/3.1.txt

@@ -96,7 +96,7 @@ Minor features
 :mod:`django.contrib.staticfiles`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-* ...
+* The :setting:`STATICFILES_DIRS` setting now supports :class:`pathlib.Path`.
 
 :mod:`django.contrib.syndication`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -226,6 +226,12 @@ Validators
 
 * ...
 
+Miscellaneous
+~~~~~~~~~~~~~
+
+* The SQLite backend now supports :class:`pathlib.Path` for the ``NAME``
+  setting.
+
 .. _backwards-incompatible-3.1:
 
 Backwards incompatible changes in 3.1

+ 17 - 1
tests/backends/sqlite/tests.py

@@ -1,11 +1,14 @@
+import os
 import re
+import tempfile
 import threading
 import unittest
+from pathlib import Path
 from sqlite3 import dbapi2
 from unittest import mock
 
 from django.core.exceptions import ImproperlyConfigured
-from django.db import connection, transaction
+from django.db import ConnectionHandler, connection, transaction
 from django.db.models import Avg, StdDev, Sum, Variance
 from django.db.models.aggregates import Aggregate
 from django.db.models.fields import CharField
@@ -89,6 +92,19 @@ class Tests(TestCase):
                 value = bool(value) if value in {0, 1} else value
                 self.assertIs(value, expected)
 
+    def test_pathlib_name(self):
+        with tempfile.TemporaryDirectory() as tmp:
+            settings_dict = {
+                'default': {
+                    'ENGINE': 'django.db.backends.sqlite3',
+                    'NAME': Path(tmp) / 'test.db',
+                },
+            }
+            connections = ConnectionHandler(settings_dict)
+            connections['default'].ensure_connection()
+            connections['default'].close()
+            self.assertTrue(os.path.isfile(os.path.join(tmp, 'test.db')))
+
 
 @unittest.skipUnless(connection.vendor == 'sqlite', 'SQLite tests')
 @isolate_apps('backends')

+ 9 - 0
tests/i18n/test_compilation.py

@@ -227,3 +227,12 @@ class AppCompilationTest(ProjectAndAppTests):
         call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO())
         self.assertTrue(os.path.exists(self.PROJECT_MO_FILE))
         self.assertTrue(os.path.exists(self.APP_MO_FILE))
+
+
+class PathLibLocaleCompilationTests(MessageCompilationTests):
+    work_subdir = 'exclude'
+
+    def test_locale_paths_pathlib(self):
+        with override_settings(LOCALE_PATHS=[Path(self.test_dir) / 'canned_locale']):
+            call_command('compilemessages', locale=['fr'], stdout=StringIO())
+            self.assertTrue(os.path.exists('canned_locale/fr/LC_MESSAGES/django.mo'))

+ 8 - 1
tests/i18n/test_extraction.py

@@ -5,6 +5,7 @@ import tempfile
 import time
 import warnings
 from io import StringIO
+from pathlib import Path
 from unittest import mock, skipIf, skipUnless
 
 from admin_scripts.tests import AdminScriptTestCase
@@ -735,11 +736,17 @@ class CustomLayoutExtractionTests(ExtractorTests):
             management.call_command('makemessages', locale=LOCALE, verbosity=0)
 
     def test_project_locale_paths(self):
+        self._test_project_locale_paths(os.path.join(self.test_dir, 'project_locale'))
+
+    def test_project_locale_paths_pathlib(self):
+        self._test_project_locale_paths(Path(self.test_dir) / 'project_locale')
+
+    def _test_project_locale_paths(self, locale_path):
         """
         * translations for an app containing a locale folder are stored in that folder
         * translations outside of that app are in LOCALE_PATHS[0]
         """
-        with override_settings(LOCALE_PATHS=[os.path.join(self.test_dir, 'project_locale')]):
+        with override_settings(LOCALE_PATHS=[locale_path]):
             management.call_command('makemessages', locale=[LOCALE], verbosity=0)
             project_de_locale = os.path.join(
                 self.test_dir, 'project_locale', 'de', 'LC_MESSAGES', 'django.po')

+ 9 - 0
tests/model_fields/test_filefield.py

@@ -1,6 +1,8 @@
 import os
 import sys
+import tempfile
 import unittest
+from pathlib import Path
 
 from django.core.files import temp
 from django.core.files.base import ContentFile
@@ -94,3 +96,10 @@ class FileFieldTests(TestCase):
         # open() doesn't write to disk.
         d.myfile.file = ContentFile(b'', name='bla')
         self.assertEqual(d.myfile, d.myfile.open())
+
+    def test_media_root_pathlib(self):
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            with override_settings(MEDIA_ROOT=Path(tmp_dir)):
+                with TemporaryUploadedFile('foo.txt', 'text/plain', 1, 'utf-8') as tmp_file:
+                    Document.objects.create(myfile=tmp_file)
+                    self.assertTrue(os.path.exists(os.path.join(tmp_dir, 'unused', 'foo.txt')))

+ 11 - 1
tests/sessions_tests/tests.py

@@ -6,6 +6,7 @@ import tempfile
 import unittest
 from datetime import timedelta
 from http import cookies
+from pathlib import Path
 
 from django.conf import settings
 from django.contrib.sessions.backends.base import UpdateError
@@ -521,7 +522,7 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase):
     def setUp(self):
         # Do file session tests in an isolated directory, and kill it after we're done.
         self.original_session_file_path = settings.SESSION_FILE_PATH
-        self.temp_session_store = settings.SESSION_FILE_PATH = tempfile.mkdtemp()
+        self.temp_session_store = settings.SESSION_FILE_PATH = self.mkdtemp()
         # Reset the file session backend's internal caches
         if hasattr(self.backend, '_storage_path'):
             del self.backend._storage_path
@@ -532,6 +533,9 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase):
         settings.SESSION_FILE_PATH = self.original_session_file_path
         shutil.rmtree(self.temp_session_store)
 
+    def mkdtemp(self):
+        return tempfile.mkdtemp()
+
     @override_settings(
         SESSION_FILE_PATH='/if/this/directory/exists/you/have/a/weird/computer',
     )
@@ -598,6 +602,12 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase):
         self.assertEqual(1, count_sessions())
 
 
+class FileSessionPathLibTests(FileSessionTests):
+    def mkdtemp(self):
+        tmp_dir = super().mkdtemp()
+        return Path(tmp_dir)
+
+
 class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
 
     backend = CacheSession

+ 4 - 1
tests/staticfiles_tests/cases.py

@@ -64,7 +64,7 @@ class CollectionTestCase(BaseStaticFilesMixin, SimpleTestCase):
 
     def setUp(self):
         super().setUp()
-        temp_dir = tempfile.mkdtemp()
+        temp_dir = self.mkdtemp()
         # Override the STATIC_ROOT for all tests from setUp to tearDown
         # rather than as a context manager
         self.patched_settings = self.settings(STATIC_ROOT=temp_dir)
@@ -78,6 +78,9 @@ class CollectionTestCase(BaseStaticFilesMixin, SimpleTestCase):
         self.patched_settings.disable()
         super().tearDown()
 
+    def mkdtemp(self):
+        return tempfile.mkdtemp()
+
     def run_collectstatic(self, *, verbosity=0, **kwargs):
         call_command('collectstatic', interactive=False, verbosity=verbosity,
                      ignore_patterns=['*.ignoreme'], **kwargs)

+ 1 - 0
tests/staticfiles_tests/project/pathlib/pathlib.txt

@@ -0,0 +1 @@
+pathlib

+ 2 - 0
tests/staticfiles_tests/settings.py

@@ -1,4 +1,5 @@
 import os.path
+from pathlib import Path
 
 TEST_ROOT = os.path.dirname(__file__)
 
@@ -10,6 +11,7 @@ TEST_SETTINGS = {
     'STATICFILES_DIRS': [
         os.path.join(TEST_ROOT, 'project', 'documents'),
         ('prefix', os.path.join(TEST_ROOT, 'project', 'prefixed')),
+        Path(TEST_ROOT) / 'project' / 'pathlib',
     ],
     'STATICFILES_FINDERS': [
         'django.contrib.staticfiles.finders.FileSystemFinder',

+ 11 - 0
tests/staticfiles_tests/test_management.py

@@ -4,6 +4,7 @@ import shutil
 import tempfile
 import unittest
 from io import StringIO
+from pathlib import Path
 from unittest import mock
 
 from admin_scripts.tests import AdminScriptTestCase
@@ -102,6 +103,7 @@ class TestFindStatic(TestDefaults, CollectionTestCase):
         # FileSystemFinder searched locations
         self.assertIn(TEST_SETTINGS['STATICFILES_DIRS'][1][1], searched_locations)
         self.assertIn(TEST_SETTINGS['STATICFILES_DIRS'][0], searched_locations)
+        self.assertIn(str(TEST_SETTINGS['STATICFILES_DIRS'][2]), searched_locations)
         # DefaultStorageFinder searched locations
         self.assertIn(
             os.path.join('staticfiles_tests', 'project', 'site_media', 'media'),
@@ -174,6 +176,15 @@ class TestCollection(TestDefaults, CollectionTestCase):
         self.assertFileNotFound('test/backup~')
         self.assertFileNotFound('test/CVS')
 
+    def test_pathlib(self):
+        self.assertFileContains('pathlib.txt', 'pathlib')
+
+
+class TestCollectionPathLib(TestCollection):
+    def mkdtemp(self):
+        tmp_dir = super().mkdtemp()
+        return Path(tmp_dir)
+
 
 class TestCollectionVerbosity(CollectionTestCase):
     copying_msg = 'Copying '

+ 12 - 0
tests/template_backends/test_django.py

@@ -1,3 +1,5 @@
+from pathlib import Path
+
 from template_tests.test_response import test_processor_name
 
 from django.template import Context, EngineHandler, RequestContext
@@ -164,3 +166,13 @@ class DjangoTemplatesTests(TemplateStringsTests):
     def test_debug_default_template_loaders(self):
         engine = DjangoTemplates({'DIRS': [], 'APP_DIRS': True, 'NAME': 'django', 'OPTIONS': {}})
         self.assertEqual(engine.engine.loaders, self.default_loaders)
+
+    def test_dirs_pathlib(self):
+        engine = DjangoTemplates({
+            'DIRS': [Path(__file__).parent / 'templates' / 'template_backends'],
+            'APP_DIRS': False,
+            'NAME': 'django',
+            'OPTIONS': {},
+        })
+        template = engine.get_template('hello.html')
+        self.assertEqual(template.render({'name': 'Joe'}), 'Hello Joe!\n')

+ 11 - 0
tests/template_backends/test_jinja2.py

@@ -1,3 +1,4 @@
+from pathlib import Path
 from unittest import skipIf
 
 from django.template import TemplateSyntaxError
@@ -85,3 +86,13 @@ class Jinja2Tests(TemplateStringsTests):
         with self.settings(STATIC_URL='/s/'):
             content = template.render(request=request)
         self.assertEqual(content, 'Static URL: /s/')
+
+    def test_dirs_pathlib(self):
+        engine = Jinja2({
+            'DIRS': [Path(__file__).parent / 'templates' / 'template_backends'],
+            'APP_DIRS': False,
+            'NAME': 'jinja2',
+            'OPTIONS': {},
+        })
+        template = engine.get_template('hello.html')
+        self.assertEqual(template.render({'name': 'Joe'}), 'Hello Joe!')

+ 6 - 0
tests/urlpatterns_reverse/test_localeregexdescriptor.py

@@ -1,4 +1,5 @@
 import os
+from pathlib import Path
 from unittest import mock
 
 from django.core.exceptions import ImproperlyConfigured
@@ -52,3 +53,8 @@ class LocaleRegexDescriptorTests(SimpleTestCase):
 
     def test_access_locale_regex_descriptor(self):
         self.assertIsInstance(RegexPattern.regex, LocaleRegexDescriptor)
+
+
+@override_settings(LOCALE_PATHS=[Path(here) / 'translations' / 'locale'])
+class LocaleRegexDescriptorPathLibTests(LocaleRegexDescriptorTests):
+    pass