Skip to content

Commit

Permalink
WIP: Fix #148 add a compare fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
jedie committed Feb 24, 2021
1 parent 9395300 commit 39b03db
Show file tree
Hide file tree
Showing 11 changed files with 515 additions and 4 deletions.
56 changes: 52 additions & 4 deletions reversion_compare/admin.py
Expand Up @@ -4,11 +4,9 @@
Admin extensions for django-reversion-compare
:copyleft: 2012-2019 by the django-reversion-compare team, see AUTHORS for more details.
:copyleft: 2012-2021 by the django-reversion-compare team, see AUTHORS for more details.
:license: GNU GPL v3 or above, see LICENSE for more details.
"""


import logging

from django.conf import settings
Expand All @@ -19,10 +17,13 @@
from django.urls import path, reverse
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
from reversion import RevertError
from reversion.admin import VersionAdmin
from reversion.models import Revision, Version

from reversion_compare.compare_raw import get_version_data, pformat
from reversion_compare.forms import SelectDiffForm
from reversion_compare.helpers import html_diff
from reversion_compare.mixins import CompareMethodsMixin, CompareMixin


Expand Down Expand Up @@ -72,6 +73,7 @@ def compare_sub_text(self, obj, version1, version2, value1, value2):

# Template file used for the compare view:
compare_template = "reversion-compare/compare.html"
compare_raw_template = "reversion-compare/compare_raw.html"

# change template from django-reversion to add compare selection form:
object_history_template = "reversion-compare/object_history.html"
Expand Down Expand Up @@ -163,7 +165,15 @@ def compare_view(self, request, object_id, extra_context=None):
next_version = queryset.filter(pk__gt=version_id2).last()
prev_version = queryset.filter(pk__lt=version_id1).first()

compare_data, has_unfollowed_fields = self.compare(obj, version1, version2)
try:
compare_data, has_unfollowed_fields = self.compare(obj, version1, version2)
except RevertError as err:
logger.exception('Fallback compare caused by: %s', err)
# A old version can't be loaded.
# e.g.: model was migrated and version JSON data not, see:
# https://github.com/etianen/django-reversion/issues/859
# Fallback to JSON compare
return self.compare_raw(request, obj, version1, version2, compare_error=err, extra_context=extra_context)

opts = self.model._meta

Expand Down Expand Up @@ -201,6 +211,44 @@ def compare_view(self, request, object_id, extra_context=None):
context.update(extra_context or {})
return render(request, self.compare_template or self._get_template_list("compare.html"), context)

def compare_raw(self, request, obj, version1, version2, compare_error, extra_context=None):
"""
Fallback: compare the raw json data.
"""
version1data = get_version_data(version1)
version2data = get_version_data(version2)

version1pformat = pformat(version1data)
version2pformat = pformat(version2data)

# TODO: Generate a nicer diff ;)
diff_html = html_diff(version1pformat, version2pformat)

opts = self.model._meta
context = {
**self.admin_site.each_context(request),
"opts": opts,
"app_label": opts.app_label,
"model_name": capfirst(opts.verbose_name),
"title": _("Compare %(name)s") % {"name": version1.object_repr},
"obj": obj,
"compare_error": compare_error,
"diff_html": diff_html,
"version1": version1,
"version2": version2,
"changelist_url": reverse(f"{self.admin_site.name}:{opts.app_label}_{opts.model_name}_changelist"),
"original": obj,
"history_url": reverse(
f"{self.admin_site.name}:{opts.app_label}_{opts.model_name}_history", args=(quote(obj.pk),)
),
"save_url": reverse(
f"{self.admin_site.name}:{opts.app_label}_{opts.model_name}_revision",
args=(quote(version1.object_id), version1.id),
),
}
context.update(extra_context or {})
return render(request, self.compare_raw_template or self._get_template_list("compare_raw.html"), context)


class CompareVersionAdmin(CompareMethodsMixin, BaseCompareVersionAdmin):
"""
Expand Down
42 changes: 42 additions & 0 deletions reversion_compare/compare_raw.py
@@ -0,0 +1,42 @@
"""
Helper to compare raw version data.
see: https://github.com/etianen/django-reversion/issues/859
:copyleft: 2021 by the django-reversion-compare team, see AUTHORS for more details.
:license: GNU GPL v3 or above, see LICENSE for more details.
"""


import json
import pprint

from django.core.serializers.json import DjangoJSONEncoder
from reversion.models import Version


def get_version_data(version):
"""
Get field data from a Version instance.
"""
assert isinstance(version, Version)
assert version.format == 'json', f'{version.format!r} not supported (only JSON)'

json_string = version.serialized_data
version_data = json.loads(json_string)
# version_data looks like:
# [{'fields': {'info': 'Migration state 2 - version 4',
# 'number_then_text': 111,
# 'text': 'Now this is a short text!!!'},
# 'model': 'reversion_compare_tests.migrationmodel',
# 'pk': 1}]
assert len(version_data) == 1
fields_data = version_data[0]['fields']
return fields_data


