فهرست منبع

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
Jeremy Stretch 8 ماه پیش
والد
کامیت
179c06ec20

+ 24 - 0
netbox/circuits/migrations/0047_circuittermination__termination.py

@@ -1,4 +1,5 @@
 import django.db.models.deletion
+from django.contrib.contenttypes.models import ContentType
 from django.db import migrations, models
 
 
@@ -49,3 +50,26 @@ class Migration(migrations.Migration):
         # Copy over existing site assignments
         migrations.RunPython(code=copy_site_assignments, reverse_code=migrations.RunPython.noop),
     ]
+
+
+def oc_circuittermination_termination(objectchange, reverting):
+    site_ct = ContentType.objects.get_by_natural_key('dcim', 'site').pk
+    provider_network_ct = ContentType.objects.get_by_natural_key('circuits', 'providernetwork').pk
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is None:
+            continue
+        if site_id := data.get('site'):
+            data.update({
+                'termination_type': site_ct,
+                'termination_id': site_id,
+            })
+        elif provider_network_id := data.get('provider_network'):
+            data.update({
+                'termination_type': provider_network_ct,
+                'termination_id': provider_network_id,
+            })
+
+
+objectchange_migrators = {
+    'circuits.circuittermination': oc_circuittermination_termination,
+}

+ 12 - 0
netbox/circuits/migrations/0048_circuitterminations_cached_relations.py

@@ -86,3 +86,15 @@ class Migration(migrations.Migration):
             new_name='_provider_network',
         ),
     ]
+
+
+def oc_circuittermination_remove_fields(objectchange, reverting):
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is not None:
+            data.pop('site', None)
+            data.pop('provider_network', None)
+
+
+objectchange_migrators = {
+    'circuits.circuittermination': oc_circuittermination_remove_fields,
+}

+ 19 - 0
netbox/circuits/migrations/0051_virtualcircuit_group_assignment.py

@@ -1,4 +1,5 @@
 import django.db.models.deletion
+from django.contrib.contenttypes.models import ContentType
 from django.db import migrations, models
 
 
@@ -82,3 +83,21 @@ class Migration(migrations.Migration):
             ),
         ),
     ]
+
+
+def oc_circuitgroupassignment_member(objectchange, reverting):
+    circuit_ct = ContentType.objects.get_by_natural_key('circuits', 'circuit').pk
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is None:
+            continue
+        if circuit_id := data.get('circuit'):
+            data.update({
+                'member_type': circuit_ct,
+                'member_id': circuit_id,
+            })
+        data.pop('circuit', None)
+
+
+objectchange_migrators = {
+    'circuits.circuitgroupassignment': oc_circuitgroupassignment_member,
+}

+ 13 - 0
netbox/dcim/migrations/0188_racktype.py

@@ -100,3 +100,16 @@ class Migration(migrations.Migration):
             ),
         ),
     ]
+
+
+def oc_rename_type(objectchange, reverting):
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is None:
+            continue
+        if 'type' in data:
+            data['form_factor'] = data.pop('type')
+
+
+objectchange_migrators = {
+    'dcim.rack': oc_rename_type,
+}

+ 43 - 1
netbox/dcim/migrations/0200_populate_mac_addresses.py

@@ -1,4 +1,6 @@
 import django.db.models.deletion
+from django.apps import apps
+from django.contrib.contenttypes.models import ContentType
 from django.db import migrations, models
 
 
@@ -15,7 +17,7 @@ def populate_mac_addresses(apps, schema_editor):
             assigned_object_type=interface_ct,
             assigned_object_id=interface.pk
         )
-        for interface in Interface.objects.filter(mac_address__isnull=False)
+        for interface in Interface.objects.using(db_alias).filter(mac_address__isnull=False)
     ]
     MACAddress.objects.using(db_alias).bulk_create(mac_addresses, batch_size=100)
 
@@ -51,3 +53,43 @@ class Migration(migrations.Migration):
             name='mac_address',
         ),
     ]
