From c6f007337e7f4b603e6c5c4e4d36fc72854f2bbf Mon Sep 17 00:00:00 2001 From: tdruez Date: Tue, 10 Feb 2026 10:34:17 +1300 Subject: [PATCH 1/6] feat: set usage policy from license profile Signed-off-by: tdruez --- license_library/admin.py | 15 +++++----- ...003_licenseprofile_default_usage_policy.py | 20 +++++++++++++ license_library/models.py | 28 +++++++++++++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 license_library/migrations/0003_licenseprofile_default_usage_policy.py diff --git a/license_library/admin.py b/license_library/admin.py index 6df8211c..95af2f4a 100644 --- a/license_library/admin.py +++ b/license_library/admin.py @@ -357,11 +357,15 @@ def set_assigned_tags_from_license_profile(self, request, obj): self.message_user(request, msg) def save_model(self, request, obj, form, change): + # Needs to be set before the `save_model` method call. + if obj.license_profile: + obj.set_usage_policy_from_license_profile() + if change: obj_before_save = License.objects.get(id=obj.id) super().save_model(request, obj, form, change) # If a LicenseProfile is set or changed, apply the values of the - # this Profile to the license assigned tags. + # Profile to the license assigned tags. if obj.license_profile and obj.license_profile != obj_before_save.license_profile: self.set_assigned_tags_from_license_profile(request, obj) else: @@ -452,7 +456,7 @@ class LicenseTagHolderBaseAdmin(DataspacedAdmin): The purpose of this class is to be extended by LicenseProfileAdmin and LicenseTagGroupAdmin. It's used to add a LicenseTag queryset in the context of the add and changes views, to display special data (added through - javascript) about tags in the page like text and guidance information. + avascript) about tags in the page like text and guidance information. """ change_form_template = "admin/license_library/tag_holder/change_form.html" @@ -519,11 +523,12 @@ class LicenseProfileAdmin(LicenseTagHolderBaseAdmin): "name", "get_assigned_tags_html", "examples", + "default_usage_policy", "get_dataspace", ) fieldsets = ( ("", {"fields": ("name",)}), - ("", {"fields": ("examples", "notes", "dataspace", "uuid")}), + ("", {"fields": ("examples", "notes", "default_usage_policy", "dataspace", "uuid")}), ) search_fields = ("name",) list_filter = DataspacedAdmin.list_filter + (ReportingQueryListFilter,) @@ -553,10 +558,6 @@ class LicenseProfileAdmin(LicenseTagHolderBaseAdmin): will be Unknown.", ) - def get_queryset(self, request): - qs = super().get_queryset(request) - return qs.prefetch_related("licenseprofileassignedtag_set__license_tag") - @admin.register(LicenseCategory, site=dejacode_site) class LicenseCategoryAdmin(DataspacedAdmin): diff --git a/license_library/migrations/0003_licenseprofile_default_usage_policy.py b/license_library/migrations/0003_licenseprofile_default_usage_policy.py new file mode 100644 index 00000000..07454ea2 --- /dev/null +++ b/license_library/migrations/0003_licenseprofile_default_usage_policy.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2.9 on 2026-02-09 02:10 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('license_library', '0002_initial'), + ('policy', '0002_initial'), + ] + + operations = [ + migrations.AddField( + model_name='licenseprofile', + name='default_usage_policy', + field=models.ForeignKey(blank=True, help_text='Default usage policy to be assigned to a license when using this license profile.', limit_choices_to={'content_type__app_label': 'license_library', 'content_type__model': 'license'}, null=True, on_delete=django.db.models.deletion.SET_NULL, to='policy.usagepolicy'), + ), + ] diff --git a/license_library/models.py b/license_library/models.py index 1354ba21..4f06458d 100644 --- a/license_library/models.py +++ b/license_library/models.py @@ -209,6 +209,20 @@ class LicenseProfile(DataspacedModel): ), ) + default_usage_policy = models.ForeignKey( + to="policy.UsagePolicy", + limit_choices_to={ + "content_type__app_label": "license_library", + "content_type__model": "license", + }, + null=True, + blank=True, + on_delete=models.SET_NULL, + help_text=_( + "Default usage policy to be assigned to a license when using this license profile." + ), + ) + class Meta: unique_together = (("dataspace", "name"), ("dataspace", "uuid")) ordering = ["name"] @@ -986,6 +1000,20 @@ def set_assigned_tags_from_license_profile(self): defaults={"value": profile_assigned_tag.value}, ) + def set_usage_policy_from_license_profile(self): + """ + Set default usage_policy value from the license_profile + when usage_policy is not already defined. + """ + apply_default_usage_policy = all([ + not self.usage_policy, + self.license_profile, + self.license_profile.default_usage_policy, + ]) + + if apply_default_usage_policy: + self.usage_policy = self.license_profile.default_usage_policy + @staticmethod def get_extra_relational_fields(): return ["annotations", "external_references"] From 86a7d8d97d052331b1261bd258ceba6e6be8c97a Mon Sep 17 00:00:00 2001 From: tdruez Date: Thu, 12 Feb 2026 08:03:49 +1300 Subject: [PATCH 2/6] fix code format Signed-off-by: tdruez --- license_library/models.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/license_library/models.py b/license_library/models.py index 4f06458d..1808ecf9 100644 --- a/license_library/models.py +++ b/license_library/models.py @@ -1005,11 +1005,13 @@ def set_usage_policy_from_license_profile(self): Set default usage_policy value from the license_profile when usage_policy is not already defined. """ - apply_default_usage_policy = all([ - not self.usage_policy, - self.license_profile, - self.license_profile.default_usage_policy, - ]) + apply_default_usage_policy = all( + [ + not self.usage_policy, + self.license_profile, + self.license_profile.default_usage_policy, + ] + ) if apply_default_usage_policy: self.usage_policy = self.license_profile.default_usage_policy From d87b314f5f650c60a96ac56c7478a5257aad7fbf Mon Sep 17 00:00:00 2001 From: tdruez Date: Thu, 12 Feb 2026 08:35:52 +1300 Subject: [PATCH 3/6] fix failing tests Signed-off-by: tdruez --- dje/tests/testfiles/test_dataset_ll_only.json | 1 + license_library/tests/test_models.py | 1 + 2 files changed, 2 insertions(+) diff --git a/dje/tests/testfiles/test_dataset_ll_only.json b/dje/tests/testfiles/test_dataset_ll_only.json index ae8f80fe..b3fc3282 100644 --- a/dje/tests/testfiles/test_dataset_ll_only.json +++ b/dje/tests/testfiles/test_dataset_ll_only.json @@ -36,6 +36,7 @@ ], "uuid": "5a4ec739-50be-4744-a7d6-4ba36f9ba4cd", "name": "1: LicenseProfile1", + "default_usage_policy": null, "examples": "", "notes": "" } diff --git a/license_library/tests/test_models.py b/license_library/tests/test_models.py index cdfa91ed..7b4245a5 100644 --- a/license_library/tests/test_models.py +++ b/license_library/tests/test_models.py @@ -355,6 +355,7 @@ def test_license_library_models_get_exclude_candidates_fields(self): ( LicenseProfile, [ + "default_usage_policy", "examples", "notes", ], From 1f6873026c94a5a4010bf322ad0bc082d2e3fa35 Mon Sep 17 00:00:00 2001 From: tdruez Date: Thu, 12 Feb 2026 11:08:08 +1300 Subject: [PATCH 4/6] remove problematic js for review Signed-off-by: tdruez --- .../license_library/license/change_form.html | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/license_library/templates/admin/license_library/license/change_form.html b/license_library/templates/admin/license_library/license/change_form.html index 8c58e0f0..415e434c 100644 --- a/license_library/templates/admin/license_library/license/change_form.html +++ b/license_library/templates/admin/license_library/license/change_form.html @@ -154,48 +154,6 @@ $('#licenseassignedtag_set-group .grp-tools').remove(); $('#licenseassignedtag_set-group .add-another').remove(); - // Hack to move the transform the Inline into a "tab" - // Manipulate the DOM to prepare before activating the tags - // Add the tabs list links - var tabs_ul = $(''); - tabs_ul.prependTo('#license_form'); - $("#grp-content-container").attr('style', 'clear:both;'); - // We put a tabs-1 id on the wrapper div in the form - $("#license_form > div:last-child").attr('id', 'tabs-1'); - // Add a new div tabs-2 in the form and move the Inline into it - $('
').appendTo('#license_form'); - $("#licenseassignedtag_set-group").appendTo('#tabs-2'); - // The footer needs to be moved too to be outside "tabs-1" - $("footer").appendTo('#license_form'); - - // Activate the tabs - $("#license_form").tabs(); - // Removing un-wanted class, to ensure visual consistency - $("#license_form").removeClass("ui-widget ui-widget-content ui-corner-all"); - $("#tabs-1, #tabs-2").removeClass("ui-tabs-panel ui-widget-content ui-corner-bottom"); - - // Hack to regroup the Assigned Tag based on the group seq - // First, build a dict of the div to be moved with the tag label as the key - var div_dict = new Array(); - $.each($('div[id^="licenseassignedtag_set"].grp-module'), function(index, value) { - var label = $(value).find('.license_tag > p:first').text(); // Addition - // Different block type on Edition - if (!label || 0 === label.length) { label = $(value).find('.license_tag > .grp-readonly:first').text(); } - div_dict[label] = $(value); - }); - - // Re-organize the inline block - var module_table = $('#licenseassignedtag_set-group > .grp-module.grp-table'); - {% for seq, group in group_dict.items %} - var group_div = $('
{{ group.0 }}
 
 
 
