Skip to content

Commit 179c06e

Browse files
Closes #19627: Object change migrators (#19628)
* Initial work on ObjectChange data migrations * Fix migration bug * Add migrators for MAC address assignments * Update reverting kwarg; allow pop() to fail * Cross-reference MAC address migrators * Split migrator logic across migrations * Add missing migrator
1 parent bd8cf64 commit 179c06e

13 files changed

+261
-1
lines changed

netbox/circuits/migrations/0047_circuittermination__termination.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import django.db.models.deletion
2+
from django.contrib.contenttypes.models import ContentType
23
from django.db import migrations, models
34

45

@@ -49,3 +50,26 @@ class Migration(migrations.Migration):
4950
# Copy over existing site assignments
5051
migrations.RunPython(code=copy_site_assignments, reverse_code=migrations.RunPython.noop),
5152
]
53+
54+
55+
def oc_circuittermination_termination(objectchange, reverting):
56+
site_ct = ContentType.objects.get_by_natural_key('dcim', 'site').pk
57+
provider_network_ct = ContentType.objects.get_by_natural_key('circuits', 'providernetwork').pk
58+
for data in (objectchange.prechange_data, objectchange.postchange_data):
59+
if data is None:
60+
continue
61+
if site_id := data.get('site'):
62+
data.update({
63+
'termination_type': site_ct,
64+
'termination_id': site_id,
65+
})
66+
elif provider_network_id := data.get('provider_network'):
67+
data.update({
68+
'termination_type': provider_network_ct,
69+
'termination_id': provider_network_id,
70+
})
71+
72+
73+
objectchange_migrators = {
74+
'circuits.circuittermination': oc_circuittermination_termination,
75+
}

netbox/circuits/migrations/0048_circuitterminations_cached_relations.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,15 @@ class Migration(migrations.Migration):
8686
new_name='_provider_network',
8787
),
8888
]
89+
90+
91+
def oc_circuittermination_remove_fields(objectchange, reverting):
92+
for data in (objectchange.prechange_data, objectchange.postchange_data):
93+
if data is not None:
94+
data.pop('site', None)
95+
data.pop('provider_network', None)
96+
97+
98+
objectchange_migrators = {
99+
'circuits.circuittermination': oc_circuittermination_remove_fields,
100+
}

netbox/circuits/migrations/0051_virtualcircuit_group_assignment.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import django.db.models.deletion
2+
from django.contrib.contenttypes.models import ContentType
23
from django.db import migrations, models
34

45

@@ -82,3 +83,21 @@ class Migration(migrations.Migration):
8283
),
8384
),
8485
]
86+
87+
88+
def oc_circuitgroupassignment_member(objectchange, reverting):
89+
circuit_ct = ContentType.objects.get_by_natural_key('circuits', 'circuit').pk
90+
for data in (objectchange.prechange_data, objectchange.postchange_data):
91+
if data is None:
92+
continue
93+
if circuit_id := data.get('circuit'):
94+
data.update({
95+
'member_type': circuit_ct,
96+
'member_id': circuit_id,
97+
})
98+
data.pop('circuit', None)
99+
100+
101+
objectchange_migrators = {
102+
'circuits.circuitgroupassignment': oc_circuitgroupassignment_member,
103+
}

netbox/dcim/migrations/0188_racktype.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,16 @@ class Migration(migrations.Migration):
100100
),
101101
),
102102
]
103+
104+
105+
def oc_rename_type(objectchange, reverting):
106+
for data in (objectchange.prechange_data, objectchange.postchange_data):
107+
if data is None:
108+
continue
109+
if 'type' in data:
110+
data['form_factor'] = data.pop('type')
111+
112+
113+
objectchange_migrators = {
114+
'dcim.rack': oc_rename_type,
115+
}

netbox/dcim/migrations/0200_populate_mac_addresses.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import django.db.models.deletion
2+
from django.apps import apps
3+
from django.contrib.contenttypes.models import ContentType
24
from django.db import migrations, models
35

46

@@ -15,7 +17,7 @@ def populate_mac_addresses(apps, schema_editor):
1517
assigned_object_type=interface_ct,
1618
assigned_object_id=interface.pk
1719
)
18-
for interface in Interface.objects.filter(mac_address__isnull=False)
20+
for interface in Interface.objects.using(db_alias).filter(mac_address__isnull=False)
1921
]
2022
MACAddress.objects.using(db_alias).bulk_create(mac_addresses, batch_size=100)
2123

