diff --git a/.gitignore b/.gitignore index 7650f01c0..34a049dcd 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ __pycache__ .venv/ # Python coverage -.coverage +.coverage* # Django .env diff --git a/manage_breast_screening/dicom/dicom_recorder.py b/manage_breast_screening/dicom/dicom_recorder.py index d3ace49f8..86ecb6f47 100644 --- a/manage_breast_screening/dicom/dicom_recorder.py +++ b/manage_breast_screening/dicom/dicom_recorder.py @@ -15,6 +15,13 @@ logger = logging.getLogger(__name__) +def lookup_appointment(source_message_id): + try: + return GatewayAction.objects.get(id=source_message_id).appointment + except GatewayAction.DoesNotExist: + return None + + class DicomProcessingError(Exception): """Custom exception for DICOM processing errors.""" @@ -26,7 +33,8 @@ class DicomRecorder: def get_or_create_records( source_message_id: str, dicom_file: File ) -> tuple[Study, Series, Image]: - if not __class__.appointment_in_progress(source_message_id): + appointment = lookup_appointment(source_message_id) + if not appointment or not appointment.is_in_progress(): raise DicomProcessingError( f"Cannot process DICOM file for source_message_id={source_message_id} " "because the associated appointment is not in progress." @@ -46,6 +54,7 @@ def get_or_create_records( ) study, _ = Study.objects.get_or_create( + appointment=appointment, study_instance_uid=study_uid, source_message_id=source_message_id, defaults={ @@ -135,10 +144,3 @@ def dataset_to_jpeg(sop_uid: str, ds: pydicom.Dataset) -> InMemoryUploadedFile: size=in_memory_file.getbuffer().nbytes, charset=None, ) - - @staticmethod - def appointment_in_progress(source_message_id: str) -> bool: - gateway_action = GatewayAction.objects.filter(id=source_message_id).first() - if not gateway_action or not gateway_action.appointment: - return False - return gateway_action.appointment.current_status.is_in_progress() diff --git a/manage_breast_screening/dicom/migrations/0009_rename_order_readingsessionitem_reading_order_and_more.py b/manage_breast_screening/dicom/migrations/0009_rename_order_readingsessionitem_reading_order_and_more.py new file mode 100644 index 000000000..44ee9a33e --- /dev/null +++ b/manage_breast_screening/dicom/migrations/0009_rename_order_readingsessionitem_reading_order_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 6.0.4 on 2026-04-27 14:38 + +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + + +def set_appointment(apps, schema_editor): + Study = apps.get_model('dicom', 'Study') + Appointment = apps.get_model('participants', 'Appointment') + GatewayAction = apps.get_model('gateway', 'GatewayAction') + for study in Study.objects.all(): + if study.source_message_id: + action = GatewayAction.objects.filter(id=study.source_message_id).first() + if action is None and settings.DJANGO_ENV != 'production': + # workaround inconsistent demo data by linking orphan studies to arbitrary appointments + study.appointment = Appointment.objects.filter(dicom_study=None).first() + else: + study.appointment = action.appointment + study.save() + +def unset_appointment(apps, schema_editor): + Study = apps.get_model('dicom', 'Study') + Study.objects.all().update(appointment=None) + +class Migration(migrations.Migration): + + dependencies = [ + ('dicom', '0008_readingsession_reading_recallforassessmentdetails_and_more'), + ('participants', '0001_squashed_0067_participantreportedmammogram_created_by_and_more'), + ('gateway', '0005_relay_provider_to_setting'), + ] + + operations = [ + migrations.RenameField( + model_name='readingsessionitem', + old_name='order', + new_name='reading_order', + ), + migrations.AlterUniqueTogether( + name='readingsessionitem', + unique_together={('session', 'reading_order')}, + ), + migrations.AddField( + model_name='study', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='study', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='study', + name='appointment', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='dicom_study', to='participants.appointment'), + ), + migrations.RunPython(code=set_appointment, reverse_code=unset_appointment), + migrations.AlterField( + model_name='study', + name='appointment', + field=models.OneToOneField( + to='participants.Appointment', on_delete=models.PROTECT, related_name="dicom_study" + ) + ) + ] diff --git a/manage_breast_screening/dicom/migrations/max_migration.txt b/manage_breast_screening/dicom/migrations/max_migration.txt index 2eeef0e30..28f2e1f77 100644 --- a/manage_breast_screening/dicom/migrations/max_migration.txt +++ b/manage_breast_screening/dicom/migrations/max_migration.txt @@ -1 +1 @@ -0008_readingsession_reading_recallforassessmentdetails_and_more +0009_rename_order_readingsessionitem_reading_order_and_more diff --git a/manage_breast_screening/dicom/models.py b/manage_breast_screening/dicom/models.py index 0c6b04cb8..5583fffc5 100644 --- a/manage_breast_screening/dicom/models.py +++ b/manage_breast_screening/dicom/models.py @@ -12,13 +12,14 @@ RepeatType, StudyCompleteness, ) +from manage_breast_screening.participants.models.appointment import Appointment def dicom_storage(): return storages["dicom"] -class Study(models.Model): +class Study(BaseModel): class Meta: indexes = [ models.Index(fields=["study_instance_uid"]), @@ -27,6 +28,9 @@ class Meta: ] id = models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True) + appointment = models.OneToOneField( + Appointment, on_delete=models.PROTECT, related_name="dicom_study" + ) study_instance_uid = models.CharField(max_length=128, unique=True) source_message_id = models.CharField(max_length=128) patient_id = models.CharField(max_length=10, blank=True) @@ -53,14 +57,6 @@ def images(self) -> models.QuerySet["Image"]: "series__series_number", "instance_number" ) - @classmethod - def for_appointment(cls, appointment): - action = appointment.gateway_actions.first() - if not action: - return None - - return cls.objects.filter(source_message_id=action.id).first() - def series_with_multiple_images(self): return self.series.annotate(image_count=models.Count("images")).filter( image_count__gt=1 @@ -245,7 +241,7 @@ class ReadingSessionItem(BaseModel): study = models.ForeignKey( Study, on_delete=models.PROTECT, related_name="reading_session_items" ) - order = models.IntegerField() + reading_order = models.IntegerField() reading = models.OneToOneField( Reading, on_delete=models.PROTECT, @@ -254,4 +250,4 @@ class ReadingSessionItem(BaseModel): ) class Meta: - unique_together = [("session", "order")] + unique_together = [("session", "reading_order")] diff --git a/manage_breast_screening/dicom/study_service.py b/manage_breast_screening/dicom/study_service.py index eb078c2e3..1ef755a64 100644 --- a/manage_breast_screening/dicom/study_service.py +++ b/manage_breast_screening/dicom/study_service.py @@ -22,8 +22,7 @@ def save( Save additional details to the Study associated with the appointment's GatewayAction. Returns the updated Study, or None if no Study is found. """ - study = Study.for_appointment(self.appointment) - + study = getattr(self.appointment, "dicom_study", None) if not study: return None diff --git a/manage_breast_screening/dicom/tests/factories.py b/manage_breast_screening/dicom/tests/factories.py index f9baa577b..43292ee33 100644 --- a/manage_breast_screening/dicom/tests/factories.py +++ b/manage_breast_screening/dicom/tests/factories.py @@ -4,6 +4,10 @@ from factory.django import DjangoModelFactory, FileField from factory.helpers import post_generation +from manage_breast_screening.participants.models.appointment import ( + AppointmentStatusNames, +) +from manage_breast_screening.participants.tests.factories import AppointmentFactory from manage_breast_screening.users.tests.factories import UserFactory from .. import models @@ -18,6 +22,9 @@ class Meta: patient_id = Sequence(lambda n: f"999{n:07d}") date_and_time = None description = "Test Study" + appointment = SubFactory( + AppointmentFactory, current_status=AppointmentStatusNames.SCREENED + ) class StudyWithImagesFactory(StudyFactory): @@ -104,7 +111,7 @@ class Meta: model = models.ReadingSessionItem study = SubFactory(StudyFactory) - order = Sequence(lambda i: i) + reading_order = Sequence(lambda i: i) class ReadingSessionFactory(DjangoModelFactory): diff --git a/manage_breast_screening/dicom/tests/test_api.py b/manage_breast_screening/dicom/tests/test_api.py index ff1196ac4..3d8827685 100644 --- a/manage_breast_screening/dicom/tests/test_api.py +++ b/manage_breast_screening/dicom/tests/test_api.py @@ -8,10 +8,13 @@ from ninja.testing import TestClient from manage_breast_screening.core.api import api -from manage_breast_screening.dicom.dicom_recorder import DicomRecorder from manage_breast_screening.dicom.models import Study from manage_breast_screening.gateway.models import GatewayActionStatus from manage_breast_screening.gateway.tests.factories import GatewayActionFactory +from manage_breast_screening.participants.models.appointment import ( + AppointmentStatusNames, +) +from manage_breast_screening.participants.tests.factories import AppointmentFactory os.environ["NINJA_SKIP_REGISTRY"] = "yes" @@ -28,14 +31,26 @@ def dicom_file(dataset) -> bytes: ) +@pytest.fixture +def appointment_stub(): + return AppointmentFactory.stub( + is_in_progress=MagicMock(return_value=True), + ) + + @pytest.mark.django_db def test_upload_success(dataset, dicom_file, monkeypatch): monkeypatch.setenv("API_ENABLED", "true") monkeypatch.setenv("API_AUTH_TOKEN", "testtoken") - with patch.object(DicomRecorder, "appointment_in_progress", return_value=True): + appointment = AppointmentFactory(current_status=AppointmentStatusNames.IN_PROGRESS) + + with patch( + "manage_breast_screening.dicom.dicom_recorder.lookup_appointment", + return_value=appointment, + ): response = client.put( - "/dicom/abc123", + f"/dicom/{appointment.pk}", FILES={"file": dicom_file}, headers={"Authorization": "Bearer " + os.getenv("API_AUTH_TOKEN", "")}, ) @@ -47,7 +62,7 @@ def test_upload_success(dataset, dicom_file, monkeypatch): assert json["series_instance_uid"] == dataset.SeriesInstanceUID assert json["sop_instance_uid"] == dataset.SOPInstanceUID assert json["instance_id"] == str(study.images().first().id) - assert study.source_message_id == "abc123" + assert study.source_message_id == str(appointment.pk) def test_upload_no_file(monkeypatch): @@ -63,7 +78,7 @@ def test_upload_no_file(monkeypatch): assert response.status_code == 422 -def test_upload_invalid_file(monkeypatch): +def test_upload_invalid_file(monkeypatch, appointment_stub): monkeypatch.setenv("API_ENABLED", "true") monkeypatch.setenv("API_AUTH_TOKEN", "testtoken") @@ -71,7 +86,10 @@ def test_upload_invalid_file(monkeypatch): "invalid.dcm", b"not a dicom file", content_type="application/dicom" ) - with patch.object(DicomRecorder, "appointment_in_progress", return_value=True): + with patch( + "manage_breast_screening.dicom.dicom_recorder.lookup_appointment", + return_value=appointment_stub, + ): response = client.put( "/dicom/abc123", FILES={"file": invalid_file}, @@ -102,7 +120,7 @@ def test_upload_file_thats_too_large(monkeypatch): assert response.json()["detail"] == "The file cannot be larger than 100MB" -def test_upload_missing_uids(dataset, monkeypatch): +def test_upload_missing_uids(dataset, monkeypatch, appointment_stub): monkeypatch.setenv("API_ENABLED", "true") monkeypatch.setenv("API_AUTH_TOKEN", "testtoken") @@ -117,7 +135,10 @@ def test_upload_missing_uids(dataset, monkeypatch): "temp.dcm", buffer.read(), content_type="application/dicom" ) - with patch.object(DicomRecorder, "appointment_in_progress", return_value=True): + with patch( + "manage_breast_screening.dicom.dicom_recorder.lookup_appointment", + return_value=appointment_stub, + ): response = client.put( "/dicom/abc123", FILES={"file": dicom_file}, @@ -133,11 +154,16 @@ def test_upload_missing_uids(dataset, monkeypatch): ) -def test_upload_appointment_not_in_progress(dicom_file, monkeypatch): +def test_upload_appointment_not_in_progress(dicom_file, monkeypatch, appointment_stub): monkeypatch.setenv("API_ENABLED", "true") monkeypatch.setenv("API_AUTH_TOKEN", "testtoken") - with patch.object(DicomRecorder, "appointment_in_progress", return_value=False): + appointment_stub.is_in_progress.return_value = False + + with patch( + "manage_breast_screening.dicom.dicom_recorder.lookup_appointment", + return_value=appointment_stub, + ): response = client.put( "/dicom/abc123", FILES={"file": dicom_file}, diff --git a/manage_breast_screening/dicom/tests/test_models.py b/manage_breast_screening/dicom/tests/test_models.py index 7c387c26f..c5da641c6 100644 --- a/manage_breast_screening/dicom/tests/test_models.py +++ b/manage_breast_screening/dicom/tests/test_models.py @@ -1,12 +1,10 @@ import pytest -from manage_breast_screening.dicom.models import Study from manage_breast_screening.dicom.tests.factories import ( ImageFactory, SeriesFactory, StudyFactory, ) -from manage_breast_screening.gateway.tests.factories import GatewayActionFactory @pytest.mark.django_db @@ -34,12 +32,6 @@ def test_study_does_not_have_series_with_multiple_images(self): assert study.has_series_with_multiple_images() is False - def test_study_for_appointment(self): - study = StudyFactory.create() - action = GatewayActionFactory.create(id=study.source_message_id) - - assert Study.for_appointment(action.appointment) == study - @pytest.mark.django_db class TestSeries: diff --git a/manage_breast_screening/dicom/tests/test_study_service.py b/manage_breast_screening/dicom/tests/test_study_service.py index a34b7fb08..e08e5f769 100644 --- a/manage_breast_screening/dicom/tests/test_study_service.py +++ b/manage_breast_screening/dicom/tests/test_study_service.py @@ -20,7 +20,10 @@ def current_user(self): def test_save_success(self, current_user): gateway_action = GatewayActionFactory() - study = StudyFactory(source_message_id=gateway_action.id) + study = StudyFactory( + source_message_id=gateway_action.id, + appointment=gateway_action.appointment, + ) with patch.object(Auditor, "audit_update") as mock_audit_update: service = StudyService(gateway_action.appointment, current_user) @@ -41,11 +44,6 @@ def test_save_success(self, current_user): assert study.completeness == "complete" mock_audit_update.assert_called_once_with(study) - def test_save_no_action(self, current_user): - service = StudyService(MagicMock(), current_user) - result = service.save() - assert result is None - def test_save_no_study(self, current_user): gateway_action = GatewayActionFactory() service = StudyService(gateway_action.appointment, current_user) @@ -54,7 +52,9 @@ def test_save_no_study(self, current_user): def test_update_additional_details(self, current_user): gateway_action = GatewayActionFactory() - study = StudyFactory(source_message_id=gateway_action.id) + study = StudyFactory( + source_message_id=gateway_action.id, appointment=gateway_action.appointment + ) with patch.object(Auditor, "audit_update") as mock_audit_update: service = StudyService(gateway_action.appointment, current_user) @@ -65,7 +65,7 @@ def test_update_additional_details(self, current_user): mock_audit_update.assert_called_once_with(study) def test_images_by_laterality_and_view(self): - action = GatewayActionFactory() + action = GatewayActionFactory.build() series = SeriesFactory.create(study__source_message_id=str(action.id)) image1 = ImageFactory.create(series=series, view_position="CC", laterality="L") image2 = ImageFactory.create(series=series, view_position="CC", laterality="R") diff --git a/manage_breast_screening/gateway/tests/test_get_images_for_appointment.py b/manage_breast_screening/gateway/tests/test_get_images_for_appointment.py index 005ae10f7..e1e54c552 100644 --- a/manage_breast_screening/gateway/tests/test_get_images_for_appointment.py +++ b/manage_breast_screening/gateway/tests/test_get_images_for_appointment.py @@ -34,7 +34,7 @@ def test_returns_empty_queryset_when_no_images(self, _): assert not images.exists() - def test_returns_images_linked_via_gateway_action(self, _): + def test_returns_images_linked_to_appointment(self, _): appointment = AppointmentFactory() RelayFactory(setting=appointment.clinic_slot.clinic.setting) @@ -43,6 +43,7 @@ def test_returns_images_linked_via_gateway_action(self, _): study = Study.objects.create( study_instance_uid="1.2.826.0.1.1", # gitleaks:allow source_message_id=str(action.id), + appointment=appointment, ) series = Series.objects.create( study=study, diff --git a/manage_breast_screening/gateway/worklist_item_service.py b/manage_breast_screening/gateway/worklist_item_service.py index 6ffcdb956..a967ee8ed 100644 --- a/manage_breast_screening/gateway/worklist_item_service.py +++ b/manage_breast_screening/gateway/worklist_item_service.py @@ -17,17 +17,14 @@ def get_images_for_appointment(appointment: Appointment): """ - Get all DICOM images for an appointment via its GatewayAction. + Get all DICOM images for an appointment. """ - action = appointment.gateway_actions.filter( - type=GatewayActionType.WORKLIST_CREATE - ).first() - - if not action: + study = getattr(appointment, "dicom_study", None) + if not study: return Image.objects.none() return ( - Image.objects.filter(series__study__source_message_id=str(action.id)) + Image.objects.filter(series__study=study) .select_related("series__study") .order_by("series__series_number", "instance_number") ) diff --git a/manage_breast_screening/mammograms/presenters/appointment_presenters.py b/manage_breast_screening/mammograms/presenters/appointment_presenters.py index b9f2e50da..f28884f83 100644 --- a/manage_breast_screening/mammograms/presenters/appointment_presenters.py +++ b/manage_breast_screening/mammograms/presenters/appointment_presenters.py @@ -5,7 +5,6 @@ from django.urls import reverse from manage_breast_screening.auth.models import Permission -from manage_breast_screening.dicom.models import Study as DicomStudy from manage_breast_screening.dicom.study_service import ( StudyService as DicomStudyService, ) @@ -359,7 +358,7 @@ def title(self): class GatewayImagesPresenter(ImagesPresenter): def __init__(self, appointment): - study = DicomStudy.for_appointment(appointment) + study = appointment.dicom_study self.additional_details = study.additional_details images = study.images() diff --git a/manage_breast_screening/mammograms/tests/forms/images/test_gateway_image_details_form.py b/manage_breast_screening/mammograms/tests/forms/images/test_gateway_image_details_form.py index d57e99273..bca053a20 100644 --- a/manage_breast_screening/mammograms/tests/forms/images/test_gateway_image_details_form.py +++ b/manage_breast_screening/mammograms/tests/forms/images/test_gateway_image_details_form.py @@ -3,7 +3,6 @@ import pytest from django.http import QueryDict -from manage_breast_screening.dicom.models import Study from manage_breast_screening.dicom.study_service import StudyService from manage_breast_screening.dicom.tests.factories import ImageFactory, StudyFactory from manage_breast_screening.gateway.tests.factories import GatewayActionFactory @@ -73,8 +72,12 @@ def test_zero_images(self): } def test_counts_provided_for_all_image_types(self, study_service, recall_service): - gateway_action = GatewayActionFactory(appointment=study_service.appointment) - StudyFactory(source_message_id=gateway_action.id) + gateway_action = GatewayActionFactory.build( + appointment=study_service.appointment + ) + StudyFactory( + source_message_id=gateway_action.id, appointment=study_service.appointment + ) form = GatewayImageDetailsForm( QueryDict( @@ -100,7 +103,7 @@ def test_counts_provided_for_all_image_types(self, study_service, recall_service study = form.save(study_service=study_service, recall_service=recall_service) - assert Study.for_appointment(study_service.appointment) == study + assert study_service.appointment.dicom_study == study assert study.additional_details == "Some additional details" assert study.completeness == StudyCompleteness.COMPLETE assert not study.imperfect_but_best_possible @@ -110,8 +113,8 @@ def test_counts_provided_for_only_one_image_type_and_should_recall( self, study_service, recall_service ): appointment = study_service.appointment - gateway_action = GatewayActionFactory(appointment=appointment) - StudyFactory(source_message_id=gateway_action.id) + gateway_action = GatewayActionFactory.build(appointment=appointment) + StudyFactory(appointment=appointment, source_message_id=gateway_action.id) form = GatewayImageDetailsForm( QueryDict( urlencode( @@ -156,8 +159,12 @@ def test_counts_provided_for_only_one_image_type_and_should_recall( def test_counts_provided_for_only_one_image_type_and_should_not_recall( self, study_service, recall_service ): - gateway_action = GatewayActionFactory(appointment=study_service.appointment) - StudyFactory(source_message_id=gateway_action.id) + gateway_action = GatewayActionFactory.build( + appointment=study_service.appointment + ) + StudyFactory( + appointment=study_service.appointment, source_message_id=gateway_action.id + ) form = GatewayImageDetailsForm( QueryDict( @@ -260,8 +267,9 @@ def test_not_all_mammograms_taken_missing_reason(self): } def test_initial(self, in_progress_appointment): - gateway_action = GatewayActionFactory(appointment=in_progress_appointment) + gateway_action = GatewayActionFactory.build(appointment=in_progress_appointment) study = StudyFactory( + appointment=in_progress_appointment, source_message_id=gateway_action.id, additional_details="important note", completeness=StudyCompleteness.INCOMPLETE, diff --git a/manage_breast_screening/mammograms/tests/views/test_appointment_workflow_views.py b/manage_breast_screening/mammograms/tests/views/test_appointment_workflow_views.py index 90aae6614..6b9c29e0e 100644 --- a/manage_breast_screening/mammograms/tests/views/test_appointment_workflow_views.py +++ b/manage_breast_screening/mammograms/tests/views/test_appointment_workflow_views.py @@ -19,12 +19,8 @@ import manage_breast_screening.dicom.tests.factories as dicom_factories from manage_breast_screening.core.models import AuditLog -from manage_breast_screening.dicom.models import Study as DicomStudy from manage_breast_screening.gateway.models import GatewayAction, GatewayActionType -from manage_breast_screening.gateway.tests.factories import ( - GatewayActionFactory, - RelayFactory, -) +from manage_breast_screening.gateway.tests.factories import RelayFactory from manage_breast_screening.mammograms.forms.images.record_images_taken_form import ( RecordImagesTakenForm, ) @@ -611,11 +607,7 @@ def test_renders_response(self, clinical_user_client, reviewed_appointment): def test_marks_the_step_complete_and_redirects_to_check_info( self, _, clinical_user_client, reviewed_appointment ): - dicom_study = dicom_factories.StudyFactory() - GatewayActionFactory.create( - id=str(dicom_study.source_message_id), - appointment=reviewed_appointment, - ) + dicom_factories.StudyFactory(appointment=reviewed_appointment) response = clinical_user_client.http.post( reverse( "mammograms:gateway_images", @@ -659,15 +651,10 @@ def test_marks_the_step_complete_and_redirects_to_check_info( def test_repeat_images_redirects_to_multiple_images_page( self, _, clinical_user_client, reviewed_appointment ): - series = dicom_factories.SeriesFactory() - study = series.study + series = dicom_factories.SeriesFactory(study__appointment=reviewed_appointment) dicom_factories.ImageFactory.create_batch( 2, laterality="R", view_position="CC", series=series ) - GatewayActionFactory.create( - id=str(study.source_message_id), - appointment=reviewed_appointment, - ) response = clinical_user_client.http.post( reverse( "mammograms:gateway_images", @@ -693,11 +680,7 @@ def test_repeat_images_redirects_to_multiple_images_page( ) def test_updates_the_study(self, clinical_user_client, reviewed_appointment): - dicom_study = dicom_factories.StudyFactory() - GatewayActionFactory.create( - id=str(dicom_study.source_message_id), - appointment=reviewed_appointment, - ) + dicom_factories.StudyFactory(appointment=reviewed_appointment) clinical_user_client.http.post( reverse( "mammograms:gateway_images", @@ -716,7 +699,9 @@ def test_updates_the_study(self, clinical_user_client, reviewed_appointment): }, ) - study = DicomStudy.for_appointment(reviewed_appointment) + study = reviewed_appointment.dicom_study + study.refresh_from_db() + assert study.additional_details == "Some details about the images" assert study.imperfect_but_best_possible is False assert not study.reasons_incomplete diff --git a/manage_breast_screening/mammograms/tests/views/test_multiple_images_information_views.py b/manage_breast_screening/mammograms/tests/views/test_multiple_images_information_views.py index 2cfdad240..dbb2d8837 100644 --- a/manage_breast_screening/mammograms/tests/views/test_multiple_images_information_views.py +++ b/manage_breast_screening/mammograms/tests/views/test_multiple_images_information_views.py @@ -6,7 +6,6 @@ from pytest_django.asserts import assertInHTML, assertQuerySetEqual, assertRedirects import manage_breast_screening.dicom.tests.factories as dicom_factories -from manage_breast_screening.gateway.tests.factories import GatewayActionFactory from manage_breast_screening.mammograms.forms.multiple_images_information_form import ( MultipleImagesInformationForm, ) @@ -362,11 +361,10 @@ def test_stale_form_when_series_disappears( def test_stale_form_when_dicom_series_is_updated( self, clinical_user_client, reviewed_appointment ): - series = dicom_factories.SeriesFactory() - study = series.study - GatewayActionFactory( - id=study.source_message_id, appointment=reviewed_appointment + series = dicom_factories.SeriesFactory( + study__appointment=reviewed_appointment ) + study = series.study dicom_factories.ImageFactory.create_batch( 2, series=series, laterality="L", view_position="CC" ) diff --git a/manage_breast_screening/mammograms/tests/views/test_show_appointment_views.py b/manage_breast_screening/mammograms/tests/views/test_show_appointment_views.py index c25544c65..832cbda59 100644 --- a/manage_breast_screening/mammograms/tests/views/test_show_appointment_views.py +++ b/manage_breast_screening/mammograms/tests/views/test_show_appointment_views.py @@ -77,8 +77,10 @@ def test_renders_view_counts(self, clinical_user_client, completed_appointment): def test_renders_gateway_images_view_counts( self, _, clinical_user_client, completed_appointment ): - action = GatewayActionFactory(appointment=completed_appointment) - study = DicomStudyFactory(source_message_id=action.id) + action = GatewayActionFactory.build(appointment=completed_appointment) + study = DicomStudyFactory( + source_message_id=action.id, appointment=completed_appointment + ) DicomImageFactory(series__study=study, view_position="MLO", laterality="R") DicomImageFactory(series__study=study, view_position="CC", laterality="R") DicomImageFactory(series__study=study, view_position="MLO", laterality="L") diff --git a/manage_breast_screening/mammograms/views/appointment_workflow_views.py b/manage_breast_screening/mammograms/views/appointment_workflow_views.py index 79ac6ceba..bc4d4a1eb 100644 --- a/manage_breast_screening/mammograms/views/appointment_workflow_views.py +++ b/manage_breast_screening/mammograms/views/appointment_workflow_views.py @@ -29,7 +29,6 @@ extract_relative_redirect_url, ) from manage_breast_screening.core.views.generic import UpdateWithAuditView -from manage_breast_screening.dicom.models import Study as DicomStudy from manage_breast_screening.dicom.study_service import ( StudyService as DicomStudyService, ) @@ -226,7 +225,7 @@ def form_valid(self, form): with transaction.atomic(): form.save() WorklistItemService.create(self.appointment) - except (IntegrityError, DatabaseError): + except IntegrityError, DatabaseError: messages.add_message( self.request, messages.INFO, @@ -392,7 +391,7 @@ def form_valid(self, form): RecallService(appointment=self.appointment, current_user=self.request.user), ) - study = DicomStudy.for_appointment(self.appointment) + study = self.appointment.dicom_study if study.has_series_with_multiple_images(): return redirect( @@ -417,7 +416,7 @@ class AddMultipleImagesInformationView(WorkflowSidebarMixin, FormView): def get_study(self): try: if gateway_images_enabled(self.appointment): - return DicomStudy.for_appointment(self.appointment) + return self.appointment.dicom_study else: return self.appointment.study except Study.DoesNotExist: diff --git a/manage_breast_screening/participants/models/appointment.py b/manage_breast_screening/participants/models/appointment.py index 42897b4fe..0174fda93 100644 --- a/manage_breast_screening/participants/models/appointment.py +++ b/manage_breast_screening/participants/models/appointment.py @@ -143,6 +143,9 @@ def current_status(self) -> "AppointmentStatus": def active(self): return self.current_status.active + def is_in_progress(self): + return self.current_status.is_in_progress() + def set_status(self, status_name, created_by): current_status = self.current_status diff --git a/manage_breast_screening/tests/system/clinical/test_gateway_images.py b/manage_breast_screening/tests/system/clinical/test_gateway_images.py index e4b864c08..173a6700b 100644 --- a/manage_breast_screening/tests/system/clinical/test_gateway_images.py +++ b/manage_breast_screening/tests/system/clinical/test_gateway_images.py @@ -4,10 +4,7 @@ from playwright.sync_api import expect from manage_breast_screening.dicom.tests.factories import ImageFactory, SeriesFactory -from manage_breast_screening.gateway.tests.factories import ( - GatewayActionFactory, - RelayFactory, -) +from manage_breast_screening.gateway.tests.factories import RelayFactory from manage_breast_screening.participants.models.appointment import ( AppointmentStatusNames, AppointmentWorkflowStepCompletion, @@ -97,12 +94,8 @@ def and_i_see_the_images_troubleshooting_content(self): ).to_be_visible() def and_there_are_images_for_the_appointment(self): - series = SeriesFactory() + series = SeriesFactory(study__appointment=self.appointment) study = series.study - GatewayActionFactory.create( - appointment=self.appointment, - id=study.source_message_id, - ) ImageFactory.create(series__study=study, laterality="R", view_position="MLO") ImageFactory.create(series=series, laterality="R", view_position="CC") ImageFactory.create(series=series, laterality="R", view_position="CC")