diff --git a/features/agree_terms_of_use.feature b/features/agree_terms_of_use.feature new file mode 100644 index 00000000..77ef896a --- /dev/null +++ b/features/agree_terms_of_use.feature @@ -0,0 +1,21 @@ +@TermsOfUse +Feature: Check if you need an appointment page + Scenario: The page is accessible + Given I am logged in + When I go to "/agree-terms-of-use" + Then there are no accessibility violations + + Scenario: Form errors + Given I am logged in + When I go to "/agree-terms-of-use" + And I click "Continue" + Then I am on "/agree-terms-of-use" + And I see a form error "Agree to the terms of use to continue" + And there are no accessibility violations + + Scenario: Navigating backwards and forwards + Given I am logged in + When I go to "/agree-terms-of-use" + Then I see a back link to "/start" + When I check "I agree" and submit + Then I am on "/have-you-ever-smoked" diff --git a/features/asbestos_exposure.feature b/features/asbestos_exposure.feature index 7b08b163..23f5ea45 100644 --- a/features/asbestos_exposure.feature +++ b/features/asbestos_exposure.feature @@ -41,4 +41,4 @@ Feature: Asbestos exposure page Scenario: Cannot answer when ineligible Given I am logged in When I go to "/asbestos-exposure" - Then I am on "/have-you-ever-smoked" + Then I am on "/agree-terms-of-use" diff --git a/features/check_need_appointment.feature b/features/check_need_appointment.feature index 5185e38a..6b520b21 100644 --- a/features/check_need_appointment.feature +++ b/features/check_need_appointment.feature @@ -9,6 +9,7 @@ Feature: Check if you need an appointment page Scenario: Form errors Given I am logged in + And I have answered questions showing I have accepted the terms of use And I have answered have you ever smoked with an eligible response And I have answered date of birth with an eligible date of birth When I go to "/check-if-you-need-an-appointment" @@ -19,6 +20,7 @@ Feature: Check if you need an appointment page Scenario: Eligibility exit if needs face to face appointment Given I am logged in + And I have answered questions showing I have accepted the terms of use And I have answered have you ever smoked with an eligible response And I have answered date of birth with an eligible date of birth When I go to "/check-if-you-need-an-appointment" @@ -28,6 +30,7 @@ Feature: Check if you need an appointment page Scenario: Navigating backwards and forwards Given I am logged in + And I have answered questions showing I have accepted the terms of use And I have answered have you ever smoked with an eligible response And I have answered date of birth with an eligible date of birth When I go to "/check-if-you-need-an-appointment" @@ -37,6 +40,7 @@ Feature: Check if you need an appointment page Scenario: Checking responses and changing them Given I am logged in + And I have answered questions showing I have accepted the terms of use And I have answered have you ever smoked with an eligible response And I have answered date of birth with an eligible date of birth When I go to "/check-if-you-need-an-appointment" diff --git a/features/date_of_birth_page.feature b/features/date_of_birth_page.feature index d6644bf5..463b3720 100644 --- a/features/date_of_birth_page.feature +++ b/features/date_of_birth_page.feature @@ -8,6 +8,7 @@ Feature: Date of birth page Scenario: Form errors Given I am logged in + And I have answered questions showing I have accepted the terms of use And I have answered have you ever smoked with an eligible response When I go to "/date-of-birth" When I click "Continue" @@ -26,6 +27,7 @@ Feature: Date of birth page Scenario: Eligibility of people not in age range Given I am logged in + And I have answered questions showing I have accepted the terms of use And I have answered have you ever smoked with an eligible response When I go to "/date-of-birth" And I fill in and submit my date of birth with "01-01-1900" @@ -34,6 +36,7 @@ Feature: Date of birth page Scenario: Navigating backwards and forwards Given I am logged in + And I have answered questions showing I have accepted the terms of use And I have answered have you ever smoked with an eligible response When I go to "/date-of-birth" Then I see a back link to "/have-you-ever-smoked" diff --git a/features/have_you_ever_smoked_page.feature b/features/have_you_ever_smoked_page.feature index 2a145880..c567608d 100644 --- a/features/have_you_ever_smoked_page.feature +++ b/features/have_you_ever_smoked_page.feature @@ -7,6 +7,7 @@ Feature: Have you ever smoked page Scenario: Form errors Given I am logged in + And I have answered questions showing I have accepted the terms of use When I go to "/have-you-ever-smoked" And I submit the form Then I am on "/have-you-ever-smoked" @@ -15,6 +16,7 @@ Feature: Have you ever smoked page Scenario: Eligibility of non smokers Given I am logged in + And I have answered questions showing I have accepted the terms of use When I go to "/have-you-ever-smoked" And I fill in and submit my smoking status with "No, I have never smoked" Then I am on "/non-smoker-exit" @@ -23,8 +25,9 @@ Feature: Have you ever smoked page Scenario: Navigating backwards and forwards Given I am logged in + And I have answered questions showing I have accepted the terms of use When I go to "/have-you-ever-smoked" - Then I see a back link to "/start" + Then I see a back link to "/agree-terms-of-use" When I fill in and submit my smoking status with "Yes, I used to smoke" Then I am on "/date-of-birth" diff --git a/features/questionnaire.feature b/features/questionnaire.feature index 1efa5ea1..4643e666 100644 --- a/features/questionnaire.feature +++ b/features/questionnaire.feature @@ -17,6 +17,9 @@ Feature: Questionnaire When I go to "/start" And I click "Continue" + Then I am on "/agree-terms-of-use" + When I check "I agree" and submit + Then I am on "/have-you-ever-smoked" When I fill in and submit my smoking status with "Yes, I used to smoke" diff --git a/features/steps/preflight_steps.py b/features/steps/preflight_steps.py index 8204b2fe..6e5920c5 100644 --- a/features/steps/preflight_steps.py +++ b/features/steps/preflight_steps.py @@ -22,6 +22,7 @@ from lung_cancer_screening.questions.tests.factories.age_when_started_smoking_response_factory import ( AgeWhenStartedSmokingResponseFactory, ) +from lung_cancer_screening.questions.tests.factories.terms_of_use_response_factory import TermsOfUseResponseFactory def get_or_create_response_set(context): @@ -32,6 +33,14 @@ def get_or_create_response_set(context): ) ) +@given("I have answered questions showing I have accepted the terms of use") +def given_i_have_answered_questions_showing_i_have_accepted_the_terms_of_use(context): + response_set = get_or_create_response_set(context) + + TermsOfUseResponseFactory.create( + response_set=response_set, + value=True, + ) @given('I have answered have you ever smoked with an eligible response') def given_i_have_answered_have_your_ever_smoked_with_an_eligible_response(context): diff --git a/lung_cancer_screening/assets/sass/components/_numbered_lists.scss b/lung_cancer_screening/assets/sass/components/_numbered_lists.scss new file mode 100644 index 00000000..32778985 --- /dev/null +++ b/lung_cancer_screening/assets/sass/components/_numbered_lists.scss @@ -0,0 +1,14 @@ +body { + counter-reset: chapter; /* Create a chapter counter scope */ + } + .numbered h2:before { + content: counter(chapter) ". "; + counter-increment: chapter; /* Add 1 to chapter */ + } + .numbered ol { counter-reset: section } + .numbered ol > li { display: block } + .numbered ol > li:before { + content: counter(chapter) "." counter(section) ". "; + counter-increment: section; + font-weight: 600; + } diff --git a/lung_cancer_screening/assets/sass/main.scss b/lung_cancer_screening/assets/sass/main.scss index 5efa3de7..37811b41 100644 --- a/lung_cancer_screening/assets/sass/main.scss +++ b/lung_cancer_screening/assets/sass/main.scss @@ -3,3 +3,5 @@ // Components that are not in the NHS.UK frontend library @forward "components/multi_field_input"; + +@forward "components/numbered_lists" diff --git a/lung_cancer_screening/core/jinja2/layout.jinja b/lung_cancer_screening/core/jinja2/layout.jinja index d8530ab0..e7604c95 100644 --- a/lung_cancer_screening/core/jinja2/layout.jinja +++ b/lung_cancer_screening/core/jinja2/layout.jinja @@ -63,7 +63,11 @@ { "href": url("questions:privacy_policy"), "text": "Privacy policy" - } + }, + { + "href": url("questions:terms_of_use"), + "text": "Terms of use" + }, ] } }) }} diff --git a/lung_cancer_screening/jinja2_env.py b/lung_cancer_screening/jinja2_env.py index bbbe4d1d..c31f3d29 100644 --- a/lung_cancer_screening/jinja2_env.py +++ b/lung_cancer_screening/jinja2_env.py @@ -38,8 +38,12 @@ def environment(**options): {"singularize": singularize} ) - env.filters.update( - {"singularize": singularize} - ) + env.filters['print'] = lambda x: "" + if (settings.DEBUG): + env.filters['print']=debug return env + +def debug(text): + print(text) + return '' diff --git a/lung_cancer_screening/nhsuk_forms/choice_field.py b/lung_cancer_screening/nhsuk_forms/choice_field.py index a95ab21b..af4f75e4 100644 --- a/lung_cancer_screening/nhsuk_forms/choice_field.py +++ b/lung_cancer_screening/nhsuk_forms/choice_field.py @@ -88,7 +88,6 @@ def _template_name(widget): ) or isinstance(widget, widgets.Select): return "select.jinja" - class MultipleChoiceField(forms.MultipleChoiceField): """ A MultipleChoiceField that renders using the NHS.UK design system checkboxes diff --git a/lung_cancer_screening/nhsuk_forms/jinja2/checkboxes.jinja b/lung_cancer_screening/nhsuk_forms/jinja2/checkboxes.jinja index 5574c168..333978be 100644 --- a/lung_cancer_screening/nhsuk_forms/jinja2/checkboxes.jinja +++ b/lung_cancer_screening/nhsuk_forms/jinja2/checkboxes.jinja @@ -6,11 +6,12 @@ {% set ns = namespace(items=[]) %} {% for value, text in unbound_field.choices %} {% set hint_text = field.get_hint_for_choice(value) %} + {% set checked = (value in (field.value() or [])) if (field.value() is iterable) else (value == field.value()) %} {% set ns.items = ns.items + [{ "id": field.auto_id ~ '_' ~ loop.index0, "value": value, "text": text, - "checked": value in (field.value() or []), + "checked": checked, "hint": { "text": hint_text } if hint_text else undefined diff --git a/lung_cancer_screening/questions/forms/agree_terms_of_use_form.py b/lung_cancer_screening/questions/forms/agree_terms_of_use_form.py new file mode 100644 index 00000000..e574b4ba --- /dev/null +++ b/lung_cancer_screening/questions/forms/agree_terms_of_use_form.py @@ -0,0 +1,28 @@ +from django import forms + +from ...nhsuk_forms.choice_field import MultipleChoiceField + +from ..models.terms_of_use_response import TermsOfUseResponse + + +class TermsOfUseForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["value"] = MultipleChoiceField( + choices=[(True, 'I agree')], + widget=forms.CheckboxSelectMultiple, + label="Accept terms of use", + label_classes="nhsuk-u-visually-hidden", + error_messages={ + "required": "Agree to the terms of use to continue", + "invalid_choice": "Agree to the terms of use to continue", + "invalid_list": "Agree to the terms of use to continue" + } + ) + class Meta: + model = TermsOfUseResponse + fields = ["value"] + + def clean_value(self): + values = self.cleaned_data.get("value") or [] + return "True" in values diff --git a/lung_cancer_screening/questions/jinja2/agree_terms_of_use.jinja b/lung_cancer_screening/questions/jinja2/agree_terms_of_use.jinja new file mode 100644 index 00000000..bc608dcd --- /dev/null +++ b/lung_cancer_screening/questions/jinja2/agree_terms_of_use.jinja @@ -0,0 +1,7 @@ +{% extends 'question_form.jinja' %} + +{% block prelude %} +

