From 1b0a23f1e7fcb3df2be500996cc462d63b8934f4 Mon Sep 17 00:00:00 2001 From: erikvw Date: Wed, 6 Dec 2023 17:58:26 -0600 Subject: [PATCH] raise on attempt to cancel actio item where reference_obj exists --- edc_action_item/exceptions.py | 4 ++++ edc_action_item/forms/action_item_form.py | 16 +++++++++++-- edc_action_item/models/action_item.py | 15 ++++++++++-- edc_action_item/site_action_items.py | 4 ++-- .../tests/tests/test_action_item.py | 23 +++++++++++++++++-- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/edc_action_item/exceptions.py b/edc_action_item/exceptions.py index 73e0d01..bce3695 100644 --- a/edc_action_item/exceptions.py +++ b/edc_action_item/exceptions.py @@ -13,6 +13,10 @@ class ActionItemError(Exception): pass +class ActionItemStatusError(Exception): + pass + + class ActionTypeError(Exception): pass diff --git a/edc_action_item/forms/action_item_form.py b/edc_action_item/forms/action_item_form.py index 7735717..a5a5160 100644 --- a/edc_action_item/forms/action_item_form.py +++ b/edc_action_item/forms/action_item_form.py @@ -1,5 +1,6 @@ from django import forms -from edc_constants.constants import NEW, OPEN +from django.apps import apps as django_apps +from edc_constants.constants import CANCELLED, NEW, OPEN from ..models import ActionItem @@ -14,13 +15,24 @@ class ActionItemForm(forms.ModelForm): def clean(self): cleaned_data = super().clean() self.force_open_status() + self.raise_if_cannot_cancel() return cleaned_data - def force_open_status(self): + def force_open_status(self) -> None: """Sets status to open for edited NEW action items.""" if self.instance.id and self.cleaned_data.get("status") == NEW: self.cleaned_data["status"] = OPEN + def raise_if_cannot_cancel(self) -> None: + if ( + self.instance.id + and self.cleaned_data.get("status") == CANCELLED + and django_apps.get_model(self.instance.reference_model) + .objects.filter(action_identifier=self.instance.action_identifier) + .exists() + ): + raise forms.ValidationError({"status": "Invalid. This action cannot be cancelled"}) + class Meta: model = ActionItem fields = "__all__" diff --git a/edc_action_item/models/action_item.py b/edc_action_item/models/action_item.py index cdc4185..f1bf2d4 100644 --- a/edc_action_item/models/action_item.py +++ b/edc_action_item/models/action_item.py @@ -7,7 +7,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.db.models.deletion import PROTECT -from edc_constants.constants import NEW +from edc_constants.constants import CANCELLED, NEW from edc_identifier.model_mixins import NonUniqueSubjectIdentifierFieldMixin from edc_model.models import BaseUuidModel, HistoricalRecords from edc_notification.model_mixins import NotificationModelMixin @@ -17,7 +17,7 @@ from .. import Action from ..choices import ACTION_STATUS, PRIORITY -from ..exceptions import SubjectDoesNotExist +from ..exceptions import ActionItemStatusError, SubjectDoesNotExist from ..identifiers import ActionIdentifier from ..site_action_items import site_action_items from .action_type import ActionType @@ -166,6 +166,17 @@ def save(self, *args, **kwargs): self.reference_model = self.action_type.reference_model self.related_reference_model = self.action_type.related_reference_model self.instructions = self.action_type.instructions + else: + if ( + self.status in [NEW, CANCELLED] + and django_apps.get_model(self.reference_model) + .objects.filter(action_identifier=self.action_identifier) + .exists() + ): + raise ActionItemStatusError( + "Invalid action item status. Reference model exists. " + f"Got `{self.get_status_display()}`. Perhaps catch this in the form" + ) super().save(*args, **kwargs) def natural_key(self) -> tuple[str]: diff --git a/edc_action_item/site_action_items.py b/edc_action_item/site_action_items.py index c5d2881..6b1caff 100644 --- a/edc_action_item/site_action_items.py +++ b/edc_action_item/site_action_items.py @@ -13,7 +13,7 @@ from edc_prn.prn import Prn from edc_prn.site_prn_forms import AlreadyRegistered as PrnAlreadyRegistered from edc_prn.site_prn_forms import site_prn_forms -from edc_sites import InvalidSiteError +from edc_sites import InvalidSiteForSubjectError from edc_sites.valid_site_for_subject_or_raise import valid_site_for_subject_or_raise from .create_or_update_action_type import create_or_update_action_type @@ -120,7 +120,7 @@ def get_by_model(self, model=None) -> Type[Action] | Type[ActionWithNotification def get_show_link_to_add_actions(self, subject_identifier: str = None) -> list[Wrapper]: try: valid_site_for_subject_or_raise(subject_identifier) - except InvalidSiteError: + except InvalidSiteForSubjectError: wrappers = [] else: names = [v.name for v in self.registry.values() if v.show_link_to_add] diff --git a/edc_action_item/tests/tests/test_action_item.py b/edc_action_item/tests/tests/test_action_item.py index 69292c4..b281997 100644 --- a/edc_action_item/tests/tests/test_action_item.py +++ b/edc_action_item/tests/tests/test_action_item.py @@ -1,11 +1,11 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models.deletion import ProtectedError from django.test import TestCase -from edc_constants.constants import CLOSED, NEW, OPEN +from edc_constants.constants import CANCELLED, CLOSED, NEW, OPEN from edc_action_item.action import Action from edc_action_item.create_or_update_action_type import create_or_update_action_type -from edc_action_item.exceptions import SubjectDoesNotExist +from edc_action_item.exceptions import ActionItemStatusError, SubjectDoesNotExist from edc_action_item.forms import ActionItemForm from edc_action_item.get_action_type import get_action_type from edc_action_item.models import ActionItem, ActionType @@ -318,3 +318,22 @@ def test_reopens_children_on_change(self): self.assertEqual(form_two.action_item.status, OPEN) self.assertEqual(form_three.action_item.status, OPEN) self.assertEqual(form_one.action_item.status, CLOSED) + + def test_cannot_cancel_if_reference_obj_exists(self): + site_action_items.register(FormOneAction) + site_action_items.register(FormTwoAction) + site_action_items.register(FormThreeAction) + + form_one_action = FormOneAction(subject_identifier=self.subject_identifier) + form_one = FormOne.objects.create( + subject_identifier=self.subject_identifier, + action_identifier=form_one_action.action_identifier, + ) + form_one.f1 = "blah" + form_one.save() + + form_one.refresh_from_db() + self.assertEqual(form_one.action_item.status, CLOSED) + + form_one.action_item.status = CANCELLED + self.assertRaises(ActionItemStatusError, form_one.action_item.save)