Skip to content

Commit 730d703

Browse files
committed
PPHA-483: Make education level multi select
1 parent 6e1d4ce commit 730d703

16 files changed

Lines changed: 97 additions & 45 deletions

features/education.feature

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@ Feature: Education page
1717
Given I am logged in
1818
When I go to "/education"
1919
Then I see a back link to "/ethnicity"
20-
When I fill in and submit my education with "A-levels"
20+
When I check "A-levels"
21+
And I click "Continue"
2122
Then I am on "/respiratory-conditions"
2223

2324
Scenario: Checking responses and changing them
2425
Given I am logged in
2526
When I go to "/education"
26-
And I fill in and submit my education with "A-levels"
27+
And I check "A-levels"
28+
And I check "GCSEs"
29+
And I click "Continue"
2730
When I go to "/check-your-answers"
28-
Then I see "A-levels" as a response to "Highest level of education" under "About you"
31+
Then I see "GCSEs and A-levels" as a response to "Highest level of education" under "About you"
2932
And I see "/education?change=True" as a link to change "Highest level of education" under "About you"
3033
When I click the link to change "Highest level of education" under "About you"
3134
Then I am on "/education?change=True"

features/steps/form_steps.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ def then_i_see_a_date_x_years_ago_filled_in(context, years):
120120
assert context.page.get_by_label('Month').input_value() == str(date_of_birth.month)
121121
assert context.page.get_by_label('Year').input_value() == str(date_of_birth.year)
122122

123+
123124
@then(u'I see "{value}" filled in for "{label}"')
124125
def then_i_see_value_filled_in_for_label(context, value, label):
125126
assert context.page.get_by_label(label, exact=True).input_value() == value
127+
128+
129+
@when('I check "{label}"')
130+
def when_i_check_label(context, label):
131+
context.page.get_by_label(label, exact=True).check()

lung_cancer_screening/questions/forms/education_form.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django import forms
22

3-
from ...nhsuk_forms.choice_field import ChoiceField
3+
from ...nhsuk_forms.choice_field import MultipleChoiceField
44
from ..models.education_response import EducationResponse, EducationValues
55

66

@@ -9,17 +9,19 @@ class EducationForm(forms.ModelForm):
99
def __init__(self, *args, **kwargs):
1010
super().__init__(*args, **kwargs)
1111

12-
self.fields["value"] = ChoiceField(
12+
self.fields["value"] = MultipleChoiceField(
1313
choices=EducationValues.choices,
1414
label="What level of education have you completed?",
15-
widget=forms.RadioSelect,
15+
widget=forms.CheckboxSelectMultiple,
1616
label_classes="nhsuk-fieldset__legend--m",
17-
hint=(
18-
"Select all that apply"
19-
),
17+
hint="Select all that apply",
2018
error_messages={
21-
'required': 'Select your level of education'
22-
}
19+
"required": "Select your level of education",
20+
"singleton_option": (
21+
"Select your level of education, or select "
22+
"'Prefer not to say'"
23+
),
24+
},
2325
)
2426

2527
self["value"].add_divider_after(

lung_cancer_screening/questions/migrations/0020_responseset_user_and_more.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ class Migration(migrations.Migration):
2828
migrations.AlterField(
2929
model_name='responseset',
3030
name='respiratory_conditions',
31-
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('P', 'Pneumonia'), ('E', 'Emphysema'), ('B', 'Bronchitis'), ('T', 'Tuberculosis (TB)'), ('C', 'Chronic obstructive pulmonary disease (COPD)'), ('N', 'No, I have not had any of these respiratory conditions')], max_length=1), blank=True, null=True, size=None, validators=[lung_cancer_screening.questions.models.respiratory_conditions_response.validate_singleton_option]),
31+
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('P', 'Pneumonia'), ('E', 'Emphysema'), ('B', 'Bronchitis'), ('T', 'Tuberculosis (TB)'), ('C', 'Chronic obstructive pulmonary disease (COPD)'), ('N', 'No, I have not had any of these respiratory conditions')], max_length=1), blank=True, null=True, size=None, validators=[lung_cancer_screening.questions.models.validators.singleton_option.validate_singleton_option]),
3232
),
3333
]

