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 %}
-
-
- {% if unit %}
- {% set action_url = request.path + '?unit=' + unit %}
- {% else %}
- {% set action_url = request.path %}
- {% endif %}
-
{% 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 %}
-
-
-
-
-
-{% 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
)