Accept terms of use

+ +

To continue, confirm that you have read and agree to the NHS Check if you need a lung scan terms of use (opens in new tab)

+{% endblock %} diff --git a/lung_cancer_screening/questions/jinja2/start.jinja b/lung_cancer_screening/questions/jinja2/start.jinja index 5f50a347..56d9efa2 100644 --- a/lung_cancer_screening/questions/jinja2/start.jinja +++ b/lung_cancer_screening/questions/jinja2/start.jinja @@ -42,7 +42,7 @@ {{ button({ "text": "Continue", - "href": url("questions:have_you_ever_smoked"), + "href": url("questions:agree_terms_of_use"), "classes": "nhsuk-button--login" }) }} diff --git a/lung_cancer_screening/questions/jinja2/terms_of_use.jinja b/lung_cancer_screening/questions/jinja2/terms_of_use.jinja new file mode 100644 index 00000000..a593215a --- /dev/null +++ b/lung_cancer_screening/questions/jinja2/terms_of_use.jinja @@ -0,0 +1,170 @@ +{% extends 'layout.jinja' %} + +{% block content %} +
+
+

NHS check if you need a lung scan terms of use

+ + +

Information:

+ +

Version 3, 5 March 2026

+ + + +
+

Introduction

+ +
    +
  1. We (NHS England) have developed a digital service called NHS check if you need a lung scan. This is a digital way to access NHS lung cancer screening. This service is currently in pilot so is only available in certain areas, and if you are invited by your GP to take part.
  2. +

    +

    This service will not provide you with any results or information in relation to the lung cancer screening, you will need to complete your lung cancer screening by phone to receive a result.

    + +

    To find out more about who we are and our role, visit the NHS England website.

    + + +
  3. Contacting us: if you have any questions about the service, contact us by email: england.digitallungcancerscreening@nhs.net
  4. +
+
+
+

When these terms apply

+ +
    +
  1. Please read these terms of use and our privacy policy, cookies policy and accessibility statement. By continuing to use the NHS check if you need a lung scan, you agree to be bound by these terms.
  2. +
  3. We may, at any time and in our sole discretion, amend these terms for any reason. The latest version of our terms will be accessible through the NHS check if you need a lung scan.
  4. +
+
+
+

How to use the NHS check if you need a lung scan

+ +
    +
  1. NHS check if you need a lung scan is free. To use NHS check if you need a lung scan, you must be invited to take part by your GP. Your GP will send you a letter and an SMS with a link to access the NHS check if you need a lung scan pilot service in a web browser.
  2. +
  3. To use the NHS check if you need a lung scan you need an NHS login account with a medium level of identity verification. If you do not have an account or a medium level of identity verification, you will be able to set this up as part of your application. Find out more about NHS login.
  4. +
  5. If you access or attempt to access the NHS check if you need a lung scan from outside of England, you are responsible for complying with any local laws that apply to you in the country from which you are using NHS check if you need a lung scan.
  6. +
  7. The NHS check if you need a lung scan is split into different sections. You must answer all the questions in each section to enable us to process your information. More information on this and how your information is used and processed is in the privacy policy.
  8. +
  9. The NHS check if you need a lung scan can only be accessed if you are aged between 55 and 74, registered with a supported GP surgery, and you smoke or used to smoke tobacco.
  10. +
