first commit

This commit is contained in:
2026-03-19 22:54:29 +03:30
parent 1a178f39b7
commit 035bc6f74d
91 changed files with 3821 additions and 130 deletions
+1
View File
@@ -19,6 +19,7 @@ class SensorDataAdmin(admin.ModelAdmin):
)
list_filter = ("updated_at",)
search_fields = ("uuid_sensor", "location_id")
filter_horizontal = ("plants",)
@admin.register(SensorDataHistory)
+3 -3
View File
@@ -10,7 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('soil_data', '0002_soildepthdata_refactor'),
('location_data', '0002_soildepthdata_refactor'),
]
operations = [
@@ -19,7 +19,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid_sensor', models.UUIDField(help_text='شناسه سنسور')),
('location_id', models.IntegerField(help_text='location_id از soil_data')),
('location_id', models.IntegerField(help_text='location_id از location_data')),
('soil_moisture', models.FloatField(blank=True, null=True)),
('soil_temperature', models.FloatField(blank=True, null=True)),
('soil_ph', models.FloatField(blank=True, null=True)),
@@ -63,7 +63,7 @@ class Migration(migrations.Migration):
('potassium', models.FloatField(blank=True, help_text='پتاسیم', null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('location', models.ForeignKey(db_column='location_id', help_text='همان location_id در soil_data', on_delete=django.db.models.deletion.CASCADE, related_name='sensor_data', to='soil_data.soillocation')),
('location', models.ForeignKey(db_column='location_id', help_text='همان location_id در location_data', on_delete=django.db.models.deletion.CASCADE, related_name='sensor_data', to='location_data.soillocation')),
],
options={
'verbose_name': 'داده سنسور',
@@ -0,0 +1,19 @@
# Generated by Django 5.2.12 on 2026-03-19 15:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('plant', '0001_initial'),
('sensor_data', '0002_seed_initial_parameters'),
]
operations = [
migrations.AddField(
model_name='sensordata',
name='plants',
field=models.ManyToManyField(blank=True, help_text='گیاهان مرتبط با این سنسور', related_name='sensor_data', to='plant.plant'),
),
]
+9 -3
View File
@@ -16,11 +16,11 @@ class SensorData(models.Model):
help_text="شناسه یکتای سنسور",
)
location = models.ForeignKey(
"soil_data.SoilLocation",
"location_data.SoilLocation",
on_delete=models.CASCADE,
related_name="sensor_data",
db_column="location_id",
help_text="همان location_id در soil_data",
help_text="همان location_id از location_data",
)
soil_moisture = models.FloatField(null=True, blank=True, help_text="رطوبت خاک")
soil_temperature = models.FloatField(null=True, blank=True, help_text="دما خاک")
@@ -31,6 +31,12 @@ class SensorData(models.Model):
nitrogen = models.FloatField(null=True, blank=True, help_text="ازت (N)")
phosphorus = models.FloatField(null=True, blank=True, help_text="فسفر")
potassium = models.FloatField(null=True, blank=True, help_text="پتاسیم")
plants = models.ManyToManyField(
"plant.Plant",
blank=True,
related_name="sensor_data",
help_text="گیاهان مرتبط با این سنسور",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -49,7 +55,7 @@ class SensorDataHistory(models.Model):
"""
uuid_sensor = models.UUIDField(help_text="شناسه سنسور")
location_id = models.IntegerField(help_text="location_id از soil_data")
location_id = models.IntegerField(help_text="location_id از location_data")
soil_moisture = models.FloatField(null=True, blank=True)
soil_temperature = models.FloatField(null=True, blank=True)
soil_ph = models.FloatField(null=True, blank=True)
+12
View File
@@ -14,11 +14,22 @@ class SensorDataUpdateSerializer(serializers.Serializer):
nitrogen = serializers.FloatField(required=False, allow_null=True)
phosphorus = serializers.FloatField(required=False, allow_null=True)
potassium = serializers.FloatField(required=False, allow_null=True)
plant_ids = serializers.ListField(
child=serializers.IntegerField(),
required=False,
help_text="لیست شناسه گیاهان مرتبط",
)
class SensorDataResponseSerializer(serializers.ModelSerializer):
"""سریالایزر خروجی برای SensorData."""
plant_ids = serializers.PrimaryKeyRelatedField(
source="plants",
many=True,
read_only=True,
)
class Meta:
model = SensorData
fields = [
@@ -31,6 +42,7 @@ class SensorDataResponseSerializer(serializers.ModelSerializer):
"nitrogen",
"phosphorus",
"potassium",
"plant_ids",
"created_at",
"updated_at",
]
+84 -1
View File
@@ -1,9 +1,16 @@
from django.db import transaction
from drf_spectacular.utils import (
OpenApiExample,
OpenApiResponse,
extend_schema,
inline_serializer,
)
from rest_framework import serializers as drf_serializers
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from soil_data.models import SoilLocation
from location_data.models import SoilLocation
from .models import ParameterUpdateLog, SensorData, SensorDataHistory, SensorParameter
from .serializers import (
@@ -18,9 +25,47 @@ class SensorDataUpdateView(APIView):
آپدیت داده سنسور. هنگام آپدیت، نسخه فعلی در SensorDataHistory ذخیره می‌شود.
"""
@extend_schema(
tags=["Sensor Data"],
summary="آپدیت کامل داده سنسور",
description="داده سنسور را بر اساس uuid_sensor آپدیت (یا ایجاد) می‌کند. نسخه قبلی در تاریخچه ذخیره می‌شود.",
request=SensorDataUpdateSerializer,
responses={
200: SensorDataResponseSerializer,
400: OpenApiResponse(description="داده نامعتبر"),
404: OpenApiResponse(description="location_id یافت نشد"),
},
examples=[
OpenApiExample(
"نمونه درخواست",
value={
"location_id": 1,
"soil_moisture": 45.2,
"soil_temperature": 22.5,
"soil_ph": 6.8,
"electrical_conductivity": 1.2,
"nitrogen": 30.0,
"phosphorus": 15.0,
"potassium": 20.0,
},
request_only=True,
),
],
)
def put(self, request, uuid_sensor):
return self._update(request, uuid_sensor)
@extend_schema(
tags=["Sensor Data"],
summary="آپدیت جزئی داده سنسور",
description="فقط فیلدهای ارسال‌شده آپدیت می‌شوند.",
request=SensorDataUpdateSerializer,
responses={
200: SensorDataResponseSerializer,
400: OpenApiResponse(description="داده نامعتبر"),
404: OpenApiResponse(description="location_id یافت نشد"),
},
)
def patch(self, request, uuid_sensor):
return self._update(request, uuid_sensor, partial=True)
@@ -35,6 +80,7 @@ class SensorDataUpdateView(APIView):
)
location_id = serializer.validated_data.pop("location_id")
plant_ids = serializer.validated_data.pop("plant_ids", None)
location = SoilLocation.objects.filter(pk=location_id).first()
if not location:
return Response(
@@ -67,6 +113,9 @@ class SensorDataUpdateView(APIView):
potassium=sensor_data.potassium,
)
if plant_ids is not None:
sensor_data.plants.set(plant_ids)
return Response(
{
"code": 200,
@@ -82,6 +131,40 @@ class SensorParameterCreateView(APIView):
اضافه کردن پارامتر جدید و ثبت در ParameterUpdateLog.
"""
@extend_schema(
tags=["Sensor Parameters"],
summary="افزودن/ویرایش پارامتر سنسور",
description="پارامتر جدید اضافه یا پارامتر موجود را ویرایش می‌کند و در لاگ ثبت می‌شود.",
request=SensorParameterSerializer,
responses={
201: inline_serializer(
name="SensorParameterResponse",
fields={
"code": drf_serializers.IntegerField(),
"msg": drf_serializers.CharField(),
"data": inline_serializer(
name="SensorParameterData",
fields={
"id": drf_serializers.IntegerField(),
"code": drf_serializers.CharField(),
"name_fa": drf_serializers.CharField(),
"unit": drf_serializers.CharField(),
"created_at": drf_serializers.DateTimeField(),
"action": drf_serializers.CharField(),
},
),
},
),
400: OpenApiResponse(description="داده نامعتبر"),
},
examples=[
OpenApiExample(
"نمونه درخواست",
value={"code": "soil_moisture", "name_fa": "رطوبت خاک", "unit": "%"},
request_only=True,
),
],
)
def post(self, request):
serializer = SensorParameterSerializer(data=request.data)
if not serializer.is_valid():