Эффективно удалить осиротевшие объекты / теги m2m в Django

У меня есть две модели - Photo и Tag - которые связаны через ManyToManyField.

class Photo(models.Model):
    tags = models.ManyToManyField(Tag)

class Tag(models.Model):
    lang = models.CharField(max_length=2)
    name_es = models.CharField(max_length=40)
    name_en = models.CharField(max_length=40)

Время от времени мы получаем потерянные теги, на которые больше не ссылается ни одна фотография. Есть ли эффективный способ удаления этих тегов? Я знаю об этом ответе: Django: удалить M2M-записи?

И наше решение выглядит следующим образом:

for tag in Tag.objects.all():
    if not tag.photo_set.select_related(): tag.delete()

Тем не менее, с увеличением базы данных время выполнения этого скрипта становится крайне высоким :-P Есть ли эффективный способ получить список всех идентификаторов тегов из таблицы тегов, а затем список всех идентификаторов тегов из «многие ко многим»? таблица для создания списка пересечений?

Ответы на вопрос(3)

Решение Вопроса

Попробуйте подзапрос с промежуточной таблицей

qs = Tag.objects.exclude(pk__in=Book.tags.through.objects.values('tag'))

# then you could
qs.delete()

# or if you need to trigger signal per item
for x in qs:
    x.delete()
 Simon Steinberger12 сент. 2012 г., 10:58
Я запускаю его только один раз в пару месяцев для нашего проекта Pixabay.com ... он все еще довольно трудоемкий, но это нормально.
 okm16 мая 2012 г., 14:38
@ Nasmon, пожалуйста. Рад, что помогает =)
 Simon Steinberger16 мая 2012 г., 06:20
Красивое решение - работает как шарм! Благодарность
 dialex02 сент. 2012 г., 21:22
@ Nasmon, но когда и где ты запускаешь этот код?

поэтому я немного изменил решение okm:

    all_tag_pks = Tag.objects.values_list('pk', flat=True)
    used_tag_pks = Photo.tags.through.objects.values_list('tag', flat=True)
    Tag.objects.filter(pk__in=list(set(all_tag_pks) - set(used_tag_pks))).delete()

Благодаря этому запрос к базе данных становится намного меньше и быстрее.

в режиме реального времени":

from django.db.models.signals import m2m_changed
from django.dispatch import receiver

class Photo(models.Model):
    tags = models.ManyToManyField(Tag)

class Tag(models.Model):
    lang = models.CharField(max_length=2)

@receiver(m2m_changed, sender=Photo.tags.through)
def delete_orphean_dateranges(sender, **kwargs):
    # when something is removed from the m2m:
    if kwargs['action'] == 'post_remove':  
        Tag.objects.filter(pk__in=kwargs['pk_set'], photo_set=None).delete()
        # select removed tags and check if they are not linked
        # to any Photo, and delete it

Таким образом, каждый раз, когда вы редактируете m2m фото при удалении тега из m2m, эта функция вызывается.

 Simon Steinberger09 дек. 2018 г., 20:16
Спасибо за ваше предложение. Я не пробовал это. Но это будет работать только в том случае, если редактирование будет происходить редко, иначе это приведет к огромному снижению производительности (как в нашем случае).
 Simon Steinberger14 дек. 2018 г., 08:52
Это только для нашего конкретного случая использования. У нас есть сотни таких изменений каждую секунду ... Но это хороший способ сделать систему простой и управляемой самостоятельно.
 Guillaume Lebreton10 дек. 2018 г., 09:56
Hum конечно, в операции удаления дела на m2m есть SQL-запрос, который выдается, но этот запрос не кажется мне особенно сложным. Он запускается только при удалении, поэтому, очевидно, он медленнее, но я не буду называть его огромным, это зависит от того, что вы ожидаете от своей базы данных.

Ваш ответ на вопрос