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
parent
23511d04b2
commit
cc1f72b23d
|
|
@ -170,6 +170,16 @@ class ThemeAdmin(admin.ModelAdmin):
|
|||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Change Form"),
|
||||
{
|
||||
"classes": ("wide",),
|
||||
"fields": (
|
||||
"show_fieldsets_as_tabs",
|
||||
"show_inlines_as_tabs",
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Recent Actions"),
|
||||
{"classes": ("wide",), "fields": ("recent_actions_visible",)},
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
),
|
||||
]
|
||||
|
|
@ -351,6 +351,14 @@ class Theme(models.Model):
|
|||
|
||||
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(
|
||||
default=True, verbose_name=_("visible")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -114,7 +114,8 @@
|
|||
href="{% static 'admin_interface/css/import-export.css' %}?v={{ version_md5_cache }}"/>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
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' %}
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v27.2.2/dist/font-face.css" />
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -80,6 +80,13 @@ def get_admin_interface_setting(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)
|
||||
def get_admin_interface_version():
|
||||
return __version__
|
||||
|
|
|
|||
|
|
@ -168,3 +168,9 @@ class AdminInterfaceTemplateTagsTestCase(TestCase):
|
|||
"{{ version_md5_hash }}"
|
||||
)
|
||||
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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue