Show nested fieldsets for form

This commit is contained in:
Tobias Kunze 2025-03-31 13:27:25 +02:00
parent 5933881262
commit 412d344536
6 changed files with 122 additions and 3 deletions

View file

@ -147,6 +147,67 @@ class CrdModelFormMixin:
for field in ("organization", "context"): for field in ("organization", "context"):
self.fields[field].widget = forms.HiddenInput() self.fields[field].widget = forms.HiddenInput()
def get_fieldsets(self):
fieldsets = []
# General fieldset for non-spec fields
general_fields = [
field for field in self.fields if not field.startswith("spec.")
]
if general_fields:
fieldsets.append(
{"title": "General", "fields": general_fields, "fieldsets": []}
)
# Process spec fields
others = []
nested_fieldsets = {}
for field_name in self.fields:
if field_name.startswith("spec."):
parts = field_name.split(".")
if len(parts) == 2: # Top-level spec field
others.append(field_name)
else:
parent_key = parts[1]
if not nested_fieldsets.get(parent_key):
nested_fieldsets[parent_key] = {
"fields": [],
"fieldsets": {},
"title": parent_key.title(),
}
parent = nested_fieldsets[parent_key]
if len(parts) == 3: # Top-level within fieldset
parent["fields"].append(field_name)
else:
sub_key = parts[2]
if not parent["fieldsets"].get(sub_key):
parent["fieldsets"][sub_key] = {
"title": sub_key.title(),
"fields": [],
}
parent["fieldsets"][sub_key]["fields"].append(field_name)
# Add nested fieldsets to fieldsets
for group in nested_fieldsets.values():
total_fields = 0
for fieldset in group["fieldsets"].values():
if (field_count := len(fieldset["fields"])) == 1:
group["fields"].append(fieldset["fields"][0])
else:
total_fields += field_count
total_fields += len(group["fields"])
if total_fields == 1:
others.append(group["fields"][0])
else:
fieldsets.append(group)
# Add 'others' tab if there are any fields
if others:
fieldsets.append({"title": "Others", "fields": others, "fieldsets": []})
return fieldsets
def get_nested_data(self): def get_nested_data(self):
""" """
Builds the original nested JSON structure from flat form data. Builds the original nested JSON structure from flat form data.

View file

@ -1,6 +1,6 @@
{% load i18n %} {% load i18n %}
<div class="form-group{% if field.field.required %} mandatory{% endif %}{% if field.errors %} is-invalid{% endif %}{% if extra_class %} {{ extra_class }}{% endif %}"> <div class="form-group{% if field.field.required %} mandatory{% endif %}{% if field.errors %} is-invalid{% endif %}{% if extra_class %} {{ extra_class }}{% endif %}">
{% if not hide_label %} {% if not hide_label and not field.is_hidden %}
{% if field.field.widget.input_type != "checkbox" or field.field.widget.allow_multiple_selected %} {% if field.field.widget.input_type != "checkbox" or field.field.widget.allow_multiple_selected %}
<label for="{{ field.auto_id }}" class="form-label">{{ field.label }}</label> <label for="{{ field.auto_id }}" class="form-label">{{ field.label }}</label>
{% endif %} {% endif %}
@ -9,7 +9,7 @@
<fieldset {% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}> <fieldset {% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}>
{% endif %} {% endif %}
{{ field }} {{ field }}
{% if field.field.widget.input_type == "checkbox" and not field.field.widget.allow_multiple_selected %} {% if field.field.widget.input_type == "checkbox" and not field.field.widget.allow_multiple_selected and not field.is_hidden %}
<label for="{{ field.auto_id }}" class="form-check-label form-label">{{ field.label }}</label> <label for="{{ field.auto_id }}" class="form-check-label form-label">{{ field.label }}</label>
{% endif %} {% endif %}
{% if field.use_fieldset %}</fieldset>{% endif %} {% if field.use_fieldset %}</fieldset>{% endif %}

View file

@ -17,7 +17,7 @@
{% translate "Oops! Something went wrong with the service form generation. Please try again later." %} {% translate "Oops! Something went wrong with the service form generation. Please try again later." %}
</div> </div>
{% else %} {% else %}
{% include "includes/form.html" with form=service_form %} {% include "includes/tabbed_fieldset_form.html" with form=service_form %}
{% endif %} {% endif %}
</div> </div>
</div> </div>

View file

@ -0,0 +1,50 @@
{% load i18n %}
{% load get_field %}
<form class="form form-vertical"
method="post"
{% if form_action %}action="{{ form_action }}"{% endif %}>
{% csrf_token %}
<ul class="nav nav-tabs" id="myTab" role="tablist">
{% for fieldset in form.get_fieldsets %}
<li class="nav-item" role="presentation">
<button class="nav-link {% if forloop.first %}active{% endif %}"
id="{{ fieldset.title|slugify }}-tab"
data-bs-toggle="tab"
data-bs-target="#{{ fieldset.title|slugify }}"
type="button"
role="tab"
aria-controls="{{ fieldset.title|slugify }}"
aria-selected="{% if forloop.first %}true{% else %}false{% endif %}">
{{ fieldset.title }}
</button>
</li>
{% endfor %}
</ul>
<div class="tab-content" id="myTabContent">
{% for fieldset in form.get_fieldsets %}
<div class="tab-pane fade my-2 {% if forloop.first %}show active{% endif %}"
id="{{ fieldset.title|slugify }}"
role="tabpanel"
aria-labelledby="{{ fieldset.title|slugify }}-tab">
{% for field in fieldset.fields %}
{% with field=form|get_field:field %}{{ field.as_field_group }}{% endwith %}
{% endfor %}
{% for subfieldset in fieldset.fieldsets.values %}
<h4>{{ subfieldset.title }}</h4>
{% for field in subfieldset.fields %}
{% with field=form|get_field:field %}{{ field.as_field_group }}{% endwith %}
{% endfor %}
{% endfor %}
</div>
{% endfor %}
</div>
<div class="col-sm-12 d-flex justify-content-end">
<button class="btn btn-primary me-1 mb-1" type="submit">
{% if form_submit_label %}
{{ form_submit_label }}
{% else %}
{% translate "Save" %}
{% endif %}
</button>
</div>
</form>

View file

@ -0,0 +1,8 @@
from django import template
register = template.Library()
@register.filter
def get_field(form, field_name):
return form[field_name]