lung_cancer_screening/questions/migrations/0031_respiratoryconditionsresponse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class Migration(migrations.Migration):
4040
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
4141
('created_at', models.DateTimeField(auto_now_add=True)),
4242
('updated_at', models.DateTimeField(auto_now=True)),
43-
('value', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('P', 'Pneumonia'), ('E', 'Emphysema'), ('B', 'Bronchitis'), ('T', 'Tuberculosis (TB)'), ('C', 'Chronic obstructive pulmonary disease (COPD)'), ('N', 'No, I have not had any of these respiratory conditions')], max_length=1), size=None, validators=[lung_cancer_screening.questions.models.respiratory_conditions_response.validate_singleton_option])),
43+
('value', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('P', 'Pneumonia'), ('E', 'Emphysema'), ('B', 'Bronchitis'), ('T', 'Tuberculosis (TB)'), ('C', 'Chronic obstructive pulmonary disease (COPD)'), ('N', 'No, I have not had any of these respiratory conditions')], max_length=1), size=None, validators=[lung_cancer_screening.questions.models.validators.singleton_option.validate_singleton_option])),
4444
('response_set', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='respiratory_conditions_response', to='questions.responseset')),
4545
],
4646
options={
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 5.2.10 on 2026-01-15 16:41
2+
3+
import django.contrib.postgres.fields
4+
import lung_cancer_screening.questions.models.validators.singleton_option
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('questions', '0040_educationresponse'),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name='educationresponse',
17+
name='value',
18+
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('X', 'I finished school before the age of 15'), ('G', 'GCSEs'), ('A', 'A-levels'), ('B', "Bachelor's degree"), ('P', 'Postgraduate degree'), ('N', "I'd prefer not to say")], max_length=1), size=None, validators=[lung_cancer_screening.questions.models.validators.singleton_option.validate_singleton_option]),
19+
),
20+
]

lung_cancer_screening/questions/models/education_response.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from django.db import models
2+
from django.contrib.postgres.fields import ArrayField
23

4+
from .validators.singleton_option import validate_singleton_option
35
from .base import BaseModel
46
from .response_set import ResponseSet
57

@@ -15,4 +17,9 @@ class EducationValues(models.TextChoices):
1517

1618
class EducationResponse(BaseModel):
1719
response_set = models.OneToOneField(ResponseSet, on_delete=models.CASCADE, related_name='education_response')
18-
value = models.CharField(max_length=1, choices=EducationValues.choices)
20+
value = ArrayField(
21+
models.CharField(max_length=1, choices=EducationValues.choices),
22+
validators=[
23+
validate_singleton_option
24+
]
25+
)
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.db import models
22
from django.contrib.postgres.fields import ArrayField
3-
from django.core.exceptions import ValidationError
43

4+
from .validators.singleton_option import validate_singleton_option
55
from .base import BaseModel
66
from .response_set import ResponseSet
77

@@ -15,17 +15,11 @@ class RespiratoryConditionValues(models.TextChoices):
1515
NONE = "N", "No, I have not had any of these respiratory conditions"
1616

1717

18-
def validate_singleton_option(value):
19-
if value and "N" in value and len(value) > 1:
20-
raise ValidationError(
21-
"Cannot have singleton value and other values selected",
22-
code="singleton_option",
23-
)
24-
25-
2618
class RespiratoryConditionsResponse(BaseModel):
2719
response_set = models.OneToOneField(ResponseSet, on_delete=models.CASCADE, related_name='respiratory_conditions_response')
2820
value = ArrayField(
2921
models.CharField(max_length=1, choices=RespiratoryConditionValues.choices),
30-
validators=[validate_singleton_option]
22+
validators=[
23+
validate_singleton_option
24+
]
3125
)

lung_cancer_screening/questions/models/validators/__init__.py

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.core.exceptions import ValidationError
2+
3+
def validate_singleton_option(value):
4+
if value and "N" in value and len(value) > 1:
5+
raise ValidationError(
6+
"Cannot have singleton value and other values selected",
7+
code="singleton_option",
8+
)
9+

0 commit comments

Comments
 (0)