Skip to content

Commit 1e3d872

Browse files
jamiefalcusmiriam-zThemitchell
authored
PPHA-266: Add ethnicity question page (#128)
* PPHA-266: Add ethnicity question page * PPHA-266: Fix white space issues * PPHA-266: More fixing white space issues * WIP * Fix assignment from tuple to object * PPHA-266: Add acceptance tests for ethnicity validation errors * PPHA-266: Remove unusued BoundField import * PPHA-266: Add newlines at end of files --------- Co-authored-by: miriam-z <[email protected]> Co-authored-by: Andy Mitchell <[email protected]>
1 parent 54143dd commit 1e3d872

20 files changed

+431
-47
lines changed

lung_cancer_screening/core/tests/acceptance/helpers/user_interaction_helpers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,11 @@ def fill_in_and_submit_gender(page, gender):
6868
page.get_by_label(gender, exact=True).check()
6969

7070
page.click("text=Continue")
71+
72+
def fill_in_and_submit_ethnicity(page, ethnicity):
73+
expect(page.locator("legend")).to_have_text(
74+
"What is your ethnic background?")
75+
76+
page.get_by_label(ethnicity, exact=True).check()
77+
78+
page.click("text=Continue")

lung_cancer_screening/core/tests/acceptance/test_cannot_change_answers_after_submission.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
fill_in_and_submit_date_of_birth,
1313
fill_in_and_submit_sex_at_birth,
1414
fill_in_and_submit_gender,
15+
fill_in_and_submit_ethnicity
1516
)
1617

1718
class TestQuestionnaire(StaticLiveServerTestCase):
@@ -44,6 +45,7 @@ def test_cannot_change_responses_once_checked_and_submitted(self):
4445
fill_in_and_submit_weight_metric(page, "25.4")
4546
fill_in_and_submit_sex_at_birth(page, "Male")
4647
fill_in_and_submit_gender(page, "Male")
48+
fill_in_and_submit_ethnicity(page, "White")
4749

4850
page.click("text=Submit")
4951

lung_cancer_screening/core/tests/acceptance/test_questionnaire.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
fill_in_and_submit_weight_metric,
1414
fill_in_and_submit_weight_imperial,
1515
fill_in_and_submit_sex_at_birth,
16-
fill_in_and_submit_gender
16+
fill_in_and_submit_gender,
17+
fill_in_and_submit_ethnicity
1718
)
1819

1920
from .helpers.assertion_helpers import expect_back_link_to_have_url
@@ -84,9 +85,14 @@ def test_full_questionnaire_user_journey(self):
8485
expect_back_link_to_have_url(page, "/sex-at-birth")
8586
fill_in_and_submit_gender(page, "Male")
8687

87-
expect(page).to_have_url(f"{self.live_server_url}/responses")
88+
expect(page).to_have_url(f"{self.live_server_url}/ethnicity")
8889
expect_back_link_to_have_url(page, "/gender")
8990

