Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #14290 -- Made format localization faster by caching the format…

… modules. Thanks, Teemu Kurppa and Anssi Kääriäinen for the report and initial patches.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13898 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 534792d05591eb2d19dc48ee89dcd2d9b5d461c0 1 parent 9c402f0
@jezdez jezdez authored
View
5 django/utils/encoding.py
@@ -58,6 +58,11 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
If strings_only is True, don't convert (some) non-string-like objects.
"""
+ # Handle the common case first, saves 30-40% in performance when s
+ # is an instance of unicode. This function gets called often in that
+ # setting.
+ if isinstance(s, unicode):
+ return s
if strings_only and is_protected_type(s):
return s
try:
View
80 django/utils/formats.py
@@ -7,34 +7,41 @@
from django.utils.encoding import smart_str
from django.utils import dateformat, numberformat, datetime_safe
+# format_cache is a mapping from (format_type, lang) to the format string.
+# By using the cache, it is possible to avoid running get_format_modules
+# repeatedly.
+_format_cache = {}
+_format_modules_cache = {}
+
+def iter_format_modules(lang):
+ """
+ Does the heavy lifting of finding format modules.
+ """
+ if check_for_language(lang) or settings.USE_L10N:
+ format_locations = ['django.conf.locale.%s']
+ if settings.FORMAT_MODULE_PATH:
+ format_locations.append(settings.FORMAT_MODULE_PATH + '.%s')
+ format_locations.reverse()
+ locale = to_locale(lang)
+ locales = set((locale, locale.split('_')[0]))
+ for location in format_locations:
+ for loc in locales:
+ try:
+ yield import_module('.formats', location % loc)
+ except ImportError:
+ pass
+
def get_format_modules(reverse=False):
"""
- Returns an iterator over the format modules found in the project and Django
+ Returns an iterator over the format modules found
"""
- modules = []
- if not check_for_language(get_language()) or not settings.USE_L10N:
- return modules
- locale = to_locale(get_language())
- if settings.FORMAT_MODULE_PATH:
- format_locations = [settings.FORMAT_MODULE_PATH + '.%s']
- else:
- format_locations = []
- format_locations.append('django.conf.locale.%s')
- for location in format_locations:
- for l in (locale, locale.split('_')[0]):
- try:
- mod = import_module('.formats', location % l)
- except ImportError:
- pass
- else:
- # Don't return duplicates
- if mod not in modules:
- modules.append(mod)
+ lang = get_language()
+ modules = _format_modules_cache.setdefault(lang, list(iter_format_modules(lang)))
if reverse:
modules.reverse()
return modules
-def get_format(format_type):
+def get_format(format_type, lang=None):
"""
For a specific format type, returns the format for the current
language (locale), defaults to the format in the settings.
@@ -42,11 +49,20 @@ def get_format(format_type):
"""
format_type = smart_str(format_type)
if settings.USE_L10N:
- for module in get_format_modules():
- try:
- return getattr(module, format_type)
- except AttributeError:
- pass
+ if lang is None:
+ lang = get_language()
+ cache_key = (format_type, lang)
+ try:
+ return _format_cache[cache_key] or getattr(settings, format_type)
+ except KeyError:
+ for module in get_format_modules():
+ try:
+ val = getattr(module, format_type)
+ _format_cache[cache_key] = val
+ return val
+ except AttributeError:
+ pass
+ _format_cache[cache_key] = None
return getattr(settings, format_type)
def date_format(value, format=None):
@@ -66,12 +82,16 @@ def number_format(value, decimal_pos=None):
"""
Formats a numeric value using localization settings
"""
+ if settings.USE_L10N:
+ lang = get_language()
+ else:
+ lang = None
return numberformat.format(
value,
- get_format('DECIMAL_SEPARATOR'),
+ get_format('DECIMAL_SEPARATOR', lang),
decimal_pos,
- get_format('NUMBER_GROUPING'),
- get_format('THOUSAND_SEPARATOR'),
+ get_format('NUMBER_GROUPING', lang),
+ get_format('THOUSAND_SEPARATOR', lang),
)
def localize(value):
@@ -97,7 +117,7 @@ def localize_input(value, default=None):
"""
if isinstance(value, (decimal.Decimal, float, int)):
return number_format(value)
- if isinstance(value, datetime.datetime):
+ elif isinstance(value, datetime.datetime):
value = datetime_safe.new_datetime(value)
format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
return value.strftime(format)
View
12 django/utils/numberformat.py
@@ -1,4 +1,6 @@
from django.conf import settings
+from django.utils.safestring import mark_safe
+
def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''):
"""
@@ -11,15 +13,20 @@ def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''):
* thousand_sep: Thousand separator symbol (for example ",")
"""
+ use_grouping = settings.USE_L10N and \
+ settings.USE_THOUSAND_SEPARATOR and grouping
+ # Make the common case fast:
+ if isinstance(number, int) and not use_grouping and not decimal_pos:
+ return mark_safe(unicode(number))
# sign
if float(number) < 0:
sign = '-'
else:
sign = ''
- # decimal part
str_number = unicode(number)
if str_number[0] == '-':
str_number = str_number[1:]
+ # decimal part
if '.' in str_number:
int_part, dec_part = str_number.split('.')
if decimal_pos:
@@ -30,13 +37,12 @@ def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''):
dec_part = dec_part + ('0' * (decimal_pos - len(dec_part)))
if dec_part: dec_part = decimal_sep + dec_part
# grouping
- if settings.USE_L10N and settings.USE_THOUSAND_SEPARATOR and grouping:
+ if use_grouping:
int_part_gd = ''
for cnt, digit in enumerate(int_part[::-1]):
if cnt and not cnt % grouping:
int_part_gd += thousand_sep
int_part_gd += digit
int_part = int_part_gd[::-1]
-
return sign + int_part + dec_part
View
0  tests/regressiontests/i18n/other/__init__.py
No changes.
View
0  tests/regressiontests/i18n/other/locale/__init__.py
No changes.
View
0  tests/regressiontests/i18n/other/locale/de/__init__.py
No changes.
View
0  tests/regressiontests/i18n/other/locale/de/formats.py
No changes.
View
20 tests/regressiontests/i18n/tests.py
@@ -8,10 +8,11 @@
from django.conf import settings
from django.template import Template, Context
from django.test import TestCase
-from django.utils.formats import get_format, date_format, time_format, localize, localize_input
+from django.utils.formats import get_format, date_format, time_format, localize, localize_input, iter_format_modules
from django.utils.numberformat import format as nformat
from django.utils.safestring import mark_safe, SafeString, SafeUnicode
from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, to_locale
+from django.utils.importlib import import_module
from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
@@ -423,6 +424,23 @@ def test_localized_input(self):
finally:
deactivate()
+ def test_iter_format_modules(self):
+ """
+ Tests the iter_format_modules function.
+ """
+ activate('de-at')
+ old_format_module_path = settings.FORMAT_MODULE_PATH
+ try:
+ settings.USE_L10N = True
+ de_format_mod = import_module('django.conf.locale.de.formats')
+ self.assertEqual(list(iter_format_modules('de')), [de_format_mod])
+ settings.FORMAT_MODULE_PATH = 'regressiontests.i18n.other.locale'
+ test_de_format_mod = import_module('regressiontests.i18n.other.locale.de.formats')
+ self.assertEqual(list(iter_format_modules('de')), [test_de_format_mod, de_format_mod])
+ finally:
+ settings.FORMAT_MODULE_PATH = old_format_module_path
+ deactivate()
+
class MiscTests(TestCase):
def test_parse_spec_http_header(self):
Please sign in to comment.
Something went wrong with that request. Please try again.