From cc1f72b23da59590ae1463c973e588145b4ad231 Mon Sep 17 00:00:00 2001 From: Vasanth Date: Sat, 26 Nov 2022 09:54:33 +0100 Subject: [PATCH] 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 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- admin_interface/admin.py | 10 +++ ...8_theme_show_fieldsets_as_tabs_and_more.py | 23 ++++++ admin_interface/models.py | 8 ++ .../admin_interface/css/tabbed-changeform.css | 35 +++++++++ .../admin_interface/js/tabbed_changeform.js | 14 ++++ .../templates/admin/base_site.html | 3 +- .../templates/admin/change_form.html | 73 +++++++++++++++++++ .../admin/edit_inline/headerless_stacked.html | 24 ++++++ .../admin/edit_inline/headerless_tabular.html | 61 ++++++++++++++++ .../admin/includes/headerless_fieldset.html | 30 ++++++++ .../templatetags/admin_interface_tags.py | 7 ++ tests/test_templatetags.py | 6 ++ 12 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 admin_interface/migrations/0028_theme_show_fieldsets_as_tabs_and_more.py create mode 100644 admin_interface/static/admin_interface/css/tabbed-changeform.css create mode 100644 admin_interface/static/admin_interface/js/tabbed_changeform.js create mode 100644 admin_interface/templates/admin/change_form.html create mode 100644 admin_interface/templates/admin/edit_inline/headerless_stacked.html create mode 100644 admin_interface/templates/admin/edit_inline/headerless_tabular.html create mode 100644 admin_interface/templates/admin/includes/headerless_fieldset.html diff --git a/admin_interface/admin.py b/admin_interface/admin.py index f474fd9..c70e3a9 100644 --- a/admin_interface/admin.py +++ b/admin_interface/admin.py @@ -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",)}, diff --git a/admin_interface/migrations/0028_theme_show_fieldsets_as_tabs_and_more.py b/admin_interface/migrations/0028_theme_show_fieldsets_as_tabs_and_more.py new file mode 100644 index 0000000..30c0bc3 --- /dev/null +++ b/admin_interface/migrations/0028_theme_show_fieldsets_as_tabs_and_more.py @@ -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"), + ), + ] diff --git a/admin_interface/models.py b/admin_interface/models.py index 980808b..25970ee 100644 --- a/admin_interface/models.py +++ b/admin_interface/models.py @@ -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") ) diff --git a/admin_interface/static/admin_interface/css/tabbed-changeform.css b/admin_interface/static/admin_interface/css/tabbed-changeform.css new file mode 100644 index 0000000..9a40d09 --- /dev/null +++ b/admin_interface/static/admin_interface/css/tabbed-changeform.css @@ -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; +} diff --git a/admin_interface/static/admin_interface/js/tabbed_changeform.js b/admin_interface/static/admin_interface/js/tabbed_changeform.js new file mode 100644 index 0000000..ef679f3 --- /dev/null +++ b/admin_interface/static/admin_interface/js/tabbed_changeform.js @@ -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"); +} diff --git a/admin_interface/templates/admin/base_site.html b/admin_interface/templates/admin/base_site.html index 13a6925..9c8fb8c 100644 --- a/admin_interface/templates/admin/base_site.html +++ b/admin_interface/templates/admin/base_site.html @@ -114,7 +114,8 @@ href="{% static 'admin_interface/css/import-export.css' %}?v={{ version_md5_cache }}"/> - + {% if current_lang == 'fa' %} diff --git a/admin_interface/templates/admin/change_form.html b/admin_interface/templates/admin/change_form.html new file mode 100644 index 0000000..b281fda --- /dev/null +++ b/admin_interface/templates/admin/change_form.html @@ -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 %} + +
+ + {% if show_fieldsets_as_tabs %} + {% for fieldset in adminform %} + + {% endfor %} + {% else %} + + {% endif %} + + {% if show_inlines_as_tabs %} + {% for inline_admin_formset in inline_admin_formsets %} + + {% endfor %} + {% endif %} +
+ + {% if show_fieldsets_as_tabs %} + {% for fieldset in adminform %} +
+ {% include "admin/includes/headerless_fieldset.html" %} +
+ {% endfor %} + {% else %} +
+ {% for fieldset in adminform %} + {% include "admin/includes/fieldset.html" %} + {% endfor %} +
+ {% endif %} + + {% for inline_admin_formset in inline_admin_formsets %} +
+ {% get_admin_interface_inline_template inline_admin_formset.opts.template as inline_template %} + {% include inline_template %} +
+ {% endfor %} + +{% 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 %} diff --git a/admin_interface/templates/admin/edit_inline/headerless_stacked.html b/admin_interface/templates/admin/edit_inline/headerless_stacked.html new file mode 100644 index 0000000..9bc1cbd --- /dev/null +++ b/admin_interface/templates/admin/edit_inline/headerless_stacked.html @@ -0,0 +1,24 @@ +{% load i18n admin_urls %} +
+
+{{ inline_admin_formset.formset.management_form }} +{{ inline_admin_formset.formset.non_form_errors }} + +{% for inline_admin_form in inline_admin_formset %}
+

{{ inline_admin_formset.opts.verbose_name|capfirst }}: {% 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 %} {% if inline_admin_formset.has_change_permission %}{% translate "Change" %}{% else %}{% translate "View" %}{% endif %}{% endif %} +{% else %}#{{ forloop.counter }}{% endif %} + {% if inline_admin_form.show_url %}{% translate "View on site" %}{% endif %} + {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %} +

+ {% 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 %} +
{% endfor %} +
+
diff --git a/admin_interface/templates/admin/edit_inline/headerless_tabular.html b/admin_interface/templates/admin/edit_inline/headerless_tabular.html new file mode 100644 index 0000000..ed9b56a --- /dev/null +++ b/admin_interface/templates/admin/edit_inline/headerless_tabular.html @@ -0,0 +1,61 @@ +{% load i18n admin_urls static admin_modify %} +
+ +
diff --git a/admin_interface/templates/admin/includes/headerless_fieldset.html b/admin_interface/templates/admin/includes/headerless_fieldset.html new file mode 100644 index 0000000..fdb5fde --- /dev/null +++ b/admin_interface/templates/admin/includes/headerless_fieldset.html @@ -0,0 +1,30 @@ +
+ {% if fieldset.description %} +
{{ fieldset.description|safe }}
+ {% endif %} + {% for line in fieldset %} +
+ {% if line.fields|length == 1 %}{{ line.errors }}{% endif %} + {% for field in line %} + + {% 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 %} +
{{ field.contents }}
+ {% else %} + {{ field.field }} + {% endif %} + {% endif %} +
+ {% if field.field.help_text %} +
+ {{ field.field.help_text|safe }} +
+ {% endif %} + {% endfor %} + + {% endfor %} +
diff --git a/admin_interface/templatetags/admin_interface_tags.py b/admin_interface/templatetags/admin_interface_tags.py index 6ee25cc..ea4bb2d 100644 --- a/admin_interface/templatetags/admin_interface_tags.py +++ b/admin_interface/templatetags/admin_interface_tags.py @@ -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__ diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index 1751d3f..359b8f4 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -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")