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?
asked Aug 27, 2020 at 0:18
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Third-party tools might exist that will do this for you. \$\endgroup\$ Commented Aug 27, 2020 at 2:00
  • \$\begingroup\$ @FMc I'll search for them (if you know of one, could you share it?) \$\endgroup\$ Commented 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\$ Commented 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\$ Commented Aug 27, 2020 at 2:13
  • 1
    \$\begingroup\$ @Graipher indeed. Correcting now \$\endgroup\$ Commented 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.

answered Aug 27, 2020 at 2:18
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.