Skip to content

Commit

Permalink
feat: django-simple-history support (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasvinclav committed Oct 20, 2023
1 parent 69cc922 commit 8776908
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 6 deletions.
54 changes: 50 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,14 @@ Unfold is theme for Django admin incorporating most common practises for buildin
- [Actions overview](#actions-overview)
- [Custom unfold @action decorator](#custom-unfold-action-decorator)
- [Action handler functions](#action-handler-functions)
- [For submit row action](#for-submit-row-action)
- [For global, row and detail action](#for-global-row-and-detail-action)
- [Action examples](#action-examples)
- [Filters](#filters)
- [Third party packages](#third-party-packages)
- [django-import-export](#django-import-export)
- [django-modeltranslation](#django-modeltranslation)
- [django-guardian](#django-guardian)
- [django-money](#django-money)
- [django-celery-beat](#django-celery-beat)
- [User Admin Form](#user-admin-form)
- [Adding Custom Styles and Scripts](#adding-custom-styles-and-scripts)
- [Project Level Tailwind Stylesheet](#project-level-tailwind-stylesheet)
Expand All @@ -73,6 +72,7 @@ INSTALLED_APPS = [
"unfold.contrib.forms", # optional, if special form elements are needed
"unfold.contrib.import_export", # optional, if django-import-export package is used
"unfold.contrib.guardian", # optional, if django-guardian package is used
"unfold.contrib.simple_history", # optional, if django-simple-history package is used
"django.contrib.admin", # required
]
```
Expand Down Expand Up @@ -378,13 +378,13 @@ Unfold also uses custom `@action` decorator, supporting 2 more parameters in com
This section provides explanation of how the action handler functions should be constructed for Unfold actions.
For default actions, follow official Django admin documentation.

#### For submit row action
#### For submit row action <!-- omit from toc -->

Submit row actions work a bit differently when compared to other custom Unfold actions.
These actions first invoke form save (same as if you hit `Save` button) and then lets you
perform additional logic on already saved instance.

#### For global, row and detail action
#### For global, row and detail action <!-- omit from toc -->

All these actions are based on custom URLs generated for each of them. Handler function for these views is
basically function based view.
Expand Down Expand Up @@ -576,6 +576,52 @@ Adding support for django-guardian is quote straightforward in Unfold, just add

This application is supported in Unfold by default. It is not needed to add any other applications into `INSTALLED_APPS`. Unfold is recognizing special form widget coming from django-money and applying specific styling.

### django-celery-beat

In general, django-celery-beat does not have any components that require special styling. The default changelist templates are not inheriting from Unfold's `ModelAdmin` but they are using default `ModelAdmin` coming from `django.contrib.admin` which is causing some design discrepancies in the changelist.

In the source code below you can find a short code snippet to unregister all `django-celery-beat` admin classes and register them with the proper parent `ModelAdmin` class.

```python
# admin.py
from django.contrib import admin
from unfold.admin import ModelAdmin

from django_celery_beat.models import (
ClockedSchedule,
CrontabSchedule,
IntervalSchedule,
PeriodicTask,
SolarSchedule,
)


admin.site.unregister(PeriodicTask)
admin.site.unregister(IntervalSchedule)
admin.site.unregister(CrontabSchedule)
admin.site.unregister(SolarSchedule)
admin.site.unregister(ClockedSchedule)

@admin.register(PeriodicTask)
class PeriodicTaskAdmin(ModelAdmin):
pass


@admin.register(IntervalSchedule)
class IntervalScheduleAdmin(ModelAdmin):
pass


@admin.register(CrontabSchedule)
class CrontabScheduleAdmin(ModelAdmin):
pass


@admin.register(SolarSchedule)
class SolarScheduleAdmin(ModelAdmin):
pass
```

## User Admin Form

User's admin in Django is specific as it contains several forms which are requiring custom styling. All of these forms has been inherited and accordingly adjusted. In user admin class it is needed to use these inherited form classes to enable custom styling matching rest of the website.
Expand Down
Empty file.
6 changes: 6 additions & 0 deletions src/unfold/contrib/simple_history/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class SimpleHistoryConfig(AppConfig):
name = "unfold.contrib.simple_history"
label = "unfold_simple_history"
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{% load i18n %}
{% load url from simple_history_compat %}
{% load admin_urls %}
{% load getattribute from getattributes %}

<table id="change-history" class="border-gray-200 border-spacing-none border-separate mb-6 text-gray-700 w-full dark:text-gray-400 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
<thead class="hidden lg:table-header-group">
<tr>
<th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
{% trans 'Object' %}
</th>

{% for column in history_list_display %}
<th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
{% trans column %}
</th>
{% endfor %}

<th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
{% trans 'Date/time' %}
</th>

<th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
{% trans 'Comment' %}
</th>

<th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
{% trans 'Changed by' %}
</th>

<th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
{% trans 'Change reason' %}
</th>
</tr>
</thead>

<tbody>
{% for action in action_list %}
<tr class="block border mb-3 rounded-md shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-gray-800">
<td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Object' %}">
<a href="{% url opts|admin_urlname:'simple_history' object.pk action.pk %}">
{{ action.history_object }}
</a>
</td>

{% for column in history_list_display %}
<td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans column %}">
{{ action|getattribute:column }}
</th>
{% endfor %}

<td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Date/time' %}">
{{ action.history_date }}
</td>

<td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Comment' %}">
{{ action.get_history_type_display }}
</td>

<td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Changed by' %}">
{% if action.history_user %}
{% url admin_user_view action.history_user_id as admin_user_url %}

{% if admin_user_url %}
<a href="{{ admin_user_url }}">{{ action.history_user }}</a>
{% else %}
{{ action.history_user }}
{% endif %}
{% else %}
{% trans "None" %}
{% endif %}
</td>

<td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Change reason' %}">
{{ action.history_change_reason }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% extends "admin/object_history.html" %}

{% load admin_urls i18n %}
{% load url from simple_history_compat %}
{% load display_list from simple_history_admin_list %}

{% block content %}
{% if not revert_disabled %}
<p class="px-3 py-3 rounded-md text-sm last:mb-8 er-amber-600/10 bg-blue-50 mb-8 text-blue-500 dark:bg-blue-500/20 dark:border-blue-500/10">
{% blocktrans %}Choose a date from the list below to revert to a previous version of this object.{% endblocktrans %}
</p>
{% endif %}

{% if action_list %}
{% display_list %}
{% else %}
<p class="mb-3 px-3 py-3 rounded-md text-sm last:mb-8 bg-amber-100 text-amber-600 dark:bg-amber-600/20 dark:border-amber-600/10">
{% trans "This object doesn't have a change history." %}
</p>
{% endif %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{% extends "admin/change_form.html" %}

{% load admin_urls i18n %}
{% load url from simple_history_compat%}

{% block breadcrumbs %}
<div class="px-4 lg:px-12">
<div class="container mb-6 mx-auto -my-3 lg:mb-12">
<ul class="flex">
{% url 'admin:index' as link %}
{% trans 'Home' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=name %}

{% url 'admin:app_list' app_label=opts.app_label as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.app_config.verbose_name %}

{% include 'unfold/helpers/breadcrumb_item.html' with link=changelist_url name=opts.verbose_name_plural|capfirst %}

{% include 'unfold/helpers/breadcrumb_item.html' with link=change_url name=original|truncatewords:'18' %}

{% url opts|admin_urlname:'change' object.pk|admin_urlquote as link %}
{% trans 'History' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link="../" name=name %}

{% if revert_disabled %}
{% trans "View" as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link="" name=name %}
{% else %}
{% trans "Revert" as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link="" name=name %}
{% endif %}
</ul>
</div>
</div>
{% endblock %}

{% block submit_buttons_top %}
{% include "simple_history/submit_line.html" %}
{% endblock %}

{% block submit_buttons_bottom %}
{% include "simple_history/submit_line.html" %}
{% endblock %}

{% block form_top %}
<p class="px-3 py-3 rounded-md text-sm last:mb-8 er-amber-600/10 bg-blue-50 mb-8 text-blue-500 dark:bg-blue-500/20 dark:border-blue-500/10">
{% if not revert_disabled %}
{% blocktrans %}Press the 'Revert' button below to revert to this version of the object.{% endblocktrans %}
{% endif %}

{% if change_history %}
{% blocktrans %}Press the 'Change History' button below to edit the history.{% endblocktrans %}
{% endif %}
</p>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% load i18n %}

<div {% if not is_popup %}id="submit-row"{% endif %} class="relative z-20 {% if not is_popup %} mt-20 lg:mt-8{% endif %}">
<div class="{% if not is_popup %}bottom-0 fixed left-0 right-0 xl:left-72{% endif %}" :class="{'xl:left-0': !sidebarDesktopOpen}">
<div class="bg-white dark:bg-gray-900 {% if not is_popup %}border-t px-4 py-4 relative scrollable-top lg:px-12 dark:border-gray-800{% endif %}">
<div class="container flex flex-col-reverse gap-3 items-center mx-auto lg:flex-row-reverse">
{% if not revert_disabled %}
<button type="submit" class="bg-primary-600 block border border-transparent font-medium px-3 py-2 rounded-md text-sm text-white w-full lg:w-auto" name="_save" {{ onclick_attrib }}>
{% trans 'Revert' %}
</button>
{% endif %}

{% if change_history %}
<button type="submit" class="bg-primary-600 block border border-transparent font-medium px-3 py-2 rounded-md text-sm text-white w-full lg:w-auto" name="_change_history" {{ onclick_attrib }}>
{% trans 'Change History' %}
</button>
{% endif %}

<a href="{{ history_url }}" class="border font-medium hidden mr-auto px-3 py-2 rounded-md text-sm text-gray-500 transition-all w-full hover:bg-gray-50 lg:block lg:w-auto dark:border-gray-700 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-900">
{% trans 'Close' %}
</a>
</div>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion src/unfold/static/unfold/css/styles.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/unfold/templates/admin/edit_inline/tabular.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ <h2 class="bg-gray-100 border border-transparent font-semibold mb-6 px-4 py-3 ro
{% if field.is_readonly or not field.field.is_hidden %}
<td{% if field.field.name %} class="field-{{ field.field.name }}{% if field.field.errors|length > 0 %} errors{% endif %}{% if inline_admin_form.original %} p-3 lg:py-3{% else %} py-3{% endif %}{% if field.is_checkbox %} align-middle{% else %} align-top{% endif %} {% if is_last_row and not inline_admin_formset.has_add_permission %}{% if is_last_col %}border-0 {% else %}border-b lg:border-0{% endif %}{% else %}border-b{% endif %} border-gray-200 flex items-center before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 before:w-72 lg:before:hidden font-normal px-3 text-left text-sm lg:table-cell dark:border-gray-800"{% endif %} data-label="{{ field.field.label }}">
{% if field.is_readonly %}
<p class="bg-gray-50 border font-medium px-3 py-2 rounded-md shadow-sm text-gray-500 text-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800">
<p class="bg-gray-50 border font-medium max-w-lg px-3 py-2 rounded-md shadow-sm text-gray-500 text-sm truncate whitespace-nowrap dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800">
{{ field.contents }}
</p>
{% else %}
Expand Down

0 comments on commit 8776908

Please sign in to comment.