From ac3e0ebe6298ef1f4140d4d59f0976c044b46538 Mon Sep 17 00:00:00 2001 From: Jino Tesauro Date: Wed, 16 Sep 2026 10:56:20 -0500 Subject: [PATCH] fix(fixtures): ship the asset attribute option rows in the sample-data fixtures #15869 converted Product.platform/lifecycle/origin to foreign keys and rewrote the sample-data fixtures to reference the seeded Product_Platform/Product_Lifecycle/ Product_Origin rows by pk, but did not add those rows to the fixtures. Because Product carries a tagulous TagField, tagulous re-tags every deserialised product and resolves each FK with a real SELECT, so the fixture only loaded when the 0297 seed rows were still present. Loading it into a flushed database (the demo server's reset) failed with "Product_Platform matching query does not exist", rolled the whole fixture back, and left the UI on default system settings. Add the fourteen option rows, with the same pks and values as the 0297 seed, ahead of the first dojo.product entry in both fixtures, and add a regression test that empties the three lookup tables before loaddata so the fixtures stay self-contained. Co-Authored-By: Claude Fable 5.1 --- dojo/fixtures/defect_dojo_sample_data.json | 140 ++++++++++++++++++ .../defect_dojo_sample_data_locations.json | 140 ++++++++++++++++++ unittests/test_sample_data.py | 27 ++++ 3 files changed, 307 insertions(+) diff --git a/dojo/fixtures/defect_dojo_sample_data.json b/dojo/fixtures/defect_dojo_sample_data.json index 081881a478..fa791f3275 100644 --- a/dojo/fixtures/defect_dojo_sample_data.json +++ b/dojo/fixtures/defect_dojo_sample_data.json @@ -2512,6 +2512,146 @@ "model": "dojo.tagulous_product_tags", "pk": 1 }, + { + "fields": { + "display_order": 10, + "icon": "plug", + "name": "API", + "value": "web service" + }, + "model": "dojo.product_platform", + "pk": 1 + }, + { + "fields": { + "display_order": 20, + "icon": "desktop", + "name": "Desktop", + "value": "desktop" + }, + "model": "dojo.product_platform", + "pk": 2 + }, + { + "fields": { + "display_order": 30, + "icon": "shuffle", + "name": "Internet of Things", + "value": "iot" + }, + "model": "dojo.product_platform", + "pk": 3 + }, + { + "fields": { + "display_order": 40, + "icon": "mobile", + "name": "Mobile", + "value": "mobile" + }, + "model": "dojo.product_platform", + "pk": 4 + }, + { + "fields": { + "display_order": 50, + "icon": "rectangle-list", + "name": "Web", + "value": "web" + }, + "model": "dojo.product_platform", + "pk": 5 + }, + { + "fields": { + "display_order": 10, + "icon": "compass", + "name": "Construction", + "value": "construction" + }, + "model": "dojo.product_lifecycle", + "pk": 1 + }, + { + "fields": { + "display_order": 20, + "icon": "ship", + "name": "Production", + "value": "production" + }, + "model": "dojo.product_lifecycle", + "pk": 2 + }, + { + "fields": { + "display_order": 30, + "icon": "moon", + "name": "Retirement", + "value": "retirement" + }, + "model": "dojo.product_lifecycle", + "pk": 3 + }, + { + "fields": { + "display_order": 10, + "icon": "book", + "name": "Third Party Library", + "value": "third party library" + }, + "model": "dojo.product_origin", + "pk": 1 + }, + { + "fields": { + "display_order": 20, + "icon": "money-bill", + "name": "Purchased", + "value": "purchased" + }, + "model": "dojo.product_origin", + "pk": 2 + }, + { + "fields": { + "display_order": 30, + "icon": "suitcase", + "name": "Contractor Developed", + "value": "contractor" + }, + "model": "dojo.product_origin", + "pk": 3 + }, + { + "fields": { + "display_order": 40, + "icon": "home", + "name": "Internally Developed", + "value": "internal" + }, + "model": "dojo.product_origin", + "pk": 4 + }, + { + "fields": { + "display_order": 50, + "icon": "code", + "name": "Open Source", + "value": "open source" + }, + "model": "dojo.product_origin", + "pk": 5 + }, + { + "fields": { + "display_order": 60, + "icon": "globe", + "name": "Outsourced", + "value": "outsourced" + }, + "model": "dojo.product_origin", + "pk": 6 + }, { "fields": { "async_updating": false, diff --git a/dojo/fixtures/defect_dojo_sample_data_locations.json b/dojo/fixtures/defect_dojo_sample_data_locations.json index 9741486eec..0f1d8faf2c 100644 --- a/dojo/fixtures/defect_dojo_sample_data_locations.json +++ b/dojo/fixtures/defect_dojo_sample_data_locations.json @@ -2521,6 +2521,146 @@ "model": "dojo.tagulous_product_tags", "pk": 1 }, + { + "fields": { + "display_order": 10, + "icon": "plug", + "name": "API", + "value": "web service" + }, + "model": "dojo.product_platform", + "pk": 1 + }, + { + "fields": { + "display_order": 20, + "icon": "desktop", + "name": "Desktop", + "value": "desktop" + }, + "model": "dojo.product_platform", + "pk": 2 + }, + { + "fields": { + "display_order": 30, + "icon": "shuffle", + "name": "Internet of Things", + "value": "iot" + }, + "model": "dojo.product_platform", + "pk": 3 + }, + { + "fields": { + "display_order": 40, + "icon": "mobile", + "name": "Mobile", + "value": "mobile" + }, + "model": "dojo.product_platform", + "pk": 4 + }, + { + "fields": { + "display_order": 50, + "icon": "rectangle-list", + "name": "Web", + "value": "web" + }, + "model": "dojo.product_platform", + "pk": 5 + }, + { + "fields": { + "display_order": 10, + "icon": "compass", + "name": "Construction", + "value": "construction" + }, + "model": "dojo.product_lifecycle", + "pk": 1 + }, + { + "fields": { + "display_order": 20, + "icon": "ship", + "name": "Production", + "value": "production" + }, + "model": "dojo.product_lifecycle", + "pk": 2 + }, + { + "fields": { + "display_order": 30, + "icon": "moon", + "name": "Retirement", + "value": "retirement" + }, + "model": "dojo.product_lifecycle", + "pk": 3 + }, + { + "fields": { + "display_order": 10, + "icon": "book", + "name": "Third Party Library", + "value": "third party library" + }, + "model": "dojo.product_origin", + "pk": 1 + }, + { + "fields": { + "display_order": 20, + "icon": "money-bill", + "name": "Purchased", + "value": "purchased" + }, + "model": "dojo.product_origin", + "pk": 2 + }, + { + "fields": { + "display_order": 30, + "icon": "suitcase", + "name": "Contractor Developed", + "value": "contractor" + }, + "model": "dojo.product_origin", + "pk": 3 + }, + { + "fields": { + "display_order": 40, + "icon": "home", + "name": "Internally Developed", + "value": "internal" + }, + "model": "dojo.product_origin", + "pk": 4 + }, + { + "fields": { + "display_order": 50, + "icon": "code", + "name": "Open Source", + "value": "open source" + }, + "model": "dojo.product_origin", + "pk": 5 + }, + { + "fields": { + "display_order": 60, + "icon": "globe", + "name": "Outsourced", + "value": "outsourced" + }, + "model": "dojo.product_origin", + "pk": 6 + }, { "fields": { "async_updating": false, diff --git a/unittests/test_sample_data.py b/unittests/test_sample_data.py index ee076b9561..b91e2ba1ac 100644 --- a/unittests/test_sample_data.py +++ b/unittests/test_sample_data.py @@ -1,6 +1,9 @@ from django.conf import settings from django.core.management import call_command +from dojo.models import Product +from dojo.product_attributes.models import Product_Lifecycle, Product_Origin, Product_Platform + from .dojo_test_case import DojoTestCase @@ -29,3 +32,27 @@ def test_loaddata(self): except Exception as e: self.assertEqual(False, True, e) self.assertEqual(True, True) + + # Regression: the sample-data fixtures referenced the Product_Platform / Product_Lifecycle / + # Product_Origin rows seeded by migration 0297 by pk without shipping the rows themselves, so + # loading them into a database whose tables had been emptied (``manage.py flush`` on the demo + # server) failed with "Product_Platform matching query does not exist". + def test_loaddata_is_self_contained_for_asset_attribute_options(self): + Product_Platform.objects.all().delete() + Product_Lifecycle.objects.all().delete() + Product_Origin.objects.all().delete() + + call_command("loaddata", self.get_fixture_file(), verbosity=0) + + expected = { + "BodgeIt": ("web", "production", "internal"), + "Internal CRM App": ("web", "construction", "internal"), + "Apple Accounting Software": ("web", "production", "purchased"), + } + for name, (platform, lifecycle, origin) in expected.items(): + product = Product.objects.get(name=name) + persisted = (product.platform.value, product.lifecycle.value, product.origin.value) + self.assertEqual( + persisted, (platform, lifecycle, origin), + msg=f"{name}: expected {(platform, lifecycle, origin)}, persisted={persisted}", + )