Add sensor_data app to Django settings and URL routing
- Included sensor_data in the INSTALLED_APPS of settings.py. - Added URL path for sensor_data in urls.py to enable API access.
This commit is contained in:
@@ -22,6 +22,7 @@ INSTALLED_APPS = [
|
|||||||
"corsheaders",
|
"corsheaders",
|
||||||
"tasks",
|
"tasks",
|
||||||
"soil_data",
|
"soil_data",
|
||||||
|
"sensor_data",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ urlpatterns = [
|
|||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/tasks/", include("tasks.urls")),
|
path("api/tasks/", include("tasks.urls")),
|
||||||
path("api/soil-data/", include("soil_data.urls")),
|
path("api/soil-data/", include("soil_data.urls")),
|
||||||
|
path("api/sensor-data/", include("sensor_data.urls")),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Fix: جداول sensor_data وجود ندارند اما migrationها بهعنوان اعمالشده ثبت شدهاند.
|
||||||
|
# اجرا: docker compose run --rm web sh /app/scripts/fix_sensor_data_tables.sh
|
||||||
|
set -e
|
||||||
|
cd /app
|
||||||
|
echo "Resetting sensor_data migrations (fake unapply - tables may not exist)..."
|
||||||
|
python manage.py migrate sensor_data zero --noinput --fake
|
||||||
|
echo "Re-applying sensor_data migrations (--fake-initial if tables already exist)..."
|
||||||
|
python manage.py migrate sensor_data --noinput --fake-initial
|
||||||
|
echo "Done. Running seed_sensor_parameters..."
|
||||||
|
python manage.py seed_sensor_parameters
|
||||||
|
echo "All done."
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import ParameterUpdateLog, SensorData, SensorDataHistory, SensorParameter
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SensorData)
|
||||||
|
class SensorDataAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"uuid_sensor",
|
||||||
|
"location_id",
|
||||||
|
"soil_moisture",
|
||||||
|
"soil_temperature",
|
||||||
|
"soil_ph",
|
||||||
|
"electrical_conductivity",
|
||||||
|
"nitrogen",
|
||||||
|
"phosphorus",
|
||||||
|
"potassium",
|
||||||
|
"updated_at",
|
||||||
|
)
|
||||||
|
list_filter = ("updated_at",)
|
||||||
|
search_fields = ("uuid_sensor", "location_id")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SensorDataHistory)
|
||||||
|
class SensorDataHistoryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"uuid_sensor",
|
||||||
|
"location_id",
|
||||||
|
"soil_moisture",
|
||||||
|
"soil_temperature",
|
||||||
|
"soil_ph",
|
||||||
|
"recorded_at",
|
||||||
|
)
|
||||||
|
list_filter = ("recorded_at",)
|
||||||
|
search_fields = ("uuid_sensor", "location_id")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SensorParameter)
|
||||||
|
class SensorParameterAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("code", "name_fa", "unit", "created_at")
|
||||||
|
search_fields = ("code", "name_fa")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ParameterUpdateLog)
|
||||||
|
class ParameterUpdateLogAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("parameter", "action", "updated_at")
|
||||||
|
list_filter = ("action", "updated_at")
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class SensorDataConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "sensor_data"
|
||||||
|
verbose_name = "Sensor Data"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
Management command to seed the 7 initial sensor parameters.
|
||||||
|
Run: python manage.py seed_sensor_parameters
|
||||||
|
"""
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from sensor_data.models import ParameterUpdateLog, SensorParameter
|
||||||
|
|
||||||
|
|
||||||
|
INITIAL_PARAMETERS = [
|
||||||
|
("soil_moisture", "رطوبت خاک", "%"),
|
||||||
|
("soil_temperature", "دما خاک", "°C"),
|
||||||
|
("soil_ph", "pH خاک", ""),
|
||||||
|
("electrical_conductivity", "هدایت الکتریکی", "dS/m"),
|
||||||
|
("nitrogen", "ازت (N)", "mg/kg"),
|
||||||
|
("phosphorus", "فسفر", "mg/kg"),
|
||||||
|
("potassium", "پتاسیم", "mg/kg"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Seed 7 initial sensor parameters (soil_moisture, soil_temperature, etc.)"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
created_count = 0
|
||||||
|
for code, name_fa, unit in INITIAL_PARAMETERS:
|
||||||
|
param, created = SensorParameter.objects.get_or_create(
|
||||||
|
code=code,
|
||||||
|
defaults={"name_fa": name_fa, "unit": unit},
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
ParameterUpdateLog.objects.create(
|
||||||
|
parameter=param,
|
||||||
|
action="added",
|
||||||
|
)
|
||||||
|
created_count += 1
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" Created: {code} ({name_fa})"))
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(f"\nDone. Created {created_count} new parameters.")
|
||||||
|
)
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Generated by Django 5.2.11 on 2026-02-27 09:47
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('soil_data', '0002_soildepthdata_refactor'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SensorDataHistory',
|
||||||
|
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')),
|
||||||
|
('soil_moisture', models.FloatField(blank=True, null=True)),
|
||||||
|
('soil_temperature', models.FloatField(blank=True, null=True)),
|
||||||
|
('soil_ph', models.FloatField(blank=True, null=True)),
|
||||||
|
('electrical_conductivity', models.FloatField(blank=True, null=True)),
|
||||||
|
('nitrogen', models.FloatField(blank=True, null=True)),
|
||||||
|
('phosphorus', models.FloatField(blank=True, null=True)),
|
||||||
|
('potassium', models.FloatField(blank=True, null=True)),
|
||||||
|
('recorded_at', models.DateTimeField(auto_now_add=True, help_text='زمان ثبت در تاریخچه')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'تاریخچه داده سنسور',
|
||||||
|
'verbose_name_plural': 'تاریخچه داده\u200cهای سنسور',
|
||||||
|
'ordering': ['-recorded_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SensorParameter',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('code', models.CharField(db_index=True, help_text='کد یکتا (مثلاً soil_moisture)', max_length=64, unique=True)),
|
||||||
|
('name_fa', models.CharField(help_text='نام فارسی', max_length=128)),
|
||||||
|
('unit', models.CharField(blank=True, help_text='واحد اندازه\u200cگیری', max_length=32)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'پارامتر سنسور',
|
||||||
|
'verbose_name_plural': 'پارامترهای سنسور',
|
||||||
|
'ordering': ['code'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SensorData',
|
||||||
|
fields=[
|
||||||
|
('uuid_sensor', models.UUIDField(default=uuid.uuid4, editable=False, help_text='شناسه یکتای سنسور', primary_key=True, serialize=False)),
|
||||||
|
('soil_moisture', models.FloatField(blank=True, help_text='رطوبت خاک', null=True)),
|
||||||
|
('soil_temperature', models.FloatField(blank=True, help_text='دما خاک', null=True)),
|
||||||
|
('soil_ph', models.FloatField(blank=True, help_text='pH خاک', null=True)),
|
||||||
|
('electrical_conductivity', models.FloatField(blank=True, help_text='هدایت الکتریکی', null=True)),
|
||||||
|
('nitrogen', models.FloatField(blank=True, help_text='ازت (N)', null=True)),
|
||||||
|
('phosphorus', models.FloatField(blank=True, help_text='فسفر', null=True)),
|
||||||
|
('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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'داده سنسور',
|
||||||
|
'verbose_name_plural': 'داده\u200cهای سنسور',
|
||||||
|
'ordering': ['-updated_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ParameterUpdateLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('action', models.CharField(choices=[('added', 'اضافه شده'), ('modified', 'ویرایش شده')], max_length=16)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('parameter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='update_logs', to='sensor_data.sensorparameter')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'لاگ آپدیت پارامتر',
|
||||||
|
'verbose_name_plural': 'لاگ آپدیت پارامترها',
|
||||||
|
'ordering': ['-updated_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Generated by Django 5.2.11 on 2026-02-27 09:48
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sensor_data', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class SensorData(models.Model):
|
||||||
|
"""
|
||||||
|
دادههای خوانش سنسور برای یک location.
|
||||||
|
هنگام آپدیت، نسخه قبلی در SensorDataHistory ذخیره میشود.
|
||||||
|
"""
|
||||||
|
|
||||||
|
uuid_sensor = models.UUIDField(
|
||||||
|
primary_key=True,
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
help_text="شناسه یکتای سنسور",
|
||||||
|
)
|
||||||
|
location = models.ForeignKey(
|
||||||
|
"soil_data.SoilLocation",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="sensor_data",
|
||||||
|
db_column="location_id",
|
||||||
|
help_text="همان location_id در soil_data",
|
||||||
|
)
|
||||||
|
soil_moisture = models.FloatField(null=True, blank=True, help_text="رطوبت خاک")
|
||||||
|
soil_temperature = models.FloatField(null=True, blank=True, help_text="دما خاک")
|
||||||
|
soil_ph = models.FloatField(null=True, blank=True, help_text="pH خاک")
|
||||||
|
electrical_conductivity = models.FloatField(
|
||||||
|
null=True, blank=True, help_text="هدایت الکتریکی"
|
||||||
|
)
|
||||||
|
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="پتاسیم")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-updated_at"]
|
||||||
|
verbose_name = "داده سنسور"
|
||||||
|
verbose_name_plural = "دادههای سنسور"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"SensorData({self.uuid_sensor}, location={self.location_id})"
|
||||||
|
|
||||||
|
|
||||||
|
class SensorDataHistory(models.Model):
|
||||||
|
"""
|
||||||
|
تاریخچه خوانشهای سنسور. کپی از SensorData هنگام آپدیت.
|
||||||
|
"""
|
||||||
|
|
||||||
|
uuid_sensor = models.UUIDField(help_text="شناسه سنسور")
|
||||||
|
location_id = models.IntegerField(help_text="location_id از soil_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)
|
||||||
|
electrical_conductivity = models.FloatField(null=True, blank=True)
|
||||||
|
nitrogen = models.FloatField(null=True, blank=True)
|
||||||
|
phosphorus = models.FloatField(null=True, blank=True)
|
||||||
|
potassium = models.FloatField(null=True, blank=True)
|
||||||
|
recorded_at = models.DateTimeField(
|
||||||
|
auto_now_add=True, help_text="زمان ثبت در تاریخچه"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-recorded_at"]
|
||||||
|
verbose_name = "تاریخچه داده سنسور"
|
||||||
|
verbose_name_plural = "تاریخچه دادههای سنسور"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"SensorDataHistory({self.uuid_sensor}, {self.recorded_at})"
|
||||||
|
|
||||||
|
|
||||||
|
class SensorParameter(models.Model):
|
||||||
|
"""
|
||||||
|
تعریف پارامترهای سنسور (مثلاً رطوبت خاک، pH، ...).
|
||||||
|
"""
|
||||||
|
|
||||||
|
code = models.CharField(
|
||||||
|
max_length=64,
|
||||||
|
unique=True,
|
||||||
|
db_index=True,
|
||||||
|
help_text="کد یکتا (مثلاً soil_moisture)",
|
||||||
|
)
|
||||||
|
name_fa = models.CharField(max_length=128, help_text="نام فارسی")
|
||||||
|
unit = models.CharField(max_length=32, blank=True, help_text="واحد اندازهگیری")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["code"]
|
||||||
|
verbose_name = "پارامتر سنسور"
|
||||||
|
verbose_name_plural = "پارامترهای سنسور"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.code} ({self.name_fa})"
|
||||||
|
|
||||||
|
|
||||||
|
class ParameterUpdateLog(models.Model):
|
||||||
|
"""
|
||||||
|
لاگ آپدیت لیست پارامترها.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ACTION_ADDED = "added"
|
||||||
|
ACTION_MODIFIED = "modified"
|
||||||
|
ACTION_CHOICES = [
|
||||||
|
(ACTION_ADDED, "اضافه شده"),
|
||||||
|
(ACTION_MODIFIED, "ویرایش شده"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parameter = models.ForeignKey(
|
||||||
|
SensorParameter,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="update_logs",
|
||||||
|
)
|
||||||
|
action = models.CharField(max_length=16, choices=ACTION_CHOICES)
|
||||||
|
updated_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-updated_at"]
|
||||||
|
verbose_name = "لاگ آپدیت پارامتر"
|
||||||
|
verbose_name_plural = "لاگ آپدیت پارامترها"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.parameter.code} - {self.action} - {self.updated_at}"
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"name": "Sensor Data",
|
||||||
|
"description": "API دادههای سنسور: آپدیت خوانش سنسور و مدیریت پارامترها",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
|
},
|
||||||
|
"variable": [
|
||||||
|
{"key": "baseUrl", "value": "http://localhost:8020"},
|
||||||
|
{"key": "uuid_sensor", "value": "00000000-0000-0000-0000-000000000000"}
|
||||||
|
],
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Update Sensor Data (PUT)",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{"key": "Content-Type", "value": "application/json"},
|
||||||
|
{"key": "Accept", "value": "application/json"}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"location_id\": 1,\n \"soil_moisture\": 25.5,\n \"soil_temperature\": 22.3,\n \"soil_ph\": 7.2,\n \"electrical_conductivity\": 1.8,\n \"nitrogen\": 120.0,\n \"phosphorus\": 45.0,\n \"potassium\": 180.0\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/sensor-data/{{uuid_sensor}}/",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "sensor-data", "{{uuid_sensor}}", ""]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "آپدیت کامل داده سنسور. نسخه جدید در تاریخچه ذخیره میشود. location_id باید به SoilLocation ارجاع دهد."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Update Sensor Data (PATCH)",
|
||||||
|
"request": {
|
||||||
|
"method": "PATCH",
|
||||||
|
"header": [
|
||||||
|
{"key": "Content-Type", "value": "application/json"},
|
||||||
|
{"key": "Accept", "value": "application/json"}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"location_id\": 1,\n \"soil_moisture\": 28.0,\n \"soil_ph\": 7.5\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/sensor-data/{{uuid_sensor}}/",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "sensor-data", "{{uuid_sensor}}", ""]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "آپدیت جزئی داده سنسور. فقط فیلدهای ارسالی بهروزرسانی میشوند."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Add Parameter",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{"key": "Content-Type", "value": "application/json"},
|
||||||
|
{"key": "Accept", "value": "application/json"}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"code\": \"soil_moisture\",\n \"name_fa\": \"رطوبت خاک\",\n \"unit\": \"%\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/sensor-data/parameters/",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "sensor-data", "parameters", ""]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "اضافه کردن یا ویرایش پارامتر جدید. در ParameterUpdateLog ثبت میشود."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import SensorData, SensorParameter
|
||||||
|
|
||||||
|
|
||||||
|
class SensorDataUpdateSerializer(serializers.Serializer):
|
||||||
|
"""سریالایزر ورودی برای آپدیت داده سنسور."""
|
||||||
|
|
||||||
|
location_id = serializers.IntegerField(required=True)
|
||||||
|
soil_moisture = serializers.FloatField(required=False, allow_null=True)
|
||||||
|
soil_temperature = serializers.FloatField(required=False, allow_null=True)
|
||||||
|
soil_ph = serializers.FloatField(required=False, allow_null=True)
|
||||||
|
electrical_conductivity = serializers.FloatField(required=False, allow_null=True)
|
||||||
|
nitrogen = serializers.FloatField(required=False, allow_null=True)
|
||||||
|
phosphorus = serializers.FloatField(required=False, allow_null=True)
|
||||||
|
potassium = serializers.FloatField(required=False, allow_null=True)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorDataResponseSerializer(serializers.ModelSerializer):
|
||||||
|
"""سریالایزر خروجی برای SensorData."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SensorData
|
||||||
|
fields = [
|
||||||
|
"uuid_sensor",
|
||||||
|
"location_id",
|
||||||
|
"soil_moisture",
|
||||||
|
"soil_temperature",
|
||||||
|
"soil_ph",
|
||||||
|
"electrical_conductivity",
|
||||||
|
"nitrogen",
|
||||||
|
"phosphorus",
|
||||||
|
"potassium",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SensorParameterSerializer(serializers.Serializer):
|
||||||
|
"""سریالایزر ورودی برای اضافه کردن پارامتر جدید."""
|
||||||
|
|
||||||
|
code = serializers.CharField(max_length=64)
|
||||||
|
name_fa = serializers.CharField(max_length=128)
|
||||||
|
unit = serializers.CharField(max_length=32, required=False, allow_blank=True)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import SensorDataUpdateView, SensorParameterCreateView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
"<uuid:uuid_sensor>/",
|
||||||
|
SensorDataUpdateView.as_view(),
|
||||||
|
name="sensor-data-update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"parameters/",
|
||||||
|
SensorParameterCreateView.as_view(),
|
||||||
|
name="sensor-parameter-create",
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
from django.db import transaction
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from soil_data.models import SoilLocation
|
||||||
|
|
||||||
|
from .models import ParameterUpdateLog, SensorData, SensorDataHistory, SensorParameter
|
||||||
|
from .serializers import (
|
||||||
|
SensorDataResponseSerializer,
|
||||||
|
SensorDataUpdateSerializer,
|
||||||
|
SensorParameterSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorDataUpdateView(APIView):
|
||||||
|
"""
|
||||||
|
آپدیت داده سنسور. هنگام آپدیت، نسخه فعلی در SensorDataHistory ذخیره میشود.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def put(self, request, uuid_sensor):
|
||||||
|
return self._update(request, uuid_sensor)
|
||||||
|
|
||||||
|
def patch(self, request, uuid_sensor):
|
||||||
|
return self._update(request, uuid_sensor, partial=True)
|
||||||
|
|
||||||
|
def _update(self, request, uuid_sensor, partial=False):
|
||||||
|
serializer = SensorDataUpdateSerializer(
|
||||||
|
data=request.data, partial=partial
|
||||||
|
)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response(
|
||||||
|
{"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
location_id = serializer.validated_data.pop("location_id")
|
||||||
|
location = SoilLocation.objects.filter(pk=location_id).first()
|
||||||
|
if not location:
|
||||||
|
return Response(
|
||||||
|
{"code": 404, "msg": "location_id یافت نشد.", "data": None},
|
||||||
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
|
)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
sensor_data, created = SensorData.objects.get_or_create(
|
||||||
|
uuid_sensor=uuid_sensor,
|
||||||
|
defaults={"location": location, **serializer.validated_data},
|
||||||
|
)
|
||||||
|
|
||||||
|
if not created:
|
||||||
|
# آپدیت رکورد اصلی
|
||||||
|
for key, value in serializer.validated_data.items():
|
||||||
|
setattr(sensor_data, key, value)
|
||||||
|
sensor_data.save()
|
||||||
|
|
||||||
|
# ذخیره نسخه جدید (همان مقادیر جدول اصلی) در تاریخچه
|
||||||
|
SensorDataHistory.objects.create(
|
||||||
|
uuid_sensor=sensor_data.uuid_sensor,
|
||||||
|
location_id=sensor_data.location_id,
|
||||||
|
soil_moisture=sensor_data.soil_moisture,
|
||||||
|
soil_temperature=sensor_data.soil_temperature,
|
||||||
|
soil_ph=sensor_data.soil_ph,
|
||||||
|
electrical_conductivity=sensor_data.electrical_conductivity,
|
||||||
|
nitrogen=sensor_data.nitrogen,
|
||||||
|
phosphorus=sensor_data.phosphorus,
|
||||||
|
potassium=sensor_data.potassium,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": SensorDataResponseSerializer(sensor_data).data,
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorParameterCreateView(APIView):
|
||||||
|
"""
|
||||||
|
اضافه کردن پارامتر جدید و ثبت در ParameterUpdateLog.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
serializer = SensorParameterSerializer(data=request.data)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response(
|
||||||
|
{"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
code = serializer.validated_data["code"]
|
||||||
|
name_fa = serializer.validated_data["name_fa"]
|
||||||
|
unit = serializer.validated_data.get("unit", "")
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
parameter, created = SensorParameter.objects.update_or_create(
|
||||||
|
code=code,
|
||||||
|
defaults={"name_fa": name_fa, "unit": unit},
|
||||||
|
)
|
||||||
|
action = (
|
||||||
|
ParameterUpdateLog.ACTION_ADDED
|
||||||
|
if created
|
||||||
|
else ParameterUpdateLog.ACTION_MODIFIED
|
||||||
|
)
|
||||||
|
ParameterUpdateLog.objects.create(parameter=parameter, action=action)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"code": 201,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"id": parameter.id,
|
||||||
|
"code": parameter.code,
|
||||||
|
"name_fa": parameter.name_fa,
|
||||||
|
"unit": parameter.unit,
|
||||||
|
"created_at": parameter.created_at,
|
||||||
|
"action": action,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status=status.HTTP_201_CREATED,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user