Handle stale ContentType models (no longer referring to a model)

This builds on top of a fix in Django 1.6, and has a workaround for
Django 1.4 and 1.5. When the base class points to a model that no longer
exists, it will be silently dropped in the polymorphic queryset results.

This behavior is identical to iterating over results when the derived
table doesn't have the object anymore.
fix_request_path_info
Diederik van der Boor 2013-05-19 14:35:29 +02:00
parent 8527244cb3
commit 8cf313335c
2 changed files with 25 additions and 8 deletions

View File

@ -94,20 +94,34 @@ class PolymorphicModel(models.Model):
return super(PolymorphicModel, self).save(*args, **kwargs)
def get_real_instance_class(self):
"""Normally not needed.
"""
Normally not needed.
If a non-polymorphic manager (like base_objects) has been used to
retrieve objects, then the real class/type of these objects may be
determined using this method."""
determined using this method.
"""
# the following line would be the easiest way to do this, but it produces sql queries
#return self.polymorphic_ctype.model_class()
# so we use the following version, which uses the CopntentType manager cache
return ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class()
# return self.polymorphic_ctype.model_class()
# so we use the following version, which uses the CopntentType manager cache.
# Note that model_class() can return None for stale content types;
# when the content type record still exists but no longer refers to an existing model.
try:
return ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class()
except AttributeError:
# Django <1.6 workaround
return None
def get_real_concrete_instance_class_id(self):
return ContentType.objects.get_for_model(self.get_real_instance_class(), for_concrete_model=True).pk
model_class = self.get_real_instance_class()
if model_class is None:
return None
return ContentType.objects.get_for_model(model_class, for_concrete_model=True).pk
def get_real_concrete_instance_class(self):
return ContentType.objects.get_for_model(self.get_real_instance_class(), for_concrete_model=True).model_class()
model_class = self.get_real_instance_class()
if model_class is None:
return None
return ContentType.objects.get_for_model(model_class, for_concrete_model=True).model_class()
def get_real_instance(self):
"""Normally not needed.

View File

@ -169,7 +169,10 @@ class PolymorphicQuerySet(QuerySet):
real_concrete_class = base_object.get_real_instance_class()
real_concrete_class_id = base_object.get_real_concrete_instance_class_id()
if real_concrete_class_id == self_concrete_model_class_id:
if real_concrete_class_id is None:
# Dealing with a stale content type
continue
elif real_concrete_class_id == self_concrete_model_class_id:
# Real and base classes share the same concrete ancestor,
# upcast it and put it in the results
results[base_object.pk] = transmogrify(real_concrete_class, base_object)