Skip to content

Commit 8fdad32

Browse files
committed
add policy rules configuration inadmin
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 5658579 commit 8fdad32

4 files changed

Lines changed: 111 additions & 16 deletions

File tree

dje/admin.py

Lines changed: 108 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
from dje.views import manage_tab_permissions_view
9797
from dje.views import object_compare_view
9898
from dje.views import object_copy_view
99+
from policy.rules import RULE_REGISTRY
99100

100101
EXTERNAL_SOURCE_LOOKUP = "external_references__external_source_id"
101102

@@ -1046,12 +1047,11 @@ def render(self, name, value, attrs=None, renderer=None):
10461047

10471048

10481049
class DataspaceConfigurationForm(forms.ModelForm):
1049-
"""
1050-
Configure Dataspace settings.
1050+
"""Configure Dataspace integration settings, with sensitive values hidden in the UI."""
10511051

1052-
This form includes fields for various API keys, with sensitive values
1053-
hidden in the UI using the HiddenValueWidget.
1054-
"""
1052+
class Meta:
1053+
model = DataspaceConfiguration
1054+
exclude = ["policy_rules_config"]
10551055

10561056
hidden_value_fields = [
10571057
"scancodeio_api_key",
@@ -1078,6 +1078,73 @@ def clean(self):
10781078
del self.cleaned_data[field_name]
10791079

10801080

1081+
class PolicyRulesConfigurationForm(forms.ModelForm):
1082+
"""Form for configuring policy rule overrides stored in policy_rules_config."""
1083+
1084+
class Meta:
1085+
model = DataspaceConfiguration
1086+
fields = []
1087+
1088+
def __init__(self, *args, **kwargs):
1089+
super().__init__(*args, **kwargs)
1090+
self.add_policy_rule_config_fields()
1091+
1092+
def add_policy_rule_config_fields(self):
1093+
"""Inject per-rule form fields with initial values from the saved policy_rules_config."""
1094+
config = getattr(self.instance, "policy_rules_config", {}) or {}
1095+
for rule_type, handler in RULE_REGISTRY.items():
1096+
rule_config = config.get(rule_type, {})
1097+
self.fields[f"rule_{rule_type}_disabled"] = forms.BooleanField(
1098+
label="Disable this rule",
1099+
required=False,
1100+
initial=not rule_config.get("is_active", True),
1101+
)
1102+
self.fields[f"rule_{rule_type}_threshold"] = forms.IntegerField(
1103+
label="Threshold",
1104+
required=False,
1105+
min_value=0,
1106+
initial=rule_config.get("threshold"),
1107+
widget=forms.NumberInput(
1108+
attrs={"placeholder": f"Default: {handler.default_threshold}"}
1109+
),
1110+
help_text="Minimum violations to trigger the rule. Leave blank to use the default.",
1111+
)
1112+
for param_name, param_desc in handler.parameters_schema.items():
1113+
self.fields[f"rule_{rule_type}_param_{param_name}"] = forms.FloatField(
1114+
label=param_name.replace("_", " ").title(),
1115+
required=False,
1116+
initial=(rule_config.get("parameters") or {}).get(param_name),
1117+
help_text=param_desc,
1118+
)
1119+
1120+
def build_policy_rules_config(self):
1121+
"""Serialize the per-rule form fields back into the policy_rules_config dict."""
1122+
policy_rules_config = {}
1123+
for rule_type, handler in RULE_REGISTRY.items():
1124+
rule_config = {}
1125+
if self.cleaned_data.get(f"rule_{rule_type}_disabled"):
1126+
rule_config["is_active"] = False
1127+
threshold = self.cleaned_data.get(f"rule_{rule_type}_threshold")
1128+
if threshold is not None:
1129+
rule_config["threshold"] = threshold
1130+
parameters = {}
1131+
for param_name in handler.parameters_schema:
1132+
param_value = self.cleaned_data.get(f"rule_{rule_type}_param_{param_name}")
1133+
if param_value is not None:
1134+
parameters[param_name] = param_value
1135+
if parameters:
1136+
rule_config["parameters"] = parameters
1137+
if rule_config:
1138+
policy_rules_config[rule_type] = rule_config
1139+
return policy_rules_config
1140+
1141+
def save(self, commit=True):
1142+
self.instance.policy_rules_config = self.build_policy_rules_config()
1143+
if commit:
1144+
self.instance.save(update_fields=["policy_rules_config"])
1145+
return self.instance
1146+
1147+
10811148
class DataspaceConfigurationInline(DataspacedFKMixin, admin.StackedInline):
10821149
model = DataspaceConfiguration
10831150
form = DataspaceConfigurationForm
@@ -1141,17 +1208,14 @@ class DataspaceConfigurationInline(DataspacedFKMixin, admin.StackedInline):
11411208
),
11421209
]
11431210
# Do not include the Dataspace related FKs on addition as the Dataspace does not exist yet
1144-
policy_rules_fieldset = (
1145-
"Policy Rules",
1146-
{"fields": ("policy_rules_config",)},
1147-
)
1148-
fieldsets = [("", {"fields": ("homepage_layout",)})] + add_fieldsets + [policy_rules_fieldset]
1211+
fieldsets = [("", {"fields": ("homepage_layout",)})] + add_fieldsets
1212+
inline_classes = ("grp-collapse grp-open",)
11491213
can_delete = False
11501214