+
+
+# See peer migrator in virtualization.0048_populate_mac_addresses before making changes
+def oc_interface_primary_mac_address(objectchange, reverting):
+    MACAddress = apps.get_model('dcim', 'MACAddress')
+    interface_ct = ContentType.objects.get_by_natural_key('dcim', 'interface')
+
+    # Swap data order if the change is being reverted
+    if not reverting:
+        before, after = objectchange.prechange_data, objectchange.postchange_data
+    else:
+        before, after = objectchange.postchange_data, objectchange.prechange_data
+
+    if after.get('mac_address') != before.get('mac_address'):
+        # Create & assign the new MACAddress (if any)
+        if after.get('mac_address'):
+            mac = MACAddress.objects.create(
+                mac_address=after['mac_address'],
+                assigned_object_type=interface_ct,
+                assigned_object_id=objectchange.changed_object_id,
+            )
+            after['primary_mac_address'] = mac.pk
+        else:
+            after['primary_mac_address'] = None
+        # Delete the old MACAddress (if any)
+        if before.get('mac_address'):
+            MACAddress.objects.filter(
+                mac_address=before['mac_address'],
+                assigned_object_type=interface_ct,
+                assigned_object_id=objectchange.changed_object_id,
+            ).delete()
+        before['primary_mac_address'] = None
+
+    before.pop('mac_address', None)
+    after.pop('mac_address', None)
+
+
+objectchange_migrators = {
+    'dcim.interface': oc_interface_primary_mac_address,
+}

+ 18 - 0
netbox/ipam/migrations/0071_prefix_scope.py

@@ -1,4 +1,5 @@
 import django.db.models.deletion
+from django.contrib.contenttypes.models import ContentType
 from django.db import migrations, models
 
 
@@ -44,3 +45,20 @@ class Migration(migrations.Migration):
         # Copy over existing site assignments
         migrations.RunPython(code=copy_site_assignments, reverse_code=migrations.RunPython.noop),
     ]
+
+
+def oc_prefix_scope(objectchange, reverting):
+    site_ct = ContentType.objects.get_by_natural_key('dcim', 'site').pk
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is None:
+            continue
+        if site_id := data.get('site'):
+            data.update({
+                'scope_type': site_ct,
+                'scope_id': site_id,
+            })
+
+
+objectchange_migrators = {
+    'ipam.prefix': oc_prefix_scope,
+}

+ 11 - 0
netbox/ipam/migrations/0072_prefix_cached_relations.py

@@ -60,3 +60,14 @@ class Migration(migrations.Migration):
             name='site',
         ),
     ]
+
+
+def oc_prefix_remove_fields(objectchange, reverting):
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is not None:
+            data.pop('site', None)
+
+
+objectchange_migrators = {
+    'ipam.prefix': oc_prefix_remove_fields,
+}

+ 24 - 0
netbox/ipam/migrations/0080_populate_service_parent.py

@@ -1,3 +1,4 @@
+from django.contrib.contenttypes.models import ContentType
 from django.db import migrations
 from django.db.models import F
 
@@ -54,3 +55,26 @@ class Migration(migrations.Migration):
                 reverse_code=repopulate_device_and_virtualmachine_relations,
             )
     ]
+
+
+def oc_service_parent(objectchange, reverting):
+    device_ct = ContentType.objects.get_by_natural_key('dcim', 'device').pk
+    virtual_machine_ct = ContentType.objects.get_by_natural_key('virtualization', 'virtualmachine').pk
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is None:
+            continue
+        if device_id := data.get('device'):
+            data.update({
+                'parent_object_type': device_ct,
+                'parent_object_id': device_id,
+            })
+        elif virtual_machine_id := data.get('virtual_machine'):
+            data.update({
+                'parent_object_type': virtual_machine_ct,
+                'parent_object_id': virtual_machine_id,
+            })
+
+
+objectchange_migrators = {
+    'ipam.service': oc_service_parent,
+}

+ 12 - 0
netbox/ipam/migrations/0081_remove_service_device_virtual_machine_add_parent_gfk_index.py

@@ -37,3 +37,15 @@ class Migration(migrations.Migration):
             ),
         ),
     ]
