mirror of https://github.com/django/django.git
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
This commit is contained in:
parent
9c402f0ecc
commit
534792d055
|
@ -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.
|
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):
|
if strings_only and is_protected_type(s):
|
||||||
return s
|
return s
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -7,34 +7,41 @@ from django.utils.importlib import import_module
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
from django.utils import dateformat, numberformat, datetime_safe
|
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):
|
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 = []
|
lang = get_language()
|
||||||
if not check_for_language(get_language()) or not settings.USE_L10N:
|
modules = _format_modules_cache.setdefault(lang, list(iter_format_modules(lang)))
|
||||||
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)
|
|
||||||
if reverse:
|
if reverse:
|
||||||
modules.reverse()
|
modules.reverse()
|
||||||
return modules
|
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
|
For a specific format type, returns the format for the current
|
||||||
language (locale), defaults to the format in the settings.
|
language (locale), defaults to the format in the settings.
|
||||||
|
@ -42,11 +49,20 @@ def get_format(format_type):
|
||||||
"""
|
"""
|
||||||
format_type = smart_str(format_type)
|
format_type = smart_str(format_type)
|
||||||
if settings.USE_L10N:
|
if settings.USE_L10N:
|
||||||
for module in get_format_modules():
|
if lang is None:
|
||||||
try:
|
lang = get_language()
|
||||||
return getattr(module, format_type)
|
cache_key = (format_type, lang)
|
||||||
except AttributeError:
|
try:
|
||||||
pass
|
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)
|
return getattr(settings, format_type)
|
||||||
|
|
||||||
def date_format(value, format=None):
|
def date_format(value, format=None):
|
||||||
|
@ -66,12 +82,16 @@ def number_format(value, decimal_pos=None):
|
||||||
"""
|
"""
|
||||||
Formats a numeric value using localization settings
|
Formats a numeric value using localization settings
|
||||||
"""
|
"""
|
||||||
|
if settings.USE_L10N:
|
||||||
|
lang = get_language()
|
||||||
|
else:
|
||||||
|
lang = None
|
||||||
return numberformat.format(
|
return numberformat.format(
|
||||||
value,
|
value,
|
||||||
get_format('DECIMAL_SEPARATOR'),
|
get_format('DECIMAL_SEPARATOR', lang),
|
||||||
decimal_pos,
|
decimal_pos,
|
||||||
get_format('NUMBER_GROUPING'),
|
get_format('NUMBER_GROUPING', lang),
|
||||||
get_format('THOUSAND_SEPARATOR'),
|
get_format('THOUSAND_SEPARATOR', lang),
|
||||||
)
|
)
|
||||||
|
|
||||||
def localize(value):
|
def localize(value):
|
||||||
|
@ -97,7 +117,7 @@ def localize_input(value, default=None):
|
||||||
"""
|
"""
|
||||||
if isinstance(value, (decimal.Decimal, float, int)):
|
if isinstance(value, (decimal.Decimal, float, int)):
|
||||||
return number_format(value)
|
return number_format(value)
|
||||||
if isinstance(value, datetime.datetime):
|
elif isinstance(value, datetime.datetime):
|
||||||
value = datetime_safe.new_datetime(value)
|
value = datetime_safe.new_datetime(value)
|
||||||
format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
|
format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
|
||||||
return value.strftime(format)
|
return value.strftime(format)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
|
||||||
def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''):
|
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 ",")
|
* 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
|
# sign
|
||||||
if float(number) < 0:
|
if float(number) < 0:
|
||||||
sign = '-'
|
sign = '-'
|
||||||
else:
|
else:
|
||||||
sign = ''
|
sign = ''
|
||||||
# decimal part
|
|
||||||
str_number = unicode(number)
|
str_number = unicode(number)
|
||||||
if str_number[0] == '-':
|
if str_number[0] == '-':
|
||||||
str_number = str_number[1:]
|
str_number = str_number[1:]
|
||||||
|
# decimal part
|
||||||
if '.' in str_number:
|
if '.' in str_number:
|
||||||
int_part, dec_part = str_number.split('.')
|
int_part, dec_part = str_number.split('.')
|
||||||
if decimal_pos:
|
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)))
|
dec_part = dec_part + ('0' * (decimal_pos - len(dec_part)))
|
||||||
if dec_part: dec_part = decimal_sep + dec_part
|
if dec_part: dec_part = decimal_sep + dec_part
|
||||||
# grouping
|
# grouping
|
||||||
if settings.USE_L10N and settings.USE_THOUSAND_SEPARATOR and grouping:
|
if use_grouping:
|
||||||
int_part_gd = ''
|
int_part_gd = ''
|
||||||
for cnt, digit in enumerate(int_part[::-1]):
|
for cnt, digit in enumerate(int_part[::-1]):
|
||||||
if cnt and not cnt % grouping:
|
if cnt and not cnt % grouping:
|
||||||
int_part_gd += thousand_sep
|
int_part_gd += thousand_sep
|
||||||
int_part_gd += digit
|
int_part_gd += digit
|
||||||
int_part = int_part_gd[::-1]
|
int_part = int_part_gd[::-1]
|
||||||
|
|
||||||
return sign + int_part + dec_part
|
return sign + int_part + dec_part
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,11 @@ import pickle
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from django.test import TestCase
|
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.numberformat import format as nformat
|
||||||
from django.utils.safestring import mark_safe, SafeString, SafeUnicode
|
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.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
|
from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
|
||||||
|
@ -423,6 +424,23 @@ class FormattingTests(TestCase):
|
||||||
finally:
|
finally:
|
||||||
deactivate()
|
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):
|
class MiscTests(TestCase):
|
||||||
|
|
||||||
def test_parse_spec_http_header(self):
|
def test_parse_spec_http_header(self):
|
||||||
|
|
Loading…
Reference in New Issue