Это правильный способ обработки изменений поля Many2Many.
Django Docs
from django.db.models.signals import m2m_changed
m2m_changed.connect(category_changed,sender=Brand.categories.through)
РЕДАКТИРОВАТЬ: Я не понял, что Chiefir запрашивает сигнал для QuerySet update метод, для которого мы не можем использовать встроенный сигнал m2m_changed. Как указывает Ральф в предыдущем ответе, запуск сигнала об обновлении может вызвать проблемы с производительностью, например, в случае прослушивания сохраненного сквозного файла. Тем не менее, есть решение, которое imho не несет проблем с производительностью.
По сути, вы можете переопределить метод обновления набора запросов, чтобы запускать пользовательский сигнал при каждом обновлении всего набора запросов. Это будет срабатывать только один раз для каждого обновления набора запросов, и вы можете указать, что оно будет запускаться только для одного конкретного обновления поля, проверив аргументы обновления.
Теперь сигнал отправляет обновляемый набор запросов и значение в качестве полезной нагрузки. Помните, что в этом коде сигнал отправляется до обновления категории. Чтобы изменить это, сохраните супер выход как переменную, отправьте сигнал и верните переменную.
from django.dispatch import Signal, receiver
product_category_updated = Signal(providing_args=["queryset", "value"])
class ProductQuerySet(models.QuerySet):
def update(self, *args, **kwargs):
if 'category' in kwargs:
product_category_updated.send(sender=self.__class__, queryset=self, value=kwargs.get('category'))
return super(ProductQuerySet, self).update(*args, **kwargs)
class ProductManager(models.Manager):
def get_queryset(self, show_hidden=False):
return ProductQuerySet(self.model, using=self._db, hints=self._hints)
class Product(TimeStampedModel):
objects = ProductManager()
category = models.ForeignKey('Category', related_name='products', to_field='category_name', on_delete=models.deletion.CASCADE)
brand = models.ForeignKey('Brand', related_name='products', to_field='brand_name', on_delete=models.deletion.CASCADE)
class Brand(models.Model):
brand_name = models.CharField(max_length=50, unique=True)
categories = models.ManyToManyField('Category', related_name='categories')
class Category(models.Model):
category_name = models.CharField(max_length=128, unique=True)
@receiver(product_category_updated, sender=ProductQuerySet)
def category_changed(sender, **kwargs):
print("Signal connected!")
Мне пришлось настроить код для моей версии Django 2.0.7, добавив атрибуты on_delete в поля ForeignKey и сделав уникальные brand_name и category_name.