@@ -51,3 +53,43 @@ class Migration(migrations.Migration):
5153
name='mac_address',
5254
),
5355
]
56+
57+
58+
# See peer migrator in virtualization.0048_populate_mac_addresses before making changes
59+
def oc_interface_primary_mac_address(objectchange, reverting):
60+
MACAddress = apps.get_model('dcim', 'MACAddress')
61+
interface_ct = ContentType.objects.get_by_natural_key('dcim', 'interface')
62+
63+
# Swap data order if the change is being reverted
64+
if not reverting:
65+
before, after = objectchange.prechange_data, objectchange.postchange_data
66+
else:
67+
before, after = objectchange.postchange_data, objectchange.prechange_data
68+
69+
if after.get('mac_address') != before.get('mac_address'):
70+
# Create & assign the new MACAddress (if any)
71+
if after.get('mac_address'):
72+
mac = MACAddress.objects.create(
73+
mac_address=after['mac_address'],
74+
assigned_object_type=interface_ct,
75+
assigned_object_id=objectchange.changed_object_id,
76+
)
77+
after['primary_mac_address'] = mac.pk
78+
else:
79+
after['primary_mac_address'] = None
80+
# Delete the old MACAddress (if any)
81+
if before.get('mac_address'):
82+
MACAddress.objects.filter(
83+
mac_address=before['mac_address'],
84+
assigned_object_type=interface_ct,
85+
assigned_object_id=objectchange.changed_object_id,
86+
).delete()
87+
before['primary_mac_address'] = None
88+
89+
before.pop('mac_address', None)
90+
after.pop('mac_address', None)
91+
92+
93+
objectchange_migrators = {
94+
'dcim.interface': oc_interface_primary_mac_address,
95+
}

netbox/ipam/migrations/0071_prefix_scope.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import django.db.models.deletion
2+
from django.contrib.contenttypes.models import ContentType
23
from django.db import migrations, models
34

45

@@ -44,3 +45,20 @@ class Migration(migrations.Migration):
4445
# Copy over existing site assignments
4546
migrations.RunPython(code=copy_site_assignments, reverse_code=migrations.RunPython.noop),
4647
]
48+
49+
50+
def oc_prefix_scope(objectchange, reverting):
51+
site_ct = ContentType.objects.get_by_natural_key('dcim', 'site').pk
52+
for data in (objectchange.prechange_data, objectchange.postchange_data):
53+
if data is None:
54+
continue
55+
if site_id := data.get('site'):
56+
data.update({
57+
'scope_type': site_ct,
58+
'scope_id': site_id,
59+
})
60+
61+
62+
objectchange_migrators = {
63+
'ipam.prefix': oc_prefix_scope,
64+
}

netbox/ipam/migrations/0072_prefix_cached_relations.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,14 @@ class Migration(migrations.Migration):
6060
name='site',
6161
),
6262
]
63+
64+
65+
def oc_prefix_remove_fields(objectchange, reverting):
66+
for data in (objectchange.prechange_data, objectchange.postchange_data):
67+
if data is not None:
68+
data.pop('site', None)
69+
70+
71+
objectchange_migrators = {
72+
'ipam.prefix': oc_prefix_remove_fields,
73+
}

netbox/ipam/migrations/0080_populate_service_parent.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.contrib.contenttypes.models import ContentType
12
from django.db import migrations
23
from django.db.models import F
34

@@ -54,3 +55,26 @@ class Migration(migrations.Migration):
5455
reverse_code=repopulate_device_and_virtualmachine_relations,
5556
)
5657
]
58+
59+
60+
def oc_service_parent(objectchange, reverting):
61+
device_ct = ContentType.objects.get_by_natural_key('dcim', 'device').pk
62+
virtual_machine_ct = ContentType.objects.get_by_natural_key('virtualization', 'virtualmachine').pk
63+
for data in (objectchange.prechange_data, objectchange.postchange_data):
64+
if data is None:
65+
continue
66+
if device_id := data.get('device'):
67+
data.update({
68+
'parent_object_type': device_ct,
69+
'parent_object_id': device_id,
70+
})
71+
elif virtual_machine_id := data.get('virtual_machine'):
72+
data.update({
73+
'parent_object_type': virtual_machine_ct,
74+
'parent_object_id': virtual_machine_id,
75+
})
76+
77+
78+
objectchange_migrators = {
79+
'ipam.service': oc_service_parent,
80+
}

netbox/ipam/migrations/0081_remove_service_device_virtual_machine_add_parent_gfk_index.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,15 @@ class Migration(migrations.Migration):
3737
),
3838
),
3939
]
40+
41+
42+
def oc_service_remove_fields(objectchange, reverting):
43+
for data in (objectchange.prechange_data, objectchange.postchange_data):
44+
if data is not None:
45+
data.pop('device', None)
46+
data.pop('virtual_machine', None)
47+
48+
49+
objectchange_migrators = {
50+
'ipam.service': oc_service_remove_fields,
51+
}

netbox/tenancy/migrations/0018_contact_groups.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,17 @@ class Migration(migrations.Migration):
6666
name='group',
6767
),
6868
]
69+
70+
71+
def oc_contact_groups(objectchange, reverting):
72+
for data in (objectchange.prechange_data, objectchange.postchange_data):
73+
if data is None:
74+
continue
75+
# Set the M2M field `groups` to a list containing the group ID
76+
data['groups'] = [data['group']] if data.get('group') else []
77+
data.pop('group', None)
78+
79+
80+
objectchange_migrators = {
81+
'tenancy.contact': oc_contact_groups,
82+
}

0 commit comments

Comments
 (0)