Add tabbed changeform support (#211).

* WIP : basic templates

* Add params

* Working override

* Use headerless version only during tabs

* Move CSS to separate file

* Extract js to static folder

* script is not self-closing

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* use classList for updating classes

* Add EOF (newline) to new text files

* a simple test to keep up code coverage

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fieldsets are not tabbed by default

* rename templatetag

* use default page if show_*_tabs are false

* Narrow down css to admin-interface

* Fix typo

* keep codacy happy

* prefix tab classes with tabbed-changeform-

* horizontal scrolling

* Update colors

* color updates

* add back missing font bold

Co-authored-by: vaz <vmohan@lenbox.io>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
master
Vasanth 2022-11-26 09:54:33 +01:00 committed by GitHub
parent 23511d04b2
commit cc1f72b23d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 293 additions and 1 deletions

View File

@ -170,6 +170,16 @@ class ThemeAdmin(admin.ModelAdmin):
), ),
}, },
), ),
(
_("Change Form"),
{
"classes": ("wide",),
"fields": (
"show_fieldsets_as_tabs",
"show_inlines_as_tabs",
),
},
),
( (
_("Recent Actions"), _("Recent Actions"),
{"classes": ("wide",), "fields": ("recent_actions_visible",)}, {"classes": ("wide",), "fields": ("recent_actions_visible",)},

View File

@ -0,0 +1,23 @@
# Generated by Django 4.1.3 on 2022-11-23 11:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("admin_interface", "0027_theme_list_filter_removal_links"),
]
operations = [
migrations.AddField(
model_name="theme",
name="show_fieldsets_as_tabs",
field=models.BooleanField(default=False, verbose_name="fieldsets as tabs"),
),
migrations.AddField(
model_name="theme",
name="show_inlines_as_tabs",
field=models.BooleanField(default=True, verbose_name="inlines as tabs"),
),
]

View File

@ -351,6 +351,14 @@ class Theme(models.Model):
foldable_apps = models.BooleanField(default=True, verbose_name=_("foldable apps")) foldable_apps = models.BooleanField(default=True, verbose_name=_("foldable apps"))
show_fieldsets_as_tabs = models.BooleanField(
default=False, verbose_name=_("fieldsets as tabs")
)
show_inlines_as_tabs = models.BooleanField(
default=True, verbose_name=_("inlines as tabs")
)
recent_actions_visible = models.BooleanField( recent_actions_visible = models.BooleanField(
default=True, verbose_name=_("visible") default=True, verbose_name=_("visible")
) )

View File

@ -0,0 +1,35 @@
.admin-interface .tabbed-changeform-tab {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: auto;
}
.admin-interface .tabbed-changeform-tab button {
border: none;
border-bottom: 1px solid var(--border-color) ;
flex-shrink: 0;
flex-grow: 0;
cursor: pointer;
padding: 8px 12px;
background-color: var(--admin-interface-module-header-text-color);
color: var(--admin-interface-module-background-color);
}
.admin-interface .tabbed-changeform-tab button.active {
font-weight: bold;
border: 1px solid var(--border-color) ;
border-bottom: none;
border-radius: var(--admin-interface-module-border-radius);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.admin-interface .tabbed-changeform-tabcontent {
display: none;
padding: 1em 0;
}
.admin-interface .tabbed-changeform-tabcontent.active {
display: block;
}

View File

@ -0,0 +1,14 @@
function openTab(evt, tabName) {
var tabcontents, tablinks;
tabcontents = document.getElementsByClassName("tabbed-changeform-tabcontent");
for (let tabcontent of tabcontents) {
tabcontent.classList.remove("active");
}
tablinks = document.getElementsByClassName("tabbed-changeform-tablinks");
for (let tablink of tablinks) {
tablink.classList.remove("active");
}
document.getElementById(tabName).classList.add("active");
evt.currentTarget.classList.add("active");
}

View File

@ -114,7 +114,8 @@
href="{% static 'admin_interface/css/import-export.css' %}?v={{ version_md5_cache }}"/> href="{% static 'admin_interface/css/import-export.css' %}?v={{ version_md5_cache }}"/>
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="{% static 'admin_interface/css/rtl.css' %}?v={{ version_md5_cache }}"/> href="{% static 'admin_interface/css/rtl.css' %}?v={{ version_md5_cache }}"/>
<link rel="stylesheet" type="text/css"
href="{% static 'admin_interface/css/tabbed-changeform.css' %}?v={{ version_md5_cache }}">
{% if current_lang == 'fa' %} {% if current_lang == 'fa' %}
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v27.2.2/dist/font-face.css" /> <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v27.2.2/dist/font-face.css" />

View File

@ -0,0 +1,73 @@
{% extends "admin/change_form.html" %}
{% load static admin_interface_tags %}
{% block field_sets %}
{% get_admin_interface_setting "show_fieldsets_as_tabs" as show_fieldsets_as_tabs %}
{% get_admin_interface_setting "show_inlines_as_tabs" as show_inlines_as_tabs %}
{% if not show_fieldsets_as_tabs and not show_inlines_as_tabs %}
{{block.super}}
{% else %}
<div class="tabbed-changeform-tab">
{% if show_fieldsets_as_tabs %}
{% for fieldset in adminform %}
<button type="button" class="tabbed-changeform-tablinks {{ forloop.counter0|default:"active" }}" onclick="openTab(event, '{{fieldset.name}}')">
{{ fieldset.name|default_if_none:opts.verbose_name|capfirst}}
</button>
{% endfor %}
{% else %}
<button type="button" class="tabbed-changeform-tablinks active" onclick="openTab(event, 'general')">
{{ opts.verbose_name|capfirst }}
</button>
{% endif %}
{% if show_inlines_as_tabs %}
{% for inline_admin_formset in inline_admin_formsets %}
<button type="button" class="tabbed-changeform-tablinks" onclick="openTab(event, '{{inline_admin_formset.opts.verbose_name_plural|capfirst}}')">
{{inline_admin_formset.opts.verbose_name_plural|capfirst}}
</button>
{% endfor %}
{% endif %}
</div>
{% if show_fieldsets_as_tabs %}
{% for fieldset in adminform %}
<div id="{{fieldset.name}}" class="tabbed-changeform-tabcontent {{ forloop.counter0|default:"active" }}">
{% include "admin/includes/headerless_fieldset.html" %}
</div>
{% endfor %}
{% else %}
<div id="general" class="tabbed-changeform-tabcontent active">
{% for fieldset in adminform %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
</div>
{% endif %}
{% for inline_admin_formset in inline_admin_formsets %}
<div id="{{inline_admin_formset.opts.verbose_name_plural|capfirst}}" class="tabbed-changeform-tabcontent">
{% get_admin_interface_inline_template inline_admin_formset.opts.template as inline_template %}
{% include inline_template %}
</div>
{% endfor %}
<script
type="text/javascript"
id="tabbed-changeform-script"
src="{% static "admin_interface/js/tabbed_changeform.js" %}"
>
</script>
{% endif %}
{% endblock %}
{% block inline_field_sets %}
{% get_admin_interface_setting "show_inlines_as_tabs" as show_inlines_as_tabs %}
{% if not show_inlines_as_tabs %}
{{block.super}}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,24 @@
{% load i18n admin_urls %}
<div class="js-inline-admin-formset inline-group"
id="{{ inline_admin_formset.formset.prefix }}-group"
data-inline-type="stacked"
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
<fieldset class="module {{ inline_admin_formset.classes }}">
{{ inline_admin_formset.formset.management_form }}
{{ inline_admin_formset.formset.non_form_errors }}
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last and inline_admin_formset.has_add_permission %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if forloop.last and inline_admin_formset.has_add_permission %}empty{% else %}{{ forloop.counter0 }}{% endif %}">
<h3><b>{{ inline_admin_formset.opts.verbose_name|capfirst }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="{{ inline_admin_formset.has_change_permission|yesno:'inlinechangelink,inlineviewlink' }}">{% if inline_admin_formset.has_change_permission %}{% translate "Change" %}{% else %}{% translate "View" %}{% endif %}</a>{% endif %}
{% else %}#{{ forloop.counter }}{% endif %}</span>
{% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% translate "View on site" %}</a>{% endif %}
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
</h3>
{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
{% for fieldset in inline_admin_form %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
</div>{% endfor %}
</fieldset>
</div>

View File

@ -0,0 +1,61 @@
{% load i18n admin_urls static admin_modify %}
<div class="js-inline-admin-formset inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"
data-inline-type="tabular"
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
{{ inline_admin_formset.formset.management_form }}
<fieldset class="module {{ inline_admin_formset.classes }}">
{{ inline_admin_formset.formset.non_form_errors }}
<table>
<thead><tr>
<th class="original"></th>
{% for field in inline_admin_formset.fields %}
<th class="column-{{ field.name }}{% if field.required %} required{% endif %}{% if field.widget.is_hidden %} hidden{% endif %}">{{ field.label|capfirst }}
{% if field.help_text %}<img src="{% static "admin/img/icon-unknown.svg" %}" class="help help-tooltip" width="10" height="10" alt="({{ field.help_text|striptags }})" title="{{ field.help_text|striptags }}">{% endif %}
</th>
{% endfor %}
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}<th>{% translate "Delete?" %}</th>{% endif %}
</tr></thead>
<tbody>
{% for inline_admin_form in inline_admin_formset %}
{% if inline_admin_form.form.non_field_errors %}
<tr class="row-form-errors"><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
{% endif %}
<tr class="form-row {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last and inline_admin_formset.has_add_permission %} empty-form{% endif %}"
id="{{ inline_admin_formset.formset.prefix }}-{% if forloop.last and inline_admin_formset.has_add_permission %}empty{% else %}{{ forloop.counter0 }}{% endif %}">
<td class="original">
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
{% if inline_admin_form.original %}
{{ inline_admin_form.original }}
{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %}<a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="{{ inline_admin_formset.has_change_permission|yesno:'inlinechangelink,inlineviewlink' }}">{% if inline_admin_formset.has_change_permission %}{% translate "Change" %}{% else %}{% translate "View" %}{% endif %}</a>{% endif %}
{% endif %}
{% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% translate "View on site" %}</a>{% endif %}
</p>{% endif %}
{% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
</td>
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
<td class="{% if field.field.name %}field-{{ field.field.name }}{% endif %}{% if field.field.is_hidden %} hidden{% endif %}">
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field.errors.as_ul }}
{{ field.field }}
{% endif %}
</td>
{% endfor %}
{% endfor %}
{% endfor %}
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</fieldset>
</div>
</div>

View File

@ -0,0 +1,30 @@
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% if line.fields|length == 1 %}{{ line.errors }}{% endif %}
{% for field in line %}
<div{% if not line.fields|length == 1 %} class="fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% endif %}>
{% if not line.fields|length == 1 and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<div class="readonly">{{ field.contents }}</div>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
</div>
{% if field.field.help_text %}
<div class="help"{% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
{{ field.field.help_text|safe }}
</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
</fieldset>

View File

@ -80,6 +80,13 @@ def get_admin_interface_setting(setting):
return getattr(theme, setting) return getattr(theme, setting)
@simple_tag()
def get_admin_interface_inline_template(template):
template_path = template.split("/")
template_path[-1] = "headerless_" + template_path[-1]
return "/".join(template_path)
@simple_tag(takes_context=False) @simple_tag(takes_context=False)
def get_admin_interface_version(): def get_admin_interface_version():
return __version__ return __version__

View File

@ -168,3 +168,9 @@ class AdminInterfaceTemplateTagsTestCase(TestCase):
"{{ version_md5_hash }}" "{{ version_md5_hash }}"
) )
self.assertEqual(rendered, hash_manual) self.assertEqual(rendered, hash_manual)
def test_get_admin_interface_inline_template(self):
headless_template = templatetags.get_admin_interface_inline_template(
"admin/edit_inline/stacked.html"
)
self.assertEqual(headless_template, "admin/edit_inline/headerless_stacked.html")