generated from NHSDigital/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 4
Add benign lump form #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add benign lump form #739
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
09dd3e4
Add create benign lumps form
malcolmbaig e068b7d
Add YearField to encapsulate year bound validation logic
malcolmbaig e969b73
Benign lumps: Make NO_PROCEDURES an exclusive choice
malcolmbaig 78580bb
Benign lumps: Make procedures legend aria-hidden
malcolmbaig fcd0d41
Benign lumps: Update validation error message wording
malcolmbaig 471a5f1
Benign lumps: Add system test for creating
malcolmbaig f53dc12
Benign lumps: Add tests for form object
malcolmbaig 156864e
Benign lumps: make YearField bounds callable
malcolmbaig 9a4ccbe
Benign lumps: rework success flash message warning
malcolmbaig 9372a87
Benign lumps: add visually hidden text to left/right breast fields
malcolmbaig 43d26fa
Benign lumps: add more detailed validation message to location details
malcolmbaig d5219d0
Benign lumps: update system spec
malcolmbaig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
145 changes: 145 additions & 0 deletions
145
manage_breast_screening/mammograms/forms/benign_lump_history_item_form.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| from django.forms import Textarea | ||
|
|
||
| from manage_breast_screening.core.services.auditor import Auditor | ||
| from manage_breast_screening.nhsuk_forms.fields.char_field import CharField | ||
| from manage_breast_screening.nhsuk_forms.fields.choice_fields import ( | ||
| ChoiceField, | ||
| MultipleChoiceField, | ||
| ) | ||
| from manage_breast_screening.nhsuk_forms.fields.integer_field import YearField | ||
| from manage_breast_screening.nhsuk_forms.forms import FormWithConditionalFields | ||
| from manage_breast_screening.participants.models.benign_lump_history_item import ( | ||
| BenignLumpHistoryItem, | ||
| ) | ||
|
|
||
|
|
||
| class BenignLumpHistoryItemForm(FormWithConditionalFields): | ||
| LOCATION_DETAIL_FIELDS = { | ||
| BenignLumpHistoryItem.ProcedureLocation.NHS_HOSPITAL: "nhs_hospital_details", | ||
| BenignLumpHistoryItem.ProcedureLocation.PRIVATE_CLINIC_UK: "private_clinic_uk_details", | ||
| BenignLumpHistoryItem.ProcedureLocation.OUTSIDE_UK: "outside_uk_details", | ||
| BenignLumpHistoryItem.ProcedureLocation.MULTIPLE_LOCATIONS: "multiple_locations_details", | ||
| BenignLumpHistoryItem.ProcedureLocation.EXACT_LOCATION_UNKNOWN: "exact_location_unknown_details", | ||
| } | ||
|
|
||
| left_breast_procedures = MultipleChoiceField( | ||
| label="Left breast", | ||
| label_classes="nhsuk-fieldset__legend--s", | ||
| visually_hidden_label_prefix="What procedure have they had in their ", | ||
| visually_hidden_label_suffix="?", | ||
| choices=BenignLumpHistoryItem.Procedure, | ||
| exclusive_choices={"NO_PROCEDURES"}, | ||
| error_messages={ | ||
| "required": "Select which procedures they have had in the left breast", | ||
| }, | ||
| ) | ||
| right_breast_procedures = MultipleChoiceField( | ||
| label="Right breast", | ||
| label_classes="nhsuk-fieldset__legend--s", | ||
| visually_hidden_label_prefix="What procedure have they had in their ", | ||
| visually_hidden_label_suffix="?", | ||
| choices=BenignLumpHistoryItem.Procedure, | ||
| exclusive_choices={"NO_PROCEDURES"}, | ||
| error_messages={ | ||
| "required": "Select which procedures they have had in the right breast", | ||
| }, | ||
| ) | ||
|
|
||
| procedure_year = YearField( | ||
| label="Year of procedure (optional)", | ||
| label_classes="nhsuk-label--m", | ||
| classes="nhsuk-input--width-4", | ||
| hint="Leave blank if unknown", | ||
| required=False, | ||
| ) | ||
|
|
||
| procedure_location = ChoiceField( | ||
| label="Where were the tests and treatment done?", | ||
| choices=BenignLumpHistoryItem.ProcedureLocation, | ||
| error_messages={ | ||
| "required": "Select where the tests and treatment were done", | ||
| }, | ||
| ) | ||
| nhs_hospital_details = CharField( | ||
| label="Provide details", | ||
| required=False, | ||
| error_messages={ | ||
| "required": "Provide details about where the surgery and treatment took place" | ||
| }, | ||
| ) | ||
| private_clinic_uk_details = CharField( | ||
| label="Provide details", | ||
| required=False, | ||
| error_messages={ | ||
| "required": "Provide details about where the surgery and treatment took place" | ||
| }, | ||
| ) | ||
| outside_uk_details = CharField( | ||
| label="Provide details", | ||
| required=False, | ||
| error_messages={ | ||
| "required": "Provide details about where the surgery and treatment took place" | ||
| }, | ||
| ) | ||
| multiple_locations_details = CharField( | ||
| label="Provide details", | ||
| required=False, | ||
| error_messages={ | ||
| "required": "Provide details about where the surgery and treatment took place" | ||
| }, | ||
| ) | ||
| exact_location_unknown_details = CharField( | ||
| label="Provide details", | ||
| required=False, | ||
| error_messages={ | ||
| "required": "Provide details about where the surgery and treatment took place" | ||
| }, | ||
| ) | ||
|
|
||
| additional_details = CharField( | ||
| label="Additional details (optional)", | ||
| label_classes="nhsuk-label--m", | ||
| required=False, | ||
| widget=Textarea(attrs={"rows": 3}), | ||
| hint="Include any other relevant information about the procedure (optional)", | ||
| ) | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
|
|
||
| for location, detail_field in self.LOCATION_DETAIL_FIELDS.items(): | ||
| self.given_field_value("procedure_location", location).require_field( | ||
| detail_field | ||
| ) | ||
|
|
||
| def create(self, appointment, request): | ||
| auditor = Auditor.from_request(request) | ||
|
|
||
| benign_lump_history_item = BenignLumpHistoryItem.objects.create( | ||
| appointment=appointment, | ||
| left_breast_procedures=self.cleaned_data.get("left_breast_procedures", []), | ||
| right_breast_procedures=self.cleaned_data.get( | ||
| "right_breast_procedures", [] | ||
| ), | ||
| procedure_year=self.cleaned_data.get("procedure_year"), | ||
| procedure_location=self.cleaned_data["procedure_location"], | ||
| procedure_location_details=self._get_selected_location_details(), | ||
| additional_details=self.cleaned_data.get("additional_details", ""), | ||
| ) | ||
|
|
||
| auditor.audit_create(benign_lump_history_item) | ||
|
|
||
| return benign_lump_history_item | ||
|
|
||
| @property | ||
| def location_detail_fields(self): | ||
| return tuple(self.LOCATION_DETAIL_FIELDS.items()) | ||
|
|
||
| def _get_selected_location_details(self): | ||
| location = self.cleaned_data.get("procedure_location") | ||
| try: | ||
| detail_field = self.LOCATION_DETAIL_FIELDS[location] | ||
| except KeyError as exc: | ||
| msg = f"Unsupported procedure location '{location}'" | ||
| raise ValueError(msg) from exc | ||
| return self.cleaned_data.get(detail_field, "") | ||
42 changes: 42 additions & 0 deletions
42
.../mammograms/medical_information/medical_history/forms/benign_lump_history_item_form.jinja
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| {% extends "layout-form.jinja" %} | ||
| {% from "nhsuk/components/button/macro.jinja" import button %} | ||
| {% from "nhsuk/components/fieldset/macro.jinja" import fieldset %} | ||
|
|
||
| {% block form %} | ||
|
|
||
| {% do form.left_breast_procedures.add_divider_after("LUMP_REMOVED", "or") %} | ||
| {% do form.right_breast_procedures.add_divider_after("LUMP_REMOVED", "or") %} | ||
|
|
||
| <h2 aria-hidden="true"> | ||
| What benign lump procedures has {{ participant_first_name }} had? | ||
| </h2> | ||
|
|
||
| <div class="nhsuk-grid-row"> | ||
| <div class="nhsuk-grid-column-one-half"> | ||
| {{ form.right_breast_procedures.as_field_group() }} | ||
| </div> | ||
| <div class="nhsuk-grid-column-one-half"> | ||
| {{ form.left_breast_procedures.as_field_group() }} | ||
| </div> | ||
| </div> | ||
|
|
||
| {{ form.procedure_year.as_field_group() }} | ||
|
|
||
| {% for location_value, detail_field_name in form.location_detail_fields %} | ||
| {% set detail_field = form[detail_field_name] %} | ||
| {% do form.procedure_location.add_conditional_html( | ||
| location_value, | ||
| detail_field.as_field_group() | ||
| ) %} | ||
| {% endfor %} | ||
| {{ form.procedure_location.as_field_group() }} | ||
|
|
||
| {{ form.additional_details.as_field_group() }} | ||
|
|
||
| <div class="nhsuk-button-group"> | ||
| {{ button({ | ||
| "text": "Save" | ||
| }) }} | ||
| </div> | ||
|
|
||
| {% endblock %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
manage_breast_screening/mammograms/tests/forms/test_benign_lump_history_item_form.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| import datetime | ||
| from urllib.parse import urlencode | ||
|
|
||
| import pytest | ||
| from django.http import QueryDict | ||
| from django.test import RequestFactory | ||
|
|
||
| from manage_breast_screening.core.models import AuditLog | ||
| from manage_breast_screening.mammograms.forms.benign_lump_history_item_form import ( | ||
| BenignLumpHistoryItemForm, | ||
| ) | ||
| from manage_breast_screening.participants.models.benign_lump_history_item import ( | ||
| BenignLumpHistoryItem, | ||
| ) | ||
| from manage_breast_screening.participants.tests.factories import AppointmentFactory | ||
|
|
||
|
|
||
| def _form_data(data): | ||
| return QueryDict(urlencode(data, doseq=True)) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| class TestBenignLumpHistoryItemForm: | ||
| def test_missing_required_fields(self): | ||
| form = BenignLumpHistoryItemForm(_form_data({})) | ||
|
|
||
| assert not form.is_valid() | ||
| assert form.errors == { | ||
| "left_breast_procedures": [ | ||
| "Select which procedures they have had in the left breast" | ||
| ], | ||
| "right_breast_procedures": [ | ||
| "Select which procedures they have had in the right breast" | ||
| ], | ||
| "procedure_location": ["Select where the tests and treatment were done"], | ||
| } | ||
|
|
||
| @pytest.mark.parametrize( | ||
| ("location", "detail_field"), | ||
| tuple(BenignLumpHistoryItemForm.LOCATION_DETAIL_FIELDS.items()), | ||
| ) | ||
| def test_requires_location_details_for_selected_location( | ||
| self, location, detail_field | ||
| ): | ||
| form = BenignLumpHistoryItemForm( | ||
| _form_data( | ||
| [ | ||
| ( | ||
| "procedure_location", | ||
| location, | ||
| ), | ||
| ] | ||
| ) | ||
| ) | ||
|
|
||
| assert not form.is_valid() | ||
| assert form.errors.get(detail_field) == [ | ||
| "Provide details about where the surgery and treatment took place" | ||
| ] | ||
|
|
||
| @pytest.mark.parametrize( | ||
| ("procedure_year", "expected_message"), | ||
| [ | ||
| ( | ||
| datetime.date.today().year - 80 - 1, | ||
| f"Year must be {datetime.date.today().year - 80} or later", | ||
| ), | ||
| ( | ||
| datetime.date.today().year + 1, | ||
| f"Year must be {datetime.date.today().year} or earlier", | ||
| ), | ||
| ], | ||
| ) | ||
| def test_procedure_year_must_be_within_range( | ||
| self, procedure_year, expected_message | ||
| ): | ||
| form = BenignLumpHistoryItemForm( | ||
| _form_data( | ||
| [ | ||
| ("procedure_year", procedure_year), | ||
| ] | ||
| ) | ||
| ) | ||
|
|
||
| assert not form.is_valid() | ||
| assert form.errors.get("procedure_year") == [expected_message] | ||
|
|
||
| def test_create_persists_data_and_audits(self, clinical_user): | ||
| appointment = AppointmentFactory() | ||
| request = RequestFactory().post("/test-form") | ||
| request.user = clinical_user | ||
|
|
||
| data = [ | ||
| ( | ||
| "left_breast_procedures", | ||
| BenignLumpHistoryItem.Procedure.NEEDLE_BIOPSY, | ||
| ), | ||
| ( | ||
| "right_breast_procedures", | ||
| BenignLumpHistoryItem.Procedure.NO_PROCEDURES, | ||
| ), | ||
| ("procedure_year", datetime.date.today().year - 1), | ||
| ( | ||
| "procedure_location", | ||
| BenignLumpHistoryItem.ProcedureLocation.NHS_HOSPITAL, | ||
| ), | ||
| ("nhs_hospital_details", "St Thomas' Hospital"), | ||
| ("additional_details", "Additional details."), | ||
| ] | ||
| form = BenignLumpHistoryItemForm(_form_data(data)) | ||
| assert form.is_valid() | ||
|
|
||
| existing_log_count = AuditLog.objects.count() | ||
| obj = form.create(appointment=appointment, request=request) | ||
| assert AuditLog.objects.count() == existing_log_count + 1 | ||
| audit_log = AuditLog.objects.filter( | ||
| object_id=obj.pk, operation=AuditLog.Operations.CREATE | ||
| ).first() | ||
| assert audit_log.actor == clinical_user | ||
|
|
||
| obj.refresh_from_db() | ||
| assert obj.appointment == appointment | ||
| assert obj.left_breast_procedures == [ | ||
| BenignLumpHistoryItem.Procedure.NEEDLE_BIOPSY | ||
| ] | ||
| assert obj.right_breast_procedures == [ | ||
| BenignLumpHistoryItem.Procedure.NO_PROCEDURES | ||
| ] | ||
| assert obj.procedure_year == data[2][1] | ||
| assert obj.procedure_location == ( | ||
| BenignLumpHistoryItem.ProcedureLocation.NHS_HOSPITAL | ||
| ) | ||
| assert obj.procedure_location_details == "St Thomas' Hospital" | ||
| assert obj.additional_details == "Additional details." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.