Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@
CharField,
YearField,
)
from manage_breast_screening.nhsuk_forms.fields.choice_fields import (
MultipleChoiceField,
)
from manage_breast_screening.nhsuk_forms.fields.choice_fields import MultipleChoiceField
from manage_breast_screening.participants.models.breast_augmentation_history_item import (
BreastAugmentationHistoryItem,
)


class BreastAugmentationHistoryForm(Form):
class BreastAugmentationHistoryBaseForm(Form):
Copy link
Contributor

@swebberuk swebberuk Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach, to have a BaseForm and separate add and update forms, is the same as has been done in #760 and #770. For some forms - e.g. #778 - we've instead combined the add and update functionality into a single form. If we can agree on what approach is best then I can review all the forms and make them consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I don't have a strong opinion either way. Initially I did the combined approach but thought it was a bit weird that the constructor param was required or not depending on what method you call.

right_breast_procedures = MultipleChoiceField(
label="Right breast",
label_classes="nhsuk-fieldset__legend--s",
Expand Down Expand Up @@ -80,19 +78,6 @@ def model_values(self):
additional_details=self.cleaned_data.get("additional_details", ""),
)

def create(self, appointment, request):
auditor = Auditor.from_request(request)
field_values = self.model_values()
breast_augmentation_history = (
appointment.breast_augmentation_history_items.create(
appointment=appointment,
**field_values,
)
)
auditor.audit_create(breast_augmentation_history)

return breast_augmentation_history

def full_clean(self):
# if a removal_year is provided then remove it if implants_have_been_removed is False
if self.data.get("removal_year") and not self.data.get(
Expand Down Expand Up @@ -121,3 +106,50 @@ def clean(self):
)

return cleaned_data


class BreastAugmentationHistoryForm(BreastAugmentationHistoryBaseForm):
def create(self, appointment, request):
auditor = Auditor.from_request(request)
field_values = self.model_values()
breast_augmentation_history = (
appointment.breast_augmentation_history_items.create(
appointment=appointment,
**field_values,
)
)
auditor.audit_create(breast_augmentation_history)

return breast_augmentation_history


class BreastAugmentationHistoryUpdateForm(BreastAugmentationHistoryBaseForm):
def __init__(self, instance, *args, **kwargs):
self.instance = instance

kwargs["initial"] = {
"right_breast_procedures": instance.right_breast_procedures,
"left_breast_procedures": instance.left_breast_procedures,
"procedure_year": instance.procedure_year,
"implants_have_been_removed": instance.implants_have_been_removed,
"removal_year": instance.removal_year,
"additional_details": instance.additional_details,
}

super().__init__(*args, **kwargs)

def update(self, request):
# fmt: off
self.instance.right_breast_procedures = self.cleaned_data["right_breast_procedures"]
self.instance.left_breast_procedures = self.cleaned_data["left_breast_procedures"]
self.instance.procedure_year = self.cleaned_data["procedure_year"]
self.instance.implants_have_been_removed = self.cleaned_data["implants_have_been_removed"]
self.instance.removal_year = self.cleaned_data["removal_year"]
self.instance.additional_details = self.cleaned_data["additional_details"]
# fmt: on

self.instance.save()

Auditor.from_request(request).audit_update(self.instance)

return self.instance
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@

{% set breast_augmentation_history_html %}
{% for presented_item in presenter.breast_augmentation_history %}
<a style="float: right" class="nhsuk-link nhsuk-link--no-visited-state" href="{{ presented_item.change_link.href}}">
{{ presented_item.change_link.text }}<span class="nhsuk-u-visually-hidden">{{ presented_item.change_link.visually_hidden_text }}</span>
</a><br>
{{ summaryList(presented_item.summary_list_params) }}
{% endfor %}
<a href="{{ presenter.add_breast_augmentation_history_link.href }}" class="nhsuk-link nhsuk-link--no-visited-state">{{ presenter.add_breast_augmentation_history_link.text }}</a><br>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from django.urls import reverse

from manage_breast_screening.core.template_helpers import multiline_content, nl2br
from manage_breast_screening.participants.models.breast_augmentation_history_item import (
BreastAugmentationHistoryItem,
)


