diff --git a/manage_breast_screening/mammograms/forms/appointment_note_form.py b/manage_breast_screening/mammograms/forms/appointment_note_form.py index a63da9018..fe7e7733f 100644 --- a/manage_breast_screening/mammograms/forms/appointment_note_form.py +++ b/manage_breast_screening/mammograms/forms/appointment_note_form.py @@ -27,3 +27,7 @@ def save(self): self.instance.content = self.cleaned_data["content"] self.instance.save() return self.instance + + @property + def instance_is_saved(self): + return self.instance and not self.instance._state.adding diff --git a/manage_breast_screening/mammograms/jinja2/mammograms/show/appointment_note.jinja b/manage_breast_screening/mammograms/jinja2/mammograms/show/appointment_note.jinja index 6e90d1475..5c027ad32 100644 --- a/manage_breast_screening/mammograms/jinja2/mammograms/show/appointment_note.jinja +++ b/manage_breast_screening/mammograms/jinja2/mammograms/show/appointment_note.jinja @@ -77,8 +77,16 @@ {{ button({ "text": "Save note" }) }} + {% if form.instance_is_saved %} +

+ + Delete appointment note + +

+ {% endif %} + {% endblock %} diff --git a/manage_breast_screening/mammograms/tests/views/test_appointment_note_views.py b/manage_breast_screening/mammograms/tests/views/test_appointment_note_views.py new file mode 100644 index 000000000..275d2bf54 --- /dev/null +++ b/manage_breast_screening/mammograms/tests/views/test_appointment_note_views.py @@ -0,0 +1,120 @@ +import pytest +from django.urls import reverse +from pytest_django.asserts import assertRedirects + +from manage_breast_screening.participants.models import AppointmentNote +from manage_breast_screening.participants.tests.factories import AppointmentFactory + + +@pytest.mark.django_db +class TestAppointmentNoteView: + def test_delete_link_not_shown_when_note_does_not_exist(self, clinical_user_client): + appointment = AppointmentFactory.create( + clinic_slot__clinic__setting__provider=clinical_user_client.current_provider + ) + response = clinical_user_client.http.get( + reverse( + "mammograms:appointment_note", + kwargs={"pk": appointment.pk}, + ) + ) + assert response.status_code == 200 + assert "Delete appointment note" not in response.content.decode() + + def test_delete_link_shown_when_note_exists(self, clinical_user_client): + appointment = AppointmentFactory.create( + clinic_slot__clinic__setting__provider=clinical_user_client.current_provider + ) + AppointmentNote.objects.create(appointment=appointment, content="Existing note") + response = clinical_user_client.http.get( + reverse( + "mammograms:appointment_note", + kwargs={"pk": appointment.pk}, + ) + ) + assert response.status_code == 200 + assert "Delete appointment note" in response.content.decode() + + @pytest.mark.parametrize( + "client_fixture", ["clinical_user_client", "administrative_user_client"] + ) + def test_users_can_save_note(self, request, client_fixture): + client = request.getfixturevalue(client_fixture) + appointment = AppointmentFactory.create( + clinic_slot__clinic__setting__provider=client.current_provider + ) + + note_content = "Participant prefers left arm blood pressure readings." + response = client.http.post( + reverse( + "mammograms:appointment_note", + kwargs={"pk": appointment.pk}, + ), + {"content": note_content}, + ) + + assertRedirects( + response, + reverse("mammograms:appointment_note", kwargs={"pk": appointment.pk}), + ) + saved_note = AppointmentNote.objects.get(appointment=appointment) + assert saved_note.content == note_content + + @pytest.mark.parametrize( + "client_fixture", ["clinical_user_client", "administrative_user_client"] + ) + def test_users_can_update_note(self, request, client_fixture): + client = request.getfixturevalue(client_fixture) + appointment = AppointmentFactory.create( + clinic_slot__clinic__setting__provider=client.current_provider + ) + note = AppointmentNote.objects.create( + appointment=appointment, content="Original note" + ) + + updated_content = "Updated note content" + response = client.http.post( + reverse("mammograms:appointment_note", kwargs={"pk": appointment.pk}), + {"content": updated_content}, + ) + + assertRedirects( + response, + reverse("mammograms:appointment_note", kwargs={"pk": appointment.pk}), + ) + updated_note = AppointmentNote.objects.get(pk=note.pk) + assert updated_note.content == updated_content + assert AppointmentNote.objects.count() == 1 + + +@pytest.mark.django_db +class TestDeleteAppointmentNoteView: + def test_get_redirects_when_note_does_not_exist(self, clinical_user_client): + appointment = AppointmentFactory.create( + clinic_slot__clinic__setting__provider=clinical_user_client.current_provider + ) + response = clinical_user_client.http.get( + reverse( + "mammograms:delete_appointment_note", + kwargs={"pk": appointment.pk}, + ) + ) + assertRedirects( + response, + reverse("mammograms:appointment_note", kwargs={"pk": appointment.pk}), + ) + + def test_post_redirects_when_note_does_not_exist(self, clinical_user_client): + appointment = AppointmentFactory.create( + clinic_slot__clinic__setting__provider=clinical_user_client.current_provider + ) + response = clinical_user_client.http.post( + reverse( + "mammograms:delete_appointment_note", + kwargs={"pk": appointment.pk}, + ) + ) + assertRedirects( + response, + reverse("mammograms:appointment_note", kwargs={"pk": appointment.pk}), + ) diff --git a/manage_breast_screening/mammograms/tests/views/test_appointment_views.py b/manage_breast_screening/mammograms/tests/views/test_appointment_views.py index 903705593..a3532b6ac 100644 --- a/manage_breast_screening/mammograms/tests/views/test_appointment_views.py +++ b/manage_breast_screening/mammograms/tests/views/test_appointment_views.py @@ -3,7 +3,6 @@ from pytest_django.asserts import assertContains, assertRedirects from manage_breast_screening.core.models import AuditLog -from manage_breast_screening.participants.models import AppointmentNote from manage_breast_screening.participants.tests.factories import AppointmentFactory @@ -22,60 +21,6 @@ def test_renders_response(self, clinical_user_client): assert response.status_code == 200 -@pytest.mark.django_db -class TestAppointmentNoteView: - @pytest.mark.parametrize( - "client_fixture", ["clinical_user_client", "administrative_user_client"] - ) - def test_users_can_save_note(self, request, client_fixture): - client = request.getfixturevalue(client_fixture) - appointment = AppointmentFactory.create( - clinic_slot__clinic__setting__provider=client.current_provider - ) - - note_content = "Participant prefers left arm blood pressure readings." - response = client.http.post( - reverse( - "mammograms:appointment_note", - kwargs={"pk": appointment.pk}, - ), - {"content": note_content}, - ) - - assertRedirects( - response, - reverse("mammograms:appointment_note", kwargs={"pk": appointment.pk}), - ) - saved_note = AppointmentNote.objects.get(appointment=appointment) - assert saved_note.content == note_content - - @pytest.mark.parametrize( - "client_fixture", ["clinical_user_client", "administrative_user_client"] - ) - def test_users_can_update_note(self, request, client_fixture): - client = request.getfixturevalue(client_fixture) - appointment = AppointmentFactory.create( - clinic_slot__clinic__setting__provider=client.current_provider - ) - note = AppointmentNote.objects.create( - appointment=appointment, content="Original note" - ) - - updated_content = "Updated note content" - response = client.http.post( - reverse("mammograms:appointment_note", kwargs={"pk": appointment.pk}), - {"content": updated_content}, - ) - - assertRedirects( - response, - reverse("mammograms:appointment_note", kwargs={"pk": appointment.pk}), - ) - updated_note = AppointmentNote.objects.get(pk=note.pk) - assert updated_note.content == updated_content - assert AppointmentNote.objects.count() == 1 - - @pytest.mark.django_db class TestConfirmIdentity: def test_renders_response(self, clinical_user_client): diff --git a/manage_breast_screening/mammograms/urls.py b/manage_breast_screening/mammograms/urls.py index cfee41fea..4a7178ee7 100644 --- a/manage_breast_screening/mammograms/urls.py +++ b/manage_breast_screening/mammograms/urls.py @@ -1,6 +1,11 @@ from django.urls import path -from .views import appointment_views, special_appointment_views, symptom_views +from .views import ( + appointment_note_views, + appointment_views, + special_appointment_views, + symptom_views, +) from .views.medical_history import ( benign_lump_history_item_views, breast_augmentation_history_item_views, @@ -36,9 +41,14 @@ ), path( "/note/", - appointment_views.AppointmentNoteView.as_view(), + appointment_note_views.AppointmentNoteView.as_view(), name="appointment_note", ), + path( + "/note/delete/", + appointment_note_views.DeleteAppointmentNoteView.as_view(), + name="delete_appointment_note", + ), path( "/confirm-identity/", appointment_views.ConfirmIdentity.as_view(), diff --git a/manage_breast_screening/mammograms/views/appointment_note_views.py b/manage_breast_screening/mammograms/views/appointment_note_views.py new file mode 100644 index 000000000..6a51bee48 --- /dev/null +++ b/manage_breast_screening/mammograms/views/appointment_note_views.py @@ -0,0 +1,92 @@ +import logging + +from django.contrib import messages +from django.shortcuts import redirect +from django.urls import reverse +from django.views.generic import FormView + +from manage_breast_screening.core.services.auditor import Auditor +from manage_breast_screening.core.views.generic import DeleteWithAuditView +from manage_breast_screening.participants.models import AppointmentNote + +from ..forms import AppointmentNoteForm +from ..presenters import AppointmentPresenter, present_secondary_nav +from .mixins import AppointmentMixin + +logger = logging.getLogger(__name__) + + +class AppointmentNoteView(AppointmentMixin, FormView): + template_name = "mammograms/show/appointment_note.jinja" + form_class = AppointmentNoteForm + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + appointment = self.appointment + appointment_presenter = AppointmentPresenter( + appointment, tab_description="Note" + ) + + context.update( + { + "heading": appointment_presenter.participant.full_name, + "caption": appointment_presenter.caption, + "page_title": appointment_presenter.page_title, + "presented_appointment": appointment_presenter, + "secondary_nav_items": present_secondary_nav( + appointment.pk, current_tab="note" + ), + } + ) + return context + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + try: + kwargs["instance"] = self.appointment.note + except AppointmentNote.DoesNotExist: + kwargs["instance"] = AppointmentNote(appointment=self.appointment) + return kwargs + + def form_valid(self, form): + is_new_note = form.instance._state.adding + note = form.save() + auditor = Auditor.from_request(self.request) + if is_new_note: + auditor.audit_create(note) + else: + auditor.audit_update(note) + messages.add_message( + self.request, + messages.SUCCESS, + "Appointment note saved", + ) + return redirect("mammograms:appointment_note", pk=self.appointment.pk) + + +class DeleteAppointmentNoteView(DeleteWithAuditView): + def get_thing_name(self, object): + return "appointment note" + + def get_success_message_content(self, object): + return "Appointment note deleted" + + def get_object(self): + provider = self.request.user.current_provider + appointment = provider.appointments.get(pk=self.kwargs["pk"]) + return appointment.note + + def get_success_url(self): + return reverse("mammograms:appointment_note", kwargs={"pk": self.kwargs["pk"]}) + + def get(self, request, *args, **kwargs): + try: + return super().get(request, *args, **kwargs) + except AppointmentNote.DoesNotExist: + return redirect(self.get_success_url()) + + def post(self, request, *args, **kwargs): + try: + return super().post(request, *args, **kwargs) + except AppointmentNote.DoesNotExist: + return redirect(self.get_success_url()) diff --git a/manage_breast_screening/mammograms/views/appointment_views.py b/manage_breast_screening/mammograms/views/appointment_views.py index 5e428261c..2a56f663e 100644 --- a/manage_breast_screening/mammograms/views/appointment_views.py +++ b/manage_breast_screening/mammograms/views/appointment_views.py @@ -1,6 +1,5 @@ import logging -from django.contrib import messages from django.http import Http404 from django.shortcuts import redirect, render from django.urls import reverse, reverse_lazy @@ -14,14 +13,12 @@ ) from manage_breast_screening.participants.models import ( Appointment, - AppointmentNote, ParticipantReportedMammogram, ) from manage_breast_screening.participants.presenters import ParticipantPresenter from ..forms import ( AppointmentCannotGoAheadForm, - AppointmentNoteForm, AskForMedicalInformationForm, RecordMedicalInformationForm, ) @@ -116,54 +113,6 @@ def get(self, request, *args, **kwargs): ) -class AppointmentNoteView(AppointmentMixin, FormView): - template_name = "mammograms/show/appointment_note.jinja" - form_class = AppointmentNoteForm - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - appointment = self.appointment - appointment_presenter = AppointmentPresenter( - appointment, tab_description="Note" - ) - - context.update( - { - "heading": appointment_presenter.participant.full_name, - "caption": appointment_presenter.caption, - "page_title": appointment_presenter.page_title, - "presented_appointment": appointment_presenter, - "secondary_nav_items": present_secondary_nav( - appointment.pk, current_tab="note" - ), - } - ) - return context - - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - try: - kwargs["instance"] = self.appointment.note - except AppointmentNote.DoesNotExist: - kwargs["instance"] = AppointmentNote(appointment=self.appointment) - return kwargs - - def form_valid(self, form): - is_new_note = form.instance._state.adding - note = form.save() - auditor = Auditor.from_request(self.request) - if is_new_note: - auditor.audit_create(note) - else: - auditor.audit_update(note) - messages.add_message( - self.request, - messages.SUCCESS, - "Appointment note saved", - ) - return redirect("mammograms:appointment_note", pk=self.appointment.pk) - - class ConfirmIdentity(InProgressAppointmentMixin, TemplateView): template_name = "mammograms/confirm_identity.jinja" diff --git a/manage_breast_screening/tests/system/clinical/test_appointment_note.py b/manage_breast_screening/tests/system/clinical/test_appointment_note.py index 11f8e8a22..706db510d 100644 --- a/manage_breast_screening/tests/system/clinical/test_appointment_note.py +++ b/manage_breast_screening/tests/system/clinical/test_appointment_note.py @@ -21,15 +21,24 @@ def test_clinical_user_adds_and_updates_an_appointment_note(self): self.when_i_enter_a_note() self.and_i_save_the_note() self.then_i_see_a_message_confirming_the_save() - self.then_the_note_field_contains(self.initial_note_text) + self.and_the_note_field_contains(self.initial_note_text) self.and_the_appointment_details_tab_shows_the_note(self.initial_note_text) self.when_i_update_the_note() self.and_i_save_the_note() self.then_i_see_a_message_confirming_the_save() - self.then_the_note_field_contains(self.updated_note_text) + self.and_the_note_field_contains(self.updated_note_text) self.and_the_appointment_details_tab_shows_the_note(self.updated_note_text) + self.when_i_click_the_note_change_link() + self.and_i_click_delete_note() + self.and_i_click_cancel() + self.then_the_note_is_not_deleted() + + self.when_i_click_delete_note() + self.and_i_confirm_deletion() + self.then_the_note_is_deleted() + def and_there_is_an_appointment_for_my_provider(self): self.appointment = AppointmentFactory( clinic_slot__clinic__setting__provider=self.current_provider @@ -67,7 +76,7 @@ def and_i_save_the_note(self): def when_i_save_the_note(self): self.and_i_save_the_note() - def then_the_note_field_contains(self, text): + def and_the_note_field_contains(self, text): expect(self.page.get_by_label("Note")).to_have_value(text) def then_i_see_a_message_confirming_the_save(self): @@ -86,3 +95,38 @@ def and_the_appointment_details_tab_shows_the_note(self, text): ) expect(note_container).to_be_visible() expect(note_container).to_contain_text(text) + + def when_i_click_the_note_change_link(self): + note_container = self.page.locator( + ".nhsuk-warning-callout", has_text="Appointment note" + ) + expect(note_container).to_be_visible() + change_link = note_container.get_by_role("link", name="Change") + change_link.click() + + def and_i_click_delete_note(self): + delete_link = self.page.get_by_role("link", name="Delete appointment note") + expect(delete_link).to_be_visible() + delete_link.click() + + def when_i_click_delete_note(self): + self.and_i_click_delete_note() + + def and_i_click_cancel(self): + cancel_link = self.page.get_by_role("link", name="Cancel") + expect(cancel_link).to_be_visible() + cancel_link.click() + + def then_the_note_is_not_deleted(self): + expect(self.page.get_by_label("Note")).to_have_value(self.updated_note_text) + + def and_i_confirm_deletion(self): + delete_button = self.page.get_by_role("button", name="Delete appointment note") + expect(delete_button).to_be_visible() + delete_button.click() + + def then_the_note_is_deleted(self): + banner = self.page.locator(".nhsuk-notification-banner--success") + expect(banner).to_be_visible() + expect(banner).to_contain_text("Appointment note deleted") + expect(self.page.get_by_label("Note")).to_have_value("")