diff --git a/django/utils/formats.py b/django/utils/formats.py index fc68179f3a..5bb8d75df7 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -47,10 +47,15 @@ def iter_format_modules(lang, format_module_path=None): Does the heavy lifting of finding format modules. """ if check_for_language(lang): - format_locations = ['django.conf.locale.%s'] - if format_module_path: - format_locations.append(format_module_path + '.%s') - format_locations.reverse() + format_locations = [] + if settings.FORMAT_MODULE_PATH: + if isinstance(settings.FORMAT_MODULE_PATH, six.string_types): + format_module_path_setting = [settings.FORMAT_MODULE_PATH] + else: + format_module_path_setting = settings.FORMAT_MODULE_PATH + for path in format_module_path_setting: + format_locations.append(path + '.%s') + format_locations.append('django.conf.locale.%s') locale = to_locale(lang) locales = [locale] if '_' in locale: diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 597e39ab99..926f0d300e 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1378,6 +1378,20 @@ like:: __init__.py formats.py +.. versionchanged:: 1.8 + + You can also set this setting to a list of Python paths, for example:: + + FORMAT_MODULE_PATH = [ + 'mysite.formats', + 'some_app.formats', + ] + + When Django searches for a certain format, it will go through all given + Python paths until it finds a module that actually defines the given + format. This means that formats defined in packages farther up in the list + will take precedence over the same formats in packages farther down. + Available formats are :setting:`DATE_FORMAT`, :setting:`TIME_FORMAT`, :setting:`DATETIME_FORMAT`, :setting:`YEAR_MONTH_FORMAT`, :setting:`MONTH_DAY_FORMAT`, :setting:`SHORT_DATE_FORMAT`, diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 6b0cb85058..22d8ca7d20 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -133,7 +133,10 @@ Forms Internationalization ^^^^^^^^^^^^^^^^^^^^ -* ... +* :setting:`FORMAT_MODULE_PATH` can now be a list of strings representing + module paths. This allows importing several format modules from different + reusable apps. It also allows overriding those custom formats in your main + Django project. Management Commands ^^^^^^^^^^^^^^^^^^^ diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt index 04b36e2767..94b2eed8ee 100644 --- a/docs/topics/i18n/formatting.txt +++ b/docs/topics/i18n/formatting.txt @@ -154,11 +154,20 @@ Django provides format definitions for many locales, but sometimes you might want to create your own, because a format files doesn't exist for your locale, or because you want to overwrite some of the values. -To use custom formats, specify the path where you'll place format files first. -To do that, just set your :setting:`FORMAT_MODULE_PATH` setting to the package -where format files will exist, for instance:: - FORMAT_MODULE_PATH = 'mysite.formats' +.. versionchanged:: 1.8 + + The ability to specify FORMAT_MODULE_PATH as a list was added. Previously, + only a single string value was supported. + +To use custom formats, specify the path where you'll place format files +first. To do that, just set your :setting:`FORMAT_MODULE_PATH` setting to +the package where format files will exist, for instance:: + + FORMAT_MODULE_PATH = [ + 'mysite.formats', + 'some_app.formats', + ] Files are not placed directly in this directory, but in a directory named as the locale, and must be named ``formats.py``. diff --git a/tests/utils_tests/test_formats.py b/tests/utils_tests/test_formats.py new file mode 100644 index 0000000000..17fa9fdadf --- /dev/null +++ b/tests/utils_tests/test_formats.py @@ -0,0 +1,59 @@ +"""Tests for ``django/utils/formats.py``.""" +from django.test import TestCase +from django.utils.formats import iter_format_modules + + +class IterFormatModulesTestCase(TestCase): + """Tests for the ``iter_format_modules`` method.""" + longMessage = True + + def test_returns_correct_default(self): + """ + Should return default module when FORMAT_MODULE_PATH is not set. + """ + result = list(iter_format_modules('en')) + self.assertEqual(len(result), 1, msg=( + "Should return only Django's default formats module.")) + self.assertEqual( + result[0].__name__, 'django.conf.locale.en.formats', msg=( + 'Should have added the language to the module path')) + + def test_with_setting_as_basestring(self): + """ + Before ticket #20477 FORMAT_MODULE_PATH was supposed to be a string. + + This test ensures backwards compatibility. + """ + with self.settings( + FORMAT_MODULE_PATH='utils_tests.test_module.formats'): + result = list(iter_format_modules('en')) + self.assertEqual(len(result), 2, msg=( + 'Should return both, the default value and the one from the' + ' setting')) + self.assertEqual( + result[0].__name__, + 'utils_tests.test_module.formats.en.formats', + msg=('Should return the module from the setting first and' + ' should have added the language to the module path')) + + def test_with_setting_as_list_of_strings(self): + """ + After ticket #20477 FORMAT_MODULE_PATH can also be a list of strings. + + This tests verifies the new functionality. + """ + FORMAT_MODULE_PATH = [ + 'utils_tests.test_module.formats', + 'utils_tests.test_module.formats2', + ] + with self.settings( + FORMAT_MODULE_PATH=FORMAT_MODULE_PATH): + result = list(iter_format_modules('en')) + self.assertEqual(len(result), 3, msg=( + 'Should return the default value and the two values from the' + ' setting')) + self.assertEqual( + result[0].__name__, + 'utils_tests.test_module.formats.en.formats', + msg=('Should return the values from the setting and add the' + ' language to the module path')) diff --git a/tests/utils_tests/test_module/formats/__init__.py b/tests/utils_tests/test_module/formats/__init__.py new file mode 100644 index 0000000000..6dc04db23d --- /dev/null +++ b/tests/utils_tests/test_module/formats/__init__.py @@ -0,0 +1,4 @@ +""" +Custom format module. Used by tests in ``tests/utils_tests/test_formats.py``. + +""" diff --git a/tests/utils_tests/test_module/formats/en/__init__.py b/tests/utils_tests/test_module/formats/en/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/utils_tests/test_module/formats/en/formats.py b/tests/utils_tests/test_module/formats/en/formats.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/utils_tests/test_module/formats2/__init__.py b/tests/utils_tests/test_module/formats2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/utils_tests/test_module/formats2/en/__init__.py b/tests/utils_tests/test_module/formats2/en/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/utils_tests/test_module/formats2/en/formats.py b/tests/utils_tests/test_module/formats2/en/formats.py new file mode 100644 index 0000000000..e69de29bb2