class BreastAugmentationHistoryItemPresenter:
def __init__(self, breast_augmentation_history_item):
def __init__(self, breast_augmentation_history_item, counter=None):
self._item = breast_augmentation_history_item
self.counter = counter

self.right_breast_procedures = self._format_multiple_choices(
self._item.right_breast_procedures, BreastAugmentationHistoryItem.Procedure
Expand Down Expand Up @@ -57,3 +60,21 @@ def summary_list_params(self):

def _format_multiple_choices(self, choices, ChoiceClass):
return ", ".join(ChoiceClass(choice).label for choice in choices)

@property
def change_link(self):
return {
"href": reverse(
"mammograms:change_breast_augmentation_history_item",
kwargs={
"pk": self._item.appointment_id,
"history_item_pk": self._item.pk,
},
),
"text": "Change",
"visually_hidden_text": (
f" breast implants or augmentation item {self.counter}"
if self.counter
else " breast implants or augmentation item"
),
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ def __init__(self, appointment):
for counter, item in enumerate(implanted_medical_device_history, 1)
]

self.breast_augmentation_history = [
BreastAugmentationHistoryItemPresenter(item)
for item in appointment.breast_augmentation_history_items.all()
]
self.breast_augmentation_history = self._present_items(
appointment.breast_augmentation_history_items.all(),
BreastAugmentationHistoryItemPresenter,
)
self.other_procedure_history = [
OtherProcedureHistoryItemPresenter(item)
for item in appointment.other_procedure_history_items.all()
Expand Down
9 changes: 9 additions & 0 deletions manage_breast_screening/mammograms/tests/forms/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import pytest
from django.test import RequestFactory


@pytest.fixture
def dummy_request(clinical_user):
request = RequestFactory().get("/test-form")
request.user = clinical_user
return request
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
from manage_breast_screening.participants.models.breast_augmentation_history_item import (
BreastAugmentationHistoryItem,
)
from manage_breast_screening.participants.tests.factories import AppointmentFactory
from manage_breast_screening.participants.tests.factories import (
AppointmentFactory,
BreastAugmentationHistoryItemFactory,
)

from ...forms.breast_augmentation_history_form import BreastAugmentationHistoryForm
from ...forms.breast_augmentation_history_form import (
BreastAugmentationHistoryForm,
BreastAugmentationHistoryUpdateForm,
)


@pytest.mark.django_db
Expand Down Expand Up @@ -432,3 +438,77 @@ def test_success(self, clinical_user, data):
assert obj.implants_have_been_removed == ("implants_have_been_removed" in data)
assert obj.removal_year == data.get("removal_year", None)
assert obj.additional_details == data.get("additional_details", "")


@pytest.mark.django_db
class TestBreastAugmentationHistoryUpdateForm:
@pytest.fixture
def instance(self):
return BreastAugmentationHistoryItemFactory(
right_breast_procedures=[
BreastAugmentationHistoryItem.Procedure.BREAST_IMPLANTS
],
left_breast_procedures=[
BreastAugmentationHistoryItem.Procedure.BREAST_IMPLANTS
],
procedure_year=2000,
implants_have_been_removed=False,
)

def test_no_data(self, instance):
form = BreastAugmentationHistoryUpdateForm(instance, QueryDict())

assert not form.is_valid()
assert form.errors == {
"left_breast_procedures": ["Select procedures for the left breast"],
"right_breast_procedures": ["Select procedures for the right breast"],
}

def test_initial(self, instance):
form = BreastAugmentationHistoryUpdateForm(instance, QueryDict())
assert form.initial == {
"right_breast_procedures": [
BreastAugmentationHistoryItem.Procedure.BREAST_IMPLANTS
],
"left_breast_procedures": [
BreastAugmentationHistoryItem.Procedure.BREAST_IMPLANTS
],
"procedure_year": 2000,
"removal_year": None,
"implants_have_been_removed": False,
"additional_details": "",
}

def test_success(self, instance, dummy_request):
form = BreastAugmentationHistoryUpdateForm(
instance,
QueryDict(
urlencode(
{
"right_breast_procedures": [
BreastAugmentationHistoryItem.Procedure.OTHER_AUGMENTATION
],
"left_breast_procedures": [
BreastAugmentationHistoryItem.Procedure.NO_PROCEDURES
],
"procedure_year": "2001",
"removal_year": "",
"additional_details": "abc",
},
doseq=True,
)
),
)

assert form.is_valid()

obj = form.update(request=dummy_request)
assert obj.right_breast_procedures == [
BreastAugmentationHistoryItem.Procedure.OTHER_AUGMENTATION
]
assert obj.left_breast_procedures == [
BreastAugmentationHistoryItem.Procedure.NO_PROCEDURES
]
assert obj.procedure_year == 2001
assert obj.removal_year is None
assert obj.additional_details == "abc"
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import pytest
from django.http import QueryDict
from django.test import RequestFactory

from manage_breast_screening.participants.models.cyst_history_item import (
CystHistoryItem,
Expand All @@ -15,13 +14,6 @@
from ...forms.cyst_history_form import CystHistoryForm, CystHistoryUpdateForm


@pytest.fixture
def dummy_request(clinical_user):
request = RequestFactory().get("/test-form")
request.user = clinical_user
return request


@pytest.mark.django_db
class TestCystHistoryForm:
def test_no_data(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,23 @@ def test_single(self):
},
],
}

def test_change_link(self):
item = BreastAugmentationHistoryItemFactory.build()

presenter = BreastAugmentationHistoryItemPresenter(item)
assert presenter.change_link == {
"href": f"/mammograms/{item.appointment_id}/record-medical-information/breast-augmentation-history/{item.pk}",
"text": "Change",
"visually_hidden_text": " breast implants or augmentation item",
}

def test_change_link_with_counter(self):
item = BreastAugmentationHistoryItemFactory.build()

presenter = BreastAugmentationHistoryItemPresenter(item, counter=2)
assert presenter.change_link == {
"href": f"/mammograms/{item.appointment_id}/record-medical-information/breast-augmentation-history/{item.pk}",
"text": "Change",
"visually_hidden_text": " breast implants or augmentation item 2",
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from urllib.parse import urlencode

import pytest
from django.contrib import messages
from django.http import QueryDict
from django.urls import reverse
from pytest_django.asserts import assertInHTML, assertMessages, assertRedirects

Expand All @@ -8,6 +11,7 @@
)
from manage_breast_screening.participants.tests.factories import (
AppointmentFactory,
BreastAugmentationHistoryItemFactory,
)


Expand Down Expand Up @@ -77,3 +81,70 @@ def test_invalid_post_renders_response_with_errors(self, clinical_user_client):
""",
response.text,
)


@pytest.mark.django_db
class TestChangeBreastAugmentationHistoryView:
@pytest.fixture
def appointment(self, clinical_user_client):
return AppointmentFactory.create(
clinic_slot__clinic__setting__provider=clinical_user_client.current_provider
)

@pytest.fixture
def history_item(self, appointment):
return BreastAugmentationHistoryItemFactory.create(appointment=appointment)

def test_renders_response(self, clinical_user_client, history_item):
response = clinical_user_client.http.get(
reverse(
"mammograms:change_breast_augmentation_history_item",
kwargs={
"pk": history_item.appointment_id,
"history_item_pk": history_item.pk,
},
)
)
assert response.status_code == 200

def test_valid_post_redirects_to_appointment(
self, clinical_user_client, history_item
):
response = clinical_user_client.http.post(
reverse(
"mammograms:change_breast_augmentation_history_item",
kwargs={
"pk": history_item.appointment_id,
"history_item_pk": history_item.pk,
},
),
QueryDict(
urlencode(
{
"left_breast_procedures": [
BreastAugmentationHistoryItem.Procedure.BREAST_IMPLANTS
],
"right_breast_procedures": [
BreastAugmentationHistoryItem.Procedure.BREAST_IMPLANTS
],
},
doseq=True,
)
),
)
assertRedirects(
response,
reverse(
"mammograms:record_medical_information",
kwargs={"pk": history_item.appointment_id},
),
)
assertMessages(
response,
[
messages.Message(
level=messages.SUCCESS,
message="Details of breast implants or augmentation updated",
)
],
)
Loading