241 lines
8.1 KiB
Python
241 lines
8.1 KiB
Python
from rest_framework import serializers
|
|
|
|
from location_data.serializers import SoilDepthDataSerializer
|
|
from irrigation.models import IrrigationMethod
|
|
from irrigation.serializers import IrrigationMethodSerializer
|
|
from weather.models import WeatherForecast
|
|
|
|
from .models import (
|
|
DEFAULT_SENSOR_DATA_TYPE,
|
|
DEFAULT_SENSOR_KEY,
|
|
FarmPlantAssignment,
|
|
PlantCatalogSnapshot,
|
|
SensorData,
|
|
)
|
|
|
|
|
|
class SensorDataUpdateSerializer(serializers.Serializer):
|
|
"""ورودی آپدیت داده سنسور در ساختار JSON."""
|
|
|
|
farm_uuid = serializers.UUIDField(required=True)
|
|
farm_boundary = serializers.JSONField(required=True)
|
|
sensor_key = serializers.CharField(required=False, default=DEFAULT_SENSOR_KEY)
|
|
sensor_payload = serializers.JSONField(required=False)
|
|
plant_ids = serializers.ListField(
|
|
child=serializers.IntegerField(),
|
|
required=False,
|
|
help_text="لیست شناسه گیاهان canonical در Backend/plants",
|
|
)
|
|
irrigation_method_id = serializers.IntegerField(
|
|
required=False,
|
|
allow_null=True,
|
|
help_text="شناسه روش آبیاری مرتبط",
|
|
)
|
|
|
|
def to_internal_value(self, data):
|
|
if not isinstance(data, dict):
|
|
raise serializers.ValidationError("بدنه درخواست باید JSON object باشد.")
|
|
|
|
payload = dict(data)
|
|
known_fields = {
|
|
"farm_uuid",
|
|
"farm_boundary",
|
|
"sensor_key",
|
|
"sensor_payload",
|
|
"plant_ids",
|
|
"irrigation_method_id",
|
|
}
|
|
flat_metrics = {
|
|
key: value
|
|
for key, value in payload.items()
|
|
if key not in known_fields
|
|
}
|
|
|
|
if flat_metrics:
|
|
sensor_key = payload.get("sensor_key", DEFAULT_SENSOR_KEY)
|
|
nested_payload = payload.get("sensor_payload") or {}
|
|
if nested_payload and not isinstance(nested_payload, dict):
|
|
raise serializers.ValidationError(
|
|
{"sensor_payload": "sensor_payload باید object باشد."}
|
|
)
|
|
merged_payload = dict(nested_payload)
|
|
current_sensor_payload = merged_payload.get(sensor_key, {})
|
|
if current_sensor_payload and not isinstance(current_sensor_payload, dict):
|
|
raise serializers.ValidationError(
|
|
{"sensor_payload": f"مقدار {sensor_key} باید object باشد."}
|
|
)
|
|
merged_sensor_payload = dict(current_sensor_payload)
|
|
merged_sensor_payload.update(flat_metrics)
|
|
merged_payload[sensor_key] = merged_sensor_payload
|
|
payload["sensor_payload"] = merged_payload
|
|
|
|
for key in flat_metrics:
|
|
payload.pop(key, None)
|
|
|
|
return super().to_internal_value(payload)
|
|
|
|
def validate_sensor_payload(self, value):
|
|
if not isinstance(value, dict):
|
|
raise serializers.ValidationError("sensor_payload باید object باشد.")
|
|
for sensor_key, sensor_values in value.items():
|
|
if not isinstance(sensor_values, dict):
|
|
raise serializers.ValidationError(
|
|
f"مقدار سنسور {sensor_key} باید object باشد."
|
|
)
|
|
return value
|
|
|
|
def validate_irrigation_method_id(self, value):
|
|
if value is None:
|
|
return value
|
|
if not IrrigationMethod.objects.filter(pk=value).exists():
|
|
raise serializers.ValidationError("روش آبیاری معتبر نیست.")
|
|
return value
|
|
|
|
def validate(self, attrs):
|
|
if (
|
|
"sensor_payload" not in attrs
|
|
and "plant_ids" not in attrs
|
|
and "irrigation_method_id" not in attrs
|
|
):
|
|
raise serializers.ValidationError(
|
|
"حداقل یکی از sensor_payload یا plant_ids یا irrigation_method_id باید ارسال شود."
|
|
)
|
|
return attrs
|
|
|
|
|
|
class SensorDataResponseSerializer(serializers.ModelSerializer):
|
|
"""سریالایزر خروجی برای SensorData."""
|
|
|
|
plant_ids = serializers.SerializerMethodField()
|
|
irrigation_method_id = serializers.IntegerField(
|
|
source="irrigation_method.id",
|
|
read_only=True,
|
|
allow_null=True,
|
|
)
|
|
|
|
def get_plant_ids(self, obj):
|
|
return [plant.backend_plant_id for plant in obj.plant_snapshots]
|
|
|
|
class Meta:
|
|
model = SensorData
|
|
fields = [
|
|
"farm_uuid",
|
|
"center_location_id",
|
|
"weather_forecast_id",
|
|
"sensor_payload",
|
|
"plant_ids",
|
|
"irrigation_method_id",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
|
|
|
|
class SensorParameterSerializer(serializers.Serializer):
|
|
"""سریالایزر ورودی برای تعریف پارامترهای سنسورهای مختلف."""
|
|
|
|
sensor_key = serializers.CharField(max_length=64, required=False, default=DEFAULT_SENSOR_KEY)
|
|
code = serializers.CharField(max_length=64)
|
|
name_fa = serializers.CharField(max_length=128)
|
|
unit = serializers.CharField(max_length=32, required=False, allow_blank=True)
|
|
data_type = serializers.CharField(
|
|
max_length=32,
|
|
required=False,
|
|
default=DEFAULT_SENSOR_DATA_TYPE,
|
|
)
|
|
metadata = serializers.JSONField(required=False, default=dict)
|
|
|
|
|
|
class FarmCenterLocationSerializer(serializers.Serializer):
|
|
id = serializers.IntegerField()
|
|
lat = serializers.DecimalField(max_digits=9, decimal_places=6)
|
|
lon = serializers.DecimalField(max_digits=9, decimal_places=6)
|
|
farm_boundary = serializers.JSONField()
|
|
|
|
|
|
class WeatherForecastDetailSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = WeatherForecast
|
|
fields = [
|
|
"id",
|
|
"forecast_date",
|
|
"temperature_min",
|
|
"temperature_max",
|
|
"temperature_mean",
|
|
"precipitation",
|
|
"precipitation_probability",
|
|
"humidity_mean",
|
|
"wind_speed_max",
|
|
"et0",
|
|
"weather_code",
|
|
]
|
|
|
|
|
|
class FarmSoilPayloadSerializer(serializers.Serializer):
|
|
resolved_metrics = serializers.JSONField()
|
|
metric_sources = serializers.JSONField()
|
|
depths = SoilDepthDataSerializer(many=True)
|
|
|
|
|
|
class PlantCatalogSnapshotSerializer(serializers.ModelSerializer):
|
|
id = serializers.IntegerField(source="backend_plant_id", read_only=True)
|
|
|
|
class Meta:
|
|
model = PlantCatalogSnapshot
|
|
fields = [
|
|
"id",
|
|
"backend_plant_id",
|
|
"name",
|
|
"slug",
|
|
"icon",
|
|
"description",
|
|
"metadata",
|
|
"light",
|
|
"watering",
|
|
"soil",
|
|
"temperature",
|
|
"growth_stage",
|
|
"growth_stages",
|
|
"planting_season",
|
|
"harvest_time",
|
|
"spacing",
|
|
"fertilizer",
|
|
"health_profile",
|
|
"irrigation_profile",
|
|
"growth_profile",
|
|
"is_active",
|
|
"source_updated_at",
|
|
"updated_at",
|
|
]
|
|
|
|
|
|
class FarmPlantAssignmentSerializer(serializers.ModelSerializer):
|
|
plant_id = serializers.IntegerField(source="plant.backend_plant_id", read_only=True)
|
|
plant = PlantCatalogSnapshotSerializer(read_only=True)
|
|
|
|
class Meta:
|
|
model = FarmPlantAssignment
|
|
fields = [
|
|
"plant_id",
|
|
"position",
|
|
"stage",
|
|
"metadata",
|
|
"assigned_at",
|
|
"updated_at",
|
|
"plant",
|
|
]
|
|
|
|
|
|
class FarmDetailSerializer(serializers.Serializer):
|
|
center_location = FarmCenterLocationSerializer()
|
|
weather = WeatherForecastDetailSerializer(allow_null=True)
|
|
sensor_payload = serializers.JSONField()
|
|
sensor_schema = serializers.JSONField()
|
|
soil = FarmSoilPayloadSerializer()
|
|
plant_ids = serializers.ListField(child=serializers.IntegerField())
|
|
plants = PlantCatalogSnapshotSerializer(many=True)
|
|
plant_assignments = FarmPlantAssignmentSerializer(many=True)
|
|
irrigation_method_id = serializers.IntegerField(allow_null=True)
|
|
irrigation_method = IrrigationMethodSerializer(allow_null=True)
|
|
created_at = serializers.DateTimeField()
|
|
updated_at = serializers.DateTimeField()
|