Model object accessible via old class after class changed raises TypeError
This Django application demonstrates a bug in django-polymorphic.
The application djangoPolymorphicTestcase has three model classes:
Base, apolymorphic.models.PolymorphicModelVariantA, which inherits fromBaseVariantB, which inherits fromBase
The test creates a VariantA object with primary key of DUPLICATE.
It then creates a VariantB object with a primary key of DUPLICATE.
As a result we do not have two objects, but a single VariantB object.
Accessing it via VariantA.objects() is possible, but throws TypeError.
Test Result
# 2017-04-25T15:17+02:00 $home/src/djangoPolymorphicTestcase ~? master(9daca92) = github.com/master
; PYTHONPATH=/usr/lib/python3/dist-packages/ python3 ./manage.py test
Creating test database for alias 'default'...
FE
======================================================================
ERROR: test_duplicate_demonstration (djangoPolymorphicTestcase.tests.tests.BaseTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/nils/src/djangoPolymorphicTestcase/djangoPolymorphicTestcase/tests/tests.py", line 19, in test_duplicate_demonstration
bug_here = VariantA.objects.first()
File "/usr/lib/python3/dist-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/usr/lib/python3/dist-packages/django/db/models/query.py", line 556, in first
objects = list((self if self.ordered else self.order_by('pk'))[:1])
File "/usr/lib/python3/dist-packages/django/db/models/query.py", line 256, in __iter__
self._fetch_all()
File "/usr/lib/python3/dist-packages/django/db/models/query.py", line 1087, in _fetch_all
self._result_cache = list(self.iterator())
File "/usr/lib/python3/dist-packages/polymorphic/query.py", line 445, in iterator
real_results = self._get_real_instances(base_result_objects)
File "/usr/lib/python3/dist-packages/polymorphic/query.py", line 328, in _get_real_instances
real_concrete_class = base_object.get_real_instance_class()
File "/usr/lib/python3/dist-packages/polymorphic/models.py", line 117, in get_real_instance_class
and not issubclass(model, self.__class__._meta.proxy_for_model):
TypeError: issubclass() arg 2 must be a class or tuple of classes
======================================================================
FAIL: test_duplicate_count (djangoPolymorphicTestcase.tests.tests.BaseTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/nils/src/djangoPolymorphicTestcase/djangoPolymorphicTestcase/tests/tests.py", line 15, in test_duplicate_count
self.assertEqual(VariantA.objects.count(), 0) # Doesn't trigger bug.
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 2 tests in 0.012s
FAILED (failures=1, errors=1)
Destroying test database for alias 'default'...
# exited 1
It seems to me that the behaviour of upcasting results with a polymorphic_ctype_id that does not match the expected self_model_class_id was intentionally introduced in commit 58c4f6f69760a1cb1b12d4c9ec77bf0f832c1653. I have no idea why it was added, though.
Also getting the same issue, but happened when running a migration against production...yikes!
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/app/.heroku/python/lib/python2.7/site-packages/polymorphic/query.py", line 458, in __repr__
return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs)
File "/app/.heroku/python/lib/python2.7/site-packages/django/db/models/query.py", line 116, in __repr__
data = list(self[:REPR_OUTPUT_SIZE + 1])
File "/app/.heroku/python/lib/python2.7/site-packages/django/db/models/query.py", line 141, in __iter__
self._fetch_all()
File "/app/.heroku/python/lib/python2.7/site-packages/django/db/models/query.py", line 966, in _fetch_all
self._result_cache = list(self.iterator())
File "/app/.heroku/python/lib/python2.7/site-packages/polymorphic/query.py", line 445, in iterator
real_results = self._get_real_instances(base_result_objects)
File "/app/.heroku/python/lib/python2.7/site-packages/polymorphic/query.py", line 328, in _get_real_instances
real_concrete_class = base_object.get_real_instance_class()
File "/app/.heroku/python/lib/python2.7/site-packages/polymorphic/models.py", line 98, in get_real_instance_class
and not issubclass(model, self.__class__._meta.proxy_for_model):
TypeError: issubclass() arg 2 must be a class or tuple of classes
This happened even after updating the polymorphic_ctype as @nmoskopp referenced.
new_ct = ContentType.objects.get_for_model(MyModel)
MyModel.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)
I've faced with the same exception when I was trying to open admin page for sub-model. After about 1 hour of mindgames I figured out what is the root cause.
My migration was applied incorrectly: parent and sub-entities had same value for the "polymorphic_ctype" and, as result, type of all the sub-entities was recognized as type of parent. That happened because of I copy-pasted migration from here.
The solution is to change forwards_func source code to handle existing parent-child inheritance relationships as follows:
from django.db import migrations, models
from django.db.models import Q
def forwards_func(apps, schema_editor):
models = apps.get_app_config('projectid').get_models()
content_type = apps.get_model('contenttypes', 'ContentType')
for model in models:
new_ct = content_type.objects.get_for_model(model)
objects = model.objects.filter(
Q(polymorphic_ctype__isnull=True)
| ~Q(polymorphic_ctype=new_ct))
objects.update(polymorphic_ctype=new_ct)
class Migration(migrations.Migration):
.... and etc.
My env:
Django==2.0.4
django-polymorphic==2.0.3
django-rest-polymorphic==0.1.7