Skip to content
523 changes: 0 additions & 523 deletions lung_cancer_screening/core/form_fields.py

This file was deleted.

728 changes: 0 additions & 728 deletions lung_cancer_screening/core/tests/unit/not_test_form_fields.py

This file was deleted.

34 changes: 34 additions & 0 deletions lung_cancer_screening/nhsuk_forms/bound_choice_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django import forms
from django.forms import widgets

from lung_cancer_screening.nhsuk_forms import choice_field

class BoundChoiceField(forms.BoundField):
"""
Specialisation of BoundField that can deal with conditionally shown fields,
and divider content between choices.
This can be used to render a set of radios or checkboxes with text boxes to capture
more details.
"""

def __init__(self, form: forms.Form, field: "choice_field", name: str):
super().__init__(form, field, name)

self._conditional_html = {}
self.dividers = {}

def add_conditional_html(self, value, html):
if isinstance(self.field.widget, widgets.Select):
raise ValueError(
"select component does not support conditional fields")

self._conditional_html[value] = html

def conditional_html(self, value):
return self._conditional_html.get(value)

def add_divider_after(self, previous, divider):
self.dividers[previous] = divider

def get_divider_after(self, previous):
return self.dividers.get(previous)
35 changes: 35 additions & 0 deletions lung_cancer_screening/nhsuk_forms/choice_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django import forms
from django.forms import widgets

from .bound_choice_field import BoundChoiceField

class ChoiceField(forms.ChoiceField):
"""
A ChoiceField that renders using NHS.UK design system radios/select
components.
"""

widget = widgets.RadioSelect
bound_field_class = BoundChoiceField

def __init__(
self,
*args,
hint=None,
label_classes=None,
classes=None,
**kwargs,
):
kwargs["template_name"] = ChoiceField._template_name(
kwargs.get("widget", self.widget)
)

self.hint = hint
self.classes = classes
self.label_classes = label_classes

super().__init__(*args, **kwargs)

@staticmethod
def _template_name(widget):
return "radios.jinja"
28 changes: 28 additions & 0 deletions lung_cancer_screening/nhsuk_forms/decimal_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django import forms

class DecimalField(forms.DecimalField):
def __init__(
self,
*args,
hint=None,
label_classes=None,
classes=None,
**kwargs,
):
kwargs["template_name"] = "input.jinja"

self.hint = hint
self.classes = classes
self.label_classes = label_classes

super().__init__(*args, **kwargs)

def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)

# Don't use min/max/step attributes.
attrs.pop("min", None)
attrs.pop("max", None)
attrs.pop("step", None)

return attrs
76 changes: 76 additions & 0 deletions lung_cancer_screening/nhsuk_forms/imperial_height_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from django import forms
from django.forms import widgets

from lung_cancer_screening.nhsuk_forms.integer_field import IntegerField


class ImperialHeightWidget(widgets.MultiWidget):
"""
A widget that splits height into feet and inches inputs.
"""

def __init__(self, attrs=None):
height_widgets = (
widgets.NumberInput(attrs=attrs),
widgets.NumberInput(attrs=attrs),
)
super().__init__(height_widgets, attrs)

def decompress(self, value):
"""
Convert total inches back to feet and inches for display.
"""
if value:
feet = int(value // 12)
inches = value % 12
return [feet, inches]
return [None, None]

def subwidgets(self, name, value, attrs=None):
"""
Expose data for each subwidget, so that we can render them separately in the template.
"""
context = self.get_context(name, value, attrs)
for subwidget in context["widget"]["subwidgets"]:
yield subwidget


class ImperialHeightField(forms.MultiValueField):
"""
A field that combines feet and inches into a single height value in cm.
"""

widget = ImperialHeightWidget

def __init__(self, *args, **kwargs):
error_messages = kwargs.get("error_messages", {})

feet_kwargs = {
"error_messages": {
'invalid': 'Feet must be in whole numbers.',
**error_messages,
},
}
inches_kwargs = {
"error_messages": {
'invalid': 'Inches must be in whole numbers.',
**error_messages,
},
}
fields = (
IntegerField(**feet_kwargs),
IntegerField(**inches_kwargs),
)
kwargs["template_name"] = "imperial-height-input.jinja"

super().__init__(fields, *args, **kwargs)

def compress(self, data_list):
"""
Convert feet and inches to total inches.
"""
if data_list and all(data_list):
feet, inches = data_list
total_inches = feet * 12 + inches
return int(total_inches)
return None
83 changes: 83 additions & 0 deletions lung_cancer_screening/nhsuk_forms/imperial_weight_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from django import forms
from django.forms import widgets

from lung_cancer_screening.nhsuk_forms.integer_field import IntegerField

class ImperialWeightWidget(widgets.MultiWidget):
"""
A widget that splits weight into stone and pounds inputs.
"""

def __init__(self, attrs=None):
weight_widgets = (
widgets.NumberInput(attrs=attrs),
widgets.NumberInput(attrs=attrs),
)
super().__init__(weight_widgets, attrs)

def decompress(self, value):
"""
Convert total pounds back to stone and pounds for display.
"""
if value:
stone = int(value // 14)
pounds = value % 14
return [stone, pounds]
return [None, None]

def subwidgets(self, name, value, attrs=None):
"""
Expose data for each subwidget, so that we can render them separately in the template.
"""
context = self.get_context(name, value, attrs)
for subwidget in context["widget"]["subwidgets"]:
yield subwidget


class ImperialWeightField(forms.MultiValueField):
"""
A field that combines stone and pounds into a single weight value in total pounds.
"""

widget = ImperialWeightWidget

def __init__(self, *args, **kwargs):
error_messages = kwargs.get("error_messages", {})

stone_kwargs = {
"min_value": 0,
"max_value": 50,
"error_messages": {
'invalid': 'Stone must be in whole numbers.',
'min_value': 'Weight must be between 4 stone and 50 stone.',
'max_value': 'Weight must be between 4 stone and 50 stone.',
**error_messages,
},
}
pounds_kwargs = {
"min_value": 0,
"max_value": 13,
"error_messages": {
'invalid': 'Pounds must be in whole numbers.',
'min_value': 'Pounds must be between 0 and 13.',
'max_value': 'Pounds must be between 0 and 13.',
**error_messages,
},
}
fields = (
IntegerField(**stone_kwargs),
IntegerField(**pounds_kwargs),
)
kwargs["template_name"] = "imperial-weight-input.jinja"

super().__init__(fields, *args, **kwargs)

def compress(self, data_list):
"""
Convert stone and pounds to total pounds.
"""
if data_list and all(item is not None for item in data_list):
stone, pounds = data_list
total_pounds = stone * 14 + pounds
return int(total_pounds)
return None
29 changes: 29 additions & 0 deletions lung_cancer_screening/nhsuk_forms/integer_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django import forms


class IntegerField(forms.IntegerField):
def __init__(
self,
*args,
hint=None,
label_classes=None,
classes=None,
**kwargs,
):
kwargs["template_name"] = "input.jinja"

self.hint = hint
self.classes = classes
self.label_classes = label_classes

super().__init__(*args, **kwargs)

def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)

# Don't use min/max/step attributes.
attrs.pop("min", None)
attrs.pop("max", None)
attrs.pop("step", None)

return attrs
Loading
Loading