如何从 Django 迁移中发送信号?

Posted

技术标签:

【中文标题】如何从 Django 迁移中发送信号?【英文标题】:How can I send signals from within Django migrations? 【发布时间】:2014-12-19 23:29: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)

这失败了,因为信号处理程序尝试创建一个新的 UserDetails 以 O2O 指向 历史 User

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

无赖。

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

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

【问题讨论】:

您找到解决方法了吗? 是的,使用signal.post_migrate 因为这个调用了。但它仍然需要不需要的代码。 您应该发布答案并接受您自己的答案,因为这个问题位于未回答的 django 问题的顶部。 【参考方案1】:

您不能(也不应该)这样做,因为在执行迁移时,您的 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。没关系:今天可以使用。

明天,我在UserDetailsadd_user_details 内部修复我的typo_fild -> typo_field。创建一个新的模式迁移以重命名数据库中的字段。

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

所以,如果我想保持一个可以随时工作的良好迁移,我必须在迁移中复制 add_user_details 的行为。 add_user_details 的冻结将不得不通过 apps.get_model("myapp", "UserDetails") 使用冻结模型 UserDetails 并使用同样被冻结的 typo_fild 创建一个新的 UserDetails

【讨论】:

如果你愿意,你可以随时用脚射击。毕竟,发送了其他信号,我现在用它们来解决这个问题。如果您对模型应用有问题的更改,您可以在信号处理程序中使用自省。 “让常见的情况变得简单,让罕见的情况成为可能。”【参考方案2】:

另一种选择是像往常一样在 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() 上发生了很多事情,并且我们认为非冻结进口是最好的选择。实际上,这不是一个大问题(对我们来说),为了进一步缓解这个问题,我们会定期修剪所有迁移。

【讨论】:

以上是关于如何从 Django 迁移中发送信号?的主要内容,如果未能解决你的问题,请参考以下文章

BOOST 如何在一个线程中发送信号并在另一个线程中执行相应的插槽?

Django:如何从 ManyToMany 迁移到 ForeignKey?

如何将模型从一个 django 应用程序迁移到一个新应用程序中?

如何重命名 Django 应用程序并将数据从一个应用程序迁移到另一个应用程序

如何在 Django 中使用 South 将数据从一个模型迁移到另一个模型?

如何将django部署从顶级目录迁移到子目录下(NGINX UWSGI DJANGO)