Django Polymorphic Models手动生成Fixture:如何处理动态ctype_id?
问题背景
我用Django Polymorphic写了几个模型,手动编了JSON格式的Fixture,用python -m django loaddata fixture_name加载后看起来没问题,但查询数据时直接抛出PolymorphicTypeUndefined错误。查了原因是loaddata绕开了模型的save()方法,没自动设置polymorphic_ctype_id。手动给这个字段加ID能解决,但这个ID在不同环境里可能变——比如换个环境装了其他应用、新增了模型,ID就不一样了。现在想问两个问题:
- 能不能指望
polymorphic_ctype_id永远不变? - 如果不能,加载Fixture时该怎么正确设置这个字段?
模型代码
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)
初始Fixture示例
[ {"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"} } ]
报错信息
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.
解答
1. 绝对不能依赖polymorphic_ctype_id固定
polymorphic_ctype_id关联的是Django内置的ContentType模型的ID,这个ID是全局唯一但完全不固定的:
- 不同环境里,应用加载顺序变了、新增/删除了模型、甚至装了其他第三方应用,都会改变
ContentType的生成顺序,导致ID不一样。 - 硬编码ID的话,换个环境直接就失效,触发类型不匹配的错误。
2. 正确设置字段的两种方法
方法一:用ContentType自然键修改Fixture
Django的ContentType支持自然键(格式是[<app标签>, <模型名>]),加载Fixture时会自动把自然键转换成对应的ID。你只需要给每个Fixture条目加polymorphic_ctype字段,用自然键代替硬编码ID就行:
修改后的Fixture:
[ {"model": "test_polymorphic.apple", "pk": "bramley", "fields": { "polymorphic_ctype": ["test_polymorphic", "apple"] }}, {"model": "test_polymorphic.apple", "pk": "granny smith", "fields": { "polymorphic_ctype": ["test_polymorphic", "apple"] }}, {"model": "test_polymorphic.grape", "pk": "00000000-0000-4000-8000-000000000000", "fields": { "colour": "red", "polymorphic_ctype": ["test_polymorphic", "grape"] } }, {"model": "test_polymorphic.grape", "pk": "00000000-0000-4000-8000-000000000001", "fields": { "colour": "green", "polymorphic_ctype": ["test_polymorphic", "grape"] } } ]
改完之后直接用loaddata加载就行,Django会自动处理自然键到ID的转换,不用额外配置。
方法二:自定义加载命令(复杂场景用)
如果Fixture数据量很大,或者需要做一些动态处理,可以写个自定义的Django管理命令,手动处理polymorphic_ctype的设置,并且调用模型的save()方法(让Polymorphic的逻辑自动生效)。
示例命令代码:
import json from django.core.management.base import BaseCommand from django.contrib.contenttypes.models import ContentType from test_polymorphic.models import Apple, Grape class Command(BaseCommand): help = '正确加载Polymorphic模型的Fixture' def add_arguments(self, parser): parser.add_argument('fixture_file', type=str, help='Fixture文件路径') def handle(self, *args, **options): with open(options['fixture_file'], 'r', encoding='utf-8') as f: fixture_data = json.load(f) for item in fixture_data: app_label, model_name = item['model'].split('.') # 获取对应模型的ContentType ct = ContentType.objects.get(app_label=app_label, model=model_name.lower()) if model_name == 'apple': Apple.objects.create( pk=item['pk'], polymorphic_ctype=ct ) elif model_name == 'grape': Grape.objects.create( pk=item['pk'], colour=item['fields']['colour'], polymorphic_ctype=ct ) self.stdout.write(self.style.SUCCESS('Fixture加载成功!'))
把这个文件放到你应用的management/commands目录下(需要先建这两个文件夹,再加__init__.py),然后用下面的命令加载:
python manage.py load_polymorphic_fixture fixture_name.json
内容的提问来源于stack exchange,提问作者MT0

