6.4 KiB
Unify Form Checkboxes Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Unify all Django form checkboxes across the codebase by routing them through our new Python Checkbox primitive.
Architecture:
- Modify
CheckboxandRadioprimitives incommon/components/primitives.pyto support headless (label-less) rendering whenlabelisNone, so they can be injected into Django's nativeform.as_div()rendering without duplicating labels. - Create a
PrimitiveCheckboxWidgetingames/forms.pythat extendsforms.CheckboxInputbut renders using ourCheckboxPython component. - Create a
PrimitiveWidgetsMixiningames/forms.pythat automatically applies thePrimitiveCheckboxWidgetto allforms.BooleanFieldinstances in a form. Add this mixin to all ModelForms.
Tech Stack: Python, Django Forms, HTML.
Task 1: Update Primitives for Headless Rendering
Files:
-
Modify:
common/components/primitives.py -
Modify:
tests/test_components.py -
Step 1: Write a failing test for headless rendering In
tests/test_components.py, add a test toComponentPrimitivesTest:
def test_checkbox_headless(self):
html = Checkbox(name="test-headless", label=None, checked=True)
self.assertNotIn('<label', html)
self.assertIn('<input', html)
self.assertIn('type="checkbox"', html)
self.assertIn('name="test-headless"', html)
-
Step 2: Run test to verify it fails Run:
pytest tests/test_components.py -k test_checkbox_headlessExpected: Fail becauseCheckboxcurrently requireslabelas astrand always renders aLabelwrapper. -
Step 3: Update
CheckboxandRadioincommon/components/primitives.pyUpdate the function signatures to acceptlabel: str | None = Noneand selectively return only theInputiflabelis missing.
def Checkbox(
name: str,
label: str | None = None,
checked: bool = False,
value: str = "1",
attributes: list[HTMLAttribute] | None = None,
) -> SafeText:
"""A filter-agnostic Checkbox component."""
attributes = attributes or []
input_attrs = [
("name", name),
("value", value),
("class", "rounded border-default-medium bg-neutral-secondary-medium text-brand focus:ring-brand"),
] + attributes
if checked:
input_attrs.append(("checked", "true"))
input_el = Input(type="checkbox", attributes=input_attrs)
if label is None:
return input_el
return Label(
attributes=[("class", "flex items-center gap-2 text-sm text-heading cursor-pointer")],
children=[input_el, label],
)
def Radio(
name: str,
label: str | None = None,
checked: bool = False,
value: str = "",
attributes: list[HTMLAttribute] | None = None,
) -> SafeText:
"""A filter-agnostic Radio component."""
attributes = attributes or []
input_attrs = [
("name", name),
("value", value),
("class", "rounded-full border-default-medium bg-neutral-secondary-medium text-brand focus:ring-brand"),
] + attributes
if checked:
input_attrs.append(("checked", "true"))
input_el = Input(type="radio", attributes=input_attrs)
if label is None:
return input_el
return Label(
attributes=[("class", "flex items-center gap-1.5 text-sm text-heading cursor-pointer")],
children=[input_el, label],
)
-
Step 4: Run test to verify it passes Run:
pytest tests/test_components.py -k ComponentPrimitivesTestExpected: PASS -
Step 5: Commit Run:
git add common/components/primitives.py tests/test_components.py
git commit -m "refactor: allow Checkbox and Radio primitives to render headlessly without labels"
Task 2: Create Django Widget Adapter and Mixin
Files:
-
Modify:
games/forms.py -
Step 1: Write the Widget and Mixin implementations At the top of
games/forms.py, importCheckboxand implementPrimitiveCheckboxWidgetandPrimitiveWidgetsMixin.
from common.components.primitives import Checkbox
class PrimitiveCheckboxWidget(forms.CheckboxInput):
"""Adapts Django's CheckboxInput to use our Checkbox component."""
def render(self, name, value, attrs=None, renderer=None):
final_attrs = self.build_attrs(self.attrs, attrs)
checked = self.check_test(value)
attributes = [(k, str(v)) for k, v in final_attrs.items() if k not in ("type", "name", "value", "checked")]
# Django uses boolean values differently for checkboxes, we omit value if empty
return str(Checkbox(
name=name,
label=None,
checked=checked,
value=str(value) if value else "1",
attributes=attributes
))
class PrimitiveWidgetsMixin:
"""Automatically applies primitive custom widgets to native Django form fields."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
if isinstance(field, forms.BooleanField):
field.widget = PrimitiveCheckboxWidget()
# Maintain the field's explicit required status (usually False for booleans)
- Step 2: Apply the Mixin to all Forms
In
games/forms.py, update all the ModelForm classes to inherit fromPrimitiveWidgetsMixinas the first base class (beforeforms.ModelForm). Example:
class SessionForm(PrimitiveWidgetsMixin, forms.ModelForm):
# ...
class PurchaseForm(PrimitiveWidgetsMixin, forms.ModelForm):
# ...
class GameForm(PrimitiveWidgetsMixin, forms.ModelForm):
# ...
class PlatformForm(PrimitiveWidgetsMixin, forms.ModelForm):
# ...
class DeviceForm(PrimitiveWidgetsMixin, forms.ModelForm):
# ...
class PlayEventForm(PrimitiveWidgetsMixin, forms.ModelForm):
# ...
class GameStatusChangeForm(PrimitiveWidgetsMixin, forms.ModelForm):
# ...
-
Step 3: Test Django Form Rendering Run the full test suite to ensure forms still validate properly and render without error. Run:
pytestExpected: PASS -
Step 4: Commit Run:
git add games/forms.py
git commit -m "feat: replace all form BooleanFields with PrimitiveCheckboxWidget via mixin"