djangodjango-1.8django-reversion

Don't create new version if nothing has changed in Django-reversion


I would like to save new object version only if something has changed, in django-reversion. I went through the documentation and didn't find anything about it. How can I achieve it?


Solution

  • You can use the ignore_duplicates option. Unfortunately

    It doesn't follow relations, as that can get expensive and slow very quickly.

    If you really want to ignore duplicates for follow relations, you have two possibilities:

    1. Do the fork and disable restrictions

    Remove and explicit here https://github.com/etianen/django-reversion/blob/master/reversion/revisions.py#L199

    Set ignore_duplicates as True by default https://github.com/etianen/django-reversion/blob/master/reversion/revisions.py#L368

    Be careful, as mentioned above it can be slow.

    1. Listen the post revision commit signal and delete duplicate versions manually

    Set ignore_duplicates as False and add the signal receiver:

    from django.db import transaction
    from django.dispatch import receiver
    from reversion.models import Revision, Version
    from reversion.signals import post_revision_commit
    
    
    def clear_versions(versions, revision):
        count = 0
        for version in versions:
            previous_version = Version.objects.filter(
                object_id=version.object_id,
                content_type_id=version.content_type_id,
                db=version.db,
                id__lt=version.id,
            ).first()
            if not previous_version:
                continue
            if previous_version._local_field_dict == version._local_field_dict:
                version.delete()
                count += 1
            if len(versions_ids) == count:
                revision.delete()
    
    
    @receiver(post_revision_commit)
    def post_revision_commit_receiver(sender, revision, versions, **kwargs):
        transaction.on_commit(lambda: clear_versions(versions, revision))
    

    It also can be slow, but you can do it asynchronously (in a Celery task, for example):

    # tasks.py
    
    @celery.task(time_limit=60, ignore_result=True)
    def clear_versions(revision_id, versions_ids):
        count = 0
        if versions_ids:
            for version in Version.objects.filter(id__in=versions_ids):
                previous_version = Version.objects.filter(
                    object_id=version.object_id,
                    content_type_id=version.content_type_id,
                    db=version.db,
                    id__lt=version.id,
                ).first()
                if not previous_version:
                    continue
                if previous_version._local_field_dict == version._local_field_dict:
                    version.delete()
                    count += 1
        if len(versions_ids) == count:
            Revision.objects.only('id').get(id=revision_id).delete()
    
    # signals.py
    
    @receiver(post_revision_commit)
    def post_revision_commit_receiver(sender, revision, versions, **kwargs):
        transaction.on_commit(
            lambda: clear_versions.delay(revision.id, [v.id for v in versions])
        )