Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions admin_interface/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",)},
Expand Down
Original file line number Diff line number Diff line change
@@ -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"),
),
]
8 changes: 8 additions & 0 deletions admin_interface/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,14 @@ def get_active_theme(database="default"):

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")
)
Expand Down
28 changes: 28 additions & 0 deletions admin_interface/static/admin_interface/css/tabbed-changeform.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.admin-interface .tabbed-changeform-tab {
display: flex;
flex-direction: row;
gap: 0.75em;
flex-wrap: wrap;
}

.admin-interface .tabbed-changeform-tab button {
border: none;
cursor: pointer;
padding: 8px 12px;
transition: 0.3s;
border-radius: var(--admin-interface-module-border-radius);
}

.admin-interface .tabbed-changeform-tab button.active {
background-color: var(--admin-interface-module-background-color);
color: var(--admin-interface-module-text-color);
}

.admin-interface .tabbed-changeform-tabcontent {
display: none;
padding: 1em 0;
}

.admin-interface .tabbed-changeform-tabcontent.active {
display: block;
}
14 changes: 14 additions & 0 deletions admin_interface/static/admin_interface/js/tabbed_changeform.js
Original file line number Diff line number Diff line change
@@ -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");
}
3 changes: 2 additions & 1 deletion admin_interface/templates/admin/base_site.html
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]/dist/font-face.css" />
Expand Down
73 changes: 73 additions & 0 deletions admin_interface/templates/admin/change_form.html
Original file line number Diff line number Diff line change
@@ -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}}')">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any appetite for making the tabs work purely with CSS?

https://www.sitepoint.com/css3-tabs-using-target-selector/

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoiding JavaScript at all for this feature would be great!

Copy link
Contributor Author

@VaZark VaZark Nov 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find that using JS is the most readable way to handle the logic.

Most CSS-only solutions I've come across are not easy to implement to be responsive and handle a dynamic no of tabs 😓

You're welcome to change it up though. 👍

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I have not any experience with CSS only tabs, I can't evaluate implementation complexity.

{{ 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 %}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
30 changes: 30 additions & 0 deletions admin_interface/templates/admin/includes/headerless_fieldset.html
Original file line number Diff line number Diff line change
@@ -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>
7 changes: 7 additions & 0 deletions admin_interface/templatetags/admin_interface_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
6 changes: 6 additions & 0 deletions tests/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,9 @@ def test_get_version_nocache(self):
"{{ 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")