UPDATE
This commit is contained in:
+31
-209
@@ -1,11 +1,5 @@
|
||||
"""
|
||||
Crop Zoning API views.
|
||||
No database. All responses are static mock data.
|
||||
Response format: {"status": "success", "data": <payload>}. HTTP 200 only.
|
||||
No processing, validation, or use of input parameters in responses.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import Http404
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
@@ -13,271 +7,99 @@ from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from config.swagger import status_response
|
||||
from .mock_data import (
|
||||
AREA_RESPONSE_DATA,
|
||||
PRODUCTS_RESPONSE_DATA,
|
||||
ZONE_DETAILS_BY_ID,
|
||||
ZONES_CULTIVATION_RISK_RESPONSE_DATA,
|
||||
ZONES_INITIAL_RESPONSE_DATA,
|
||||
ZONES_SOIL_QUALITY_RESPONSE_DATA,
|
||||
ZONES_WATER_NEED_RESPONSE_DATA,
|
||||
from .services import (
|
||||
create_zones_and_dispatch,
|
||||
get_cultivation_risk_payload,
|
||||
get_default_area_feature,
|
||||
get_initial_zones_payload,
|
||||
get_latest_area_payload,
|
||||
get_products_payload,
|
||||
get_soil_quality_payload,
|
||||
get_water_need_payload,
|
||||
get_zone_details_payload,
|
||||
)
|
||||
from .services import persist_zones
|
||||
|
||||
|
||||
class AreaView(APIView):
|
||||
"""
|
||||
GET endpoint for fixed land area (GeoJSON polygon).
|
||||
|
||||
Purpose:
|
||||
Returns static land area polygon for display on map. User cannot
|
||||
draw or edit the region; it is loaded from backend.
|
||||
|
||||
Input parameters:
|
||||
None.
|
||||
|
||||
Response structure:
|
||||
- status: string, always "success".
|
||||
- data: object with key "area" (GeoJSON Feature with Polygon geometry).
|
||||
|
||||
No processing or validation is performed on inputs.
|
||||
"""
|
||||
|
||||
@extend_schema(
|
||||
tags=["Crop Zoning"],
|
||||
responses={200: status_response("CropZoningAreaResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": AREA_RESPONSE_DATA},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response({"status": "success", "data": get_latest_area_payload()}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProductsView(APIView):
|
||||
"""
|
||||
GET endpoint for list of crop products and colors.
|
||||
|
||||
Purpose:
|
||||
Returns static list of cultivable products with display color and
|
||||
Persian label for the Crop Zoning page (Legend and zone detail panel).
|
||||
Used when loading the crop-zoning page.
|
||||
|
||||
Input parameters:
|
||||
- locale: string, optional. Location: query. Language code (e.g. fa, en).
|
||||
Not read or used in response.
|
||||
|
||||
Response structure:
|
||||
- status: string, always "success".
|
||||
- data: object with key "products" (array of { id, label, color }).
|
||||
|
||||
No processing or validation is performed on inputs.
|
||||
"""
|
||||
|
||||
@extend_schema(
|
||||
tags=["Crop Zoning"],
|
||||
responses={200: status_response("CropZoningProductsResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": PRODUCTS_RESPONSE_DATA},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response({"status": "success", "data": get_products_payload()}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ZonesInitialView(APIView):
|
||||
"""
|
||||
POST endpoint for initial zone data (map + hover/tooltip).
|
||||
|
||||
Purpose:
|
||||
Accepts the main area polygon and creates zones based on configured
|
||||
area chunk size. Stores generated zones in database.
|
||||
|
||||
Input parameters (body, JSON):
|
||||
- area: GeoJSON Feature. Location: body. Main land polygon.
|
||||
If omitted, the static area from mock data is used.
|
||||
|
||||
Response structure:
|
||||
- status: string.
|
||||
- data: object with total_area_hectares, total_area_sqm, zone_count,
|
||||
chunk_area_sqm and zones.
|
||||
"""
|
||||
|
||||
@extend_schema(
|
||||
tags=["Crop Zoning"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: status_response("CropZoningZonesInitialResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def post(self, request):
|
||||
area_feature = request.data.get("area") or AREA_RESPONSE_DATA.get("area")
|
||||
area_feature = request.data.get("area") or request.data.get("area_geojson") or get_default_area_feature()
|
||||
|
||||
try:
|
||||
zoning_result = persist_zones(area_feature)
|
||||
crop_area, _zones = create_zones_and_dispatch(area_feature)
|
||||
except ValueError as exc:
|
||||
return Response(
|
||||
{"status": "error", "message": str(exc)},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return Response({"status": "error", "message": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except ImproperlyConfigured as exc:
|
||||
return Response(
|
||||
{"status": "error", "message": str(exc)},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
area_data = zoning_result["area"]
|
||||
response_data = {
|
||||
"area": {
|
||||
"id": area_data["id"],
|
||||
"uuid": area_data["uuid"],
|
||||
"geometry": area_data["geometry"],
|
||||
"points": area_data["points"],
|
||||
"center": area_data["center"],
|
||||
"area_sqm": round(area_data["area_sqm"], 2),
|
||||
"area_hectares": round(area_data["area_hectares"], 4),
|
||||
"chunk_area_sqm": round(area_data["chunk_area_sqm"], 2),
|
||||
"zone_count": area_data["zone_count"],
|
||||
},
|
||||
"zones": [
|
||||
{
|
||||
"zoneId": zone["zone_id"],
|
||||
"geometry": zone["geometry"],
|
||||
"points": zone["points"],
|
||||
"center": zone["center"],
|
||||
"area_sqm": round(zone["area_sqm"], 2),
|
||||
"area_hectares": round(zone["area_hectares"], 4),
|
||||
}
|
||||
for zone in zoning_result["zones"]
|
||||
],
|
||||
}
|
||||
|
||||
return Response(
|
||||
{"status": "success", "data": response_data},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response({"status": "error", "message": str(exc)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
return Response({"status": "success", "data": get_initial_zones_payload(crop_area)}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ZonesWaterNeedView(APIView):
|
||||
"""
|
||||
POST endpoint for water need per zone (water need layer).
|
||||
|
||||
Purpose:
|
||||
Accepts zones (FeatureCollection) and returns static water need
|
||||
per zone for the water need map layer (level, value, color).
|
||||
|
||||
Input parameters (body, JSON):
|
||||
- zones: GeoJSON FeatureCollection. Location: body.
|
||||
- products: array of strings, optional. Location: body. Not used.
|
||||
|
||||
Response structure:
|
||||
- status: string, always "success".
|
||||
- data: object with zones (array of { zoneId, geometry, level, value, color }).
|
||||
|
||||
No processing or validation is performed on inputs.
|
||||
"""
|
||||
|
||||
@extend_schema(
|
||||
tags=["Crop Zoning"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: status_response("CropZoningZonesWaterNeedResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def post(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": ZONES_WATER_NEED_RESPONSE_DATA},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
zone_ids = request.data.get("zoneIds")
|
||||
return Response({"status": "success", "data": get_water_need_payload(zone_ids)}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ZonesSoilQualityView(APIView):
|
||||
"""
|
||||
POST endpoint for soil quality per zone (soil quality layer).
|
||||
|
||||
Purpose:
|
||||
Accepts zones (FeatureCollection) and returns static soil quality
|
||||
per zone for the soil quality map layer (level, score, color).
|
||||
|
||||
Input parameters (body, JSON):
|
||||
- zones: GeoJSON FeatureCollection. Location: body.
|
||||
- products: array of strings, optional. Location: body. Not used.
|
||||
|
||||
Response structure:
|
||||
- status: string, always "success".
|
||||
- data: object with zones (array of { zoneId, geometry, level, score, color }).
|
||||
|
||||
No processing or validation is performed on inputs.
|
||||
"""
|
||||
|
||||
@extend_schema(
|
||||
tags=["Crop Zoning"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: status_response("CropZoningZonesSoilQualityResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def post(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": ZONES_SOIL_QUALITY_RESPONSE_DATA},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
zone_ids = request.data.get("zoneIds")
|
||||
return Response({"status": "success", "data": get_soil_quality_payload(zone_ids)}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ZonesCultivationRiskView(APIView):
|
||||
"""
|
||||
POST endpoint for cultivation risk per zone (cultivation risk layer).
|
||||
|
||||
Purpose:
|
||||
Accepts zones (FeatureCollection) and returns static cultivation
|
||||
risk per zone for the risk map layer (level, color).
|
||||
|
||||
Input parameters (body, JSON):
|
||||
- zones: GeoJSON FeatureCollection. Location: body.
|
||||
- products: array of strings, optional. Location: body. Not used.
|
||||
|
||||
Response structure:
|
||||
- status: string, always "success".
|
||||
- data: object with zones (array of { zoneId, geometry, level, color }).
|
||||
|
||||
No processing or validation is performed on inputs.
|
||||
"""
|
||||
|
||||
@extend_schema(
|
||||
tags=["Crop Zoning"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: status_response("CropZoningZonesCultivationRiskResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def post(self, request):
|
||||
return Response(
|
||||
{"status": "success", "data": ZONES_CULTIVATION_RISK_RESPONSE_DATA},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
zone_ids = request.data.get("zoneIds")
|
||||
return Response({"status": "success", "data": get_cultivation_risk_payload(zone_ids)}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ZoneDetailsView(APIView):
|
||||
"""
|
||||
GET endpoint for zone detail data (detail panel after click).
|
||||
|
||||
Purpose:
|
||||
Returns static detail data for a single zone: reason, criteria,
|
||||
area_hectares for the detail panel and radar chart.
|
||||
|
||||
Input parameters:
|
||||
- zoneId: string. Location: path. Zone identifier (e.g. zone-0).
|
||||
Not read or used in response.
|
||||
|
||||
Response structure:
|
||||
- status: string, always "success".
|
||||
- data: object with zoneId, crop, matchPercent, waterNeed,
|
||||
estimatedProfit, reason, criteria (array), area_hectares.
|
||||
|
||||
No processing or validation is performed on inputs. Input values are
|
||||
not used in the response.
|
||||
"""
|
||||
|
||||
@extend_schema(
|
||||
tags=["Crop Zoning"],
|
||||
responses={200: status_response("CropZoningZoneDetailsResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def get(self, request, zone_id):
|
||||
data = ZONE_DETAILS_BY_ID.get(zone_id, ZONE_DETAILS_BY_ID["zone-0"])
|
||||
return Response(
|
||||
{"status": "success", "data": data},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
try:
|
||||
data = get_zone_details_payload(zone_id)
|
||||
except Exception as exc:
|
||||
if exc.__class__.__name__ == "DoesNotExist":
|
||||
raise Http404("Zone not found")
|
||||
raise
|
||||
return Response({"status": "success", "data": data}, status=status.HTTP_200_OK)
|
||||
|
||||
Reference in New Issue
Block a user