diff --git a/features/date_of_birth_page.feature b/features/date_of_birth_page.feature index 4f27518e..2b83a815 100644 --- a/features/date_of_birth_page.feature +++ b/features/date_of_birth_page.feature @@ -43,7 +43,7 @@ Feature: Date of birth page Scenario: Checking responses and changing them Given I am logged in - And I have answered have you ever smoked with an eligible response + And I have answered questions showing I am eligible When I go to "/date-of-birth" And I fill in and submit my date of birth as 55 years ago When I go to "/check-your-answers" diff --git a/features/have_you_ever_smoked_page.feature b/features/have_you_ever_smoked_page.feature index 4ccf2071..2a145880 100644 --- a/features/have_you_ever_smoked_page.feature +++ b/features/have_you_ever_smoked_page.feature @@ -30,6 +30,7 @@ Feature: Have you ever smoked page Scenario: Checking responses and changing them Given I am logged in + And I have answered questions showing I am eligible When I go to "/have-you-ever-smoked" And I fill in and submit my smoking status with "Yes, I used to smoke" When I go to "/check-your-answers" diff --git a/features/periods_when_you_stopped_smoking.feature b/features/periods_when_you_stopped_smoking.feature index 8924d131..ae0ba95c 100644 --- a/features/periods_when_you_stopped_smoking.feature +++ b/features/periods_when_you_stopped_smoking.feature @@ -26,7 +26,7 @@ Feature: Periods when you stopped smoking page When I check "Yes" And I fill in "Enter the total number of years you stopped smoking for" with "1" And I submit the form - Then I am on "/check-your-answers" + Then I am on "/types-tobacco-smoking" Scenario: Checking responses and changing them Given I am logged in diff --git a/features/questionnaire.feature b/features/questionnaire.feature index 07d5b82b..81c4f5f1 100644 --- a/features/questionnaire.feature +++ b/features/questionnaire.feature @@ -65,9 +65,12 @@ Feature: Questionnaire And I fill in "Enter the total number of years you stopped smoking for" with "10" And I submit the form - Then I am on "/check-your-answers" - And I see a back link to "/periods-when-you-stopped-smoking" + Then I am on "/types-tobacco-smoking" + When I check "Cigarettes" + And I check "Pipe" + And I submit the form + Then I am on "/check-your-answers" And I see "Yes, I used to smoke" as a response to "Have you ever smoked tobacco?" under "Eligibility" And I see a date 55 years ago as a response to "Date of birth" under "Eligibility" diff --git a/features/steps/debug_steps.py b/features/steps/debug_steps.py new file mode 100644 index 00000000..7e262936 --- /dev/null +++ b/features/steps/debug_steps.py @@ -0,0 +1,20 @@ +from behave import when +from datetime import datetime + +@given("I take a screenshot") +@given("I take a screenshot {value}") +@when("I take a screenshot") +@when("I take a screenshot {value}") +@then("I take a screenshot") +@then("I take a screenshot {value}") +def screenshot(context, value=""): + context.page.screenshot( + full_page=True, path=f"screenshots/{datetime.now()}-{value}-screenshot.png" + ) + +@given("I print eligibiity") +@when("I print eligibiity") +@then("I print eligibiity") +def print_eligibiity(context): + print("Is user eligible?: ", context.current_user.responseset_set.last().is_eligible()) + diff --git a/features/steps/form_steps.py b/features/steps/form_steps.py index 2a8dec41..d6b9ed81 100644 --- a/features/steps/form_steps.py +++ b/features/steps/form_steps.py @@ -14,11 +14,6 @@ def when_i_enter_value_and_submit(context, field, value): context.page.get_by_label(field).fill(value) when_i_submit_the_form(context) -@when("I take a screenshot") -@when("I take a screenshot {value}") -def screenshot(context, value=""): - context.page.screenshot(full_page=True, path=f"screenshots/{datetime.now()}-{value}-screenshot.png") - @when('I fill in and submit my smoking status with "{smoking_status}"') def when_i_fill_in_and_submit_my_smoking_status(context, smoking_status): context.page.get_by_label(smoking_status).check() diff --git a/features/types_tobacco_smoking.feature b/features/types_tobacco_smoking.feature new file mode 100644 index 00000000..c43cd88c --- /dev/null +++ b/features/types_tobacco_smoking.feature @@ -0,0 +1,45 @@ +@TypesTobaccoSmoking +Feature: Types tobacco smoking page + Scenario: The page is accessible + Given I am logged in + And I have answered questions showing I am eligible + When I go to "/types-tobacco-smoking" + Then there are no accessibility violations + + Scenario: Form errors + Given I am logged in + And I have answered questions showing I am eligible + When I go to "/types-tobacco-smoking" + And I click "Continue" + Then I am on "/types-tobacco-smoking" + And I see a form error "Select the type of tobacco you smoke or have smoked" + And there are no accessibility violations + + Scenario: Navigating backwards and forwards + Given I am logged in + And I have answered questions showing I am eligible + When I go to "/types-tobacco-smoking" + Then I see a back link to "/periods-when-you-stopped-smoking" + When I check "Cigarettes" + And I submit the form + Then I am on "/check-your-answers" + + Scenario: Checking responses and changing them + Given I am logged in + And I have answered questions showing I am eligible + When I go to "/types-tobacco-smoking" + And I check "Cigarettes" + And I check "Cigars" + And I submit the form + When I go to "/check-your-answers" + Then I take a screenshot + Then I see "Cigarettes and Cigars" as a response to "Types of tobacco smoked" under "Smoking history" + And I see "/types-tobacco-smoking?change=True" as a link to change "Types of tobacco smoked" under "Smoking history" + When I click the link to change "Types of tobacco smoked" under "Smoking history" + Then I am on "/types-tobacco-smoking?change=True" + And I see "Cigarettes" selected + And I see "Cigars" selected + When I check "Pipe" + And I click "Continue" + Then I am on "/check-your-answers" + And I see "Cigarettes, Pipe, and Cigars" as a response to "Types of tobacco smoked" under "Smoking history" diff --git a/lung_cancer_screening/questions/forms/types_tobacco_smoking_form.py b/lung_cancer_screening/questions/forms/types_tobacco_smoking_form.py new file mode 100644 index 00000000..d9c5efd0 --- /dev/null +++ b/lung_cancer_screening/questions/forms/types_tobacco_smoking_form.py @@ -0,0 +1,59 @@ +from django import forms + +from ...nhsuk_forms.choice_field import MultipleChoiceField +from ..models.tobacco_smoking_history import TobaccoSmokingHistoryTypes, TobaccoSmokingHistory + + + +class TypesTobaccoSmokingForm(forms.Form): + + value = MultipleChoiceField( + choices=TobaccoSmokingHistoryTypes.choices, + widget=forms.CheckboxSelectMultiple, + label="What do you or have you smoked?", + label_classes="nhsuk-fieldset__legend--m", + hint="Select all that apply", + error_messages={ + "required": "Select the type of tobacco you smoke or have smoked" + }, + ) + + def __init__(self, *args, response_set, **kwargs): + super().__init__(*args, **kwargs) + self.response_set = response_set + self.existing_types = list( + response_set.tobacco_smoking_history.values_list('type', flat=True) + ) + self.fields["value"].initial = self.existing_types + + value_field=self['value'] + value_field.add_hint_for_choice( + TobaccoSmokingHistoryTypes.CIGARILLOS, + "Cafe Creme or Signature cigars, roughly the size of a cigarette" + ) + + + def save(self, commit=True): + if not self.is_valid(): + return None + + self._delete_types_not_selected() + return self._create_types_selected() + + + def _delete_types_not_selected(self): + for kind in self.existing_types: + if kind not in self.cleaned_data["value"]: + TobaccoSmokingHistory.objects.filter(response_set=self.response_set, type=kind).delete() + + + def _create_types_selected(self): + instances = [ + TobaccoSmokingHistory(response_set=self.response_set, type=kind) + for kind in self.cleaned_data["value"] + if kind not in self.existing_types + ] + + TobaccoSmokingHistory.objects.bulk_create(instances) + + return instances diff --git a/lung_cancer_screening/questions/jinja2/responses.jinja b/lung_cancer_screening/questions/jinja2/responses.jinja index e2ec309a..64cf4437 100644 --- a/lung_cancer_screening/questions/jinja2/responses.jinja +++ b/lung_cancer_screening/questions/jinja2/responses.jinja @@ -40,7 +40,7 @@
-

