Files
Backend/crop_zoning/views.py
T
2026-05-13 22:29:18 +03:30

263 lines
9.6 KiB
Python

from copy import deepcopy
from uuid import UUID
from django.core.exceptions import ImproperlyConfigured
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.views import APIView
from config.swagger import code_response
from external_api_adapter.adapter import request as external_api_request
from external_api_adapter.exceptions import ExternalAPIRequestError
from farm_hub.models import FarmHub
from .serializers import (
FarmUUIDRequestSerializer,
KOptionActivateSerializer,
LocationDataUpsertSerializer,
)
from .services import (
AI_CLUSTER_RECOMMENDATIONS_PATH,
AI_LOCATION_DATA_PATH,
AI_REMOTE_SENSING_PATH,
)
AI_PROXY_ERROR_MESSAGE = "ارتباط با سرویس AI ناموفق بود."
FARM_NOT_FOUND_MESSAGE = "مزرعه پیدا نشد."
QUERY_FARM_NOT_FOUND_MESSAGE = "location پیدا نشد."
SUCCESS_RESPONSE = code_response("LocationDataGenericSuccess", data=serializers.JSONField())
ERROR_RESPONSE = code_response("LocationDataGenericError", data=serializers.JSONField())
LOCATION_DATA_QUERY_PARAMETERS = [
OpenApiParameter("lat", OpenApiTypes.NUMBER, OpenApiParameter.QUERY, required=False),
OpenApiParameter("lon", OpenApiTypes.NUMBER, OpenApiParameter.QUERY, required=False),
OpenApiParameter("farm_uuid", OpenApiTypes.UUID, OpenApiParameter.QUERY, required=False),
]
REMOTE_SENSING_QUERY_PARAMETERS = [
OpenApiParameter("farm_uuid", OpenApiTypes.UUID, OpenApiParameter.QUERY, required=True),
OpenApiParameter("page", OpenApiTypes.INT, OpenApiParameter.QUERY, required=False),
OpenApiParameter("page_size", OpenApiTypes.INT, OpenApiParameter.QUERY, required=False),
OpenApiParameter("start_date", OpenApiTypes.DATE, OpenApiParameter.QUERY, required=False),
OpenApiParameter("end_date", OpenApiTypes.DATE, OpenApiParameter.QUERY, required=False),
]
CLUSTER_BLOCK_LIVE_QUERY_PARAMETERS = [
OpenApiParameter("start_date", OpenApiTypes.DATE, OpenApiParameter.QUERY, required=False),
OpenApiParameter("end_date", OpenApiTypes.DATE, OpenApiParameter.QUERY, required=False),
OpenApiParameter("farm_uuid", OpenApiTypes.UUID, OpenApiParameter.QUERY, required=False),
]
OPTIONAL_FARM_UUID_QUERY_PARAMETER = [
OpenApiParameter("farm_uuid", OpenApiTypes.UUID, OpenApiParameter.QUERY, required=False),
]
class AILocationDataProxyView(APIView):
ai_path = AI_LOCATION_DATA_PATH
farm_uuid_locations = ()
farm_not_found_message = FARM_NOT_FOUND_MESSAGE
def _build_path(self, **kwargs):
return self.ai_path.format(**kwargs)
def _get_payload(self, request):
if not request.data:
return None
if isinstance(request.data, dict):
return deepcopy(request.data)
return request.data
def _get_query(self, request):
if not request.query_params:
return None
query = {}
for key, values in request.query_params.lists():
query[key] = values if len(values) > 1 else values[0]
return query
def _parse_uuid(self, value):
if not value:
return None
try:
return UUID(str(value))
except (TypeError, ValueError, AttributeError):
return None
def _extract_farm_uuid(self, request, payload, query):
for location in self.farm_uuid_locations:
if location == "body" and isinstance(payload, dict) and payload.get("farm_uuid"):
parsed = self._parse_uuid(payload.get("farm_uuid"))
if parsed is not None:
return parsed
if location == "query" and isinstance(query, dict) and query.get("farm_uuid"):
parsed = self._parse_uuid(query.get("farm_uuid"))
if parsed is not None:
return parsed
return None
def _ensure_farm_access(self, request, farm_uuid):
if farm_uuid is None:
return None
if FarmHub.objects.filter(farm_uuid=farm_uuid, owner=request.user).exists():
return None
return Response(
{"code": 404, "msg": self.farm_not_found_message, "data": None},
status=status.HTTP_404_NOT_FOUND,
)
def _build_proxy_error(self, exc):
return Response(
{"code": 502, "msg": AI_PROXY_ERROR_MESSAGE, "data": {"detail": str(exc)}},
status=status.HTTP_502_BAD_GATEWAY,
)
def _proxy(self, request, *, method, **path_kwargs):
payload = self._get_payload(request)
query = self._get_query(request)
farm_uuid = self._extract_farm_uuid(request, payload, query)
farm_error = self._ensure_farm_access(request, farm_uuid)
if farm_error is not None:
return farm_error
try:
adapter_response = external_api_request(
"ai",
self._build_path(**path_kwargs),
method=method,
payload=payload,
query=query,
)
except (ExternalAPIRequestError, ImproperlyConfigured) as exc:
return self._build_proxy_error(exc)
response_payload = adapter_response.data
if not isinstance(response_payload, dict):
response_payload = {
"code": adapter_response.status_code,
"msg": "success" if adapter_response.status_code < 400 else "error",
"data": response_payload,
}
return Response(response_payload, status=adapter_response.status_code)
class LocationDataView(AILocationDataProxyView):
farm_uuid_locations = ("query", "body")
farm_not_found_message = QUERY_FARM_NOT_FOUND_MESSAGE
@extend_schema(
tags=["Location Data"],
parameters=LOCATION_DATA_QUERY_PARAMETERS,
responses={200: SUCCESS_RESPONSE, 400: ERROR_RESPONSE, 404: ERROR_RESPONSE},
)
def get(self, request):
return self._proxy(request, method="GET")
@extend_schema(
tags=["Location Data"],
request=LocationDataUpsertSerializer,
responses={200: SUCCESS_RESPONSE, 400: ERROR_RESPONSE, 404: ERROR_RESPONSE},
)
def post(self, request):
return self._proxy(request, method="POST")
class LocationDataNdviHealthView(AILocationDataProxyView):
ai_path = f"{AI_LOCATION_DATA_PATH}ndvi-health/"
farm_uuid_locations = ("body",)
@extend_schema(
tags=["Location Data"],
request=FarmUUIDRequestSerializer,
responses={200: SUCCESS_RESPONSE, 400: ERROR_RESPONSE, 404: ERROR_RESPONSE},
)
def post(self, request):
return self._proxy(request, method="POST")
class LocationDataRemoteSensingView(AILocationDataProxyView):
ai_path = AI_REMOTE_SENSING_PATH
farm_uuid_locations = ("query", "body")
@extend_schema(
tags=["Location Data"],
parameters=REMOTE_SENSING_QUERY_PARAMETERS,
responses={200: SUCCESS_RESPONSE, 400: ERROR_RESPONSE, 404: ERROR_RESPONSE},
)
def get(self, request):
return self._proxy(request, method="GET")
@extend_schema(
tags=["Location Data"],
request=FarmUUIDRequestSerializer,
responses={202: SUCCESS_RESPONSE, 400: ERROR_RESPONSE, 404: ERROR_RESPONSE},
)
def post(self, request):
return self._proxy(request, method="POST")
class ClusterBlockLiveView(AILocationDataProxyView):
ai_path = f"{AI_REMOTE_SENSING_PATH}cluster-blocks/{{cluster_uuid}}/live/"
farm_uuid_locations = ("query",)
@extend_schema(
tags=["Location Data"],
parameters=CLUSTER_BLOCK_LIVE_QUERY_PARAMETERS,
responses={200: SUCCESS_RESPONSE, 400: ERROR_RESPONSE, 404: ERROR_RESPONSE, 502: ERROR_RESPONSE},
)
def get(self, request, cluster_uuid):
return self._proxy(request, method="GET", cluster_uuid=cluster_uuid)
class ClusterRecommendationsView(AILocationDataProxyView):
ai_path = AI_CLUSTER_RECOMMENDATIONS_PATH
farm_uuid_locations = ("query",)
@extend_schema(
tags=["Location Data"],
parameters=REMOTE_SENSING_QUERY_PARAMETERS[:1],
responses={200: SUCCESS_RESPONSE, 400: ERROR_RESPONSE, 404: ERROR_RESPONSE},
)
def get(self, request):
return self._proxy(request, method="GET")
class KOptionsView(AILocationDataProxyView):
ai_path = f"{AI_REMOTE_SENSING_PATH}results/{{result_id}}/k-options/"
farm_uuid_locations = ("query",)
@extend_schema(
tags=["Location Data"],
parameters=OPTIONAL_FARM_UUID_QUERY_PARAMETER,
responses={200: SUCCESS_RESPONSE, 404: ERROR_RESPONSE},
)
def get(self, request, result_id):
return self._proxy(request, method="GET", result_id=result_id)
class KOptionsActivateView(AILocationDataProxyView):
ai_path = f"{AI_REMOTE_SENSING_PATH}results/{{result_id}}/k-options/activate/"
farm_uuid_locations = ("query", "body")
@extend_schema(
tags=["Location Data"],
parameters=OPTIONAL_FARM_UUID_QUERY_PARAMETER,
request=KOptionActivateSerializer,
responses={200: SUCCESS_RESPONSE, 400: ERROR_RESPONSE, 404: ERROR_RESPONSE},
)
def post(self, request, result_id):
return self._proxy(request, method="POST", result_id=result_id)
class RunStatusView(AILocationDataProxyView):
ai_path = f"{AI_REMOTE_SENSING_PATH}runs/{{run_id}}/status/"
farm_uuid_locations = ("query",)
@extend_schema(
tags=["Location Data"],
parameters=OPTIONAL_FARM_UUID_QUERY_PARAMETER,
responses={200: SUCCESS_RESPONSE, 404: ERROR_RESPONSE},
)
def get(self, request, run_id):
return self._proxy(request, method="GET", run_id=run_id)