from rest_framework import serializers from location_data.serializers import SoilDepthDataSerializer from irrigation.models import IrrigationMethod from irrigation.serializers import IrrigationMethodSerializer from plant.serializers import PlantSerializer from weather.models import WeatherForecast from .models import DEFAULT_SENSOR_DATA_TYPE, DEFAULT_SENSOR_KEY, 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="لیست شناسه گیاهان مرتبط", ) 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.PrimaryKeyRelatedField( source="plants", many=True, read_only=True, ) irrigation_method_id = serializers.IntegerField( source="irrigation_method.id", read_only=True, allow_null=True, ) 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 FarmDetailSerializer(serializers.Serializer): center_location = FarmCenterLocationSerializer() weather = WeatherForecastDetailSerializer(allow_null=True) sensor_payload = serializers.JSONField() soil = FarmSoilPayloadSerializer() plant_ids = serializers.ListField(child=serializers.IntegerField()) plants = PlantSerializer(many=True) irrigation_method_id = serializers.IntegerField(allow_null=True) irrigation_method = IrrigationMethodSerializer(allow_null=True) created_at = serializers.DateTimeField() updated_at = serializers.DateTimeField()