4

I want to update m2m field depend on the its parent model for example:

class ModelA(models.Model):
    status = models.BooleanField(default=True)
    status_of_product = models.ManyToManyField(Product, verbose_name='product')

class Product(models.Model):
    active = models.BooleanField(default=False)
    products = models.CharField(max_length=30, unique=True)

Now this makes a separate table named modela_status_of_product.

I want to update active field in Product only when if status field in ModelA equal to True.

def update_product_m2m(sender, instance, **kwargs):
    if instance.status == True: #this is wrong and doesnt work 
        Product.objects.filter(pk__in=kwargs.get('pk_set')).update(active=True)

m2m_changed.connect(update_product_m2m, sender=ModelA.status_of_product.through)

Is it possible please? Thanks for helping.

Uri
  • 2,992
  • 8
  • 43
  • 86
art_cs
  • 683
  • 1
  • 8
  • 17

2 Answers2

2
def update_product_m2m(sender, **kwargs):
    instance = kwargs.pop('instance', None)
    if instance and instance.status == True:
        instance.status_of_product.update(active=True)

m2m_changed.connect(update_product_m2m, sender=ModelA.status_of_product.through)

or:

class ModelA(models.Model):
 ...
   def save(self):
       if self.pk and self.status:
          self.status_of_product.update(active=True)
       super().save()
rgermain
  • 708
  • 4
  • 11
  • thank you for replying , but i think it doesnt work , because i want to update Product model and m2m table has not instance.status – art_cs Aug 02 '20 at 19:48
  • try second solution, and print the type of instance in singals, it is normally an instance of ModelA, and sender Prodcut. – rgermain Aug 02 '20 at 19:53
  • doesnt work , product is a M2M field we have to think about something to contain pk__in=pk_set – art_cs Aug 03 '20 at 05:36
1

The listener will be called in different ways depending on how you actually use your M2M relation. You have to check for it in your listener. As is, your listener is called pre and post update, pre and post delete, and pre and post clear. The version I'm presenting is only doing stuff post update, but it knows how to handle forward and reverse uses of the relation.

It might not be enough, but I think it's a good starting point. You will definitely want to treat "post_delete" and "post_clear" differently depending on your business logic (maybe a Product needs to become inactive after it loses his last ModelA whose status was True)

Read the full documentation of the m2m_changed signal for more information.

def update_product_m2m(sender, instance, action, reverse, pk_set, **kwargs):
    if action != "post_add":
        return

    if reverse:  # product.modela_set.add(modela)
        # instance is the product being modified
        # pk_set are the modelas being added
        if ModelA.objects.filter(pk__in=pk_set, status=True).exists():
            instance.active=True
            instance.save()

    else:  # modela.status_of_product.add(product)
        # instance is the modela being modified.
        # pk_set are the product ids being added
        if instance.status is True:
            Product.objects.filter(pk__in=pk_set).update(active=True)

In any case, instance is never the "through table". It is either an instance of ModelA or an instance of Product depending on how the m2m was updated.

Source: https://docs.djangoproject.com/en/3.1/ref/signals/#m2m-changed

If this is not enough to solve your problem, please post the traceback.

Olivier Roux
  • 145
  • 5