diff --git a/rocky/reports/serializers.py b/rocky/reports/serializers.py index 9f037ec138e..bb1cb126121 100644 --- a/rocky/reports/serializers.py +++ b/rocky/reports/serializers.py @@ -20,7 +20,7 @@ def to_representation(self, instance): class ReportRecipeSerializer(serializers.Serializer): - id = serializers.UUIDField(source="recipe_id", read_only=True) + id = serializers.UUIDField(source="recipe_id", required=False) report_name_format = serializers.CharField() subreport_name_format = serializers.CharField(required=False, allow_blank=True) diff --git a/rocky/reports/viewsets.py b/rocky/reports/viewsets.py index 365faf1e3f6..6d055cb9b43 100644 --- a/rocky/reports/viewsets.py +++ b/rocky/reports/viewsets.py @@ -90,9 +90,7 @@ def get_object(self) -> ReportRecipe: return recipe - def get_schedule_id(self) -> str | None: - pk = self.kwargs["pk"] - + def get_schedule_id(self, pk: str) -> str | None: filters = {"filters": [{"column": "data", "field": "report_recipe_id", "operator": "eq", "value": pk}]} response = self.scheduler_client.post_schedule_search(filters) @@ -103,10 +101,25 @@ def get_schedule_id(self) -> str | None: return str(response.results[0].id) def perform_create(self, serializer: ReportRecipeSerializer) -> None: - deadline_at = serializer.validated_data.pop("start_date", datetime.now(timezone.utc).date().isoformat()) - recipe_id = uuid4() + data = serializer.validated_data + + deadline_at = data.pop("start_date", None) + + update = False + if "recipe_id" in data: + # Update the already existing recipe if a recipe with this id already exists. + try: + self.octopoes_api_connector.get( + Reference.from_str(f"ReportRecipe|{data['recipe_id']}"), valid_time=self.valid_time + ) + except ObjectNotFoundException: + pass + else: + update = True + else: + data["recipe_id"] = uuid4() - report_recipe = ReportRecipe.model_validate({"recipe_id": recipe_id, **serializer.validated_data}) + report_recipe = ReportRecipe.model_validate(data) create_ooi( api_connector=self.octopoes_api_connector, @@ -115,24 +128,39 @@ def perform_create(self, serializer: ReportRecipeSerializer) -> None: observed_at=self.valid_time, ) - report_task = ReportTask( - organisation_id=self.organization.code, report_recipe_id=str(report_recipe.recipe_id) - ).model_dump() - - schedule_request = ScheduleRequest( - scheduler_id=f"report-{self.organization.code}", - data=report_task, - schedule=report_recipe.cron_expression, - deadline_at=deadline_at, - ) + if update: + schedule_id = self.get_schedule_id(str(data["recipe_id"])) + if not schedule_id: + raise APIException("Schedule for recipe does not exist") + + if deadline_at: + self.scheduler_client.patch_schedule( + schedule_id, params={"schedule": report_recipe.cron_expression, "deadline_at": deadline_at} + ) + else: + self.scheduler_client.patch_schedule(schedule_id, params={"schedule": report_recipe.cron_expression}) + else: + report_task = ReportTask( + organisation_id=self.organization.code, report_recipe_id=str(report_recipe.recipe_id) + ).model_dump() + + if not deadline_at: + deadline_at = datetime.now(timezone.utc).date().isoformat() + + schedule_request = ScheduleRequest( + scheduler_id=f"report-{self.organization.code}", + data=report_task, + schedule=report_recipe.cron_expression, + deadline_at=deadline_at, + ) - self.scheduler_client.post_schedule(schedule=schedule_request) + self.scheduler_client.post_schedule(schedule=schedule_request) # This will make DRF return the new instance with the generated id serializer.instance = report_recipe def perform_update(self, serializer: ReportRecipeSerializer) -> None: - schedule_id = self.get_schedule_id() + schedule_id = self.get_schedule_id(self.kwargs["pk"]) if not schedule_id: raise APIException("Schedule for recipe does not exist") @@ -154,7 +182,7 @@ def perform_update(self, serializer: ReportRecipeSerializer) -> None: serializer.instance = report_recipe def perform_destroy(self, instance: ReportRecipe) -> None: - schedule_id = self.get_schedule_id() + schedule_id = self.get_schedule_id(self.kwargs["pk"]) # If we would return an error here this would mean we can never delete a # recipe in octopoes that doesn't have a schedule anymore. This could