mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #24590 -- Cached calls to swappable_setting.
Moved the lookup in Field.swappable_setting to Apps, and added an lru_cache to cache the results. Refs #24743 Thanks Marten Kenbeek for the initial work on the patch. Thanks Aymeric Augustin and Tim Graham for the review.
This commit is contained in:
parent
91f701f4fc
commit
e1427cc609
@ -260,6 +260,28 @@ class Apps(object):
|
||||
"Model '%s.%s' not registered." % (app_label, model_name))
|
||||
return model
|
||||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def get_swappable_settings_name(self, to_string):
|
||||
"""
|
||||
For a given model string (e.g. "auth.User"), return the name of the
|
||||
corresponding settings name if it refers to a swappable model. If the
|
||||
referred model is not swappable, return None.
|
||||
|
||||
This method is decorated with lru_cache because it's performance
|
||||
critical when it comes to migrations. Since the swappable settings don't
|
||||
change after Django has loaded the settings, there is no reason to get
|
||||
the respective settings attribute over and over again.
|
||||
"""
|
||||
for model in self.get_models(include_swapped=True):
|
||||
swapped = model._meta.swapped
|
||||
# Is this model swapped out for the model given by to_string?
|
||||
if swapped and swapped == to_string:
|
||||
return model._meta.swappable
|
||||
# Is this model swappable and the one given by to_string?
|
||||
if model._meta.swappable and model._meta.label == to_string:
|
||||
return model._meta.swappable
|
||||
return None
|
||||
|
||||
def set_available_apps(self, available):
|
||||
"""
|
||||
Restricts the set of installed apps used by get_app_config[s].
|
||||
|
@ -314,17 +314,8 @@ class RelatedField(Field):
|
||||
if isinstance(self.remote_field.model, six.string_types):
|
||||
to_string = self.remote_field.model
|
||||
else:
|
||||
to_string = "%s.%s" % (
|
||||
self.remote_field.model._meta.app_label,
|
||||
self.remote_field.model._meta.object_name,
|
||||
)
|
||||
# See if anything swapped/swappable matches
|
||||
for model in apps.get_models(include_swapped=True):
|
||||
if model._meta.swapped:
|
||||
if model._meta.swapped == to_string:
|
||||
return model._meta.swappable
|
||||
if ("%s.%s" % (model._meta.app_label, model._meta.object_name)) == to_string and model._meta.swappable:
|
||||
return model._meta.swappable
|
||||
to_string = self.remote_field.model._meta.label
|
||||
return apps.get_swappable_settings_name(to_string)
|
||||
return None
|
||||
|
||||
def set_attributes_from_rel(self):
|
||||
|
@ -396,7 +396,7 @@ class Options(object):
|
||||
# or as part of validation.
|
||||
return swapped_for
|
||||
|
||||
if '%s.%s' % (swapped_label, swapped_object.lower()) not in (None, self.label_lower):
|
||||
if '%s.%s' % (swapped_label, swapped_object.lower()) != self.label_lower:
|
||||
return swapped_for
|
||||
return None
|
||||
|
||||
|
@ -29,7 +29,7 @@ except ImportError:
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Approximate', 'ContextList', 'get_runner',
|
||||
'Approximate', 'ContextList', 'isolate_lru_cache', 'get_runner',
|
||||
'modify_settings', 'override_settings',
|
||||
'requires_tz_support',
|
||||
'setup_test_environment', 'teardown_test_environment',
|
||||
@ -503,6 +503,16 @@ def extend_sys_path(*paths):
|
||||
sys.path = _orig_sys_path
|
||||
|
||||
|
||||
@contextmanager
|
||||
def isolate_lru_cache(lru_cache_object):
|
||||
"""Clear the cache of an LRU cache object on entering and exiting."""
|
||||
lru_cache_object.cache_clear()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
lru_cache_object.cache_clear()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def captured_output(stream_name):
|
||||
"""Return a context manager used by captured_stdout/stdin/stderr
|
||||
|
@ -1,7 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import apps
|
||||
from django.db import models
|
||||
from django.test import SimpleTestCase, override_settings
|
||||
from django.test.utils import isolate_lru_cache
|
||||
from django.utils import six
|
||||
|
||||
|
||||
@ -219,6 +221,7 @@ class FieldDeconstructionTests(SimpleTestCase):
|
||||
|
||||
@override_settings(AUTH_USER_MODEL="auth.Permission")
|
||||
def test_foreign_key_swapped(self):
|
||||
with isolate_lru_cache(apps.get_swappable_settings_name):
|
||||
# It doesn't matter that we swapped out user for permission;
|
||||
# there's no validation. We just want to check the setting stuff works.
|
||||
field = models.ForeignKey("auth.Permission", models.CASCADE)
|
||||
@ -297,6 +300,7 @@ class FieldDeconstructionTests(SimpleTestCase):
|
||||
|
||||
@override_settings(AUTH_USER_MODEL="auth.Permission")
|
||||
def test_many_to_many_field_swapped(self):
|
||||
with isolate_lru_cache(apps.get_swappable_settings_name):
|
||||
# It doesn't matter that we swapped out user for permission;
|
||||
# there's no validation. We just want to check the setting stuff works.
|
||||
field = models.ManyToManyField("auth.Permission")
|
||||
|
Loading…
Reference in New Issue
Block a user