15

我使用 Django 1.7 迁移,特别是想用初始数据填充新创建的数据库。因此,我为此使用了数据迁移。它看起来像这样:

def populate_with_initial_data(apps, schema_editor):
    User = apps.get_model("auth", "User")
    new_user = User.objects.create(username="nobody")

class Migration(migrations.Migration):

    ...

    operations = [
        migrations.RunPython(populate_with_initial_data),
    ]

同时,我想UserDetails为每个新用户创建一个模型实例:

@receiver(signals.post_save, sender=django.contrib.auth.models.User)
def add_user_details(sender, instance, created, **kwargs):
    if created:
        my_app.UserDetails.objects.create(user=instance)

但是:此信号仅在迁移之外有效。原因是这与没有发送信号有apps.get_model("auth", "User")很大不同。django.contrib.auth.models.User如果我尝试像这样手动执行此操作,则会失败:

signals.post_save.send(django.contrib.auth.models.User, instance=julia, created=True)

这失败了,因为那时,信号处理程序尝试使用 O2O创建一个指向历史的 指针:UserDetails User

ValueError: Cannot assign "<User: User object>": "UserDetails.user" must be a "User" instance.

真可惜。

好的,我可以直接调用信号处理程序。但是我必须在关键字参数中传递历史UserDetails类(以及它需要的其他历史类)。此外,具有 的应用程序UserDetails不是具有此数据迁移的应用程序,因此这将是一个丑陋的依赖关系,很容易破坏,例如,如果UserDetails应用程序从INSTALLED_APPS.

那么,这仅仅是我必须用丑陋的代码和 FixMe 注释来解决的当前限制吗?或者有没有办法从数据迁移中发送信号?

4

2 回答 2

11

您不能(也不应该)这样做,因为执行迁移时,您UserDetails可能与编写此迁移时真正不同。这就是为什么 django(和南)使用与您编写迁移时相同的“冻结模型”。

“不幸的是”,您必须在迁移中冻结信号代码,以保持编写迁移时预期的行为

一个简单的例子来理解为什么在迁移中不使用真实模型(或信号等)很重要:

今天,我可以有这个:

class UserDetails(models.Model):
    user = models.ForeignKey(...)
    typo_fild = models.CharField(...)

@receiver(signals.post_save, sender=django.contrib.auth.models.User)
def add_user_details(sender, instance, created, **kwargs):
    if created:
        UserDetails.objects.create(user=instance, typo_fild='yo')

然后,我有一个创建新用户的数据迁移(称为“populate_users”),我强制在其中执行add_user_details。没关系:今天有效。

明天,我修复我的typo_fild-> typo_fieldinsideUserDetails和 inside add_user_details。创建一个新的模式迁移以重命名数据库中的字段。

此时,我的迁移“populate_users”将失败,因为当创建新用户时,它将尝试UserDetails使用数据库中尚不存在的字段“typo_field”创建一个新用户:该字段只会在具有下一次迁移的数据库。

所以,如果我想保持一个可以随时工作的良好迁移,我必须复制add_user_details迁移内部的行为。这种冻结add_user_details将不得不使用冻结的模型UserDetailsapps.get_model("myapp", "UserDetails")并创建一个新UserDetailstypo_fild也被冻结的模型。

于 2015-10-27T11:36:14.130 回答
0

另一种选择是像往常一样在populate_with_initial_data函数中简单地导入模型:

def populate_with_initial_data(apps, schema_editor):
    from django.auth.models import User
    new_user = User.objects.create(username="nobody")

这有一个显着的缺点,即如果您稍后尝试运行迁移,迁移可能会中断,并且此后数据库模式发生了重大变化。在您提到的情况下,这似乎不太可能,但必须根据具体情况进行评估。

我们有几个模型,其中发生了很多事情save(),我们认为非冻结进口是最好的选择。在实践中,这不是一个大问题(对我们来说),为了进一步缓解这个问题,我们会定期修剪所有的迁移。

于 2021-12-07T10:07:13.903 回答