2
\$\begingroup\$

I'm working on a Django project for which I'll need to store the historic information of many models.

So far, I'm using the following strategy:

  1. Memoize the relevant values in __init__.
  2. When saving, check if a relevant value has changed.
  3. If a relevant value has changed, save memoized values into a "history" model.

My code goes as follows:

from django.db import models
from core.models import User

class Spam(models.Model):
    user= models.OneToOneField(User, on_delete=models.PROTECT, related_name='spam')
    ham = models.CharField(max_length=10)
    eggs = models.IntegerField()

    def __init__(self, *args, **kwargs):
        super(Spam, self).__init__(*args, **kwargs)
        self.__relevant_fields = ['ham', 'eggs']
        self._memoize()

   def _memoize(self):
       for f in self.__relevant_fields:
           setattr(self, '__prev_%s' % f, getattr(self, f))

   def _get_prev_value(self, f):
       return getattr(self, '__prev_%s' % f)

   def _dirty_field(self, f) -> bool:
       return getattr(self, '__prev_%s' % f) != getattr(self, f)

   def _dirty(self) -> bool:
       return any([self._dirty_field(f) for f in self.__relevant_fields])

   def _save_history(self):
       history = dict()
       for f in self.__relevant_fields:
           history[f] = self._get_prev_value(f)
       self.user.spam_history.create(**history)

   def save(self, *args, **kwargs):
       super(Spam, self).save(*args, **kwargs)
       if self._dirty():
           self._save_history()

class SpamHistory(models.Model):
    user = models.ForeignKey(User, on_delete=models.PROTECT, related_name='spam_history')
    ham = models.CharField(max_length=10)
    eggs = models.IntegerField()

Although this approach works, there are some things that make me uncomfortable:

  1. I need to do this for many models, so I'm repeating myself a lot! I'm looking into inheritance, mixins or the like, but so far I haven't found a right way to make it work.
  2. Is this the best approach to achieve what I want? Considering that I will need to fetch historic records frequently, I need the "history" model for each model I need to keep history from.

So, my specific questions are:

  • Am I implementing a good strategy for keeping the history of my model(s)?
  • Is there a way to improve this strategy by using an inheritance or mixin strategy?
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Third-party tools might exist that will do this for you. \$\endgroup\$
    – FMc
    Aug 27, 2020 at 2:00
  • \$\begingroup\$ @FMc I'll search for them (if you know of one, could you share it?) \$\endgroup\$
    – Barranka
    Aug 27, 2020 at 2:02
  • \$\begingroup\$ @Fmc I've found django-model-changes-py3; however, it seems that this just saves the current state plus the two previous states of the model instance... I'd like to keep the full history. \$\endgroup\$
    – Barranka
    Aug 27, 2020 at 2:06
  • 1
    \$\begingroup\$ @FMc Ook! I found what I need: django-simple-history! Right now I'm feeling a bit dumb I didn't found this earlier. \$\endgroup\$
    – Barranka
    Aug 27, 2020 at 2:13
  • 1
    \$\begingroup\$ @Graipher indeed. Correcting now \$\endgroup\$
    – Barranka
    Aug 29, 2020 at 13:47

1 Answer 1

1
\$\begingroup\$

Ok, after googling a bit more, I found a possible solution: django-simple-history:

from django.db import models
from core.models import User
from simple_history.models import HistoricalRecords

class Spam(models.Model):
    user = models.OneToOneField(User, on_delete=models.PROTECT, related_name='spam')
    ham = models.CharField(max_length=10)
    eggs = models.IntegerField()
    history = HistoricalRecords()

This seems to do exactly what I need to do. I'll test it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.