Overview of django-simple-history
mastercreate, update, and delete operation. It provides a mechanism to track changes over time, allowing you to audit model data and view historical versions of your records.repository·master·Indexed 25 days ago
https://github.com/django-commons/django-simple-historyA Django application that tracks and stores the state of Django models during create, update, and delete operations to provide a full audit trail. It includes integration with the Django admin site for viewing and reverting model versions, utilities for bulk creation and updates with history, and customizable historical model configurations including custom field types, table names, and indexing options.
create, update, and delete operation. It provides a mechanism to track changes over time, allowing you to audit model data and view historical versions of your records.To track which user is responsible for changes to a model, django-simple-history provides four primary methods:
HistoryRequestMiddleware: Automatically sets the history_user on the history table to the User instance making the current request.SimpleHistoryAdmin: When using the Django admin, this class automatically sets the _history_user on the object by overriding save_model._history_user attribute: Manually assign a user to the _history_user attribute on your model instance.history_user_id for scenarios where the user model lives in a different database or is accessed via an external API.Standard Django foreign keys point to the current state of a related object. If you are traversing history using as_of, you want the related object to also reflect the state it had at that specific point in time.
Use HistoricForeignKey or HistoricOneToOneField to ensure that chasing relationships from an as_of acquired instance honors the historical point in time for both forward and reverse relationships.
You can track history on models using multi-table inheritance.
Key Behaviors:
HistoricalRecords is not inherited. You must explicitly add it to the parent, the child, or both if you want to track both.class ParentModel(models.Model):
parent_field = models.CharField(max_length=255)
history = HistoricalRecords()
class ChildModel(ParentModel):
child_field = models.CharField(max_length=255)
# history is NOT inherited automaticallyThe output of diff_against() varies significantly based on the foreign_keys_are_objs flag:
False): Returns the raw primary key (e.g., 'poll' changed from '15' to '31').True): Returns the model instance (e.g., 'poll' changed from '<Poll: what's up?>' to '<Poll: still around?>').False): Returns a list of dictionaries containing through-model field names and primary keys (e.g., [{'poll': 15, 'category': 63}]).True): Returns a list of dictionaries where the values are the actual model instances (e.g., [{'poll': <Poll: what's up?>, 'category': <Category: informal questions>}]).If an object is deleted, foreign_keys_are_objs=True will represent it as a DeletedObject (e.g., DeletedObject(model=<class 'models.Category'>, pk=63)).
When you enable history tracking, django-simple-history creates a new table for each tracked model, prefixed with historical. These tables store a snapshot of the model's fields plus the following metadata fields:
history_user: The user who performed the change.history_date: The datetime of the change.history_change_reason: The reason for the change (defaults to null).history_id: The primary key for the historical record (note that the base model's PK is not unique in this table).history_type: The type of operation: + for create, ~ for update, and - for delete.history_user using an explicit ID instead of a model instance. Refer to the 'Manually Track User Model' documentation for implementation details.To programmatically revert a model instance to a state captured in a historical record, retrieve the historical object and call .save() on its .instance attribute. This updates the live model with the historical data and creates a new historical record representing this reversion.
# Revert to the earliest version
>>> earliest_poll = poll.history.earliest()
>>> earliest_poll.instance.save()You can access historical records through the HistoricalRecords object.
instance.history to access a manager for that specific object's history (e.g., poll.history.all()).Model.history to access a manager for all historical records across all instances of that model (e.g., Choice.history.all()).Because the history is a model, you can use standard Django QuerySet methods like .filter() on the history manager.
By default, many-to-many (M2M) fields are ignored by history tracking. To track changes in M2M relationships, you must explicitly list them in the m2m_fields argument of HistoricalRecords.
This creates a historical intermediate model that tracks relational changes. You can pass either the field name (string) or the field instance itself.
class Category(models.Model):
name = models.CharField(max_length=200)
class Poll(models.Model):
question = models.CharField(max_length=200)
categories = models.ManyToManyField(Category)
history = HistoricalRecords(m2m_fields=[categories])django-simple-history provides signals that allow you to execute custom logic whenever a historical record is created or updated. You can connect your callbacks using the standard Django @receiver decorator.
When a historical record for a single model instance is being processed, the following arguments are passed to the signal receiver:
instance: The source model instance being saved.history_instance: The corresponding history record.history_date: Datetime of the history record's creation.history_change_reason: Freetext description of the reason for the change.history_user: The user that instigated the change.using: The database alias being used.For signals related to Many-to-Many fields, the following additional arguments are available:
instance: The source model instance being saved.history_instance: The corresponding history record.rows (available in pre_create_historical_m2m_records): The elements to be bulk inserted into the m2m table.created_rows (available in post_create_historical_m2m_records): The created elements into the m2m table.field: The recorded field object.from django.dispatch import receiver
from simple_history.signals import (
pre_create_historical_record,
post_create_historical_record,
pre_create_historical_m2m_records,
post_create_historical_m2m_records,
)
@receiver(pre_create_historical_record)
def pre_create_historical_record_callback(sender, **kwargs):
print("Sent before saving historical record")
@receiver(post_create_historical_record)
def post_create_historical_record_callback(sender, **kwargs):
print("Sent after saving historical record")
@receiver(pre_create_historical_m2m_records)
def pre_create_historical_m2m_records_callback(sender, **kwargs):
print("Sent before saving many to many field on historical record")
@receiver(post_create_historical_m2m_records)
def post_create_historical_m2m_records_callback(sender, **kwargs):
print("Sent after saving many to many field on historical record")Install the package using pip:
$ pip install django-simple-history