Coverage for backend/django/core/auxiliary/viewsets/ScenarioViewSet.py: 92%
83 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
1from core.viewset import ModelViewSet
3from django.db import transaction
4from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
5from rest_framework import status
6from rest_framework.decorators import action
7from rest_framework.response import Response
8from ..serializers.ScenarioSerializer import ScenarioSerializer, OptimizationDegreesOfFreedomSerializer
9from ..models.Scenario import (
10 Scenario,
11 OptimizationDegreesOfFreedom,
12 ScenarioInputModeEnum,
13)
14from ..services import parameter_sweep, scenario_time_series
15from core.managers import get_flowsheet_access
18class ScenarioViewSet(ModelViewSet):
19 serializer_class = ScenarioSerializer
22 def get_queryset(self):
23 queryset = Scenario.objects.all()
24 simulationObjectId = self.request.query_params.get("simulationObjectId")
25 if simulationObjectId is not None:
26 queryset = queryset.filter(simulationObject=simulationObjectId)
27 return queryset
29 @extend_schema(
30 parameters=[
31 OpenApiParameter(name="simulationObjectId", required=True, type=OpenApiTypes.INT),
32 ]
33 )
34 def list(self, request):
35 return super().list(request)
37 def _validate_sweep_mode_data(self, instance: Scenario, validated_data: dict):
38 """Reject MSS mode changes that general scenario validation cannot assess.
40 The serializer only validates the submitted fields. Parameter sweep mode
41 changes also depend on existing sweep definitions and optimisation state,
42 so this guard runs before update side effects can clear CSV or sweep rows.
43 """
44 requested_mode = validated_data.get("mss_input_mode", instance.mss_input_mode)
45 requested_optimization = validated_data.get(
46 "enable_optimization",
47 instance.enable_optimization,
48 )
49 sweep_definition_exists = hasattr(instance, "parameterSweepDefinition")
50 clearing_sweep_mode = (
51 requested_mode == ScenarioInputModeEnum.Csv
52 and (
53 instance.mss_input_mode == ScenarioInputModeEnum.ParameterSweep
54 or sweep_definition_exists
55 )
56 )
58 if (
59 requested_optimization is True
60 and (
61 requested_mode == ScenarioInputModeEnum.ParameterSweep
62 or (sweep_definition_exists and not clearing_sweep_mode)
63 )
64 ):
65 return Response(
66 {
67 "enable_optimization": (
68 "Optimisation cannot be enabled while parameter sweep data "
69 "or a sweep definition is active."
70 )
71 },
72 status=status.HTTP_400_BAD_REQUEST,
73 )
75 if ( 75 ↛ 79line 75 didn't jump to line 79 because the condition on line 75 was never true
76 requested_mode == ScenarioInputModeEnum.ParameterSweep
77 and requested_optimization is True
78 ):
79 return Response(
80 {
81 "mss_input_mode": (
82 "Parameter sweep mode cannot be enabled while optimisation is active."
83 )
84 },
85 status=status.HTTP_400_BAD_REQUEST,
86 )
88 return None
90 def _update_scenario(self, request, *, partial: bool):
91 instance = self.get_object()
92 serializer = self.get_serializer(instance, data=request.data, partial=partial)
93 serializer.is_valid(raise_exception=True)
94 requested_mode = serializer.validated_data.get("mss_input_mode")
95 previous_mode = instance.mss_input_mode
96 had_parameter_sweep_definition = hasattr(instance, "parameterSweepDefinition")
97 previous_time_series_values = scenario_time_series.mss_time_series_settings_snapshot(instance)
99 validation_response = self._validate_sweep_mode_data(
100 instance,
101 serializer.validated_data,
102 )
103 if validation_response is not None:
104 return validation_response
106 with transaction.atomic():
107 self.perform_update(serializer)
108 if requested_mode is not None:
109 parameter_sweep.clear_mss_input_data_after_mode_switch(
110 serializer.instance,
111 previous_mode=previous_mode,
112 requested_mode=requested_mode,
113 had_parameter_sweep_definition=had_parameter_sweep_definition,
114 )
115 if scenario_time_series.mss_time_series_settings_changed(
116 serializer.instance,
117 previous_values=previous_time_series_values,
118 ):
119 scenario_time_series.handle_mss_time_series_configuration_changed(serializer.instance)
121 if getattr(instance, "_prefetched_objects_cache", None): 121 ↛ 122line 121 didn't jump to line 122 because the condition on line 121 was never true
122 instance._prefetched_objects_cache = {}
124 instance.refresh_from_db()
125 return Response(self.get_serializer(instance).data)
127 def partial_update(self, request, *args, **kwargs):
128 return self._update_scenario(request, partial=True)
130 def update(self, request, *args, **kwargs):
131 return self._update_scenario(request, partial=False)
133 @extend_schema(
134 operation_id="core_scenario_eligible_parameter_sweep_targets_list",
135 responses=parameter_sweep.EligibleParameterSweepTargetsResponse,
136 )
137 @action(detail=True, methods=["get"], url_path="eligible_parameter_sweep_targets")
138 def eligible_parameter_sweep_targets(self, request, pk=None):
139 scenario = self.get_object()
140 targets = parameter_sweep.eligible_parameter_sweep_targets(
141 scenario.flowsheet_state.flowsheet_id
142 )
143 return Response(
144 parameter_sweep.EligibleParameterSweepTargetsResponse(targets).model_dump(
145 mode="json",
146 )
147 )
149 @extend_schema(
150 methods=["POST"],
151 request=parameter_sweep.ParameterSweepRequest,
152 responses=parameter_sweep.ParameterSweepPreviewResponse,
153 )
154 @action(detail=True, methods=["post"], url_path="parameter_sweep_preview")
155 def parameter_sweep_preview(self, request, pk=None):
156 scenario = self.get_object()
157 preview = parameter_sweep.preview_parameter_sweep(scenario, request.data)
158 return Response(preview.model_dump(mode="json"))
160 @extend_schema(
161 request=parameter_sweep.ParameterSweepRequest,
162 responses=parameter_sweep.ParameterSweepGenerateResponse,
163 )
164 @action(detail=True, methods=["post"], url_path="generate_parameter_sweep")
165 def generate_parameter_sweep(self, request, pk=None):
166 scenario = self.get_object()
167 access_state = get_flowsheet_access(
168 request.user,
169 scenario.flowsheet_state.flowsheet_id,
170 )
171 if access_state.has_read_access and not access_state.has_write_access: 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true
172 return Response(
173 {"error": "This flowsheet is shared with read-only access."},
174 status=status.HTTP_403_FORBIDDEN,
175 )
176 if scenario.enable_optimization: 176 ↛ 177line 176 didn't jump to line 177 because the condition on line 176 was never true
177 return Response(
178 {
179 "enable_optimization": (
180 "Parameter sweep generation is not available while optimisation is enabled."
181 )
182 },
183 status=status.HTTP_400_BAD_REQUEST,
184 )
186 result = parameter_sweep.generate_parameter_sweep(
187 scenario,
188 request.data,
189 )
190 return Response(result.model_dump(mode="json"))
193class OptimizationDegreesOfFreedomViewSet(ModelViewSet):
194 serializer_class = OptimizationDegreesOfFreedomSerializer
196 def get_queryset(self):
197 return OptimizationDegreesOfFreedom.objects.all()