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()
Конечно, я не эксперт, не уверен, что это лучший подход и что он действительно затратен по времени, поэтому я открыт для предложений. Но на данный момент это единственное решение, которое я нашел, чтобы выполнить эту операцию атомарно.