如何为包含 on-delete 级联的 ManyToMany 关系创建 Django 迁移?

Posted

技术标签:

【中文标题】如何为包含 on-delete 级联的 ManyToMany 关系创建 Django 迁移?【英文标题】:How do I create a Django migration for my ManyToMany relation that includes an on-delete cascade? 【发布时间】:2021-12-31 04:30:33 【问题描述】:

我正在使用 PostGres 10、Python 3.9 和 Django 3.2。我已经建立了这个模型,伴随着多对多的关系......

class Account(models.Model):    
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    ...
    crypto_currencies = models.ManyToManyField(CryptoCurrency)

生成并运行 Django 迁移后,创建了下表 ...

\d cbapp_account_crypto_currencies;
                               Table "public.cbapp_account_crypto_currencies"
      Column       |  Type   |                                  Modifiers                                   
-------------------+---------+------------------------------------------------------------------------------
 id                | integer | not null default nextval('cbapp_account_crypto_currencies_id_seq'::regclass)
 account_id        | uuid    | not null
 cryptocurrency_id | uuid    | not null
Indexes:
    "cbapp_account_crypto_currencies_pkey" PRIMARY KEY, btree (id)
    "cbapp_account_crypto_cur_account_id_cryptocurrenc_38c41c43_uniq" UNIQUE CONSTRAINT, btree (account_id, cryptocurrency_id)
    "cbapp_account_crypto_currencies_account_id_611c9b45" btree (account_id)
    "cbapp_account_crypto_currencies_cryptocurrency_id_685fb811" btree (cryptocurrency_id)
Foreign-key constraints:
    "cbapp_account_crypto_account_id_611c9b45_fk_cbapp_acc" FOREIGN KEY (account_id) REFERENCES cbapp_account(id) DEFERRABLE INITIALLY DEFERRED
    "cbapp_account_crypto_cryptocurrency_id_685fb811_fk_cbapp_cry" FOREIGN KEY (cryptocurrency_id) REFERENCES cbapp_cryptocurrency(id) DEFERRABLE INITIALLY DEFERRED

如何更改我的字段关系或生成迁移,以使级联关系为 ON-DELETE CASCADE?也就是说,当我删除一个帐户时,我希望该表中的伴随记录也被删除。

【问题讨论】:

When I delete an account, I would like accompanying records in this table to also be deleted. -- 这应该是您删除帐户对象时的行为。 ON-DELETE CASCADE 对于多对多关系听起来不合逻辑。例如有4个司机和3辆汽车。一辆车可以由多个司机驾驶,因此如果您删除一个司机,则不应从数据库中删除该车,因为其他司机仍然可以驾驶该特定车。 【参考方案1】:

对此进行了仔细研究。我试图复制你的模型,我还看到中间表没有级联。我没有回答你关于如何添加级联的主要问题,但似乎 django 的级联行为已经支持这一点:

当我删除一个帐户时,我希望该表中的伴随记录也被删除。

演示:

a = Account.objects.create(name='test')
c1 = CryptoCurrency.objects.create(name='c1')
c2 = CryptoCurrency.objects.create(name='c2')
c3 = CryptoCurrency.objects.create(name='c3')
a.crypto_currencies.set([c1, c2, c3])

如果你这样做:

a.delete()

Django 运行以下 SQL 模拟中间表上的级联:

[
    
        'sql': 'DELETE FROM "myapp_account_crypto_currencies" WHERE "myapp_account_crypto_currencies"."account_id" IN (3)', 'time': '0.002'
    , 
    
        'sql': 'DELETE FROM "myapp_account" WHERE "myapp_account"."id" IN (3)', 'time': '0.001'
    
]

我在文档中找不到为什么这样做。即使添加这样的自定义中介也会导致相同的行为:

class Account(models.Model):
    name = models.CharField(max_length=100)
    crypto_currencies = models.ManyToManyField(CryptoCurrency, through='myapp.AccountCryptocurrencies')


class AccountCryptocurrencies(models.Model):
    account = models.ForeignKey(Account, on_delete=models.CASCADE)
    cryptocurrency = models.ForeignKey(CryptoCurrency, on_delete=models.CASCADE)

【讨论】:

【参考方案2】:

当您使用ManyToManyField 时,Django 会为您创建一个中间表,在本例中名为cbapp_account_crypto_currencies。您将来要做的是始终显式创建中间模型AccountCryptoCurrencies,然后设置ManyToManyFieldthrough 属性。这将允许您将来向中间模型添加更多字段。在此处查看更多信息:https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ManyToManyField.through。

您现在需要做的是创建这个中间表:

class AccountCryptoCurrencies(models.Model):
    account = models.ForeignKey(Account)
    cryptocurrency = models.ForeignKey(CryptoCurrency)

    class Meta:
        db_table = 'cbapp_account_crypto_currencies'

class Account(models.Model):    
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    ...
    crypto_currencies = models.ManyToManyField(CryptoCurrency, through=AccountCryptoCurrencies)

您现在需要生成迁移,但不要应用它!通过将迁移包装在 SeparateDatabaseAndState 中来修改迁移。我没有创建您的迁移文件,因为我没有完整的模型,但您可以在此处查看如何操作:How to add through option to existing ManyToManyField with migrations and data in django

现在您可以应用迁移,并且您现在应该有一个明确的中间表而不会丢失数据。您现在还可以向中间表添加其他字段并更改现有字段。您可以将on_delete=models.CASCADE 添加到account 字段并迁移更改。

【讨论】:

谢谢,尽管我不明白链接的哪一部分适用于我的答案。您可以仅将该答案的相关部分剪切并粘贴到您的帖子中吗? 第 3 步。在@grain 的回答中,展示了如何通过将迁移文件包装在SeparateDatabaseAndState 中来修改迁移文件。

以上是关于如何为包含 on-delete 级联的 ManyToMany 关系创建 Django 迁移?的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis + mysql 实现两级级联的查询

Haar 级联的图像大小考虑

excel下拉级联 ,excel下拉级联的做法

多个级联的Postgresql顺序?

使用 LBP 训练 Haar 级联的问题

如何在两个外键上设置 Hibernate @ManyToMany 与级联的关联?