Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion layers/fixture_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

LAYER_MODEL = "layers.layer"
MULTILAYER_ASSOCIATION_MODEL = "layers.multilayerassociation"
MULTILAYER_DIMENSION_MODEL = "layers.multilayerdimension"
MULTILAYER_DIMENSION_VALUE_MODEL = "layers.multilayerdimensionvalue"
ATTRIBUTE_INFO_MODEL = "layers.attributeinfo"
LOOKUP_INFO_MODEL = "layers.lookupinfo"
COMPANIONSHIP_MODEL = "layers.companionship"
Expand Down Expand Up @@ -137,8 +139,12 @@ def import_fixture_rows(

Layer = apps.get_model(LAYER_MODEL)
MultilayerAssociation = apps.get_model(MULTILAYER_ASSOCIATION_MODEL)
MultilayerDimension = apps.get_model(MULTILAYER_DIMENSION_MODEL)
MultilayerDimensionValue = apps.get_model(MULTILAYER_DIMENSION_VALUE_MODEL)
layer_manager = _model_manager(Layer)
association_manager = _model_manager(MultilayerAssociation)
dimension_manager = _model_manager(MultilayerDimension)
dimension_value_manager = _model_manager(MultilayerDimensionValue)

def _execute_import():
# First pass: upsert UUID-keyed rows that do not require relation remaps.
Expand Down Expand Up @@ -213,6 +219,59 @@ def _execute_import():
assoc_obj.layer = layer_obj
assoc_obj.save()

# Second pass: upsert multilayer dimensions and resolve owning layer by UUID.
for row in rows:
if row.get(NODE_MODEL_KEY) != MULTILAYER_DIMENSION_MODEL:
continue

dimension_uuid = normalize_uuid(row.get(NODE_UUID_KEY))
if not dimension_uuid:
raise ValueError("MultilayerDimension row missing UUID")

relations = row.get(NODE_RELATIONS_KEY, {})
layer_ref = relations.get("layer")
if not layer_ref:
raise ValueError("Missing layer relation for MultilayerDimension")

owner_layer = _resolve_ref_instance(layer_ref, missing_ref_policy)

dimension_obj = dimension_manager.filter(uuid=dimension_uuid).first()
if dimension_obj is None:
dimension_obj = MultilayerDimension(uuid=dimension_uuid)

_apply_fields(dimension_obj, row.get(NODE_FIELDS_KEY, {}))
dimension_obj.layer = owner_layer
dimension_obj.save()

# Second pass: upsert multilayer dimension values and resolve relations by UUID.
for row in rows:
if row.get(NODE_MODEL_KEY) != MULTILAYER_DIMENSION_VALUE_MODEL:
continue

value_uuid = normalize_uuid(row.get(NODE_UUID_KEY))
if not value_uuid:
raise ValueError("MultilayerDimensionValue row missing UUID")

relations = row.get(NODE_RELATIONS_KEY, {})
dimension_ref = relations.get("dimension")
if not dimension_ref:
raise ValueError("Missing dimension relation for MultilayerDimensionValue")

dimension_obj = _resolve_ref_instance(dimension_ref, missing_ref_policy)
resolved_associations = _resolve_ref_list(
relations.get("associations") or [],
missing_ref_policy,
)

value_obj = dimension_value_manager.filter(uuid=value_uuid).first()
if value_obj is None:
value_obj = MultilayerDimensionValue(uuid=value_uuid)

_apply_fields(value_obj, row.get(NODE_FIELDS_KEY, {}))
value_obj.dimension = dimension_obj
value_obj.save()
value_obj.associations.set(resolved_associations)

# Second pass: companionship relation rows (non-UUID identity).
for row in rows:
if row.get(NODE_MODEL_KEY) != COMPANIONSHIP_MODEL:
Expand Down Expand Up @@ -318,7 +377,13 @@ def import_multilayer_rows(
multilayer_rows = [
row
for row in (rows or [])
if row.get(NODE_MODEL_KEY) in {LAYER_MODEL, MULTILAYER_ASSOCIATION_MODEL}
if row.get(NODE_MODEL_KEY)
in {
LAYER_MODEL,
MULTILAYER_ASSOCIATION_MODEL,
MULTILAYER_DIMENSION_MODEL,
MULTILAYER_DIMENSION_VALUE_MODEL,
}
]
return import_fixture_rows(
multilayer_rows,
Expand Down
221 changes: 221 additions & 0 deletions layers/tests/test_fixture_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
LayerXYZ,
LookupInfo,
MultilayerAssociation,
MultilayerDimension,
MultilayerDimensionValue,
)

try:
Expand Down Expand Up @@ -531,6 +533,225 @@ def test_missing_attribute_relation_uuid_raises_error_under_strict_policy(self):
with self.assertRaises(ValueError):
import_fixture_rows(fixture_rows, **self._import_kwargs())


class LayerFixtureImportPR07Test(TestCase):
"""PR07 contract tests for multilayer import graph integrity."""

def _require_importer(self):
self.assertIsNotNone(
import_fixture_rows,
"importer API missing: expected layers.fixture_import.import_fixture_rows",
)

def _import_kwargs(self):
return {
"dry_run": False,
"associate_all_sites": True,
"missing_ref_policy": "error",
"duplicate_uuid_policy": "error",
}

def _layer_fields(self, name, layer_type="WMS"):
return {
"name": name,
"layer_type": layer_type,
"slug_name": None,
"url": None,
}

def test_multilayer_dimension_value_association_graph_resolves_by_uuid(self):
self._require_importer()

parent_uuid = uuid4()
target_a_uuid = uuid4()
target_b_uuid = uuid4()
dimension_uuid = uuid4()
association_a_uuid = uuid4()
association_b_uuid = uuid4()
value_1_uuid = uuid4()
value_2_uuid = uuid4()

fixture_rows = [
build_node(
model="layers.layer",
source_pk=1001,
uuid_value=parent_uuid,
fields=self._layer_fields("Parent Slider Layer"),
relations={},
),
build_node(
model="layers.layer",
source_pk=1002,
uuid_value=target_a_uuid,
fields=self._layer_fields("Target A"),
relations={},
),
build_node(
model="layers.layer",
source_pk=1003,
uuid_value=target_b_uuid,
fields=self._layer_fields("Target B"),
relations={},
),
build_node(
model="layers.multilayerdimension",
source_pk=1101,
uuid_value=dimension_uuid,
fields={
"name": "Year",
"label": "Year",
"order": 10,
"animated": True,
"angle_labels": False,
},
relations={
"layer": build_ref(
model="layers.layer",
source_pk=50101,
uuid_value=parent_uuid,
)
},
),
build_node(
model="layers.multilayerassociation",
source_pk=1201,
uuid_value=association_a_uuid,
fields={"name": "A"},
relations={
"parentLayer": build_ref(
model="layers.layer",
source_pk=50201,
uuid_value=parent_uuid,
),
"layer": build_ref(
model="layers.layer",
source_pk=50202,
uuid_value=target_a_uuid,
),
},
),
build_node(
model="layers.multilayerassociation",
source_pk=1202,
uuid_value=association_b_uuid,
fields={"name": "B"},
relations={
"parentLayer": build_ref(
model="layers.layer",
source_pk=50301,
uuid_value=parent_uuid,
),
"layer": build_ref(
model="layers.layer",
source_pk=50302,
uuid_value=target_b_uuid,
),
},
),
build_node(
model="layers.multilayerdimensionvalue",
source_pk=1301,
uuid_value=value_1_uuid,
fields={"value": "2020", "label": "2020", "order": 1},
relations={
"dimension": build_ref(
model="layers.multilayerdimension",
source_pk=50401,
uuid_value=dimension_uuid,
),
"associations": [
build_ref(
model="layers.multilayerassociation",
source_pk=50402,
uuid_value=association_a_uuid,
)
],
},
),
build_node(
model="layers.multilayerdimensionvalue",
source_pk=1302,
uuid_value=value_2_uuid,
fields={"value": "2021", "label": "2021", "order": 2},
relations={
"dimension": build_ref(
model="layers.multilayerdimension",
source_pk=50501,
uuid_value=dimension_uuid,
),
"associations": [
build_ref(
model="layers.multilayerassociation",
source_pk=50502,
uuid_value=association_b_uuid,
)
],
},
),
]

import_fixture_rows(fixture_rows, **self._import_kwargs())

imported_parent = Layer.objects.get(uuid=parent_uuid)
imported_target_a = Layer.objects.get(uuid=target_a_uuid)
imported_target_b = Layer.objects.get(uuid=target_b_uuid)

imported_dimension = MultilayerDimension.objects.get(uuid=dimension_uuid)
self.assertEqual(imported_dimension.layer_id, imported_parent.pk)

association_a = MultilayerAssociation.objects.get(uuid=association_a_uuid)
association_b = MultilayerAssociation.objects.get(uuid=association_b_uuid)
self.assertEqual(association_a.parentLayer_id, imported_parent.pk)
self.assertEqual(association_b.parentLayer_id, imported_parent.pk)
self.assertEqual(association_a.layer_id, imported_target_a.pk)
self.assertEqual(association_b.layer_id, imported_target_b.pk)

value_1 = MultilayerDimensionValue.objects.get(uuid=value_1_uuid)
value_2 = MultilayerDimensionValue.objects.get(uuid=value_2_uuid)
self.assertEqual(value_1.dimension_id, imported_dimension.pk)
self.assertEqual(value_2.dimension_id, imported_dimension.pk)
self.assertEqual(
set(value_1.associations.values_list("uuid", flat=True)),
{association_a_uuid},
)
self.assertEqual(
set(value_2.associations.values_list("uuid", flat=True)),
{association_b_uuid},
)

def test_missing_dimension_or_association_reference_raises_in_strict_mode(self):
self._require_importer()

value_uuid = uuid4()
missing_dimension_uuid = uuid4()
missing_association_uuid = uuid4()

fixture_rows = [
build_node(
model="layers.multilayerdimensionvalue",
source_pk=1401,
uuid_value=value_uuid,
fields={"value": "X", "label": "X", "order": 1},
relations={
"dimension": build_ref(
model="layers.multilayerdimension",
source_pk=60101,
uuid_value=missing_dimension_uuid,
),
"associations": [
build_ref(
model="layers.multilayerassociation",
source_pk=60102,
uuid_value=missing_association_uuid,
)
],
},
)
]

with self.assertRaises(ValueError):
import_fixture_rows(fixture_rows, **self._import_kwargs())

def test_second_pass_resolves_specific_layer_rows_by_layer_uuid(self):
self._require_importer()

Expand Down