diff --git a/backend/composer/api/views.py b/backend/composer/api/views.py index c6406eb2..92ee2459 100644 --- a/backend/composer/api/views.py +++ b/backend/composer/api/views.py @@ -77,13 +77,12 @@ def assign_owner(self, request, pk=None): instance.assign_owner(request) serializer = self.get_serializer(instance) return Response(serializer.data) - + def retrieve(self, request, *args, **kwargs): self.get_object().auto_assign_owner(request) return super().retrieve(request, *args, **kwargs) - class TagMixin(viewsets.GenericViewSet): @extend_schema( parameters=[ @@ -321,7 +320,7 @@ class NoteViewSet(viewsets.ModelViewSet): queryset = Note.objects.all() serializer_class = NoteSerializer permission_classes = [ - permissions.IsAuthenticatedOrReadOnly, + permissions.IsAuthenticatedOrReadOnly, ] filterset_class = NoteFilter @@ -392,16 +391,23 @@ def update(self, request, *args, **kwargs): origin_ids = request.data.pop("origins", None) graph_rendering_state_data = request.data.pop("graph_rendering_state", None) + # Call the UpdateModelMixin's update response = super().update(request, *args, **kwargs) if response.status_code == status.HTTP_200_OK: instance = self.get_object() + + # Handle custom updates self.handle_graph_rendering_state( instance, graph_rendering_state_data, request.user ) if origin_ids is not None: instance.set_origins(origin_ids) + # Re-serialize the instance with all modifications + serializer = self.get_serializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + return response @extend_schema( @@ -412,14 +418,21 @@ def update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs): graph_rendering_state_data = request.data.pop("graph_rendering_state", None) + # Call the UpdateModelMixin's partial_update response = super().partial_update(request, *args, **kwargs) if response.status_code == status.HTTP_200_OK: instance = self.get_object() + + # Handle custom updates self.handle_graph_rendering_state( instance, graph_rendering_state_data, request.user ) + # Re-serialize the instance with all modifications + serializer = self.get_serializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + return response diff --git a/backend/composer/signals.py b/backend/composer/signals.py index 5b559cba..ec780213 100644 --- a/backend/composer/signals.py +++ b/backend/composer/signals.py @@ -5,8 +5,18 @@ from django_fsm.signals import post_transition from .enums import CSState, NoteType -from .models import ConnectivityStatement, ExportBatch, Note, Sentence, \ - AnatomicalEntity, Layer, Region +from .models import ( + ConnectivityStatement, + Destination, + ExportBatch, + GraphRenderingState, + Note, + Sentence, + AnatomicalEntity, + Layer, + Region, + Via, +) from .services.export_services import compute_metrics, ConnectivityStatementStateService @@ -42,8 +52,8 @@ def post_transition_callback(sender, instance, name, source, target, **kwargs): def post_transition_cs(sender, instance, name, source, target, **kwargs): if issubclass(sender, ConnectivityStatement): if target == CSState.COMPOSE_NOW and source in ( - CSState.NPO_APPROVED, - CSState.EXPORTED, + CSState.NPO_APPROVED, + CSState.EXPORTED, ): # add important tag to CS when transition to COMPOSE_NOW from NPO Approved or Exported instance = ConnectivityStatementStateService.add_important_tag(instance) @@ -70,3 +80,87 @@ def delete_associated_entities(sender, instance, **kwargs): # Delete the associated region_layer if it exists if instance.region_layer: instance.region_layer.delete() + + +# Signals for ConnectivityStatement origins +@receiver(m2m_changed, sender=ConnectivityStatement.origins.through) +def connectivity_statement_origins_changed(sender, instance, action, **kwargs): + if action in ["post_add", "post_remove", "post_clear"]: + try: + instance.graph_rendering_state.delete() + except GraphRenderingState.DoesNotExist: + pass + except ValueError: + pass + + +# Signals for Via anatomical_entities +@receiver(m2m_changed, sender=Via.anatomical_entities.through) +def via_anatomical_entities_changed(sender, instance, action, **kwargs): + if action in ["post_add", "post_remove", "post_clear"]: + try: + instance.connectivity_statement.graph_rendering_state.delete() + except GraphRenderingState.DoesNotExist: + pass + except ValueError: + pass + + +# Signals for Via from_entities +@receiver(m2m_changed, sender=Via.from_entities.through) +def via_from_entities_changed(sender, instance, action, **kwargs): + if action in ["post_add", "post_remove", "post_clear"]: + try: + instance.connectivity_statement.graph_rendering_state.delete() + except GraphRenderingState.DoesNotExist: + pass + except ValueError: + pass + + +# Signals for Destination anatomical_entities +@receiver(m2m_changed, sender=Destination.anatomical_entities.through) +def destination_anatomical_entities_changed(sender, instance, action, **kwargs): + if action in ["post_add", "post_remove", "post_clear"]: + try: + instance.connectivity_statement.graph_rendering_state.delete() + except GraphRenderingState.DoesNotExist: + pass + except ValueError: + pass + + +# Signals for Destination from_entities +@receiver(m2m_changed, sender=Destination.from_entities.through) +def destination_from_entities_changed(sender, instance, action, **kwargs): + if action in ["post_add", "post_remove", "post_clear"]: + try: + instance.connectivity_statement.graph_rendering_state.delete() + except GraphRenderingState.DoesNotExist: + pass + except ValueError: + pass + + +# Signals for Via model changes +@receiver(post_save, sender=Via) +@receiver(post_delete, sender=Via) +def via_changed(sender, instance, **kwargs): + try: + instance.connectivity_statement.graph_rendering_state.delete() + except GraphRenderingState.DoesNotExist: + pass + except ValueError: + pass + + +# Signals for Destination model changes +@receiver(post_save, sender=Destination) +@receiver(post_delete, sender=Destination) +def destination_changed(sender, instance, **kwargs): + try: + instance.connectivity_statement.graph_rendering_state.delete() + except GraphRenderingState.DoesNotExist: + pass + except ValueError: + pass diff --git a/frontend/src/components/Forms/StatementForm.tsx b/frontend/src/components/Forms/StatementForm.tsx index d21494a4..81c0120d 100644 --- a/frontend/src/components/Forms/StatementForm.tsx +++ b/frontend/src/components/Forms/StatementForm.tsx @@ -312,6 +312,7 @@ const StatementForm = (props: any) => { entityId: getConnectionId(formId, statement.vias), entityType: "via", propertyToUpdate: "anatomical_entities", + refreshStatement }); }, errors: "", @@ -354,6 +355,7 @@ const StatementForm = (props: any) => { entityId: getConnectionId(formId, statement.vias), entityType: "via", propertyToUpdate: "from_entities", + refreshStatement }); }, areConnectionsExplicit: (formId: any) => { @@ -489,6 +491,7 @@ const StatementForm = (props: any) => { entityId: getConnectionId(formId, statement?.destinations), entityType: "destination", propertyToUpdate: "anatomical_entities", + refreshStatement }); }, errors: "", @@ -534,6 +537,7 @@ const StatementForm = (props: any) => { entityId: getConnectionId(formId, statement?.destinations), entityType: "destination", propertyToUpdate: "from_entities", + refreshStatement }); }, areConnectionsExplicit: (formId: any) => { diff --git a/frontend/src/services/CustomDropdownService.ts b/frontend/src/services/CustomDropdownService.ts index a9ad507f..1c856364 100644 --- a/frontend/src/services/CustomDropdownService.ts +++ b/frontend/src/services/CustomDropdownService.ts @@ -1,6 +1,6 @@ import { Option } from "../types"; import { composerApi as api } from "./apis"; -import {autocompleteRows, ChangeRequestStatus} from "../helpers/settings"; +import { autocompleteRows, ChangeRequestStatus } from "../helpers/settings"; import { convertToConnectivityStatementUpdate, getViasGroupLabel, @@ -18,10 +18,10 @@ import { PatchedVia, ViaSerializerDetails, } from "../apiclient/backend"; -import {searchAnatomicalEntities} from "../helpers/helpers"; +import { searchAnatomicalEntities } from "../helpers/helpers"; import connectivityStatementService from "./StatementService"; import statementService from "./StatementService"; -import {checkOwnership, getOwnershipAlertMessage} from "../helpers/ownershipAlert"; +import { checkOwnership, getOwnershipAlertMessage } from "../helpers/ownershipAlert"; export async function getAnatomicalEntities( searchValue: string, @@ -53,7 +53,9 @@ export async function updateOrigins( }; try { - return await statementService.partialUpdate(statementId, patchedStatement); + const response = await statementService.partialUpdate(statementId, patchedStatement); + setStatement(response); + return response; } catch (error) { alert(`Error updating origins: ${error}`); } @@ -65,6 +67,7 @@ export type UpdateEntityParams = { entityId: number | null; entityType: "via" | "destination"; propertyToUpdate: "anatomical_entities" | "from_entities"; + refreshStatement: () => void; }; const apiFunctionMap = { @@ -80,6 +83,7 @@ export async function updateEntity({ entityId, entityType, propertyToUpdate, + refreshStatement, }: UpdateEntityParams) { if (entityId == null) { alert(`Error updating ${entityType}`); @@ -96,6 +100,7 @@ export async function updateEntity({ try { if (entityId != null) { await updateFunction(entityId, patchObject); + refreshStatement() } } catch (error) { // Ownership error occurred, trigger ownership check @@ -231,9 +236,9 @@ export async function searchForwardConnection( try { const forwardConnectionOrigins = statement.destinations?.flatMap( - (destination) => - destination.anatomical_entities?.map((entity) => entity.id) ?? [], - ) ?? []; + (destination) => + destination.anatomical_entities?.map((entity) => entity.id) ?? [], + ) ?? []; if (forwardConnectionOrigins.length === 0) { return [] } @@ -262,16 +267,16 @@ export async function searchForwardConnection( const sameSentenceOptions = sameRes.results ? mapConnectivityStatementsToOptions( - sameRes.results.filter((res) => res.id !== statement.id), - forwardConnectionGroups.sameSentence, - ) + sameRes.results.filter((res) => res.id !== statement.id), + forwardConnectionGroups.sameSentence, + ) : []; const differentSentenceOptions = diffRes.results ? mapConnectivityStatementsToOptions( - diffRes.results, - forwardConnectionGroups.otherSentence, - ) + diffRes.results, + forwardConnectionGroups.otherSentence, + ) : []; return [...sameSentenceOptions, ...differentSentenceOptions];