9696from dje .views import manage_tab_permissions_view
9797from dje .views import object_compare_view
9898from dje .views import object_copy_view
99+ from policy .rules import RULE_REGISTRY
99100
100101EXTERNAL_SOURCE_LOOKUP = "external_references__external_source_id"
101102
@@ -1046,12 +1047,26 @@ def render(self, name, value, attrs=None, renderer=None):
10461047
10471048
10481049class 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+ fields = [
1055+ "homepage_layout" ,
1056+ "scancodeio_url" ,
1057+ "scancodeio_api_key" ,
1058+ "vulnerablecode_url" ,
1059+ "vulnerablecode_api_key" ,
1060+ "vulnerabilities_risk_threshold" ,
1061+ "purldb_url" ,
1062+ "purldb_api_key" ,
1063+ "forgejo_token" ,
1064+ "github_token" ,
1065+ "gitlab_token" ,
1066+ "jira_user" ,
1067+ "jira_token" ,
1068+ "sourcehut_token" ,
1069+ ]
10551070
10561071 hidden_value_fields = [
10571072 "scancodeio_api_key" ,
@@ -1078,6 +1093,73 @@ def clean(self):
10781093 del self .cleaned_data [field_name ]
10791094
10801095
1096+ class PolicyRulesConfigurationForm (forms .ModelForm ):
1097+ """Form for configuring policy rule overrides stored in policy_rules_config."""
1098+
1099+ class Meta :
1100+ model = DataspaceConfiguration
1101+ fields = []
1102+
1103+ def __init__ (self , * args , ** kwargs ):
1104+ super ().__init__ (* args , ** kwargs )
1105+ self .add_policy_rule_config_fields ()
1106+
1107+ def add_policy_rule_config_fields (self ):
1108+ """Inject per-rule form fields with initial values from the saved policy_rules_config."""
1109+ config = getattr (self .instance , "policy_rules_config" , {}) or {}
1110+ for rule_type , handler in RULE_REGISTRY .items ():
1111+ rule_config = config .get (rule_type , {})
1112+ self .fields [f"rule_{ rule_type } _enabled" ] = forms .BooleanField (
1113+ label = "Enable this rule" ,
1114+ required = False ,
1115+ initial = rule_config .get ("is_active" , False ),
1116+ )
1117+ self .fields [f"rule_{ rule_type } _threshold" ] = forms .IntegerField (
1118+ label = "Threshold" ,
1119+ required = False ,
1120+ min_value = 0 ,
1121+ initial = rule_config .get ("threshold" ),
1122+ widget = forms .NumberInput (
1123+ attrs = {"placeholder" : f"Default: { handler .default_threshold } " }
1124+ ),
1125+ help_text = "Minimum violations to trigger the rule. Leave blank to use the default." ,
1126+ )
1127+ for param_name , param_desc in handler .parameters_schema .items ():
1128+ self .fields [f"rule_{ rule_type } _param_{ param_name } " ] = forms .FloatField (
1129+ label = param_name .replace ("_" , " " ).title (),
1130+ required = False ,
1131+ initial = (rule_config .get ("parameters" ) or {}).get (param_name ),
1132+ help_text = param_desc ,
1133+ )
1134+
1135+ def build_policy_rules_config (self ):
1136+ """Serialize the per-rule form fields back into the policy_rules_config dict."""
1137+ policy_rules_config = {}
1138+ for rule_type , handler in RULE_REGISTRY .items ():
1139+ rule_config = {}
1140+ if self .cleaned_data .get (f"rule_{ rule_type } _enabled" ):
1141+ rule_config ["is_active" ] = True
1142+ threshold = self .cleaned_data .get (f"rule_{ rule_type } _threshold" )
1143+ if threshold is not None :
1144+ rule_config ["threshold" ] = threshold
1145+ parameters = {}
1146+ for param_name in handler .parameters_schema :
1147+ param_value = self .cleaned_data .get (f"rule_{ rule_type } _param_{ param_name } " )
1148+ if param_value is not None :
1149+ parameters [param_name ] = param_value
1150+ if parameters :
1151+ rule_config ["parameters" ] = parameters
1152+ if rule_config :
1153+ policy_rules_config [rule_type ] = rule_config
1154+ return policy_rules_config
1155+
1156+ def save (self , commit = True ):
1157+ self .instance .policy_rules_config = self .build_policy_rules_config ()
1158+ if commit :
1159+ self .instance .save (update_fields = ["policy_rules_config" ])
1160+ return self .instance
1161+
1162+
10811163class DataspaceConfigurationInline (DataspacedFKMixin , admin .StackedInline ):
10821164 model = DataspaceConfiguration
10831165 form = DataspaceConfigurationForm
@@ -1142,12 +1224,13 @@ class DataspaceConfigurationInline(DataspacedFKMixin, admin.StackedInline):
11421224 ]
11431225 # Do not include the Dataspace related FKs on addition as the Dataspace does not exist yet
11441226 fieldsets = [("" , {"fields" : ("homepage_layout" ,)})] + add_fieldsets
1227+ inline_classes = ("grp-collapse grp-open" ,)
11451228 can_delete = False
11461229
11471230 def get_fieldsets (self , request , obj = None ):
11481231 if not obj :
11491232 return self .add_fieldsets
1150- return super (). get_fieldsets ( request , obj )
1233+ return [( "" , { "fields" : ( "homepage_layout" ,)})] + self . add_fieldsets
11511234
11521235 def get_readonly_fields (self , request , obj = None ):
11531236 """Only a user from the current Dataspace can edit Dataspace related FKs."""
@@ -1160,6 +1243,40 @@ def get_readonly_fields(self, request, obj=None):
11601243 return readonly_fields
11611244
11621245
1246+ class PolicyRulesConfigurationInline (DataspacedFKMixin , admin .StackedInline ):
1247+ model = DataspaceConfiguration
1248+ form = PolicyRulesConfigurationForm
1249+ verbose_name_plural = _ ("Policy Rules Configuration" )
1250+ verbose_name = _ ("Policy Rules Configuration" )
1251+ classes = ("grp-collapse grp-open" ,)
1252+ inline_classes = ("grp-collapse grp-open" ,)
1253+ can_delete = False
1254+
1255+ def get_fieldsets (self , request , obj = None ):
1256+ if not obj :
1257+ return []
1258+ rule_fieldsets = []
1259+ for rule_type , handler in RULE_REGISTRY .items ():
1260+ fields = [f"rule_{ rule_type } _enabled" , f"rule_{ rule_type } _threshold" ]
1261+ for param_name in handler .parameters_schema :
1262+ fields .append (f"rule_{ rule_type } _param_{ param_name } " )
1263+ rule_fieldsets .append (
1264+ (
1265+ handler .label ,
1266+ {
1267+ "fields" : fields ,
1268+ "description" : handler .description ,
1269+ "classes" : ("grp-collapse grp-open" ,),
1270+ },
1271+ )
1272+ )
1273+ return rule_fieldsets
1274+
1275+ def get_formset (self , request , obj = None , ** kwargs ):
1276+ kwargs ["fields" ] = []
1277+ return super ().get_formset (request , obj , ** kwargs )
1278+
1279+
11631280@admin .register (Dataspace , site = dejacode_site )
11641281class DataspaceAdmin (
11651282 ReferenceOnlyPermissions ,
@@ -1239,7 +1356,7 @@ class DataspaceAdmin(
12391356 ),
12401357 )
12411358 search_fields = ("name" ,)
1242- inlines = [DataspaceConfigurationInline ]
1359+ inlines = [DataspaceConfigurationInline , PolicyRulesConfigurationInline ]
12431360 form = DataspaceAdminForm
12441361 change_form_template = "admin/dje/dataspace/change_form.html"
12451362 change_list_template = "admin/change_list_extended.html"
0 commit comments