尽管@rockallite的答案很好,但它没有解释如何处理依赖于自然键而不是整数pk
值的固定装置。
简化版本
首先,请注意@rockallite的解决方案可以通过使用unittest.mock.patch
作为上下文管理器并补丁apps
而简化,而不是补丁_get_model
:
...
from unittest.mock import patch
...
def load_fixture(apps, schema_editor):
with patch('django.core.serializers.python.apps', apps):
call_command('loaddata', 'your_data.json', ...)
...
只要你的固定装置不依赖于自然键,这个方法就很有效。
如果它们依赖于自然键,你可能会看到一个DeserializationError: ... value must be an integer...
。
自然键的问题
在幕后, loaddata
使用django.core.serializers.deserialize()
来加载你的固定装置对象。
基于自然键的固定装置的反序列化依赖于两个因素:
- 模型的默认管理器上存在get_by_natural_key()方法
- 模型本身上存在natural_key()方法
get_by_natural_key()
方法对于反序列化程序来说是必要的,以便知道如何解释自然键,而不是整数pk
值。
这两种方法都是必要的,以便反序列化程序可以通过自然键从数据库中获取
现有对象,正如此处所解释的那样。
然而,在您的迁移中可用的apps
注册表使用历史模型,这些模型无法访问自定义管理器或自定义方法,例如natural_key()
。
可能的解决方案:步骤1
我们自定义的模型管理器缺少get_by_natural_key()
方法的问题相对容易解决:
只需在自定义管理器上设置use_in_migrations=True
,如文档所述。
这样可以确保您的历史模型在迁移期间可以访问当前的get_by_natural_key()
,并且夹具加载现在应该成功了。
但是,您的历史模型仍然没有natural_key()
方法。因此,即使它们已经存在于数据库中,您的夹具也将被视为新对象。
如果数据迁移再次应用,这可能会导致各种错误,例如:
- 唯一约束冲突(如果您的模型具有唯一约束)
- 重复的夹具对象(如果您的模型没有唯一约束)
- “获取多个对象”错误(由于先前创建的重复夹具对象)
因此,在反序列化期间,您仍然错过了一种类似于get_or_create的行为。
要体验这个过程,只需按照上述描述在测试环境中应用数据迁移,然后回滚相同的数据迁移(不删除数据),然后重新应用数据迁移。
可能的解决方案:第二步
模型本身缺少natural_key()
方法的问题有点难以解决。
一种解决方案是将当前模型的natural_key()
方法分配给历史模型,例如:
...
from unittest.mock import patch
from django.apps import apps as current_apps
from django.core.management import call_command
...
def load_fixture(apps, schema_editor):
def _get_model_patch(app_label):
""" add natural_key method from current model to historical model """
historical_model = apps.get_model(app_label=app_label)
current_model = current_apps.get_model(app_label=app_label)
historical_model.natural_key = current_model.natural_key
return historical_model
with patch('django.core.serializers.python._get_model', _get_model_patch):
call_command('loaddata', 'your_data.json', ...)
...
注:
- 为了清晰起见,我在示例中省略了错误处理和属性检查等内容。您应该在必要时实现这些内容。
- 此解决方案使用当前模型的
natural_key
方法,在某些情况下仍可能导致问题,但对于Django的模型管理器use_in_migrations
选项也是如此。