UPDATE
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("irrigation", "0001_initial"),
|
||||
("sensor_data", "0010_rename_tables_to_farm_data"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="sensordata",
|
||||
name="irrigation_method",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_column="irrigation_method_id",
|
||||
help_text="روش آبیاری انتخابشده برای این farm",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="farm_data",
|
||||
to="irrigation.irrigationmethod",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -115,6 +115,15 @@ class SensorData(SensorPayloadMixin, models.Model):
|
||||
related_name="farm_data",
|
||||
help_text="گیاهان مرتبط با این farm",
|
||||
)
|
||||
irrigation_method = models.ForeignKey(
|
||||
"irrigation.IrrigationMethod",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="farm_data",
|
||||
db_column="irrigation_method_id",
|
||||
help_text="روش آبیاری انتخابشده برای این farm",
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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
|
||||
|
||||
@@ -19,6 +21,11 @@ class SensorDataUpdateSerializer(serializers.Serializer):
|
||||
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):
|
||||
@@ -31,6 +38,7 @@ class SensorDataUpdateSerializer(serializers.Serializer):
|
||||
"sensor_key",
|
||||
"sensor_payload",
|
||||
"plant_ids",
|
||||
"irrigation_method_id",
|
||||
}
|
||||
flat_metrics = {
|
||||
key: value
|
||||
@@ -71,10 +79,21 @@ class SensorDataUpdateSerializer(serializers.Serializer):
|
||||
)
|
||||
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:
|
||||
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 باید ارسال شود."
|
||||
"حداقل یکی از sensor_payload یا plant_ids یا irrigation_method_id باید ارسال شود."
|
||||
)
|
||||
return attrs
|
||||
|
||||
@@ -87,6 +106,11 @@ class SensorDataResponseSerializer(serializers.ModelSerializer):
|
||||
many=True,
|
||||
read_only=True,
|
||||
)
|
||||
irrigation_method_id = serializers.IntegerField(
|
||||
source="irrigation_method.id",
|
||||
read_only=True,
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SensorData
|
||||
@@ -96,6 +120,7 @@ class SensorDataResponseSerializer(serializers.ModelSerializer):
|
||||
"weather_forecast_id",
|
||||
"sensor_payload",
|
||||
"plant_ids",
|
||||
"irrigation_method_id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
@@ -148,12 +173,13 @@ class FarmSoilPayloadSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class FarmDetailSerializer(serializers.Serializer):
|
||||
farm_uuid = serializers.UUIDField()
|
||||
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()
|
||||
|
||||
+12
-2
@@ -7,6 +7,7 @@ from django.db import transaction
|
||||
from location_data.models import SoilLocation
|
||||
from location_data.serializers import SoilDepthDataSerializer
|
||||
from location_data.tasks import fetch_soil_data_for_coordinates
|
||||
from irrigation.serializers import IrrigationMethodSerializer
|
||||
from plant.serializers import PlantSerializer
|
||||
from weather.services import update_weather_for_location
|
||||
from weather.models import WeatherForecast
|
||||
@@ -25,7 +26,11 @@ class ExternalDataSyncError(Exception):
|
||||
|
||||
def get_farm_details(farm_uuid: str):
|
||||
farm = (
|
||||
SensorData.objects.select_related("center_location", "weather_forecast")
|
||||
SensorData.objects.select_related(
|
||||
"center_location",
|
||||
"weather_forecast",
|
||||
"irrigation_method",
|
||||
)
|
||||
.prefetch_related("plants", "center_location__depths")
|
||||
.filter(farm_uuid=farm_uuid)
|
||||
.first()
|
||||
@@ -53,7 +58,6 @@ def get_farm_details(farm_uuid: str):
|
||||
metric_sources[key] = "sensor"
|
||||
|
||||
return {
|
||||
"farm_uuid": farm.farm_uuid,
|
||||
"center_location": {
|
||||
"id": center_location.id,
|
||||
"lat": center_location.latitude,
|
||||
@@ -69,6 +73,12 @@ def get_farm_details(farm_uuid: str):
|
||||
},
|
||||
"plant_ids": list(farm.plants.values_list("id", flat=True)),
|
||||
"plants": PlantSerializer(farm.plants.all(), many=True).data,
|
||||
"irrigation_method_id": farm.irrigation_method_id,
|
||||
"irrigation_method": (
|
||||
IrrigationMethodSerializer(farm.irrigation_method).data
|
||||
if farm.irrigation_method
|
||||
else None
|
||||
),
|
||||
"created_at": farm.created_at,
|
||||
"updated_at": farm.updated_at,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ from rest_framework.test import APIClient
|
||||
|
||||
from location_data.models import SoilDepthData, SoilLocation
|
||||
from farm_data.models import SensorData
|
||||
from irrigation.models import IrrigationMethod
|
||||
from plant.models import Plant
|
||||
from weather.models import WeatherForecast
|
||||
|
||||
@@ -58,11 +59,13 @@ class FarmDetailApiTests(TestCase):
|
||||
)
|
||||
self.plant1 = Plant.objects.create(name="گوجهفرنگی")
|
||||
self.plant2 = Plant.objects.create(name="خیار")
|
||||
self.irrigation_method = IrrigationMethod.objects.create(name="آبیاری قطرهای")
|
||||
self.farm_uuid = uuid.uuid4()
|
||||
self.farm = SensorData.objects.create(
|
||||
farm_uuid=self.farm_uuid,
|
||||
center_location=self.location,
|
||||
weather_forecast=self.weather,
|
||||
irrigation_method=self.irrigation_method,
|
||||
sensor_payload={
|
||||
"sensor-7-1": {
|
||||
"soil_moisture": 33.5,
|
||||
@@ -78,7 +81,7 @@ class FarmDetailApiTests(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()["data"]
|
||||
|
||||
self.assertEqual(payload["farm_uuid"], str(self.farm_uuid))
|
||||
self.assertNotIn("farm_uuid", payload)
|
||||
self.assertEqual(payload["center_location"]["id"], self.location.id)
|
||||
self.assertEqual(payload["weather"]["id"], self.weather.id)
|
||||
self.assertEqual(
|
||||
@@ -100,6 +103,8 @@ class FarmDetailApiTests(TestCase):
|
||||
self.assertEqual(returned_plants[self.plant1.id]["name"], self.plant1.name)
|
||||
self.assertEqual(returned_plants[self.plant2.id]["name"], self.plant2.name)
|
||||
self.assertIn("light", returned_plants[self.plant1.id])
|
||||
self.assertEqual(payload["irrigation_method_id"], self.irrigation_method.id)
|
||||
self.assertEqual(payload["irrigation_method"]["name"], self.irrigation_method.name)
|
||||
|
||||
def test_returns_404_when_farm_is_missing(self):
|
||||
response = self.client.get(f"/api/farm-data/{uuid.uuid4()}/detail/")
|
||||
@@ -123,6 +128,7 @@ class FarmDataUpsertApiTests(TestCase):
|
||||
temperature_max=24.0,
|
||||
temperature_mean=17.5,
|
||||
)
|
||||
self.irrigation_method = IrrigationMethod.objects.create(name="بارانی")
|
||||
|
||||
def test_post_creates_farm_data_with_explicit_farm_uuid(self):
|
||||
farm_uuid = uuid.uuid4()
|
||||
@@ -138,6 +144,7 @@ class FarmDataUpsertApiTests(TestCase):
|
||||
"nitrogen": 18.0,
|
||||
}
|
||||
},
|
||||
"irrigation_method_id": self.irrigation_method.id,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
@@ -150,6 +157,7 @@ class FarmDataUpsertApiTests(TestCase):
|
||||
farm = SensorData.objects.get(farm_uuid=farm_uuid)
|
||||
self.assertEqual(farm.center_location_id, self.location.id)
|
||||
self.assertEqual(farm.weather_forecast_id, self.weather.id)
|
||||
self.assertEqual(farm.irrigation_method_id, self.irrigation_method.id)
|
||||
self.assertEqual(
|
||||
farm.sensor_payload["sensor-7-1"]["soil_moisture"],
|
||||
31.2,
|
||||
|
||||
@@ -168,6 +168,7 @@ class FarmDataUpsertView(APIView):
|
||||
farm_uuid = serializer.validated_data["farm_uuid"]
|
||||
farm_boundary = serializer.validated_data["farm_boundary"]
|
||||
plant_ids = serializer.validated_data.get("plant_ids")
|
||||
irrigation_method_id = serializer.validated_data.get("irrigation_method_id")
|
||||
sensor_payload = serializer.validated_data.get("sensor_payload", {})
|
||||
try:
|
||||
center_location = resolve_center_location_from_boundary(farm_boundary)
|
||||
@@ -210,12 +211,15 @@ class FarmDataUpsertView(APIView):
|
||||
|
||||
farm_data.center_location = center_location
|
||||
farm_data.weather_forecast = weather_forecast
|
||||
if "irrigation_method_id" in serializer.validated_data:
|
||||
farm_data.irrigation_method_id = irrigation_method_id
|
||||
if not created:
|
||||
farm_data.save(
|
||||
update_fields=[
|
||||
"center_location",
|
||||
"weather_forecast",
|
||||
"sensor_payload",
|
||||
"irrigation_method",
|
||||
"updated_at",
|
||||
]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user