from django.utils import timezone from .models import AccessFeature, AccessRule, FarmAccessProfile def _manager_id_set(manager): return {obj.id for obj in manager.all()} 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: return False farm_type_ids = _manager_id_set(rule.farm_types) if farm_type_ids and farm.farm_type_id not in farm_type_ids: return False product_rule_ids = _manager_id_set(rule.products) if product_rule_ids: product_ids = product_ids if product_ids is not None else set(farm.products.values_list("id", flat=True)) if not product_ids or product_rule_ids.isdisjoint(product_ids): return False sensor_catalog_rule_ids = _manager_id_set(rule.sensor_catalogs) if sensor_catalog_rule_ids: sensor_catalog_ids = ( sensor_catalog_ids if sensor_catalog_ids is not None else set(farm.sensors.exclude(sensor_catalog_id__isnull=True).values_list("sensor_catalog_id", flat=True)) ) if not sensor_catalog_ids or sensor_catalog_rule_ids.isdisjoint(sensor_catalog_ids): 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( farm.sensors.exclude(sensor_catalog__name__isnull=True).values_list("sensor_catalog__name", flat=True) ) if not farm_sensor_catalog_names or sensor_catalog_rule_names.isdisjoint(farm_sensor_catalog_names): return False return True def build_farm_access_profile(farm): features = AccessFeature.objects.all().order_by("feature_type", "code") resolved = { feature.code: { "enabled": feature.default_enabled, "type": feature.feature_type, "name": feature.name, "description": feature.description, "metadata": feature.metadata, "source": "default", } for feature in features } product_ids = set(farm.products.values_list("id", flat=True)) sensor_catalog_ids = set(farm.sensors.exclude(sensor_catalog_id__isnull=True).values_list("sensor_catalog_id", flat=True)) rules = ( AccessRule.objects.filter(is_active=True, features__isnull=False) .distinct() .prefetch_related("features", "subscription_plans", "farm_types", "products", "sensor_catalogs") .order_by("priority", "id") ) matched_rules = [] for rule in rules: if not rule_matches_farm(rule, farm, product_ids=product_ids, sensor_catalog_ids=sensor_catalog_ids): continue matched_rules.append( { "code": rule.code, "name": rule.name, "effect": rule.effect, "priority": rule.priority, } ) is_enabled = rule.effect == AccessRule.ALLOW for feature in rule.features.all(): resolved[feature.code] = { "enabled": is_enabled, "type": feature.feature_type, "name": feature.name, "description": feature.description, "metadata": feature.metadata, "source": rule.code, } grouped = {} for code, payload in resolved.items(): grouped.setdefault(f"{payload['type']}s", {})[code] = payload profile, _created = FarmAccessProfile.objects.update_or_create( farm=farm, defaults={ "cached_features": resolved, "cached_groups": grouped, "matched_rules": matched_rules, "last_resolved_at": timezone.now(), }, ) 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, } if farm.subscription_plan_id else None, "features": profile.cached_features, "groups": profile.cached_groups, "matched_rules": profile.matched_rules, "resolved_from_profile": True, } def is_feature_enabled_for_farm(farm, feature_code): profile = getattr(farm, "access_profile", None) if profile and isinstance(profile.cached_features, dict): feature_payload = profile.cached_features.get(feature_code) if feature_payload is not None: return bool(feature_payload.get("enabled")) profile_data = build_farm_access_profile(farm) feature_payload = profile_data["features"].get(feature_code) if feature_payload is None: return False return bool(feature_payload.get("enabled"))