Ответ
Для отслеживания изменений в ManyToManyField используется специальный сигнал m2m_changed. Стандартные сигналы, такие как post_save, не срабатывают для M2M-отношений, поскольку они управляются через отдельную промежуточную таблицу, а не прямым изменением поля в основной модели.
Сигнал m2m_changed отправляется до и после добавления, удаления или очистки связей.
Пример:
Предположим, у нас есть модели Post и Tag со связью ManyToManyField.
# models.py
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=50)
class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag, related_name='posts')
Чтобы отследить добавление тега к посту, нужно подключиться к сигналу m2m_changed, указав в sender промежуточную модель (Post.tags.through).
# signals.py
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from .models import Post
@receiver(m2m_changed, sender=Post.tags.through)
def tags_changed_handler(sender, instance, action, pk_set, **kwargs):
"""
Обработчик сигнала изменения M2M-связи для тегов поста.
"""
print(f"Действие: {action} для поста '{instance.title}'")
if action == "post_add":
# Действие после добавления связей
added_tags = instance.tags.filter(pk__in=pk_set)
print(f"Добавлены теги: {[tag.name for tag in added_tags]}")
elif action == "post_remove":
# Действие после удаления связей
print(f"Удалены связи с тегами (ID): {pk_set}")
elif action == "post_clear":
# Действие после полной очистки связей
print("Все теги были удалены.")
Ключевые аргументы обработчика:
sender: Промежуточная модель, управляющая связью (Post.tags.through).instance: Экземпляр модели, у которой было изменено M2M-поле (в нашем случае — экземплярPost).action: Строка, описывающая тип действия. Основные значения:'pre_add','post_add''pre_remove','post_remove''pre_clear','post_clear'
pk_set: Набор (set) первичных ключей объектов, которые были добавлены или удалены.
Ответ 18+ 🔞
А, слушай, смотри, тут такая хуйня интересная! Хочешь отследить, когда в ManyToManyField что-то меняется? Так вот, стандартный post_save тут нихуя не сработает, потому что это всё через отдельную таблицу промежуточную управляется, а не прямо в модели. Для этого есть специальный сигнал — m2m_changed, он стреляет до и после любых манипуляций со связями: добавления, удаления, полной зачистки.
Пример, чтобы было понятнее:
Допустим, у нас есть модель Post (пост) и Tag (тег), связанные через ManyToManyField.
# models.py
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=50)
class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag, related_name='posts')
Теперь, чтобы поймать момент, когда к посту прикручивают новый тег, нужно подписаться на сигнал m2m_changed, указав в sender именно промежуточную модель (Post.tags.through).
# signals.py
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from .models import Post
@receiver(m2m_changed, sender=Post.tags.through)
def tags_changed_handler(sender, instance, action, pk_set, **kwargs):
"""
Обработчик сигнала изменения M2M-связи для тегов поста.
"""
print(f"Действие: {action} для поста '{instance.title}'")
if action == "post_add":
# Действие после добавления связей
added_tags = instance.tags.filter(pk__in=pk_set)
print(f"Добавлены теги: {[tag.name for tag in added_tags]}")
elif action == "post_remove":
# Действие после удаления связей
print(f"Удалены связи с тегами (ID): {pk_set}")
elif action == "post_clear":
# Действие после полной очистки связей
print("Все теги были удалены.")
Аргументы, которые прилетают в обработчик, просто пиздец какие полезные:
sender: Промежуточная модель, которая всем этим безобразием управляет (Post.tags.through).instance: Сам объект, у которого поле M2M меняли (в нашем случае — конкретный постPost).action: Строка, которая кричит, что именно произошло. Основные варианты:'pre_add','post_add'— перед и после добавления.'pre_remove','post_remove'— перед и после удаления.'pre_clear','post_clear'— перед и после полной очистки.
pk_set: Этоsetс первичными ключами объектов, которые либо добавили, либо выпилили. Овердохуища важная штука!