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

5

我正在使用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?也就是说,当我删除一个账户时,我希望在这个表中的相关记录也被删除。


当我删除一个账户时,我希望该表中的相关记录也被删除。-- 当您删除账户对象时,这应该已经是默认行为了。 - Brian Destura
ON-DELETE CASCADE在多对多关系中听起来并不合乎逻辑。例如,有4个司机和3辆汽车。一辆汽车可以由多个司机驾驶,所以如果您删除一个司机,则不应该从数据库中删除汽车,因为其他司机仍然可以驾驶该特定汽车。 - Neeraj
2个回答

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)

0

当您使用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中修改迁移。我还没有创建您的迁移文件,因为我没有完整的模型,但您可以在此处查看如何执行此操作:如何使用Django中的迁移和数据向现有ManyToManyField添加through选项

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


谢谢,虽然我不明白链接的哪个部分适用于我的答案。你能把那个答案中相关的部分剪切粘贴到你的帖子里吗? - Dave
第三步,在 @grain 的答案中,展示了你应该如何修改迁移文件,将其包装在 SeparateDatabaseAndState 中。 - TaipanRex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接