From 9ec0807d3ced5030ef2460bdbaf9bf6abd5f3eaf Mon Sep 17 00:00:00 2001 From: Mohammad Sajad Pourajam Date: Fri, 27 Feb 2026 13:31:16 +0330 Subject: [PATCH] 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. --- config/settings.py | 1 + config/urls.py | 1 + scripts/fix_sensor_data_tables.sh | 12 ++ sensor_data/__init__.py | 0 sensor_data/admin.py | 48 +++++++ sensor_data/apps.py | 7 + sensor_data/management/__init__.py | 0 sensor_data/management/commands/__init__.py | 0 .../commands/seed_sensor_parameters.py | 40 ++++++ sensor_data/migrations/0001_initial.py | 88 +++++++++++++ .../0002_seed_initial_parameters.py | 13 ++ sensor_data/migrations/__init__.py | 0 sensor_data/models.py | 123 ++++++++++++++++++ sensor_data/postman/sensor_data.json | 73 +++++++++++ sensor_data/serializers.py | 44 +++++++ sensor_data/urls.py | 16 +++ sensor_data/views.py | 123 ++++++++++++++++++ 17 files changed, 589 insertions(+) create mode 100644 scripts/fix_sensor_data_tables.sh create mode 100644 sensor_data/__init__.py create mode 100644 sensor_data/admin.py create mode 100644 sensor_data/apps.py create mode 100644 sensor_data/management/__init__.py create mode 100644 sensor_data/management/commands/__init__.py create mode 100644 sensor_data/management/commands/seed_sensor_parameters.py create mode 100644 sensor_data/migrations/0001_initial.py create mode 100644 sensor_data/migrations/0002_seed_initial_parameters.py create mode 100644 sensor_data/migrations/__init__.py create mode 100644 sensor_data/models.py create mode 100644 sensor_data/postman/sensor_data.json create mode 100644 sensor_data/serializers.py create mode 100644 sensor_data/urls.py create mode 100644 sensor_data/views.py diff --git a/config/settings.py b/config/settings.py index e51981e..e895d67 100644 --- a/config/settings.py +++ b/config/settings.py @@ -22,6 +22,7 @@ INSTALLED_APPS = [ "corsheaders", "tasks", "soil_data", + "sensor_data", ] MIDDLEWARE = [ diff --git a/config/urls.py b/config/urls.py index ff5d222..b7776bd 100644 --- a/config/urls.py +++ b/config/urls.py @@ -5,4 +5,5 @@ urlpatterns = [ path("admin/", admin.site.urls), path("api/tasks/", include("tasks.urls")), path("api/soil-data/", include("soil_data.urls")), + path("api/sensor-data/", include("sensor_data.urls")), ] diff --git a/scripts/fix_sensor_data_tables.sh b/scripts/fix_sensor_data_tables.sh new file mode 100644 index 0000000..143503b --- /dev/null +++ b/scripts/fix_sensor_data_tables.sh @@ -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." diff --git a/sensor_data/__init__.py b/sensor_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sensor_data/admin.py b/sensor_data/admin.py new file mode 100644 index 0000000..761b4da --- /dev/null +++ b/sensor_data/admin.py @@ -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") diff --git a/sensor_data/apps.py b/sensor_data/apps.py new file mode 100644 index 0000000..bf7b3fc --- /dev/null +++ b/sensor_data/apps.py @@ -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" diff --git a/sensor_data/management/__init__.py b/sensor_data/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sensor_data/management/commands/__init__.py b/sensor_data/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sensor_data/management/commands/seed_sensor_parameters.py b/sensor_data/management/commands/seed_sensor_parameters.py new file mode 100644 index 0000000..0d1310d --- /dev/null +++ b/sensor_data/management/commands/seed_sensor_parameters.py @@ -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.") + ) diff --git a/sensor_data/migrations/0001_initial.py b/sensor_data/migrations/0001_initial.py new file mode 100644 index 0000000..abb16d3 --- /dev/null +++ b/sensor_data/migrations/0001_initial.py @@ -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'], + }, + ), + ] diff --git a/sensor_data/migrations/0002_seed_initial_parameters.py b/sensor_data/migrations/0002_seed_initial_parameters.py new file mode 100644 index 0000000..ec952e7 --- /dev/null +++ b/sensor_data/migrations/0002_seed_initial_parameters.py @@ -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 = [ + ] diff --git a/sensor_data/migrations/__init__.py b/sensor_data/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sensor_data/models.py b/sensor_data/models.py new file mode 100644 index 0000000..6ff0f18 --- /dev/null +++ b/sensor_data/models.py @@ -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}" diff --git a/sensor_data/postman/sensor_data.json b/sensor_data/postman/sensor_data.json new file mode 100644 index 0000000..1ac164d --- /dev/null +++ b/sensor_data/postman/sensor_data.json @@ -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 ثبت می‌شود." + } + ] +} diff --git a/sensor_data/serializers.py b/sensor_data/serializers.py new file mode 100644 index 0000000..50ac472 --- /dev/null +++ b/sensor_data/serializers.py @@ -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) diff --git a/sensor_data/urls.py b/sensor_data/urls.py new file mode 100644 index 0000000..4d81ff2 --- /dev/null +++ b/sensor_data/urls.py @@ -0,0 +1,16 @@ +from django.urls import path + +from .views import SensorDataUpdateView, SensorParameterCreateView + +urlpatterns = [ + path( + "/", + SensorDataUpdateView.as_view(), + name="sensor-data-update", + ), + path( + "parameters/", + SensorParameterCreateView.as_view(), + name="sensor-parameter-create", + ), +] diff --git a/sensor_data/views.py b/sensor_data/views.py new file mode 100644 index 0000000..c80e038 --- /dev/null +++ b/sensor_data/views.py @@ -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, + )