') - group_div.appendTo(module_table); - {% for label in group.1 %} - if (div_dict['{{ label }}'] !== undefined) { - div_dict['{{ label }}'].appendTo(module_table); - } - {% endfor %} - {% endfor %} - }); })(grp.jQuery); From 3a4103f19bbb9f578d19d063b43861b3d97b23ab Mon Sep 17 00:00:00 2001 From: tdruez Date: Thu, 12 Feb 2026 18:11:15 +1300 Subject: [PATCH 5/6] add back problematic js Signed-off-by: tdruez --- .../license_library/license/change_form.html | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/license_library/templates/admin/license_library/license/change_form.html b/license_library/templates/admin/license_library/license/change_form.html index 415e434c..8c58e0f0 100644 --- a/license_library/templates/admin/license_library/license/change_form.html +++ b/license_library/templates/admin/license_library/license/change_form.html @@ -154,6 +154,48 @@ $('#licenseassignedtag_set-group .grp-tools').remove(); $('#licenseassignedtag_set-group .add-another').remove(); + // Hack to move the transform the Inline into a "tab" + // Manipulate the DOM to prepare before activating the tags + // Add the tabs list links + var tabs_ul = $(''); + tabs_ul.prependTo('#license_form'); + $("#grp-content-container").attr('style', 'clear:both;'); + // We put a tabs-1 id on the wrapper div in the form + $("#license_form > div:last-child").attr('id', 'tabs-1'); + // Add a new div tabs-2 in the form and move the Inline into it + $('
').appendTo('#license_form'); + $("#licenseassignedtag_set-group").appendTo('#tabs-2'); + // The footer needs to be moved too to be outside "tabs-1" + $("footer").appendTo('#license_form'); + + // Activate the tabs + $("#license_form").tabs(); + // Removing un-wanted class, to ensure visual consistency + $("#license_form").removeClass("ui-widget ui-widget-content ui-corner-all"); + $("#tabs-1, #tabs-2").removeClass("ui-tabs-panel ui-widget-content ui-corner-bottom"); + + // Hack to regroup the Assigned Tag based on the group seq + // First, build a dict of the div to be moved with the tag label as the key + var div_dict = new Array(); + $.each($('div[id^="licenseassignedtag_set"].grp-module'), function(index, value) { + var label = $(value).find('.license_tag > p:first').text(); // Addition + // Different block type on Edition + if (!label || 0 === label.length) { label = $(value).find('.license_tag > .grp-readonly:first').text(); } + div_dict[label] = $(value); + }); + + // Re-organize the inline block + var module_table = $('#licenseassignedtag_set-group > .grp-module.grp-table'); + {% for seq, group in group_dict.items %} + var group_div = $('
{{ group.0 }}
 
 
 
