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

Tor*_*ger 11 django data-migration django-signals django-migrations

我使用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),
    ]
Run Code Online (Sandbox Code Playgroud)

同时,我希望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)
Run Code Online (Sandbox Code Playgroud)

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

signals.post_save.send(django.contrib.auth.models.User, instance=julia, created=True)
Run Code Online (Sandbox Code Playgroud)

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

ValueError: Cannot assign "<User: User object>": "UserDetails.user" must be a "User" instance.
Run Code Online (Sandbox Code Playgroud)

游民.

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

那么,这只是一个当前的限制,我必须解决丑陋的代码和FixMe评论?或者有没有办法从数据迁移中发送信号?

Dyl*_*del 6

您不能(也不应该)这样做,因为在执行迁移时,您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')
Run Code Online (Sandbox Code Playgroud)

然后,我有一个数据迁移(称为"populate_users"),它创建新用户,并强制执行add_user_details它.没关系:它今天有效.

明天,我修复了我的typo_fild- > typo_field内部UserDetails和内部add_user_details.创建新的模式迁移以重命名数据库中的字段.

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

所以,如果我希望保持良好的迁移能够随时工作,我必须复制add_user_details迁移内部的行为.这一冻结add_user_details将不得不使用冷冻模型UserDetails通过apps.get_model("myapp", "UserDetails")创建一个新的UserDetailstypo_fild它太冻结.

  • 如果您愿意,您可以随时用脚射击.毕竟,其他信号*被发送,我现在用它们来解决这个问题.如果对模型应用有问题的更改,则可以在信号处理程序中使用内省."使常见案例变得简单,并且罕见的情况可能发生." (2认同)