diff --git a/lung_cancer_screening/core/tests/acceptance/helpers/user_interaction_helpers.py b/lung_cancer_screening/core/tests/acceptance/helpers/user_interaction_helpers.py index 86beaceb..1bf7b468 100644 --- a/lung_cancer_screening/core/tests/acceptance/helpers/user_interaction_helpers.py +++ b/lung_cancer_screening/core/tests/acceptance/helpers/user_interaction_helpers.py @@ -76,3 +76,11 @@ def fill_in_and_submit_ethnicity(page, ethnicity): page.get_by_label(ethnicity, exact=True).check() page.click("text=Continue") + +def fill_in_and_submit_asbestos_exposure(page, answer): + expect(page.locator("legend")).to_have_text( + "Have you ever worked in a job where you might have been exposed to asbestos?") + + page.get_by_label(answer, exact=True).check() + + page.click("text=Continue") diff --git a/lung_cancer_screening/core/tests/acceptance/test_cannot_change_answers_after_submission.py b/lung_cancer_screening/core/tests/acceptance/test_cannot_change_answers_after_submission.py index 01493b3e..b91d0224 100644 --- a/lung_cancer_screening/core/tests/acceptance/test_cannot_change_answers_after_submission.py +++ b/lung_cancer_screening/core/tests/acceptance/test_cannot_change_answers_after_submission.py @@ -12,7 +12,8 @@ fill_in_and_submit_date_of_birth, fill_in_and_submit_sex_at_birth, fill_in_and_submit_gender, - fill_in_and_submit_ethnicity + fill_in_and_submit_ethnicity, + fill_in_and_submit_asbestos_exposure ) class TestQuestionnaire(StaticLiveServerTestCase): @@ -46,6 +47,7 @@ def test_cannot_change_responses_once_checked_and_submitted(self): fill_in_and_submit_sex_at_birth(page, "Male") fill_in_and_submit_gender(page, "Male") fill_in_and_submit_ethnicity(page, "White") + fill_in_and_submit_asbestos_exposure(page, "No") page.click("text=Submit") diff --git a/lung_cancer_screening/core/tests/acceptance/test_questionnaire.py b/lung_cancer_screening/core/tests/acceptance/test_questionnaire.py index b26c10f0..bef1e1bb 100644 --- a/lung_cancer_screening/core/tests/acceptance/test_questionnaire.py +++ b/lung_cancer_screening/core/tests/acceptance/test_questionnaire.py @@ -14,7 +14,8 @@ fill_in_and_submit_weight_imperial, fill_in_and_submit_sex_at_birth, fill_in_and_submit_gender, - fill_in_and_submit_ethnicity + fill_in_and_submit_ethnicity, + fill_in_and_submit_asbestos_exposure ) from .helpers.assertion_helpers import expect_back_link_to_have_url @@ -90,9 +91,14 @@ def test_full_questionnaire_user_journey(self): fill_in_and_submit_ethnicity(page, "White") - expect(page).to_have_url(f"{self.live_server_url}/responses") + expect(page).to_have_url(f"{self.live_server_url}/asbestos-exposure") expect_back_link_to_have_url(page, "/ethnicity") + fill_in_and_submit_asbestos_exposure(page, "No") + + expect(page).to_have_url(f"{self.live_server_url}/responses") + expect_back_link_to_have_url(page, "/asbestos-exposure") + responses = page.locator(".responses") expect(responses).to_contain_text("Have you ever smoked? Yes, I used to smoke regularly") expect(responses).to_contain_text( @@ -102,6 +108,7 @@ def test_full_questionnaire_user_journey(self): expect(responses).to_contain_text("What was your sex at birth? Male") expect(responses).to_contain_text("Which of these best describes you? Male") expect(responses).to_contain_text("What is your ethnic background? White") + expect(responses).to_contain_text("Have you ever worked in a job where you might have been exposed to asbestos? No") page.click("text=Submit") diff --git a/lung_cancer_screening/questions/forms/asbestos_exposure_form.py b/lung_cancer_screening/questions/forms/asbestos_exposure_form.py new file mode 100644 index 00000000..80f27e28 --- /dev/null +++ b/lung_cancer_screening/questions/forms/asbestos_exposure_form.py @@ -0,0 +1,25 @@ +from django import forms +from ...nhsuk_forms.typed_choice_field import TypedChoiceField +from ..models.response_set import ResponseSet + + +class AsbestosExposureForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + self.participant = kwargs.pop('participant') + super().__init__(*args, **kwargs) + self.instance.participant = self.participant + + self.fields["asbestos_exposure"] = TypedChoiceField( + choices=[(True, 'Yes'), (False, 'No')], + widget=forms.RadioSelect, + label="Have you ever worked in a job where you might have been exposed to asbestos?", + label_classes="nhsuk-fieldset__legend--m", + coerce=lambda x: x == 'True', + error_messages={ + 'required': 'Select if you have been exposed to asbestos.' + } + ) + + class Meta: + model = ResponseSet + fields = ['asbestos_exposure'] diff --git a/lung_cancer_screening/questions/jinja2/asbestos_exposure.jinja b/lung_cancer_screening/questions/jinja2/asbestos_exposure.jinja new file mode 100644 index 00000000..25e3a84c --- /dev/null +++ b/lung_cancer_screening/questions/jinja2/asbestos_exposure.jinja @@ -0,0 +1,61 @@ +{% extends 'layout.jinja' %} +{% from 'nhsuk/components/button/macro.jinja' import button %} +{% from 'nhsuk/components/back-link/macro.jinja' import backLink %} +{% from 'nhsuk/components/details/macro.jinja' import details %} + +{% block beforeContent %} + {{ + backLink({ + "href": url("questions:ethnicity"), + "text": "Back" + }) + }} +{% endblock beforeContent %} + +{% block page_content %} +
+
+

Tell us if you might have been exposed to asbestos at work

+ +

You may have been exposed to asbestos if you worked in an industry such as building or construction, particularly from the 1950s to the 1990s.

+ +

You could be exposed to asbestos today if your job involves working in certain roles in old buildings.

+ +

Examples include:

+ + +

You may have come into contact with asbestos from existing asbestos-containing materials in buildings and products. If they are intact or undamaged, they pose very little risk.

+ + {{ + details({ + "summaryText": "What is asbestos?", + "html": "

Asbestos was used in a number of building materials and products. For example:

+ +

If you worked in an industry such as building or construction you are more likely to have come into contact with damaged asbestos materials and products.

" + }) + }} + +
+ {{ csrf_input }} + + {{ form }} + + {{ button({ + "text": "Continue" + }) }} +
+
+
+{% endblock %} diff --git a/lung_cancer_screening/questions/jinja2/responses.jinja b/lung_cancer_screening/questions/jinja2/responses.jinja index 41163e94..6e6dd84f 100644 --- a/lung_cancer_screening/questions/jinja2/responses.jinja +++ b/lung_cancer_screening/questions/jinja2/responses.jinja @@ -5,7 +5,7 @@ {% block beforeContent %} {{ backLink({ - "href": url("questions:ethnicity"), + "href": url("questions:asbestos_exposure"), "text": "Back" }) }} @@ -23,6 +23,7 @@
  • What was your sex at birth? {{ response_set.get_sex_at_birth_display() }}
  • Which of these best describes you? {{ response_set.get_gender_display() }}
  • What is your ethnic background? {{ response_set.get_ethnicity_display() }}
  • +
  • Have you ever worked in a job where you might have been exposed to asbestos? {{ "Yes" if response_set.asbestos_exposure else "No" }}
  • diff --git a/lung_cancer_screening/questions/migrations/0016_responseset_asbestos_exposure.py b/lung_cancer_screening/questions/migrations/0016_responseset_asbestos_exposure.py new file mode 100644 index 00000000..7afaef79 --- /dev/null +++ b/lung_cancer_screening/questions/migrations/0016_responseset_asbestos_exposure.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2025-11-12 08:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('questions', '0015_responseset_ethnicity_alter_responseset_gender'), + ] + + operations = [ + migrations.AddField( + model_name='responseset', + name='asbestos_exposure', + field=models.CharField(blank=True, choices=[('Y', 'Yes'), ('N', 'No')], max_length=1, null=True), + ), + ] diff --git a/lung_cancer_screening/questions/migrations/0017_alter_responseset_asbestos_exposure.py b/lung_cancer_screening/questions/migrations/0017_alter_responseset_asbestos_exposure.py new file mode 100644 index 00000000..e76f04cd --- /dev/null +++ b/lung_cancer_screening/questions/migrations/0017_alter_responseset_asbestos_exposure.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-11-12 11:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('questions', '0016_responseset_asbestos_exposure'), + ] + + operations = [ + migrations.AlterField( + model_name='responseset', + name='asbestos_exposure', + field=models.BooleanField(blank=True, null=True), + ), + ] diff --git a/lung_cancer_screening/questions/models/response_set.py b/lung_cancer_screening/questions/models/response_set.py index bd7826a0..56c59729 100644 --- a/lung_cancer_screening/questions/models/response_set.py +++ b/lung_cancer_screening/questions/models/response_set.py @@ -95,6 +95,11 @@ class ResponseSet(BaseModel): blank=True ) + asbestos_exposure = models.BooleanField( + null=True, + blank=True + ) + submitted_at = models.DateTimeField(null=True, blank=True) class Meta: diff --git a/lung_cancer_screening/questions/tests/unit/forms/test_asbestos_exposure_form.py b/lung_cancer_screening/questions/tests/unit/forms/test_asbestos_exposure_form.py new file mode 100644 index 00000000..87d63570 --- /dev/null +++ b/lung_cancer_screening/questions/tests/unit/forms/test_asbestos_exposure_form.py @@ -0,0 +1,61 @@ +from django.test import TestCase + +from ....models.participant import Participant +from ....forms.asbestos_exposure_form import AsbestosExposureForm + + +class TestAsbestosExposureForm(TestCase): + def setUp(self): + self.participant = Participant.objects.create(unique_id="1234567890") + + def test_is_valid_with_yes(self): + form = AsbestosExposureForm( + participant=self.participant, + data={ + "asbestos_exposure": True + } + ) + self.assertTrue(form.is_valid()) + self.assertEqual( + form.cleaned_data["asbestos_exposure"], + True + ) + + def test_is_valid_with_no(self): + form = AsbestosExposureForm( + participant=self.participant, + data={ + "asbestos_exposure": False + } + ) + self.assertTrue(form.is_valid()) + self.assertEqual( + form.cleaned_data["asbestos_exposure"], + False + ) + + def test_is_invalid_with_an_invalid_value(self): + form = AsbestosExposureForm( + participant=self.participant, + data={ + "asbestos_exposure": "invalid" + } + ) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors["asbestos_exposure"], + ["Select a valid choice. invalid is not one of the available choices."] + ) + + def test_is_invalid_when_no_option_is_selected(self): + form = AsbestosExposureForm( + participant=self.participant, + data={ + "asbestos_exposure": None + } + ) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors["asbestos_exposure"], + ["Select if you have been exposed to asbestos."] + ) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_asbestos_exposure.py b/lung_cancer_screening/questions/tests/unit/views/test_asbestos_exposure.py new file mode 100644 index 00000000..1dfd7e7e --- /dev/null +++ b/lung_cancer_screening/questions/tests/unit/views/test_asbestos_exposure.py @@ -0,0 +1,87 @@ +from django.test import TestCase +from django.urls import reverse + +from lung_cancer_screening.questions.models.participant import Participant + + +class TestAsbestosExposure(TestCase): + def setUp(self): + self.participant = Participant.objects.create(unique_id="12345") + self.participant.responseset_set.create() + self.valid_params = {"asbestos_exposure": True} + + session = self.client.session + session['participant_id'] = self.participant.unique_id + session.save() + + def test_get_redirects_if_the_participant_does_not_exist(self): + session = self.client.session + session['participant_id'] = "somebody none existant participant" + session.save() + + response = self.client.get( + reverse("questions:asbestos_exposure") + ) + + self.assertRedirects(response, reverse("questions:start")) + + def test_get_responds_successfully(self): + response = self.client.get(reverse("questions:asbestos_exposure")) + self.assertEqual(response.status_code, 200) + + def test_get_contains_the_correct_form_fields(self): + response = self.client.get(reverse("questions:asbestos_exposure")) + self.assertContains(response, "Have you ever worked in a job where you might have been exposed to asbestos?") + + def test_post_redirects_if_the_participant_does_not_exist(self): + session = self.client.session + session['participant_id'] = "somebody none existant participant" + session.save() + + response = self.client.post( + reverse("questions:asbestos_exposure"), + self.valid_params + ) + + self.assertRedirects(response, reverse("questions:start")) + + def test_post_stores_a_valid_response_for_the_participant(self): + self.client.post( + reverse("questions:asbestos_exposure"), + self.valid_params + ) + + response_set = self.participant.responseset_set.first() + self.assertEqual( + response_set.asbestos_exposure, + self.valid_params["asbestos_exposure"] + ) + self.assertEqual(response_set.participant, self.participant) + + def test_post_redirects_to_the_next_page(self): + response = self.client.post( + reverse("questions:asbestos_exposure"), + self.valid_params + ) + + # Assuming it redirects to the next question page - adjust as needed + self.assertEqual(response.status_code, 302) + + def test_post_responds_with_422_if_the_response_fails_to_create(self): + response = self.client.post( + reverse("questions:asbestos_exposure"), + {"asbestos_exposure": "something not in list"} + ) + + self.assertEqual(response.status_code, 422) + + def test_post_does_not_update_response_set_on_invalid_data(self): + self.client.post( + reverse("questions:asbestos_exposure"), + {"asbestos_exposure": "invalid"} + ) + + self.assertEqual( + self.participant.responseset_set.first().asbestos_exposure, + None + ) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_ethnicity.py b/lung_cancer_screening/questions/tests/unit/views/test_ethnicity.py index acdd91a2..3677dd5d 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_ethnicity.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_ethnicity.py @@ -70,13 +70,13 @@ def test_post_sets_the_participant_id_in_session(self): self.assertEqual(self.client.session["participant_id"], "12345") - def test_post_redirects_to_the_responses_path(self): + def test_post_redirects_to_the_asbestos_exposure_path(self): response = self.client.post( reverse("questions:ethnicity"), self.valid_params ) - self.assertRedirects(response, reverse("questions:responses")) + self.assertRedirects(response, reverse("questions:asbestos_exposure")) def test_post_responds_with_422_if_the_date_response_fails_to_create(self): response = self.client.post( diff --git a/lung_cancer_screening/questions/urls.py b/lung_cancer_screening/questions/urls.py index 4e7d211a..df5df938 100644 --- a/lung_cancer_screening/questions/urls.py +++ b/lung_cancer_screening/questions/urls.py @@ -27,6 +27,7 @@ from .views.sex_at_birth import sex_at_birth from .views.gender import gender from .views.ethnicity import ethnicity +from .views.asbestos_exposure import AsbestosExposureView urlpatterns = [ path('start', start, name='start'), @@ -37,6 +38,7 @@ path('sex-at-birth', sex_at_birth, name='sex_at_birth'), path('gender', gender, name='gender'), path('ethnicity', ethnicity, name='ethnicity'), + path('asbestos-exposure', AsbestosExposureView.as_view(), name='asbestos_exposure'), path('responses', responses, name='responses'), path('age-range-exit', age_range_exit, name='age_range_exit'), path('non-smoker-exit', non_smoker_exit, name='non_smoker_exit'), diff --git a/lung_cancer_screening/questions/views/asbestos_exposure.py b/lung_cancer_screening/questions/views/asbestos_exposure.py new file mode 100644 index 00000000..425feb03 --- /dev/null +++ b/lung_cancer_screening/questions/views/asbestos_exposure.py @@ -0,0 +1,36 @@ +from django.shortcuts import render, redirect +from django.urls import reverse +from django.views import View +from django.utils.decorators import method_decorator + + +from .decorators.participant_decorators import require_participant +from ..forms.asbestos_exposure_form import AsbestosExposureForm + +@method_decorator(require_participant, name="dispatch") +class AsbestosExposureView(View): + def get(self, request): + return render( + request, + "asbestos_exposure.jinja", + {"form": AsbestosExposureForm(participant=request.participant)} + ) + + def post(self, request): + form = AsbestosExposureForm( + participant=request.participant, + data=request.POST + ) + + if form.is_valid(): + response_set = request.participant.responseset_set.last() + response_set.asbestos_exposure = form.cleaned_data["asbestos_exposure"] + response_set.save() + return redirect(reverse("questions:responses")) + else: + return render( + request, + "asbestos_exposure.jinja", + {"form": form}, + status=422 + ) diff --git a/lung_cancer_screening/questions/views/ethnicity.py b/lung_cancer_screening/questions/views/ethnicity.py index c94cb922..c2b67ffa 100644 --- a/lung_cancer_screening/questions/views/ethnicity.py +++ b/lung_cancer_screening/questions/views/ethnicity.py @@ -19,7 +19,7 @@ def ethnicity(request): response_set = request.participant.responseset_set.last() response_set.ethnicity = form.cleaned_data["ethnicity"] response_set.save() - return redirect(reverse("questions:responses")) + return redirect(reverse("questions:asbestos_exposure")) else: return render( request,