UPDATE
This commit is contained in:
+59
-19
@@ -1,28 +1,68 @@
|
||||
"""
|
||||
Static mock data for Farm Dashboard API.
|
||||
No database, no dynamic values. Pure static payloads.
|
||||
"""
|
||||
|
||||
# Config payload for GET/PATCH farm-dashboard-config (section 2.1)
|
||||
# row_order must use valid row IDs only: overviewKpis, weatherAlerts, sensorMonitoring,
|
||||
# sensorCharts, alertsWater, predictions, soilHeatmap, ndviRecommendations, economic
|
||||
CONFIG = {
|
||||
"disabled_card_ids": [
|
||||
"predictions",
|
||||
],
|
||||
"row_order": [
|
||||
"overviewKpis",
|
||||
"weatherAlerts",
|
||||
"sensorMonitoring",
|
||||
"sensorCharts",
|
||||
"alertsWater",
|
||||
"soilHeatmap",
|
||||
"ndviRecommendations",
|
||||
"economic",
|
||||
],
|
||||
"enable_drag_reorder": False,
|
||||
from copy import deepcopy
|
||||
from threading import Lock
|
||||
|
||||
|
||||
VALID_ROW_IDS = [
|
||||
"overviewKpis",
|
||||
"weatherAlerts",
|
||||
"sensorMonitoring",
|
||||
"sensorCharts",
|
||||
"alertsWater",
|
||||
"predictions",
|
||||
"soilHeatmap",
|
||||
"ndviRecommendations",
|
||||
"economic",
|
||||
]
|
||||
|
||||
VALID_CARD_IDS = [
|
||||
"farmOverviewKpis",
|
||||
"farmWeatherCard",
|
||||
"farmAlertsTracker",
|
||||
"sensorValuesList",
|
||||
"sensorRadarChart",
|
||||
"sensorComparisonChart",
|
||||
"anomalyDetectionCard",
|
||||
"farmAlertsTimeline",
|
||||
"waterNeedPrediction",
|
||||
"harvestPredictionCard",
|
||||
"yieldPredictionChart",
|
||||
"soilMoistureHeatmap",
|
||||
"ndviHealthCard",
|
||||
"recommendationsList",
|
||||
"economicOverview",
|
||||
]
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"disabled_card_ids": [],
|
||||
"row_order": VALID_ROW_IDS.copy(),
|
||||
"enable_drag_reorder": True,
|
||||
}
|
||||
|
||||
_config_lock = Lock()
|
||||
_config_state = deepcopy(DEFAULT_CONFIG)
|
||||
|
||||
|
||||
def get_config():
|
||||
with _config_lock:
|
||||
return deepcopy(_config_state)
|
||||
|
||||
|
||||
def update_config(changes):
|
||||
with _config_lock:
|
||||
_config_state.update(deepcopy(changes))
|
||||
return deepcopy(_config_state)
|
||||
|
||||
|
||||
def reset_config():
|
||||
with _config_lock:
|
||||
_config_state.clear()
|
||||
_config_state.update(deepcopy(DEFAULT_CONFIG))
|
||||
return deepcopy(_config_state)
|
||||
|
||||
# 4.1 farmOverviewKpis
|
||||
FARM_OVERVIEW_KPIS = {
|
||||
"kpis": [
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .mock_data import VALID_CARD_IDS, VALID_ROW_IDS
|
||||
|
||||
|
||||
class FarmDashboardConfigSerializer(serializers.Serializer):
|
||||
disabled_card_ids = serializers.ListField(
|
||||
child=serializers.CharField(),
|
||||
allow_empty=True,
|
||||
)
|
||||
row_order = serializers.ListField(
|
||||
child=serializers.CharField(),
|
||||
allow_empty=False,
|
||||
)
|
||||
enable_drag_reorder = serializers.BooleanField()
|
||||
|
||||
def validate_disabled_card_ids(self, value):
|
||||
invalid_ids = [card_id for card_id in value if card_id not in VALID_CARD_IDS]
|
||||
if invalid_ids:
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid card IDs: {', '.join(invalid_ids)}."
|
||||
)
|
||||
if len(set(value)) != len(value):
|
||||
raise serializers.ValidationError("disabled_card_ids must not contain duplicates.")
|
||||
return value
|
||||
|
||||
def validate_row_order(self, value):
|
||||
invalid_ids = [row_id for row_id in value if row_id not in VALID_ROW_IDS]
|
||||
if invalid_ids:
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid row IDs: {', '.join(invalid_ids)}."
|
||||
)
|
||||
if len(set(value)) != len(value):
|
||||
raise serializers.ValidationError("row_order must not contain duplicates.")
|
||||
if set(value) != set(VALID_ROW_IDS):
|
||||
raise serializers.ValidationError(
|
||||
"row_order must contain each valid row ID exactly once."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class FarmDashboardConfigPatchSerializer(FarmDashboardConfigSerializer):
|
||||
disabled_card_ids = serializers.ListField(
|
||||
child=serializers.CharField(),
|
||||
allow_empty=True,
|
||||
required=False,
|
||||
)
|
||||
row_order = serializers.ListField(
|
||||
child=serializers.CharField(),
|
||||
allow_empty=False,
|
||||
required=False,
|
||||
)
|
||||
enable_drag_reorder = serializers.BooleanField(required=False)
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super().validate(attrs)
|
||||
if not attrs:
|
||||
raise serializers.ValidationError("At least one config field must be provided.")
|
||||
return attrs
|
||||
@@ -0,0 +1,66 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
from rest_framework.test import APIRequestFactory
|
||||
|
||||
from .mock_data import DEFAULT_CONFIG, reset_config
|
||||
from .views import FarmDashboardConfigView
|
||||
|
||||
|
||||
class FarmDashboardConfigViewTests(SimpleTestCase):
|
||||
def setUp(self):
|
||||
self.factory = APIRequestFactory()
|
||||
reset_config()
|
||||
|
||||
def tearDown(self):
|
||||
reset_config()
|
||||
|
||||
def test_get_returns_canonical_config(self):
|
||||
request = self.factory.get("/api/farm-dashboard-config/")
|
||||
response = FarmDashboardConfigView.as_view()(request)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data["code"], 200)
|
||||
self.assertEqual(response.data["msg"], "OK")
|
||||
self.assertEqual(response.data["data"], DEFAULT_CONFIG)
|
||||
|
||||
def test_patch_partial_update_returns_full_final_config(self):
|
||||
request = self.factory.patch(
|
||||
"/api/farm-dashboard-config/",
|
||||
{"disabled_card_ids": ["farmWeatherCard"]},
|
||||
format="json",
|
||||
)
|
||||
response = FarmDashboardConfigView.as_view()(request)
|
||||
|
||||
expected = deepcopy(DEFAULT_CONFIG)
|
||||
expected["disabled_card_ids"] = ["farmWeatherCard"]
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data["data"], expected)
|
||||
|
||||
def test_patch_only_drag_flag_still_returns_full_config(self):
|
||||
request = self.factory.patch(
|
||||
"/api/farm-dashboard-config/",
|
||||
{"enable_drag_reorder": False},
|
||||
format="json",
|
||||
)
|
||||
response = FarmDashboardConfigView.as_view()(request)
|
||||
|
||||
expected = deepcopy(DEFAULT_CONFIG)
|
||||
expected["enable_drag_reorder"] = False
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data["data"], expected)
|
||||
self.assertIn("disabled_card_ids", response.data["data"])
|
||||
self.assertIn("row_order", response.data["data"])
|
||||
|
||||
def test_patch_rejects_invalid_row_order(self):
|
||||
request = self.factory.patch(
|
||||
"/api/farm-dashboard-config/",
|
||||
{"row_order": ["overviewKpis"]},
|
||||
format="json",
|
||||
)
|
||||
response = FarmDashboardConfigView.as_view()(request)
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn("row_order", response.data)
|
||||
+1
-1
@@ -3,6 +3,6 @@ from django.urls import path
|
||||
from .views import FarmDashboardCardsView
|
||||
|
||||
urlpatterns = [
|
||||
path("cards/", FarmDashboardCardsView.as_view(), name="farm-dashboard-cards"),
|
||||
# path("cards/", FarmDashboardCardsView.as_view(), name="farm-dashboard-cards"),
|
||||
path("", FarmDashboardCardsView.as_view(), name="farm-dashboard"),
|
||||
]
|
||||
|
||||
+23
-13
@@ -1,43 +1,51 @@
|
||||
"""
|
||||
Farm Dashboard API views.
|
||||
No database connection. All responses use static mock data from mock_data.py.
|
||||
"""
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework import serializers
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
from config.swagger import code_response
|
||||
from external_api_adapter import request as external_api_request
|
||||
from .mock_data import CONFIG
|
||||
from .mock_data import get_config, update_config
|
||||
from .serializers import FarmDashboardConfigPatchSerializer, FarmDashboardConfigSerializer
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
tags=["Farm Dashboard"],
|
||||
responses={200: code_response("FarmDashboardConfigGetResponse", data=serializers.JSONField())},
|
||||
responses={200: code_response("FarmDashboardConfigGetResponse", data=FarmDashboardConfigSerializer())},
|
||||
),
|
||||
patch=extend_schema(
|
||||
tags=["Farm Dashboard"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: code_response("FarmDashboardConfigPatchResponse", data=serializers.JSONField())},
|
||||
request=FarmDashboardConfigPatchSerializer,
|
||||
responses={200: code_response("FarmDashboardConfigPatchResponse", data=FarmDashboardConfigSerializer())},
|
||||
),
|
||||
)
|
||||
class FarmDashboardConfigView(APIView):
|
||||
"""
|
||||
Farm dashboard config endpoints: GET and PATCH.
|
||||
GET returns static config (disabled_card_ids, row_order, enable_drag_reorder).
|
||||
PATCH accepts body but returns same static config; no processing or validation.
|
||||
No database. No input values used in response.
|
||||
Farm dashboard config endpoints.
|
||||
GET returns the current config.
|
||||
PATCH accepts partial updates and returns the full final config.
|
||||
"""
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get(self, request):
|
||||
return Response({"code": 200, "msg": "OK", "data": CONFIG}, status=status.HTTP_200_OK)
|
||||
config = get_config()
|
||||
return Response({"code": 200, "msg": "OK", "data": config}, status=status.HTTP_200_OK)
|
||||
|
||||
def patch(self, request):
|
||||
return Response({"code": 200, "msg": "OK", "data": CONFIG}, status=status.HTTP_200_OK)
|
||||
serializer = FarmDashboardConfigPatchSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
config = update_config(serializer.validated_data)
|
||||
response_serializer = FarmDashboardConfigSerializer(config)
|
||||
return Response(
|
||||
{"code": 200, "msg": "OK", "data": response_serializer.data},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
@@ -53,5 +61,7 @@ class FarmDashboardCardsView(APIView):
|
||||
No database. Static mock data only.
|
||||
"""
|
||||
def get(self, request):
|
||||
from external_api_adapter import request as external_api_request
|
||||
|
||||
adapter_response = external_api_request("ai", "/dashboard-data/status", method="GET")
|
||||
return Response(adapter_response.data, status=adapter_response.status_code)
|
||||
|
||||
Reference in New Issue
Block a user