Сигнал m2m_changed
Сигнал m2m_changed отправляется при изменении связи
ManyToManyField. Он позволяет выполнять дополнительные
действия до или после добавления, удаления или очистки
связанных объектов. Сигнал принимает несколько параметров:
отправитель, экземпляр модели, действие, обратный набор
моделей, идентификаторы объектов и флаг инициализации.
Параметры сигнала
Сигнал m2m_changed передает следующие аргументы
в функцию-обработчик:
⁅b⁆sender⁅/b⁆ - класс модели, которая содержит ManyToManyField.
⁅b⁆instance⁅/b⁆ - экземпляр модели, для которого изменяется связь.
⁅b⁆action⁅/b⁆ - строка, указывающая тип операции:
pre_add, post_add, pre_remove,
post_remove, pre_clear или
post_clear.
⁅b⁆reverse⁅/b⁆ - булево значение, указывающее, является ли операция обратной (через связанную модель).
⁅b⁆model⁅/b⁆ - класс модели, которая добавляется или удаляется.
⁅b⁆pk_set⁅/b⁆ - множество идентификаторов объектов, которые участвуют в операции.
⁅b⁆using⁅/b⁆ - псевдоним базы данных, на которой выполняется операция.
Синтаксис
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
@receiver(m2m_changed, sender=Model.m2m_field.through)
def handler(sender, instance, action, reverse, model, pk_set, using, **kwargs):
# код обработчика
pass
Важно указывать sender как промежуточную модель
через атрибут through поля ManyToManyField.
Пример
Создадим модели Article и Tag со связью ManyToMany, и добавим обработчик, который выводит информацию о добавлении тегов:
from django.db import models
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
class Tag(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
@receiver(m2m_changed, sender=Article.tags.through)
def article_tags_changed(sender, instance, action, reverse, model, pk_set, using, **kwargs):
if action == 'post_add' and pk_set:
tag_names = Tag.objects.filter(pk__in=pk_set).values_list('name', flat=True)
print(f"Added tags to article '{instance.title}': {list(tag_names)}")
Теперь при добавлении тегов к статье сработает сигнал и выведет информацию:
from myapp.models import Article, Tag
article = Article.objects.create(title="Django signals")
tag1 = Tag.objects.create(name="django")
tag2 = Tag.objects.create(name="python")
article.tags.add(tag1, tag2)
Результат выполнения кода:
"Added tags to article 'Django signals': ['django', 'python']"
Пример
Используем сигнал для предотвращения удаления последнего тега у статьи. Если у статьи остается только один тег, запретим его удаление:
from django.db import models
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from django.core.exceptions import ValidationError
class Tag(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
@receiver(m2m_changed, sender=Article.tags.through)
def prevent_last_tag_removal(sender, instance, action, reverse, model, pk_set, using, **kwargs):
if action == 'pre_remove' and pk_set:
current_tags = instance.tags.count()
removing_count = len(pk_set)
if current_tags - removing_count < 1:
raise ValidationError("Article must have at least one tag")
При попытке удалить последний тег будет вызвана ошибка:
from myapp.models import Article, Tag
article = Article.objects.create(title="My article")
tag = Tag.objects.create(name="important")
article.tags.add(tag)
# Попытка удалить единственный тег
article.tags.remove(tag)
Результат выполнения кода:
"ValidationError: Article must have at least one tag"
Пример
Обработаем событие очистки всех тегов у статьи
с помощью действия pre_clear:
from django.db import models
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
class Tag(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
@receiver(m2m_changed, sender=Article.tags.through)
def log_tag_clear(sender, instance, action, reverse, model, pk_set, using, **kwargs):
if action == 'pre_clear':
tag_count = instance.tags.count()
print(f"Clearing all {tag_count} tags from article '{instance.title}'")
elif action == 'post_clear':
print(f"All tags cleared from article '{instance.title}'")
Теперь при очистке тегов сработает сигнал и выведет информацию:
from myapp.models import Article, Tag
article = Article.objects.create(title="Test article")
article.tags.add(Tag.objects.create(name="tag1"))
article.tags.add(Tag.objects.create(name="tag2"))
article.tags.clear()
Результат выполнения кода:
"Clearing all 2 tags from article 'Test article'"
"All tags cleared from article 'Test article'"
Смотрите также
-
сигнал
pre_save,
который отправляется перед сохранением модели -
сигнал
post_save,
который отправляется после сохранения модели -
сигнал
pre_delete,
который отправляется перед удалением модели -
сигнал
post_delete,
который отправляется после удаления модели