如何从 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
。没关系:今天可以使用。
明天,我在UserDetails
和add_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 应用程序并将数据从一个应用程序迁移到另一个应用程序