I have some Django Polymorphic models:
import uuid
from django.db import models
from polymorphic.models import PolymorphicModel
class Fruit(PolymorphicModel):
class Meta:
abstract = True
class Apple(Fruit):
variety=models.CharField(max_length=30,primary_key=True)
class Grape(Fruit):
id=models.UUIDField(primary_key=True, default=uuid.uuid4)
colour=models.CharField(max_length=30)
Then I can create some fixtures:
[
{"model": "test_polymorphic.apple", "pk": "bramley", "fields": {}},
{"model": "test_polymorphic.apple", "pk": "granny smith", "fields": {}},
{"model": "test_polymorphic.grape", "pk": "00000000-0000-4000-8000-000000000000", "fields": { "colour": "red"} },
{"model": "test_polymorphic.grape", "pk": "00000000-0000-4000-8000-000000000001", "fields": { "colour": "green"} }
]
and use python -m django loaddata fixture_name
to load it into the database which "appears" to be successful.
Then if I use:
from test_polymorphic import models
models.Apple.objects.all()
It raises the error:
PolymorphicTypeUndefined: The model Apple#bramley does not have a `polymorphic_ctype_id` value defined. If you created models outside polymorphic, e.g. through an import or migration, make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass.
Using loaddata
bypasses the save()
method of the model so the default content-types are not set on the models.
I could find the appropriate content types using:
from django.contrib.contenttypes.models import ContentType
for model_name in ("apple", "grape"):
print(
model_name,
ContentType.objects.get(app_label="test_polymorphic", model=model_name).id,
)
Which outputs:
apple: 3 grape: 2
and then change the fixture to:
[
{
"model": "test_polymorphic.apple",
"pk": "bramley",
"fields": { "polymorphic_ctype_id": 3 }
},
{
"model": "test_polymorphic.apple",
"pk": "granny smith",
"fields": { "polymorphic_ctype_id": 3 }
},
{
"model": "test_polymorphic.grape",
"pk": "00000000-0000-4000-8000-000000000000",
"fields": { "colour": "red", "polymorphic_ctype_id": 2 }
},
{
"model": "test_polymorphic.grape",
"pk": "00000000-0000-4000-8000-000000000001",
"fields": { "colour": "green", "polymorphic_ctype_id": 2 }
}
]
Including the polymorphic content type in the fields and then the fixture works. However, this value is effectively a magic number and I am not convinced that it will not vary if:
Can I rely on the polymorphic_ctype_id
to be a fixed value and, if not, how should I set the default polymorphic content type on the model when loading fixtures (if using loaddata
bypasses the save
method of the model and I cannot be certain what the id values of the content types would be)?
The Django Polymorphic - "Migrating existing models to polymorphic" documentation shows how to migrate non-polymorphic models to polymorphic models:
The following Python code can be used to fill the value of a model:
from django.contrib.contenttypes.models import ContentType from myapp.models import MyModel new_ct = ContentType.objects.get_for_model(MyModel) MyModel.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)
They give an example of how to include this in a migration but it can also be run manually after using loaddata
to load a fixture where the values are not set.
If there are multiple models that need updating then you can write a helper function:
from django.contrib.contenttypes.models import ContentType
def set_polymorphic_ctype(model):
app_label = model._meta.app_label
name = model._meta.model_name
ctype = ContentType.objects.get(app_label=app_label, model=name)
return model.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=ctype)
and call it for each of the models:
set_polymorphic_ctype(models.Apple)
set_polymorphic_ctype(models.Grape)
If you are loading fixtures in a TestCase
then I didn't find a callback method that would run after the fixtures are loaded but you can call the helper function during test set-up.
class FruitTestCase(TestCase):
fixtures = [ "fixture_name" ]
def setUp(self):
set_polymorphic_ctype(models.Apple)
set_polymorphic_ctype(models.Grape)
def test_something(self):
...