From bfd75d995983a6a86e24356d4ac9635afa5681d9 Mon Sep 17 00:00:00 2001 From: Zalo Date: Fri, 20 Dec 2024 13:55:23 -0500 Subject: [PATCH] Added custom serializer class and services function --- src/planscape/impacts/serializers.py | 11 ++++ src/planscape/impacts/services.py | 53 ++++++++++++++++- src/planscape/impacts/views.py | 88 +++++++++++----------------- 3 files changed, 95 insertions(+), 57 deletions(-) diff --git a/src/planscape/impacts/serializers.py b/src/planscape/impacts/serializers.py index 277b04aa5..6a1ae035e 100644 --- a/src/planscape/impacts/serializers.py +++ b/src/planscape/impacts/serializers.py @@ -317,3 +317,14 @@ class TreatmentResultsPlotSerializer(serializers.Serializer): metrics = serializers.ListField( child=serializers.CharField(max_length=20), help_text="Metrics." ) + + +from rest_framework import serializers +from stands.models import Stand + + +class StandQuerySerializer(serializers.Serializer): + # Use PrimaryKeyRelatedField to ensure stand_id is valid PK from Stand model + stand_id = serializers.PrimaryKeyRelatedField( + queryset=Stand.objects.all(), required=True + ) diff --git a/src/planscape/impacts/services.py b/src/planscape/impacts/services.py index eda0e4928..b3596c78b 100644 --- a/src/planscape/impacts/services.py +++ b/src/planscape/impacts/services.py @@ -530,9 +530,56 @@ def calculate_project_area_deltas( return results +from impacts.models import TreatmentResult, ImpactVariable +def get_treatment_results_table_data(treatment_plan, stand_id): + """ + Retrieves treatment results for RATE_OF_SPREAD and FLAME_LENGTH for the given stand and treatment plan. + Return a list of dictionaries: [{"year": ..., "rate_of_spread": ..., "flame_length": ...}, ...] + """ + # Filter results for the given stand and plan, for the two variables we need + queryset = TreatmentResult.objects.filter( + treatment_plan=treatment_plan, + stand_id=stand_id, + variable__in=[ImpactVariable.RATE_OF_SPREAD, ImpactVariable.FLAME_LENGTH], + ) + + # If no results, return an empty list + if not queryset.exists(): + return [] + + # Extract distinct years + years = queryset.values_list("year", flat=True).distinct().order_by("year") + + # Build table rows + table_data = [] + for year in years: + # For each year, get the corresponding records + year_results = queryset.filter(year=year) + + # Extract the rate_of_spread value + rate_of_spread_val = ( + year_results.filter(variable=ImpactVariable.RATE_OF_SPREAD) + .values_list("value", flat=True) + .first() + ) + + # Extract the flame_length value + flame_length_val = ( + year_results.filter(variable=ImpactVariable.FLAME_LENGTH) + .values_list("Value", flat=True) + .first() + ) + + # Add a dictionary -- table row -- for each avaialble year + table_data.append( + { + "year": year, + "rate_of_spread": rate_of_spread_val, + "flame_length": flame_length_val, + } + ) -def get_treatment_result_for_stand(): - # Create logic to retrieve desired stand data from Planscape database tables - pass \ No newline at end of file + # returns list of rows to view + return table_data diff --git a/src/planscape/impacts/views.py b/src/planscape/impacts/views.py index 887d2b1da..4972f54c5 100644 --- a/src/planscape/impacts/views.py +++ b/src/planscape/impacts/views.py @@ -257,74 +257,54 @@ def plot(self, request, pk=None): ) return Response(data=data_to_plot, status=status.HTTP_200_OK) - - - # Import endpoint database logic from impacts/services.py function - from impacts.services import get_treatment_result_for_stand + # Imports + from rest_framework import status, response + from rest_framework.decorators import action + from drf_spectacular.utils import extend_schema + from planscape.serializers import BaseErrorMessageSerializer + from impacts.serializers import StandQuerySerializer + from impacts.services import get_treatment_results_table_data + from impacts.models import TreatmentResult, ImpactVariable @extend_schema( - description = "Retrieve treatment result information for a specific stand.", + description="Retrieve treatment result information for a specific stand.", responses={ - 200: TreatmentResultSerializer, # Successful response with serialized data - 404: BaseErrorMessageSerializer, # Stand not found - 400: BaseErrorMessageSerializer, # Missing or invalid stand_id + 200: TreatmentResultSerializer, # Successful response with serialized data + 404: BaseErrorMessageSerializer, # Stand not found + 400: BaseErrorMessageSerializer, # Missing or invalid stand_id }, ) - @action( - detail = True, # creates custom endpoint applied to stands in specific treatment plan (pk) - methods = ["get"], # sets custom endpoint to only respond to HTTP GET requests - filterset_class = None, # No additional filtering needed + detail=True, # creates custom endpoint applied to stands in specific treatment plan (pk) + methods=["get"], # sets custom endpoint to only respond to HTTP GET requests + filterset_class=None, # No additional filtering needed + url_path="stand-treatment-results", # The custom endpoint name appended to the URL ) - - def stand_treatment_results(self, request): + def stand_treatment_results(self, request, pk=None): """ Endpoint to retrieve treatment results for a specific stand ID. """ - # Get stand id from query parameters (re: as a string from URL of HTTP request) - stand_id = request.query_params.get("stand_id") - - # Check if stand id is provided (re: not none, empty, etc.) - if not stand_id: - return response.Response( - {"detail": "stand_id is required."}, - status = status.HTTP_400_BAD_REQUEST, - ) - - # Check if stand is a valid integer - try: - stand_id = int(stand_id) - except ValueError: - return response.Response( - {"detail": "stand_id must be an integer."}, - status = status.HTTP_400_BAD_REQUEST, - ) - - # Get treatment plan id using primary key, pk (re: from the URL of HTTP request) - try: - treatment_plan = self.get_object() - except TreatmentPlan.DoesNotExist: - return response.Response( - {"detail": "stand_id must be an integer."}, - status = status.HTTP_400_BAD_REQUEST, - ) - - # Use function from impacts/services.py to get stand treatment result - try: - result = get_treatment_result_for_stand(treatment_plan, stand_id) - except ProjectAreaTreatmentResult.DoesNotExist: - return response.Response( - {"detail": "Stand treatment results not found in this treatment plan."}, - status = status.HTTP_404_NOT_FOUND, - ) - - # Serialize the stand treatment result and return serialized response - serializer = TreatmentResultSerializer(result) - return response.Response(serializer.data, status = status.HTTP_200_OK) + # Validates stand id by putting stand id from URL into custom serializer, then creates stand object + serializer = StandQuerySerializer(data=request.query_params) + serializer.is_valid(raise_exception=True) + stand = serializer.validated_data["stand_id"] + # Retrieves the treatment plan, using the PK from the URL + treatment_plan = self.get_object() + # Gets table data from the service function, returning error if not found + table_data = get_treatment_results_table_data(treatment_plan, stand.id) + if not table_data: + return response.Response( + { + "detail": "No treatment results found for this stand in the given treatment plan." + }, + status=status.HTTP_404_NOT_FOUND, + ) + # Returns the data in the correct JSON format with a 200 OK status + return response.Response(table_data, status=status.HTTP_200_OK) @extend_schema_view(