+
+ +
+

Accessing the NHS check if you need a lung scan

+ +
    +
  1. You are responsible for making all arrangements necessary for you to access the NHS check if you need a lung scan, including but not limited to:
  2. +
      +
    • a secure internet connection (see Cyber Aware website)
    • +
    • an appropriate device, operating system and browser
    • +
    • using your own virus protection software (and regularly updating it) when accessing and using the NHS check if you need a lung scan
    • +
    + +
+
+ +
+

Details about the NHS check if you need a lung scan

+ +
    +
  1. If you download, print or export any of your submitted information, you are responsible for ensuring that this is held securely, and we will not be liable for any associated disclosure of sensitive and personal data.
  2. +
  3. In order for you to receive the intended benefits of the NHS check if you need a lung scan, you must ensure that all data provided by you is complete and accurate.
  4. +
  5. Your GP health records are created and kept up to date by your GP and remain under your GP’s control. We do not hold or have access to any GP records and are unable to answer queries about them or provide hard copies.
  6. +
  7. The NHS check if you need a lung scan: +
      +
    • is not a substitute for seeking medical advice - always follow any medical advice given by your healthcare professionals
    • +
    • does not provide medical or clinical diagnostic services
    • +
    • does not arrange or guarantee further healthcare treatment. You remain responsible for booking further healthcare treatment via the phone service as directed by your GP.
    • +
    +
  8. +
  9. We are not responsible for any delay or lack of response by a GP surgery or for the outcome of any decision your GP may make about any follow-on treatment or advice. No information or results will be shared with GPs through NHS check if you need a lung scan, and we do not guarantee it will lead to onward healthcare.
  10. +
+
+ +
+

Ending your use of the NHS check if you need a lung scan

+
    +
  1. You may stop using the NHS check if you need a lung scan at any time. If you fail to complete your NHS check if you need a lung scan within a set period of time your data will automatically be deleted. More information on how long your information is kept is in the privacy policy.
  2. +
+
+ +
+

Your right to use the NHS check if you need a lung scan

+
    +
  1. We own or have the right to use all intellectual property rights used for the provision of the NHS check if you need a lung scan, including rights in copyright, patents, database rights, trademarks and other intellectual property rights, ("NHS IPR").
  2. +
  3. You have permission to use the NHS check if you need a lung scan for the sole purposes described in these terms and must not use it in any other way.
  4. +
  5. Unless permitted by law or under these terms, you will:
  6. +
      +
    • not copy the NHS check if you need a lung scan or any NHS IPR, except where such copying is incidental to normal use
    • +
    • not rent, lease, sub-license, loan, translate, merge, adapt or modify the NHS check if you need a lung scan or any NHS IPR
    • +
    • not combine or incorporate the NHS check if you need a lung scan in any other programmes or services
    • +
    • not disassemble, decompile, reverse-engineer or create derivative works based on the whole or any part of the NHS check if you need a lung scan or other NHS IPR
    • +
    • comply with all technology control or export laws that apply to the technology used by the NHS check if you need a lung scan or any other NHS IPR
    • +
    + +
+
+ +
+

Prohibited uses

+
    +
  1. You may not use the NHS check if you need a lung scan: +
      +
    • to collect any data or attempt to decipher any transmissions to or from our servers
    • +
    • in a way that could damage, disable, overburden, impair or compromise our systems or security
    • +
    • to transmit any material that is insulting or offensive
    • +
    • in a way that interferes with other users
    • +
    • in any unlawful manner or for any unlawful purpose
    • +
    • in a manner that is improper use or inconsistent with these terms
    • +
    • to act fraudulently or maliciously by seeking to access or add data to another patient's GP record
    • +
    • to transmit, send or upload any data that contains viruses, Trojan horses, worms, spyware or any other harmful programs designed to adversely affect the operation of computer software or hardware
    • +
    • in connection with any kind of denial of service attack
    • +
    • on any device or operating system that has been modified outside the mobile device or operating system vendor supported or warranted configurations. This includes devices that have been "jail-broken" or "rooted"
    • +
    • with someone else's NHS login account
    • +
    +
  2. +
+

If you do any of the above acts you may also be committing a criminal offence, and we will report any such activity to the relevant law enforcement authorities. We will co-operate with those authorities by disclosing your identity to them.

+
+ +
+

Our liability to you

+
    +
  1. Although we make reasonable efforts to provide, maintain and update the NHS check if you need a lung scan it is provided "as is" and, to the extent permitted by law, we make no representations, warranties or guarantees, whether express or implied (including but not limited to the implied warranties of satisfactory quality and fitness for a particular purpose), that the NHS check if you need a lung scan: +
      +
    • is accurate, complete or up-to-date
    • +
    • will meet your particular requirements or needs
    • +
    • will always be available, error free, uninterrupted or free of viruses
    • +
    +
  2. We are not responsible for external links to or from the NHS check if you need a lung scan and cannot guarantee these will always work.
  3. +
  4. Nothing in these terms excludes or limits our liability for:
  5. +
      +
    • death or personal injury arising from our negligence
    • +
    • fraud or fraudulent misrepresentation
    • +
    • any loss or damage to a device or digital content belonging to you, if you can show that a) this was caused by NHS check if you need a lung scan and b) we failed use to use reasonable skill and care to prevent this
    • +
    • any other liability that cannot be excluded or limited under English law
    • +
    + +
  6. Subject to clause 9.3 of these terms, we will not be liable or responsible to you or any other person for:
  7. +
      +
    • any harm, loss or damage suffered where this is not caused by i) our negligence or ii) our breach of these terms
    • +
    • any loss or damage arising from an inability to access or use the NHS check if you need a lung scan in whole or in part
    • +
    • any business loss (including but not limited to loss of profits, revenue, contracts, anticipated savings, data, goodwill or wasted expenditure)
    • +
    • any indirect or consequential losses that were not foreseeable to both you and us when you commenced using the NHS check if you need a lung scan (loss or damage is "foreseeable" if it was an obvious consequence of our breach or if it was recognised by you and us at the time we entered into the contract created by your use of the NHS check if you need a lung scan)
    • +
    + +
  8. This clause 9 does not affect any legal rights you may have as a consumer in relation to defective services or software. Advice about your legal rights is available from your local Citizen's Advice or Trading Standards Office.
  9. +
+ +
+ +
+

General

+
    +
  1. These terms, any instructions in the service, and any other terms or policies referenced, set out the entire agreement between you and us in respect of your use of the NHS check if you need a lung scan.
  2. +
  3. These terms do not give any rights to any third party to enforce any of these terms.
  4. +
  5. Each of the clauses and sub-clauses of these terms operates separately. If any part is determined to be invalid or unenforceable it will be superseded by a valid and enforceable provision that most closely matches the intent of the original and all other terms shall continue in effect.
  6. +
  7. Even if we delay in enforcing these terms, we can still enforce them later.
  8. +
  9. The laws of England shall apply exclusively to these terms and all matters relating to use of the NHS check if you need a lung scan, and any dispute shall be subject to the exclusive jurisdiction of the courts of England.
  10. +
