Remove unused __init__.py file and update mock data to include new endpoints for area, products, and zones, along with their respective response structures. Refactor views to implement new API endpoints and adjust Postman collection to reflect these changes.

This commit is contained in:
2026-02-26 00:38:46 +03:30
parent 2a77f90ccd
commit 4c5b1298a0
6 changed files with 1080 additions and 168 deletions
-1
View File
@@ -1 +0,0 @@
+315 -77
View File
@@ -1,116 +1,354 @@
"""
Static mock data for Crop Zoning API.
Matches API_RESPONSE_SPEC.md. No database, no dynamic values.
Matches CROP_ZONING_APIS.md. No database, no dynamic values.
"""
# Response for POST optimize: GeoJSON FeatureCollection (API_RESPONSE_SPEC §1)
OPTIMIZE_ZONING_RESPONSE = {
"type": "FeatureCollection",
"features": [
# ---------------------------------------------------------------------------
# GET /api/crop-zoning/area/
# منطقهٔ ثابت — کاربر امکان رسم ندارد
# ---------------------------------------------------------------------------
AREA_RESPONSE_DATA = {
"area": {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.405, 35.672],
[51.41, 35.695],
[51.385, 35.71],
[51.365, 35.688],
[51.38, 35.68],
]
],
},
}
}
# ---------------------------------------------------------------------------
# GET /api/crop-zoning/products/
# ---------------------------------------------------------------------------
PRODUCTS_RESPONSE_DATA = {
"products": [
{"id": "wheat", "label": "گندم", "color": "#6bcb77"},
{"id": "canola", "label": "کلزا", "color": "#ffd93d"},
{"id": "saffron", "label": "زعفران", "color": "#9b59b6"},
]
}
# ---------------------------------------------------------------------------
# POST /api/crop-zoning/zones/initial/
# دیتای اولیه برای نقشه و هاور/tooltip — بدون reason و criteria
# ---------------------------------------------------------------------------
ZONES_INITIAL_RESPONSE_DATA = {
"total_area_hectares": 23.45,
"total_area_sqm": 234500,
"zone_count": 3,
"zones": [
{
"type": "Feature",
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.381, 35.68],
[51.381, 35.681],
[51.38, 35.681],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68],
]
],
},
"properties": {
"zoneId": "zone-0",
"crop": "wheat",
"matchPercent": 78,
"waterNeed": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimatedProfit": "۱۵-۲۵ میلیون/هکتار",
"reason": "دمای مناسب، خاک حاصلخیز، دسترسی به آب کافی",
"criteria": [
{"name": "دما", "value": 85},
{"name": "بارش", "value": 72},
{"name": "خاک", "value": 80},
{"name": "آب", "value": 65},
],
},
"crop": "wheat",
"matchPercent": 85,
"waterNeed": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimatedProfit": "۱۵-۲۵ میلیون/هکتار",
},
{
"type": "Feature",
"zoneId": "zone-1",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.381, 35.68],
[51.382, 35.68],
[51.382, 35.681],
[51.381, 35.681],
[51.381, 35.68],
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68],
]
],
},
"properties": {
"zoneId": "zone-1",
"crop": "canola",
"matchPercent": 82,
"waterNeed": "۳۵۰۰-۴۵۰۰ m³/ha",
"estimatedProfit": "۲۰-۳۰ میلیون/هکتار",
"reason": "بارش کافی، خاک با بافت مناسب",
"criteria": [
{"name": "دما", "value": 70},
{"name": "بارش", "value": 88},
{"name": "خاک", "value": 75},
{"name": "آب", "value": 90},
],
},
"crop": "canola",
"matchPercent": 78,
"waterNeed": "۵۰۰۰-۶۰۰۰ m³/ha",
"estimatedProfit": "۲۰-۳۵ میلیون/هکتار",
},
{
"type": "Feature",
"zoneId": "zone-2",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.382, 35.68],
[51.40, 35.68],
[51.40, 35.681],
[51.382, 35.681],
[51.382, 35.68],
[51.38, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.683],
[51.38, 35.683],
[51.38, 35.6815],
]
],
},
"properties": {
"zoneId": "zone-2",
"crop": "saffron",
"matchPercent": 65,
"waterNeed": "۲۵۰۰-۳۵۰۰ m³/ha",
"estimatedProfit": "۸۰-۱۲۰ میلیون/هکتار",
"reason": "آب و هوای خشک و سرد مناسب زعفران",
"criteria": [
{"name": "دما", "value": 60},
{"name": "بارش", "value": 55},
{"name": "خاک", "value": 85},
{"name": "آب", "value": 50},
],
},
"crop": "saffron",
"matchPercent": 92,
"waterNeed": "۳۰۰۰-۴۰۰۰ m³/ha",
"estimatedProfit": "۵۰-۱۵۰ میلیون/هکتار",
},
],
}
# Response for GET initial region: GeoJSON Feature with Polygon (API_RESPONSE_SPEC §2)
INITIAL_REGION_RESPONSE = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.40, 35.68],
[51.40, 35.70],
[51.38, 35.70],
[51.38, 35.68],
]
# ---------------------------------------------------------------------------
# POST /api/crop-zoning/zones/water-need/
# نیاز آبی هر منطقه برای لایهٔ نیاز آبی
# ---------------------------------------------------------------------------
ZONES_WATER_NEED_RESPONSE_DATA = {
"zones": [
{
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68],
]
],
},
"level": "medium",
"value": "۴۵۰۰-۵۵۰۰ m³/ha",
"color": "#0ea5e9",
},
{
"zoneId": "zone-1",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68],
]
],
},
"level": "high",
"value": "۵۰۰۰-۶۰۰۰ m³/ha",
"color": "#0369a1",
},
{
"zoneId": "zone-2",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.683],
[51.38, 35.683],
[51.38, 35.6815],
]
],
},
"level": "low",
"value": "۳۰۰۰-۴۰۰۰ m³/ha",
"color": "#7dd3fc",
},
]
}
# ---------------------------------------------------------------------------
# POST /api/crop-zoning/zones/soil-quality/
# کیفیت خاک هر منطقه برای لایهٔ کیفیت خاک
# ---------------------------------------------------------------------------
ZONES_SOIL_QUALITY_RESPONSE_DATA = {
"zones": [
{
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68],
]
],
},
"level": "high",
"score": 88,
"color": "#22c55e",
},
{
"zoneId": "zone-1",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68],
]
],
},
"level": "medium",
"score": 62,
"color": "#eab308",
},
{
"zoneId": "zone-2",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.683],
[51.38, 35.683],
[51.38, 35.6815],
]
],
},
"level": "high",
"score": 95,
"color": "#22c55e",
},
]
}
# ---------------------------------------------------------------------------
# POST /api/crop-zoning/zones/cultivation-risk/
# ریسک کشت هر منطقه برای لایهٔ ریسک کشت
# ---------------------------------------------------------------------------
ZONES_CULTIVATION_RISK_RESPONSE_DATA = {
"zones": [
{
"zoneId": "zone-0",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.68],
[51.3815, 35.68],
[51.3815, 35.6815],
[51.38, 35.6815],
[51.38, 35.68],
]
],
},
"level": "low",
"color": "#22c55e",
},
{
"zoneId": "zone-1",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.3815, 35.68],
[51.383, 35.68],
[51.383, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.68],
]
],
},
"level": "medium",
"color": "#f59e0b",
},
{
"zoneId": "zone-2",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[51.38, 35.6815],
[51.3815, 35.6815],
[51.3815, 35.683],
[51.38, 35.683],
[51.38, 35.6815],
]
],
},
"level": "low",
"color": "#22c55e",
},
]
}
# ---------------------------------------------------------------------------
# GET /api/crop-zoning/zones/:zoneId/details/
# دیتای تکمیلی برای پنل جزئیات — شامل reason و criteria
# منطبق با createZonedGrid و MOCK_AREA_GEOJSON
# ---------------------------------------------------------------------------
ZONE_DETAILS_BY_ID = {
"zone-0": {
"zoneId": "zone-0",
"crop": "wheat",
"matchPercent": 85,
"waterNeed": "۴۵۰۰-۵۵۰۰ m³/ha",
"estimatedProfit": "۱۵-۲۵ میلیون/هکتار",
"reason": "دمای مناسب، خاک حاصلخیز، دسترسی به آب کافی",
"criteria": [
{"name": "دما", "value": 82},
{"name": "بارش", "value": 75},
{"name": "خاک", "value": 88},
{"name": "آب", "value": 70},
],
"area_hectares": 2.25,
},
"zone-1": {
"zoneId": "zone-1",
"crop": "canola",
"matchPercent": 78,
"waterNeed": "۵۰۰۰-۶۰۰۰ m³/ha",
"estimatedProfit": "۲۰-۳۵ میلیون/هکتار",
"reason": "شرایط اقلیمی مساعد، نیاز آبی قابل تأمین",
"criteria": [
{"name": "دما", "value": 75},
{"name": "بارش", "value": 72},
{"name": "خاک", "value": 80},
{"name": "آب", "value": 78},
],
"area_hectares": 2.25,
},
"zone-2": {
"zoneId": "zone-2",
"crop": "saffron",
"matchPercent": 92,
"waterNeed": "۳۰۰۰-۴۰۰۰ m³/ha",
"estimatedProfit": "۵۰-۱۵۰ میلیون/هکتار",
"reason": "ارتفاع و آب و هوای خشک مناسب، پتانسیل سود بالا",
"criteria": [
{"name": "دما", "value": 90},
{"name": "بارش", "value": 65},
{"name": "خاک", "value": 95},
{"name": "آب", "value": 85},
],
"area_hectares": 2.25,
},
}
File diff suppressed because one or more lines are too long
+32 -3
View File
@@ -1,8 +1,37 @@
from django.urls import path
from .views import InitialRegionView, OptimizeZoningView
from .views import (
AreaView,
ProductsView,
ZoneDetailsView,
ZonesCultivationRiskView,
ZonesInitialView,
ZonesSoilQualityView,
ZonesWaterNeedView,
)
urlpatterns = [
path("optimize/", OptimizeZoningView.as_view(), name="crop-zoning-optimize"),
path("initial-region/", InitialRegionView.as_view(), name="crop-zoning-initial-region"),
path("area/", AreaView.as_view(), name="crop-zoning-area"),
path("products/", ProductsView.as_view(), name="crop-zoning-products"),
path("zones/initial/", ZonesInitialView.as_view(), name="crop-zoning-zones-initial"),
path(
"zones/water-need/",
ZonesWaterNeedView.as_view(),
name="crop-zoning-zones-water-need",
),
path(
"zones/soil-quality/",
ZonesSoilQualityView.as_view(),
name="crop-zoning-zones-soil-quality",
),
path(
"zones/cultivation-risk/",
ZonesCultivationRiskView.as_view(),
name="crop-zoning-zones-cultivation-risk",
),
path(
"zones/<str:zone_id>/details/",
ZoneDetailsView.as_view(),
name="crop-zoning-zone-details",
),
]
+188 -39
View File
@@ -3,65 +3,214 @@ Crop Zoning API views.
Plain Django only; no DRF. 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.
CSRF exempt on POST so frontend can call without token.
"""
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from .mock_data import INITIAL_REGION_RESPONSE, OPTIMIZE_ZONING_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,
)
class OptimizeZoningView(View):
class AreaView(View):
"""
POST endpoint for zoning optimization.
GET endpoint for fixed land area (GeoJSON polygon).
Purpose:
Returns a static GeoJSON FeatureCollection of zones with crop suggestions
(API_RESPONSE_SPEC §1). Used when the user selects a region on the map
and triggers "optimize zoning". No processing is performed on the request.
Returns static land area polygon for display on map. User cannot
draw or edit the region; it is loaded from backend.
Input parameters:
- body (optional): JSON body; may contain a GeoJSON Feature with Polygon.
Data type: object. Location: body. Not read or validated; not used in response.
None.
Response structure:
- status: string, always "success".
- data: object, GeoJSON FeatureCollection with features containing
geometry (Polygon) and properties (zoneId, crop, matchPercent, waterNeed,
estimatedProfit, reason, criteria).
No processing or validation is performed on inputs.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": OPTIMIZE_ZONING_RESPONSE},
status=200,
)
class InitialRegionView(View):
"""
GET endpoint for the initial map region.
Purpose:
Returns a static GeoJSON Feature with Polygon defining the initial
map region (API_RESPONSE_SPEC §2). Optional; used when the initial
region is loaded from the server instead of a fixed client mock.
Input parameters:
None. Query parameters, if sent, are not read or used.
Response structure:
- status: string, always "success".
- data: object, GeoJSON Feature with geometry.type "Polygon" and
coordinates as [longitude, latitude]; first and last point equal.
- data: object with key "area" (GeoJSON Feature with Polygon geometry).
No processing or validation is performed on inputs.
"""
def get(self, request):
return JsonResponse(
{"status": "success", "data": INITIAL_REGION_RESPONSE},
{"status": "success", "data": AREA_RESPONSE_DATA},
status=200,
)
class ProductsView(View):
"""
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.
"""
def get(self, request):
return JsonResponse(
{"status": "success", "data": PRODUCTS_RESPONSE_DATA},
status=200,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesInitialView(View):
"""
POST endpoint for initial zone data (map + hover/tooltip).
Purpose:
Accepts zones (FeatureCollection of grid squares) and returns static
initial data per zone for map rendering and hover/tooltip display.
Does not include reason or criteria (those are in zone details).
Input parameters (body, JSON):
- zones: GeoJSON FeatureCollection. Location: body. Grid square polygons.
- products: array of strings, optional. Location: body. Product IDs.
Not read or used in response.
Response structure:
- status: string, always "success".
- data: object with total_area_hectares, total_area_sqm, zone_count,
zones (array of { zoneId, geometry, crop, matchPercent, waterNeed,
estimatedProfit }).
No processing or validation is performed on inputs. Input values are
not used in the response.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ZONES_INITIAL_RESPONSE_DATA},
status=200,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesWaterNeedView(View):
"""
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.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ZONES_WATER_NEED_RESPONSE_DATA},
status=200,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesSoilQualityView(View):
"""
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.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ZONES_SOIL_QUALITY_RESPONSE_DATA},
status=200,
)
@method_decorator(csrf_exempt, name="dispatch")
class ZonesCultivationRiskView(View):
"""
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.
"""
def post(self, request):
return JsonResponse(
{"status": "success", "data": ZONES_CULTIVATION_RISK_RESPONSE_DATA},
status=200,
)
class ZoneDetailsView(View):
"""
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.
"""
def get(self, request, zone_id):
data = ZONE_DETAILS_BY_ID.get(zone_id, ZONE_DETAILS_BY_ID["zone-0"])
return JsonResponse(
{"status": "success", "data": data},
status=200,
)