Fixed #27453 -- Avoided unnecessary recompilation of non-translated URL regexes.
This commit is contained in:
parent
e454db3eee
commit
6e222dae56
|
@ -79,6 +79,37 @@ def get_ns_resolver(ns_pattern, resolver):
|
||||||
return RegexURLResolver(r'^/', [ns_resolver])
|
return RegexURLResolver(r'^/', [ns_resolver])
|
||||||
|
|
||||||
|
|
||||||
|
class LocaleRegexDescriptor(object):
|
||||||
|
def __get__(self, instance, cls=None):
|
||||||
|
"""
|
||||||
|
Return a compiled regular expression based on the active language.
|
||||||
|
"""
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
# As a performance optimization, if the given regex string is a regular
|
||||||
|
# string (not a lazily-translated string proxy), compile it once and
|
||||||
|
# avoid per-language compilation.
|
||||||
|
if isinstance(instance._regex, six.string_types):
|
||||||
|
instance.__dict__['regex'] = self._compile(instance._regex)
|
||||||
|
return instance.__dict__['regex']
|
||||||
|
language_code = get_language()
|
||||||
|
if language_code not in instance._regex_dict:
|
||||||
|
instance._regex_dict[language_code] = self._compile(force_text(instance._regex))
|
||||||
|
return instance._regex_dict[language_code]
|
||||||
|
|
||||||
|
def _compile(self, regex):
|
||||||
|
"""
|
||||||
|
Compile and return the given regular expression.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return re.compile(regex, re.UNICODE)
|
||||||
|
except re.error as e:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'"%s" is not a valid regular expression: %s' %
|
||||||
|
(regex, six.text_type(e))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LocaleRegexProvider(object):
|
class LocaleRegexProvider(object):
|
||||||
"""
|
"""
|
||||||
A mixin to provide a default regex property which can vary by active
|
A mixin to provide a default regex property which can vary by active
|
||||||
|
@ -91,23 +122,7 @@ class LocaleRegexProvider(object):
|
||||||
self._regex = regex
|
self._regex = regex
|
||||||
self._regex_dict = {}
|
self._regex_dict = {}
|
||||||
|
|
||||||
@property
|
regex = LocaleRegexDescriptor()
|
||||||
def regex(self):
|
|
||||||
"""
|
|
||||||
Return a compiled regular expression based on the activate language.
|
|
||||||
"""
|
|
||||||
language_code = get_language()
|
|
||||||
if language_code not in self._regex_dict:
|
|
||||||
regex = self._regex if isinstance(self._regex, six.string_types) else force_text(self._regex)
|
|
||||||
try:
|
|
||||||
compiled_regex = re.compile(regex, re.UNICODE)
|
|
||||||
except re.error as e:
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
'"%s" is not a valid regular expression: %s' %
|
|
||||||
(regex, six.text_type(e))
|
|
||||||
)
|
|
||||||
self._regex_dict[language_code] = compiled_regex
|
|
||||||
return self._regex_dict[language_code]
|
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,6 +5,7 @@ import os
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import SimpleTestCase, mock, override_settings
|
from django.test import SimpleTestCase, mock, override_settings
|
||||||
from django.urls import LocaleRegexProvider
|
from django.urls import LocaleRegexProvider
|
||||||
|
from django.urls.resolvers import LocaleRegexDescriptor
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils._os import upath
|
from django.utils._os import upath
|
||||||
|
|
||||||
|
@ -33,9 +34,24 @@ class LocaleRegexProviderTests(SimpleTestCase):
|
||||||
self.assertEqual(de_compiled.pattern, '^foo-de/$')
|
self.assertEqual(de_compiled.pattern, '^foo-de/$')
|
||||||
self.assertEqual(de_compiled, de_compiled_2)
|
self.assertEqual(de_compiled, de_compiled_2)
|
||||||
|
|
||||||
|
def test_nontranslated_regex_compiled_once(self):
|
||||||
|
provider = LocaleRegexProvider('^foo/$')
|
||||||
|
with translation.override('de'):
|
||||||
|
de_compiled = provider.regex
|
||||||
|
with translation.override('fr'):
|
||||||
|
# compiled only once, regardless of language
|
||||||
|
error = AssertionError('tried to compile non-translated url regex twice')
|
||||||
|
with mock.patch('django.urls.resolvers.re.compile', side_effect=error):
|
||||||
|
fr_compiled = provider.regex
|
||||||
|
self.assertEqual(de_compiled.pattern, '^foo/$')
|
||||||
|
self.assertEqual(fr_compiled.pattern, '^foo/$')
|
||||||
|
|
||||||
def test_regex_compile_error(self):
|
def test_regex_compile_error(self):
|
||||||
"""Regex errors are re-raised as ImproperlyConfigured."""
|
"""Regex errors are re-raised as ImproperlyConfigured."""
|
||||||
provider = LocaleRegexProvider('*')
|
provider = LocaleRegexProvider('*')
|
||||||
msg = '"*" is not a valid regular expression: nothing to repeat'
|
msg = '"*" is not a valid regular expression: nothing to repeat'
|
||||||
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
provider.regex
|
provider.regex
|
||||||
|
|
||||||
|
def test_access_locale_regex_descriptor(self):
|
||||||
|
self.assertIsInstance(LocaleRegexProvider.regex, LocaleRegexDescriptor)
|
||||||
|
|
Loading…
Reference in New Issue