Skip to content

Commit b412b20

Browse files
authored
feat: fieldset as tabs (#249)
1 parent 06efaa5 commit b412b20

File tree

5 files changed

+99
-6
lines changed

5 files changed

+99
-6
lines changed

README.md

+52-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ Did you decide to start using Unfold but you don't have time to make the switch
3030
- **WYSIWYG:** built-in support for WYSIWYG (Trix)
3131
- **Custom filters:** widgets for filtering number & datetime values
3232
- **Dashboard:** custom components for rapid dashboard development
33-
- **Tabs:** define custom tab navigations for models
33+
- **Model tabs:** define custom tab navigations for models
34+
- **Fieldset tabs:** merge several fielsets into tabs in change form
3435
- **Colors:** possibility to override default color scheme
3536
- **Third party packages:** default support for multiple popular applications
3637
- **Environment label**: distinguish between environments by displaying a label
@@ -50,6 +51,7 @@ Did you decide to start using Unfold but you don't have time to make the switch
5051
- [Numeric filters](#numeric-filters)
5152
- [Date/time filters](#datetime-filters)
5253
- [Display decorator](#display-decorator)
54+
- [Change form tabs](#change-form-tabs)
5355
- [Third party packages](#third-party-packages)
5456
- [django-celery-beat](#django-celery-beat)
5557
- [django-guardian](#django-guardian)
@@ -582,6 +584,55 @@ class UserAdmin(ModelAdmin):
582584
return "First main heading", "Smaller additional description", "AB"
583585
```
584586

587+
## Change form tabs
588+
589+
When the change form contains a lot of fieldsets, sometimes it is better to group them into tabs so it will not be needed to scroll. To mark a fieldset for tab navigation it is required to add a `tab` CSS class to the fieldset. Once the fieldset contains `tab` class it will be recognized in a template and grouped into tab navigation. Each tab must contain its name. If the name is not available, it will be not included in the tab navigation.
590+
591+
```python
592+
# admin.py
593+
594+
from django.contrib import admin
595+
from django.utils.translation import gettext_lazy as _
596+
from unfold.admin import ModelAdmin
597+
598+
from .models import MyModel
599+
600+
601+
@admin.register(MyModel)
602+
class MyModelAdmin(ModelAdmin):
603+
fieldsets = (
604+
(
605+
None,
606+
{
607+
"fields": [
608+
"field_1",
609+
"field_2",
610+
],
611+
},
612+
),
613+
(
614+
_("Tab 1"),
615+
{
616+
"classes": ["tab"],
617+
"fields": [
618+
"field_3",
619+
"field_4",
620+
],
621+
},
622+
),
623+
(
624+
_("Tab 2"),
625+
{
626+
"classes": ["tab"],
627+
"fields": [
628+
"field_5",
629+
"field_6",
630+
],
631+
},
632+
),
633+
)
634+
```
635+
585636
## Third party packages
586637

587638
### django-celery-beat

src/unfold/templates/admin/change_form.html

+5-1
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,12 @@
9595

9696
{% block field_sets %}
9797
{% for fieldset in adminform %}
98-
{% include 'admin/includes/fieldset.html' %}
98+
{% if "tab" not in fieldset.classes %}
99+
{% include 'admin/includes/fieldset.html' %}
100+
{% endif %}
99101
{% endfor %}
102+
103+
{% include "unfold/helpers/fieldsets_tabs.html" %}
100104
{% endblock %}
101105

102106
{% block after_field_sets %}{% endblock %}

src/unfold/templates/admin/includes/fieldset.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{% load unfold %}
22

3-
<fieldset class="module {{ fieldset.classes }}">
4-
{% if fieldset.name %}
5-
<h2 class="bg-gray-100 border border-transparent font-semibold mb-6 px-4 py-3 rounded-md text-gray-900 text-sm lg:-mx-4 dark:bg-white/[.02] dark:border dark:border-gray-800 dark:text-gray-200">
3+
<fieldset class="module{% if fieldset.classes %} {{ fieldset.classes }}{% endif %}">
4+
{% if fieldset.name and "tab" not in fieldset.classes %}
5+
<h2 class="bg-gray-100 border border-transparent font-semibold mb-6 px-4 py-3 rounded-md text-gray-700 text-sm lg:-mx-4 dark:bg-white/[.02] dark:border dark:border-gray-800 dark:text-gray-200">
66
{{ fieldset.name }}
77
</h2>
88
{% endif %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{% load unfold %}
2+
3+
{% with tabs=adminform|tabs %}
4+
{% if tabs %}
5+
<div x-data="{openTab: null}">
6+
<ul class="bg-gray-100 border border-transparent flex gap-10 mb-6 px-4 py-3 rounded-md text-gray-400 text-sm lg:-mx-4 dark:bg-white/[.02] dark:border dark:border-gray-800 dark:text-gray-400">
7+
{% for fieldset in tabs %}
8+
<li>
9+
<a class="cursor-pointer font-semibold hover:text-gray-700 dark:hover:text-white"
10+
x-on:click="openTab = '{{ fieldset.name|slugify }}'"
11+
x-bind:class="openTab == '{{ fieldset.name|slugify }}'{% if forloop.first %} || openTab == null{% endif %} ? 'text-gray-700 dark:text-white' : ''">
12+
{{ fieldset.name }}
13+
</a>
14+
</li>
15+
{% endfor %}
16+
</ul>
17+
18+
{% for fieldset in tabs %}
19+
<div class="tab-wrapper{% if fieldset.name %} fieldset-{{ fieldset.name|slugify }}{% endif %}"
20+
x-show="openTab == '{{ fieldset.name|slugify }}'{% if forloop.first %} || openTab == null{% endif %}">
21+
{% include 'admin/includes/fieldset.html' %}
22+
</div>
23+
{% endfor %}
24+
</div>
25+
{% endif %}
26+
{% endwith %}

src/unfold/templatetags/unfold.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from typing import Any, Dict, Mapping, Optional, Union
1+
from typing import Any, Dict, List, Mapping, Optional, Union
22

33
from django import template
4+
from django.contrib.admin.helpers import AdminForm, Fieldset
45
from django.forms import Field
56
from django.template import Library, Node, RequestContext, TemplateSyntaxError
67
from django.template.base import NodeList, Parser, Token, token_kwargs
@@ -42,6 +43,17 @@ def index(indexable: Mapping[int, Any], i: int) -> Any:
4243
return indexable[i]
4344

4445

46+
@register.filter
47+
def tabs(adminform: AdminForm) -> List[Fieldset]:
48+
result = []
49+
50+
for fieldset in adminform:
51+
if "tab" in fieldset.classes and fieldset.name:
52+
result.append(fieldset)
53+
54+
return result
55+
56+
4557
class CaptureNode(Node):
4658
def __init__(self, nodelist: NodeList, varname: str, silent: bool) -> None:
4759
self.nodelist = nodelist

0 commit comments

Comments
 (0)