Fixed #27453 -- Avoided unnecessary recompilation of non-translated URL regexes.

This commit is contained in:
Carl Meyer 2016-11-06 13:52:07 +01:00 committed by Tim Graham
parent e454db3eee
commit 6e222dae56
2 changed files with 48 additions and 17 deletions

View File

@ -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):
""" """

View File

@ -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)