def pformat(value):
try:
return DjangoJSONEncoder(indent=4, sort_keys=True, ensure_ascii=False).encode(value)
except TypeError:
# Fallback if values are not serializable with JSON:
return pprint.pformat(value, width=120)
69 changes: 69 additions & 0 deletions reversion_compare/templates/reversion-compare/compare_raw.html
@@ -0,0 +1,69 @@
{% extends "admin/base_site.html" %}
{% load i18n %}

{% block extrastyle %}{{ block.super }}
<style type="text/css">
/* minimal style for the diffs */
pre.highlight {
max-width: 900px;
}
del, ins {
color: #000;
text-decoration: none;
}
del { background-color: #fdb8c0 }
ins { background-color: #acf2bd; }
sup.follow { color: #5555ff; }
.diff-line {
display: inline-block;
width: 100%;
margin-left: -1.0em;
padding-left: 0.5em;
background-color: #f6f6f6;
color: #222;
}
.diff-line.diff-ins { border-left: 0.5em solid #bef5cb; background-color: #e6ffed; }
.diff-line.diff-del { border-left: 0.5em solid #fdaeb7; background-color: #ffeef0; }
.diff-line.diff-ins.diff-del { border-left: 0.5em solid #bef5cb; background-color: #e6ffed; } /* mixed del/ins == green */
</style>
{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a> &rsaquo;
<a href="{% url 'admin:app_list' app_label %}">{{app_label|capfirst|escape}}</a> &rsaquo;
<a href="{{changelist_url}}">{{opts.verbose_name_plural|capfirst}}</a> &rsaquo;
<a href="{{history_url}}">{% trans "History" %}</a> &rsaquo;
{% blocktrans with opts.verbose_name_plural|escape as name %}Compare {{name}}{% endblocktrans %}
{{title}}
</div>
{% endblock %}


{% block content %}
<div id="content-main">

{% block object-tools %}
<ul class="object-tools">
{% block object-tools-items %}
{% include "reversion-compare/compare_links_partial.html" %}
{% endblock %}
</ul>
{% endblock %}

<p class="help">
{% blocktrans with date1=version1.revision.date_created|date:_("DATETIME_FORMAT") date2=version2.revision.date_created|date:_("DATETIME_FORMAT") %}
Compare <strong>{{ date1 }}</strong> with <strong>{{ date2 }}</strong>:
{% endblocktrans %}
</p>
&lsaquo; <a href="{{history_url}}">{% trans "Go back to history list" %}</a>
&vert;
<a href="{{save_url}}">{% trans "Revert to this version" %}</a> &rsaquo;

{% include "reversion-compare/compare_raw_partial.html" %}

&lsaquo; <a href="{{history_url}}">{% trans "Go back to history list" %}</a>
&vert;
<a href="{{save_url}}">{% trans "Revert to this version" %}</a> &rsaquo;
</div>
{% endblock %}
@@ -0,0 +1,17 @@
{% load i18n %}

{{ diff_html }}

<h4>{% trans "Edit comment:" %}</h4>
<blockquote>{{ version2.revision.comment|default:_("(no comment exists)") }}</blockquote>

<h4>{% trans "Note:" %}</h4>
<p>
This is the fallback compare, because the normal compare can't be applied
between the two selected version:
</p>
<pre>{{ compare_error }}</pre>
<p>
(More info: <a href="https://github.com/etianen/django-reversion/issues/859">
django-reversion issues #859</a>)
</p>
8 changes: 8 additions & 0 deletions reversion_compare_tests/admin.py
Expand Up @@ -17,6 +17,7 @@
CustomModel,
Factory,
Identity,
MigrationModel,
Person,
Pet,
SimpleModel,
Expand All @@ -32,6 +33,13 @@ class SimpleModelAdmin(CompareVersionAdmin):
admin.site.register(SimpleModel, SimpleModelAdmin)


class MigrationModelAdmin(CompareVersionAdmin):
pass


admin.site.register(MigrationModel, MigrationModelAdmin)


class FactoryAdmin(CompareVersionAdmin):
pass

Expand Down
@@ -0,0 +1,54 @@
"""
:copyleft: 2015-2016 by the django-reversion-compare team, see AUTHORS for more details.
:created: 2015 by JensDiemer.de
:license: GNU GPL v3 or above, see LICENSE for more details.
"""


from django.core.management import BaseCommand, call_command
from reversion import create_revision, set_comment

from reversion_compare_tests.models import MigrationModel


lorem_ipsum = (
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor'
' invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et'
' accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata'
' sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing'
' elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,'
' sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita'
' kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'
)


class Command(BaseCommand):
help = "Run Unittest-Server"

def verbose_call(self, command, **kwargs):
self.stdout.write("\n")
self.stdout.write("_" * 79)
self.stdout.write(self.style.NOTICE(f" *** call '{command}' command:"))
self.stdout.write("\n")
call_command(command, **kwargs)

def handle(self, *args, **options):
# Migration state 1:
# number_then_text = models.CharField()
# text = models.TextField()
with create_revision():
info = 'Migration state 1 - version 1'
item = MigrationModel.objects.create(
info=info,
number_then_text='123',
text=lorem_ipsum
)
set_comment(f'Reversion comment: {info}')

if self.verbose:
print("version 1:", item1)

with create_revision():
item1.text = "version two"
item1.save()
set_comment("simply change the CharField text.")

0 comments on commit 39b03db

Please sign in to comment.