From a89c153709ff3f7f3b0080a854dfb28fec764714 Mon Sep 17 00:00:00 2001 From: Nicolas Forstner Date: Sun, 20 Apr 2025 14:08:21 +0100 Subject: [PATCH 1/2] Check for None on real_model in get_real_instance --- polymorphic/models.py | 5 ++++- polymorphic/tests/test_orm.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/polymorphic/models.py b/polymorphic/models.py index 4d8f6937..ba6e5a4f 100644 --- a/polymorphic/models.py +++ b/polymorphic/models.py @@ -157,13 +157,16 @@ def get_real_instance(self): retrieve objects, then the complete object with it's real class/type and all fields may be retrieved with this method. + If the model of the object's actual type does not exist (e.g. it was + removed but its ContentType still exists), this method returns self. + .. note:: Each method call executes one db query (if necessary). Use the :meth:`~polymorphic.managers.PolymorphicQuerySet.get_real_instances` to upcast a complete list in a single efficient query. """ real_model = self.get_real_instance_class() - if real_model == self.__class__: + if real_model == self.__class__ or real_model is None: return self return real_model.objects.db_manager(self._state.db).get(pk=self.pk) diff --git a/polymorphic/tests/test_orm.py b/polymorphic/tests/test_orm.py index 1f2de936..0ca07086 100644 --- a/polymorphic/tests/test_orm.py +++ b/polymorphic/tests/test_orm.py @@ -321,6 +321,13 @@ def test_manual_get_real_instance(self): o = Model2A.objects.non_polymorphic().get(field1="C1") assert o.get_real_instance().__class__ == Model2C + def test_get_real_instance_with_no_model_class(self): + ctype = ContentType.objects.create(app_label="tests", model="nonexisting") + o = Model2A.objects.create(field1="A1", polymorphic_ctype=ctype) + + assert o.get_real_instance_class() is None + assert o.get_real_instance().__class__ == Model2A + def test_non_polymorphic(self): self.create_model2abcd() From d4456bdb3b197fa720ef9af3765fbaebaaef5393 Mon Sep 17 00:00:00 2001 From: Nicolas Forstner Date: Thu, 1 May 2025 11:37:21 +0100 Subject: [PATCH 2/2] Raise PolymorphicTypeInvalid when model is None --- polymorphic/models.py | 12 +++++++++--- polymorphic/tests/test_orm.py | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/polymorphic/models.py b/polymorphic/models.py index ba6e5a4f..845209cb 100644 --- a/polymorphic/models.py +++ b/polymorphic/models.py @@ -157,8 +157,9 @@ def get_real_instance(self): retrieve objects, then the complete object with it's real class/type and all fields may be retrieved with this method. - If the model of the object's actual type does not exist (e.g. it was - removed but its ContentType still exists), this method returns self. + If the model of the object's actual type does not exist (i.e. its + ContentType is stale), this method raises a + :class:`~polymorphic.models.PolymorphicTypeInvalid` exception. .. note:: Each method call executes one db query (if necessary). @@ -166,8 +167,13 @@ def get_real_instance(self): to upcast a complete list in a single efficient query. """ real_model = self.get_real_instance_class() - if real_model == self.__class__ or real_model is None: + if real_model == self.__class__: return self + if real_model is None: + raise PolymorphicTypeInvalid( + f"ContentType {self.polymorphic_ctype_id} for {self.__class__} " + f"#{self.pk} does not have a corresponding model!" + ) return real_model.objects.db_manager(self._state.db).get(pk=self.pk) def __init__(self, *args, **kwargs): diff --git a/polymorphic/tests/test_orm.py b/polymorphic/tests/test_orm.py index 0ca07086..5e655552 100644 --- a/polymorphic/tests/test_orm.py +++ b/polymorphic/tests/test_orm.py @@ -321,12 +321,14 @@ def test_manual_get_real_instance(self): o = Model2A.objects.non_polymorphic().get(field1="C1") assert o.get_real_instance().__class__ == Model2C - def test_get_real_instance_with_no_model_class(self): - ctype = ContentType.objects.create(app_label="tests", model="nonexisting") + def test_get_real_instance_with_stale_content_type(self): + ctype = ContentType.objects.create(app_label="tests", model="stale") o = Model2A.objects.create(field1="A1", polymorphic_ctype=ctype) assert o.get_real_instance_class() is None - assert o.get_real_instance().__class__ == Model2A + match = "does not have a corresponding model" + with pytest.raises(PolymorphicTypeInvalid, match=match): + o.get_real_instance() def test_non_polymorphic(self): self.create_model2abcd()