djangodjango-signalsdjango-simple-history

Circumventing F expression problem of django-simple-history by overriding save


Django-simple-history is inserting new record on each save of target model. In docs the problem with F expressions is described. I try to circumvent this with overriden save method.

   def save(self, *args, **kwargs):
       super().save(*args, **kwargs)
       # some other actions
       self.refresh_from_db()

But it seems, that this is not working. Is the post_save signal of base model called directly after super().save() call? If so, is there a way to solve this problem keeping the F expression in target model update?

UPDATE: Saved instance has one of its attributes defined using an F expression, so this code is called in some other module:

   instance.some_attribute = (F('some_attribute') + 15)
   instance.save(update_fields=['some_attribute'])

This throws an error in django-simple-history's post_save signal, when it tries to insert a extended copy of instance to history table. I tried to refresh the instance in overriden save method to get rid of the F expression in some_attribute so the actual value is loaded. From the traceback it seems that the post_save is called right after super().save() call, before the refresh. Is it the way Django post_save with overriden save works? If so, is there a way to not change the update code (leave the update with F expression) and solve the history insert in model's save?


Solution

  • django-simple-history provides signals for before and after the historical record is created: https://django-simple-history.readthedocs.io/en/2.7.0/signals.html

    I suggest using these to update the instance before it gets saved to the historical table. Something like this should work:

    from django.dispatch import receiver
    from simple_history.signals import (
        pre_create_historical_record,
        post_create_historical_record
    )
    
    @receiver(pre_create_historical_record)
    def pre_create_historical_record_callback(sender, **kwargs):
        instance = kwargs["instance"]
        history_instance = kwargs["history_instance"]
            if isinstance(instance, ModelYouWantToRefresh)
        instance.refresh_from_db()
        history_instance.some_attribute = instance.some_attribute