Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Jannis Leidel authored
5  django/utils/encoding.py
@@ -58,6 +58,11 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
58 58
 
59 59
     If strings_only is True, don't convert (some) non-string-like objects.
60 60
     """
  61
+    # Handle the common case first, saves 30-40% in performance when s
  62
+    # is an instance of unicode. This function gets called often in that
  63
+    # setting.
  64
+    if isinstance(s, unicode):
  65
+        return s
61 66
     if strings_only and is_protected_type(s):
62 67
         return s
63 68
     try:
80  django/utils/formats.py
@@ -7,34 +7,41 @@
7 7
 from django.utils.encoding import smart_str
8 8
 from django.utils import dateformat, numberformat, datetime_safe
9 9
 
  10
+# format_cache is a mapping from (format_type, lang) to the format string.
  11
+# By using the cache, it is possible to avoid running get_format_modules
  12
+# repeatedly.
  13
+_format_cache = {}
  14
+_format_modules_cache = {}
  15
+
  16
+def iter_format_modules(lang):
  17
+    """
  18
+    Does the heavy lifting of finding format modules.
  19
+    """
  20
+    if check_for_language(lang) or settings.USE_L10N:
  21
+        format_locations = ['django.conf.locale.%s']
  22
+        if settings.FORMAT_MODULE_PATH:
  23
+            format_locations.append(settings.FORMAT_MODULE_PATH + '.%s')
  24
+            format_locations.reverse()
  25
+        locale = to_locale(lang)
  26
+        locales = set((locale, locale.split('_')[0]))
  27
+        for location in format_locations:
  28
+            for loc in locales:
  29
+                try:
  30
+                    yield import_module('.formats', location % loc)
  31
+                except ImportError:
  32
+                    pass
  33
+
10 34
 def get_format_modules(reverse=False):
11 35
     """
12  
-    Returns an iterator over the format modules found in the project and Django
  36
+    Returns an iterator over the format modules found
13 37
     """
14  
-    modules = []
15  
-    if not check_for_language(get_language()) or not settings.USE_L10N:
16  
-        return modules
17  
-    locale = to_locale(get_language())
18  
-    if settings.FORMAT_MODULE_PATH:
19  
-        format_locations = [settings.FORMAT_MODULE_PATH + '.%s']
20  
-    else:
21  
-        format_locations = []
22  
-    format_locations.append('django.conf.locale.%s')
23  
-    for location in format_locations:
24  
-        for l in (locale, locale.split('_')[0]):
25  
-            try:
26  
-                mod = import_module('.formats', location % l)
27  
-            except ImportError:
28  
-                pass
29  
-            else:
30  
-                # Don't return duplicates
31  
-                if mod not in modules:
32  
-                    modules.append(mod)
  38
+    lang = get_language()
  39
+    modules = _format_modules_cache.setdefault(lang, list(iter_format_modules(lang)))
33 40
     if reverse:
34 41
         modules.reverse()
35 42
     return modules
36 43
 
37  
-def get_format(format_type):
  44
+def get_format(format_type, lang=None):
38 45
     """
39 46
     For a specific format type, returns the format for the current
40 47
     language (locale), defaults to the format in the settings.
@@ -42,11 +49,20 @@ def get_format(format_type):
42 49
     """
43 50
     format_type = smart_str(format_type)
44 51
     if settings.USE_L10N:
45  
-        for module in get_format_modules():
46  
-            try:
47  
-                return getattr(module, format_type)
48  
-            except AttributeError:
49  
-                pass
  52
+        if lang is None:
  53
+            lang = get_language()
  54
+        cache_key = (format_type, lang)
  55
+        try:
  56
+            return _format_cache[cache_key] or getattr(settings, format_type)
  57
+        except KeyError:
  58
+            for module in get_format_modules():
  59
+                try:
  60
+                    val = getattr(module, format_type)
  61
+                    _format_cache[cache_key] = val
  62
+                    return val
  63
+                except AttributeError:
  64
+                    pass
  65
+            _format_cache[cache_key] = None
50 66
     return getattr(settings, format_type)
51 67
 
52 68
 def date_format(value, format=None):
@@ -66,12 +82,16 @@ def number_format(value, decimal_pos=None):
66 82
     """
67 83
     Formats a numeric value using localization settings
68 84
     """
  85
+    if settings.USE_L10N:
  86
+        lang = get_language()
  87
+    else:
  88
+        lang = None
69 89
     return numberformat.format(
70 90
         value,
71  
-        get_format('DECIMAL_SEPARATOR'),
  91
+        get_format('DECIMAL_SEPARATOR', lang),
72 92
         decimal_pos,
73  
-        get_format('NUMBER_GROUPING'),
74  
-        get_format('THOUSAND_SEPARATOR'),
  93
+        get_format('NUMBER_GROUPING', lang),
  94
+        get_format('THOUSAND_SEPARATOR', lang),
75 95
     )
76 96
 
77 97
 def localize(value):
@@ -97,7 +117,7 @@ def localize_input(value, default=None):
97 117
     """
