Skip to main content
Premium

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 tracked ModelAdmin — a boolean, or a method taking the request for per-user rules;
  • the admin's can_read otherwise.

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").