Search code examples
djangohistorymodels

Storing the edit history of a django model in another custom model


I have two models lets say:

class superfields(Model):
    fieldA = models.FloatField()
    fieldB = models.FloatField()
    class Meta:
        abstract = True

class my_model( superfields ):
    def has_history( self ):
        return self.my_model_history_set.count() > 0

class my_model_history( superfields ):
    reason = models.TextField()
    mymodel = models.ForeignKey( my_model )

'my_model' is populated with data (under fieldA and fieldB). Whenever someone edits 'my_model's fields and saves, I don't want to save the change in this model but want to store it as a new row with all values in 'my_model_history', in addition to a 'reason' field while 'my_model' data stays the same.

What is the best way to approach this scenario in terms of custom templates, custom views, model admins etc etc. Am I doing it correctly?

To give my question above some sense, in my project, the nature of data under 'my_model' is market prices and I need to maintain a history of all the market prices ever edited with a 'reason' for the edit.


Solution

  • My Solution:

    yes. A simple and quick solution I am following is as follows: I create three models similar to this:

    class my_super_abstract_model(Model):
        #All fields I need to keep a history for:
        fieldA = models.FloatField()
        fieldB = models.FloatField()
        class Meta:
            abstract = True
    
    class my_model( my_super_abstract_model ):
        def has_history( self ):
            return self.my_model_history_set.count() > 0
    
    class my_model_history( my_super_abstract_model ):
        reason = models.TextField()
        history_entry_for = models.ForeignKey( my_model )
    

    I've setup a signal:

    pre_save.connect( create_history, 
                      sender = my_model_history )
    

    and 'create history' to be called by the pre_save() signal before saving in my_model_history:

    def create_history(sender, **kwargs):
        #get variables passed by the pre-save signal:
        history_model = kwargs['instance']
        # Get main model object
        main_model = history_model.history_entry_for
        # swap all common fields between history edit and main model (except id)  
        main_model_fields = [f.name for f in main_model._meta.fields]
        history_model_fields = [f.name for f in history_model._meta.fields]
        field_index = list( [f for f in history_model_fields if f in main_model_fields and f != 'id' and f != 'created_date' ] )
        #loop thru to swap values:
        for field_name in field_index:
            temp = getattr(main_model, field_name)
            setattr( main_model, field_name, getattr( history_model, field_name ) )
            setattr( history_model, field_name, temp)
        # After the swap, save main model object here 
        main_model.save()
    

    Whenever user clicks on a my_model row for editing, I use 'my_model_history' to generate my edit form and populate it with the values from the user selected row. (Have written a view and template to do that)

    So the edit form will now have:

    1. field A -populated with values from my_model data row
    2. field B -populated with values from my_model data row
    3. Reason -empty text box
    4. history_entry_for -hidden from view

    User can now edit fieldA/fieldB. Enter a reason. Press save to trigger the signal above. Before saving,

    1. Signal will swap the values between the main model(old values) and history model(New values)
    2. Replace and save the main model row (with the new values).
    3. Insert and save a new row in the history model (with the old values) with a reason.

    Hope it helps. Let me know if there are any further questions.