Change History (Reversion)
habarico-enterprise ships a reversion module that adds an object change-history UI on
top of django-reversion. It registers read-only
Version and Revision admin resources and renders a per-field diff between consecutive
versions as HTML (<ins> / <del> for inserted / deleted text), so a frontend can display it
directly in a RichText field.
Habarico only provides the admin UI layer. Capturing the snapshots is handled by
django-reversion, so the consuming project owns model registration, settings and migrations.
Installation
Install habarico-enterprise with the reversion extra (pulls in django-reversion and dmp):
pip install habarico-enterprise[reversion]
Configuration
Add both apps to INSTALLED_APPS, enable RevisionMiddleware (groups every model change made
during a request into a single revision and records the acting user) and run the migrations:
INSTALLED_APPS = [
...,
"habarico",
"habarico_enterprise", # optional, adds the translations of the package's messages
"reversion", # django-reversion: the snapshots, models and migrations
"habarico_enterprise.reversion", # this feature: the version / revision admin resources
]
MIDDLEWARE = [
...,
"reversion.middleware.RevisionMiddleware",
]
python manage.py migrate
The two reversion entries are different packages and both are needed: django-reversion captures
the history, this one presents it. Listing habarico_enterprise.reversion is what switches the
feature on — it registers the version and revision admin resources and adds history_fields /
can_read_history to your admins. Leave it out and nothing of the feature is loaded.
Tracking a Model
Mark the models whose history you want to keep with @reversion.register and make sure their
changes happen inside a revision. With RevisionMiddleware enabled, every change made during a
request is captured automatically; outside the request cycle use the reversion.create_revision()
context manager:
import reversion
from django.db import models
@reversion.register
class Product(models.Model):
name = models.CharField(max_length=200)
description = models.TextField()
status = models.CharField(max_length=20)
with reversion.create_revision():
product.name = "New title"
product.save()
History Permissions
A version is only as readable as the resource it tracks. The version resource narrows every
query to the content types whose admin allows reading history for the request's user:
can_read_history, when declared on the trackedModelAdmin— a boolean, or a method taking the request for per-user rules;- the admin's
can_readotherwise.
Versions of models without a registered admin are not readable at all, and fetching a version outside these permissions returns 404.
class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "description", "status"]
history_fields = ["name", "description", "status"]
# Everyone with read access sees products; only auditors may read their change history.
def can_read_history(self, request):
return request.user.groups.filter(name="auditors").exists()
Selecting History Fields
Add a history_fields attribute to the ModelAdmin of the tracked resource. It lists the fields
that are diffed and shown in the history view. Without it, the history is still recorded but no
per-field diff is rendered:
from habarico import ModelAdmin
from .models import Product
class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "description", "status"]
history_fields = ["name", "description", "status"]
CharField changes are rendered as a character-level diff; other field changes are rendered as
old value → new value.
Displaying History on a Resource
Use VersionListBox inside a view schema generator to embed the object's history (a filtered list
of its versions) directly in the resource's detail view:
from habarico.schema.generators import ShowViewSchemaGenerator
from habarico_enterprise.reversion.utils import VersionListBox
class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "description", "status"]
history_fields = ["name", "description", "status"]
view_schema_generators = [
ShowViewSchemaGenerator(
boxes=[
"name",
"description",
"status",
VersionListBox(reference="product"),
]
),
]
The reference argument is the admin resource name of the tracked model (by default the model
name, e.g. "product").