Refs #29983 -- Added support for using pathlib.Path in all settings.
This commit is contained in:
parent
367634f976
commit
77aa74cb70
|
@ -21,7 +21,7 @@ class Command(LabelCommand):
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
searched_locations = (
|
searched_locations = (
|
||||||
"\nLooking in the following locations:\n %s" %
|
"\nLooking in the following locations:\n %s" %
|
||||||
"\n ".join(finders.searched_locations)
|
"\n ".join([str(loc) for loc in finders.searched_locations])
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
searched_locations = ''
|
searched_locations = ''
|
||||||
|
|
|
@ -174,7 +174,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
"settings.DATABASES is improperly configured. "
|
"settings.DATABASES is improperly configured. "
|
||||||
"Please supply the NAME value.")
|
"Please supply the NAME value.")
|
||||||
kwargs = {
|
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,
|
'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
|
||||||
**settings_dict['OPTIONS'],
|
**settings_dict['OPTIONS'],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.db.backends.base.creation import BaseDatabaseCreation
|
from django.db.backends.base.creation import BaseDatabaseCreation
|
||||||
|
|
||||||
|
@ -9,7 +10,9 @@ class DatabaseCreation(BaseDatabaseCreation):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_in_memory_db(database_name):
|
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):
|
def _get_test_db_name(self):
|
||||||
test_database_name = self.connection.settings_dict['TEST']['NAME'] or ':memory:'
|
test_database_name = self.connection.settings_dict['TEST']['NAME'] or ':memory:'
|
||||||
|
|
|
@ -39,7 +39,7 @@ class EngineMixin:
|
||||||
def engine(self):
|
def engine(self):
|
||||||
return self.backend({
|
return self.backend({
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'DIRS': [str(ROOT / self.backend.app_dirname)],
|
'DIRS': [ROOT / self.backend.app_dirname],
|
||||||
'NAME': 'djangoforms',
|
'NAME': 'djangoforms',
|
||||||
'OPTIONS': {},
|
'OPTIONS': {},
|
||||||
})
|
})
|
||||||
|
|
|
@ -99,7 +99,7 @@ def get_app_template_dirs(dirname):
|
||||||
installed applications.
|
installed applications.
|
||||||
"""
|
"""
|
||||||
template_dirs = [
|
template_dirs = [
|
||||||
str(Path(app_config.path) / dirname)
|
Path(app_config.path) / dirname
|
||||||
for app_config in apps.get_app_configs()
|
for app_config in apps.get_app_configs()
|
||||||
if app_config.path and (Path(app_config.path) / dirname).is_dir()
|
if app_config.path and (Path(app_config.path) / dirname).is_dir()
|
||||||
]
|
]
|
||||||
|
|
|
@ -96,7 +96,7 @@ Minor features
|
||||||
:mod:`django.contrib.staticfiles`
|
:mod:`django.contrib.staticfiles`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* The :setting:`STATICFILES_DIRS` setting now supports :class:`pathlib.Path`.
|
||||||
|
|
||||||
:mod:`django.contrib.syndication`
|
: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-3.1:
|
||||||
|
|
||||||
Backwards incompatible changes in 3.1
|
Backwards incompatible changes in 3.1
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
from sqlite3 import dbapi2
|
from sqlite3 import dbapi2
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
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 import Avg, StdDev, Sum, Variance
|
||||||
from django.db.models.aggregates import Aggregate
|
from django.db.models.aggregates import Aggregate
|
||||||
from django.db.models.fields import CharField
|
from django.db.models.fields import CharField
|
||||||
|
@ -89,6 +92,19 @@ class Tests(TestCase):
|
||||||
value = bool(value) if value in {0, 1} else value
|
value = bool(value) if value in {0, 1} else value
|
||||||
self.assertIs(value, expected)
|
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')
|
@unittest.skipUnless(connection.vendor == 'sqlite', 'SQLite tests')
|
||||||
@isolate_apps('backends')
|
@isolate_apps('backends')
|
||||||
|
|
|
@ -227,3 +227,12 @@ class AppCompilationTest(ProjectAndAppTests):
|
||||||
call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO())
|
call_command('compilemessages', locale=[self.LOCALE], stdout=StringIO())
|
||||||
self.assertTrue(os.path.exists(self.PROJECT_MO_FILE))
|
self.assertTrue(os.path.exists(self.PROJECT_MO_FILE))
|
||||||
self.assertTrue(os.path.exists(self.APP_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'))
|
||||||
|
|
|
@ -5,6 +5,7 @@ import tempfile
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
from pathlib import Path
|
||||||
from unittest import mock, skipIf, skipUnless
|
from unittest import mock, skipIf, skipUnless
|
||||||
|
|
||||||
from admin_scripts.tests import AdminScriptTestCase
|
from admin_scripts.tests import AdminScriptTestCase
|
||||||
|
@ -735,11 +736,17 @@ class CustomLayoutExtractionTests(ExtractorTests):
|
||||||
management.call_command('makemessages', locale=LOCALE, verbosity=0)
|
management.call_command('makemessages', locale=LOCALE, verbosity=0)
|
||||||
|
|
||||||
def test_project_locale_paths(self):
|
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 for an app containing a locale folder are stored in that folder
|
||||||
* translations outside of that app are in LOCALE_PATHS[0]
|
* 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)
|
management.call_command('makemessages', locale=[LOCALE], verbosity=0)
|
||||||
project_de_locale = os.path.join(
|
project_de_locale = os.path.join(
|
||||||
self.test_dir, 'project_locale', 'de', 'LC_MESSAGES', 'django.po')
|
self.test_dir, 'project_locale', 'de', 'LC_MESSAGES', 'django.po')
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.core.files import temp
|
from django.core.files import temp
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
@ -94,3 +96,10 @@ class FileFieldTests(TestCase):
|
||||||
# open() doesn't write to disk.
|
# open() doesn't write to disk.
|
||||||
d.myfile.file = ContentFile(b'', name='bla')
|
d.myfile.file = ContentFile(b'', name='bla')
|
||||||
self.assertEqual(d.myfile, d.myfile.open())
|
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')))
|
||||||
|
|
|
@ -6,6 +6,7 @@ import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from http import cookies
|
from http import cookies
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.sessions.backends.base import UpdateError
|
from django.contrib.sessions.backends.base import UpdateError
|
||||||
|
@ -521,7 +522,7 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Do file session tests in an isolated directory, and kill it after we're done.
|
# 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.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
|
# Reset the file session backend's internal caches
|
||||||
if hasattr(self.backend, '_storage_path'):
|
if hasattr(self.backend, '_storage_path'):
|
||||||
del 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
|
settings.SESSION_FILE_PATH = self.original_session_file_path
|
||||||
shutil.rmtree(self.temp_session_store)
|
shutil.rmtree(self.temp_session_store)
|
||||||
|
|
||||||
|
def mkdtemp(self):
|
||||||
|
return tempfile.mkdtemp()
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
SESSION_FILE_PATH='/if/this/directory/exists/you/have/a/weird/computer',
|
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())
|
self.assertEqual(1, count_sessions())
|
||||||
|
|
||||||
|
|
||||||
|
class FileSessionPathLibTests(FileSessionTests):
|
||||||
|
def mkdtemp(self):
|
||||||
|
tmp_dir = super().mkdtemp()
|
||||||
|
return Path(tmp_dir)
|
||||||
|
|
||||||
|
|
||||||
class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
|
class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
|
||||||
|
|
||||||
backend = CacheSession
|
backend = CacheSession
|
||||||
|
|
|
@ -64,7 +64,7 @@ class CollectionTestCase(BaseStaticFilesMixin, SimpleTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
temp_dir = tempfile.mkdtemp()
|
temp_dir = self.mkdtemp()
|
||||||
# Override the STATIC_ROOT for all tests from setUp to tearDown
|
# Override the STATIC_ROOT for all tests from setUp to tearDown
|
||||||
# rather than as a context manager
|
# rather than as a context manager
|
||||||
self.patched_settings = self.settings(STATIC_ROOT=temp_dir)
|
self.patched_settings = self.settings(STATIC_ROOT=temp_dir)
|
||||||
|
@ -78,6 +78,9 @@ class CollectionTestCase(BaseStaticFilesMixin, SimpleTestCase):
|
||||||
self.patched_settings.disable()
|
self.patched_settings.disable()
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
|
||||||
|
def mkdtemp(self):
|
||||||
|
return tempfile.mkdtemp()
|
||||||
|
|
||||||
def run_collectstatic(self, *, verbosity=0, **kwargs):
|
def run_collectstatic(self, *, verbosity=0, **kwargs):
|
||||||
call_command('collectstatic', interactive=False, verbosity=verbosity,
|
call_command('collectstatic', interactive=False, verbosity=verbosity,
|
||||||
ignore_patterns=['*.ignoreme'], **kwargs)
|
ignore_patterns=['*.ignoreme'], **kwargs)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
pathlib
|
|
@ -1,4 +1,5 @@
|
||||||
import os.path
|
import os.path
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
TEST_ROOT = os.path.dirname(__file__)
|
TEST_ROOT = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ TEST_SETTINGS = {
|
||||||
'STATICFILES_DIRS': [
|
'STATICFILES_DIRS': [
|
||||||
os.path.join(TEST_ROOT, 'project', 'documents'),
|
os.path.join(TEST_ROOT, 'project', 'documents'),
|
||||||
('prefix', os.path.join(TEST_ROOT, 'project', 'prefixed')),
|
('prefix', os.path.join(TEST_ROOT, 'project', 'prefixed')),
|
||||||
|
Path(TEST_ROOT) / 'project' / 'pathlib',
|
||||||
],
|
],
|
||||||
'STATICFILES_FINDERS': [
|
'STATICFILES_FINDERS': [
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||||
|
|
|
@ -4,6 +4,7 @@ import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from admin_scripts.tests import AdminScriptTestCase
|
from admin_scripts.tests import AdminScriptTestCase
|
||||||
|
@ -102,6 +103,7 @@ class TestFindStatic(TestDefaults, CollectionTestCase):
|
||||||
# FileSystemFinder searched locations
|
# FileSystemFinder searched locations
|
||||||
self.assertIn(TEST_SETTINGS['STATICFILES_DIRS'][1][1], searched_locations)
|
self.assertIn(TEST_SETTINGS['STATICFILES_DIRS'][1][1], searched_locations)
|
||||||
self.assertIn(TEST_SETTINGS['STATICFILES_DIRS'][0], searched_locations)
|
self.assertIn(TEST_SETTINGS['STATICFILES_DIRS'][0], searched_locations)
|
||||||
|
self.assertIn(str(TEST_SETTINGS['STATICFILES_DIRS'][2]), searched_locations)
|
||||||
# DefaultStorageFinder searched locations
|
# DefaultStorageFinder searched locations
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
os.path.join('staticfiles_tests', 'project', 'site_media', 'media'),
|
os.path.join('staticfiles_tests', 'project', 'site_media', 'media'),
|
||||||
|
@ -174,6 +176,15 @@ class TestCollection(TestDefaults, CollectionTestCase):
|
||||||
self.assertFileNotFound('test/backup~')
|
self.assertFileNotFound('test/backup~')
|
||||||
self.assertFileNotFound('test/CVS')
|
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):
|
class TestCollectionVerbosity(CollectionTestCase):
|
||||||
copying_msg = 'Copying '
|
copying_msg = 'Copying '
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from template_tests.test_response import test_processor_name
|
from template_tests.test_response import test_processor_name
|
||||||
|
|
||||||
from django.template import Context, EngineHandler, RequestContext
|
from django.template import Context, EngineHandler, RequestContext
|
||||||
|
@ -164,3 +166,13 @@ class DjangoTemplatesTests(TemplateStringsTests):
|
||||||
def test_debug_default_template_loaders(self):
|
def test_debug_default_template_loaders(self):
|
||||||
engine = DjangoTemplates({'DIRS': [], 'APP_DIRS': True, 'NAME': 'django', 'OPTIONS': {}})
|
engine = DjangoTemplates({'DIRS': [], 'APP_DIRS': True, 'NAME': 'django', 'OPTIONS': {}})
|
||||||
self.assertEqual(engine.engine.loaders, self.default_loaders)
|
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')
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from pathlib import Path
|
||||||
from unittest import skipIf
|
from unittest import skipIf
|
||||||
|
|
||||||
from django.template import TemplateSyntaxError
|
from django.template import TemplateSyntaxError
|
||||||
|
@ -85,3 +86,13 @@ class Jinja2Tests(TemplateStringsTests):
|
||||||
with self.settings(STATIC_URL='/s/'):
|
with self.settings(STATIC_URL='/s/'):
|
||||||
content = template.render(request=request)
|
content = template.render(request=request)
|
||||||
self.assertEqual(content, 'Static URL: /s/')
|
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!')
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
@ -52,3 +53,8 @@ class LocaleRegexDescriptorTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_access_locale_regex_descriptor(self):
|
def test_access_locale_regex_descriptor(self):
|
||||||
self.assertIsInstance(RegexPattern.regex, LocaleRegexDescriptor)
|
self.assertIsInstance(RegexPattern.regex, LocaleRegexDescriptor)
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(LOCALE_PATHS=[Path(here) / 'translations' / 'locale'])
|
||||||
|
class LocaleRegexDescriptorPathLibTests(LocaleRegexDescriptorTests):
|
||||||
|
pass
|
||||||
|
|
Loading…
Reference in New Issue