fix: prevent module component duplication during import

This commit is contained in:
2026-08-05 14:24:46 +02:00
parent 8107e152d2
commit d4ba5de2e7
8 changed files with 119 additions and 7 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ class NetBoxExportConfig(PluginConfig):
name = "netbox_export"
verbose_name = "NetBox-Export"
description = "Portable ZIP export and import for tenants and locations"
version = "0.3.7"
version = "0.3.8"
author = "NetBox Export contributors"
base_url = "netbox-export"
min_version = "4.6.0"
+1 -1
View File
@@ -39,7 +39,7 @@ def export_scope(
"created_at": datetime.now(UTC).isoformat(),
"source_instance": str(InstanceIdentity.local_id()),
"source_netbox_version": getattr(getattr(settings, "RELEASE", None), "version", "4.6"),
"plugin_version": "0.3.7",
"plugin_version": "0.3.8",
"scope": {
"type": scope_type,
"source_pk": str(scope_id),
+2 -1
View File
@@ -41,7 +41,7 @@ EXPLICIT_IDENTITIES = {
"dcim.frontport": ("device", "name"),
"dcim.rearport": ("device", "name"),
"dcim.devicebay": ("device", "name"),
"dcim.modulebay": ("device", "name"),
"dcim.modulebay": ("device", "module", "name"),
"dcim.inventoryitem": ("device", "parent", "name"),
"ipam.prefix": ("vrf", "prefix"),
"ipam.ipaddress": ("vrf", "address"),
@@ -411,6 +411,7 @@ def import_archive(parsed: ParsedArchive, *, conflict_strategy: str, dry_run: bo
progressed = True
continue
_set_files(obj, record, parsed.assets, saved_files, dry_run=dry_run)
compatibility.prepare_initial_save(obj, is_new=existing is None)
compatibility.save(obj)
action = "updated" if existing is not None else "created"
writable.add(record_id)
+18 -2
View File
@@ -27,6 +27,13 @@ def is_tenant_relation(field) -> bool:
return getattr(getattr(related_model, "_meta", None), "label_lower", None) == "tenancy.tenant"
def is_module_relation(field) -> bool:
if field.name != "module":
return False
related_model = getattr(field.remote_field, "model", None)
return getattr(getattr(related_model, "_meta", None), "label_lower", None) == "dcim.module"
def _condition_field_names(condition):
for child in getattr(condition, "children", ()):
if isinstance(child, tuple):
@@ -47,8 +54,10 @@ def check_constraint_field_names(model) -> frozenset[str]:
def relation_required_before_save(field) -> bool:
return is_tenant_relation(field) or field.name in check_constraint_field_names(
getattr(field, "model", None)
return (
is_tenant_relation(field)
or is_module_relation(field)
or field.name in check_constraint_field_names(getattr(field, "model", None))
)
@@ -136,6 +145,13 @@ class PluginCompatibility:
raise
return obj.save(**kwargs)
@staticmethod
def prepare_initial_save(obj, *, is_new: bool):
if is_new and obj._meta.label_lower == "dcim.module":
# Module.save() otherwise replicates ModuleType components which are
# imported explicitly from the archive and may already exist.
obj._disable_replication = True
def release_unique_relation(self, obj, field_name: str, value):
field = obj._meta.get_field(field_name)
if value is None or not relation_should_be_deferred(field):