98 118
     if isinstance(value, (decimal.Decimal, float, int)):
99 119
         return number_format(value)
100  
-    if isinstance(value, datetime.datetime):
  120
+    elif isinstance(value, datetime.datetime):
101 121
         value = datetime_safe.new_datetime(value)
102 122
         format = smart_str(default or get_format('DATETIME_INPUT_FORMATS')[0])
103 123
         return value.strftime(format)
12  django/utils/numberformat.py
... ...
@@ -1,4 +1,6 @@
1 1
 from django.conf import settings
  2
+from django.utils.safestring import mark_safe
  3
+
2 4
 
3 5
 def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''):
4 6
     """
@@ -11,15 +13,20 @@ def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''):
11 13
     * thousand_sep: Thousand separator symbol (for example ",")
12 14
 
13 15
     """
  16
+    use_grouping = settings.USE_L10N and \
  17
+        settings.USE_THOUSAND_SEPARATOR and grouping
  18
+    # Make the common case fast:
  19
+    if isinstance(number, int) and not use_grouping and not decimal_pos:
  20
+        return mark_safe(unicode(number))
14 21
     # sign
15 22
     if float(number) < 0:
16 23
         sign = '-'
17 24
     else:
18 25
         sign = ''
19  
-    # decimal part
20 26
     str_number = unicode(number)
21 27
     if str_number[0] == '-':
22 28
         str_number = str_number[1:]
  29
+    # decimal part
23 30
     if '.' in str_number:
24 31
         int_part, dec_part = str_number.split('.')
25 32
         if decimal_pos:
@@ -30,13 +37,12 @@ def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''):
30 37
         dec_part = dec_part + ('0' * (decimal_pos - len(dec_part)))
31 38
     if dec_part: dec_part = decimal_sep + dec_part
32 39
     # grouping
33  
-    if settings.USE_L10N and settings.USE_THOUSAND_SEPARATOR and grouping:
  40
+    if use_grouping:
34 41
         int_part_gd = ''
35 42
         for cnt, digit in enumerate(int_part[::-1]):
36 43
             if cnt and not cnt % grouping:
37 44
                 int_part_gd += thousand_sep
38 45
             int_part_gd += digit
39 46
         int_part = int_part_gd[::-1]
40  
-
41 47
     return sign + int_part + dec_part
42 48
 
0  tests/regressiontests/i18n/other/__init__.py
No changes.
0  tests/regressiontests/i18n/other/locale/__init__.py
No changes.
0  tests/regressiontests/i18n/other/locale/de/__init__.py
No changes.
0  tests/regressiontests/i18n/other/locale/de/formats.py
No changes.
20  tests/regressiontests/i18n/tests.py
@@ -8,10 +8,11 @@
8 8
 from django.conf import settings
9 9
 from django.template import Template, Context
10 10
 from django.test import TestCase
11  
-from django.utils.formats import get_format, date_format, time_format, localize, localize_input
  11
+from django.utils.formats import get_format, date_format, time_format, localize, localize_input, iter_format_modules
12 12
 from django.utils.numberformat import format as nformat
13 13
 from django.utils.safestring import mark_safe, SafeString, SafeUnicode
14 14
 from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, to_locale
  15
+from django.utils.importlib import import_module
15 16
 
16 17
 
17 18
 from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
@@ -423,6 +424,23 @@ def test_localized_input(self):
423 424
         finally:
424 425
             deactivate()
425 426
 
  427
+    def test_iter_format_modules(self):
  428
+        """
  429
+        Tests the iter_format_modules function.
  430
+        """
  431
+        activate('de-at')
  432
+        old_format_module_path = settings.FORMAT_MODULE_PATH
  433
+        try:
  434
+            settings.USE_L10N = True
  435
+            de_format_mod = import_module('django.conf.locale.de.formats')
  436
+            self.assertEqual(list(iter_format_modules('de')), [de_format_mod])
  437
+            settings.FORMAT_MODULE_PATH = 'regressiontests.i18n.other.locale'
  438
+            test_de_format_mod = import_module('regressiontests.i18n.other.locale.de.formats')
  439
+            self.assertEqual(list(iter_format_modules('de')), [test_de_format_mod, de_format_mod])
  440
+        finally:
  441
+            settings.FORMAT_MODULE_PATH = old_format_module_path
  442
+            deactivate()
  443
+
426 444
 class MiscTests(TestCase):
427 445
 
428 446
     def test_parse_spec_http_header(self):

0 notes on commit 534792d

Please sign in to comment.
Something went wrong with that request. Please try again.