Avoid post_init duplicate queries

HI. It’s very easy to track and manage changes of a Model instance using the post_init signal in Django.

As shown in the code below, I want to achieve the following behavior:

Whenever a product’s category changes, all associated ProductSpecs objects should be removed.
BTW there isn’t a many-to-many relationship between Product and ProductSpecs.

@receiver(post_init , sender=Product)
def set_old_category(sender, instance, **kwargs):
    instance.old_category = instance.category


@receiver(post_save, sender=Product)
def reference_product_signal(sender, instance, created, **kwargs):
    if instance.category != instance.old_category:
        ProductSpecs.objects.filter(product=instance, key__category=instance.old_category).delete()

The provided code do its job, but it generates duplicate queries each time it see a product on a page.

My question is: How can I avoid these duplicate queries?

Don’t use signals for this.

For tracking a change to a model, create a from_db method in your model to create the copy of the existing values.

On handling the removal of the (unrelated) ProductSpecs, do this in the save method of the model. However, regardless of how you do the delete, you’re going to do an extra query - that’s by design, and it’s nothing to worry about.

1 Like

AMAZING!!!
Thank you very much @KenWhitesell
its very smooth… the working code

    @classmethod
    def from_db(cls, db, field_names, values):
        if len(values) != len(cls._meta.concrete_fields):
            values = list(values)
            values.reverse()
            values = [
                values.pop() if f.attname in field_names else DEFERRED
                for f in cls._meta.concrete_fields
            ]
        instance = cls(*values)
        instance._state.adding = False
        instance._state.db = db
        instance._loaded_values = dict(
            zip(field_names, (value for value in values if value is not DEFERRED))
        )
        return instance
    
    def save(self, *args, **kwargs):
        if not self._state.adding and (
            self.category_id != self._loaded_values["category_id"]
        ):
            ProductSpecs.objects.filter(product=self, key__category__id=self._loaded_values["category_id"]).delete()
        super(Product, self).save(*args, **kwargs)