如何为包含 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
,然后设置ManyToManyField
的through
属性。这将允许您将来向中间模型添加更多字段。在此处查看更多信息: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 迁移?的主要内容,如果未能解决你的问题,请参考以下文章