91+
fill_in_and_submit_ethnicity(page, "White")
92+
93+
expect(page).to_have_url(f"{self.live_server_url}/responses")
94+
expect_back_link_to_have_url(page, "/ethnicity")
95+
9096
responses = page.locator(".responses")
9197
expect(responses).to_contain_text("Have you ever smoked? Yes, I used to smoke regularly")
9298
expect(responses).to_contain_text(
@@ -95,6 +101,7 @@ def test_full_questionnaire_user_journey(self):
95101
expect(responses).to_contain_text(f"What is your weight? {weight_stone} stone {weight_pound} pound")
96102
expect(responses).to_contain_text("What was your sex at birth? Male")
97103
expect(responses).to_contain_text("Which of these best describes you? Male")
104+
expect(responses).to_contain_text("What is your ethnic background? White")
98105

99106
page.click("text=Submit")
100107

lung_cancer_screening/core/tests/acceptance/test_questionnaire_validation_errors.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,16 @@ def test_weight_validation_errors(self):
102102
expect(page.locator(".nhsuk-error-message")).to_contain_text(
103103
"Weight must be between 25.4kg and 317.5kg"
104104
)
105+
106+
def test_ethnicity_validation_errors(self):
107+
participant_id = '123'
108+
109+
page = self.browser.new_page()
110+
page.goto(f"{self.live_server_url}/start")
111+
fill_in_and_submit_participant_id(page, participant_id)
112+
page.goto(f"{self.live_server_url}/ethnicity")
113+
114+
page.click("text=Continue")
115+
expect(page.locator(".nhsuk-error-message")).to_contain_text(
116+
"Select your ethnic background."
117+
)

lung_cancer_screening/nhsuk_forms/bound_choice_field.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

lung_cancer_screening/nhsuk_forms/choice_field.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,60 @@
11
from django import forms
22
from django.forms import widgets
33

4+
class RadioSelectWithoutFieldset(widgets.RadioSelect):
5+
use_fieldset = False
6+
7+
class CheckboxSelectMultipleWithoutFieldset(widgets.CheckboxSelectMultiple):
8+
use_fieldset = False
9+
10+
class BoundChoiceField(forms.BoundField):
11+
"""
12+
Specialisation of BoundField that can deal with conditionally shown fields,
13+
and divider content between choices.
14+
This can be used to render a set of radios or checkboxes with text boxes to capture
15+
more details.
16+
"""
17+
18+
def __init__(self, form: forms.Form, field: "ChoiceField", name: str):
19+
super().__init__(form, field, name)
20+
21+
self._conditional_html = {}
22+
self.dividers = {}
23+
24+
def add_conditional_html(self, value, html):
25+
if isinstance(self.field.widget, widgets.Select):
26+
raise ValueError("select component does not support conditional fields")
27+
28+
self._conditional_html[value] = html
29+
30+
def conditional_html(self, value):
31+
return self._conditional_html.get(value)
32+
33+
def add_divider_after(self, previous, divider):
34+
self.dividers[previous] = divider
35+
36+
def get_divider_after(self, previous):
37+
return self.dividers.get(previous)
38+
39+
440
class ChoiceField(forms.ChoiceField):
541
"""
642
A ChoiceField that renders using NHS.UK design system radios/select
743
components.
44+
45+
To render a select instead, pass Select for the `widget` argument.
46+
To render radios without the fieldset, pass RadioSelectWithoutFieldset
47+
for the `widget` argument.
848
"""
949

1050
widget = widgets.RadioSelect
51+
bound_field_class = BoundChoiceField
1152

1253
def __init__(
1354
self,
1455
*args,
1556
hint=None,
16-
label_classes=None,
57+
label_classes="nhsuk-fieldset__legend--m",
1758
classes=None,
1859
**kwargs,
1960
):
@@ -29,4 +70,40 @@ def __init__(
2970

3071
@staticmethod
3172
def _template_name(widget):
32-
return "radios.jinja"
73+
if (
74+
isinstance(widget, type) and issubclass(widget, widgets.RadioSelect)
75+
) or isinstance(widget, widgets.RadioSelect):
76+
return "radios.jinja"
77+
elif (
78+
isinstance(widget, type) and issubclass(widget, widgets.Select)
79+
) or isinstance(widget, widgets.Select):
80+
return "select.jinja"
81+
82+
83+
class MultipleChoiceField(forms.MultipleChoiceField):
84+
"""
85+
A MultipleChoiceField that renders using the NHS.UK design system checkboxes
86+
component.
87+
88+
To render checkboxes without the fieldset, pass CheckboxSelectMultipleWithoutFieldset
89+
for the `widget` argument.
90+
"""
91+
92+
widget = widgets.CheckboxSelectMultiple
93+
bound_field_class = BoundChoiceField
94+
95+
def __init__(
96+
self,
97+
*args,
98+
hint=None,
99+
label_classes="nhsuk-fieldset__legend--m",
100+
classes=None,
101+
**kwargs,
102+
):
103+
kwargs["template_name"] = "checkboxes.jinja"
104+
105+
self.hint = hint
106+
self.classes = classes
107+
self.label_classes = label_classes
108+
109+
super().__init__(*args, **kwargs)

lung_cancer_screening/nhsuk_forms/jinja2/radios.jinja

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,32 @@
55
{% endif %}
66
{% set ns = namespace(items=[]) %}
77
{% for value, text in unbound_field.choices %}
8+
{% set conditional_html = field.conditional_html(value) %}
89
{% set ns.items = ns.items + [{
910
"id": field.auto_id if loop.first,
1011
"value": value,
1112
"text": text,
12-
"checked": field.value() == value
13+
"checked": field.value() == value,
14+
"conditional": {
15+
"html": conditional_html
16+
} if conditional_html else undefined
1317
}] %}
18+
{% set divider = field.get_divider_after(value) %}
19+
{% if divider %}
20+
{% set ns.items = ns.items + [{"divider": divider}] %}
21+
{% endif %}
1422
{% endfor %}
1523
{{ radios({
1624
"name": field.html_name,
1725
"idPrefix": field.auto_id,
18-
"fieldset": {
26+
"fieldset": {
1927
"legend": {
2028
"text": field.label,
2129
"classes": unbound_field.label_classes
2230
}
23-
},
31+
} if field.use_fieldset else none,
2432
"errorMessage": error_message,
33+
"classes": unbound_field.classes if unbound_field.classes,
2534
"hint": {
2635
"html": unbound_field.hint|e
2736
} if unbound_field.hint,

lung_cancer_screening/nhsuk_forms/typed_choice_field.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from django import forms
33
from django.forms import widgets
44

5-
from .bound_choice_field import BoundChoiceField
5+
from .choice_field import BoundChoiceField
66

77
class TypedChoiceField(forms.TypedChoiceField):
88
"""
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from django import forms
2+
3+
from ...nhsuk_forms.choice_field import ChoiceField
4+
from ..models.response_set import ResponseSet, EthnicityValues
5+
6+
class EthnicityForm(forms.ModelForm):
7+
8+
def __init__(self, *args, **kwargs):
9+
self.participant = kwargs.pop('participant')
10+
super().__init__(*args, **kwargs)
11+
self.instance.participant = self.participant
12+
13+
self.fields["ethnicity"] = ChoiceField(
14+
choices=EthnicityValues.choices,
15+
label="What is your ethnic background?",
16+
widget=forms.RadioSelect,
17+
label_classes="nhsuk-fieldset__legend--m",
18+
hint="Your ethnicity may impact your chances of developing lung cancer.",
19+
error_messages={
20+
'required': 'Select your ethnic background.'
21+
}
22+
)
23+
24+
self["ethnicity"].add_divider_after(EthnicityValues.OTHER.value, "or")
25+
26+
class Meta:
27+
model = ResponseSet
28+
fields = ['ethnicity']
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{% extends 'layout.jinja' %}
2+
{% from 'nhsuk/components/button/macro.jinja' import button %}
3+
{% from 'nhsuk/components/back-link/macro.jinja' import backLink %}
4+
5+
{% block beforeContent %}
6+
{{
7+
backLink({
8+
"href": url("questions:gender"),
9+
"text": "Back"
10+
})
11+
}}
12+
{% endblock beforeContent %}
13+
14+
{% block page_content %}
15+
<div class="nhsuk-grid-row">
16+
<div class="nhsuk-grid-column-two-thirds">
17+
<form action="{{ request.path }}" method="POST">
18+
{{ csrf_input }}
19+
20+
{{ form }}
21+
22+
{{ button({
23+
"text": "Continue"
24+
}) }}
25+
</form>
26+
</div>
27+
</div>
28+
{% endblock %}

0 commit comments

Comments
 (0)