Advanced Usage

Database Migrations

By default, Historical models live in the same app as the model they track. Historical models are tracked by migrations in the same way as any other model. Whenever the original model changes, the historical model will change also.

Therefore tracking historical models with migrations should work automatically.

Locating past model instance

Two extra methods are provided for locating previous models instances on historical record model managers.

as_of

This method will return an instance of the model as it would have existed at the provided date and time.

>>> from datetime import datetime
>>> poll.history.as_of(datetime(2010, 10, 25, 18, 4, 0))
<Poll: Poll object as of 2010-10-25 18:03:29.855689>
>>> poll.history.as_of(datetime(2010, 10, 25, 18, 5, 0))
<Poll: Poll object as of 2010-10-25 18:04:13.814128>

most_recent

This method will return the most recent copy of the model available in the model history.

>>> from datetime import datetime
>>> poll.history.most_recent()
<Poll: Poll object as of 2010-10-25 18:04:13.814128>

History for a Third-Party Model

To track history for a model you didn’t create, use the simple_history.register utility. You can use this to track models from third-party apps you don’t have control over. Here’s an example of using simple_history.register to history-track the User model from the django.contrib.auth app:

from simple_history import register
from django.contrib.auth.models import User

register(User)

Allow tracking to be inherited

By default history tracking is only added for the model that is passed to register() or has the HistoricalRecords descriptor. By passing inherit=True to either way of registering you can change that behavior so that any child model inheriting from it will have historical tracking as well. Be careful though, in cases where a model can be tracked more than once, MultipleRegistrationsError will be raised.

from django.contrib.auth.models import User
from django.db import models
from simple_history import register
from simple_history.models import HistoricalRecords

# register() example
register(User, inherit=True)

# HistoricalRecords example
class Poll(models.Model):
    history = HistoricalRecords(inherit=True)

Both User and Poll in the example above will cause any model inheriting from them to have historical tracking as well.

Recording Which User Changed a Model

There are three documented ways to attach users to a tracked change:

1. Use the middleware as described in Quick Start. The middleware sets the User instance that made the request as the history_user on the history table.

2. Use simple_history.admin.SimpleHistoryAdmin`. Under the hood, ``SimpleHistoryAdmin actually sets the _history_user on the object to attach the user to the tracked change by overriding the save_model method.

3. Assign a user to the _history_user attribute of the object as described below:

Using _history_user to Record Which User Changed a Model

To denote which user changed a model, assign a _history_user attribute on your model.

For example if you have a changed_by field on your model that records which user last changed the model, you could create a _history_user property referencing the changed_by field:

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

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    changed_by = models.ForeignKey('auth.User')
    history = HistoricalRecords()

    @property
    def _history_user(self):
        return self.changed_by

    @_history_user.setter
    def _history_user(self, value):
        self.changed_by = value

Admin integration requires that you use a _history_user.setter attribute with your custom _history_user property (see Integration with Django Admin).

Custom history_id

By default, the historical table of a model will use an AutoField for the table’s history_id (the history table’s primary key). However, you can specify a different type of field for history_id by passing a different field to history_id_field parameter.

A common use case for this would be to use a UUIDField. If you want to use a UUIDField as the default for all classes set SIMPLE_HISTORY_HISTORY_ID_USE_UUID=True in the settings. This setting can still be overridden using the history_id_field parameter on a per model basis.

You can use the history_id_field parameter with both HistoricalRecords() or register() to change this behavior.

Note: regardless of what field type you specify as your history_id field, that field will automatically set primary_key=True and editable=False.

import uuid
from django.db import models
from simple_history.models import HistoricalRecords

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    history = HistoricalRecords(
        history_id_field=models.UUIDField(default=uuid.uuid4)
    )

Custom history_date

You’re able to set a custom history_date attribute for the historical record, by defining the property _history_date in your model. That’s helpful if you want to add versions to your model, which happened before the current model version, e.g. when batch importing historical data. The content of the property _history_date has to be a datetime-object, but setting the value of the property to a DateTimeField, which is already defined in the model, will work too.

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

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    changed_by = models.ForeignKey('auth.User')
    history = HistoricalRecords()
    __history_date = None

    @property
    def _history_date(self):
        return self.__history_date

    @_history_date.setter
    def _history_date(self, value):
        self.__history_date = value
from datetime import datetime
from models import Poll

my_poll = Poll(question="what's up?")
my_poll._history_date = datetime.now()
my_poll.save()

Change Base Class of HistoricalRecord Models

To change the auto-generated HistoricalRecord models base class from models.Model, pass in the abstract class in a list to bases.

class RoutableModel(models.Model):
    class Meta:
        abstract = True


class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    changed_by = models.ForeignKey('auth.User')
    history = HistoricalRecords(bases=[RoutableModel])

Custom history table name

By default, the table name for historical models follow the Django convention and just add historical before model name. For instance, if your application name is polls and your model name Question, then the table name will be polls_historicalquestion.

You can use the table_name parameter with both HistoricalRecords() or register() to change this behavior.

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    history = HistoricalRecords(table_name='polls_question_history')
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

register(Question, table_name='polls_question_history')

Choosing fields to not be stored

It is possible to use the parameter excluded_fields to choose which fields will be stored on every create/update/delete.

For example, if you have the model:

class PollWithExcludeFields(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

And you don’t want to store the changes for the field pub_date, it is necessary to update the model to:

class PollWithExcludeFields(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    history = HistoricalRecords(excluded_fields=['pub_date'])

By default, django-simple-history stores the changes for all fields in the model.

Change Reason

Change reason is a message to explain why the change was made in the instance. It is stored in the field history_change_reason and its default value is None.

By default, the django-simple-history gets the change reason in the field changeReason of the instance. Also, is possible to pass the changeReason explicitly. For this, after a save or delete in an instance, is necessary call the function utils.update_change_reason. The first argument of this function is the instance and the second is the message that represents the change reason.

For instance, for the model:

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

class Poll(models.Model):
    question = models.CharField(max_length=200)
    history = HistoricalRecords()

You can create a instance with a implicity change reason.

poll = Poll(question='Question 1')
poll.changeReason = 'Add a question'
poll.save()

Or you can pass the change reason explicitly:

from simple_history.utils import update_change_reason

poll = Poll(question='Question 1')
poll.save()
update_change_reason(poll, 'Add a question')

Save without a historical record

If you want to save a model without a historical record, you can use the following:

class Poll(models.Model):
    question = models.CharField(max_length=200)
    history = HistoricalRecords()

    def save_without_historical_record(self, *args, **kwargs):
        self.skip_history_when_saving = True
        try:
            ret = self.save(*args, **kwargs)
        finally:
            del self.skip_history_when_saving
        return ret


poll = Poll(question='something')
poll.save_without_historical_record()