Skip to content

Commit a0ca94a

Browse files
committed
feat(contrib): reversion / reversion-compare templates
1 parent 3a89e4b commit a0ca94a

22 files changed

Lines changed: 673 additions & 7 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class MyModelAdmin(ModelAdmin):
8888

8989
- [django-guardian](https://github.com/django-guardian/django-guardian) - [Integration guide](https://unfoldadmin.com/docs/integrations/django-guardian/)
9090
- [django-import-export](https://github.com/django-import-export/django-import-export) - [Integration guide](https://unfoldadmin.com/docs/integrations/django-import-export/)
91+
- [django-reversion](https://github.com/etianen/django-reversion) / [django-reversion-compare](https://github.com/jedie/django-reversion-compare) - [Integration guide](https://unfoldadmin.com/docs/integrations/django-reversion/)
9192
- [django-simple-history](https://github.com/jazzband/django-simple-history) - [Integration guide](https://unfoldadmin.com/docs/integrations/django-simple-history/)
9293
- [django-constance](https://github.com/jazzband/django-constance) - [Integration guide](https://unfoldadmin.com/docs/integrations/django-constance/)
9394
- [django-celery-beat](https://github.com/celery/django-celery-beat) - [Integration guide](https://unfoldadmin.com/docs/integrations/django-celery-beat/)

docs/installation/quickstart.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ INSTALLED_APPS = [
1818
"unfold.contrib.inlines", # optional, if special inlines are needed
1919
"unfold.contrib.import_export", # optional, if django-import-export package is used
2020
"unfold.contrib.guardian", # optional, if django-guardian package is used
21+
"unfold.contrib.reversion", # optional, if django-reversion package is used
2122
"unfold.contrib.simple_history", # optional, if django-simple-history package is used
2223
"unfold.contrib.location_field", # optional, if django-location-field package is used
2324
"unfold.contrib.constance", # optional, if django-constance package is used
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
title: django-reversion
3+
order: 0
4+
description: Integrate django-reversion and django-reversion-compare with Django Unfold to style object history, recover flows, and version comparison screens in the admin interface.
5+
---
6+
7+
# django-reversion
8+
9+
To integrate django-reversion with Unfold, add `unfold.contrib.reversion` to your `INSTALLED_APPS` setting after `unfold` and before `reversion`. If you also use django-reversion-compare, keep `unfold.contrib.reversion` before `reversion_compare` as well so Unfold's template overrides take precedence.
10+
11+
```python
12+
# settings.py
13+
14+
INSTALLED_APPS = [
15+
"unfold",
16+
# ...
17+
"unfold.contrib.reversion",
18+
# ...
19+
"reversion",
20+
"reversion_compare", # optional
21+
]
22+
```
23+
24+
For plain django-reversion history and recover flows, inherit from both `VersionAdmin` and `unfold.admin.ModelAdmin` in your admin configuration:
25+
26+
```python
27+
# admin.py
28+
29+
from django.contrib import admin
30+
from reversion.admin import VersionAdmin
31+
32+
from unfold.admin import ModelAdmin
33+
34+
from .models import ExampleModel
35+
36+
37+
@admin.register(ExampleModel)
38+
class ExampleAdmin(VersionAdmin, ModelAdmin):
39+
pass
40+
```
41+
42+
If you also want side-by-side version comparison, inherit from `CompareVersionAdmin` instead:
43+
44+
```python
45+
# admin.py
46+
47+
from django.contrib import admin
48+
from reversion_compare.admin import CompareVersionAdmin
49+
50+
from unfold.admin import ModelAdmin
51+
52+
from .models import ExampleModel
53+
54+
55+
@admin.register(ExampleModel)
56+
class ExampleCompareAdmin(CompareVersionAdmin, ModelAdmin):
57+
pass
58+
```

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ dev = [
4343
"django-import-export>=4.3",
4444
"django-location-field>=2.7",
4545
"django-money>=3.5",
46+
"django-reversion>=6.1",
47+
"django-reversion-compare>=0.18.1",
4648
"django-simple-history>=3.11",
4749
"django-stubs>=5.2",
4850
"pillow>=12.1",

src/unfold/contrib/reversion/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class ReversionConfig(AppConfig):
5+
name = "unfold.contrib.reversion"
6+
label = "unfold_reversion"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.reversion-compare {
2+
--del: rgb(244 63 94 / 0.16);
3+
--ins: rgb(16 185 129 / 0.18);
4+
}
5+
6+
.reversion-compare pre.highlight {
7+
max-width: none;
8+
overflow-x: auto;
9+
border: 1px solid var(--color-base-200);
10+
border-radius: 6px;
11+
padding: 0.875rem 1rem;
12+
background-color: var(--color-base-50);
13+
color: var(--color-font-default-light);
14+
}
15+
16+
html.dark .reversion-compare pre.highlight,
17+
html[data-theme="dark"] .reversion-compare pre.highlight {
18+
border-color: var(--color-base-800);
19+
background-color: rgb(255 255 255 / 0.02);
20+
color: var(--color-font-default-dark);
21+
}
22+
23+
.reversion-compare del,
24+
.reversion-compare ins {
25+
border-radius: 4px;
26+
padding: 0 0.125rem;
27+
text-decoration: none;
28+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{% extends "admin/base_site.html" %}
2+
{% load i18n static admin_urls unfold %}
3+
4+
{% block extrastyle %}
5+
{{ block.super }}
6+
<link rel="stylesheet" type="text/css" href="{% static 'reversion_compare.css' %}">
7+
<link rel="stylesheet" type="text/css" href="{% static 'unfold/reversion/css/reversion-compare.css' %}">
8+
{% endblock %}
9+
10+
{% block content %}
11+
{% url opts|admin_urlname:'history' original.pk as history_url %}
12+
{% url opts|admin_urlname:'revision' version1.object_id version1.id as save_url_local %}
13+
14+
<div class="reversion-compare flex flex-col gap-4">
15+
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
16+
<h1 class="text-2xl font-semibold tracking-tight text-font-important-light dark:text-font-important-dark">
17+
{% blocktrans %}Compare{% endblocktrans %} {{ original|default:obj }}
18+
</h1>
19+
20+
<div class="flex flex-wrap gap-2">
21+
{% if prev_url %}
22+
{% component "unfold/components/button.html" with href=prev_url variant="default" icon="arrow_back" %}
23+
{% trans "Previous" %}
24+
{% endcomponent %}
25+
{% endif %}
26+
27+
{% if next_url %}
28+
{% component "unfold/components/button.html" with href=next_url variant="default" %}
29+
{% trans "Next" %}
30+
<span class="material-symbols-outlined">arrow_forward</span>
31+
{% endcomponent %}
32+
{% endif %}
33+
34+
{% component "unfold/components/button.html" with href=history_url variant="default" %}
35+
{% trans "Back to history" %}
36+
{% endcomponent %}
37+
38+
{% component "unfold/components/button.html" with href=save_url|default:save_url_local variant="secondary" icon="restore_page" %}
39+
{% trans "Revert to this version" %}
40+
{% endcomponent %}
41+
</div>
42+
</div>
43+
44+
<p class="mb-0 text-sm text-base-500 dark:text-base-400">
45+
{% blocktrans with date1=version1.revision.date_created|date:_("DATETIME_FORMAT") date2=version2.revision.date_created|date:_("DATETIME_FORMAT") %}
46+
Compare <strong>{{ date1 }}</strong> with <strong>{{ date2 }}</strong>:
47+
{% endblocktrans %}
48+
</p>
49+
50+
{% include "reversion-compare/compare_partial.html" %}
51+
</div>
52+
{% endblock %}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{% load i18n %}
2+
3+
<div class="flex flex-col gap-4">
4+
{% for field_diff in compare_data %}
5+
<section class="overflow-hidden rounded-default border border-base-200 shadow-xs dark:border-base-800">
6+
<header class="border-b border-base-200 px-4 py-3 dark:border-base-800">
7+
<h2 class="text-sm font-medium text-font-important-light dark:text-font-important-dark">
8+
{% firstof field_diff.field.verbose_name field_diff.field.related_name %}
9+
{% if field_diff.is_related and not field_diff.follow %}
10+
<sup class="text-amber-600 dark:text-amber-400">*</sup>
11+
{% endif %}
12+
</h2>
13+
14+
{% if field_diff.field.help_text %}
15+
<p class="mt-1 text-xs text-base-500 dark:text-base-400">{{ field_diff.field.help_text|safe }}</p>
16+
{% endif %}
17+
</header>
18+
19+
<div class="px-4 py-4">
20+
{{ field_diff.diff }}
21+
</div>
22+
</section>
23+
{% empty %}
24+
{% trans "There are no differences." as message %}
25+
{% include "unfold/helpers/messages/info.html" with message=message class="mb-0" %}
26+
{% endfor %}
27+
28+
<section class="rounded-default border border-base-200 p-4 shadow-xs dark:border-base-800">
29+
<h2 class="text-sm font-medium text-font-important-light dark:text-font-important-dark">
30+
{% trans "Revision comment" %}
31+
</h2>
32+
33+
<blockquote class="mt-3 rounded-default border border-base-200 bg-base-50 px-4 py-3 text-sm text-font-default-light dark:border-base-800 dark:bg-white/[.02] dark:text-font-default-dark">
34+
{{ version2.revision.comment|default:_("(no comment exists)") }}
35+
</blockquote>
36+
</section>
37+
38+
{% if has_unfollowed_fields %}
39+
{% blocktrans asvar note_message %}
40+
Fields or entries marked with <sup>*</sup> are not under reversion control.
41+
Some marked information may be incomplete or inaccurate.
42+
{% endblocktrans %}
43+
{% include "unfold/helpers/messages/warning.html" with message=note_message class="mb-0" %}
44+
{% endif %}
45+
</div>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
{% extends "admin/base_site.html" %}
2+
{% load i18n l10n admin_urls unfold %}
3+
4+
{% block content %}
5+
{% trans "Choose a date from the list below to revert to a previous version of this object." as message %}
6+
{% include "unfold/helpers/messages/info.html" with message=message %}
7+
8+
{% if action_list %}
9+
{% url opts|admin_urlname:'compare' object.pk as compare_base %}
10+
11+
<form method="get" action="{{ compare_base }}" class="flex flex-col gap-4">
12+
<div class="flex justify-end">
13+
{% if comparable %}
14+
{% component "unfold/components/button.html" with submit=1 variant="default" %}
15+
{% trans "Compare" %}
16+
{% endcomponent %}
17+
{% else %}
18+
<button type="submit"
19+
disabled
20+
class="font-medium inline-flex group items-center gap-1 relative rounded-default justify-center whitespace-nowrap px-3 py-2 border border-base-200 bg-white shadow-xs text-important cursor-not-allowed opacity-50 dark:border-base-700 dark:bg-transparent">
21+
{% trans "Compare" %}
22+
</button>
23+
{% endif %}
24+
</div>
25+
26+
<table id="change-history" class="border-base-200 border-spacing-none border-separate mb-2 w-full lg:border lg:rounded-default lg:shadow-xs lg:dark:border-base-800">
27+
<thead class="hidden lg:table-header-group text-base-900 dark:text-base-100">
28+
<tr>
29+
<th class="align-middle font-medium px-3 py-2 text-left lg:text-center">
30+
{% translate "Older" %}
31+
</th>
32+
<th class="align-middle font-medium px-3 py-2 text-left lg:text-center">
33+
{% translate "Newer" %}
34+
</th>
35+
<th class="align-middle font-medium px-3 py-2 text-left">
36+
{% translate "Date/time" %}
37+
</th>
38+
<th class="align-middle font-medium px-3 py-2 text-left">
39+
{% translate "User" %}
40+
</th>
41+
<th class="align-middle font-medium px-3 py-2 text-left">
42+
{% translate "Comment" %}
43+
</th>
44+
</tr>
45+
</thead>
46+
<tbody>
47+
{% for action in action_list %}
48+
<tr class="block border mb-3 rounded-default shadow-xs lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-base-800">
49+
<td class="align-middle flex border-t border-base-200 font-normal px-3 py-2 text-left before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell lg:text-center dark:border-base-800" data-label="{% translate 'Older' %}">
50+
{% if comparable %}
51+
<input type="radio"
52+
name="version_id1"
53+
value="{{ action.version.pk|unlocalize }}"
54+
class="appearance-none bg-white block border border-base-300 h-4 min-w-4 relative rounded-full w-4 dark:bg-base-900 dark:border-base-700 after:absolute after:bg-transparent after:content-[''] after:flex after:h-2 after:items-center after:justify-center after:leading-none after:left-1/2 after:rounded-full after:text-white after:top-1/2 after:-translate-x-1/2 after:-translate-y-1/2 after:w-2 dark:after:text-base-700 dark:after:bg-transparent checked:bg-primary-600 checked:border-primary-600 checked:after:bg-white dark:checked:after:bg-base-900 mx-auto lg:inline-block{% if action.first %} invisible{% endif %}"
55+
{% if version1.pk|unlocalize == action.version.pk|unlocalize or action.second %}checked="checked"{% endif %}>
56+
{% else %}
57+
&mdash;
58+
{% endif %}
59+
</td>
60+
<td class="align-middle flex border-t border-base-200 font-normal px-3 py-2 text-left before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell lg:text-center dark:border-base-800" data-label="{% translate 'Newer' %}">
61+
{% if comparable %}
62+
<input type="radio"
63+
name="version_id2"
64+
value="{{ action.version.pk|unlocalize }}"
65+
class="appearance-none bg-white block border border-base-300 h-4 min-w-4 relative rounded-full w-4 dark:bg-base-900 dark:border-base-700 after:absolute after:bg-transparent after:content-[''] after:flex after:h-2 after:items-center after:justify-center after:leading-none after:left-1/2 after:rounded-full after:text-white after:top-1/2 after:-translate-x-1/2 after:-translate-y-1/2 after:w-2 dark:after:text-base-700 dark:after:bg-transparent checked:bg-primary-600 checked:border-primary-600 checked:after:bg-white dark:checked:after:bg-base-900 mx-auto lg:inline-block"
66+
{% if version2.pk|unlocalize == action.version.pk|unlocalize or action.first == 1 %}checked="checked"{% endif %}>
67+
{% else %}
68+
&mdash;
69+
{% endif %}
70+
</td>
71+
<th class="align-middle flex border-t border-base-200 font-normal px-3 py-2 text-left before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-base-800" data-label="{% translate 'Date/time' %}">
72+
{% if action.url %}
73+
<a href="{{ action.url }}" class="text-primary-600 hover:underline dark:text-primary-500">
74+
{{ action.revision.date_created|date:"DATETIME_FORMAT" }}
75+
</a>
76+
{% else %}
77+
{{ action.revision.date_created|date:"DATETIME_FORMAT" }}
78+
{% endif %}
79+
</th>
80+
<td class="align-middle flex border-t border-base-200 font-normal px-3 py-2 text-left before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-base-800" data-label="{% translate 'User' %}">
81+
{% if action.revision.user %}
82+
{{ action.revision.user.get_username }}
83+
{% if action.revision.user.get_full_name %}
84+
({{ action.revision.user.get_full_name }})
85+
{% endif %}
86+
{% else %}
87+
&mdash;
88+
{% endif %}
89+
</td>
90+
<td class="align-middle flex border-t border-base-200 font-normal px-3 py-2 text-left before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-base-800" data-label="{% translate 'Comment' %}">
91+
{{ action.revision.comment|default:"" }}
92+
</td>
93+
</tr>
94+
{% endfor %}
95+
</tbody>
96+
</table>
97+
</form>
98+
{% else %}
99+
{% trans "This object doesn’t have a change history. It probably wasn’t added via this admin site." as message %}
100+
{% include "unfold/helpers/messages/warning.html" with message=message %}
101+
{% endif %}
102+
{% endblock %}

0 commit comments

Comments
 (0)