UPDATE
This commit is contained in:
+9
-24
@@ -3,6 +3,7 @@ import uuid
|
||||
from django.db import transaction
|
||||
|
||||
from account.seeds import seed_admin_user
|
||||
from sensor_catalog.management import seed_sensor_catalog
|
||||
from sensor_catalog.models import SensorCatalog
|
||||
|
||||
from .catalog import CATALOG_SEED_DATA
|
||||
@@ -16,32 +17,15 @@ ADMIN_FARM_DATA = {
|
||||
"is_active": True,
|
||||
"sensors": [
|
||||
{
|
||||
"sensor_catalog_name": "Sensor 7 - Soil Moisture Sensor v1.2",
|
||||
"physical_device_uuid": uuid.UUID("22222222-2222-2222-2222-222222222221"),
|
||||
"name": "Station 1",
|
||||
"sensor_type": "weather_station",
|
||||
"is_active": True,
|
||||
"specifications": {
|
||||
"model": "CL-SENSE-PRO-X",
|
||||
"firmware": "2.4.1",
|
||||
"manufacturer": "CropLogic",
|
||||
},
|
||||
"power_source": {
|
||||
"type": "hybrid",
|
||||
"battery": {"capacity_mah": 12000, "voltage": 12},
|
||||
"solar": {"panel_watt": 40, "controller": "MPPT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"sensor_catalog_name": "Sensor 7 - Soil Moisture Sensor v1.2",
|
||||
"sensor_catalog_code": "sensor_7_soil_moisture_sensor_v1_2",
|
||||
"physical_device_uuid": uuid.UUID("22222222-2222-2222-2222-222222222222"),
|
||||
"name": "Soil Probe 1",
|
||||
"sensor_type": "soil_probe",
|
||||
"is_active": True,
|
||||
"specifications": {
|
||||
"capabilities": ["soil_moisture", "soil_temperature", "ph", "ec"],
|
||||
"capabilities": ["soil_moisture", "analog_output", "digital_output"],
|
||||
},
|
||||
"power_source": {"type": "battery", "backup": "solar"},
|
||||
"power_source": {"type": "solar"},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -81,12 +65,13 @@ def _get_default_catalog():
|
||||
return FarmType.objects.get(name=default_farm_type_name), created_products[:2]
|
||||
|
||||
|
||||
def _get_sensor_catalog_by_name(name):
|
||||
return SensorCatalog.objects.filter(name=name).first()
|
||||
def _get_sensor_catalog_by_code(code):
|
||||
return SensorCatalog.objects.filter(code=code).first()
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def seed_admin_farm():
|
||||
seed_sensor_catalog()
|
||||
owner, _ = seed_admin_user()
|
||||
farm_type, products = _get_default_catalog()
|
||||
farm, created = FarmHub.objects.update_or_create(
|
||||
@@ -103,8 +88,8 @@ def seed_admin_farm():
|
||||
sensors = []
|
||||
for sensor_data in ADMIN_FARM_DATA["sensors"]:
|
||||
sensor_data = sensor_data.copy()
|
||||
sensor_catalog_name = sensor_data.pop("sensor_catalog_name", None)
|
||||
sensor_data["sensor_catalog"] = _get_sensor_catalog_by_name(sensor_catalog_name) if sensor_catalog_name else None
|
||||
sensor_catalog_code = sensor_data.pop("sensor_catalog_code", None)
|
||||
sensor_data["sensor_catalog"] = _get_sensor_catalog_by_code(sensor_catalog_code) if sensor_catalog_code else None
|
||||
sensors.append(farm.sensors.model(farm=farm, **sensor_data))
|
||||
farm.sensors.bulk_create(sensors)
|
||||
if created:
|
||||
|
||||
@@ -2,6 +2,7 @@ from rest_framework import serializers
|
||||
from access_control.models import SubscriptionPlan
|
||||
from access_control.serializers import SubscriptionPlanSerializer
|
||||
from access_control.catalog import GOLD_PLAN_CODE
|
||||
from access_control.services import get_effective_subscription_plan
|
||||
|
||||
from .models import FarmHub, FarmSensor, FarmType, Product
|
||||
from sensor_catalog.models import SensorCatalog
|
||||
@@ -58,7 +59,7 @@ class FarmSensorSerializer(serializers.ModelSerializer):
|
||||
class FarmHubSerializer(serializers.ModelSerializer):
|
||||
last_updated = serializers.DateTimeField(source="updated_at", read_only=True)
|
||||
farm_type = FarmTypeSerializer(read_only=True)
|
||||
subscription_plan = SubscriptionPlanSerializer(read_only=True)
|
||||
subscription_plan = serializers.SerializerMethodField()
|
||||
products = ProductSerializer(many=True, read_only=True)
|
||||
sensors = FarmSensorSerializer(many=True, read_only=True)
|
||||
area_uuid = serializers.UUIDField(source="current_crop_area.uuid", read_only=True)
|
||||
@@ -78,6 +79,12 @@ class FarmHubSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
read_only_fields = ["farm_uuid", "last_updated"]
|
||||
|
||||
def get_subscription_plan(self, obj):
|
||||
subscription_plan = get_effective_subscription_plan(obj)
|
||||
if subscription_plan is None:
|
||||
return None
|
||||
return SubscriptionPlanSerializer(subscription_plan, context=self.context).data
|
||||
|
||||
|
||||
class FarmSensorWriteSerializer(serializers.ModelSerializer):
|
||||
sensor_catalog_uuid = serializers.UUIDField(write_only=True, required=False)
|
||||
|
||||
+48
-16
@@ -7,6 +7,7 @@ from access_control.services import build_farm_access_profile
|
||||
from access_control.views import FarmAccessProfileView
|
||||
from crop_zoning.models import CropArea
|
||||
from farm_hub.models import FarmHub, FarmType, Product
|
||||
from farm_hub.serializers import FarmHubSerializer
|
||||
from farm_hub.seeds import seed_admin_farm
|
||||
from farm_hub.views import FarmListCreateView, FarmTypeListView, FarmTypeProductsView
|
||||
from sensor_catalog.models import SensorCatalog
|
||||
@@ -46,6 +47,7 @@ class FarmListCreateViewTests(TestCase):
|
||||
self.wheat, _ = Product.objects.get_or_create(farm_type=self.farm_type, name="گندم")
|
||||
self.plan = SubscriptionPlan.objects.create(code="gold", name="Gold")
|
||||
self.weather_station, _ = SensorCatalog.objects.get_or_create(
|
||||
code="sensor_7_soil_moisture_sensor_v1_2",
|
||||
name="Sensor 7 - Soil Moisture Sensor v1.2",
|
||||
defaults={"supported_power_sources": ["solar", "direct_power"]},
|
||||
)
|
||||
@@ -159,23 +161,16 @@ class FarmListCreateViewTests(TestCase):
|
||||
)
|
||||
class FarmSeedTests(TestCase):
|
||||
def test_seed_admin_farm_dispatches_crop_logic_flow_on_create(self):
|
||||
SensorCatalog.objects.get_or_create(
|
||||
name="Sensor 7 - Soil Moisture Sensor v1.2",
|
||||
defaults={"supported_power_sources": ["solar", "direct_power"]},
|
||||
)
|
||||
farm, created = seed_admin_farm()
|
||||
|
||||
self.assertTrue(created)
|
||||
self.assertEqual(farm.farm_uuid.hex, "11111111111111111111111111111111")
|
||||
self.assertEqual(CropArea.objects.count(), 1)
|
||||
self.assertEqual(farm.sensors.count(), 2)
|
||||
self.assertEqual(farm.sensors.count(), 1)
|
||||
self.assertIsNotNone(farm.sensors.first().physical_device_uuid)
|
||||
self.assertTrue(SensorCatalog.objects.filter(code="sensor_7_soil_moisture_sensor_v1_2").exists())
|
||||
|
||||
def test_seed_admin_farm_does_not_dispatch_twice_for_existing_seed(self):
|
||||
SensorCatalog.objects.get_or_create(
|
||||
name="Sensor 7 - Soil Moisture Sensor v1.2",
|
||||
defaults={"supported_power_sources": ["solar", "direct_power"]},
|
||||
)
|
||||
first_farm, first_created = seed_admin_farm()
|
||||
second_farm, second_created = seed_admin_farm()
|
||||
|
||||
@@ -252,7 +247,7 @@ class FarmAccessProfileTests(TestCase):
|
||||
self.plan = SubscriptionPlan.objects.create(code="starter", name="Starter")
|
||||
self.farm_type = FarmType.objects.create(name="گلخانه ای")
|
||||
self.product = Product.objects.create(farm_type=self.farm_type, name="خیار")
|
||||
self.sensor_catalog = SensorCatalog.objects.create(name="Climate Sensor")
|
||||
self.sensor_catalog = SensorCatalog.objects.create(code="climate_sensor", name="Climate Sensor")
|
||||
self.farm = FarmHub.objects.create(
|
||||
owner=self.user,
|
||||
farm_type=self.farm_type,
|
||||
@@ -314,24 +309,61 @@ class FarmAccessProfileTests(TestCase):
|
||||
response = FarmAccessProfileView.as_view()(request, farm_uuid=self.farm.farm_uuid)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data["data"]["groups"]["pages"]["greenhouse-dashboard"]["enabled"], True)
|
||||
self.assertEqual(response.data["data"]["groups"]["widgets"]["sensor-analytics"]["enabled"], True)
|
||||
self.assertNotIn("features", response.data["data"])
|
||||
self.assertNotIn("groups", response.data["data"])
|
||||
self.assertEqual(len(response.data["data"]["matched_rules"]), 3)
|
||||
self.assertTrue(FarmAccessProfile.objects.filter(farm=self.farm).exists())
|
||||
|
||||
def test_sensor_rule_can_match_by_metadata_sensor_name(self):
|
||||
def test_sensor_rule_can_match_by_metadata_sensor_code(self):
|
||||
sensor_page = AccessFeature.objects.create(
|
||||
code="sensor-page",
|
||||
name="Sensor Page",
|
||||
feature_type=AccessFeature.PAGE,
|
||||
)
|
||||
sensor_rule = AccessRule.objects.create(
|
||||
code="sensor-page-by-name",
|
||||
name="Sensor Page By Name",
|
||||
code="sensor-page-by-code",
|
||||
name="Sensor Page By Code",
|
||||
priority=40,
|
||||
metadata={"sensor_catalog_names": [self.sensor_catalog.name]},
|
||||
metadata={"sensor_catalog_codes": [self.sensor_catalog.code]},
|
||||
)
|
||||
sensor_rule.features.add(sensor_page)
|
||||
|
||||
profile = build_farm_access_profile(self.farm)
|
||||
|
||||
self.assertTrue(profile["features"]["sensor-page"]["enabled"])
|
||||
|
||||
def test_build_farm_access_profile_falls_back_to_default_plan(self):
|
||||
default_plan = SubscriptionPlan.objects.create(code="gold", name="Gold", metadata={"is_default": True})
|
||||
fallback_farm = FarmHub.objects.create(
|
||||
owner=self.user,
|
||||
farm_type=self.farm_type,
|
||||
subscription_plan=None,
|
||||
name="Fallback Plan Farm",
|
||||
)
|
||||
fallback_farm.products.add(self.product)
|
||||
fallback_feature = AccessFeature.objects.create(
|
||||
code="fallback-dashboard",
|
||||
name="Fallback Dashboard",
|
||||
feature_type=AccessFeature.PAGE,
|
||||
)
|
||||
fallback_rule = AccessRule.objects.create(code="gold-fallback-rule", name="Gold Fallback Rule", priority=5)
|
||||
fallback_rule.features.add(fallback_feature)
|
||||
fallback_rule.subscription_plans.add(default_plan)
|
||||
|
||||
profile = build_farm_access_profile(fallback_farm)
|
||||
|
||||
self.assertEqual(profile["subscription_plan"]["code"], "gold")
|
||||
self.assertTrue(profile["features"]["fallback-dashboard"]["enabled"])
|
||||
|
||||
def test_farm_serializer_returns_default_plan_when_model_plan_is_null(self):
|
||||
SubscriptionPlan.objects.create(code="gold", name="Gold", metadata={"is_default": True})
|
||||
fallback_farm = FarmHub.objects.create(
|
||||
owner=self.user,
|
||||
farm_type=self.farm_type,
|
||||
subscription_plan=None,
|
||||
name="Serializer Fallback Farm",
|
||||
)
|
||||
|
||||
payload = FarmHubSerializer(fallback_farm).data
|
||||
|
||||
self.assertEqual(payload["subscription_plan"]["code"], "gold")
|
||||
|
||||
Reference in New Issue
Block a user