ipfabric_netbox 3.1.2__py3-none-any.whl → 3.2.0__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/forms.py +2 -8
- 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 +227 -0
- ipfabric_netbox/models.py +10 -1
- ipfabric_netbox/tables.py +4 -0
- ipfabric_netbox/tests/test_models.py +17 -950
- ipfabric_netbox/utilities/ipfutils.py +12 -0
- ipfabric_netbox/utilities/transform_map.py +37 -15
- ipfabric_netbox/views.py +3 -9
- {ipfabric_netbox-3.1.2.dist-info → ipfabric_netbox-3.2.0.dist-info}/METADATA +17 -3
- {ipfabric_netbox-3.1.2.dist-info → ipfabric_netbox-3.2.0.dist-info}/RECORD +15 -12
- ipfabric_netbox/signals.py +0 -68
- {ipfabric_netbox-3.1.2.dist-info → ipfabric_netbox-3.2.0.dist-info}/WHEEL +0 -0
ipfabric_netbox/forms.py
CHANGED
|
@@ -2,7 +2,6 @@ import copy
|
|
|
2
2
|
|
|
3
3
|
from core.choices import DataSourceStatusChoices
|
|
4
4
|
from django import forms
|
|
5
|
-
from django.conf import settings
|
|
6
5
|
from django.contrib.contenttypes.models import ContentType
|
|
7
6
|
from django.core.exceptions import ValidationError
|
|
8
7
|
from django.utils import timezone
|
|
@@ -10,7 +9,7 @@ from django.utils.translation import gettext_lazy as _
|
|
|
10
9
|
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 = {
|
|
@@ -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,227 @@
|
|
|
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
|
+
# TODO: Add MAC Address to existing IPFabricSync instances if Interface is allowed
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def create_transform_map(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
48
|
+
"""Create transform map structure for MAC Address (new in NetBox v4.2).
|
|
49
|
+
Keeps current template for MAC Address if it exists.
|
|
50
|
+
|
|
51
|
+
Also fixes interface.duplex not being populated."""
|
|
52
|
+
|
|
53
|
+
with contextlib.suppress(
|
|
54
|
+
apps.get_model("ipfabric_netbox", "IPFabricTransformMap").DoesNotExist
|
|
55
|
+
):
|
|
56
|
+
# The map will already be present if we're using newer transform map.
|
|
57
|
+
# This means we don't need to run the migration since it got created with 0008_prepare_transform_maps migration
|
|
58
|
+
get_current_transform_map(apps, "macaddress")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
# region - MAC Address
|
|
62
|
+
current_mac_field = get_current_map_field(apps, "interface")
|
|
63
|
+
transform_map_data = get_transform_map()
|
|
64
|
+
|
|
65
|
+
mac_address_transform_map = None
|
|
66
|
+
# Get MAC Address transform map defined in transform_map.json
|
|
67
|
+
for i, transform_map in enumerate(transform_map_data[:]):
|
|
68
|
+
if transform_map["data"]["name"] == "MAC Address Transform Map":
|
|
69
|
+
mac_address_transform_map = [transform_map]
|
|
70
|
+
# Replace the template only the current field is there
|
|
71
|
+
if current_mac_field:
|
|
72
|
+
for field_map in mac_address_transform_map[0]["field_maps"]:
|
|
73
|
+
if field_map["source_field"] == "mac":
|
|
74
|
+
field_map["template"] = current_mac_field.template
|
|
75
|
+
|
|
76
|
+
build_transform_maps(mac_address_transform_map)
|
|
77
|
+
current_mac_field.delete()
|
|
78
|
+
# endregion
|
|
79
|
+
|
|
80
|
+
# region - duplex
|
|
81
|
+
# Create duplex transform field for Interface. Ignore if it already exists
|
|
82
|
+
IPFabricTransformField = apps.get_model("ipfabric_netbox", "IPFabricTransformField")
|
|
83
|
+
field_data = {
|
|
84
|
+
"source_field": "duplex",
|
|
85
|
+
"target_field": "duplex",
|
|
86
|
+
"transform_map": get_current_transform_map(apps, "interface"),
|
|
87
|
+
}
|
|
88
|
+
try:
|
|
89
|
+
IPFabricTransformField.objects.get(**field_data)
|
|
90
|
+
except IPFabricTransformField.DoesNotExist:
|
|
91
|
+
IPFabricTransformField(**field_data).save()
|
|
92
|
+
# endregion
|
|
93
|
+
|
|
94
|
+
# region - Prefix site
|
|
95
|
+
# Prefix.site has changed to Prefix.scope relation (to allow site groups etc.)
|
|
96
|
+
prefix_transform_map = get_current_transform_map(
|
|
97
|
+
apps, source_model="prefix", app_label="ipam", target_model="prefix"
|
|
98
|
+
)
|
|
99
|
+
current_site_relationship = prefix_transform_map.relationship_maps.get(
|
|
100
|
+
target_field="site"
|
|
101
|
+
)
|
|
102
|
+
IPFabricTransformField(
|
|
103
|
+
source_field="siteName",
|
|
104
|
+
target_field="scope_id",
|
|
105
|
+
coalesce=True,
|
|
106
|
+
template="{% if object.siteName is defined %}"
|
|
107
|
+
+ current_site_relationship.template
|
|
108
|
+
+ "{% else %}None{% endif %}",
|
|
109
|
+
transform_map=prefix_transform_map,
|
|
110
|
+
).save()
|
|
111
|
+
current_site_relationship.template = '{% if object.siteName is defined %}{{ contenttypes.ContentType.objects.get(app_label="dcim", model="site").pk }}{% else %}None{% endif %}'
|
|
112
|
+
current_site_relationship.source_model = apps.get_model(
|
|
113
|
+
"contenttypes", "ContentType"
|
|
114
|
+
).objects.get(app_label="contenttypes", model="contenttype")
|
|
115
|
+
current_site_relationship.target_field = "scope_type"
|
|
116
|
+
current_site_relationship.save()
|
|
117
|
+
# endregion
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def delete_transform_map(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
121
|
+
current_mac_field = get_current_map_field(apps, "macaddress")
|
|
122
|
+
|
|
123
|
+
# region - MAC Address
|
|
124
|
+
apps.get_model("ipfabric_netbox", "IPFabricTransformField")(
|
|
125
|
+
coalesce=False,
|
|
126
|
+
source_field="mac",
|
|
127
|
+
target_field="mac_address",
|
|
128
|
+
template=current_mac_field.template,
|
|
129
|
+
transform_map=get_current_transform_map(apps, "interface"),
|
|
130
|
+
).save()
|
|
131
|
+
|
|
132
|
+
# Use CASCADE to delete the transform fields and relationships
|
|
133
|
+
current_mac_field.transform_map.delete()
|
|
134
|
+
# endregion
|
|
135
|
+
|
|
136
|
+
# region - duplex
|
|
137
|
+
# Delete new duplex field
|
|
138
|
+
apps.get_model("ipfabric_netbox", "IPFabricTransformField").objects.get(
|
|
139
|
+
source_field="duplex",
|
|
140
|
+
target_field="duplex",
|
|
141
|
+
transform_map=get_current_transform_map(apps, "interface"),
|
|
142
|
+
).delete()
|
|
143
|
+
# endregion
|
|
144
|
+
|
|
145
|
+
# region - Prefix site
|
|
146
|
+
prefix_transform_map = get_current_transform_map(
|
|
147
|
+
apps, source_model="prefix", app_label="ipam", target_model="prefix"
|
|
148
|
+
)
|
|
149
|
+
current_site_relationship = prefix_transform_map.relationship_maps.get(
|
|
150
|
+
target_field="scope_type"
|
|
151
|
+
)
|
|
152
|
+
current_site_field = prefix_transform_map.field_maps.get(source_field="siteName")
|
|
153
|
+
match = re.search(r"is defined %}(.*){% else %}None", current_site_field.template)
|
|
154
|
+
current_site_field.delete()
|
|
155
|
+
if match:
|
|
156
|
+
current_site_relationship.template = match.group(1)
|
|
157
|
+
else:
|
|
158
|
+
current_site_relationship.template = "{% set SLUG = object.siteName | slugify %}{{ dcim.Site.objects.filter(slug=SLUG).first().pk }}"
|
|
159
|
+
current_site_relationship.source_model = apps.get_model(
|
|
160
|
+
"contenttypes", "ContentType"
|
|
161
|
+
).objects.get(app_label="dcim", model="site")
|
|
162
|
+
current_site_relationship.target_field = "site"
|
|
163
|
+
current_site_relationship.save()
|
|
164
|
+
# endregion
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def set_macaddress_sync_param(apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
168
|
+
IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
|
|
169
|
+
for sync in IPFabricSync.objects.all():
|
|
170
|
+
if sync.parameters.get("interface"):
|
|
171
|
+
sync.parameters["macaddress"] = True
|
|
172
|
+
else:
|
|
173
|
+
sync.parameters["macaddress"] = False
|
|
174
|
+
sync.save()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def remove_macaddress_sync_param(
|
|
178
|
+
apps: "apps", schema_editor: "BaseDatabaseSchemaEditor"
|
|
179
|
+
):
|
|
180
|
+
IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
|
|
181
|
+
for sync in IPFabricSync.objects.all():
|
|
182
|
+
sync.parameters.pop("macaddress", None)
|
|
183
|
+
sync.save()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class Migration(migrations.Migration):
|
|
187
|
+
dependencies = [
|
|
188
|
+
("contenttypes", "0002_remove_content_type_name"),
|
|
189
|
+
("ipfabric_netbox", "0008_prepare_transform_maps"),
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
operations = [
|
|
193
|
+
migrations.AlterField(
|
|
194
|
+
model_name="ipfabrictransformmap",
|
|
195
|
+
name="target_model",
|
|
196
|
+
field=models.ForeignKey(
|
|
197
|
+
limit_choices_to=models.Q(
|
|
198
|
+
models.Q(
|
|
199
|
+
models.Q(("app_label", "dcim"), ("model", "site")),
|
|
200
|
+
models.Q(("app_label", "dcim"), ("model", "manufacturer")),
|
|
201
|
+
models.Q(("app_label", "dcim"), ("model", "platform")),
|
|
202
|
+
models.Q(("app_label", "dcim"), ("model", "devicerole")),
|
|
203
|
+
models.Q(("app_label", "dcim"), ("model", "devicetype")),
|
|
204
|
+
models.Q(("app_label", "dcim"), ("model", "device")),
|
|
205
|
+
models.Q(("app_label", "dcim"), ("model", "virtualchassis")),
|
|
206
|
+
models.Q(("app_label", "dcim"), ("model", "interface")),
|
|
207
|
+
models.Q(("app_label", "dcim"), ("model", "macaddress")),
|
|
208
|
+
models.Q(("app_label", "ipam"), ("model", "vlan")),
|
|
209
|
+
models.Q(("app_label", "ipam"), ("model", "vrf")),
|
|
210
|
+
models.Q(("app_label", "ipam"), ("model", "prefix")),
|
|
211
|
+
models.Q(("app_label", "ipam"), ("model", "ipaddress")),
|
|
212
|
+
models.Q(
|
|
213
|
+
("app_label", "contenttypes"), ("model", "contenttype")
|
|
214
|
+
),
|
|
215
|
+
models.Q(("app_label", "tenancy"), ("model", "tenant")),
|
|
216
|
+
models.Q(("app_label", "dcim"), ("model", "inventoryitem")),
|
|
217
|
+
_connector="OR",
|
|
218
|
+
)
|
|
219
|
+
),
|
|
220
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
221
|
+
related_name="+",
|
|
222
|
+
to="contenttypes.contenttype",
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
migrations.RunPython(set_macaddress_sync_param, remove_macaddress_sync_param),
|
|
226
|
+
migrations.RunPython(create_transform_map, delete_transform_map),
|
|
227
|
+
]
|
ipfabric_netbox/models.py
CHANGED
|
@@ -71,6 +71,7 @@ IPFabricSupportedSyncModels = Q(
|
|
|
71
71
|
| Q(app_label="dcim", model="device")
|
|
72
72
|
| Q(app_label="dcim", model="virtualchassis")
|
|
73
73
|
| Q(app_label="dcim", model="interface")
|
|
74
|
+
| Q(app_label="dcim", model="macaddress")
|
|
74
75
|
| Q(app_label="ipam", model="vlan")
|
|
75
76
|
| Q(app_label="ipam", model="vrf")
|
|
76
77
|
| Q(app_label="ipam", model="prefix")
|
|
@@ -199,7 +200,7 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
199
200
|
if instance:
|
|
200
201
|
apply_tags(instance, tags)
|
|
201
202
|
except Exception as e:
|
|
202
|
-
error_message = f"""Failed to create instance
|
|
203
|
+
error_message = f"""Failed to create instance of `{str(self.target_model)}`:<br/>
|
|
203
204
|
message: `{e}`<br/>
|
|
204
205
|
raw data: `{data}`<br/>
|
|
205
206
|
context: `{context}`<br/>
|
|
@@ -597,6 +598,14 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
|
|
|
597
598
|
# TODO: Add docs url
|
|
598
599
|
return ""
|
|
599
600
|
|
|
601
|
+
@property
|
|
602
|
+
def logger(self):
|
|
603
|
+
return getattr(self, "_logger", SyncLogging(job=self.pk))
|
|
604
|
+
|
|
605
|
+
@logger.setter
|
|
606
|
+
def logger(self, value):
|
|
607
|
+
self._logger = value
|
|
608
|
+
|
|
600
609
|
def get_absolute_url(self):
|
|
601
610
|
return reverse("plugins:ipfabric_netbox:ipfabricsync", args=[self.pk])
|
|
602
611
|
|
ipfabric_netbox/tables.py
CHANGED
|
@@ -143,6 +143,8 @@ class SyncTable(NetBoxTable):
|
|
|
143
143
|
|
|
144
144
|
|
|
145
145
|
class StagedChangesTable(NetBoxTable):
|
|
146
|
+
# There is no view for single StagedChange, remove the link in ID
|
|
147
|
+
id = tables.Column(verbose_name=_("ID"))
|
|
146
148
|
pk = None
|
|
147
149
|
object_type = tables.Column(
|
|
148
150
|
accessor="object_type.model", verbose_name="Object Type"
|
|
@@ -172,6 +174,8 @@ class StagedChangesTable(NetBoxTable):
|
|
|
172
174
|
name = f"Tagging object ({record.data['object_id']})"
|
|
173
175
|
elif value == "prefix":
|
|
174
176
|
name = f"{record.data['prefix']} ({record.data['vrf']})"
|
|
177
|
+
elif value == "MAC address":
|
|
178
|
+
name = record.data["mac_address"]
|
|
175
179
|
else:
|
|
176
180
|
name = record.data
|
|
177
181
|
else:
|