UPDATE
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.1.15 on 2026-04-04 21:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="SensorExternalRequestLog",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("farm_uuid", models.UUIDField(db_index=True)),
|
||||||
|
("sensor_catalog_uuid", models.UUIDField(blank=True, db_index=True, null=True)),
|
||||||
|
("physical_device_uuid", models.UUIDField(db_index=True)),
|
||||||
|
("payload", models.JSONField(blank=True, default=dict)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "sensor_external_request_logs",
|
||||||
|
"ordering": ["-created_at", "-id"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class SensorExternalRequestLog(models.Model):
|
||||||
|
farm_uuid = models.UUIDField(db_index=True)
|
||||||
|
sensor_catalog_uuid = models.UUIDField(null=True, blank=True, db_index=True)
|
||||||
|
physical_device_uuid = models.UUIDField(db_index=True)
|
||||||
|
payload = models.JSONField(default=dict, blank=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "sensor_external_request_logs"
|
||||||
|
ordering = ["-created_at", "-id"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.physical_device_uuid}:{self.created_at.isoformat()}"
|
||||||
@@ -1,5 +1,86 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from farm_hub.models import FarmSensor
|
||||||
|
from sensor_catalog.models import SensorCatalog
|
||||||
|
|
||||||
|
from .models import SensorExternalRequestLog
|
||||||
|
|
||||||
|
|
||||||
class SensorExternalRequestSerializer(serializers.Serializer):
|
class SensorExternalRequestSerializer(serializers.Serializer):
|
||||||
|
uuid = serializers.UUIDField()
|
||||||
payload = serializers.JSONField(required=False, default=dict)
|
payload = serializers.JSONField(required=False, default=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorExternalRequestLogQuerySerializer(serializers.Serializer):
|
||||||
|
farm_uuid = serializers.UUIDField()
|
||||||
|
page = serializers.IntegerField(required=False, min_value=1, default=1)
|
||||||
|
page_size = serializers.IntegerField(required=False, min_value=1, max_value=100, default=20)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorExternalRequestLogSerializer(serializers.ModelSerializer):
|
||||||
|
farm_sensor = serializers.SerializerMethodField()
|
||||||
|
sensor_catalog = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SensorExternalRequestLog
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"farm_uuid",
|
||||||
|
"sensor_catalog_uuid",
|
||||||
|
"physical_device_uuid",
|
||||||
|
"farm_sensor",
|
||||||
|
"sensor_catalog",
|
||||||
|
"payload",
|
||||||
|
"created_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_farm_sensor(self, obj):
|
||||||
|
farm_sensor_map = self.context.get("farm_sensor_map", {})
|
||||||
|
farm_sensor = farm_sensor_map.get((obj.farm_uuid, obj.sensor_catalog_uuid, obj.physical_device_uuid))
|
||||||
|
if farm_sensor is None:
|
||||||
|
return None
|
||||||
|
return FarmSensorLogSerializer(farm_sensor).data
|
||||||
|
|
||||||
|
def get_sensor_catalog(self, obj):
|
||||||
|
farm_sensor_map = self.context.get("farm_sensor_map", {})
|
||||||
|
farm_sensor = farm_sensor_map.get((obj.farm_uuid, obj.sensor_catalog_uuid, obj.physical_device_uuid))
|
||||||
|
if farm_sensor is None or farm_sensor.sensor_catalog is None:
|
||||||
|
return None
|
||||||
|
return SensorCatalogLogSerializer(farm_sensor.sensor_catalog).data
|
||||||
|
|
||||||
|
|
||||||
|
class FarmSensorLogSerializer(serializers.ModelSerializer):
|
||||||
|
sensor_catalog_uuid = serializers.UUIDField(source="sensor_catalog.uuid", read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FarmSensor
|
||||||
|
fields = [
|
||||||
|
"uuid",
|
||||||
|
"sensor_catalog_uuid",
|
||||||
|
"physical_device_uuid",
|
||||||
|
"name",
|
||||||
|
"sensor_type",
|
||||||
|
"is_active",
|
||||||
|
"specifications",
|
||||||
|
"power_source",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SensorCatalogLogSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SensorCatalog
|
||||||
|
fields = [
|
||||||
|
"uuid",
|
||||||
|
"code",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"customizable_fields",
|
||||||
|
"supported_power_sources",
|
||||||
|
"returned_data_fields",
|
||||||
|
"sample_payload",
|
||||||
|
"is_active",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,20 +1,91 @@
|
|||||||
from django.db import ProgrammingError, OperationalError
|
from django.db import OperationalError, ProgrammingError, transaction
|
||||||
|
|
||||||
|
from farm_hub.models import FarmSensor
|
||||||
from notifications.services import create_notification_for_farm_uuid
|
from notifications.services import create_notification_for_farm_uuid
|
||||||
|
|
||||||
|
from .models import SensorExternalRequestLog
|
||||||
DEFAULT_SENSOR_EXTERNAL_FARM_UUID = "11111111-1111-1111-1111-111111111111"
|
|
||||||
|
|
||||||
|
|
||||||
def create_sensor_external_notification(*, payload=None):
|
def get_sensor_external_request_logs_for_farm(*, farm_uuid):
|
||||||
payload = payload or {}
|
|
||||||
try:
|
try:
|
||||||
return create_notification_for_farm_uuid(
|
return SensorExternalRequestLog.objects.filter(farm_uuid=farm_uuid).order_by("-created_at", "-id")
|
||||||
farm_uuid=DEFAULT_SENSOR_EXTERNAL_FARM_UUID,
|
except (ProgrammingError, OperationalError) as exc:
|
||||||
title="Sensor external API request",
|
raise ValueError("Sensor external API tables are not migrated.") from exc
|
||||||
message="A request was received by sensor_external_api.",
|
|
||||||
level="info",
|
|
||||||
metadata={"payload": payload},
|
def get_farm_sensor_map_for_logs(*, logs):
|
||||||
|
try:
|
||||||
|
logs = list(logs)
|
||||||
|
if not logs:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
farm_sensor_queryset = (
|
||||||
|
FarmSensor.objects.select_related("farm", "sensor_catalog")
|
||||||
|
.filter(
|
||||||
|
farm__farm_uuid__in={log.farm_uuid for log in logs},
|
||||||
|
physical_device_uuid__in={log.physical_device_uuid for log in logs},
|
||||||
|
)
|
||||||
|
.order_by("-created_at", "-id")
|
||||||
|
)
|
||||||
|
|
||||||
|
farm_sensor_map = {}
|
||||||
|
for farm_sensor in farm_sensor_queryset:
|
||||||
|
key = (
|
||||||
|
farm_sensor.farm.farm_uuid,
|
||||||
|
farm_sensor.sensor_catalog.uuid if farm_sensor.sensor_catalog else None,
|
||||||
|
farm_sensor.physical_device_uuid,
|
||||||
|
)
|
||||||
|
farm_sensor_map.setdefault(key, farm_sensor)
|
||||||
|
|
||||||
|
return farm_sensor_map
|
||||||
|
except (ProgrammingError, OperationalError) as exc:
|
||||||
|
raise ValueError("Sensor external API tables are not migrated.") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_sensor_external_request_log(*, farm_uuid, sensor_catalog_uuid, physical_device_uuid):
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
SensorExternalRequestLog.objects.filter(
|
||||||
|
farm_uuid=farm_uuid,
|
||||||
|
sensor_catalog_uuid=sensor_catalog_uuid,
|
||||||
|
physical_device_uuid=physical_device_uuid,
|
||||||
|
)
|
||||||
|
.order_by("-created_at", "-id")
|
||||||
|
.first()
|
||||||
)
|
)
|
||||||
except (ProgrammingError, OperationalError) as exc:
|
except (ProgrammingError, OperationalError) as exc:
|
||||||
raise ValueError("Notifications table is not migrated.") from exc
|
raise ValueError("Sensor external API tables are not migrated.") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def create_sensor_external_notification(*, physical_device_uuid, payload=None):
|
||||||
|
payload = payload or {}
|
||||||
|
sensor = (
|
||||||
|
FarmSensor.objects.select_related("farm", "sensor_catalog")
|
||||||
|
.filter(physical_device_uuid=physical_device_uuid)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if sensor is None:
|
||||||
|
raise ValueError("Physical device not found.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
SensorExternalRequestLog.objects.create(
|
||||||
|
farm_uuid=sensor.farm.farm_uuid,
|
||||||
|
sensor_catalog_uuid=sensor.sensor_catalog.uuid if sensor.sensor_catalog else None,
|
||||||
|
physical_device_uuid=sensor.physical_device_uuid,
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
return create_notification_for_farm_uuid(
|
||||||
|
farm_uuid=sensor.farm.farm_uuid,
|
||||||
|
title="Sensor external API request",
|
||||||
|
message=f"Payload received from device {sensor.physical_device_uuid}.",
|
||||||
|
level="info",
|
||||||
|
metadata={
|
||||||
|
"farm_uuid": str(sensor.farm.farm_uuid),
|
||||||
|
"sensor_catalog_uuid": str(sensor.sensor_catalog.uuid) if sensor.sensor_catalog else None,
|
||||||
|
"physical_device_uuid": str(sensor.physical_device_uuid),
|
||||||
|
"payload": payload,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except (ProgrammingError, OperationalError) as exc:
|
||||||
|
raise ValueError("Sensor external API tables are not migrated.") from exc
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ from django.contrib.auth import get_user_model
|
|||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
|
|
||||||
from farm_hub.models import FarmHub, FarmType
|
from farm_hub.models import FarmHub, FarmSensor, FarmType
|
||||||
from notifications.models import FarmNotification
|
from notifications.models import FarmNotification
|
||||||
|
from sensor_catalog.models import SensorCatalog
|
||||||
|
|
||||||
from .views import SensorExternalAPIView
|
from .models import SensorExternalRequestLog
|
||||||
|
from .services import get_latest_sensor_external_request_log
|
||||||
|
from .views import SensorExternalAPIView, SensorExternalRequestLogListAPIView
|
||||||
|
|
||||||
|
|
||||||
@override_settings(SENSOR_EXTERNAL_API_KEY="12345")
|
@override_settings(SENSOR_EXTERNAL_API_KEY="12345")
|
||||||
@@ -23,20 +26,34 @@ class SensorExternalAPIViewTests(TestCase):
|
|||||||
owner=self.user,
|
owner=self.user,
|
||||||
farm_type=self.farm_type,
|
farm_type=self.farm_type,
|
||||||
name="Farm External",
|
name="Farm External",
|
||||||
farm_uuid="11111111-1111-1111-1111-111111111111",
|
)
|
||||||
|
self.sensor_catalog = SensorCatalog.objects.create(
|
||||||
|
code="ext-sensor-v1",
|
||||||
|
name="External Sensor",
|
||||||
|
)
|
||||||
|
self.sensor = FarmSensor.objects.create(
|
||||||
|
farm=self.farm,
|
||||||
|
sensor_catalog=self.sensor_catalog,
|
||||||
|
physical_device_uuid="11111111-1111-1111-1111-111111111111",
|
||||||
|
name="External device",
|
||||||
|
sensor_type="weather_station",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_requires_api_key(self):
|
def test_requires_api_key(self):
|
||||||
request = self.factory.post("/api/sensor-external-api/", {"payload": {"temp": 12}}, format="json")
|
request = self.factory.post(
|
||||||
|
"/api/sensor-external-api/",
|
||||||
|
{"uuid": str(self.sensor.physical_device_uuid), "payload": {"temp": 12}},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
response = SensorExternalAPIView.as_view()(request)
|
response = SensorExternalAPIView.as_view()(request)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 401)
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
||||||
def test_creates_notification_for_fixed_farm_uuid(self):
|
def test_creates_notification_and_request_log_for_device_uuid(self):
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
"/api/sensor-external-api/",
|
"/api/sensor-external-api/",
|
||||||
{"payload": {"temp": 12}},
|
{"uuid": str(self.sensor.physical_device_uuid), "payload": {"temp": 12}},
|
||||||
format="json",
|
format="json",
|
||||||
HTTP_X_API_KEY="12345",
|
HTTP_X_API_KEY="12345",
|
||||||
)
|
)
|
||||||
@@ -50,3 +67,166 @@ class SensorExternalAPIViewTests(TestCase):
|
|||||||
title="Sensor external API request",
|
title="Sensor external API request",
|
||||||
).exists()
|
).exists()
|
||||||
)
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
SensorExternalRequestLog.objects.filter(
|
||||||
|
farm_uuid=self.farm.farm_uuid,
|
||||||
|
sensor_catalog_uuid=self.sensor_catalog.uuid,
|
||||||
|
physical_device_uuid=self.sensor.physical_device_uuid,
|
||||||
|
payload={"temp": 12},
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_returns_404_for_unknown_device_uuid(self):
|
||||||
|
request = self.factory.post(
|
||||||
|
"/api/sensor-external-api/",
|
||||||
|
{"uuid": "22222222-2222-2222-2222-222222222222", "payload": {"temp": 12}},
|
||||||
|
format="json",
|
||||||
|
HTTP_X_API_KEY="12345",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = SensorExternalAPIView.as_view()(request)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorExternalServiceTests(TestCase):
|
||||||
|
def test_get_latest_sensor_external_request_log_returns_latest_matching_record(self):
|
||||||
|
first_log = SensorExternalRequestLog.objects.create(
|
||||||
|
farm_uuid="11111111-1111-1111-1111-111111111111",
|
||||||
|
sensor_catalog_uuid="22222222-2222-2222-2222-222222222222",
|
||||||
|
physical_device_uuid="33333333-3333-3333-3333-333333333333",
|
||||||
|
payload={"temp": 12},
|
||||||
|
)
|
||||||
|
latest_log = SensorExternalRequestLog.objects.create(
|
||||||
|
farm_uuid=first_log.farm_uuid,
|
||||||
|
sensor_catalog_uuid=first_log.sensor_catalog_uuid,
|
||||||
|
physical_device_uuid=first_log.physical_device_uuid,
|
||||||
|
payload={"temp": 18},
|
||||||
|
)
|
||||||
|
SensorExternalRequestLog.objects.create(
|
||||||
|
farm_uuid=first_log.farm_uuid,
|
||||||
|
sensor_catalog_uuid=first_log.sensor_catalog_uuid,
|
||||||
|
physical_device_uuid="44444444-4444-4444-4444-444444444444",
|
||||||
|
payload={"temp": 25},
|
||||||
|
)
|
||||||
|
|
||||||
|
log = get_latest_sensor_external_request_log(
|
||||||
|
farm_uuid=first_log.farm_uuid,
|
||||||
|
sensor_catalog_uuid=first_log.sensor_catalog_uuid,
|
||||||
|
physical_device_uuid=first_log.physical_device_uuid,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNotNone(log)
|
||||||
|
self.assertEqual(log.id, latest_log.id)
|
||||||
|
self.assertEqual(log.payload, {"temp": 18})
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(SENSOR_EXTERNAL_API_KEY="12345")
|
||||||
|
class SensorExternalRequestLogListAPIViewTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.factory = APIRequestFactory()
|
||||||
|
self.user = get_user_model().objects.create_user(
|
||||||
|
username="sensor-external-log-user",
|
||||||
|
password="secret123",
|
||||||
|
email="sensor-external-log@example.com",
|
||||||
|
phone_number="09120000016",
|
||||||
|
)
|
||||||
|
self.farm_type = FarmType.objects.create(name="لاگ سنسور خارجی")
|
||||||
|
self.farm = FarmHub.objects.create(
|
||||||
|
owner=self.user,
|
||||||
|
farm_type=self.farm_type,
|
||||||
|
name="Farm Log External",
|
||||||
|
farm_uuid="11111111-1111-1111-1111-111111111111",
|
||||||
|
)
|
||||||
|
self.farm_uuid = self.farm.farm_uuid
|
||||||
|
self.other_farm_uuid = "aaaaaaaa-1111-1111-1111-111111111111"
|
||||||
|
self.first_catalog = SensorCatalog.objects.create(
|
||||||
|
code="ext-sensor-log-1",
|
||||||
|
name="External Sensor Log 1",
|
||||||
|
description="Sensor catalog for first log",
|
||||||
|
returned_data_fields=["temp"],
|
||||||
|
)
|
||||||
|
self.second_catalog = SensorCatalog.objects.create(
|
||||||
|
code="ext-sensor-log-2",
|
||||||
|
name="External Sensor Log 2",
|
||||||
|
description="Sensor catalog for second log",
|
||||||
|
returned_data_fields=["humidity"],
|
||||||
|
)
|
||||||
|
self.first_sensor = FarmSensor.objects.create(
|
||||||
|
farm=self.farm,
|
||||||
|
sensor_catalog=self.first_catalog,
|
||||||
|
physical_device_uuid="33333333-3333-3333-3333-333333333333",
|
||||||
|
name="External device 1",
|
||||||
|
sensor_type="weather_station",
|
||||||
|
specifications={"model": "FH-1"},
|
||||||
|
power_source={"type": "battery"},
|
||||||
|
)
|
||||||
|
self.second_sensor = FarmSensor.objects.create(
|
||||||
|
farm=self.farm,
|
||||||
|
sensor_catalog=self.second_catalog,
|
||||||
|
physical_device_uuid="55555555-5555-5555-5555-555555555555",
|
||||||
|
name="External device 2",
|
||||||
|
sensor_type="soil_sensor",
|
||||||
|
specifications={"model": "FH-2"},
|
||||||
|
power_source={"type": "solar"},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.first_log = SensorExternalRequestLog.objects.create(
|
||||||
|
farm_uuid=self.farm_uuid,
|
||||||
|
sensor_catalog_uuid=self.first_catalog.uuid,
|
||||||
|
physical_device_uuid=self.first_sensor.physical_device_uuid,
|
||||||
|
payload={"temp": 12},
|
||||||
|
)
|
||||||
|
self.second_log = SensorExternalRequestLog.objects.create(
|
||||||
|
farm_uuid=self.farm_uuid,
|
||||||
|
sensor_catalog_uuid=self.second_catalog.uuid,
|
||||||
|
physical_device_uuid=self.second_sensor.physical_device_uuid,
|
||||||
|
payload={"temp": 18},
|
||||||
|
)
|
||||||
|
SensorExternalRequestLog.objects.create(
|
||||||
|
farm_uuid=self.other_farm_uuid,
|
||||||
|
sensor_catalog_uuid="66666666-6666-6666-6666-666666666666",
|
||||||
|
physical_device_uuid="77777777-7777-7777-7777-777777777777",
|
||||||
|
payload={"temp": 24},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_requires_api_key(self):
|
||||||
|
request = self.factory.get(f"/api/sensor-external-api/logs/?farm_uuid={self.farm_uuid}")
|
||||||
|
|
||||||
|
response = SensorExternalRequestLogListAPIView.as_view()(request)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
||||||
|
def test_returns_paginated_logs_for_farm_uuid(self):
|
||||||
|
request = self.factory.get(
|
||||||
|
f"/api/sensor-external-api/logs/?farm_uuid={self.farm_uuid}&page=1&page_size=1",
|
||||||
|
HTTP_X_API_KEY="12345",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = SensorExternalRequestLogListAPIView.as_view()(request)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.data["code"], 200)
|
||||||
|
self.assertEqual(response.data["count"], 2)
|
||||||
|
self.assertEqual(len(response.data["data"]), 1)
|
||||||
|
self.assertEqual(response.data["data"][0]["id"], self.second_log.id)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data["data"][0]["physical_device_uuid"],
|
||||||
|
str(self.second_log.physical_device_uuid),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data["data"][0]["sensor_catalog"]["uuid"],
|
||||||
|
str(self.second_catalog.uuid),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data["data"][0]["sensor_catalog"]["name"],
|
||||||
|
self.second_catalog.name,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data["data"][0]["farm_sensor"]["uuid"],
|
||||||
|
str(self.second_sensor.uuid),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data["data"][0]["farm_sensor"]["physical_device_uuid"],
|
||||||
|
str(self.second_sensor.physical_device_uuid),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import SensorExternalAPIView
|
from .views import SensorExternalAPIView, SensorExternalRequestLogListAPIView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", SensorExternalAPIView.as_view(), name="sensor-external-api"),
|
path("", SensorExternalAPIView.as_view(), name="sensor-external-api"),
|
||||||
|
path("logs/", SensorExternalRequestLogListAPIView.as_view(), name="sensor-external-api-log-list"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from rest_framework import status
|
from rest_framework import serializers, status
|
||||||
|
from rest_framework.pagination import PageNumberPagination
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
@@ -8,8 +9,22 @@ from config.swagger import code_response
|
|||||||
from notifications.serializers import FarmNotificationSerializer
|
from notifications.serializers import FarmNotificationSerializer
|
||||||
|
|
||||||
from .authentication import SensorExternalAPIKeyAuthentication
|
from .authentication import SensorExternalAPIKeyAuthentication
|
||||||
from .serializers import SensorExternalRequestSerializer
|
from .serializers import (
|
||||||
from .services import create_sensor_external_notification
|
SensorExternalRequestLogQuerySerializer,
|
||||||
|
SensorExternalRequestLogSerializer,
|
||||||
|
SensorExternalRequestSerializer,
|
||||||
|
)
|
||||||
|
from .services import (
|
||||||
|
create_sensor_external_notification,
|
||||||
|
get_farm_sensor_map_for_logs,
|
||||||
|
get_sensor_external_request_logs_for_farm,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorExternalRequestLogPagination(PageNumberPagination):
|
||||||
|
page_size = 20
|
||||||
|
page_size_query_param = "page_size"
|
||||||
|
max_page_size = 100
|
||||||
|
|
||||||
|
|
||||||
class SensorExternalAPIView(APIView):
|
class SensorExternalAPIView(APIView):
|
||||||
@@ -32,8 +47,8 @@ class SensorExternalAPIView(APIView):
|
|||||||
responses={
|
responses={
|
||||||
201: code_response("SensorExternalAPIResponse", data=FarmNotificationSerializer()),
|
201: code_response("SensorExternalAPIResponse", data=FarmNotificationSerializer()),
|
||||||
401: code_response("SensorExternalAPIUnauthorizedResponse"),
|
401: code_response("SensorExternalAPIUnauthorizedResponse"),
|
||||||
404: code_response("SensorExternalAPIFarmNotFoundResponse"),
|
404: code_response("SensorExternalAPIDeviceNotFoundResponse"),
|
||||||
503: code_response("SensorExternalAPINotificationsUnavailableResponse"),
|
503: code_response("SensorExternalAPIUnavailableResponse"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
@@ -41,14 +56,87 @@ class SensorExternalAPIView(APIView):
|
|||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
notification = create_sensor_external_notification(payload=serializer.validated_data.get("payload"))
|
notification = create_sensor_external_notification(
|
||||||
|
physical_device_uuid=serializer.validated_data["uuid"],
|
||||||
|
payload=serializer.validated_data.get("payload"),
|
||||||
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
if str(exc) == "Notifications table is not migrated.":
|
if "not migrated" in str(exc):
|
||||||
return Response(
|
return Response(
|
||||||
{"code": 503, "msg": "Notifications table is not ready. Run migrations."},
|
{"code": 503, "msg": "Required tables are not ready. Run migrations."},
|
||||||
status=status.HTTP_503_SERVICE_UNAVAILABLE,
|
status=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
return Response({"code": 404, "msg": "Farm not found."}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"code": 404, "msg": "Physical device not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
data = FarmNotificationSerializer(notification).data
|
data = FarmNotificationSerializer(notification).data
|
||||||
return Response({"code": 201, "msg": "success", "data": data}, status=status.HTTP_201_CREATED)
|
return Response({"code": 201, "msg": "success", "data": data}, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorExternalRequestLogListAPIView(APIView):
|
||||||
|
authentication_classes = [SensorExternalAPIKeyAuthentication]
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
pagination_class = SensorExternalRequestLogPagination
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
tags=["Sensor External API"],
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(name="farm_uuid", type=OpenApiTypes.UUID, location=OpenApiParameter.QUERY, required=True),
|
||||||
|
OpenApiParameter(name="page", type=OpenApiTypes.INT, location=OpenApiParameter.QUERY, required=False),
|
||||||
|
OpenApiParameter(name="page_size", type=OpenApiTypes.INT, location=OpenApiParameter.QUERY, required=False),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="X-API-Key",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location=OpenApiParameter.HEADER,
|
||||||
|
required=True,
|
||||||
|
default="12345",
|
||||||
|
description="API key for sensor external API.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses={
|
||||||
|
200: code_response(
|
||||||
|
"SensorExternalRequestLogListResponse",
|
||||||
|
data=SensorExternalRequestLogSerializer(many=True),
|
||||||
|
extra_fields={
|
||||||
|
"count": serializers.IntegerField(),
|
||||||
|
"next": serializers.CharField(allow_null=True),
|
||||||
|
"previous": serializers.CharField(allow_null=True),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
401: code_response("SensorExternalRequestLogListUnauthorizedResponse"),
|
||||||
|
503: code_response("SensorExternalRequestLogListUnavailableResponse"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
serializer = SensorExternalRequestLogQuerySerializer(data=request.query_params)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
queryset = get_sensor_external_request_logs_for_farm(
|
||||||
|
farm_uuid=serializer.validated_data["farm_uuid"],
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return Response(
|
||||||
|
{"code": 503, "msg": "Required tables are not ready. Run migrations."},
|
||||||
|
status=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
paginator = self.pagination_class()
|
||||||
|
paginator.page_size = serializer.validated_data["page_size"]
|
||||||
|
page = paginator.paginate_queryset(queryset, request, view=self)
|
||||||
|
farm_sensor_map = get_farm_sensor_map_for_logs(logs=page)
|
||||||
|
data = SensorExternalRequestLogSerializer(
|
||||||
|
page,
|
||||||
|
many=True,
|
||||||
|
context={"farm_sensor_map": farm_sensor_map},
|
||||||
|
).data
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"count": paginator.page.paginator.count,
|
||||||
|
"next": paginator.get_next_link(),
|
||||||
|
"previous": paginator.get_previous_link(),
|
||||||
|
"data": data,
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user