Skip to content

Commit

Permalink
Fixed #35407 -- Cached model's Options.swapped.
Browse files Browse the repository at this point in the history
  • Loading branch information
adamchainz committed Apr 29, 2024
1 parent 85c154d commit 665cf5e
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 2 deletions.
10 changes: 9 additions & 1 deletion django/db/models/options.py
Expand Up @@ -5,6 +5,7 @@
from django.apps import apps
from django.conf import settings
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.core.signals import setting_changed
from django.db import connections
from django.db.models import AutoField, Manager, OrderWrt, UniqueConstraint
from django.db.models.query_utils import PathInfo
Expand Down Expand Up @@ -230,6 +231,9 @@ def contribute_to_class(self, cls, name):
self.db_table, connection.ops.max_name_length()
)

if self.swappable:
setting_changed.connect(self.setting_changed)

def _format_names(self, objs):
"""App label/class name interpolation for object names."""
names = {"app_label": self.app_label.lower(), "class": self.model_name}
Expand Down Expand Up @@ -399,7 +403,7 @@ def verbose_name_raw(self):
with override(None):
return str(self.verbose_name)

@property
@cached_property
def swapped(self):
"""
Has this model been swapped out for another? If so, return the model
Expand Down Expand Up @@ -427,6 +431,10 @@ def swapped(self):
return swapped_for
return None

def setting_changed(self, *, setting, **kwargs):
if setting == self.swappable and "swapped" in self.__dict__:
del self.swapped

@cached_property
def managers(self):
managers = []
Expand Down
6 changes: 6 additions & 0 deletions tests/model_meta/models.py
Expand Up @@ -166,6 +166,12 @@ class Relating(models.Model):
people_hidden = models.ManyToManyField(Person, related_name="+")


# SwappedTests models
class Swappable(models.Model):
class Meta:
swappable = "MODEL_META_TESTS_SWAPPED"


# ParentListTests models
class CommonAncestor(models.Model):
pass
Expand Down
28 changes: 27 additions & 1 deletion tests/model_meta/tests.py
Expand Up @@ -3,7 +3,7 @@
from django.core.exceptions import FieldDoesNotExist
from django.db.models import CharField, Field, ForeignObjectRel, ManyToManyField
from django.db.models.options import EMPTY_RELATION_TREE, IMMUTABLE_WARNING
from django.test import SimpleTestCase
from django.test import SimpleTestCase, override_settings

from .models import (
AbstractPerson,
Expand All @@ -16,6 +16,7 @@
Relating,
Relation,
SecondParent,
Swappable,
)
from .results import TEST_RESULTS

Expand Down Expand Up @@ -233,6 +234,31 @@ def test_gettext(self):
self.assertEqual(Person._meta.verbose_name_raw, "Person")


class SwappedTests(SimpleTestCase):
def test_plain_model_none(self):
self.assertIsNone(Relation._meta.swapped)

def test_unset(self):
self.assertIsNone(Swappable._meta.swapped)

def test_set_and_unset(self):
with override_settings(MODEL_META_TESTS_SWAPPED="model_meta.Relation"):
self.assertEqual(Swappable._meta.swapped, "model_meta.Relation")
self.assertIsNone(Swappable._meta.swapped)

def test_setting_none(self):
with override_settings(MODEL_META_TESTS_SWAPPED=None):
self.assertIsNone(Swappable._meta.swapped)

def test_setting_non_label(self):
with override_settings(MODEL_META_TESTS_SWAPPED="not-a-label"):
self.assertEqual(Swappable._meta.swapped, "not-a-label")

def test_setting_self(self):
with override_settings(MODEL_META_TESTS_SWAPPED="model_meta.swappable"):
self.assertIsNone(Swappable._meta.swapped)


class RelationTreeTests(SimpleTestCase):
all_models = (Relation, AbstractPerson, BasePerson, Person, ProxyPerson, Relating)

Expand Down

0 comments on commit 665cf5e

Please sign in to comment.