+
+
+def oc_service_remove_fields(objectchange, reverting):
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is not None:
+            data.pop('device', None)
+            data.pop('virtual_machine', None)
+
+
+objectchange_migrators = {
+    'ipam.service': oc_service_remove_fields,
+}

+ 14 - 0
netbox/tenancy/migrations/0018_contact_groups.py

@@ -66,3 +66,17 @@ class Migration(migrations.Migration):
             name='group',
         ),
     ]
+
+
+def oc_contact_groups(objectchange, reverting):
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is None:
+            continue
+        # Set the M2M field `groups` to a list containing the group ID
+        data['groups'] = [data['group']] if data.get('group') else []
+        data.pop('group', None)
+
+
+objectchange_migrators = {
+    'tenancy.contact': oc_contact_groups,
+}

+ 18 - 0
netbox/virtualization/migrations/0044_cluster_scope.py

@@ -1,4 +1,5 @@
 import django.db.models.deletion
+from django.contrib.contenttypes.models import ContentType
 from django.db import migrations, models
 
 
@@ -43,3 +44,20 @@ class Migration(migrations.Migration):
         # Copy over existing site assignments
         migrations.RunPython(code=copy_site_assignments, reverse_code=migrations.RunPython.noop),
     ]
+
+
+def oc_cluster_scope(objectchange, reverting):
+    site_ct = ContentType.objects.get_by_natural_key('dcim', 'site').pk
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is None:
+            continue
+        if site_id := data.get('site'):
+            data.update({
+                'scope_type': site_ct,
+                'scope_id': site_id,
+            })
+
+
+objectchange_migrators = {
+    'virtualization.cluster': oc_cluster_scope,
+}

+ 11 - 0
netbox/virtualization/migrations/0045_clusters_cached_relations.py

@@ -87,3 +87,14 @@ class Migration(migrations.Migration):
             ),
         ),
     ]
+
+
+def oc_cluster_remove_site(objectchange, reverting):
+    for data in (objectchange.prechange_data, objectchange.postchange_data):
+        if data is not None:
+            data.pop('site', None)
+
+
+objectchange_migrators = {
+    'virtualization.cluster': oc_cluster_remove_site,
+}

+ 42 - 0
netbox/virtualization/migrations/0048_populate_mac_addresses.py

@@ -1,4 +1,6 @@
 import django.db.models.deletion
+from django.apps import apps
+from django.contrib.contenttypes.models import ContentType
 from django.db import migrations, models
 
 
@@ -50,3 +52,43 @@ class Migration(migrations.Migration):
             name='mac_address',
         ),
     ]
+
+
+# See peer migrator in dcim.0200_populate_mac_addresses before making changes
+def oc_vminterface_primary_mac_address(objectchange, reverting):
+    MACAddress = apps.get_model('dcim', 'MACAddress')
+    vminterface_ct = ContentType.objects.get_by_natural_key('virtualization', 'vminterface')
+
+    # Swap data order if the change is being reverted
+    if not reverting:
+        before, after = objectchange.prechange_data, objectchange.postchange_data
+    else:
+        before, after = objectchange.postchange_data, objectchange.prechange_data
+
+    if after.get('mac_address') != before.get('mac_address'):
+        # Create & assign the new MACAddress (if any)
+        if after.get('mac_address'):
+            mac = MACAddress.objects.create(
+                mac_address=after['mac_address'],
+                assigned_object_type=vminterface_ct,
+                assigned_object_id=objectchange.changed_object_id,
+            )
+            after['primary_mac_address'] = mac.pk
+        else:
+            after['primary_mac_address'] = None
+        # Delete the old MACAddress (if any)
+        if before.get('mac_address'):
+            MACAddress.objects.filter(
+                mac_address=before['mac_address'],
+                assigned_object_type=vminterface_ct,
+                assigned_object_id=objectchange.changed_object_id,
+            ).delete()
+        before['primary_mac_address'] = None
+
+    before.pop('mac_address', None)
+    after.pop('mac_address', None)
+
+
+objectchange_migrators = {
+    'virtualization.vminterface': oc_vminterface_primary_mac_address,
+}