This is the correct way to handle Many2Many field changes
Django Docs
from django.db.models.signals import m2m_changed
m2m_changed.connect(category_changed,sender=Brand.categories.through)
EDIT: I did not realize Chiefir is asking for a signal on QuerySet update method, for which we cannot use the inbuild m2m_changed signal. As Ralf in previous answer points out, triggering a signal on update may cause performance issues, for example in case of listening to the through being saved. However, there is a solution that imho does not carry performance problems.
Essentially, you can override the queryset update method to trigger a custom signal every time a whole queryset is updated. This will be triggered only once per queryset update and you can specify it to only be triggered on one particular field update by checking the update arguments.
The signal now sends the queryset being updated and the value as a payload. Be aware that in this code, the signal is being sent before the category updates. To change that, save the super output as a variable, dispatch the signal and return the variable.
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!")
I had to adjust the code to my Django 2.0.7 version by adding on_delete attributes on ForeignKey fields and making the brand_name and category_name unique.