+
+
+
+{% endblock %} diff --git a/lung_cancer_screening/questions/migrations/0007_termsofuseresponse.py b/lung_cancer_screening/questions/migrations/0007_termsofuseresponse.py new file mode 100644 index 00000000..a538358b --- /dev/null +++ b/lung_cancer_screening/questions/migrations/0007_termsofuseresponse.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.11 on 2026-03-09 15:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('questions', '0006_alter_smokingfrequencyresponse_value'), + ] + + operations = [ + migrations.CreateModel( + name='TermsOfUseResponse', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('value', models.BooleanField()), + ('response_set', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='terms_of_use_response', to='questions.responseset')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/lung_cancer_screening/questions/models/__init__.py b/lung_cancer_screening/questions/models/__init__.py index 5ed6ceda..97c0536a 100644 --- a/lung_cancer_screening/questions/models/__init__.py +++ b/lung_cancer_screening/questions/models/__init__.py @@ -19,5 +19,6 @@ from .smoking_current_response import SmokingCurrentResponse # noqa: F401 from .smoking_frequency_response import SmokingFrequencyResponse # noqa: F401 from .smoked_amount_response import SmokedAmountResponse # noqa: F401 +from .terms_of_use_response import TermsOfUseResponse # noqa: F401 from .tobacco_smoking_history import TobaccoSmokingHistory # noqa: F401 from .weight_response import WeightResponse # noqa: F401 diff --git a/lung_cancer_screening/questions/models/response_set.py b/lung_cancer_screening/questions/models/response_set.py index 74c5f0f6..458836d2 100644 --- a/lung_cancer_screening/questions/models/response_set.py +++ b/lung_cancer_screening/questions/models/response_set.py @@ -109,6 +109,7 @@ def is_complete(self): def is_eligible(self): if not all( hasattr(self, attr) for attr in [ + "terms_of_use_response", 'have_you_ever_smoked_response', 'date_of_birth_response', 'check_need_appointment_response' @@ -117,6 +118,7 @@ def is_eligible(self): return False return all([ + self.terms_of_use_response.has_accepted(), self.have_you_ever_smoked_response.is_eligible(), self.date_of_birth_response.is_eligible(), self.check_need_appointment_response.is_eligible() diff --git a/lung_cancer_screening/questions/models/terms_of_use_response.py b/lung_cancer_screening/questions/models/terms_of_use_response.py new file mode 100644 index 00000000..3a559a05 --- /dev/null +++ b/lung_cancer_screening/questions/models/terms_of_use_response.py @@ -0,0 +1,11 @@ +from django.db import models + +from .base import BaseModel +from .response_set import ResponseSet + +class TermsOfUseResponse(BaseModel): + response_set = models.OneToOneField(ResponseSet, on_delete=models.CASCADE, related_name='terms_of_use_response') + value = models.BooleanField() + + def has_accepted(self): + return self.value diff --git a/lung_cancer_screening/questions/tests/factories/response_set_factory.py b/lung_cancer_screening/questions/tests/factories/response_set_factory.py index 83d6902f..76001786 100644 --- a/lung_cancer_screening/questions/tests/factories/response_set_factory.py +++ b/lung_cancer_screening/questions/tests/factories/response_set_factory.py @@ -26,6 +26,11 @@ class Meta: class Params: eligible = factory.Trait( + terms_of_use_response=factory.RelatedFactory( + "lung_cancer_screening.questions.tests.factories.terms_of_use_response_factory.TermsOfUseResponseFactory", + factory_related_name="response_set", + accepted=True + ), have_you_ever_smoked_response=factory.RelatedFactory( "lung_cancer_screening.questions.tests.factories.have_you_ever_smoked_response_factory.HaveYouEverSmokedResponseFactory", factory_related_name="response_set", diff --git a/lung_cancer_screening/questions/tests/factories/terms_of_use_response_factory.py b/lung_cancer_screening/questions/tests/factories/terms_of_use_response_factory.py new file mode 100644 index 00000000..2e0e8c78 --- /dev/null +++ b/lung_cancer_screening/questions/tests/factories/terms_of_use_response_factory.py @@ -0,0 +1,21 @@ +import factory + +from .response_set_factory import ResponseSetFactory +from ...models.terms_of_use_response import TermsOfUseResponse + + +class TermsOfUseResponseFactory(factory.django.DjangoModelFactory): + class Meta: + model = TermsOfUseResponse + + response_set = factory.SubFactory(ResponseSetFactory) + value = factory.Faker('boolean') + + class Params: + accepted = factory.Trait( + value=True + ) + + not_accepted = factory.Trait( + value=False + ) diff --git a/lung_cancer_screening/questions/tests/unit/forms/test_agree_terms_of_use_form.py b/lung_cancer_screening/questions/tests/unit/forms/test_agree_terms_of_use_form.py new file mode 100644 index 00000000..ce0e35b8 --- /dev/null +++ b/lung_cancer_screening/questions/tests/unit/forms/test_agree_terms_of_use_form.py @@ -0,0 +1,56 @@ +from django.test import TestCase, tag + +from ....models.terms_of_use_response import TermsOfUseResponse + +from ...factories.response_set_factory import ResponseSetFactory +from ....forms.agree_terms_of_use_form import TermsOfUseForm + +@tag("TermsOfUse") +class TestAgreeTermsOfUseForm(TestCase): + def setUp(self): + self.response_set = ResponseSetFactory() + self.response = TermsOfUseResponse.objects.create( + response_set=self.response_set, + value=False + ) + + + def test_is_valid_with_a_valid_value(self): + form = TermsOfUseForm( + instance=self.response, + data={ + "value": ["True"] + } + ) + + self.assertTrue(form.is_valid()) + self.assertEqual( + form.data["value"], + ["True"] + ) + + def test_is_invalid_with_an_invalid_value(self): + form = TermsOfUseForm( + instance=self.response, + data={ + "value": "Invalid entry" + } + ) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors["value"], + ["Agree to the terms of use to continue"] + ) + + def test_is_invalid_when_no_option_is_selected(self): + form = TermsOfUseForm( + instance=self.response, + data={ + "value": None + } + ) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors["value"], + ["Agree to the terms of use to continue"] + ) diff --git a/lung_cancer_screening/questions/tests/unit/models/test_agree_terms_of_use_response.py b/lung_cancer_screening/questions/tests/unit/models/test_agree_terms_of_use_response.py new file mode 100644 index 00000000..64d94cec --- /dev/null +++ b/lung_cancer_screening/questions/tests/unit/models/test_agree_terms_of_use_response.py @@ -0,0 +1,52 @@ +from django.test import TestCase, tag + +from ...factories.response_set_factory import ResponseSetFactory +from ...factories.terms_of_use_response_factory import TermsOfUseResponseFactory + +from ....models.terms_of_use_response import TermsOfUseResponse + +@tag("TermsOfUse") +class TestTermsOfUseResponse(TestCase): + def setUp(self): + self.response_set = ResponseSetFactory() + + def test_has_a_valid_factory(self): + model = TermsOfUseResponseFactory.build(response_set=self.response_set) + model.full_clean() + + + def test_has_response_set_as_foreign_key(self): + response_set = ResponseSetFactory() + response = TermsOfUseResponse.objects.create( + response_set=response_set, + value=True + ) + + self.assertEqual(response.response_set, response_set) + + def test_has_value_as_bool(self): + response_set = ResponseSetFactory() + response = TermsOfUseResponse.objects.create( + response_set=response_set, + value=False + ) + + self.assertIsInstance(response.value, bool) + + + def test_has_accepted_returns_true_when_value_is_true(self): + response = TermsOfUseResponse.objects.create( + response_set=self.response_set, + value=True + ) + + self.assertTrue(response.has_accepted()) + + + def test_has_accepted_returns_false_when_value_is_false(self): + response = TermsOfUseResponse.objects.create( + response_set=self.response_set, + value=False + ) + + self.assertFalse(response.has_accepted()) diff --git a/lung_cancer_screening/questions/tests/unit/models/test_response_set.py b/lung_cancer_screening/questions/tests/unit/models/test_response_set.py index d5f08475..7754face 100644 --- a/lung_cancer_screening/questions/tests/unit/models/test_response_set.py +++ b/lung_cancer_screening/questions/tests/unit/models/test_response_set.py @@ -6,6 +6,7 @@ from ...factories.user_factory import UserFactory from ...factories.response_set_factory import ResponseSetFactory +from ...factories.terms_of_use_response_factory import TermsOfUseResponseFactory from ...factories.tobacco_smoking_history_factory import TobaccoSmokingHistoryFactory from ...factories.have_you_ever_smoked_response_factory import HaveYouEverSmokedResponseFactory from ...factories.date_of_birth_response_factory import DateOfBirthResponseFactory @@ -215,6 +216,11 @@ def test_is_ineligible_returns_false_when_any_eligibility_question_is_not_answer def test_is_eligible_returns_true_when_smoked_age_and_need_appointment_are_eligible(self): + TermsOfUseResponseFactory.create( + response_set=self.response_set, + value=True + ) + HaveYouEverSmokedResponseFactory( response_set=self.response_set, eligible=True diff --git a/lung_cancer_screening/questions/tests/unit/views/test_age_when_started_smoking.py b/lung_cancer_screening/questions/tests/unit/views/test_age_when_started_smoking.py index 9541765f..38a609d2 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_age_when_started_smoking.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_age_when_started_smoking.py @@ -41,7 +41,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:age_when_started_smoking") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -93,7 +93,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_an_age_when_started_smoking_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) 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 index 0f6cfc58..ebd50d7d 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_asbestos_exposure.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_asbestos_exposure.py @@ -41,7 +41,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:asbestos_exposure") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): @@ -93,7 +93,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_an_asbestos_exposure_response(self): diff --git a/lung_cancer_screening/questions/tests/unit/views/test_cancer_diagnosis.py b/lung_cancer_screening/questions/tests/unit/views/test_cancer_diagnosis.py index 61b1a952..5d3a6863 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_cancer_diagnosis.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_cancer_diagnosis.py @@ -41,7 +41,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:cancer_diagnosis") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -93,7 +93,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_cancer_diagnosis_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_check_need_appointment.py b/lung_cancer_screening/questions/tests/unit/views/test_check_need_appointment.py index ea3f31cd..31cbd4f5 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_check_need_appointment.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_check_need_appointment.py @@ -1,6 +1,8 @@ from django.test import TestCase, tag from django.urls import reverse +from ...factories.terms_of_use_response_factory import TermsOfUseResponseFactory + from .helpers.authentication import login_user from ...factories.response_set_factory import ResponseSetFactory from ...factories.date_of_birth_response_factory import DateOfBirthResponseFactory @@ -12,6 +14,11 @@ def setUp(self): self.response_set = ResponseSetFactory.create(user=self.user) + TermsOfUseResponseFactory.create( + response_set=self.response_set, + value=True + ) + def test_redirects_if_the_user_is_not_logged_in(self): self.client.logout() @@ -70,6 +77,11 @@ def setUp(self): self.user = login_user(self.client) self.response_set = ResponseSetFactory.create(user=self.user) + TermsOfUseResponseFactory.create( + response_set=self.response_set, + value=True + ) + self.valid_params = {"value": False} diff --git a/lung_cancer_screening/questions/tests/unit/views/test_date_of_birth.py b/lung_cancer_screening/questions/tests/unit/views/test_date_of_birth.py index 50140315..a636ace8 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_date_of_birth.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_date_of_birth.py @@ -9,6 +9,7 @@ ) from ...factories.response_set_factory import ResponseSetFactory from ...factories.have_you_ever_smoked_response_factory import HaveYouEverSmokedResponseFactory +from ...factories.terms_of_use_response_factory import TermsOfUseResponseFactory @tag("DateOfBirth") @@ -19,6 +20,12 @@ def setUp(self): self.response_set = ResponseSetFactory.create( user=self.user, ) + + TermsOfUseResponseFactory.create( + response_set=self.response_set, + value=True + ) + self.response = HaveYouEverSmokedResponseFactory.create( response_set=self.response_set, value=HaveYouEverSmokedValues.YES_I_USED_TO_SMOKE_REGULARLY, @@ -83,6 +90,12 @@ def setUp(self): self.response_set = ResponseSetFactory.create( user=self.user, ) + + TermsOfUseResponseFactory.create( + response_set=self.response_set, + value=True + ) + self.response = HaveYouEverSmokedResponseFactory.create( response_set=self.response_set, value=HaveYouEverSmokedValues.YES_I_USED_TO_SMOKE_REGULARLY, diff --git a/lung_cancer_screening/questions/tests/unit/views/test_education.py b/lung_cancer_screening/questions/tests/unit/views/test_education.py index f0476d5d..81da78e0 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_education.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_education.py @@ -44,7 +44,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:education") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -97,7 +97,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_an_education_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) 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 c95d8d31..ea530f96 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_ethnicity.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_ethnicity.py @@ -43,7 +43,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:ethnicity") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -95,7 +95,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_an_ethnicity_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_family_history_lung_cancer.py b/lung_cancer_screening/questions/tests/unit/views/test_family_history_lung_cancer.py index fa9cc69a..5aaf6c7b 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_family_history_lung_cancer.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_family_history_lung_cancer.py @@ -43,7 +43,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:family_history_lung_cancer") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -98,7 +98,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_family_history_lung_cancer_response(self): diff --git a/lung_cancer_screening/questions/tests/unit/views/test_gender.py b/lung_cancer_screening/questions/tests/unit/views/test_gender.py index cdca25e0..73821272 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_gender.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_gender.py @@ -43,7 +43,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:gender") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -95,7 +95,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_gender_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_have_you_ever_smoked.py b/lung_cancer_screening/questions/tests/unit/views/test_have_you_ever_smoked.py index bcc2a31d..96a8286b 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_have_you_ever_smoked.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_have_you_ever_smoked.py @@ -1,8 +1,10 @@ from django.test import TestCase, tag from django.urls import reverse +from ...factories.terms_of_use_response_factory import TermsOfUseResponseFactory + from .helpers.authentication import login_user -from lung_cancer_screening.questions.models.have_you_ever_smoked_response import HaveYouEverSmokedResponse, HaveYouEverSmokedValues +from ....models.have_you_ever_smoked_response import HaveYouEverSmokedResponse, HaveYouEverSmokedValues from ...factories.response_set_factory import ResponseSetFactory @@ -11,7 +13,6 @@ class TestGetHaveYouEverSmoked(TestCase): def setUp(self): self.user = login_user(self.client) - def test_get_redirects_if_the_user_is_not_logged_in(self): self.client.logout() @@ -36,6 +37,11 @@ def test_get_redirects_when_an_submitted_response_set_exists_within_the_last_yea def test_get_responds_successfully(self): + TermsOfUseResponseFactory.create( + response_set=ResponseSetFactory.create(user=self.user), + value=True + ) + response = self.client.get(reverse("questions:have_you_ever_smoked")) self.assertEqual(response.status_code, 200) @@ -48,7 +54,6 @@ def setUp(self): self.valid_params = { "value": HaveYouEverSmokedValues.YES_I_USED_TO_SMOKE_REGULARLY } - def test_post_redirects_if_the_user_is_not_logged_in(self): self.client.logout() @@ -60,21 +65,12 @@ def test_post_redirects_if_the_user_is_not_logged_in(self): self.assertRedirects(response, "/oidc/authenticate/?next=/have-you-ever-smoked", fetch_redirect_response=False) - def test_post_creates_an_unsubmitted_response_set_for_the_user_when_no_response_set_exists(self): - self.client.post( - reverse("questions:have_you_ever_smoked"), - self.valid_params - ) - - response_set = self.user.responseset_set.first() - self.assertEqual(self.user.responseset_set.count(), 1) - self.assertEqual(response_set.submitted_at, None) - self.assertEqual(HaveYouEverSmokedResponse.objects.get(response_set=response_set).value, self.valid_params["value"]) - self.assertEqual(response_set.user, self.user) - - def test_post_updates_an_unsubmitted_response_set_for_the_user_when_an_unsubmitted_response_set_exists(self): response_set = self.user.responseset_set.create() + TermsOfUseResponseFactory.create( + response_set=response_set, + value=True + ) self.client.post( reverse("questions:have_you_ever_smoked"), @@ -87,13 +83,17 @@ def test_post_updates_an_unsubmitted_response_set_for_the_user_when_an_unsubmitt self.assertEqual(HaveYouEverSmokedResponse.objects.get(response_set=response_set).value, self.valid_params["value"]) self.assertEqual(response_set.user, self.user) - def test_post_creates_an_new_unsubmitted_response_set_for_the_user_when_a_non_recently_submitted_response_set_exists(self): ResponseSetFactory.create( user=self.user, not_recently_submitted=True ) + TermsOfUseResponseFactory.create( + response_set=ResponseSetFactory.create(user=self.user), + value=True + ) + self.client.post( reverse("questions:have_you_ever_smoked"), self.valid_params @@ -123,6 +123,11 @@ def test_post_redirects_when_an_submitted_response_set_exists_within_the_last_ye def test_post_redirects_to_date_of_birth(self): + TermsOfUseResponseFactory.create( + response_set=ResponseSetFactory.create(user=self.user), + value=True + ) + response = self.client.post( reverse("questions:have_you_ever_smoked"), self.valid_params @@ -131,6 +136,10 @@ def test_post_redirects_to_date_of_birth(self): self.assertRedirects(response, reverse("questions:date_of_birth")) def test_post_redirects_to_responses_if_change_query_param_is_true(self): + TermsOfUseResponseFactory.create( + response_set=ResponseSetFactory.create(user=self.user), + value=True + ) response = self.client.post( reverse("questions:have_you_ever_smoked"), { @@ -142,6 +151,10 @@ def test_post_redirects_to_responses_if_change_query_param_is_true(self): self.assertRedirects(response, reverse("questions:responses"), fetch_redirect_response=False) def test_post_responds_with_422_if_the_date_response_fails_to_create(self): + TermsOfUseResponseFactory.create( + response_set=ResponseSetFactory.create(user=self.user), + value=True + ) response = self.client.post( reverse("questions:have_you_ever_smoked"), {"value": "something not in list"} @@ -151,6 +164,11 @@ def test_post_responds_with_422_if_the_date_response_fails_to_create(self): def test_post_redirects_if_the_user_not_a_smoker(self): + TermsOfUseResponseFactory.create( + response_set=ResponseSetFactory.create(user=self.user), + value=True + ) + response = self.client.post( reverse("questions:have_you_ever_smoked"), {"value": HaveYouEverSmokedValues.NO_I_HAVE_NEVER_SMOKED.value } diff --git a/lung_cancer_screening/questions/tests/unit/views/test_height.py b/lung_cancer_screening/questions/tests/unit/views/test_height.py index acdbee79..713f1ae1 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_height.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_height.py @@ -43,7 +43,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:height") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -130,7 +130,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_height_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_periods_when_you_stopped_smoking.py b/lung_cancer_screening/questions/tests/unit/views/test_periods_when_you_stopped_smoking.py index 694a34c1..622f4607 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_periods_when_you_stopped_smoking.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_periods_when_you_stopped_smoking.py @@ -45,7 +45,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:periods_when_you_stopped_smoking") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): @@ -101,7 +101,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_periods_when_you_stopped_smoking_response(self): diff --git a/lung_cancer_screening/questions/tests/unit/views/test_relatives_age_when_diagnosed.py b/lung_cancer_screening/questions/tests/unit/views/test_relatives_age_when_diagnosed.py index 74f6b910..3f7fb17a 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_relatives_age_when_diagnosed.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_relatives_age_when_diagnosed.py @@ -46,7 +46,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:relatives_age_when_diagnosed") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): FamilyHistoryLungCancerResponseFactory( @@ -118,7 +118,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_relatives_age_when_diagnosed_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_respiratory_conditions.py b/lung_cancer_screening/questions/tests/unit/views/test_respiratory_conditions.py index 658ab5ff..cdfeac99 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_respiratory_conditions.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_respiratory_conditions.py @@ -42,7 +42,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:respiratory_conditions") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -95,7 +95,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_respiratory_conditions_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_responses.py b/lung_cancer_screening/questions/tests/unit/views/test_responses.py index 910f0d27..2a6761bb 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_responses.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_responses.py @@ -48,7 +48,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:responses") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_get_responds_successfully(self): @@ -120,7 +120,7 @@ def test_redirects_when_the_user_is_not_eligible(self): response = self.client.post(reverse("questions:responses")) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_post_responds_with_422_if_the_response_set_is_not_complete(self): diff --git a/lung_cancer_screening/questions/tests/unit/views/test_sex_at_birth.py b/lung_cancer_screening/questions/tests/unit/views/test_sex_at_birth.py index a7316928..f5fff9a3 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_sex_at_birth.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_sex_at_birth.py @@ -43,7 +43,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:sex_at_birth") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -95,7 +95,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_sex_at_birth_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_smoked_amount.py b/lung_cancer_screening/questions/tests/unit/views/test_smoked_amount.py index 8d455b28..da02c61d 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_smoked_amount.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_smoked_amount.py @@ -53,7 +53,7 @@ def test_redirects_when_the_user_is_not_eligible(self): }) ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_404_when_a_smoking_history_item_does_not_exist_for_the_given_type(self): self.smoking_history.delete() @@ -175,7 +175,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_404_when_a_smoking_history_item_does_not_exist_for_the_given_type(self): self.smoking_history.delete() diff --git a/lung_cancer_screening/questions/tests/unit/views/test_smoked_total_years.py b/lung_cancer_screening/questions/tests/unit/views/test_smoked_total_years.py index b2a3a8b2..4742451f 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_smoked_total_years.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_smoked_total_years.py @@ -53,7 +53,7 @@ def test_redirects_when_the_user_is_not_eligible(self): }) ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_redirects_when_the_user_has_not_answered_age_when_started_smoking(self): @@ -236,7 +236,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_redirects_when_the_user_has_not_answered_age_when_started_smoking(self): diff --git a/lung_cancer_screening/questions/tests/unit/views/test_smoking_change.py b/lung_cancer_screening/questions/tests/unit/views/test_smoking_change.py index e3b4d94a..ede39c0e 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_smoking_change.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_smoking_change.py @@ -66,7 +66,7 @@ def test_redirects_when_the_user_is_not_eligible(self): ) ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_redirects_to_smoking_frequency_when_does_not_have_a_smoking_frequency_response( self, @@ -188,7 +188,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_smoking_current.py b/lung_cancer_screening/questions/tests/unit/views/test_smoking_current.py index 41112fec..e570b6b3 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_smoking_current.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_smoking_current.py @@ -56,7 +56,7 @@ def test_redirects_when_the_user_is_not_eligible(self): }) ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): @@ -169,7 +169,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_smoking_current_response(self): self.client.post(reverse("questions:smoking_current", kwargs = { diff --git a/lung_cancer_screening/questions/tests/unit/views/test_smoking_frequency.py b/lung_cancer_screening/questions/tests/unit/views/test_smoking_frequency.py index 4ea9b55e..18e17e16 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_smoking_frequency.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_smoking_frequency.py @@ -64,7 +64,7 @@ def test_redirects_when_the_user_is_not_eligible(self): }) ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_redirects_to_smoking_current_if_the_user_has_not_answered_smoking_current(self): @@ -211,7 +211,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_redirects_to_smoking_current_if_the_user_has_not_answered_smoking_current(self): diff --git a/lung_cancer_screening/questions/tests/unit/views/test_terms_of_use.py b/lung_cancer_screening/questions/tests/unit/views/test_terms_of_use.py new file mode 100644 index 00000000..09d0deec --- /dev/null +++ b/lung_cancer_screening/questions/tests/unit/views/test_terms_of_use.py @@ -0,0 +1,128 @@ +from django.test import TestCase, tag +from django.urls import reverse + +from ...factories.terms_of_use_response_factory import TermsOfUseResponseFactory + +from ....models.terms_of_use_response import TermsOfUseResponse +from .helpers.authentication import login_user +from ...factories.response_set_factory import ResponseSetFactory + +@tag("TermsOfUse") +class TestGetAgreeTermsOfUse(TestCase): + def setUp(self): + self.user = login_user(self.client) + + self.response_set = ResponseSetFactory.create(user=self.user) + + + def test_redirects_if_the_user_is_not_logged_in(self): + self.client.logout() + + response = self.client.get( + reverse("questions:agree_terms_of_use") + ) + + self.assertRedirects( + response, + "/oidc/authenticate/?next=/agree-terms-of-use", + fetch_redirect_response=False + ) + + + def test_redirects_when_a_submitted_response_set_exists_within_the_last_year(self): + self.response_set.delete() + ResponseSetFactory.create( + user=self.user, + recently_submitted=True + ) + + response = self.client.get( + reverse("questions:agree_terms_of_use") + ) + + self.assertRedirects(response, reverse("questions:confirmation")) + + + def test_responds_successfully(self): + TermsOfUseResponseFactory.create( + response_set=self.response_set, accepted=True + ) + + response = self.client.get(reverse("questions:agree_terms_of_use")) + + self.assertEqual(response.status_code, 200) + + +@tag("TermsOfUse") +class TestPostAgreeTermsOfUse(TestCase): + def setUp(self): + self.user = login_user(self.client) + self.response_set = ResponseSetFactory.create(user=self.user) + + self.valid_params = {"value": True} + + + def test_redirects_if_the_user_is_not_logged_in(self): + self.client.logout() + + response = self.client.post( + reverse("questions:agree_terms_of_use"), + self.valid_params + ) + + self.assertRedirects(response, "/oidc/authenticate/?next=/agree-terms-of-use", fetch_redirect_response=False) + + + def test_redirects_when_a_submitted_response_set_exists_within_the_last_year(self): + self.response_set.delete() + ResponseSetFactory.create( + user=self.user, + recently_submitted=True + ) + + response = self.client.post( + reverse("questions:agree_terms_of_use"), + self.valid_params + ) + + self.assertRedirects(response, reverse("questions:confirmation")) + + + def test_redirects_to_the_next_page(self): + TermsOfUseResponseFactory.create( + response_set=self.response_set, accepted=True + ) + + response = self.client.post( + reverse("questions:agree_terms_of_use"), + self.valid_params + ) + + self.assertRedirects(response, reverse("questions:have_you_ever_smoked"), fetch_redirect_response=False) + + + def test_post_creates_an_unsubmitted_response_set_for_the_user_when_no_response_set_exists(self): + self.client.post( + reverse("questions:agree_terms_of_use"), + self.valid_params + ) + + response_set = self.user.responseset_set.first() + + self.assertEqual(self.user.responseset_set.count(), 1) + self.assertEqual(response_set.submitted_at, None) + self.assertEqual(TermsOfUseResponse.objects.get(response_set=response_set).value, self.valid_params["value"]) + self.assertEqual(response_set.user, self.user) + + + def test_responds_with_422_if_the_response_fails_to_create(self): + TermsOfUseResponseFactory.create( + response_set=self.response_set, accepted=True + ) + + response = self.client.post( + reverse("questions:agree_terms_of_use"), + {"value": False} + ) + + self.assertEqual(response.status_code, 422) diff --git a/lung_cancer_screening/questions/tests/unit/views/test_types_tobacco_smoking.py b/lung_cancer_screening/questions/tests/unit/views/test_types_tobacco_smoking.py index 65fb07c9..1031a5d0 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_types_tobacco_smoking.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_types_tobacco_smoking.py @@ -47,7 +47,7 @@ def test_redirects_when_the_user_is_not_eligible(self): response = self.client.get(reverse("questions:types_tobacco_smoking")) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_get_responds_successfully(self): @@ -102,7 +102,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_tobacco_smoking_type_parent_model_for_each_type_given(self): diff --git a/lung_cancer_screening/questions/tests/unit/views/test_weight.py b/lung_cancer_screening/questions/tests/unit/views/test_weight.py index d86706d3..25066b71 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_weight.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_weight.py @@ -43,7 +43,7 @@ def test_redirects_when_the_user_is_not_eligible(self): reverse("questions:weight") ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_responds_successfully(self): ResponseSetFactory.create(user=self.user, eligible=True) @@ -129,7 +129,7 @@ def test_redirects_when_the_user_is_not_eligible(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + self.assertRedirects(response, reverse("questions:agree_terms_of_use")) def test_creates_a_weight_response(self): response_set = ResponseSetFactory.create(user=self.user, eligible=True) diff --git a/lung_cancer_screening/questions/urls.py b/lung_cancer_screening/questions/urls.py index a32af907..236389ca 100644 --- a/lung_cancer_screening/questions/urls.py +++ b/lung_cancer_screening/questions/urls.py @@ -45,11 +45,13 @@ from .views.start import StartView from .views.weight import WeightView from .views.confirmation import ConfirmationView +from .views.agree_terms_of_use import AgreeTermsOfUseView urlpatterns = [ path('', RedirectView.as_view(url='/start'), name='root'), path('age-range-exit', AgeRangeExitView.as_view(), name='age_range_exit'), path('age-when-started-smoking', AgeWhenStartedSmokingView.as_view(), name='age_when_started_smoking'), + path('agree-terms-of-use', AgreeTermsOfUseView.as_view(), name='agree_terms_of_use'), path("agree-to-share-information", TemplateView.as_view(template_name="agree_to_share_information.jinja"), name="agree_to_share_information"), path('asbestos-exposure', AsbestosExposureView.as_view(), name='asbestos_exposure'), path('call-us-to-book-an-appointment', BookAnAppointmentExitView.as_view(), name='book_an_appointment'), @@ -78,6 +80,7 @@ path('check-your-answers', ResponsesView.as_view(), name='responses'), path('sex-at-birth', SexAtBirthView.as_view(), name='sex_at_birth'), path('start', StartView.as_view(), name='start'), + path('terms-of-use', TemplateView.as_view(template_name='terms_of_use.jinja'), name='terms_of_use'), path('weight', WeightView.as_view(), name='weight'), path('confirmation', ConfirmationView.as_view(), name='confirmation'), path('privacy-policy', TemplateView.as_view(template_name='privacy_policy.jinja'), name='privacy_policy'), diff --git a/lung_cancer_screening/questions/views/agree_terms_of_use.py b/lung_cancer_screening/questions/views/agree_terms_of_use.py new file mode 100644 index 00000000..8a86ee83 --- /dev/null +++ b/lung_cancer_screening/questions/views/agree_terms_of_use.py @@ -0,0 +1,22 @@ +from django.urls import reverse, reverse_lazy +from django.contrib.auth.mixins import LoginRequiredMixin + + +from .mixins.ensure_response_set import EnsureResponseSet +from .question_base_view import QuestionBaseView +from ..forms.agree_terms_of_use_form import TermsOfUseForm +from ..models.terms_of_use_response import TermsOfUseResponse + + +class AgreeTermsOfUseView(LoginRequiredMixin, EnsureResponseSet, QuestionBaseView): + template_name = "agree_terms_of_use.jinja" + form_class = TermsOfUseForm + model = TermsOfUseResponse + success_url = reverse_lazy("questions:have_you_ever_smoked") + back_link_url = reverse_lazy("questions:start") + + def get_success_url(self): + if self.object.value: + return reverse("questions:have_you_ever_smoked") + else: + return super().get_success_url() diff --git a/lung_cancer_screening/questions/views/check_need_appointment.py b/lung_cancer_screening/questions/views/check_need_appointment.py index 0dabc757..b0331e15 100644 --- a/lung_cancer_screening/questions/views/check_need_appointment.py +++ b/lung_cancer_screening/questions/views/check_need_appointment.py @@ -2,6 +2,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect +from lung_cancer_screening.questions.views.mixins.ensure_accepted_terms_of_use import EnsureAcceptedTermsEligible + from .mixins.ensure_response_set import EnsureResponseSet from .question_base_view import QuestionBaseView from ..forms.check_need_appointment_form import CheckNeedAppointmentForm @@ -21,7 +23,7 @@ def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) -class CheckNeedAppointmentView(LoginRequiredMixin, EnsureResponseSet, EnsureAgeEligible, QuestionBaseView): +class CheckNeedAppointmentView(LoginRequiredMixin, EnsureResponseSet, EnsureAcceptedTermsEligible, EnsureAgeEligible, QuestionBaseView): template_name = "check_need_appointment.jinja" form_class = CheckNeedAppointmentForm model = CheckNeedAppointmentResponse diff --git a/lung_cancer_screening/questions/views/date_of_birth.py b/lung_cancer_screening/questions/views/date_of_birth.py index 33ac354b..e8c71419 100644 --- a/lung_cancer_screening/questions/views/date_of_birth.py +++ b/lung_cancer_screening/questions/views/date_of_birth.py @@ -2,6 +2,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect +from lung_cancer_screening.questions.views.mixins.ensure_accepted_terms_of_use import EnsureAcceptedTermsEligible + from .mixins.ensure_response_set import EnsureResponseSet from .question_base_view import QuestionBaseView from ..forms.date_of_birth_form import DateOfBirthForm @@ -18,7 +20,7 @@ def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) -class DateOfBirthView(LoginRequiredMixin, EnsureResponseSet, EnsureSmokedEligible, QuestionBaseView): +class DateOfBirthView(LoginRequiredMixin, EnsureResponseSet, EnsureAcceptedTermsEligible, EnsureSmokedEligible, QuestionBaseView): template_name = "question_form.jinja" form_class = DateOfBirthForm model = DateOfBirthResponse 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 c3168587..854edcff 100644 --- a/lung_cancer_screening/questions/views/have_you_ever_smoked.py +++ b/lung_cancer_screening/questions/views/have_you_ever_smoked.py @@ -1,20 +1,23 @@ from django.urls import reverse, reverse_lazy from django.contrib.auth.mixins import LoginRequiredMixin +from .mixins.ensure_accepted_terms_of_use import EnsureAcceptedTermsEligible from .mixins.ensure_response_set import EnsureResponseSet + from .question_base_view import QuestionBaseView + from ..forms.have_you_ever_smoked_form import HaveYouEverSmokedForm from ..models.have_you_ever_smoked_response import ( HaveYouEverSmokedResponse ) -class HaveYouEverSmokedView(LoginRequiredMixin, EnsureResponseSet, QuestionBaseView): +class HaveYouEverSmokedView(LoginRequiredMixin, EnsureResponseSet, EnsureAcceptedTermsEligible, QuestionBaseView): template_name = "have_you_ever_smoked.jinja" form_class = HaveYouEverSmokedForm model = HaveYouEverSmokedResponse success_url = reverse_lazy("questions:date_of_birth") - back_link_url = reverse_lazy("questions:start") + back_link_url = reverse_lazy("questions:agree_terms_of_use") def get_success_url(self): if self.object.has_smoked_regularly(): diff --git a/lung_cancer_screening/questions/views/mixins/ensure_accepted_terms_of_use.py b/lung_cancer_screening/questions/views/mixins/ensure_accepted_terms_of_use.py new file mode 100644 index 00000000..fe434384 --- /dev/null +++ b/lung_cancer_screening/questions/views/mixins/ensure_accepted_terms_of_use.py @@ -0,0 +1,13 @@ +from django.shortcuts import redirect +from django.urls import reverse + + +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) diff --git a/lung_cancer_screening/questions/views/mixins/ensure_eligible.py b/lung_cancer_screening/questions/views/mixins/ensure_eligible.py index cec58609..edcdc058 100644 --- a/lung_cancer_screening/questions/views/mixins/ensure_eligible.py +++ b/lung_cancer_screening/questions/views/mixins/ensure_eligible.py @@ -5,6 +5,6 @@ class EnsureEligibleMixin: def dispatch(self, request, *args, **kwargs): if not request.response_set.is_eligible(): - return redirect(reverse("questions:have_you_ever_smoked")) + return redirect(reverse("questions:agree_terms_of_use")) return super().dispatch(request, *args, **kwargs) diff --git a/scripts/tests/unit.sh b/scripts/tests/unit.sh index a379966f..6573b06d 100755 --- a/scripts/tests/unit.sh +++ b/scripts/tests/unit.sh @@ -21,8 +21,10 @@ cd "$(git rev-parse --show-toplevel)" if [[ -n "${TAG:-}" ]]; then TAG="--tag=$TAG" + COVERAGE="" else TAG="" + COVERAGE="&& coverage report -m --skip-covered" fi if [[ -n "${TEST_MODULE:-}" ]]; then @@ -38,6 +40,5 @@ fi env UID="$(id -u)" docker compose run --rm web sh -c " \ poetry run coverage run manage.py test $TEST_MODULE $TAG \ --settings=lung_cancer_screening.settings_test \ - --exclude-tag=accessibility && \ - coverage report -m --skip-covered \ -" + --exclude-tag=accessibility \ + $COVERAGE"