From d7fdc59bef34d79ffaf18dfc6991da15381eeb9e Mon Sep 17 00:00:00 2001 From: tdruez Date: Tue, 17 Feb 2026 19:26:44 +1300 Subject: [PATCH 1/3] raise colspan value Signed-off-by: tdruez --- .../templates/product_portfolio/tables/product_list_table.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_portfolio/templates/product_portfolio/tables/product_list_table.html b/product_portfolio/templates/product_portfolio/tables/product_list_table.html index b69e966b..226931cf 100644 --- a/product_portfolio/templates/product_portfolio/tables/product_list_table.html +++ b/product_portfolio/templates/product_portfolio/tables/product_list_table.html @@ -71,7 +71,7 @@ {% endfor %} {% empty %} - No results. + No results. {% endfor %} \ No newline at end of file From 1645e68a41e48e0cc469072d627355a963832619 Mon Sep 17 00:00:00 2001 From: tdruez Date: Tue, 17 Feb 2026 20:12:07 +1300 Subject: [PATCH 2/3] remove protected field on form bound for safety no need to validate the likely "empty" data form the disabled field Signed-off-by: tdruez --- component_catalog/tests/test_views.py | 12 ++++++++---- dje/forms.py | 22 ++++++++++++++-------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/component_catalog/tests/test_views.py b/component_catalog/tests/test_views.py index d67beecd..a2703e40 100644 --- a/component_catalog/tests/test_views.py +++ b/component_catalog/tests/test_views.py @@ -3566,7 +3566,7 @@ def test_component_catalog_package_form_add(self): "usage_policy": policy_approved.pk, } form = PackageForm(user=self.super_user, data=data) - self.assertEqual(0, len(form.fields["usage_policy"].queryset)) + self.assertNotIn("usage_policy", form.fields) self.assertTrue(form.is_valid()) package = form.save() self.assertIsNone(package.usage_policy) @@ -3574,7 +3574,8 @@ def test_component_catalog_package_form_add(self): data["filename"] = "with policy" self.super_user = add_perm(self.super_user, "change_usage_policy_on_package") form = PackageForm(user=self.super_user, data=data) - self.assertEqual(1, len(form.fields["usage_policy"].queryset)) + self.assertIn("usage_policy", form.fields) + self.assertQuerySetEqual([policy_approved], form.fields["usage_policy"].queryset) self.assertTrue(form.is_valid()) package = form.save() self.assertEqual(policy_approved, package.usage_policy) @@ -4386,7 +4387,7 @@ def test_component_catalog_component_form_add(self): "usage_policy": policy_approved.pk, } form = ComponentForm(user=self.user, data=data) - self.assertEqual(0, len(form.fields["usage_policy"].queryset)) + self.assertNotIn("usage_policy", form.fields) self.assertTrue(form.is_valid()) component = form.save() self.assertIsNone(component.usage_policy) @@ -4394,7 +4395,8 @@ def test_component_catalog_component_form_add(self): data["version"] = "with policy" self.user = add_perm(self.user, "change_usage_policy_on_component") form = ComponentForm(user=self.user, data=data) - self.assertEqual(1, len(form.fields["usage_policy"].queryset)) + self.assertIn("usage_policy", form.fields) + self.assertQuerySetEqual([policy_approved], form.fields["usage_policy"].queryset) self.assertTrue(form.is_valid()) component = form.save() self.assertEqual(policy_approved, component.usage_policy) @@ -4413,6 +4415,7 @@ def test_component_catalog_component_form_add(self): "homepage_url": "https://nexb.com", "configuration_status": status.pk, "release_date": "2019-03-01", + "usage_policy": policy_approved.pk, "submit": "Add Component", } form = ComponentForm(user=self.user, data=data) @@ -4422,6 +4425,7 @@ def test_component_catalog_component_form_add(self): self.assertEqual(status, component.configuration_status) self.assertEqual(license1.key, component.license_expression) self.assertEqual(["Key1", "Another keyword"], component.keywords) + self.assertEqual(policy_approved, component.usage_policy) def test_component_catalog_component_form_assigned_packages(self): data = { diff --git a/dje/forms.py b/dje/forms.py index 9606fe54..d2169b6f 100644 --- a/dje/forms.py +++ b/dje/forms.py @@ -109,18 +109,24 @@ class ScopeAndProtectRelationships: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - protected_fields = get_protected_fields(self._meta.model, self.user) - self.protected_fields = protected_fields + self.protected_fields = get_protected_fields(self._meta.model, self.user) - for name, field in self.fields.items(): - has_queryset = hasattr(field, "queryset") - - if name in protected_fields: + # On submit, remove protected fields so they are not validated or saved. + # On display, render them as disabled with an empty queryset. + for name in self.protected_fields: + if self.is_bound: + self.fields.pop(name, None) + else: + field = self.fields[name] field.disabled = True - if has_queryset: + if hasattr(field, "queryset"): field.queryset = field.queryset.none() - elif has_queryset and is_dataspace_related(field.queryset.model): + # Scope relational fields to the user's Dataspace + for name, field in self.fields.items(): + has_queryset = hasattr(field, "queryset") + + if has_queryset and is_dataspace_related(field.queryset.model): field.queryset = field.queryset.scope(self.user.dataspace) related_model = field.queryset.model From ff7e0c8e01bc4dc7465988b91735180c84708bf5 Mon Sep 17 00:00:00 2001 From: tdruez Date: Wed, 18 Feb 2026 14:58:58 +1300 Subject: [PATCH 3/3] refine code and add unit test Signed-off-by: tdruez --- dje/forms.py | 3 +-- product_portfolio/tests/test_views.py | 39 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/dje/forms.py b/dje/forms.py index d2169b6f..3be984d7 100644 --- a/dje/forms.py +++ b/dje/forms.py @@ -116,8 +116,7 @@ def __init__(self, *args, **kwargs): for name in self.protected_fields: if self.is_bound: self.fields.pop(name, None) - else: - field = self.fields[name] + elif field := self.fields.get(name): field.disabled = True if hasattr(field, "queryset"): field.queryset = field.queryset.none() diff --git a/product_portfolio/tests/test_views.py b/product_portfolio/tests/test_views.py index 91326914..3f0a1142 100644 --- a/product_portfolio/tests/test_views.py +++ b/product_portfolio/tests/test_views.py @@ -2680,6 +2680,45 @@ def test_product_portfolio_product_manage_packages_grid_view_permissions(self): response = self.client.get(manage_url) self.assertEqual(200, response.status_code) + def test_product_portfolio_product_manage_packages_grid_fields_permissions(self): + add_perms(self.basic_user, ["change_productpackage"]) + assign_perm("view_product", self.basic_user, self.product1) + assign_perm("change_product", self.basic_user, self.product1) + self.client.login(username=self.basic_user.username, password="secret") + + make_product_package(self.product1) + manage_url = self.product1.get_manage_packages_url() + response = self.client.get(manage_url) + + self.assertEqual(200, response.status_code) + expected = ( + '" + ) + self.assertContains(response, expected, html=True) + form = response.context["formset"].forms[0] + self.assertIn("review_status", form.fields) + self.assertTrue(form.fields["review_status"].disabled) + + data = { + "form-TOTAL_FORMS": 1, + "form-INITIAL_FORMS": 0, + "form-MIN_NUM_FORMS": 0, + "form-MAX_NUM_FORMS": 1000, + "form-0-product": self.product1.pk, + "form-0-package": self.package1.pk, + "form-0-object_display": str(self.package1), + "form-0-review_status": "PROTECTED FIELD", + "form-0-notes": "Some notes", + } + response = self.client.post(manage_url, data, follow=True) + self.assertContains(response, "Product changes saved.") + self.assertRedirects(response, manage_url) + pp2 = ProductPackage.objects.get(product=self.product1, package=self.package1.pk) + self.assertIsNone(pp2.review_status) + def test_product_portfolio_product_manage_packages_grid_view_delete(self): self.client.login(username=self.basic_user.username, password="secret")