Как создать миграцию Django для отношения ManyToMany, включающую каскад удаления?

Я использую 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? То есть, когда я удаляю учетную запись, я хочу, чтобы сопутствующие записи в этой таблице также были удалены.

Когда вы используете ManyToManyField, Django создает для вас промежуточную таблицу, в данном случае с именем cbapp_account_crypto_currencies. Что вы хотите сделать в будущем, так это всегда явно создавать промежуточную модель, AccountCryptoCurrencies, а затем устанавливать атрибут through в ManyToManyField. Это позволит вам в будущем добавлять больше полей в промежуточную модель. См. подробнее здесь: 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. Я не создал ваш файл миграции, потому что у меня нет полной модели, но вы можете посмотреть здесь, как это сделать: Как добавить сквозную опцию к существующему ManyToManyField с миграциями и данными в django

Теперь вы можете применить миграцию, и теперь у вас должна быть явная промежуточная таблица без потери данных. Теперь вы также можете добавлять дополнительные поля в промежуточную таблицу и изменять существующие поля. Вы можете добавить on_delete=models.CASCADE к полю account и мигрировать изменение.

Посмотрел на это внимательнее. Я попытался воспроизвести ваши модели, и я также вижу, что промежуточная таблица не имеет каскада. У меня нет ответа на ваш главный вопрос о том, как добавить каскад, но кажется, что 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'
    }
]

Я не могу найти в документации, почему это делается именно так.

Вернуться на верх