UPDATE
This commit is contained in:
+18
-16
@@ -1,5 +1,6 @@
|
||||
GOLD_PLAN_CODE = "gold"
|
||||
SENSOR_7_NAME = "Sensor 7 - Soil Moisture Sensor v1.2"
|
||||
SENSOR_7_CODE = "sensor_7_soil_moisture_sensor_v1_2"
|
||||
|
||||
|
||||
DEFAULT_SUBSCRIPTION_PLANS = [
|
||||
@@ -13,21 +14,21 @@ DEFAULT_SUBSCRIPTION_PLANS = [
|
||||
|
||||
|
||||
DEFAULT_ACCESS_FEATURES = [
|
||||
{"code": "dashboards", "name": "داشبوردها", "feature_type": "section"},
|
||||
{"code": "data-section", "name": "بخش داده ها", "feature_type": "section"},
|
||||
{"code": "water-data", "name": "دیتاهای آب", "feature_type": "page"},
|
||||
{"code": "soil-information", "name": "اطلاعات خاک", "feature_type": "page"},
|
||||
{"code": "crop-zoning", "name": "زون بندی کشت", "feature_type": "page"},
|
||||
{"code": "simulator", "name": "شبیه ساز", "feature_type": "section"},
|
||||
{"code": "plant-growth-simulator", "name": "شبیه ساز رشد گیاه", "feature_type": "page"},
|
||||
{"code": "recommendations", "name": "توصیه ها", "feature_type": "section"},
|
||||
{"code": "irrigation-recommendation", "name": "توصیه آبیاری", "feature_type": "page"},
|
||||
{"code": "fertilization-recommendation", "name": "توصیه کوددهی", "feature_type": "page"},
|
||||
{"code": "smart-assistant", "name": "دستیار هوشمند", "feature_type": "section"},
|
||||
{"code": "farm-ai-assistant", "name": "دستیار هوشمند مزرعه", "feature_type": "page"},
|
||||
{"code": "pest-detection", "name": "تشخیص آفات گیاهی", "feature_type": "page"},
|
||||
{"code": "sensor-page", "name": "صفحه سنسور", "feature_type": "page"},
|
||||
{"code": "greenhouse-dashboard", "name": "Greenhouse Dashboard", "feature_type": "page"},
|
||||
{"code": "dashboards", "name": "داشبوردها", "feature_type": "section", "default_enabled": True},
|
||||
{"code": "data-section", "name": "بخش داده ها", "feature_type": "section", "default_enabled": True},
|
||||
{"code": "water-data", "name": "دیتاهای آب", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "soil-information", "name": "اطلاعات خاک", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "crop-zoning", "name": "زون بندی کشت", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "simulator", "name": "شبیه ساز", "feature_type": "section", "default_enabled": True},
|
||||
{"code": "plant-growth-simulator", "name": "شبیه ساز رشد گیاه", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "recommendations", "name": "توصیه ها", "feature_type": "section", "default_enabled": True},
|
||||
{"code": "irrigation-recommendation", "name": "توصیه آبیاری", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "fertilization-recommendation", "name": "توصیه کوددهی", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "smart-assistant", "name": "دستیار هوشمند", "feature_type": "section", "default_enabled": True},
|
||||
{"code": "farm-ai-assistant", "name": "دستیار هوشمند مزرعه", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "pest-detection", "name": "تشخیص آفات گیاهی", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "sensor-page", "name": "صفحه سنسور", "feature_type": "page", "default_enabled": True},
|
||||
{"code": "greenhouse-dashboard", "name": "Greenhouse Dashboard", "feature_type": "page", "default_enabled": True},
|
||||
]
|
||||
|
||||
|
||||
@@ -64,6 +65,7 @@ DEFAULT_ACCESS_RULES = [
|
||||
"priority": 20,
|
||||
"features": ["sensor-page"],
|
||||
"sensor_catalogs": [SENSOR_7_NAME],
|
||||
"metadata": {"sensor_catalog_names": [SENSOR_7_NAME]},
|
||||
"sensor_catalog_codes": [SENSOR_7_CODE],
|
||||
"metadata": {"sensor_catalog_codes": [SENSOR_7_CODE]},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def enable_default_feature_access(apps, schema_editor):
|
||||
AccessFeature = apps.get_model("access_control", "AccessFeature")
|
||||
|
||||
from access_control.catalog import DEFAULT_ACCESS_FEATURES
|
||||
|
||||
default_enabled_codes = [item["code"] for item in DEFAULT_ACCESS_FEATURES if item.get("default_enabled", False)]
|
||||
AccessFeature.objects.filter(code__in=default_enabled_codes).update(default_enabled=True)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("access_control", "0003_seed_default_access_rules"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(enable_default_feature_access, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def backfill_farm_subscription_plans(apps, schema_editor):
|
||||
SubscriptionPlan = apps.get_model("access_control", "SubscriptionPlan")
|
||||
FarmHub = apps.get_model("farm_hub", "FarmHub")
|
||||
|
||||
default_plan = (
|
||||
SubscriptionPlan.objects.filter(is_active=True, metadata__is_default=True).order_by("name").first()
|
||||
or SubscriptionPlan.objects.filter(code="gold", is_active=True).first()
|
||||
)
|
||||
if default_plan is None:
|
||||
return
|
||||
|
||||
FarmHub.objects.filter(subscription_plan__isnull=True).update(subscription_plan=default_plan)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("access_control", "0004_enable_default_feature_access"),
|
||||
("farm_hub", "0007_farmhub_subscription_plan"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(backfill_farm_subscription_plans, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -12,8 +12,6 @@ class SubscriptionPlanSerializer(serializers.ModelSerializer):
|
||||
class FarmAccessProfileSerializer(serializers.Serializer):
|
||||
farm_uuid = serializers.UUIDField()
|
||||
subscription_plan = SubscriptionPlanSerializer(allow_null=True)
|
||||
features = serializers.DictField()
|
||||
groups = serializers.DictField()
|
||||
matched_rules = serializers.ListField()
|
||||
resolved_from_profile = serializers.BooleanField()
|
||||
|
||||
@@ -31,4 +29,3 @@ class FarmAccessProfileCacheSerializer(serializers.ModelSerializer):
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import AccessFeature, AccessRule, FarmAccessProfile
|
||||
from .catalog import GOLD_PLAN_CODE
|
||||
from .models import AccessFeature, AccessRule, FarmAccessProfile, SubscriptionPlan
|
||||
|
||||
|
||||
def _manager_id_set(manager):
|
||||
return {obj.id for obj in manager.all()}
|
||||
|
||||
|
||||
def get_effective_subscription_plan(farm):
|
||||
if getattr(farm, "subscription_plan_id", None):
|
||||
return farm.subscription_plan
|
||||
|
||||
return (
|
||||
SubscriptionPlan.objects.filter(is_active=True, metadata__is_default=True).order_by("name").first()
|
||||
or SubscriptionPlan.objects.filter(code=GOLD_PLAN_CODE, is_active=True).first()
|
||||
)
|
||||
|
||||
|
||||
def rule_matches_farm(rule, farm, product_ids=None, sensor_catalog_ids=None):
|
||||
if not rule.is_active:
|
||||
return False
|
||||
|
||||
subscription_plan_ids = _manager_id_set(rule.subscription_plans)
|
||||
if subscription_plan_ids:
|
||||
if farm.subscription_plan_id is None or farm.subscription_plan_id not in subscription_plan_ids:
|
||||
subscription_plan = get_effective_subscription_plan(farm)
|
||||
if subscription_plan is None or subscription_plan.id not in subscription_plan_ids:
|
||||
return False
|
||||
|
||||
farm_type_ids = _manager_id_set(rule.farm_types)
|
||||
@@ -36,6 +48,14 @@ def rule_matches_farm(rule, farm, product_ids=None, sensor_catalog_ids=None):
|
||||
if not sensor_catalog_ids or sensor_catalog_rule_ids.isdisjoint(sensor_catalog_ids):
|
||||
return False
|
||||
|
||||
sensor_catalog_rule_codes = set(rule.metadata.get("sensor_catalog_codes", [])) if isinstance(rule.metadata, dict) else set()
|
||||
if sensor_catalog_rule_codes:
|
||||
farm_sensor_catalog_codes = set(
|
||||
farm.sensors.exclude(sensor_catalog__code__isnull=True).values_list("sensor_catalog__code", flat=True)
|
||||
)
|
||||
if not farm_sensor_catalog_codes or sensor_catalog_rule_codes.isdisjoint(farm_sensor_catalog_codes):
|
||||
return False
|
||||
|
||||
sensor_catalog_rule_names = set(rule.metadata.get("sensor_catalog_names", [])) if isinstance(rule.metadata, dict) else set()
|
||||
if sensor_catalog_rule_names:
|
||||
farm_sensor_catalog_names = set(
|
||||
@@ -48,6 +68,7 @@ def rule_matches_farm(rule, farm, product_ids=None, sensor_catalog_ids=None):
|
||||
|
||||
|
||||
def build_farm_access_profile(farm):
|
||||
subscription_plan = get_effective_subscription_plan(farm)
|
||||
features = AccessFeature.objects.all().order_by("feature_type", "code")
|
||||
resolved = {
|
||||
feature.code: {
|
||||
@@ -112,11 +133,11 @@ def build_farm_access_profile(farm):
|
||||
return {
|
||||
"farm_uuid": str(farm.farm_uuid),
|
||||
"subscription_plan": {
|
||||
"uuid": str(farm.subscription_plan.uuid),
|
||||
"code": farm.subscription_plan.code,
|
||||
"name": farm.subscription_plan.name,
|
||||
"uuid": str(subscription_plan.uuid),
|
||||
"code": subscription_plan.code,
|
||||
"name": subscription_plan.name,
|
||||
}
|
||||
if farm.subscription_plan_id
|
||||
if subscription_plan is not None
|
||||
else None,
|
||||
"features": profile.cached_features,
|
||||
"groups": profile.cached_groups,
|
||||
@@ -125,6 +146,16 @@ def build_farm_access_profile(farm):
|
||||
}
|
||||
|
||||
|
||||
def build_farm_access_profile_response(farm):
|
||||
profile_data = build_farm_access_profile(farm)
|
||||
return {
|
||||
"farm_uuid": profile_data["farm_uuid"],
|
||||
"subscription_plan": profile_data["subscription_plan"],
|
||||
"matched_rules": profile_data["matched_rules"],
|
||||
"resolved_from_profile": profile_data["resolved_from_profile"],
|
||||
}
|
||||
|
||||
|
||||
def is_feature_enabled_for_farm(farm, feature_code):
|
||||
profile = getattr(farm, "access_profile", None)
|
||||
if profile and isinstance(profile.cached_features, dict):
|
||||
|
||||
@@ -9,7 +9,7 @@ from farm_hub.models import FarmHub
|
||||
|
||||
from .models import SubscriptionPlan
|
||||
from .serializers import FarmAccessProfileSerializer, SubscriptionPlanSerializer
|
||||
from .services import build_farm_access_profile
|
||||
from .services import build_farm_access_profile_response
|
||||
|
||||
|
||||
class AccessControlBaseView(APIView):
|
||||
@@ -52,6 +52,5 @@ class FarmAccessProfileView(AccessControlBaseView):
|
||||
if farm is None:
|
||||
return Response({"code": 404, "msg": "Farm not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
data = build_farm_access_profile(farm)
|
||||
data = build_farm_access_profile_response(farm)
|
||||
return Response({"code": 200, "msg": "success", "data": data}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user