Show nested fieldsets for form
This commit is contained in:
parent
5933881262
commit
412d344536
6 changed files with 122 additions and 3 deletions
|
@ -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.
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
0
src/servala/frontend/templatetags/__init__.py
Normal file
0
src/servala/frontend/templatetags/__init__.py
Normal file
8
src/servala/frontend/templatetags/get_field.py
Normal file
8
src/servala/frontend/templatetags/get_field.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def get_field(form, field_name):
|
||||||
|
return form[field_name]
|
Loading…
Add table
Add a link
Reference in a new issue