Skip to content

Commit

Permalink
refactor(history): use django-tables2 for history table
Browse files Browse the repository at this point in the history
  • Loading branch information
b1rger committed Apr 26, 2024
1 parent dc8eec5 commit 16fc2cc
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 85 deletions.
18 changes: 18 additions & 0 deletions apis_core/history/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ def get_absolute_url(self):
ct = ContentType.objects.get_for_model(self)
return reverse("apis_core:generic:detail", args=[ct, self.history_id])

def get_diff(self, other_version=None):
if self.history_type == "-":
return None
version = other_version or self.prev_record
if version:
delta = self.diff_against(version)
else:
delta = self.diff_against(self.__class__())
changes = list(
filter(
lambda x: (x.new != "" or x.old is not None)
and x.field != "id"
and not x.field.endswith("_ptr"),
delta.changes,
)
)
return sorted(changes, key=lambda change: change.field)


class VersionMixin(models.Model):
history = APISHistoricalRecords(
Expand Down
9 changes: 9 additions & 0 deletions apis_core/history/static/css/history.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.diff-remove {
background: #ffebe9;
}
.diff-insert {
background: #dafbe1;
}
.difftable td {
width: 40%;
}
13 changes: 13 additions & 0 deletions apis_core/history/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,16 @@ class APISHistoryTableBaseTable(tables.Table):

class Meta:
fields = ["history_id", "desc", "most_recent", "view"]


class HistoryGenericTable(tables.Table):
model = tables.Column(empty_values=())
fields_changed = tables.Column(empty_values=())
instance = tables.Column(linkify=lambda record: record.get_absolute_url())
fields_changed = tables.TemplateColumn(template_name="history/columns/fields_changed.html")

class Meta:
fields = ["model", "instance", "tag", "fields_changed", "history_type", "history_date", "history_user"]

def render_model(self, record):
return record.instance.__class__.__name__
92 changes: 8 additions & 84 deletions apis_core/history/templates/history/change_history.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,90 +2,14 @@
{% load django_tables2 %}
{% load apis_history_templatetags %}
{% load apiscore %}
{% load static %}

{% block card-body %}
<table class="table table-sm">
<thead>
<tr>
<th>Model</th>
<th>Instance</th>
<th>Version</th>
<th>Fields changed</th>
<th>Change</th>
<th>When</th>
<th>By</th>
</tr>
</thead>
<tbody>
{% with object|get_history_data as changelog %}
{% for change in changelog %}
<tr>
<td>{{ change.model }}</td>
<td>
<a href="/apis/{{ change.module }}.version{{ change.model|lower }}/{{ change.history_id }}">{{ change.instance }}</a>
</td>
<td>

{% if change.version_tag == None %}
-
{% else %}
{{ change.version_tag }}
{% endif %}

</td>
<td>
<details>
<summary>
{% for c2 in change.diff.changed_fields %}
{{ c2 }}

{% if forloop.last %}
{% else %}
,
{% endif %}

{% endfor %}
</summary>
<table class="table table-m0">
<tr>
<th>field</th>
<th>old</th>
<th>new</th>
</tr>
<tbody>
{% for c2 in change.diff.changes %}
<tr>
<td>{{ c2.field }}</td>
<td>
{% block styles %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/history.css' %}" />
{% endblock styles %}

{% if c2.old == None %}
-
{% else %}
{{ c2.old }}
{% endif %}

</td>
<td>

{% if c2.new == None %}
-
{% else %}
{{ c2.new }}
{% endif %}

</td>
</tr>
{% endfor %}
</tbody>
</table>
</details>
</td>
<td>{{ change.action }}</td>
<td>{{ change.timestamp }}</td>
<td>{{ change.user }}</td>
</tr>
{% endfor %}
{% endwith %}
</tbody>
</table>
{% block card-body %}
{% load django_tables2 %}
{% render_table table %}
{% endblock card-body %}
26 changes: 26 additions & 0 deletions apis_core/history/templates/history/columns/fields_changed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% load apis_history_templatetags %}
<details>
<summary>
{% for change in record.get_diff %}
{{ change.field }}{% if not forloop.last %}, {% endif %}
{% endfor %}
</summary>
<table class="table table-sm table-hover table-bordered difftable">
<thead>
<tr>
<th></th>
<th>Old value</th>
<th>New value</th>
</tr>
</thead>
<tbody>
{% for change in record.get_diff %}
<tr>
<th>{{ change.field }}</th>
<td>{{ change|get_diff_old }}</td>
<td>{{ change|get_diff_new }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</details>
50 changes: 50 additions & 0 deletions apis_core/history/templatetags/apis_history_templatetags.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import difflib
from apis_core.history.serializers import HistoryLogSerializer
from django import template
from apis_core.history.utils import triple_sidebar_history
from django.utils.safestring import mark_safe

register = template.Library()

Expand All @@ -17,3 +19,51 @@ def object_relations_history(context, detail=True):
def get_history_data(obj):
data = HistoryLogSerializer(obj.get_history_data(), many=True).data
return data


@register.filter
def get_diff(change, show_new=True, show_old=True, shorten=0):
def style_remove(text):
return f"<span class='diff-remove'>{text}</span>"

def style_insert(text):
return f"<span class='diff-insert'>{text}</span>"

if change.old in ["", None]:
result = style_insert(change.new) if show_new else ""
elif change.new in ["", None]:
result = style_remove(change.old) if show_old else ""
else:
result = ""
old = str(change.old)
new = str(change.new)
codes = difflib.SequenceMatcher(None, old, new).get_opcodes()
for opcode, old_start, old_end, new_start, new_end in codes:
match opcode:
case "equal":
equal = old[old_start:old_end]
if shorten and len(equal) > shorten:
equal = equal[:5] + " ... " + equal[-10:]
result += equal
case "delete":
if show_old:
result += style_remove(old[old_start:old_end])
case "insert":
if show_new:
result += style_insert(new[new_start:new_end])
case "replace":
if show_new:
result += style_insert(new[new_start:new_end])
if show_old:
result += style_remove(old[old_start:old_end])
return mark_safe(result)


@register.filter
def get_diff_old(change, shorte=0):
return get_diff(change, show_new=False)


@register.filter
def get_diff_new(change, shorten=0):
return get_diff(change, show_old=False)
15 changes: 14 additions & 1 deletion apis_core/history/views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
from apis_core.generic.views import GenericModelMixin
from apis_core.generic.helpers import module_paths, first_member_match
from apis_core.generic.tables import GenericTable
from django.shortcuts import redirect
from django.urls import reverse
from django.contrib.contenttypes.models import ContentType
from django.views.generic.detail import DetailView
from django.utils import timezone
from django_tables2 import SingleTableMixin
from django_tables2.tables import table_factory
from .tables import HistoryGenericTable


class ChangeHistoryView(GenericModelMixin, DetailView):
class ChangeHistoryView(GenericModelMixin, SingleTableMixin, DetailView):
template_name = "history/change_history.html"

def get_table_class(self):
table_modules = module_paths(self.model, path="tables", suffix="HistoryTable")
table_class = first_member_match(table_modules, HistoryGenericTable)
return table_factory(self.model, table_class)

def get_table_data(self):
return self.get_object().get_history_data()


def create_new_version(request, contenttype, pk):
"""Gets the version of the history instance and creates a new version."""
Expand Down

0 comments on commit 16fc2cc

Please sign in to comment.