11511215
def get_fieldsets(self, request, obj=None):
11521216
if not obj:
11531217
return self.add_fieldsets
1154-
return super().get_fieldsets(request, obj)
1218+
return [("", {"fields": ("homepage_layout",)})] + self.add_fieldsets
11551219

11561220
def get_readonly_fields(self, request, obj=None):
11571221
"""Only a user from the current Dataspace can edit Dataspace related FKs."""
@@ -1164,6 +1228,38 @@ def get_readonly_fields(self, request, obj=None):
11641228
return readonly_fields
11651229

11661230

1231+
class PolicyRulesConfigurationInline(DataspacedFKMixin, admin.StackedInline):
1232+
model = DataspaceConfiguration
1233+
form = PolicyRulesConfigurationForm
1234+
verbose_name_plural = _("Policy Rules Configuration")
1235+
verbose_name = _("Policy Rules Configuration")
1236+
classes = ("grp-collapse grp-open",)
1237+
inline_classes = ("grp-collapse grp-open",)
1238+
can_delete = False
1239+
1240+
def get_fieldsets(self, request, obj=None):
1241+
if not obj:
1242+
return []
1243+
rule_fieldsets = []
1244+
for rule_type, handler in RULE_REGISTRY.items():
1245+
fields = [f"rule_{rule_type}_disabled", f"rule_{rule_type}_threshold"]
1246+
for param_name in handler.parameters_schema:
1247+
fields.append(f"rule_{rule_type}_param_{param_name}")
1248+
rule_fieldsets.append((
1249+
handler.label,
1250+
{
1251+
"fields": fields,
1252+
"description": handler.description,
1253+
"classes": ("grp-collapse grp-open",),
1254+
},
1255+
))
1256+
return rule_fieldsets
1257+
1258+
def get_formset(self, request, obj=None, **kwargs):
1259+
kwargs["fields"] = []
1260+
return super().get_formset(request, obj, **kwargs)
1261+
1262+
11671263
@admin.register(Dataspace, site=dejacode_site)
11681264
class DataspaceAdmin(
11691265
ReferenceOnlyPermissions,
@@ -1243,7 +1339,7 @@ class DataspaceAdmin(
12431339
),
12441340
)
12451341
search_fields = ("name",)
1246-
inlines = [DataspaceConfigurationInline]
1342+
inlines = [DataspaceConfigurationInline, PolicyRulesConfigurationInline]
12471343
form = DataspaceAdminForm
12481344
change_form_template = "admin/dje/dataspace/change_form.html"
12491345
change_list_template = "admin/change_list_extended.html"

dje/migrations/0016_dataspaceconfiguration_policy_rules_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ class Migration(migrations.Migration):
1313
migrations.AddField(
1414
model_name='dataspaceconfiguration',
1515
name='policy_rules_config',
16-
field=models.JSONField(blank=True, default=dict, help_text='Override default policy rule settings for this dataspace. '),
16+
field=models.JSONField(blank=True, default=dict, help_text='Override default policy rule settings for this dataspace.'),
1717
),
1818
]

dje/models.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -617,9 +617,7 @@ class DataspaceConfiguration(DataspaceForeignKeyValidationMixin, models.Model):
617617
policy_rules_config = models.JSONField(
618618
blank=True,
619619
default=dict,
620-
help_text=_(
621-
"Override default policy rule settings for this dataspace. "
622-
),
620+
help_text=_("Override default policy rule settings for this dataspace."),
623621
)
624622

625623
def __str__(self):

policy/rules.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class BaseRule:
1515
rule_type = None
1616
label = None
1717
description = None
18+
default_threshold = 0
1819
parameters_schema = {}
1920

2021
def count_violations(self, product, threshold, parameters):

0 commit comments

Comments
 (0)