feat: implement _parse_bool_nullable and _filter_boolean_radio helper
This commit is contained in:
@@ -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,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user