303 lines
8.9 KiB
Markdown
303 lines
8.9 KiB
Markdown
|
|
# مستند API کاتالوگ سنسورها
|
||
|
|
|
||
|
|
این فایل API ثبتشده در `sensor_catalog/urls.py` را بهصورت کامل توضیح میدهد.
|
||
|
|
|
||
|
|
## فایل route
|
||
|
|
|
||
|
|
فایل route این app:
|
||
|
|
|
||
|
|
`sensor_catalog/urls.py`
|
||
|
|
|
||
|
|
محتوای آن:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from django.urls import path
|
||
|
|
|
||
|
|
from .views import SensorCatalogListView
|
||
|
|
|
||
|
|
urlpatterns = [
|
||
|
|
path("", SensorCatalogListView.as_view(), name="sensor-catalog-list"),
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
## آدرس نهایی endpoint
|
||
|
|
|
||
|
|
این route در `config/urls.py` اینطور mount شده است:
|
||
|
|
|
||
|
|
```python
|
||
|
|
path("api/sensor-catalog/", include("sensor_catalog.urls"))
|
||
|
|
```
|
||
|
|
|
||
|
|
پس آدرس نهایی API این است:
|
||
|
|
|
||
|
|
`GET /api/sensor-catalog/`
|
||
|
|
|
||
|
|
## هدف API
|
||
|
|
|
||
|
|
این endpoint برای گرفتن لیست کاتالوگ سنسورها استفاده میشود.
|
||
|
|
|
||
|
|
منظور از کاتالوگ سنسور، تعریف مرجع هر نوع سنسور است؛ مثلا:
|
||
|
|
|
||
|
|
- کد سنسور
|
||
|
|
- نام سنسور
|
||
|
|
- توضیحات
|
||
|
|
- فیلدهای خروجی سنسور
|
||
|
|
- نمونه payload
|
||
|
|
- منبع تغذیههای پشتیبانیشده
|
||
|
|
|
||
|
|
این API بیشتر برای frontend یا تنظیمات سیستم مفید است تا بداند چه نوع سنسورهایی در سیستم تعریف شدهاند و هر سنسور چه ساختاری دارد.
|
||
|
|
|
||
|
|
## View مربوطه
|
||
|
|
|
||
|
|
این endpoint در فایل `sensor_catalog/views.py` پیادهسازی شده است:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class SensorCatalogListView(APIView):
|
||
|
|
permission_classes = [IsAuthenticated]
|
||
|
|
```
|
||
|
|
|
||
|
|
این یعنی:
|
||
|
|
|
||
|
|
- فقط متد `GET` پشتیبانی میشود
|
||
|
|
- کاربر باید authenticated باشد
|
||
|
|
|
||
|
|
## احراز هویت و دسترسی
|
||
|
|
|
||
|
|
این View از:
|
||
|
|
|
||
|
|
```python
|
||
|
|
permission_classes = [IsAuthenticated]
|
||
|
|
```
|
||
|
|
|
||
|
|
استفاده میکند.
|
||
|
|
|
||
|
|
در این پروژه بهصورت پیشفرض authentication از طریق JWT انجام میشود، چون در `config/settings.py` مقدار زیر تعریف شده:
|
||
|
|
|
||
|
|
```python
|
||
|
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||
|
|
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
پس اگر کاربر توکن معتبر نداشته باشد، این API پاسخ `401 Unauthorized` برمیگرداند.
|
||
|
|
|
||
|
|
## رفتار endpoint
|
||
|
|
|
||
|
|
در متد `get` این View:
|
||
|
|
|
||
|
|
```python
|
||
|
|
def get(self, request):
|
||
|
|
sensors = SensorCatalog.objects.order_by("code")
|
||
|
|
data = SensorCatalogSerializer(sensors, many=True).data
|
||
|
|
return Response({"code": 200, "msg": "success", "data": data}, status=status.HTTP_200_OK)
|
||
|
|
```
|
||
|
|
|
||
|
|
این منطق اجرا میشود:
|
||
|
|
|
||
|
|
1. همه رکوردهای `SensorCatalog` از دیتابیس خوانده میشوند
|
||
|
|
2. خروجی بر اساس `code` مرتب میشود
|
||
|
|
3. دادهها با serializer به JSON تبدیل میشوند
|
||
|
|
4. پاسخ استاندارد با `code` و `msg` و `data` برگردانده میشود
|
||
|
|
|
||
|
|
## مدل دیتابیس
|
||
|
|
|
||
|
|
مدل این API در `sensor_catalog/models.py` قرار دارد:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class SensorCatalog(models.Model):
|
||
|
|
uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True)
|
||
|
|
code = models.CharField(max_length=255, unique=True, db_index=True)
|
||
|
|
name = models.CharField(max_length=255, unique=True, db_index=True)
|
||
|
|
description = models.TextField(blank=True, default="")
|
||
|
|
customizable_fields = models.JSONField(default=list, blank=True)
|
||
|
|
supported_power_sources = models.JSONField(default=list, blank=True)
|
||
|
|
returned_data_fields = models.JSONField(default=list, blank=True)
|
||
|
|
sample_payload = models.JSONField(default=dict, blank=True)
|
||
|
|
is_active = models.BooleanField(default=True)
|
||
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
||
|
|
updated_at = models.DateTimeField(auto_now=True)
|
||
|
|
```
|
||
|
|
|
||
|
|
### معنی فیلدها
|
||
|
|
|
||
|
|
- `uuid`: شناسه یکتا برای هر کاتالوگ
|
||
|
|
- `code`: کد یکتا و فنی سنسور
|
||
|
|
- `name`: نام قابل نمایش سنسور
|
||
|
|
- `description`: توضیح سنسور
|
||
|
|
- `customizable_fields`: فیلدهایی که موقع ساخت/پیکربندی سنسور ممکن است قابل تنظیم باشند
|
||
|
|
- `supported_power_sources`: نوع منبع تغذیههای پشتیبانیشده
|
||
|
|
- `returned_data_fields`: فیلدهایی که این سنسور در payload خود برمیگرداند
|
||
|
|
- `sample_payload`: یک نمونه payload برای درک ساختار داده
|
||
|
|
- `is_active`: فعال یا غیرفعال بودن این کاتالوگ
|
||
|
|
- `created_at` و `updated_at`: زمان ایجاد و آخرین بروزرسانی
|
||
|
|
|
||
|
|
## Serializer خروجی
|
||
|
|
|
||
|
|
serializer این endpoint در `sensor_catalog/serializers.py` تعریف شده است:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class SensorCatalogSerializer(serializers.ModelSerializer):
|
||
|
|
class Meta:
|
||
|
|
model = SensorCatalog
|
||
|
|
fields = [
|
||
|
|
"uuid",
|
||
|
|
"code",
|
||
|
|
"name",
|
||
|
|
"description",
|
||
|
|
"customizable_fields",
|
||
|
|
"supported_power_sources",
|
||
|
|
"returned_data_fields",
|
||
|
|
"sample_payload",
|
||
|
|
"is_active",
|
||
|
|
]
|
||
|
|
read_only_fields = fields
|
||
|
|
```
|
||
|
|
|
||
|
|
### نکته مهم
|
||
|
|
|
||
|
|
این serializer فقط این فیلدها را در خروجی برمیگرداند:
|
||
|
|
|
||
|
|
- `uuid`
|
||
|
|
- `code`
|
||
|
|
- `name`
|
||
|
|
- `description`
|
||
|
|
- `customizable_fields`
|
||
|
|
- `supported_power_sources`
|
||
|
|
- `returned_data_fields`
|
||
|
|
- `sample_payload`
|
||
|
|
- `is_active`
|
||
|
|
|
||
|
|
پس فیلدهای `created_at` و `updated_at` در پاسخ این API نیستند.
|
||
|
|
|
||
|
|
## ورودی API
|
||
|
|
|
||
|
|
این endpoint ورودی body یا query param خاصی ندارد.
|
||
|
|
|
||
|
|
فقط کافی است کاربر authenticated باشد.
|
||
|
|
|
||
|
|
### نمونه درخواست
|
||
|
|
|
||
|
|
```http
|
||
|
|
GET /api/sensor-catalog/
|
||
|
|
Authorization: Bearer <access_token>
|
||
|
|
```
|
||
|
|
|
||
|
|
## خروجی موفق
|
||
|
|
|
||
|
|
نمونه پاسخ موفق:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"code": 200,
|
||
|
|
"msg": "success",
|
||
|
|
"data": [
|
||
|
|
{
|
||
|
|
"uuid": "11111111-1111-1111-1111-111111111111",
|
||
|
|
"code": "sensor_7_soil_moisture_sensor_v1_2",
|
||
|
|
"name": "Sensor 7 - Soil Moisture Sensor v1.2",
|
||
|
|
"description": "Measures only soil moisture using electrical resistance between two metal probes.",
|
||
|
|
"customizable_fields": [],
|
||
|
|
"supported_power_sources": ["solar", "direct_power"],
|
||
|
|
"returned_data_fields": ["soil_moisture", "analog_output", "digital_output"],
|
||
|
|
"sample_payload": {
|
||
|
|
"soil_moisture": 42,
|
||
|
|
"analog_output": 610,
|
||
|
|
"digital_output": 1
|
||
|
|
},
|
||
|
|
"is_active": true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"uuid": "22222222-2222-2222-2222-222222222222",
|
||
|
|
"code": "legacy_sensor",
|
||
|
|
"name": "Legacy Sensor",
|
||
|
|
"description": "",
|
||
|
|
"customizable_fields": [],
|
||
|
|
"supported_power_sources": ["direct_power"],
|
||
|
|
"returned_data_fields": ["status"],
|
||
|
|
"sample_payload": {
|
||
|
|
"status": "offline"
|
||
|
|
},
|
||
|
|
"is_active": false
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## ترتیب خروجی
|
||
|
|
|
||
|
|
خروجی با این دستور مرتب میشود:
|
||
|
|
|
||
|
|
```python
|
||
|
|
SensorCatalog.objects.order_by("code")
|
||
|
|
```
|
||
|
|
|
||
|
|
یعنی لیست همیشه بر اساس `code` به صورت صعودی برگردانده میشود.
|
||
|
|
|
||
|
|
## سناریوهای کاربردی
|
||
|
|
|
||
|
|
این API معمولا برای این موارد استفاده میشود:
|
||
|
|
|
||
|
|
- ساخت dropdown برای انتخاب نوع سنسور
|
||
|
|
- نمایش ساختار داده قابل انتظار از یک سنسور
|
||
|
|
- فهمیدن اینکه هر سنسور چه فیلدهایی برمیگرداند
|
||
|
|
- ساخت فرمهای داینامیک برای پیکربندی سنسور
|
||
|
|
- نمایش `sample_payload` در Swagger یا UI مدیریتی
|
||
|
|
|
||
|
|
## وضعیتهای خطا
|
||
|
|
|
||
|
|
### 401 Unauthorized
|
||
|
|
|
||
|
|
اگر کاربر login نباشد یا توکن معتبر نداشته باشد.
|
||
|
|
|
||
|
|
### 200 با لیست خالی
|
||
|
|
|
||
|
|
اگر هیچ رکوردی در جدول `sensor_catalogs` وجود نداشته باشد، پاسخ موفق است اما `data` خالی خواهد بود:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"code": 200,
|
||
|
|
"msg": "success",
|
||
|
|
"data": []
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## تست موجود
|
||
|
|
|
||
|
|
برای این endpoint تست در فایل `sensor_catalog/tests.py` وجود دارد.
|
||
|
|
|
||
|
|
تست اصلی بررسی میکند که:
|
||
|
|
|
||
|
|
- کاربر authenticated بتواند endpoint را صدا بزند
|
||
|
|
- پاسخ `200` باشد
|
||
|
|
- همه سنسورهای موجود برگردانده شوند
|
||
|
|
|
||
|
|
نمونه assertion:
|
||
|
|
|
||
|
|
```python
|
||
|
|
self.assertEqual(response.status_code, 200)
|
||
|
|
self.assertEqual(response.data["code"], 200)
|
||
|
|
self.assertEqual(len(response.data["data"]), 2)
|
||
|
|
```
|
||
|
|
|
||
|
|
## خلاصه
|
||
|
|
|
||
|
|
API موجود در `sensor_catalog/urls.py` فقط یک endpoint دارد:
|
||
|
|
|
||
|
|
- `GET /api/sensor-catalog/`
|
||
|
|
|
||
|
|
این endpoint:
|
||
|
|
|
||
|
|
- نیاز به احراز هویت دارد
|
||
|
|
- همه کاتالوگهای سنسور را از دیتابیس میخواند
|
||
|
|
- آنها را بر اساس `code` مرتب میکند
|
||
|
|
- اطلاعات ساختاری سنسورها را برای frontend یا پنل مدیریتی برمیگرداند
|
||
|
|
|
||
|
|
## فایلهای مرتبط
|
||
|
|
|
||
|
|
- `sensor_catalog/urls.py`
|
||
|
|
- `sensor_catalog/views.py`
|
||
|
|
- `sensor_catalog/serializers.py`
|
||
|
|
- `sensor_catalog/models.py`
|
||
|
|
- `sensor_catalog/tests.py`
|
||
|
|
- `config/urls.py`
|