Going through historical changes with django-simple-history
I have been using django-simple-history for one of my project just to track the changes between edits. This library provides an easy way to revisit the historical changes by storing the Django model state between every create/update/delete operation. It also provides some really handy utilities out of the box. One such utility is history diffing.
When we have two instances of the same HistoricalRecord
, we can perform diffs to see what has changed.
The document uses the example of a simple Polls application and creates two records. It then tries to check the delta change between those records -
>>> p = Poll.objects.create(question="What's up?")
>>> p.question = "What's Cooking?"
>>> p.save()
>>> new_record, old_record = p.history.all()
>>> delta = new_record.diff_against(old_record)
>>> for change in delta.changes():
>>> print("{} changed from {} to {}".format(change.field, change.old, change.new))
This is straight forward. But in a real world scenario, we will have more than two records. This is where we will start seeing ValueError
exception because there will be more than two values to unpack -
>>> new_record, old_record = p.history.all()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack
In this article, we will go beyond to check the changes for a record that has been updated more than twice. One way to do it is by efficiently pairing the records like below -
from itertools import tee
def pair_iterable_for_delta_changes(iterable):
a, b = tee(iterable)
next(b, None)
return zip(a,b)
def check_delta_for_poll(poll):
poll_iterator = poll.history.all().order_by('history_date').iterator()
for record_pair in pair_iterable_for_delta_changes(poll_iterator):
old_record, new_record = record_pair
delta = new_record.diff_against(old_record)
for change in delta.changes():
print("{} changed from {} to {}".format(change.field, change.old, change.new))
Here we have used itertools.tee
function to pair the records. The pairing will work like this -
s = tee(iter([1,2,3,4,...])) = [s(1, 2), s(2, 3), s(3, 4), ...]
We can now use the same diff_against
on the record pairs to check the diff between new_record
and old_record
.