Smoking history

+

Smoking history

{{ summaryList({ "rows": response_set.smoking_history_responses_items() diff --git a/lung_cancer_screening/questions/jinja2/types_tobacco_smoking.jinja b/lung_cancer_screening/questions/jinja2/types_tobacco_smoking.jinja new file mode 100644 index 00000000..285ad096 --- /dev/null +++ b/lung_cancer_screening/questions/jinja2/types_tobacco_smoking.jinja @@ -0,0 +1,11 @@ +{% extends 'question_form.jinja' %} + +{% block prelude %} +

The type of tobacco you smoke or used to smoke

+ +

Smoking affects your health in different ways depending on what you smoke.

+ +

We need to know what you smoke, or used to smoke, on a regular basis. For example, if you have smoked a pipe on a weekly basis for a year or longer.

+ +

You do not need to tell us about less frequent forms of smoking. For example, a cigar on special occasions.

+{% endblock prelude %} diff --git a/lung_cancer_screening/questions/migrations/0046_alter_ethnicityresponse_value_and_more.py b/lung_cancer_screening/questions/migrations/0046_alter_ethnicityresponse_value_and_more.py new file mode 100644 index 00000000..0ed41559 --- /dev/null +++ b/lung_cancer_screening/questions/migrations/0046_alter_ethnicityresponse_value_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.10 on 2026-01-21 16:57 + +import django.contrib.postgres.fields +import django.db.models.deletion +import lung_cancer_screening.questions.models.validators.singleton_option +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("questions", "0045_periodswhenyoustoppedsmokingresponse_duration_years"), + ] + + operations = [ + migrations.CreateModel( + name='TypesTobaccoSmokingResponse', + 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', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('C', 'Cigarettes'), ('G', 'Cigars'), ('P', 'Pipes'), ('E', 'E-cigarettes or vaping'), ('N', 'None of the above')], max_length=1), size=None, validators=[lung_cancer_screening.questions.models.validators.singleton_option.validate_singleton_option])), + ('response_set', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='types_tobacco_smoking_response', to='questions.responseset')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/lung_cancer_screening/questions/migrations/0047_alter_agewhenstartedsmokingresponse_value_and_more.py b/lung_cancer_screening/questions/migrations/0047_alter_agewhenstartedsmokingresponse_value_and_more.py new file mode 100644 index 00000000..92bb6aaf --- /dev/null +++ b/lung_cancer_screening/questions/migrations/0047_alter_agewhenstartedsmokingresponse_value_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 5.2.10 on 2026-01-27 14:59 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('questions', '0046_alter_ethnicityresponse_value_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='agewhenstartedsmokingresponse', + name='value', + field=models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1, message='The age you started smoking must be between 1 and your current age')]), + ), + migrations.AlterField( + model_name='ethnicityresponse', + name='value', + field=models.CharField(choices=[('A', 'Asian or Asian British'), ('B', 'Black, African, Caribbean or Black British'), ('M', 'Mixed or multiple ethnic groups'), ('W', 'White'), ('O', 'Other ethnic group'), ('N', 'Prefer not to say')], max_length=1), + ), + migrations.AlterField( + model_name='sexatbirthresponse', + name='value', + field=models.CharField(choices=[('F', 'Female'), ('M', 'Male'), ('I', 'Intersex')], max_length=1), + ), + migrations.AlterField( + model_name='typestobaccosmokingresponse', + name='value', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('C', 'Cigarettes'), ('R', 'Rolled cigarettes, or roll-ups'), ('P', 'Pipe'), ('G', 'Cigars'), ('E', 'Cigarillos'), ('S', 'Shisha')], max_length=1), size=None), + ), + ] diff --git a/lung_cancer_screening/questions/migrations/0048_tobaccosmokinghistory_and_more.py b/lung_cancer_screening/questions/migrations/0048_tobaccosmokinghistory_and_more.py new file mode 100644 index 00000000..e48661f7 --- /dev/null +++ b/lung_cancer_screening/questions/migrations/0048_tobaccosmokinghistory_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.10 on 2026-01-28 10:46 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('questions', '0047_alter_agewhenstartedsmokingresponse_value_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='TobaccoSmokingHistory', + 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)), + ('type', models.CharField(choices=[('Cigarettes', 'Cigarettes'), ('RolledCigarettes', 'Rolled cigarettes, or roll-ups'), ('Pipe', 'Pipe'), ('Cigars', 'Cigars'), ('Cigarillos', 'Cigarillos'), ('Shisha', 'Shisha')])), + ('response_set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='types_tobacco_smoking', to='questions.responseset')), + ], + options={ + 'abstract': False, + }, + ), + migrations.DeleteModel( + name='TypesTobaccoSmokingResponse', + ), + ] diff --git a/lung_cancer_screening/questions/models/__init__.py b/lung_cancer_screening/questions/models/__init__.py index add601db..6c3ef225 100644 --- a/lung_cancer_screening/questions/models/__init__.py +++ b/lung_cancer_screening/questions/models/__init__.py @@ -16,4 +16,5 @@ from .relatives_age_when_diagnosed_response import RelativesAgeWhenDiagnosedResponse # noqa: F401 from .respiratory_conditions_response import RespiratoryConditionsResponse # noqa: F401 from .sex_at_birth_response import SexAtBirthResponse # 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/tobacco_smoking_history.py b/lung_cancer_screening/questions/models/tobacco_smoking_history.py new file mode 100644 index 00000000..ead8cd4b --- /dev/null +++ b/lung_cancer_screening/questions/models/tobacco_smoking_history.py @@ -0,0 +1,33 @@ +from django.db import models + +from .base import BaseModel +from .response_set import ResponseSet + + +class TobaccoSmokingHistoryTypes(models.TextChoices): + CIGARETTES = "Cigarettes", "Cigarettes" + ROLLED_CIGARETTES = "RolledCigarettes", "Rolled cigarettes, or roll-ups" + PIPE = "Pipe", "Pipe" + CIGARS = "Cigars", "Cigars" + CIGARILLOS = "Cigarillos", "Cigarillos" + SHISHA = "Shisha", "Shisha" + + +class TobaccoSmokingHistory(BaseModel): + response_set = models.ForeignKey( + ResponseSet, + on_delete=models.CASCADE, + related_name="tobacco_smoking_history", + ) + type = models.CharField( + choices=TobaccoSmokingHistoryTypes.choices + ) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["response_set", "type"], + name="unique_tobacco_smoking_history_per_response_set", + violation_error_message="A tobacco smoking history already exists for this response set and type" + ) + ] diff --git a/lung_cancer_screening/questions/presenters/response_set_presenter.py b/lung_cancer_screening/questions/presenters/response_set_presenter.py index d4454e2a..232ef9b8 100644 --- a/lung_cancer_screening/questions/presenters/response_set_presenter.py +++ b/lung_cancer_screening/questions/presenters/response_set_presenter.py @@ -4,6 +4,7 @@ from ..models.education_response import EducationValues from ..models.respiratory_conditions_response import RespiratoryConditionValues from ..models.family_history_lung_cancer_response import FamilyHistoryLungCancerValues +from ..models.tobacco_smoking_history import TobaccoSmokingHistoryTypes class ResponseSetPresenter: DATE_FORMAT = "%-d %B %Y" # eg 8 September 2000 @@ -147,8 +148,20 @@ def respiratory_conditions(self): ]) + @property + def types_tobacco_smoking(self): + types_smoked = self.response_set.tobacco_smoking_history.values_list('type', flat=True) + + types_smoked_ordered = sorted(types_smoked, key=lambda x: TobaccoSmokingHistoryTypes.values.index(x)) + + return self._list_to_sentence([ + TobaccoSmokingHistoryTypes(code).label + for code in types_smoked_ordered + ]) + + def eligibility_responses_items(self): - return [ + items = [ self._check_your_answer_item( "Have you ever smoked tobacco?", self.have_you_ever_smoked, @@ -158,9 +171,10 @@ def eligibility_responses_items(self): "Date of birth", self.date_of_birth, "questions:date_of_birth", - ) + ), ] + return items def about_you_responses_items(self): return [ @@ -247,6 +261,11 @@ def smoking_history_responses_items(self): self.periods_when_you_stopped_smoking, "questions:periods_when_you_stopped_smoking", ), + self._check_your_answer_item( + "Types of tobacco smoked", + self.types_tobacco_smoking, + "questions:types_tobacco_smoking", + ) ] 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 4d536d52..80cebbc3 100644 --- a/lung_cancer_screening/questions/tests/factories/response_set_factory.py +++ b/lung_cancer_screening/questions/tests/factories/response_set_factory.py @@ -44,6 +44,7 @@ class Params: ) complete = factory.Trait( + eligible=True, asbestos_exposure_response=factory.RelatedFactory( "lung_cancer_screening.questions.tests.factories.asbestos_exposure_response_factory.AsbestosExposureResponseFactory", factory_related_name="response_set" @@ -52,14 +53,6 @@ class Params: "lung_cancer_screening.questions.tests.factories.cancer_diagnosis_response_factory.CancerDiagnosisResponseFactory", factory_related_name="response_set" ), - check_need_appointment_response=factory.RelatedFactory( - "lung_cancer_screening.questions.tests.factories.check_need_appointment_response_factory.CheckNeedAppointmentResponseFactory", - factory_related_name="response_set" - ), - date_of_birth_response=factory.RelatedFactory( - "lung_cancer_screening.questions.tests.factories.date_of_birth_response_factory.DateOfBirthResponseFactory", - factory_related_name="response_set" - ), education_response=factory.RelatedFactory( "lung_cancer_screening.questions.tests.factories.education_response_factory.EducationResponseFactory", factory_related_name="response_set" @@ -76,10 +69,6 @@ class Params: "lung_cancer_screening.questions.tests.factories.gender_response_factory.GenderResponseFactory", factory_related_name="response_set" ), - 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" - ), height_response=factory.RelatedFactory( "lung_cancer_screening.questions.tests.factories.height_response_factory.HeightResponseFactory", factory_related_name="response_set" diff --git a/lung_cancer_screening/questions/tests/factories/tobacco_smoking_history_factory.py b/lung_cancer_screening/questions/tests/factories/tobacco_smoking_history_factory.py new file mode 100644 index 00000000..6d29e940 --- /dev/null +++ b/lung_cancer_screening/questions/tests/factories/tobacco_smoking_history_factory.py @@ -0,0 +1,12 @@ +import factory + +from .response_set_factory import ResponseSetFactory +from ...models.tobacco_smoking_history import TobaccoSmokingHistory, TobaccoSmokingHistoryTypes + + +class TobaccoSmokingHistoryFactory(factory.django.DjangoModelFactory): + class Meta: + model = TobaccoSmokingHistory + + response_set = factory.SubFactory(ResponseSetFactory) + type = TobaccoSmokingHistoryTypes.CIGARETTES diff --git a/lung_cancer_screening/questions/tests/unit/forms/test_types_tobacco_smoking_form.py b/lung_cancer_screening/questions/tests/unit/forms/test_types_tobacco_smoking_form.py new file mode 100644 index 00000000..55f406fb --- /dev/null +++ b/lung_cancer_screening/questions/tests/unit/forms/test_types_tobacco_smoking_form.py @@ -0,0 +1,99 @@ +from django.test import TestCase, tag + +from ...factories.response_set_factory import ResponseSetFactory +from ...factories.tobacco_smoking_history_factory import TobaccoSmokingHistoryFactory +from ....models.tobacco_smoking_history import ( + TobaccoSmokingHistoryTypes, +) +from ....forms.types_tobacco_smoking_form import TypesTobaccoSmokingForm + + +@tag("TypesTobaccoSmoking") +class TestTypesTobaccoSmokingForm(TestCase): + def setUp(self): + self.response_set = ResponseSetFactory() + + + def test_is_valid_a_valid_value(self): + form = TypesTobaccoSmokingForm( + response_set=self.response_set, + data={ + "value": [TobaccoSmokingHistoryTypes.CIGARETTES] + } + ) + self.assertTrue(form.is_valid()) + + + def test_is_invalid_with_an_invalid_value(self): + form = TypesTobaccoSmokingForm( + response_set=self.response_set, + data={ + "value": ["invalid"] + } + ) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors["value"], + ["Select a valid choice. invalid is not one of the available choices."] + ) + + + def test_is_invalid_when_no_option_is_selected(self): + form = TypesTobaccoSmokingForm( + response_set=self.response_set, + data={ + "value": [] + } + ) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors["value"], + ["Select the type of tobacco you smoke or have smoked"], + ) + + def test_saves_an_tobacco_smoking_type_for_each_value_selected(self): + form = TypesTobaccoSmokingForm( + response_set=self.response_set, + data={ + "value": [ + TobaccoSmokingHistoryTypes.CIGARETTES, + TobaccoSmokingHistoryTypes.PIPE + ] + } + ) + + form.save() + self.assertEqual(self.response_set.tobacco_smoking_history.count(), 2) + self.assertEqual(self.response_set.tobacco_smoking_history.first().type, TobaccoSmokingHistoryTypes.CIGARETTES) + self.assertEqual(self.response_set.tobacco_smoking_history.last().type, TobaccoSmokingHistoryTypes.PIPE) + + + def test_does_not_create_a_new_tobacco_smoking_type_if_it_already_exists(self): + TobaccoSmokingHistoryFactory(response_set=self.response_set, type=TobaccoSmokingHistoryTypes.CIGARETTES) + + form = TypesTobaccoSmokingForm( + response_set=self.response_set, + data={ + "value": [TobaccoSmokingHistoryTypes.CIGARETTES] + } + ) + form.save() + self.assertEqual(self.response_set.tobacco_smoking_history.count(), 1) + self.assertEqual(self.response_set.tobacco_smoking_history.first().type, TobaccoSmokingHistoryTypes.CIGARETTES) + + + def test_deletes_a_tobacco_smoking_type_if_it_is_no_longer_selected(self): + TobaccoSmokingHistoryFactory(response_set=self.response_set, type=TobaccoSmokingHistoryTypes.CIGARETTES) + + form = TypesTobaccoSmokingForm( + response_set=self.response_set, + data={ + "value": [TobaccoSmokingHistoryTypes.PIPE] + } + ) + form.save() + self.assertEqual(self.response_set.tobacco_smoking_history.count(), 1) + self.assertNotIn( + TobaccoSmokingHistoryTypes.CIGARETTES, + self.response_set.tobacco_smoking_history.values_list('type', flat=True) + ) diff --git a/lung_cancer_screening/questions/tests/unit/models/test_tobacco_smoking_history.py b/lung_cancer_screening/questions/tests/unit/models/test_tobacco_smoking_history.py new file mode 100644 index 00000000..7958e631 --- /dev/null +++ b/lung_cancer_screening/questions/tests/unit/models/test_tobacco_smoking_history.py @@ -0,0 +1,42 @@ +from django.test import TestCase, tag +from django.core.exceptions import ValidationError + +from ...factories.response_set_factory import ResponseSetFactory +from ...factories.tobacco_smoking_history_factory import TobaccoSmokingHistoryFactory + +from ....models.tobacco_smoking_history import TobaccoSmokingHistoryTypes + + +@tag("TypesTobaccoSmoking") +class TestTobaccoSmokingHistory(TestCase): + def setUp(self): + self.response_set = ResponseSetFactory() + + + def test_has_a_valid_factory(self): + model = TobaccoSmokingHistoryFactory.build(response_set=self.response_set) + model.full_clean() + + + def test_has_response_set_as_foreign_key(self): + response = TobaccoSmokingHistoryFactory.build( + response_set=self.response_set + ) + + self.assertEqual(response.response_set, self.response_set) + + + def test_has_type_as_a_string(self): + response = TobaccoSmokingHistoryFactory.build( + response_set=self.response_set, + type=TobaccoSmokingHistoryTypes.CIGARETTES + ) + + self.assertIsInstance(response.type, str) + + + def test_is_invalid_with_a_duplicate_response_set_and_type(self): + TobaccoSmokingHistoryFactory(response_set=self.response_set, type=TobaccoSmokingHistoryTypes.CIGARETTES) + + with self.assertRaises(ValidationError): + TobaccoSmokingHistoryFactory(response_set=self.response_set, type=TobaccoSmokingHistoryTypes.CIGARETTES) diff --git a/lung_cancer_screening/questions/tests/unit/presenters/test_response_set_presenter.py b/lung_cancer_screening/questions/tests/unit/presenters/test_response_set_presenter.py index e7a1afa4..42ad48b5 100644 --- a/lung_cancer_screening/questions/tests/unit/presenters/test_response_set_presenter.py +++ b/lung_cancer_screening/questions/tests/unit/presenters/test_response_set_presenter.py @@ -1,4 +1,4 @@ -from django.test import TestCase +from django.test import TestCase, tag from ...factories.response_set_factory import ResponseSetFactory from ...factories.have_you_ever_smoked_response_factory import HaveYouEverSmokedResponseFactory @@ -16,6 +16,7 @@ from ...factories.relatives_age_when_diagnosed_response_factory import RelativesAgeWhenDiagnosedResponseFactory from ...factories.periods_when_you_stopped_smoking_response_factory import PeriodsWhenYouStoppedSmokingResponseFactory from ...factories.age_when_started_smoking_response_factory import AgeWhenStartedSmokingResponseFactory +from ...factories.tobacco_smoking_history_factory import TobaccoSmokingHistoryFactory from ....models.have_you_ever_smoked_response import HaveYouEverSmokedValues from ....models.sex_at_birth_response import SexAtBirthValues @@ -24,7 +25,7 @@ from ....models.education_response import EducationValues from ....models.family_history_lung_cancer_response import FamilyHistoryLungCancerValues from ....models.relatives_age_when_diagnosed_response import RelativesAgeWhenDiagnosedValues - +from ....models.tobacco_smoking_history import TobaccoSmokingHistoryTypes from ....models.respiratory_conditions_response import RespiratoryConditionValues @@ -32,7 +33,7 @@ class TestResponseSetPresenter(TestCase): def setUp(self): - self.response_set = ResponseSetFactory() + self.response_set = ResponseSetFactory.create() def test_have_you_ever_smoked_with_no_value(self): @@ -259,3 +260,20 @@ def test_relative_age_when_diagnosed_with_value(self): ) presenter = ResponseSetPresenter(self.response_set) self.assertEqual(presenter.relatives_age_when_diagnosed, RelativesAgeWhenDiagnosedValues.YES.label) + + @tag("TypesTobaccoSmoking") + def test_types_tobacco_smoking(self): + TobaccoSmokingHistoryFactory.create( + response_set=self.response_set, + type=TobaccoSmokingHistoryTypes.SHISHA + ) + TobaccoSmokingHistoryFactory.create( + response_set=self.response_set, + type=TobaccoSmokingHistoryTypes.CIGARS + ) + TobaccoSmokingHistoryFactory.create( + response_set=self.response_set, + type=TobaccoSmokingHistoryTypes.CIGARETTES + ) + presenter = ResponseSetPresenter(self.response_set) + self.assertEqual(presenter.types_tobacco_smoking, "Cigarettes, Cigars, and Shisha") diff --git a/lung_cancer_screening/questions/tests/unit/views/test_confirmation.py b/lung_cancer_screening/questions/tests/unit/views/test_confirmation.py index 09852c71..fca10acb 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_confirmation.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_confirmation.py @@ -32,7 +32,7 @@ def test_get_redirects_when_no_submitted_response_set_exists( reverse("questions:confirmation") ) - self.assertRedirects(response, reverse("questions:responses")) + self.assertRedirects(response, reverse("questions:responses"), fetch_redirect_response=False) def test_get_responds_successfully_when_a_submitted_response_set_exists(self): ResponseSetFactory.create(user=self.user, recently_submitted=True) 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 0e21b911..50140315 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 @@ -173,7 +173,7 @@ def test_post_redirects_to_responses_if_change_query_param_is_true(self): } ) - self.assertRedirects(response, reverse("questions:responses")) + self.assertRedirects(response, reverse("questions:responses"), fetch_redirect_response=False) def test_post_responds_with_422_if_the_resource_is_invalid(self): response = self.client.post( 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 06fef0fb..bcc2a31d 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 @@ -139,7 +139,7 @@ def test_post_redirects_to_responses_if_change_query_param_is_true(self): } ) - self.assertRedirects(response, reverse("questions:responses")) + self.assertRedirects(response, reverse("questions:responses"), fetch_redirect_response=False) 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/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 d3f77b61..694a34c1 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 @@ -118,7 +118,7 @@ def test_redirects_to_responses(self): self.valid_params ) - self.assertRedirects(response, reverse("questions:responses")) + self.assertRedirects(response, reverse("questions:types_tobacco_smoking")) def test_redirects_to_responses_if_change_query_param_is_true(self): 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 20ee79c2..910f0d27 100644 --- a/lung_cancer_screening/questions/tests/unit/views/test_responses.py +++ b/lung_cancer_screening/questions/tests/unit/views/test_responses.py @@ -40,34 +40,46 @@ def test_get_redirects_when_submitted_response_set_exists_within_last_year( response, reverse("questions:confirmation") ) + + def test_redirects_when_the_user_is_not_eligible(self): + ResponseSetFactory.create(user=self.user, eligible=False) + + response = self.client.get( + reverse("questions:responses") + ) + + self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + + def test_get_responds_successfully(self): + ResponseSetFactory.create(user=self.user, eligible=True) + response = self.client.get(reverse("questions:responses")) self.assertEqual(response.status_code, 200) + def test_get_contains_the_users_responses(self): - response_set = ResponseSetFactory.create(user=self.user) - DateOfBirthResponseFactory.create( - response_set=response_set, - value=date(2000, 9, 8) - ) + response_set = ResponseSetFactory.create(user=self.user, eligible=True) response = self.client.get( reverse("questions:responses") ) - self.assertContains(response, "8 September 2000") + self.assertContains(response, response_set.date_of_birth_response.value.strftime("%-d %B %Y")) + def test_get_does_not_contain_responses_for_other_users(self): - response_set = ResponseSetFactory.create(user=self.user) - DateOfBirthResponseFactory.create(response_set=response_set, value=date(2000, 9, 8)) + ResponseSetFactory.create(user=self.user, eligible=True) other_response_set = ResponseSetFactory.create() DateOfBirthResponseFactory.create(response_set=other_response_set, value=date(1990, 1, 1)) response = self.client.get(reverse("questions:responses")) - self.assertNotContains(response, "1 January 1990") + self.assertNotContains( + response, other_response_set.date_of_birth_response.value.strftime("%-d %B %Y") + ) class TestPostResponses(TestCase): @@ -103,8 +115,16 @@ def test_post_redirects_when_submitted_response_set_exists_within_last_year( self.assertRedirects(response, reverse("questions:confirmation")) + def test_redirects_when_the_user_is_not_eligible(self): + ResponseSetFactory.create(user=self.user, eligible=False) + + response = self.client.post(reverse("questions:responses")) + + self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + + def test_post_responds_with_422_if_the_response_set_is_not_complete(self): - ResponseSetFactory.create(user=self.user) + ResponseSetFactory.create(user=self.user, eligible=True) response = self.client.post(reverse("questions:responses")) 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 new file mode 100644 index 00000000..65189637 --- /dev/null +++ b/lung_cancer_screening/questions/tests/unit/views/test_types_tobacco_smoking.py @@ -0,0 +1,141 @@ +from django.test import TestCase, tag +from django.urls import reverse + +from .helpers.authentication import login_user +from ...factories.response_set_factory import ResponseSetFactory +from ....models.tobacco_smoking_history import TobaccoSmokingHistoryTypes + + +@tag("TypesTobaccoSmoking") +class TestGetTypesTobaccoSmoking(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() + + response = self.client.get( + reverse("questions:types_tobacco_smoking") + ) + + self.assertRedirects( + response, + "/oidc/authenticate/?next=/types-tobacco-smoking", + fetch_redirect_response=False + ) + + + def test_get_redirects_when_submitted_response_set_exists_within_last_year( + self + ): + ResponseSetFactory.create( + user=self.user, + recently_submitted=True + ) + + response = self.client.get( + reverse("questions:types_tobacco_smoking") + ) + + self.assertRedirects(response, reverse("questions:confirmation")) + + + def test_redirects_when_the_user_is_not_eligible(self): + ResponseSetFactory.create(user=self.user) + + response = self.client.get(reverse("questions:types_tobacco_smoking")) + + self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + + + def test_get_responds_successfully(self): + ResponseSetFactory.create(user=self.user, eligible=True) + + response = self.client.get( + reverse("questions:types_tobacco_smoking") + ) + + self.assertEqual(response.status_code, 200) + + +@tag("TypesTobaccoSmoking") +class TestPostTypesTobaccoSmoking(TestCase): + def setUp(self): + self.user = login_user(self.client) + + self.valid_params = {"value": [TobaccoSmokingHistoryTypes.CIGARETTES.value]} + + def test_post_redirects_if_the_user_is_not_logged_in(self): + self.client.logout() + + response = self.client.post( + reverse("questions:types_tobacco_smoking"), + self.valid_params + ) + + self.assertRedirects( + response, + "/oidc/authenticate/?next=/types-tobacco-smoking", + fetch_redirect_response=False + ) + + def test_redirects_when_a_submitted_response_set_exists_within_the_last_year(self): + ResponseSetFactory.create( + user=self.user, + recently_submitted=True + ) + + response = self.client.post( + reverse("questions:types_tobacco_smoking"), + self.valid_params + ) + + self.assertRedirects(response, reverse("questions:confirmation")) + + def test_redirects_when_the_user_is_not_eligible(self): + ResponseSetFactory.create(user=self.user) + + response = self.client.post( + reverse("questions:types_tobacco_smoking"), + self.valid_params + ) + + self.assertRedirects(response, reverse("questions:have_you_ever_smoked")) + + + def creates_a_tobacco_smoking_type_parent_model_for_each_type_given(self): + response_set = ResponseSetFactory.create(user=self.user, eligible=True) + + self.client.post( + reverse("questions:types_tobacco_smoking"), + self.valid_params + ) + + response_set.refresh_from_db() + self.assertEqual(response_set.tobacco_smoking_history.count(), 1) + self.assertEqual(response_set.tobacco_smoking_history.first().type, TobaccoSmokingHistoryTypes.CIGARETTES.value) + + + def test_post_redirects_to_responses(self): + ResponseSetFactory.create(user=self.user, eligible=True) + response = self.client.post( + reverse("questions:types_tobacco_smoking"), + self.valid_params + ) + + self.assertRedirects(response, reverse("questions:responses")) + + + def test_post_redirects_to_responses_if_change_query_param_is_true(self): + ResponseSetFactory.create(user=self.user, eligible=True) + + response = self.client.post( + reverse("questions:types_tobacco_smoking"), + { + **self.valid_params, + "change": "True" + } + ) + + self.assertRedirects(response, reverse("questions:responses")) diff --git a/lung_cancer_screening/questions/urls.py b/lung_cancer_screening/questions/urls.py index 6a6167d7..0952c85e 100644 --- a/lung_cancer_screening/questions/urls.py +++ b/lung_cancer_screening/questions/urls.py @@ -36,6 +36,7 @@ from .views.respiratory_conditions import RespiratoryConditionsView from .views.responses import ResponsesView from .views.sex_at_birth import SexAtBirthView +from .views.types_tobacco_smoking import TypesTobaccoSmokingView from .views.start import StartView from .views.weight import WeightView from .views.confirmation import ConfirmationView @@ -59,6 +60,7 @@ path('relatives-age-when-diagnosed', RelativesAgeWhenDiagnosedView.as_view(), name='relatives_age_when_diagnosed'), path('respiratory-conditions', RespiratoryConditionsView.as_view(), name='respiratory_conditions'), path('periods-when-you-stopped-smoking', PeriodsWhenYouStoppedSmokingView.as_view(), name='periods_when_you_stopped_smoking'), + path('types-tobacco-smoking', TypesTobaccoSmokingView.as_view(), name='types_tobacco_smoking'), 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'), diff --git a/lung_cancer_screening/questions/views/periods_when_you_stopped_smoking.py b/lung_cancer_screening/questions/views/periods_when_you_stopped_smoking.py index fd4d89ac..d770ec78 100644 --- a/lung_cancer_screening/questions/views/periods_when_you_stopped_smoking.py +++ b/lung_cancer_screening/questions/views/periods_when_you_stopped_smoking.py @@ -12,5 +12,5 @@ class PeriodsWhenYouStoppedSmokingView(LoginRequiredMixin, EnsureResponseSet, En template_name = "periods_when_you_stopped_smoking.jinja" form_class = PeriodsWhenYouStoppedSmokingForm model = PeriodsWhenYouStoppedSmokingResponse - success_url = reverse_lazy("questions:responses") + success_url = reverse_lazy("questions:types_tobacco_smoking") back_link_url = reverse_lazy("questions:age_when_started_smoking") diff --git a/lung_cancer_screening/questions/views/responses.py b/lung_cancer_screening/questions/views/responses.py index 2716c81a..e1a2cb6b 100644 --- a/lung_cancer_screening/questions/views/responses.py +++ b/lung_cancer_screening/questions/views/responses.py @@ -6,10 +6,11 @@ from django.core.exceptions import ValidationError from .mixins.ensure_response_set import EnsureResponseSet +from .mixins.ensure_eligible import EnsureEligibleMixin from ..presenters.response_set_presenter import ResponseSetPresenter -class ResponsesView(LoginRequiredMixin, EnsureResponseSet, View): +class ResponsesView(LoginRequiredMixin, EnsureResponseSet, EnsureEligibleMixin, View): def get(self, request): return render_template(request, request.response_set) @@ -31,7 +32,7 @@ def render_template(request, response_set, status=200): "responses.jinja", { "response_set": ResponseSetPresenter(request.response_set), - "back_link_url": reverse("questions:periods_when_you_stopped_smoking") + "back_link_url": reverse("questions:types_tobacco_smoking") }, status=status, ) diff --git a/lung_cancer_screening/questions/views/types_tobacco_smoking.py b/lung_cancer_screening/questions/views/types_tobacco_smoking.py new file mode 100644 index 00000000..0af51794 --- /dev/null +++ b/lung_cancer_screening/questions/views/types_tobacco_smoking.py @@ -0,0 +1,31 @@ +from django.urls import reverse_lazy +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views.generic.edit import FormView + +from .mixins.ensure_response_set import EnsureResponseSet +from .mixins.ensure_eligible import EnsureEligibleMixin +from ..forms.types_tobacco_smoking_form import TypesTobaccoSmokingForm + + +class TypesTobaccoSmokingView( + LoginRequiredMixin, EnsureResponseSet, EnsureEligibleMixin, FormView +): + template_name = "types_tobacco_smoking.jinja" + form_class = TypesTobaccoSmokingForm + success_url = reverse_lazy("questions:responses") + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['response_set'] = self.request.response_set + return kwargs + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["back_link_url"] = reverse_lazy("questions:periods_when_you_stopped_smoking") + return context + + def form_valid(self, form): + if form.is_valid(): + form.save() + + return super(TypesTobaccoSmokingView, self).form_valid(form) diff --git a/scripts/tests/ui.sh b/scripts/tests/ui.sh index af8eba30..d3a5bda2 100755 --- a/scripts/tests/ui.sh +++ b/scripts/tests/ui.sh @@ -9,4 +9,5 @@ fi env UID="$(id -u)" docker compose run --rm web \ poetry run python manage.py behave $TAG \ --settings=lung_cancer_screening.settings_test \ - --no-skipped + --no-skipped \ + --no-capture