Parcourir la source

Fixes #22219: Label inline fields as required if any individual field is required

Jeremy Stretch il y a 2 jours
Parent
commit
c36c690a90

+ 1 - 1
netbox/utilities/templates/form_helpers/render_fieldset.html

@@ -26,7 +26,7 @@
     {% elif layout == 'inline' %}
       {# Multiple form fields on the same line #}
       <div class="row mb-3"{% if title %} role="group" aria-label="{{ title }}"{% endif %}>
-        <label class="col col-3 col-form-label text-lg-end">{{ title|default:'' }}</label>
+        <label class="col col-3 col-form-label text-lg-end{% if items|any_required %} required{% endif %}">{{ title|default:'' }}</label>
         {% for field in items %}
           <div class="col mb-1">
             {% render_field_with_aria field has_helptext=True %}

+ 9 - 0
netbox/utilities/templatetags/form_helpers.py

@@ -6,6 +6,7 @@ from django.conf import settings
 from utilities.forms.rendering import InlineFields, M2MAddRemoveFields, ObjectAttribute, TabbedGroups
 
 __all__ = (
+    'any_required',
     'getfield',
     'render_custom_fields',
     'render_errors',
@@ -34,6 +35,14 @@ def getfield(form, fieldname):
         return None
 
 
+@register.filter()
+def any_required(fields):
+    """
+    Return True if any of the given bound form fields is required.
+    """
+    return any(getattr(f, 'field', None) and f.field.required for f in fields)
+
+
 @register.filter(name='widget_type')
 def widget_type(field):
     """

+ 53 - 1
netbox/utilities/tests/test_templatetags.py

@@ -9,8 +9,9 @@ from core.models import ObjectType
 from dcim.models import Site
 from extras.choices import CustomFieldTypeChoices
 from extras.models import CustomField, CustomFieldChoiceSet
+from utilities.forms.rendering import FieldSet, InlineFields
 from utilities.templatetags.builtins.tags import badge, customfield_value, static_with_params
-from utilities.templatetags.form_helpers import render_field_with_aria
+from utilities.templatetags.form_helpers import any_required, render_field_with_aria, render_fieldset
 from utilities.templatetags.helpers import _humanize_capacity, humanize_speed
 
 
@@ -284,3 +285,54 @@ class RenderFieldWithAriaTestCase(TestCase):
         # No aria-label should be synthesized — an untranslated English fallback
         # would degrade accessibility under non-English locales.
         self.assertNotIn('aria-label', html)
+
+
+class AnyRequiredTestCase(TestCase):
+    """
+    Test the any_required template filter.
+    """
+
+    class TestForm(forms.Form):
+        required_field = forms.CharField(required=True)
+        optional_field = forms.CharField(required=False)
+
+    def test_returns_true_when_any_field_required(self):
+        form = self.TestForm()
+        self.assertTrue(any_required([form['optional_field'], form['required_field']]))
+
+    def test_returns_false_when_no_field_required(self):
+        form = self.TestForm()
+        self.assertFalse(any_required([form['optional_field']]))
+
+    def test_returns_false_for_empty_list(self):
+        self.assertFalse(any_required([]))
+
+
+class RenderFieldsetInlineRequiredTestCase(TestCase):
+    """
+    Verify the shared label for an InlineFields row receives the `required`
+    CSS class when at least one inline field is required.
+    """
+
+    class TestForm(forms.Form):
+        required_field = forms.CharField(required=True)
+        optional_field = forms.CharField(required=False)
+        another_optional = forms.CharField(required=False)
+
+    def _render(self, fieldset):
+        context = render_fieldset(self.TestForm(), fieldset)
+        return render_to_string('form_helpers/render_fieldset.html', context)
+
+    def test_inline_label_marked_required_when_any_field_required(self):
+        fieldset = FieldSet(
+            InlineFields('optional_field', 'required_field', label='Combined'),
+        )
+        html = self._render(fieldset)
+        self.assertIn('col-form-label text-lg-end required', html)
+
+    def test_inline_label_not_marked_required_when_no_field_required(self):
+        fieldset = FieldSet(
+            InlineFields('optional_field', 'another_optional', label='Combined'),
+        )
+        html = self._render(fieldset)
+        self.assertNotIn('col-form-label text-lg-end required', html)