Django удаляет экземпляр m2m, когда больше нет отношений

В случае, если у нас была модель:

class Publication(models.Model):
    title = models.CharField(max_length=30)

class Article(models.Model):
    publications = models.ManyToManyField(Publication)

Согласно: https://docs.djangoproject.com/en/4.0/topics/db/examples/many_to_many/, чтобы создать объект, мы должны сохранить оба объекта, прежде чем мы сможем создать отношение:

p1 = Publication(title='The Python Journal')
p1.save()
a1 = Article(headline='Django lets you build web apps easily')
a1.save()
a1.publications.add(p1)

Теперь, если бы мы вызвали delete в любом из этих объектов, объект был бы удален из БД вместе с отношением между обоими объектами. До этого момента я понимаю

Но есть ли способ сделать так, чтобы если удаляется Article, то все Publications, которые не связаны ни с одной Article, тоже удалялись из БД? Или единственный способ добиться этого - сначала запросить все статьи, а затем перебрать их следующим образом:

to_delete = []
qset = a1.publications.all()
for publication in qset:
    if publication.article_set.count() == 1:
        to_delete(publication.id)
a1.delete()
Publications.filter(id__in=to_delete).delete()

Но это имеет много проблем, особенно с параллелизмом, так как может оказаться, что publication используется другим article между вызовом .count() и publication.delete().

Есть ли способ сделать это автоматически, например, сделать "условный" on_delete=models.CASCADE при создании модели или что-то в этом роде?

Спасибо!

Вы можете удалить связанные объекты с помощью этого запроса:

a1.publications.annotate(article_count=Count('article_set')).filter(article_count=1).delete()

annotate создает временное поле для кверисета (псевдополе), которое агрегирует количество связанных Article объектов для каждого экземпляра в кверисете Publication объектов, используя функцию Count. Count - это встроенная в любой SQL функция агрегирования, которая возвращает количество строк из запроса (в данном случае количество связанных экземпляров). Затем мы отфильтровываем те результаты, где article_count равен 1, и удаляем их.

Я пробовал с @Ersain ответ:

a1.publications.annotate(article_count=Count('article_set')).filter(article_count=1).delete()

Не смог заставить его работать. Во-первых, я не смог найти переменную article_set в отношениях.

django.core.exceptions.FieldError: Невозможно преобразовать ключевое слово 'article_set' в поле. Варианты: article, id, title

.

А затем, запустив фильтр count на QuerySet после фильтрации по статье, вернул ВСЕ теги из статьи, а не только те, которые содержат article_count=1. И наконец, вот код, с помощью которого мне удалось заставить его работать:

Publication.objects.annotate(article_count=Count('article')).filter(article_count=1).filter(article=a1).delete()

Конечно, я не эксперт, не уверен, что это лучший подход и что он действительно затратен по времени, поэтому я открыт для предложений. Но на данный момент это единственное решение, которое я нашел, чтобы выполнить эту операцию атомарно.

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