Compare commits

...

3 commits

Author SHA1 Message Date
2beae209cd Merge pull request 'Fix mandatory field detection' (#175) from 156-mandatory-field-handling into main
All checks were successful
Build and Deploy Staging / build (push) Successful in 48s
Tests / test (push) Successful in 25s
Build and Deploy Staging / deploy (push) Successful in 8s
Reviewed-on: #175
2025-09-05 13:38:17 +00:00
36387e76f9 Make mandatory fields in CRD forms more prominent
All checks were successful
Tests / test (push) Successful in 25s
2025-09-05 14:51:46 +02:00
c52ffee699 Fix mandatory field detection 2025-09-05 14:26:44 +02:00
3 changed files with 75 additions and 6 deletions

View file

@ -49,7 +49,7 @@ def generate_django_model(schema, group, version, kind):
# resourceRef object # resourceRef object
spec = schema["properties"].get("spec") or {} spec = schema["properties"].get("spec") or {}
spec["properties"].pop("resourceRef", None) spec["properties"].pop("resourceRef", None)
model_fields.update(build_object_fields(spec, "spec", parent_required=True)) model_fields.update(build_object_fields(spec, "spec", parent_required=False))
# Store the original schema on the model class # Store the original schema on the model class
model_fields["SCHEMA"] = schema model_fields["SCHEMA"] = schema
@ -69,7 +69,7 @@ def build_object_fields(schema, name, verbose_name_prefix=None, parent_required=
fields = {} fields = {}
for field_name, field_schema in properties.items(): for field_name, field_schema in properties.items():
is_required = field_name in required_fields and parent_required is_required = field_name in required_fields or parent_required
full_name = f"{name}.{field_name}" full_name = f"{name}.{field_name}"
result = get_django_field( result = get_django_field(
field_schema, field_schema,
@ -229,6 +229,12 @@ class CrdModelFormMixin:
if field and field.label and (position := field.label.find(label)) != -1: if field and field.label and (position := field.label.find(label)) != -1:
field.label = field.label[position + len(label) :] field.label = field.label[position + len(label) :]
def has_mandatory_fields(self, field_list):
for field_name in field_list:
if field_name in self.fields and self.fields[field_name].required:
return True
return False
def get_fieldsets(self): def get_fieldsets(self):
fieldsets = [] fieldsets = []
@ -239,7 +245,12 @@ class CrdModelFormMixin:
if not field_name.startswith("spec.") if not field_name.startswith("spec.")
] ]
if general_fields: if general_fields:
fieldset = {"title": "General", "fields": general_fields, "fieldsets": []} fieldset = {
"title": "General",
"fields": general_fields,
"fieldsets": [],
"has_mandatory": self.has_mandatory_fields(general_fields),
}
if all( if all(
[ [
isinstance(self.fields[field].widget, forms.HiddenInput) isinstance(self.fields[field].widget, forms.HiddenInput)
@ -315,11 +326,24 @@ class CrdModelFormMixin:
title = f"{fieldset['title']}: " title = f"{fieldset['title']}: "
for field in fieldset["fields"]: for field in fieldset["fields"]:
self.strip_title(field, title) self.strip_title(field, title)
all_fields = fieldset["fields"][:]
for sub_fieldset in nested_fieldsets_list:
all_fields.extend(sub_fieldset["fields"])
fieldset["has_mandatory"] = self.has_mandatory_fields(all_fields)
fieldsets.append(fieldset) fieldsets.append(fieldset)
# Add 'others' tab if there are any fields # Add 'others' tab if there are any fields
if others: if others:
fieldsets.append({"title": "Others", "fields": others, "fieldsets": []}) fieldsets.append(
{
"title": "Others",
"fields": others,
"fieldsets": [],
"has_mandatory": self.has_mandatory_fields(others),
}
)
if hidden_spec_fields: if hidden_spec_fields:
fieldsets.append( fieldsets.append(
@ -328,6 +352,7 @@ class CrdModelFormMixin:
"fields": hidden_spec_fields, "fields": hidden_spec_fields,
"fieldsets": [], "fieldsets": [],
"hidden": True, "hidden": True,
"has_mandatory": self.has_mandatory_fields(hidden_spec_fields),
} }
) )

View file

@ -1,6 +1,6 @@
{% load i18n %} {% load i18n %}
{% load get_field %} {% load get_field %}
<form class="form form-vertical" <form class="form form-vertical crd-form"
method="post" method="post"
{% if form_action %}action="{{ form_action }}"{% endif %}> {% if form_action %}action="{{ form_action }}"{% endif %}>
{% csrf_token %} {% csrf_token %}
@ -8,7 +8,7 @@
{% for fieldset in form.get_fieldsets %} {% for fieldset in form.get_fieldsets %}
{% if not fieldset.hidden %} {% if not fieldset.hidden %}
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link {% if forloop.first %}active{% endif %}" <button class="nav-link {% if forloop.first %}active{% endif %}{% if fieldset.has_mandatory %} has-mandatory{% endif %}"
id="{{ fieldset.title|slugify }}-tab" id="{{ fieldset.title|slugify }}-tab"
data-bs-toggle="tab" data-bs-toggle="tab"
data-bs-target="#{{ fieldset.title|slugify }}" data-bs-target="#{{ fieldset.title|slugify }}"
@ -17,6 +17,7 @@
aria-controls="{{ fieldset.title|slugify }}" aria-controls="{{ fieldset.title|slugify }}"
aria-selected="{% if forloop.first %}true{% else %}false{% endif %}"> aria-selected="{% if forloop.first %}true{% else %}false{% endif %}">
{{ fieldset.title }} {{ fieldset.title }}
{% if fieldset.has_mandatory %}<span class="mandatory-indicator">*</span>{% endif %}
</button> </button>
</li> </li>
{% endif %} {% endif %}

View file

@ -192,3 +192,46 @@ a.btn-keycloak {
.service-cards-container .card-footer { .service-cards-container .card-footer {
margin-top: auto; margin-top: auto;
} }
/* CRD Form mandatory field styling */
.crd-form .form-group.mandatory .form-label {
font-weight: bold;
position: relative;
}
.crd-form .form-group.mandatory .form-label::after {
content: " *";
color: #dc3545;
font-weight: bold;
}
.crd-form .form-group.mandatory {
border-left: 3px solid #dc3545;
padding-left: 10px;
background-color: rgba(220, 53, 69, 0.05);
border-radius: 3px;
}
.crd-form .nav-tabs .nav-link .mandatory-indicator {
color: #dc3545;
font-weight: bold;
font-size: 1.1em;
margin-left: 4px;
}
html[data-bs-theme="dark"] .crd-form .form-group.mandatory {
background-color: rgba(220, 53, 69, 0.1);
border-left-color: #ff6b6b;
}
html[data-bs-theme="dark"] .crd-form .form-group.mandatory .form-label::after {
color: #ff6b6b;
}
html[data-bs-theme="dark"] .crd-form .nav-tabs .nav-link .mandatory-indicator {
color: #ff6b6b;
}
.crd-form .nav-tabs .nav-link.has-mandatory {
position: relative;
}