Welcome to django-versioning’s documentation!

django-revisions is a Django app that allows you to keep a version history for model instances. It has a simple API, but is also integrated with the Django admin interface. django-revisions doesn’t add any tables to your database, nor does it work by serializing old revisions – making this app very natural to work with and migration-friendly, as opposed to other solutions out there. (See The basic design of django-revisions.)

  • Access and revert to any previous model save with a convenient and minimally intrusive API.
  • An optional trash bin for deleted content.
  • Admin integration: restore trash, revert content to an older revision.
  • Works flawlessly with migration tools like South

django-revisions takes care of model instance version history. If you found this page while looking for asset versioning of media files, like javascript or CSS, take a look at django-css and related apps instead.

Getting Started

Installation

Models

django-revisions works by adding models.VersionedModel as a base class to your model as well as — if you prefer — shortcuts.VersionedModel. You can enable a trash function by adding models.TrashableModel as a base class to your model and adding the class decorator decorators.trash_aware to any model for which you want to enable trash (that is, soft deletes).

All three classes are independent, so you’ll have to add them in separately.

The models and API work with both single-table models and joined tables (that is, concrete inheritance).

django-revisions makes no effort to be a drop-in for existing models. It adds fields to your models, which means you’ll have to run a South migration to get you started, and you’ll have to run a small script to generate bundle IDs (they can just equal the object PK).

See Caveats when working with VersionedModel for more detail.

Admin integration

To make sure django-versioning works smoothly with the admin interface, you should add revisions.middleware.VersionedModelRedirectMiddleware to your middlewares in settings.py, e.g.:

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'revisions.middleware.VersionedModelRedirectMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)

django-revisions also comes with a couple of optional ModelForm classes.

  • AutoRevisionForm makes sure to clear any revision-specific fields, like log messages. Since these are tied to each individual revision, revision-specific fields should be empty upon each new edit.
  • RevisionForm inherits from AutoRevisionForm, but adds a checkbox to the form that allows users to specify they only want to make a small change, and that we ought to save it in-place rather than creating a new revision.

Specify fields that need to be cleared as a list of attribute names like so:

class MyModel(VersionedModel):
    class Versioning:
        clear_each_revision = ['log_message', 'codename', ]

If you need neither of these features, you may simply use the default forms.ModelForm instead for versioned models.

When in doubt, just use the AutoRevisionForm.

If you don’t specify any fields, or don’t have a Versioning class in your model, the AutoRevisionForm will simply function like any old ModelForm.

Deleting and trashing versioned content

This application also includes a simple abstract model that will put deleted objects into a trash bin, rather than outright deleting them from the database. TrashableModel works with any model, versioned or not. It adds a single is_trash field to the database table, so make sure to add that in manually or remember to execute a migration.

Note that, for design reasons, you can’t trash individual revisions. If you want to undo a revision, obj.revert_to(obj.get_revisions().prev) or obj.get_revisions().prev.make_current_revision() are the preferred methods. That way, the version history is kept intact.

Hard deleting indidual revisions is possible for administration purposes, using obj.delete_revision(), but is highly discouraged.

API

django-revisions is still sorely lacking in documentation, though early adopters can get started by browsing through the methods available on models.TrashableModel, models.VersionedModel and the shortcuts in shortcuts.VersionedModel.

Some examples

# models.py
from django.db import models
import revisions
class Story(revisions.models.VersionedModel, revisions.shortcuts.VersionedModel):
    title = models.CharField(max_length=200)
    date = models.DateTimeField(auto_now=True)
    log = models.CharField(max_length=200)

    def __unicode__(self):
        return self.title

    class Versioning:
        publication_date = 'date'
        clear_each_revision = ['log']

# interactive session
>>> story = Story(title="a story (v1)")
>>> story.save()
>>> story.pk
1
>>> story.title = "a story (v2)"
>>> story.save()
>>> story.pk
2
>>> story.get_revisions()
[<Story: a story (v1)>, <Story: a story (v2)>]
>>> story.get_revisions()[0].make_current_revision()
>>> story.pk
3
>>> story.get_revisions()
[<Story: a story (v1)>, <Story: a story (v2)>, <Story: a story (v1)>]
>>> old_story = story.get_revisions()[0]
>>> # revert to a story
>>> story.revert_to(old_story)
>>> # or a primary key
>>> story.revert_to(2)
>>> # dates work as well
>>> story.revert_to(old_story.date)
>>> story.log = "Changed some stuff"
>>> story.save()
>>> # the Django admin clears out fields that have to be empty on each rev for you:
>>> story.prepare_for_writing()
>>> story.log
''

Methods and attributes

class revisions.models.TrashableModel(*args, **kwargs)

Bases: django.db.models.base.Model

Users wanting a version history may also expect a trash bin that allows them to recover deleted content, as is e.g. the case in WordPress. This is that thing.

class Meta
TrashableModel.delete()

It makes no sense to trash individual revisions: either you keep a version history or you don’t. If you want to undo a revision, you should use obj.revert_to(preferred_revision) instead.

TrashableModel.delete_permanently()
TrashableModel.get_content_bundle()
TrashableModel.is_trash
class revisions.models.VersionedModel(*args, **kwargs)

Bases: django.db.models.base.Model

class Meta
class VersionedModel.Versioning
VersionedModel.check_if_latest_revision()
VersionedModel.delete(*vargs, **kwargs)
VersionedModel.delete_revision(*vargs, **kwargs)
classmethod VersionedModel.fetch(criterion)
classmethod VersionedModel.get_implementations()
VersionedModel.get_latest_revision()
VersionedModel.get_revisions()
VersionedModel.make_current_revision()
VersionedModel.prepare_for_writing()

This method allows you to clear out certain fields in the model that are specific to each revision, like a log message.

VersionedModel.revert_to(criterion)
VersionedModel.save(new_revision=True, *vargs, **kwargs)
VersionedModel.show_diff_to(to, field)

Shortcuts

class revisions.shortcuts.VersionedModel

Bases: object

VersionedModel defines a few common operations (check_if_latest_revision, get_latest_revision) that involve a database lookup. Common practice dictates that these should be implemented as methods rather than as properties, because properties would signal to the programmer that a simple lookup is taking place.

However, because we value code that feels as natural and domain-driven as possible, and because the database lookups involved are fairly quick queries, we’ve also included a helper class that implements these common operations as properties – a few convenient shortcuts if you will.

is_latest_revision
latest_revision
revisions

Development: reporting bugs, helping out, running the test suite

Development takes place on GitHub. Feel free to fork, and please report any bugs or feature requests over there. Run the test suite simply by adding revisions and revisions.tests to your apps, and subsequently running python manage.py test revisions. django-versioning has been known to work on Django 1.2 but only undergoes frequent testing on Django 1.3. That said, it will probably work on any 1.x installation.

Roadmap

The first priority is better documentation. After that, there are some features that may or may not get added to the app:

  • view version history and do diffs in the admin
  • a view wrapper or query shortcut that can handle redirecting to the latest revision when users stumble on outdated content (e.g. when a new revision has a different slug)

Indices and tables