diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a01f1d5d..42034626 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,8 @@ { "name": "Ubuntu", - "image": "mcr.microsoft.com/devcontainers/base:noble", + "build": { + "dockerfile": "dockerfile" + }, "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": { "moby": true, @@ -21,11 +23,14 @@ "installOhMyZshConfig": true, "configureZshAsDefaultShell": true }, + "ghcr.io/devcontainers/features/terraform:1": {}, "ghcr.io/devcontainers-extra/features/zsh-plugins:0": { "plugins": "zsh-autosuggestions zsh-syntax-highlighting", "omzPlugins": "https://github.com/zsh-users/zsh-autosuggestions.git https://github.com/zsh-users/zsh-syntax-highlighting.git" } }, + "workspaceMount": "source=./,target=/app,type=bind,consistency=cached", + "workspaceFolder": "/app", "postCreateCommand": "pipx install pre-commit && make config && echo 'export GPG_TTY=$TTY' | cat - ~/.zshrc > temp && mv temp ~/.zshrc", "mounts": [ "source=${localEnv:HOME}/.gnupg,target=/home/vscode/.gnupg,type=bind,consistency=cached" diff --git a/.devcontainer/dockerfile b/.devcontainer/dockerfile new file mode 100644 index 00000000..8a41cdc1 --- /dev/null +++ b/.devcontainer/dockerfile @@ -0,0 +1,5 @@ +FROM mcr.microsoft.com/devcontainers/base:noble + +COPY *.crt /usr/local/share/ca-certificates/ + +RUN update-ca-certificates diff --git a/.gitignore b/.gitignore index e88632d2..d5e07a30 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ lung_cancer_screening/assets/compiled/* !lung_cancer_screening/assets/compiled/.gitkeep .DS_Store .venv +.devcontainer/ca.crt 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 b91d0224..ced1e554 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 @@ -47,8 +47,11 @@ 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") + page.click("text=Continue") # education + page.click("text=Continue") # respiratory conditions fill_in_and_submit_asbestos_exposure(page, "No") - + page.click("text=Continue") # cancer diagnosis + page.click("text=Continue") # family history page.click("text=Submit") page.goto(f"{self.live_server_url}/start") diff --git a/lung_cancer_screening/core/tests/acceptance/test_questionnaire.py b/lung_cancer_screening/core/tests/acceptance/test_questionnaire.py index bef1e1bb..50bfe6b9 100644 --- a/lung_cancer_screening/core/tests/acceptance/test_questionnaire.py +++ b/lung_cancer_screening/core/tests/acceptance/test_questionnaire.py @@ -88,21 +88,34 @@ def test_full_questionnaire_user_journey(self): expect(page).to_have_url(f"{self.live_server_url}/ethnicity") expect_back_link_to_have_url(page, "/gender") - fill_in_and_submit_ethnicity(page, "White") - expect(page).to_have_url(f"{self.live_server_url}/asbestos-exposure") + expect(page).to_have_url(f"{self.live_server_url}/education") expect_back_link_to_have_url(page, "/ethnicity") + page.click("text=Continue") + expect(page).to_have_url(f"{self.live_server_url}/respiratory-conditions") + expect_back_link_to_have_url(page, "/education") + page.click("text=Continue") + + expect(page).to_have_url(f"{self.live_server_url}/asbestos-exposure") + expect_back_link_to_have_url(page, "/respiratory-conditions") fill_in_and_submit_asbestos_exposure(page, "No") - expect(page).to_have_url(f"{self.live_server_url}/responses") + expect(page).to_have_url(f"{self.live_server_url}/cancer-diagnosis") expect_back_link_to_have_url(page, "/asbestos-exposure") + page.click("text=Continue") + + expect(page).to_have_url(f"{self.live_server_url}/family-history-lung-cancer") + expect_back_link_to_have_url(page, "/cancer-diagnosis") + page.click("text=Continue") + + expect(page).to_have_url(f"{self.live_server_url}/responses") + expect_back_link_to_have_url(page, "/family-history-lung-cancer") responses = page.locator(".responses") expect(responses).to_contain_text("Have you ever smoked? Yes, I used to smoke regularly") - expect(responses).to_contain_text( - age.strftime("What is your date of birth? %Y-%m-%d")) + expect(responses).to_contain_text(age.strftime("What is your date of birth? %Y-%m-%d")) expect(responses).to_contain_text(f"What is your height? {feet} feet {inches} inches") expect(responses).to_contain_text(f"What is your weight? {weight_stone} stone {weight_pound} pound") expect(responses).to_contain_text("What was your sex at birth? Male") diff --git a/lung_cancer_screening/questions/forms/gender_form.py b/lung_cancer_screening/questions/forms/gender_form.py index 56c8bffa..5c346f0d 100644 --- a/lung_cancer_screening/questions/forms/gender_form.py +++ b/lung_cancer_screening/questions/forms/gender_form.py @@ -1,6 +1,6 @@ from django import forms -from ...nhsuk_forms.typed_choice_field import TypedChoiceField +from ...nhsuk_forms.choice_field import ChoiceField from ..models.response_set import ResponseSet, GenderValues class GenderForm(forms.ModelForm): @@ -10,7 +10,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.instance.participant = self.participant - self.fields["gender"] = TypedChoiceField( + self.fields["gender"] = ChoiceField( choices=GenderValues.choices, widget=forms.RadioSelect, label="Which of these best describes you?", diff --git a/lung_cancer_screening/questions/forms/sex_at_birth_form.py b/lung_cancer_screening/questions/forms/sex_at_birth_form.py index 97e4617b..6f32dc10 100644 --- a/lung_cancer_screening/questions/forms/sex_at_birth_form.py +++ b/lung_cancer_screening/questions/forms/sex_at_birth_form.py @@ -1,6 +1,6 @@ from django import forms -from ...nhsuk_forms.typed_choice_field import TypedChoiceField +from ...nhsuk_forms.choice_field import ChoiceField from ..models.response_set import ResponseSet, SexAtBirthValues class SexAtBirthForm(forms.ModelForm): @@ -10,7 +10,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.instance.participant = self.participant - self.fields["sex_at_birth"] = TypedChoiceField( + self.fields["sex_at_birth"] = ChoiceField( choices=SexAtBirthValues.choices, widget=forms.RadioSelect, label="What was your sex at birth?", diff --git a/lung_cancer_screening/questions/jinja2/asbestos_exposure.jinja b/lung_cancer_screening/questions/jinja2/asbestos_exposure.jinja index 25e3a84c..2054e4e3 100644 --- a/lung_cancer_screening/questions/jinja2/asbestos_exposure.jinja +++ b/lung_cancer_screening/questions/jinja2/asbestos_exposure.jinja @@ -6,7 +6,7 @@ {% block beforeContent %} {{ backLink({ - "href": url("questions:ethnicity"), + "href": url("questions:respiratory_conditions"), "text": "Back" }) }} diff --git a/lung_cancer_screening/questions/jinja2/date_of_birth.jinja b/lung_cancer_screening/questions/jinja2/date_of_birth.jinja deleted file mode 100644 index 0557718e..00000000 --- a/lung_cancer_screening/questions/jinja2/date_of_birth.jinja +++ /dev/null @@ -1,34 +0,0 @@ -{% extends 'layout.jinja' %} - -{% from 'nhsuk/components/date-input/macro.jinja' import dateInput %} -{% from 'nhsuk/components/button/macro.jinja' import button %} -{% from 'nhsuk/components/back-link/macro.jinja' import backLink %} - -{% if error %} - {% set error_message = { "text": error } %} -{% endif %} - -{% block beforeContent %} - {{ - backLink({ - "href": url("questions:have_you_ever_smoked"), - "text": "Back" - }) - }} -{% endblock beforeContent %} - -{% block page_content %} -
-
-
- {{ csrf_input }} - - {{ form }} - - {{ button({ - "text": "Continue" - }) }} -
-
-
-{% endblock %} diff --git a/lung_cancer_screening/questions/jinja2/gender.jinja b/lung_cancer_screening/questions/jinja2/gender.jinja deleted file mode 100644 index bff0e163..00000000 --- a/lung_cancer_screening/questions/jinja2/gender.jinja +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'layout.jinja' %} -{% from 'nhsuk/components/button/macro.jinja' import button %} -{% from 'nhsuk/components/back-link/macro.jinja' import backLink %} - -{% block beforeContent %} - {{ - backLink({ - "href": url("questions:sex_at_birth"), - "text": "Back" - }) - }} -{% endblock beforeContent %} - -{% block page_content %} -
-
-
- {{ csrf_input }} - - {{ form }} - - {{ button({ - "text": "Continue" - }) }} -
-
-
-{% endblock %} diff --git a/lung_cancer_screening/questions/jinja2/height.jinja b/lung_cancer_screening/questions/jinja2/height.jinja index 2d19600b..c957a77f 100644 --- a/lung_cancer_screening/questions/jinja2/height.jinja +++ b/lung_cancer_screening/questions/jinja2/height.jinja @@ -4,50 +4,47 @@ {% from 'nhsuk/components/fieldset/macro.jinja' import fieldset %} {% block beforeContent %} - {{ - backLink({ - "href": url("questions:date_of_birth"), - "text": "Back" - }) - }} +{{ +backLink({ +"href": url("questions:date_of_birth"), +"text": "Back" +}) +}} {% endblock beforeContent %} {% block page_content %} -
-
- {% if unit %} - {% set action_url = request.path + '?unit=' + unit %} - {% else %} - {% set action_url = request.path %} - {% endif %} -
- {{ csrf_input }} -

What is your height?

-

An accurate measurement is important. - -

You can measure your height at home with a measuring tape. Some pharmacies and gyms have machines to measure your height. +

+
+ + {{ csrf_input }} +

What is your height?

+

An accurate measurement is important. + +

You can measure your height at home with a measuring tape. Some pharmacies and gyms have machines to measure + your height. {% call fieldset({ "legend": { "text": "Enter your height", "classes": "nhsuk-label--m" } - }) %} + }) + %} - {% if unit == "imperial" %} - {{ form.height_imperial.as_field_group() }} - {% else %} - {{ form.height.as_field_group() }} - {% endif %} + {% if unit == "imperial" %} + {{ form.height_imperial.as_field_group() }} + {% else %} + {{ form.height.as_field_group() }} + {% endif %} -

Switch to {{ switch_to_unit }}

+

Switch to {{ switch_to_unit }}

- {% endcall %} + {% endcall %} - {{ button({ - "text": "Continue" - }) }} - -
+ {{ button({ + "text": "Continue" + }) }} +
+
{% endblock %} diff --git a/lung_cancer_screening/questions/jinja2/ethnicity.jinja b/lung_cancer_screening/questions/jinja2/question_form.jinja similarity index 84% rename from lung_cancer_screening/questions/jinja2/ethnicity.jinja rename to lung_cancer_screening/questions/jinja2/question_form.jinja index b85ec470..29a63702 100644 --- a/lung_cancer_screening/questions/jinja2/ethnicity.jinja +++ b/lung_cancer_screening/questions/jinja2/question_form.jinja @@ -1,11 +1,16 @@ {% extends 'layout.jinja' %} + {% from 'nhsuk/components/button/macro.jinja' import button %} {% from 'nhsuk/components/back-link/macro.jinja' import backLink %} +{% if error %} + {% set error_message = { "text": error } %} +{% endif %} + {% block beforeContent %} {{ backLink({ - "href": url("questions:gender"), + "href": back_link_url, "text": "Back" }) }} diff --git a/lung_cancer_screening/questions/jinja2/responses.jinja b/lung_cancer_screening/questions/jinja2/responses.jinja index 6e6dd84f..893cb111 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:asbestos_exposure"), + "href": url("questions:family_history_lung_cancer"), "text": "Back" }) }} diff --git a/lung_cancer_screening/questions/jinja2/sex_at_birth.jinja b/lung_cancer_screening/questions/jinja2/sex_at_birth.jinja deleted file mode 100644 index 95ce5044..00000000 --- a/lung_cancer_screening/questions/jinja2/sex_at_birth.jinja +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'layout.jinja' %} -{% from 'nhsuk/components/button/macro.jinja' import button %} -{% from 'nhsuk/components/back-link/macro.jinja' import backLink %} - -{% block beforeContent %} - {{ - backLink({ - "href": url("questions:weight"), - "text": "Back" - }) - }} -{% endblock beforeContent %} - -{% block page_content %} -
-
-
- {{ csrf_input }} - - {{ form }} - - {{ button({ - "text": "Continue" - }) }} -
-
-
-{% endblock %} 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 3677dd5d..ebc4c9de 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_ethnicity.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_ethnicity.py @@ -76,7 +76,7 @@ def test_post_redirects_to_the_asbestos_exposure_path(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:asbestos_exposure")) + self.assertRedirects(response, reverse("questions:education")) 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 df5df938..a340d22e 100644 --- a/lung_cancer_screening/questions/urls.py +++ b/lung_cancer_screening/questions/urls.py @@ -27,7 +27,11 @@ from .views.sex_at_birth import sex_at_birth from .views.gender import gender from .views.ethnicity import ethnicity +from .views.education import EducationView +from .views.respiratory_conditions import RespiratoryConditionsView from .views.asbestos_exposure import AsbestosExposureView +from .views.cancer_diagnosis import CancerDiagnosisView +from .views.family_history_lung_cancer import FamilyHistoryLungCancerView urlpatterns = [ path('start', start, name='start'), @@ -38,7 +42,11 @@ path('sex-at-birth', sex_at_birth, name='sex_at_birth'), path('gender', gender, name='gender'), path('ethnicity', ethnicity, name='ethnicity'), + path('education', EducationView.as_view(), name='education'), + path('respiratory-conditions', RespiratoryConditionsView.as_view(), name='respiratory_conditions'), path('asbestos-exposure', AsbestosExposureView.as_view(), name='asbestos_exposure'), + path('cancer-diagnosis', CancerDiagnosisView.as_view(), name='cancer_diagnosis'), + path('family-history-lung-cancer', FamilyHistoryLungCancerView.as_view(), name='family_history_lung_cancer'), 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 index 425feb03..56bfd5c7 100644 --- a/lung_cancer_screening/questions/views/asbestos_exposure.py +++ b/lung_cancer_screening/questions/views/asbestos_exposure.py @@ -26,7 +26,7 @@ def post(self, request): response_set = request.participant.responseset_set.last() response_set.asbestos_exposure = form.cleaned_data["asbestos_exposure"] response_set.save() - return redirect(reverse("questions:responses")) + return redirect(reverse("questions:cancer_diagnosis")) else: return render( request, diff --git a/lung_cancer_screening/questions/views/cancer_diagnosis.py b/lung_cancer_screening/questions/views/cancer_diagnosis.py new file mode 100644 index 00000000..c044eb75 --- /dev/null +++ b/lung_cancer_screening/questions/views/cancer_diagnosis.py @@ -0,0 +1,24 @@ +from django.shortcuts import render, redirect +from django.urls import reverse + +from .decorators.participant_decorators import require_participant +from django.views import View +from django.utils.decorators import method_decorator + +@method_decorator(require_participant, name="dispatch") +class CancerDiagnosisView(View): + def get(self, request): + return render_template(request) + + def post(self, request): + return redirect(reverse("questions:family_history_lung_cancer")) + +def render_template(request, status=200): + return render( + request, + "question_form.jinja", + { + "back_link_url": reverse("questions:asbestos_exposure") + }, + status=status + ) diff --git a/lung_cancer_screening/questions/views/date_of_birth.py b/lung_cancer_screening/questions/views/date_of_birth.py index 0d4fba02..4bdf8933 100644 --- a/lung_cancer_screening/questions/views/date_of_birth.py +++ b/lung_cancer_screening/questions/views/date_of_birth.py @@ -31,15 +31,24 @@ def date_of_birth(request): return redirect(reverse("questions:age_range_exit")) else: - return render( + return render_template( request, - "date_of_birth.jinja", - { "form": form }, + form, status=422 ) + return render_template( + request, + DateOfBirthForm(participant=request.participant) + ) + +def render_template(request, form, status=200): return render( request, - "date_of_birth.jinja", - { "form": DateOfBirthForm(participant=request.participant) } + "question_form.jinja", + { + "form": form, + "back_link_url": reverse("questions:have_you_ever_smoked") + }, + status=status ) diff --git a/lung_cancer_screening/questions/views/education.py b/lung_cancer_screening/questions/views/education.py new file mode 100644 index 00000000..1c97f3c7 --- /dev/null +++ b/lung_cancer_screening/questions/views/education.py @@ -0,0 +1,24 @@ +from django.shortcuts import render, redirect +from django.urls import reverse + +from .decorators.participant_decorators import require_participant +from django.views import View +from django.utils.decorators import method_decorator + +@method_decorator(require_participant, name="dispatch") +class EducationView(View): + def get(self, request): + return render_template(request) + + def post(self, request): + return redirect(reverse("questions:respiratory_conditions")) + +def render_template(request, status=200): + return render( + request, + "question_form.jinja", + { + "back_link_url": reverse("questions:ethnicity") + }, + status=status + ) diff --git a/lung_cancer_screening/questions/views/ethnicity.py b/lung_cancer_screening/questions/views/ethnicity.py index c2b67ffa..3d02353c 100644 --- a/lung_cancer_screening/questions/views/ethnicity.py +++ b/lung_cancer_screening/questions/views/ethnicity.py @@ -19,17 +19,26 @@ def ethnicity(request): response_set = request.participant.responseset_set.last() response_set.ethnicity = form.cleaned_data["ethnicity"] response_set.save() - return redirect(reverse("questions:asbestos_exposure")) + return redirect(reverse("questions:education")) else: - return render( + return render_template( request, - "ethnicity.jinja", - { "form": form }, + form, status=422 ) + return render_template( + request, + EthnicityForm(participant=request.participant) + ) + +def render_template(request, form, status=200): return render( request, - "ethnicity.jinja", - { "form": EthnicityForm(participant=request.participant) } + "question_form.jinja", + { + "form": form, + "back_link_url": reverse("questions:gender") + }, + status=status ) diff --git a/lung_cancer_screening/questions/views/family_history_lung_cancer.py b/lung_cancer_screening/questions/views/family_history_lung_cancer.py new file mode 100644 index 00000000..054c3568 --- /dev/null +++ b/lung_cancer_screening/questions/views/family_history_lung_cancer.py @@ -0,0 +1,24 @@ +from django.shortcuts import render, redirect +from django.urls import reverse + +from .decorators.participant_decorators import require_participant +from django.views import View +from django.utils.decorators import method_decorator + +@method_decorator(require_participant, name="dispatch") +class FamilyHistoryLungCancerView(View): + def get(self, request): + return render_template(request) + + def post(self, request): + return redirect(reverse("questions:responses")) + +def render_template(request, status=200): + return render( + request, + "question_form.jinja", + { + "back_link_url": reverse("questions:cancer_diagnosis") + }, + status=status + ) diff --git a/lung_cancer_screening/questions/views/gender.py b/lung_cancer_screening/questions/views/gender.py index 9f6fbf02..fc5acdb6 100644 --- a/lung_cancer_screening/questions/views/gender.py +++ b/lung_cancer_screening/questions/views/gender.py @@ -20,15 +20,24 @@ def gender(request): response_set.save() return redirect(reverse("questions:ethnicity")) else: - return render( + return render_template( request, - "gender.jinja", - { "form": form }, + form, status=422 ) + return render_template( + request, + GenderForm(participant=request.participant), + ) + +def render_template(request, form, status=200): return render( request, - "gender.jinja", - { "form": GenderForm(participant=request.participant) } + "question_form.jinja", + { + "form": form, + "back_link_url": reverse("questions:sex_at_birth") + }, + status=status ) diff --git a/lung_cancer_screening/questions/views/have_you_ever_smoked.py b/lung_cancer_screening/questions/views/have_you_ever_smoked.py index 547ef042..8da6a556 100644 --- a/lung_cancer_screening/questions/views/have_you_ever_smoked.py +++ b/lung_cancer_screening/questions/views/have_you_ever_smoked.py @@ -28,15 +28,25 @@ def have_you_ever_smoked(request): return redirect(reverse("questions:non_smoker_exit")) else: - return render( + return render_template( request, - "have_you_ever_smoked.jinja", - { "form": form }, + HaveYouEverSmokedForm(participant=request.participant), status=422 ) + return render_template( + request, + HaveYouEverSmokedForm(participant=request.participant) + ) + + +def render_template(request, form, status=200): return render( request, - "have_you_ever_smoked.jinja", - {"form": HaveYouEverSmokedForm(participant=request.participant)} + "question_form.jinja", + { + "form": form, + "back_link_url": reverse("questions:start") + }, + status=status ) diff --git a/lung_cancer_screening/questions/views/respiratory_conditions.py b/lung_cancer_screening/questions/views/respiratory_conditions.py new file mode 100644 index 00000000..0f67e39c --- /dev/null +++ b/lung_cancer_screening/questions/views/respiratory_conditions.py @@ -0,0 +1,24 @@ +from django.shortcuts import render, redirect +from django.urls import reverse + +from .decorators.participant_decorators import require_participant +from django.views import View +from django.utils.decorators import method_decorator + +@method_decorator(require_participant, name="dispatch") +class RespiratoryConditionsView(View): + def get(self, request): + return render_template(request) + + def post(self, request): + return redirect(reverse("questions:asbestos_exposure")) + +def render_template(request, status=200): + return render( + request, + "question_form.jinja", + { + "back_link_url": reverse("questions:education") + }, + status=status + ) diff --git a/lung_cancer_screening/questions/views/sex_at_birth.py b/lung_cancer_screening/questions/views/sex_at_birth.py index b65c9ebc..35dbbd6f 100644 --- a/lung_cancer_screening/questions/views/sex_at_birth.py +++ b/lung_cancer_screening/questions/views/sex_at_birth.py @@ -20,15 +20,24 @@ def sex_at_birth(request): response_set.save() return redirect(reverse("questions:gender")) else: - return render( + return render_template( request, - "sex_at_birth.jinja", - { "form": form }, + form, status=422 ) + return render_template( + request, + SexAtBirthForm(participant=request.participant) + ) + +def render_template(request, form, status=200): return render( request, - "sex_at_birth.jinja", - { "form": SexAtBirthForm(participant=request.participant) } + "question_form.jinja", + { + "form": form, + "back_link_url": reverse("questions:weight") + }, + status=status )