mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +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:
		| @@ -260,6 +260,28 @@ class Apps(object): | |||||||
|                 "Model '%s.%s' not registered." % (app_label, model_name)) |                 "Model '%s.%s' not registered." % (app_label, model_name)) | ||||||
|         return model |         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): |     def set_available_apps(self, available): | ||||||
|         """ |         """ | ||||||
|         Restricts the set of installed apps used by get_app_config[s]. |         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): |             if isinstance(self.remote_field.model, six.string_types): | ||||||
|                 to_string = self.remote_field.model |                 to_string = self.remote_field.model | ||||||
|             else: |             else: | ||||||
|                 to_string = "%s.%s" % ( |                 to_string = self.remote_field.model._meta.label | ||||||
|                     self.remote_field.model._meta.app_label, |             return apps.get_swappable_settings_name(to_string) | ||||||
|                     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 |  | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def set_attributes_from_rel(self): |     def set_attributes_from_rel(self): | ||||||
|   | |||||||
| @@ -396,7 +396,7 @@ class Options(object): | |||||||
|                     # or as part of validation. |                     # or as part of validation. | ||||||
|                     return swapped_for |                     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 swapped_for | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ except ImportError: | |||||||
|  |  | ||||||
|  |  | ||||||
| __all__ = ( | __all__ = ( | ||||||
|     'Approximate', 'ContextList', 'get_runner', |     'Approximate', 'ContextList', 'isolate_lru_cache', 'get_runner', | ||||||
|     'modify_settings', 'override_settings', |     'modify_settings', 'override_settings', | ||||||
|     'requires_tz_support', |     'requires_tz_support', | ||||||
|     'setup_test_environment', 'teardown_test_environment', |     'setup_test_environment', 'teardown_test_environment', | ||||||
| @@ -503,6 +503,16 @@ def extend_sys_path(*paths): | |||||||
|         sys.path = _orig_sys_path |         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 | @contextmanager | ||||||
| def captured_output(stream_name): | def captured_output(stream_name): | ||||||
|     """Return a context manager used by captured_stdout/stdin/stderr |     """Return a context manager used by captured_stdout/stdin/stderr | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | from django.apps import apps | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.test import SimpleTestCase, override_settings | from django.test import SimpleTestCase, override_settings | ||||||
|  | from django.test.utils import isolate_lru_cache | ||||||
| from django.utils import six | from django.utils import six | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -219,6 +221,7 @@ class FieldDeconstructionTests(SimpleTestCase): | |||||||
|  |  | ||||||
|     @override_settings(AUTH_USER_MODEL="auth.Permission") |     @override_settings(AUTH_USER_MODEL="auth.Permission") | ||||||
|     def test_foreign_key_swapped(self): |     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; |             # It doesn't matter that we swapped out user for permission; | ||||||
|             # there's no validation. We just want to check the setting stuff works. |             # there's no validation. We just want to check the setting stuff works. | ||||||
|             field = models.ForeignKey("auth.Permission", models.CASCADE) |             field = models.ForeignKey("auth.Permission", models.CASCADE) | ||||||
| @@ -297,6 +300,7 @@ class FieldDeconstructionTests(SimpleTestCase): | |||||||
|  |  | ||||||
|     @override_settings(AUTH_USER_MODEL="auth.Permission") |     @override_settings(AUTH_USER_MODEL="auth.Permission") | ||||||
|     def test_many_to_many_field_swapped(self): |     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; |             # It doesn't matter that we swapped out user for permission; | ||||||
|             # there's no validation. We just want to check the setting stuff works. |             # there's no validation. We just want to check the setting stuff works. | ||||||
|             field = models.ManyToManyField("auth.Permission") |             field = models.ManyToManyField("auth.Permission") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user