Django Reversion

Hint

Refer to the manual for details of how to install and use django-reversion. It includes a list of which version of django-reversion is compatible with each version of Django - worth keeping an eye on as the recommended version for the latest version of Django does change.

Note

On production systems we create a shell script to run django administration commands where this document refers to django-admin you should use the appropriate script on the production system.

History

from contact.models import Contact
contact = Contact.objects.get(pk=12424)
from reversion.models import Version
versions = Version.objects.get_for_object(contact)
for x in versions: print(x.revision.user, x.revision.date_created, x)

Tip

x.field_dict will display a dictionary showing changes to individual fields.

Import Signature

From django-reversion version 1.10 the import signature changed from import reversion to:

from reversion import revisions as reversion

Add reversion to a model

To register reversion on a model e.g:

from reversion import revisions as reversion

@reversion.register()
class ModelWithReversion(models.Model):
    # ...

The legacy method is as follows:

class ModelWithReversion(models.Model):
    # ...

reversion.register(ModelWithReversion)

If the reversion is added after a model it was initially created you can create initial revision using the management command

django-admin createinitialrevisions your_app.YourModel --comment="Initial revision."

Register

To unregister models (to stop storing versions while you do some processing):

import reversion

model_list = [
    Contact,
    ContactAddress,
    ContactEmail,
    ContactPhone,
]
for m in model_list:
    if reversion.is_registered(m):
        reversion.unregister(m)

To register the same models:

for m in model_list:
    if not reversion.is_registered(m):
        reversion.register(m)

To make this easier, you could unregister and then register in a try, finally block like this:

try:
    _unregister()
    do_something_useful()
finally:
    _register()

Reversion statistics

Count of Revision records

We’ve added a management command to the base app with list the number of revision records for each model in a project.

Note

to use this command, you must be using django-reversion version 2.0.5 or later

To use the command simply change to the project directory and type:

django-admin revision_count

Space used in the database

The space used by Postgres can be monitored using the command (the reversion tables are prefixed by reversion_):

psql -U postgres <database name> -c "select relname, relpages from pg_class order by relpages desc;" | grep "reversion_"jk

A more detailed breakdown can be obtained using

psql -U postgres <database name> -c "SELECT *, pg_size_pretty(total_bytes) AS total
    , pg_size_pretty(index_bytes) AS INDEX
    , pg_size_pretty(toast_bytes) AS toast
    , pg_size_pretty(table_bytes) AS TABLE
  FROM (
  SELECT *, total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes FROM (
      SELECT c.oid,nspname AS table_schema, relname AS TABLE_NAME
              , c.reltuples AS row_estimate
              , pg_total_relation_size(c.oid) AS total_bytes
              , pg_indexes_size(c.oid) AS index_bytes
              , pg_total_relation_size(reltoastrelid) AS toast_bytes
          FROM pg_class c
          LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
          WHERE relkind = 'r'
  ) a
) a order by total_bytes desc limit 50;"

Size of reversion tables in backup

To find out how much many lines of the Postgres dump file is given over to reversion:

grep -n "^COPY" <backup file name> > /tmp/bkup-lines.txt

then view the file and search for reversion_ the difference between the line number of this copy command and the next one gives the number of lines that the backup of that table spans.

Reversion in not always appropriate

Where a table changes frequently (e.g. for recording stats) it can create a large number of reversion records that serve no useful purpose.

See the next section for how to remove reversion from an existing model.

Remove reversion from an existing model

Note

Before removing an existing model from reversion you should remove the existing revision data otherwise this data will be retained but will not be accessable.

To delete existing revision information use the deleterevisions management command as follows:

django-admin deleterevisions <app name>.<Model name>

If there are a large number of records it is better to delete these in chunks so that we do not create large transaction on which are time consuming on Postgres e.g. the following script deletes the revision data for a model called Model in the app app, a day at a time

day=365
while [ $day -gt 0 ]; do
  day=`expr $day - 1`
  echo "Deleting reversion older than $day days"
  django-admin deleterevisions app.Model --days=$day
done

This will split the task into smaller chunks.

Once the data is deleted you can then remove the reversion.register() line from the models module.

Tip

If you want to delete all revisions, run the deleterevisions management command without the model parameter.

Testing

To check the number of Version records created:

import reversion

qs = reversion.models.Version.objects.all()
assert 0 == qs.count()

To filter on Version for a model:

pks = [
    x.pk
    for x in reversion.models.Version.objects.get_for_model(UserConsent)
]