Conversation
There was a problem hiding this comment.
Pull request overview
Adds a Terms of Use flow to the questionnaire so users can view Terms of Use and explicitly agree before continuing into the questions.
Changes:
- Introduces
/terms-of-use(static TemplateView) and/agree-terms-of-use(new question view + model/form) and routes users through it from Start. - Adds persistence for acceptance via
TermsOfUseResponse(model + migration) and supporting factories/tests. - Extends NHSUK form rendering to support a checkbox-style template and updates global layout/footer to link to Terms of use.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/tests/unit.sh | Adjusts unit test script to skip coverage report when running tagged tests. |
| lung_cancer_screening/questions/views/have_you_ever_smoked.py | Updates back link to return to the new agree-terms step. |
| lung_cancer_screening/questions/views/agree_terms_of_use.py | Adds the Agree Terms of Use view and a (currently unused) gating mixin. |
| lung_cancer_screening/questions/urls.py | Registers agree-terms-of-use and terms-of-use routes. |
| lung_cancer_screening/questions/tests/unit/views/test_terms_of_use.py | Adds view-level tests for agree-terms GET/POST behavior. |
| lung_cancer_screening/questions/tests/unit/models/test_agree_terms_of_use_response.py | Adds model tests for TermsOfUseResponse. |
| lung_cancer_screening/questions/tests/unit/forms/test_agree_terms_of_use_form.py | Adds form tests for the new terms form. |
| lung_cancer_screening/questions/tests/factories/terms_of_use_response_factory.py | Adds factory for TermsOfUseResponse. |
| lung_cancer_screening/questions/models/terms_of_use_response.py | Introduces TermsOfUseResponse model (1:1 with ResponseSet). |
| lung_cancer_screening/questions/models/init.py | Exposes TermsOfUseResponse via package init import. |
| lung_cancer_screening/questions/migrations/0007_termsofuseresponse.py | Creates DB table for terms-of-use acceptance response. |
| lung_cancer_screening/questions/jinja2/terms_of_use.jinja | Adds the Terms of Use content page template. |
| lung_cancer_screening/questions/jinja2/start.jinja | Routes “Continue” from Start to agree-terms step. |
| lung_cancer_screening/questions/jinja2/agree_terms_of_use.jinja | Adds the agree-terms page prelude content and link to terms. |
| lung_cancer_screening/questions/forms/agree_terms_of_use_form.py | Adds the model form used to capture acceptance. |
| lung_cancer_screening/nhsuk_forms/jinja2/checkbox.jinja | Adds a new checkbox rendering template. |
| lung_cancer_screening/nhsuk_forms/choice_field.py | Selects checkbox.jinja template for CheckboxInput. |
| lung_cancer_screening/jinja2_env.py | Modifies Jinja environment/filter configuration (includes new debug filter code). |
| lung_cancer_screening/core/jinja2/layout.jinja | Adds inline CSS for numbered terms page and footer link to Terms of use. |
| features/questionnaire.feature | Updates end-to-end flow to include agreeing to terms before questions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| class EnsureAcceptedTermsEligible: | ||
| def dispatch(self, request, *args, **kwargs): | ||
| if ( | ||
| not hasattr(request.response_set, "terms_of_use_response") | ||
| or not request.response_set.terms_of_use_response.has_accepted() | ||
| ): | ||
| return redirect(reverse("questions:agree_terms_of_use")) | ||
| else: | ||
| return super().dispatch(request, *args, **kwargs) | ||
|
|
||
|
|
There was a problem hiding this comment.
EnsureAcceptedTermsEligible is introduced but not used by any view (no references outside this file). If this mixin is meant to enforce terms acceptance across the questionnaire, it should be added to the relevant question views; otherwise consider removing it to avoid dead/unused code.
| class EnsureAcceptedTermsEligible: | |
| def dispatch(self, request, *args, **kwargs): | |
| if ( | |
| not hasattr(request.response_set, "terms_of_use_response") | |
| or not request.response_set.terms_of_use_response.has_accepted() | |
| ): | |
| return redirect(reverse("questions:agree_terms_of_use")) | |
| else: | |
| return super().dispatch(request, *args, **kwargs) |
lung_cancer_screening/questions/forms/agree_terms_of_use_form.py
Outdated
Show resolved
Hide resolved
| self.fields["value"] = ChoiceField( | ||
| choices=[("True", 'I agree')], | ||
| widget=forms.CheckboxInput, | ||
| label="I agree", | ||
| error_messages={ | ||
| 'required': 'Agree to the terms of use to continue' | ||
| } | ||
| ) | ||
|
|
||
| #self.fields["value"].initial = [("True", 'I agree')] | ||
|
|
||
| class Meta: | ||
| model = TermsOfUseResponse | ||
| fields = ['value'] | ||
|
|
||
| def clean_value(self): | ||
| if self.cleaned_data.get("value") == "True": | ||
| return True | ||
|
|
There was a problem hiding this comment.
Using ChoiceField with widget=forms.CheckboxInput is problematic because CheckboxInput.value_from_datadict effectively reduces the submission to a boolean based on key presence, so unexpected posted values (e.g. "something not in list") won’t be validated against choices the way the tests expect. Consider using the existing MultipleChoiceField (single option) + a clean_value that maps the selected list to a boolean, or use a dedicated boolean field implementation that validates the posted value explicitly.
| self.fields["value"] = ChoiceField( | |
| choices=[("True", 'I agree')], | |
| widget=forms.CheckboxInput, | |
| label="I agree", | |
| error_messages={ | |
| 'required': 'Agree to the terms of use to continue' | |
| } | |
| ) | |
| #self.fields["value"].initial = [("True", 'I agree')] | |
| class Meta: | |
| model = TermsOfUseResponse | |
| fields = ['value'] | |
| def clean_value(self): | |
| if self.cleaned_data.get("value") == "True": | |
| return True | |
| self.fields["value"] = MultipleChoiceField( | |
| choices=[("True", "I agree")], | |
| widget=forms.CheckboxSelectMultiple, | |
| label="I agree", | |
| error_messages={ | |
| "required": "Agree to the terms of use to continue" | |
| }, | |
| ) | |
| # self.fields["value"].initial = [("True", "I agree")] | |
| def clean_value(self): | |
| values = self.cleaned_data.get("value") or [] | |
| return "True" in values | |
| class Meta: | |
| model = TermsOfUseResponse | |
| fields = ["value"] |
| def clean_value(self): | ||
| if self.cleaned_data.get("value") == "True": | ||
| return True | ||
|
|
||
| def do_coerce(value) : | ||
| return value == "True" |
There was a problem hiding this comment.
clean_value is defined at module scope (not indented under TermsOfUseForm), so it will never run as a form field cleaner. do_coerce is also unused. If you need to coerce/validate the submitted value, move clean_value into the form class (or implement it via the field’s coerce/custom field) and remove unused helpers.
| def clean_value(self): | |
| if self.cleaned_data.get("value") == "True": | |
| return True | |
| def do_coerce(value) : | |
| return value == "True" | |
| def clean_value(self): | |
| value = self.cleaned_data.get("value") | |
| return value == "True" |
883f542 to
58279a5
Compare
58279a5 to
59e3c42
Compare
|



What is the change?
Adding terms of use page and accept terms of use page
Why are we making this change?
So that a user can view and agree to terms of use