feat: implement _parse_bool_nullable and _filter_boolean_radio helper

This commit is contained in:
2026-06-09 19:58:20 +02:00
parent af62120c8d
commit 0b9dd702e1
2 changed files with 63 additions and 12 deletions
+42 -11
View File
@@ -6,7 +6,7 @@ from django.db import models
from django.utils.safestring import SafeText, mark_safe
from common.components.core import Component
from common.components.primitives import Div, Input, Label, Span
from common.components.primitives import Checkbox, Div, Input, Label, Radio, Span
from common.components.search_select import (
DEFAULT_PREFETCH,
FilterSelect,
@@ -43,6 +43,12 @@ _FILTER_CHECKBOX_CLASS = (
)
_FILTER_RADIO_CLASS = (
"rounded-full border-default-medium bg-neutral-secondary-medium "
"text-brand focus:ring-brand"
)
_FILTER_GRID_CLASS = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4"
@@ -90,6 +96,24 @@ def _parse_bool(existing: dict, key: str) -> bool:
return bool(field.get("value", False))
def _parse_bool_nullable(existing: dict, key: str) -> bool | None:
"""Extract a nullable boolean value from a filter criterion."""
if key not in existing:
return None
field = existing[key]
if not isinstance(field, dict):
return None
val = field.get("value")
if val is None:
return None
if isinstance(val, str):
if val.lower() in ("true", "1", "yes"):
return True
if val.lower() in ("false", "0", "no"):
return False
return bool(val)
# ── FilterSelect adapters ────────────────────────────────────────────────────
# Each list filter is a FilterSelect. Enum fields pre-render their small, fixed
# option set; model-backed fields fetch from a search endpoint on demand, with
@@ -231,19 +255,26 @@ def _filter_field(label: str, widget, for_widget: str = None) -> SafeText:
def _filter_checkbox(name: str, label: str, checked: bool) -> SafeText:
return Label(
attributes=[("class", "flex items-center gap-2 text-sm text-heading")],
"""Thin adapter mapping legacy checkbox filters to the generalized Checkbox primitive."""
return Checkbox(name=name, label=label, checked=checked)
def _filter_boolean_radio(name: str, label: str, value: bool | None) -> SafeText:
"""Renders a filter-specific boolean radio button group with 'True' and 'False' options."""
return Div(
attributes=[("class", "flex flex-col gap-1")],
children=[
Input(
attributes=[
("type", "checkbox"),
("name", name),
("value", "1"),
*([("checked", "true")] if checked else []),
("class", _FILTER_CHECKBOX_CLASS),
Span(
attributes=[("class", _FILTER_LABEL_CLASS)],
children=[label],
),
Div(
attributes=[("class", "flex items-center gap-4 h-9")],
children=[
Radio(name=name, label="True", checked=value is True, value="true"),
Radio(name=name, label="False", checked=value is False, value="false"),
],
),
label,
],
)
+21 -1
View File
@@ -2,7 +2,7 @@
from django.test import SimpleTestCase
from common.components.filters import _parse_bool, _parse_range
from common.components.filters import _parse_bool, _parse_range, _parse_bool_nullable
class ParseRangeTest(SimpleTestCase):
@@ -66,3 +66,23 @@ class ParseBoolTest(SimpleTestCase):
def test_missing_value_in_field(self):
self.assertFalse(_parse_bool({"field": {}}, "field"))
class ParseBoolNullableTest(SimpleTestCase):
def test_missing_key(self):
self.assertIsNone(_parse_bool_nullable({}, "field"))
def test_null_value(self):
self.assertIsNone(_parse_bool_nullable({"field": None}, "field"))
self.assertIsNone(_parse_bool_nullable({"field": {}}, "field"))
def test_boolean_values(self):
self.assertTrue(_parse_bool_nullable({"field": {"value": True}}, "field"))
self.assertFalse(_parse_bool_nullable({"field": {"value": False}}, "field"))
def test_string_values(self):
self.assertTrue(_parse_bool_nullable({"field": {"value": "true"}}, "field"))
self.assertTrue(_parse_bool_nullable({"field": {"value": "1"}}, "field"))
self.assertFalse(_parse_bool_nullable({"field": {"value": "false"}}, "field"))
self.assertFalse(_parse_bool_nullable({"field": {"value": "0"}}, "field"))