ipfabric_netbox 3.1.3__py3-none-any.whl → 3.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ipfabric_netbox/__init__.py +2 -31
- ipfabric_netbox/data/transform_map.json +625 -0
- ipfabric_netbox/exceptions.py +24 -0
- ipfabric_netbox/forms.py +4 -10
- ipfabric_netbox/migrations/0007_prepare_custom_fields.py +102 -0
- ipfabric_netbox/migrations/0008_prepare_transform_maps.py +42 -0
- ipfabric_netbox/migrations/0009_transformmap_changes_for_netbox_v4_2.py +224 -0
- ipfabric_netbox/migrations/0010_remove_uuid_from_get_or_create.py +95 -0
- ipfabric_netbox/models.py +67 -63
- ipfabric_netbox/signals.py +27 -66
- ipfabric_netbox/tables.py +4 -0
- ipfabric_netbox/tests/test_models.py +26 -975
- ipfabric_netbox/utilities/ipfutils.py +327 -200
- ipfabric_netbox/utilities/logging.py +12 -7
- ipfabric_netbox/utilities/nbutils.py +0 -26
- ipfabric_netbox/utilities/transform_map.py +37 -15
- ipfabric_netbox/views.py +3 -9
- {ipfabric_netbox-3.1.3.dist-info → ipfabric_netbox-3.2.1.dist-info}/METADATA +19 -11
- {ipfabric_netbox-3.1.3.dist-info → ipfabric_netbox-3.2.1.dist-info}/RECORD +20 -14
- {ipfabric_netbox-3.1.3.dist-info → ipfabric_netbox-3.2.1.dist-info}/WHEEL +1 -1
ipfabric_netbox/forms.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
|
|
3
3
|
from core.choices import DataSourceStatusChoices
|
|
4
|
+
from core.choices import JobIntervalChoices
|
|
4
5
|
from django import forms
|
|
5
|
-
from django.conf import settings
|
|
6
6
|
from django.contrib.contenttypes.models import ContentType
|
|
7
7
|
from django.core.exceptions import ValidationError
|
|
8
8
|
from django.utils import timezone
|
|
9
9
|
from django.utils.translation import gettext_lazy as _
|
|
10
|
-
from extras.choices import DurationChoices
|
|
11
10
|
from netbox.forms import NetBoxModelFilterSetForm
|
|
12
11
|
from netbox.forms import NetBoxModelForm
|
|
13
|
-
from
|
|
12
|
+
from netbox.forms.mixins import SavedFiltersMixin
|
|
14
13
|
from utilities.datetime import local_now
|
|
15
14
|
from utilities.forms import add_blank_choice
|
|
16
15
|
from utilities.forms import FilterForm
|
|
@@ -34,12 +33,6 @@ from .models import IPFabricSync
|
|
|
34
33
|
from .models import IPFabricTransformField
|
|
35
34
|
from .models import IPFabricTransformMap
|
|
36
35
|
|
|
37
|
-
NETBOX_CURRENT_VERSION = version.parse(settings.VERSION)
|
|
38
|
-
|
|
39
|
-
if NETBOX_CURRENT_VERSION >= version.parse("3.7.0"):
|
|
40
|
-
from netbox.forms.mixins import SavedFiltersMixin
|
|
41
|
-
else:
|
|
42
|
-
from extras.forms.mixins import SavedFiltersMixin
|
|
43
36
|
|
|
44
37
|
exclude_fields = [
|
|
45
38
|
"id",
|
|
@@ -83,6 +76,7 @@ dcim_parameters = {
|
|
|
83
76
|
required=False, label=_("Virtual Chassis"), initial=True
|
|
84
77
|
),
|
|
85
78
|
"interface": forms.BooleanField(required=False, label=_("Interfaces")),
|
|
79
|
+
"macaddress": forms.BooleanField(required=False, label=_("MAC Addresses")),
|
|
86
80
|
"inventoryitem": forms.BooleanField(required=False, label=_("Part Numbers")),
|
|
87
81
|
}
|
|
88
82
|
ipam_parameters = {
|
|
@@ -482,7 +476,7 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
482
476
|
required=False,
|
|
483
477
|
min_value=1,
|
|
484
478
|
label=_("Recurs every"),
|
|
485
|
-
widget=NumberWithOptions(options=
|
|
479
|
+
widget=NumberWithOptions(options=JobIntervalChoices),
|
|
486
480
|
help_text=_("Interval at which this sync is re-run (in minutes)"),
|
|
487
481
|
)
|
|
488
482
|
auto_merge = forms.BooleanField(
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
from extras.choices import CustomFieldTypeChoices
|
|
5
|
+
from extras.choices import CustomFieldUIEditableChoices
|
|
6
|
+
from extras.choices import CustomFieldUIVisibleChoices
|
|
7
|
+
from extras.choices import CustomLinkButtonClassChoices
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from django.apps import apps
|
|
12
|
+
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_custom_field(
|
|
16
|
+
apps: "apps",
|
|
17
|
+
field_name: str,
|
|
18
|
+
label: str,
|
|
19
|
+
models: list,
|
|
20
|
+
object_type=None,
|
|
21
|
+
cf_type: str | None = "type_text",
|
|
22
|
+
):
|
|
23
|
+
"""Create a single custom field and link it to required models."""
|
|
24
|
+
ObjectType = apps.get_model("core", "ObjectType")
|
|
25
|
+
|
|
26
|
+
defaults = {
|
|
27
|
+
"label": label,
|
|
28
|
+
"related_object_type": ObjectType.objects.get_for_model(object_type)
|
|
29
|
+
if object_type
|
|
30
|
+
else None,
|
|
31
|
+
"ui_visible": getattr(CustomFieldUIVisibleChoices, "ALWAYS"),
|
|
32
|
+
"ui_editable": getattr(CustomFieldUIEditableChoices, "NO"),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
custom_field, _ = apps.get_model("extras", "CustomField").objects.update_or_create(
|
|
36
|
+
type=getattr(CustomFieldTypeChoices, cf_type.upper()),
|
|
37
|
+
name=field_name,
|
|
38
|
+
defaults=defaults,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
for model in models:
|
|
42
|
+
custom_field.object_types.add(ObjectType.objects.get_for_model(model))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def prepare_custom_fields(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
46
|
+
"""Forward migration to prepare ipfabric_netbox custom fields and links."""
|
|
47
|
+
Device = apps.get_model("dcim", "Device")
|
|
48
|
+
Site = apps.get_model("dcim", "Site")
|
|
49
|
+
|
|
50
|
+
create_custom_field(
|
|
51
|
+
apps,
|
|
52
|
+
"ipfabric_source",
|
|
53
|
+
"IP Fabric Source",
|
|
54
|
+
[Device, Site],
|
|
55
|
+
cf_type="type_object",
|
|
56
|
+
object_type=apps.get_model("ipfabric_netbox", "IPFabricSource"),
|
|
57
|
+
)
|
|
58
|
+
create_custom_field(
|
|
59
|
+
apps,
|
|
60
|
+
"ipfabric_branch",
|
|
61
|
+
"IP Fabric Last Sync",
|
|
62
|
+
[Device, Site],
|
|
63
|
+
cf_type="type_object",
|
|
64
|
+
object_type=apps.get_model("ipfabric_netbox", "IPFabricBranch"),
|
|
65
|
+
)
|
|
66
|
+
cl, _ = apps.get_model("extras", "CustomLink").objects.update_or_create(
|
|
67
|
+
defaults={
|
|
68
|
+
"link_text": "{% if object.custom_field_data.ipfabric_source is defined %}{% set SOURCE_ID = object.custom_field_data.ipfabric_source %}{% if SOURCE_ID %}IP Fabric{% endif %}{% endif %}",
|
|
69
|
+
"link_url": '{% if object.custom_field_data.ipfabric_source is defined %}{% set SOURCE_ID = object.custom_field_data.ipfabric_source %}{% if SOURCE_ID %}{% set BASE_URL = object.custom_fields.filter(related_object_type__model="ipfabricsource").first().related_object_type.model_class().objects.get(pk=SOURCE_ID).url %}{{ BASE_URL }}/inventory/devices?options={"filters":{"sn": ["like","{{ object.serial }}"]}}{% endif %}{%endif%}',
|
|
70
|
+
"new_window": True,
|
|
71
|
+
"button_class": CustomLinkButtonClassChoices.BLUE,
|
|
72
|
+
},
|
|
73
|
+
name="ipfabric",
|
|
74
|
+
)
|
|
75
|
+
cl.object_types.add(
|
|
76
|
+
apps.get_model("core", "ObjectType").objects.get_for_model(Device)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def cleanup_custom_fields(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
81
|
+
"""Reverse migration to prepare ipfabric_netbox custom fields and links."""
|
|
82
|
+
for custom_field_name in ["ipfabric_source", "ipfabric_branch"]:
|
|
83
|
+
custom_field = apps.get_model("extras", "CustomField").objects.get(
|
|
84
|
+
name=custom_field_name
|
|
85
|
+
)
|
|
86
|
+
for model in custom_field.object_types.all()[:]:
|
|
87
|
+
custom_field.object_types.remove(model)
|
|
88
|
+
custom_field.delete()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Migration(migrations.Migration):
|
|
92
|
+
dependencies = [
|
|
93
|
+
("dcim", "0191_module_bay_rebuild"),
|
|
94
|
+
("extras", "0121_customfield_related_object_filter"),
|
|
95
|
+
(
|
|
96
|
+
"ipfabric_netbox",
|
|
97
|
+
"0006_alter_ipfabrictransformmap_target_model",
|
|
98
|
+
),
|
|
99
|
+
]
|
|
100
|
+
operations = [
|
|
101
|
+
migrations.RunPython(prepare_custom_fields, cleanup_custom_fields),
|
|
102
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
from ipfabric_netbox.utilities.transform_map import build_transform_maps
|
|
6
|
+
from ipfabric_netbox.utilities.transform_map import get_transform_map
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from django.apps import apps
|
|
11
|
+
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def prepare_transform_maps(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
15
|
+
"""Create transform maps if they do not exist yet.
|
|
16
|
+
They used to be created during plugin.ready() so they might be present on older DBs.
|
|
17
|
+
"""
|
|
18
|
+
if apps.get_model("ipfabric_netbox", "IPFabricTransformMap").objects.count() == 0:
|
|
19
|
+
build_transform_maps(data=get_transform_map())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cleanup_transform_maps(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
23
|
+
"""Delete all transform maps."""
|
|
24
|
+
# IPFabricTransformField and IPFabricRelationshipField are deleted by CASCADE
|
|
25
|
+
apps.get_model("ipfabric_netbox", "IPFabricTransformMap").objects.all().delete()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Migration(migrations.Migration):
|
|
29
|
+
# Depend on all models that are used in transform maps
|
|
30
|
+
dependencies = [
|
|
31
|
+
("core", "0012_job_object_type_optional"),
|
|
32
|
+
("dcim", "0191_module_bay_rebuild"),
|
|
33
|
+
("extras", "0121_customfield_related_object_filter"),
|
|
34
|
+
("ipam", "0070_vlangroup_vlan_id_ranges"),
|
|
35
|
+
(
|
|
36
|
+
"ipfabric_netbox",
|
|
37
|
+
"0007_prepare_custom_fields",
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
operations = [
|
|
41
|
+
migrations.RunPython(prepare_transform_maps, cleanup_transform_maps),
|
|
42
|
+
]
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import re
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import django.db.models.deletion
|
|
6
|
+
from django.db import migrations
|
|
7
|
+
from django.db import models
|
|
8
|
+
|
|
9
|
+
from ipfabric_netbox.utilities.transform_map import build_transform_maps
|
|
10
|
+
from ipfabric_netbox.utilities.transform_map import get_transform_map
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from django.apps import apps
|
|
15
|
+
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|
16
|
+
from ipfabric_netbox.models import IPFabricTransformField, IPFabricTransformMap
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_current_transform_map(
|
|
20
|
+
apps: "apps",
|
|
21
|
+
target_model: str,
|
|
22
|
+
app_label: str = "dcim",
|
|
23
|
+
source_model: str = "interface",
|
|
24
|
+
) -> "IPFabricTransformMap":
|
|
25
|
+
return apps.get_model("ipfabric_netbox", "IPFabricTransformMap").objects.get(
|
|
26
|
+
source_model=source_model,
|
|
27
|
+
target_model=apps.get_model("contenttypes", "ContentType").objects.get(
|
|
28
|
+
app_label=app_label,
|
|
29
|
+
model=target_model,
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_current_map_field(apps: "apps", target_model: str) -> "IPFabricTransformField":
|
|
35
|
+
"""Finds the current TransformMapField for MAC address so we keep the template.
|
|
36
|
+
We need to do this because some customer might have altered it."""
|
|
37
|
+
return apps.get_model("ipfabric_netbox", "IPFabricTransformField").objects.get(
|
|
38
|
+
source_field="mac",
|
|
39
|
+
target_field="mac_address",
|
|
40
|
+
transform_map=get_current_transform_map(apps, target_model),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def create_transform_map(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
45
|
+
"""Create transform map structure for MAC Address (new in NetBox v4.2).
|
|
46
|
+
Keeps current template for MAC Address if it exists.
|
|
47
|
+
|
|
48
|
+
Also fixes interface.duplex not being populated."""
|
|
49
|
+
|
|
50
|
+
with contextlib.suppress(
|
|
51
|
+
apps.get_model("ipfabric_netbox", "IPFabricTransformMap").DoesNotExist
|
|
52
|
+
):
|
|
53
|
+
# The map will already be present if we're using newer transform map.
|
|
54
|
+
# This means we don't need to run the migration since it got created with 0008_prepare_transform_maps migration
|
|
55
|
+
get_current_transform_map(apps, "macaddress")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# region - MAC Address
|
|
59
|
+
current_mac_field = get_current_map_field(apps, "interface")
|
|
60
|
+
transform_map_data = get_transform_map()
|
|
61
|
+
|
|
62
|
+
mac_address_transform_map = None
|
|
63
|
+
# Get MAC Address transform map defined in transform_map.json
|
|
64
|
+
for i, transform_map in enumerate(transform_map_data[:]):
|
|
65
|
+
if transform_map["data"]["name"] == "MAC Address Transform Map":
|
|
66
|
+
mac_address_transform_map = [transform_map]
|
|
67
|
+
# Replace the template only the current field is there
|
|
68
|
+
if current_mac_field:
|
|
69
|
+
for field_map in mac_address_transform_map[0]["field_maps"]:
|
|
70
|
+
if field_map["source_field"] == "mac":
|
|
71
|
+
field_map["template"] = current_mac_field.template
|
|
72
|
+
|
|
73
|
+
build_transform_maps(mac_address_transform_map)
|
|
74
|
+
current_mac_field.delete()
|
|
75
|
+
# endregion
|
|
76
|
+
|
|
77
|
+
# region - duplex
|
|
78
|
+
# Create duplex transform field for Interface. Ignore if it already exists
|
|
79
|
+
IPFabricTransformField = apps.get_model("ipfabric_netbox", "IPFabricTransformField")
|
|
80
|
+
field_data = {
|
|
81
|
+
"source_field": "duplex",
|
|
82
|
+
"target_field": "duplex",
|
|
83
|
+
"transform_map": get_current_transform_map(apps, "interface"),
|
|
84
|
+
}
|
|
85
|
+
try:
|
|
86
|
+
IPFabricTransformField.objects.get(**field_data)
|
|
87
|
+
except IPFabricTransformField.DoesNotExist:
|
|
88
|
+
IPFabricTransformField(**field_data).save()
|
|
89
|
+
# endregion
|
|
90
|
+
|
|
91
|
+
# region - Prefix site
|
|
92
|
+
# Prefix.site has changed to Prefix.scope relation (to allow site groups etc.)
|
|
93
|
+
prefix_transform_map = get_current_transform_map(
|
|
94
|
+
apps, source_model="prefix", app_label="ipam", target_model="prefix"
|
|
95
|
+
)
|
|
96
|
+
current_site_relationship = prefix_transform_map.relationship_maps.get(
|
|
97
|
+
target_field="site"
|
|
98
|
+
)
|
|
99
|
+
IPFabricTransformField(
|
|
100
|
+
source_field="siteName",
|
|
101
|
+
target_field="scope_id",
|
|
102
|
+
coalesce=True,
|
|
103
|
+
template="{% if object.siteName is defined %}"
|
|
104
|
+
+ current_site_relationship.template
|
|
105
|
+
+ "{% else %}None{% endif %}",
|
|
106
|
+
transform_map=prefix_transform_map,
|
|
107
|
+
).save()
|
|
108
|
+
current_site_relationship.template = '{% if object.siteName is defined %}{{ contenttypes.ContentType.objects.get(app_label="dcim", model="site").pk }}{% else %}None{% endif %}'
|
|
109
|
+
current_site_relationship.source_model = apps.get_model(
|
|
110
|
+
"contenttypes", "ContentType"
|
|
111
|
+
).objects.get(app_label="contenttypes", model="contenttype")
|
|
112
|
+
current_site_relationship.target_field = "scope_type"
|
|
113
|
+
current_site_relationship.save()
|
|
114
|
+
# endregion
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def delete_transform_map(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
118
|
+
current_mac_field = get_current_map_field(apps, "macaddress")
|
|
119
|
+
|
|
120
|
+
# region - MAC Address
|
|
121
|
+
apps.get_model("ipfabric_netbox", "IPFabricTransformField")(
|
|
122
|
+
coalesce=False,
|
|
123
|
+
source_field="mac",
|
|
124
|
+
target_field="mac_address",
|
|
125
|
+
template=current_mac_field.template,
|
|
126
|
+
transform_map=get_current_transform_map(apps, "interface"),
|
|
127
|
+
).save()
|
|
128
|
+
|
|
129
|
+
# Use CASCADE to delete the transform fields and relationships
|
|
130
|
+
current_mac_field.transform_map.delete()
|
|
131
|
+
# endregion
|
|
132
|
+
|
|
133
|
+
# region - duplex
|
|
134
|
+
# Delete new duplex field
|
|
135
|
+
apps.get_model("ipfabric_netbox", "IPFabricTransformField").objects.get(
|
|
136
|
+
source_field="duplex",
|
|
137
|
+
target_field="duplex",
|
|
138
|
+
transform_map=get_current_transform_map(apps, "interface"),
|
|
139
|
+
).delete()
|
|
140
|
+
# endregion
|
|
141
|
+
|
|
142
|
+
# region - Prefix site
|
|
143
|
+
prefix_transform_map = get_current_transform_map(
|
|
144
|
+
apps, source_model="prefix", app_label="ipam", target_model="prefix"
|
|
145
|
+
)
|
|
146
|
+
current_site_relationship = prefix_transform_map.relationship_maps.get(
|
|
147
|
+
target_field="scope_type"
|
|
148
|
+
)
|
|
149
|
+
current_site_field = prefix_transform_map.field_maps.get(source_field="siteName")
|
|
150
|
+
match = re.search(r"is defined %}(.*){% else %}None", current_site_field.template)
|
|
151
|
+
current_site_field.delete()
|
|
152
|
+
if match:
|
|
153
|
+
current_site_relationship.template = match.group(1)
|
|
154
|
+
else:
|
|
155
|
+
current_site_relationship.template = "{% set SLUG = object.siteName | slugify %}{{ dcim.Site.objects.filter(slug=SLUG).first().pk }}"
|
|
156
|
+
current_site_relationship.source_model = apps.get_model(
|
|
157
|
+
"contenttypes", "ContentType"
|
|
158
|
+
).objects.get(app_label="dcim", model="site")
|
|
159
|
+
current_site_relationship.target_field = "site"
|
|
160
|
+
current_site_relationship.save()
|
|
161
|
+
# endregion
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def set_macaddress_sync_param(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
165
|
+
IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
|
|
166
|
+
for sync in IPFabricSync.objects.all():
|
|
167
|
+
if sync.parameters.get("interface"):
|
|
168
|
+
sync.parameters["macaddress"] = True
|
|
169
|
+
else:
|
|
170
|
+
sync.parameters["macaddress"] = False
|
|
171
|
+
sync.save()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def remove_macaddress_sync_param(
|
|
175
|
+
apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"
|
|
176
|
+
):
|
|
177
|
+
IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
|
|
178
|
+
for sync in IPFabricSync.objects.all():
|
|
179
|
+
sync.parameters.pop("macaddress", None)
|
|
180
|
+
sync.save()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class Migration(migrations.Migration):
|
|
184
|
+
dependencies = [
|
|
185
|
+
("contenttypes", "0002_remove_content_type_name"),
|
|
186
|
+
("ipfabric_netbox", "0008_prepare_transform_maps"),
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
operations = [
|
|
190
|
+
migrations.AlterField(
|
|
191
|
+
model_name="ipfabrictransformmap",
|
|
192
|
+
name="target_model",
|
|
193
|
+
field=models.ForeignKey(
|
|
194
|
+
limit_choices_to=models.Q(
|
|
195
|
+
models.Q(
|
|
196
|
+
models.Q(("app_label", "dcim"), ("model", "site")),
|
|
197
|
+
models.Q(("app_label", "dcim"), ("model", "manufacturer")),
|
|
198
|
+
models.Q(("app_label", "dcim"), ("model", "platform")),
|
|
199
|
+
models.Q(("app_label", "dcim"), ("model", "devicerole")),
|
|
200
|
+
models.Q(("app_label", "dcim"), ("model", "devicetype")),
|
|
201
|
+
models.Q(("app_label", "dcim"), ("model", "device")),
|
|
202
|
+
models.Q(("app_label", "dcim"), ("model", "virtualchassis")),
|
|
203
|
+
models.Q(("app_label", "dcim"), ("model", "interface")),
|
|
204
|
+
models.Q(("app_label", "dcim"), ("model", "macaddress")),
|
|
205
|
+
models.Q(("app_label", "ipam"), ("model", "vlan")),
|
|
206
|
+
models.Q(("app_label", "ipam"), ("model", "vrf")),
|
|
207
|
+
models.Q(("app_label", "ipam"), ("model", "prefix")),
|
|
208
|
+
models.Q(("app_label", "ipam"), ("model", "ipaddress")),
|
|
209
|
+
models.Q(
|
|
210
|
+
("app_label", "contenttypes"), ("model", "contenttype")
|
|
211
|
+
),
|
|
212
|
+
models.Q(("app_label", "tenancy"), ("model", "tenant")),
|
|
213
|
+
models.Q(("app_label", "dcim"), ("model", "inventoryitem")),
|
|
214
|
+
_connector="OR",
|
|
215
|
+
)
|
|
216
|
+
),
|
|
217
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
218
|
+
related_name="+",
|
|
219
|
+
to="contenttypes.contenttype",
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
migrations.RunPython(set_macaddress_sync_param, remove_macaddress_sync_param),
|
|
223
|
+
migrations.RunPython(create_transform_map, delete_transform_map),
|
|
224
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
from ipfabric_netbox.utilities.transform_map import get_transform_map
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from django.apps import apps
|
|
9
|
+
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
items_to_change = {
|
|
13
|
+
("ip_address", "ipam", "ipaddress"): {"fields": [], "relationships": ["vrf"]},
|
|
14
|
+
("device", "dcim", "platform"): {"fields": [], "relationships": ["manufacturer"]},
|
|
15
|
+
("device", "dcim", "device"): {
|
|
16
|
+
"fields": [],
|
|
17
|
+
"relationships": ["platform", "site", "device_type", "role"],
|
|
18
|
+
},
|
|
19
|
+
("device", "dcim", "devicetype"): {"fields": [], "relationships": ["manufacturer"]},
|
|
20
|
+
("interface", "dcim", "interface"): {"fields": [], "relationships": ["device"]},
|
|
21
|
+
("part_number", "dcim", "inventoryitem"): {
|
|
22
|
+
"fields": ["part_id"],
|
|
23
|
+
"relationships": ["device", "manufacturer"],
|
|
24
|
+
},
|
|
25
|
+
("vlan", "ipam", "vlan"): {"fields": ["name"], "relationships": ["site"]},
|
|
26
|
+
("prefix", "ipam", "prefix"): {"fields": ["scope_id"], "relationships": ["vrf"]},
|
|
27
|
+
("virtualchassis", "dcim", "virtualchassis"): {
|
|
28
|
+
"fields": [],
|
|
29
|
+
"relationships": ["master"],
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def add_templates(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
35
|
+
transform_map_data = get_transform_map()
|
|
36
|
+
|
|
37
|
+
for item in transform_map_data:
|
|
38
|
+
transform_map_id = (
|
|
39
|
+
item["data"]["source_model"],
|
|
40
|
+
item["data"]["target_model"]["app_label"],
|
|
41
|
+
item["data"]["target_model"]["model"],
|
|
42
|
+
)
|
|
43
|
+
if transform_map_id not in items_to_change:
|
|
44
|
+
continue
|
|
45
|
+
source_model, app_label, target_model = transform_map_id
|
|
46
|
+
transform_map = apps.get_model(
|
|
47
|
+
"ipfabric_netbox", "IPFabricTransformMap"
|
|
48
|
+
).objects.get(
|
|
49
|
+
source_model=source_model,
|
|
50
|
+
target_model=apps.get_model("contenttypes", "ContentType").objects.get(
|
|
51
|
+
app_label=app_label,
|
|
52
|
+
model=target_model,
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
change_data = items_to_change[transform_map_id]
|
|
57
|
+
|
|
58
|
+
for field_map in item["field_maps"]:
|
|
59
|
+
if field_map["target_field"] not in change_data["fields"]:
|
|
60
|
+
continue
|
|
61
|
+
field = apps.get_model(
|
|
62
|
+
"ipfabric_netbox", "IPFabricTransformField"
|
|
63
|
+
).objects.get(
|
|
64
|
+
target_field=field_map["target_field"], transform_map=transform_map
|
|
65
|
+
)
|
|
66
|
+
field.template = field_map["template"]
|
|
67
|
+
field.save()
|
|
68
|
+
|
|
69
|
+
for relationship_map in item["relationship_maps"]:
|
|
70
|
+
if relationship_map["target_field"] not in change_data["relationships"]:
|
|
71
|
+
continue
|
|
72
|
+
relationship = apps.get_model(
|
|
73
|
+
"ipfabric_netbox", "IPFabricRelationshipField"
|
|
74
|
+
).objects.get(
|
|
75
|
+
target_field=relationship_map["target_field"],
|
|
76
|
+
transform_map=transform_map,
|
|
77
|
+
)
|
|
78
|
+
relationship.template = relationship_map["template"]
|
|
79
|
+
relationship.save()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def return_templates(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
83
|
+
"""It would be way too complex to revert this migration and there is no need to do so.
|
|
84
|
+
The original code works the same with or without the templates."""
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Migration(migrations.Migration):
|
|
89
|
+
dependencies = [
|
|
90
|
+
("ipfabric_netbox", "0009_transformmap_changes_for_netbox_v4_2"),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
operations = [
|
|
94
|
+
migrations.RunPython(add_templates, return_templates),
|
|
95
|
+
]
|