') + group_div.appendTo(module_table); + {% for label in group.1 %} + if (div_dict['{{ label }}'] !== undefined) { + div_dict['{{ label }}'].appendTo(module_table); + } + {% endfor %} + {% endfor %} + }); })(grp.jQuery); From 2e4ef1ffa3e1043872206d4514caf92a525fa1a5 Mon Sep 17 00:00:00 2001 From: tdruez Date: Thu, 12 Feb 2026 18:12:01 +1300 Subject: [PATCH 6/6] add unit test for set_usage_policy_from_license_profile Signed-off-by: tdruez --- license_library/models.py | 10 ++++------ license_library/tests/test_models.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/license_library/models.py b/license_library/models.py index 1808ecf9..75590aeb 100644 --- a/license_library/models.py +++ b/license_library/models.py @@ -1005,12 +1005,10 @@ def set_usage_policy_from_license_profile(self): Set default usage_policy value from the license_profile when usage_policy is not already defined. """ - apply_default_usage_policy = all( - [ - not self.usage_policy, - self.license_profile, - self.license_profile.default_usage_policy, - ] + apply_default_usage_policy = ( + not self.usage_policy + and self.license_profile + and self.license_profile.default_usage_policy ) if apply_default_usage_policy: diff --git a/license_library/tests/test_models.py b/license_library/tests/test_models.py index 7b4245a5..93f8ff11 100644 --- a/license_library/tests/test_models.py +++ b/license_library/tests/test_models.py @@ -28,6 +28,7 @@ from license_library.models import LicenseTagGroup from license_library.models import LicenseTagGroupAssignedTag from organization.models import Owner +from policy.tests import make_usage_policy from product_portfolio.models import Product @@ -304,6 +305,20 @@ def test_license_model_data_for_expression_builder(self): data_for_expression_builder = License.objects.all().data_for_expression_builder() self.assertEqual(expected, data_for_expression_builder) + def test_license_model_set_usage_policy_from_license_profile(self): + self.license1.set_usage_policy_from_license_profile() + self.assertIsNone(self.license1.usage_policy) + + self.license1.update(license_profile=self.license_profile) + self.license1.set_usage_policy_from_license_profile() + self.assertIsNone(self.license1.usage_policy) + + license_policy = make_usage_policy(self.dataspace, model=License) + self.license_profile.update(default_usage_policy=license_policy) + + self.license1.set_usage_policy_from_license_profile() + self.assertEqual(license_policy, self.license1.usage_policy) + def test_license_library_models_get_exclude_candidates_fields(self): input_data = ( (