ipfabric_netbox 4.3.2b1__py3-none-any.whl → 4.3.2b3__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.
@@ -3,6 +3,8 @@ import json
3
3
 
4
4
  from django.apps import apps as django_apps
5
5
 
6
+ # region Transform Map Creation
7
+
6
8
  # These functions are used in the migration file to prepare the transform maps
7
9
  # Because of this we have to use historical models
8
10
  # see https://docs.djangoproject.com/en/5.1/topics/migrations/#historical-models
@@ -58,3 +60,174 @@ def get_transform_map() -> dict:
58
60
  with open(data_file, "rb") as data_file:
59
61
  return json.load(data_file)
60
62
  raise FileNotFoundError("'transform_map.json' not found in installed package")
63
+
64
+
65
+ # endregion
66
+ # region Transform Map Updating
67
+
68
+
69
+ class Record:
70
+ """Base class for field and relationship records."""
71
+
72
+ def __init__(
73
+ self,
74
+ coalesce: bool | None = None,
75
+ old_template: str = None,
76
+ new_template: str = None,
77
+ ):
78
+ self.coalesce = coalesce
79
+ # Keep the original template here rather than loading it from transform_map.json
80
+ # so our revert won’t break if that template ever changes.
81
+ self.old_template = old_template
82
+ self.new_template = new_template
83
+
84
+
85
+ class FieldRecord(Record):
86
+ def __init__(
87
+ self,
88
+ source_field: str,
89
+ target_field: str,
90
+ new_source_field: str | None = None,
91
+ new_target_field: str | None = None,
92
+ **kwargs,
93
+ ):
94
+ super().__init__(**kwargs)
95
+ self.source_field = source_field
96
+ self.target_field = target_field
97
+ self.new_source_field = new_source_field
98
+ self.new_target_field = new_target_field
99
+
100
+
101
+ class RelationshipRecord(Record):
102
+ def __init__(self, source_model: str, target_field: str, **kwargs):
103
+ super().__init__(**kwargs)
104
+ self.source_model = source_model
105
+ self.target_field = target_field
106
+
107
+
108
+ class TransformMapRecord:
109
+ def __init__(
110
+ self,
111
+ source_model: str,
112
+ target_model: str,
113
+ fields: tuple[FieldRecord, ...] = tuple(),
114
+ relationships: tuple[RelationshipRecord, ...] = tuple(),
115
+ ):
116
+ self.source_model = source_model
117
+ self.target_model = target_model
118
+ self.fields = fields
119
+ self.relationships = relationships
120
+
121
+
122
+ def do_change(
123
+ apps, schema_editor, changes: tuple[TransformMapRecord, ...], forward: bool = True
124
+ ):
125
+ """Apply the changes, `forward` determines direction."""
126
+
127
+ ContentType = apps.get_model("contenttypes", "ContentType")
128
+ IPFabricTransformMap = apps.get_model("ipfabric_netbox", "IPFabricTransformMap")
129
+ IPFabricTransformField = apps.get_model("ipfabric_netbox", "IPFabricTransformField")
130
+ IPFabricRelationshipField = apps.get_model(
131
+ "ipfabric_netbox", "IPFabricRelationshipField"
132
+ )
133
+
134
+ try:
135
+ for change in changes:
136
+ app, model = change.target_model.split(".")
137
+ try:
138
+ transform_map = IPFabricTransformMap.objects.get(
139
+ source_model=change.source_model,
140
+ target_model=ContentType.objects.get(app_label=app, model=model),
141
+ )
142
+ except IPFabricTransformMap.DoesNotExist:
143
+ continue
144
+
145
+ for field in change.fields:
146
+ # Find the correct transform field.
147
+ # Only 1 should be found if it exists, but keep it as queryset so we can filter and update.
148
+ transform_field_qs = IPFabricTransformField.objects.filter(
149
+ transform_map=transform_map,
150
+ source_field=field.source_field
151
+ if forward
152
+ else field.new_source_field or field.source_field,
153
+ target_field=field.target_field
154
+ if forward
155
+ else field.new_target_field or field.target_field,
156
+ )
157
+ if not transform_field_qs.exists():
158
+ continue
159
+
160
+ if field.old_template is not None and field.new_template is not None:
161
+ # First update the template if needed
162
+ transform_field_qs.filter(
163
+ template=field.old_template if forward else field.new_template
164
+ ).update(
165
+ template=field.new_template if forward else field.old_template
166
+ )
167
+
168
+ if field.coalesce is not None:
169
+ # Next update coalesce if needed
170
+ transform_field_qs.filter(
171
+ coalesce=not field.coalesce if forward else field.coalesce
172
+ ).update(coalesce=field.coalesce if forward else not field.coalesce)
173
+
174
+ if (
175
+ field.new_target_field is not None
176
+ or field.new_source_field is not None
177
+ ):
178
+ # And at the end update source_field/target_field if needed
179
+ transform_field_qs.update(
180
+ source_field=field.new_source_field or field.source_field
181
+ if forward
182
+ else field.source_field,
183
+ target_field=field.new_target_field or field.target_field
184
+ if forward
185
+ else field.target_field,
186
+ )
187
+
188
+ for relationship in change.relationships:
189
+ s_app, s_model = relationship.source_model.split(".")
190
+ source_model = ContentType.objects.get(app_label=s_app, model=s_model)
191
+
192
+ # Find the correct relationship field.
193
+ # Only 1 should be found if it exists, but keep it as queryset so we can filter and update.
194
+ relationship_qs = IPFabricRelationshipField.objects.filter(
195
+ transform_map=transform_map,
196
+ source_model=source_model,
197
+ target_field=relationship.target_field,
198
+ )
199
+ if not relationship_qs.exists():
200
+ continue
201
+
202
+ if (
203
+ relationship.old_template is not None
204
+ and relationship.new_template is not None
205
+ ):
206
+ # First update the template if needed
207
+ relationship_qs.filter(
208
+ template=relationship.old_template
209
+ if forward
210
+ else relationship.new_template,
211
+ ).update(
212
+ template=relationship.new_template
213
+ if forward
214
+ else relationship.old_template
215
+ ),
216
+
217
+ if relationship.coalesce is not None:
218
+ # Next update coalesce if needed
219
+ relationship_qs.filter(
220
+ coalesce=not relationship.coalesce
221
+ if forward
222
+ else relationship.coalesce,
223
+ ).update(
224
+ coalesce=relationship.coalesce
225
+ if forward
226
+ else not relationship.coalesce
227
+ ),
228
+
229
+ except Exception as e:
230
+ print(f"Error applying Transform map updates: {e}")
231
+
232
+
233
+ # endregion
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipfabric_netbox
3
- Version: 4.3.2b1
3
+ Version: 4.3.2b3
4
4
  Summary: NetBox plugin to sync IP Fabric data into NetBox
5
5
  License: MIT
6
6
  Keywords: netbox,ipfabric,plugin,sync
@@ -19,6 +19,7 @@ Provides-Extra: ipfabric-6-10
19
19
  Provides-Extra: ipfabric-7-0
20
20
  Provides-Extra: ipfabric-7-2
21
21
  Provides-Extra: ipfabric-7-3
22
+ Requires-Dist: httpx (>0.26,<0.29)
22
23
  Requires-Dist: ipfabric (>=6.10.0,<6.11.0) ; extra == "ipfabric_6_10" and extra != "ipfabric_7_0" and extra != "ipfabric_7_2" and extra != "ipfabric_7_3"
23
24
  Requires-Dist: ipfabric (>=6.6.4) ; extra != "ipfabric_6_10" and extra != "ipfabric_7_0" and extra != "ipfabric_7_2" and extra != "ipfabric_7_3"
24
25
  Requires-Dist: ipfabric (>=7.0.0,<7.1.0) ; extra != "ipfabric_6_10" and extra == "ipfabric_7_0" and extra != "ipfabric_7_2" and extra != "ipfabric_7_3"
@@ -1,11 +1,11 @@
1
- ipfabric_netbox/__init__.py,sha256=CDSZRxo7iaMvP0CLde8RZCVINtzDQqQqKqEC1MoxGlI,674
1
+ ipfabric_netbox/__init__.py,sha256=ubD3_CcKoeKzWiGAgw-Jbc6qYADxMkuX4GHa2dTMYK0,674
2
2
  ipfabric_netbox/api/__init__.py,sha256=XRclTGWVR0ZhAAwgYul5Wm_loug5_hUjEumbLQEwKYM,47
3
3
  ipfabric_netbox/api/serializers.py,sha256=92Cwhnqsm1l1oZfdHH5aJI1VFX0eO5JS4BsdXE6Ur18,6738
4
4
  ipfabric_netbox/api/urls.py,sha256=1fXXVTxNY5E64Nfz6b7zXD9bZI3FcefuxAWKMe0w_QU,1240
5
5
  ipfabric_netbox/api/views.py,sha256=qOBTIzPtOBY75tTjirsTBbiRXrQQid478Tp15-WKbmQ,6859
6
6
  ipfabric_netbox/choices.py,sha256=r1A7zasYw92fdB6MxnvcLkzz4mA61_wSUmbfuDbmg0M,6017
7
- ipfabric_netbox/data/transform_map.json,sha256=4PsucgMHcLW3SPoKEptQCd0gA5tCF4hjrR4bGQFCWy8,21744
8
- ipfabric_netbox/exceptions.py,sha256=DT4dpbakvqoROtBR_F0LzvQCMNWpGhufFcUbZTx0OLY,2655
7
+ ipfabric_netbox/data/transform_map.json,sha256=dYPXiaLJFuO9vabGgkuywn7XFCe7xdgPrfvOvP206TM,22016
8
+ ipfabric_netbox/exceptions.py,sha256=5nyAVoaPEGDHcrUXNpsCr_Nhq1vz1VbAmq54fU03iRg,1453
9
9
  ipfabric_netbox/filtersets.py,sha256=4I_ogO0Wqexf4e4gy_CirdGmA6vSCybyCadFcjI2LM8,8011
10
10
  ipfabric_netbox/forms.py,sha256=s9jYgK75CJzCrhnEeB3WxxZ9bF2YfNDA4N-sO9xTqgc,50068
11
11
  ipfabric_netbox/graphql/__init__.py,sha256=-a5w_VY7pc-RVt8MvThkTzeAqCC3xCan4Ue6iMefmjI,754
@@ -35,14 +35,15 @@ ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py,sha256=IVbA
35
35
  ipfabric_netbox/migrations/0018_remove_type_field.py,sha256=ffxW6IS3BLCbvM5M9DbDb_x6spMmRxnV1iq8IuXxMGw,385
36
36
  ipfabric_netbox/migrations/0019_alter_ipfabrictransformmap_options_and_more.py,sha256=ieDVedt9KpJBicAiC3kdZXzHeos12N0L9EdRXKmIVgY,501
37
37
  ipfabric_netbox/migrations/0020_clean_scheduled_jobs.py,sha256=zjCVKnCWTKYYkpVRwHjqRIRR2j6ALSKXYMfraRjNu7Y,2652
38
+ ipfabric_netbox/migrations/0021_update_transform_maps.py,sha256=W9GSgMQNmEdxnRI1c13at3XG1F_hyo9oZWsTZ0TLN9c,5464
38
39
  ipfabric_netbox/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- ipfabric_netbox/models.py,sha256=3etGCInD1FH1uyOlNh-Lx8iCd1SqdqoN583XK6yhBsw,38402
40
+ ipfabric_netbox/models.py,sha256=5bgQzfGfCUWQdRL3j6CNFXVDW3oDfxpQ0Y6JCKRJz-M,39250
40
41
  ipfabric_netbox/navigation.py,sha256=g2PyyaMqjgYwO3VIKza8vMS-dhbkvxscsHwVfWBj_dk,2287
41
- ipfabric_netbox/signals.py,sha256=cGa5PVD2i24pGXiVNfbu6ruIDqPVdwKQHTSWe9Ura84,1838
42
+ ipfabric_netbox/signals.py,sha256=y3x2jKT8yTjOfC4B3h4YZPRsYQkBal4cFdHJFkbPoS8,1411
42
43
  ipfabric_netbox/tables.py,sha256=jXiHcRrR4XwkvyUnnU92JW290nZg-85IdpKjsQOsFcc,9052
43
44
  ipfabric_netbox/template_content.py,sha256=lxZ02BFVihbSgjXCETGsWmhdElQUUO3uUGd0WfhlRmw,1120
44
45
  ipfabric_netbox/templates/ipfabric_netbox/inc/clone_form.html,sha256=f6O5ugjQg7u37QWxUpKvE38jNslwb3rCMghlAUqjWBk,1127
45
- ipfabric_netbox/templates/ipfabric_netbox/inc/diff.html,sha256=npdsr8vWOfpUafto1gj3CwR34dGV5BkoygnY9U8bSNs,3439
46
+ ipfabric_netbox/templates/ipfabric_netbox/inc/diff.html,sha256=tuXJdQnesdIF0pUoKoho_8fQTGSIwdoEWhieHu6ikVM,3439
46
47
  ipfabric_netbox/templates/ipfabric_netbox/inc/json.html,sha256=Z1zxFjy4PaAin3g_1EXy1KSlJyqSxWCD096wbRulQaE,635
47
48
  ipfabric_netbox/templates/ipfabric_netbox/inc/logs_pending.html,sha256=lZXUFWQCQtu6RMe_OQQmjLMjJEnZ71EHPrhvSTE_-R4,353
48
49
  ipfabric_netbox/templates/ipfabric_netbox/inc/merge_form.html,sha256=hey2JVm3IDZUqdpujzPUJYB7xZ4nxQ0pxl5wwRQIxOY,836
@@ -52,7 +53,7 @@ ipfabric_netbox/templates/ipfabric_netbox/inc/snapshotdata.html,sha256=1ItOCPjjp
52
53
  ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_field_map.html,sha256=MSpU2mQnrGg_jA1-eqQgaSK9DXCeo3w6j33tV_GFNJ4,535
53
54
  ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_relationship_map.html,sha256=tmIV0gDhfVxBse4xDeE5atMi4KEMkvxB_WRx94gu44U,539
54
55
  ipfabric_netbox/templates/ipfabric_netbox/ipfabric_table.html,sha256=HsxENF0KaaGT8w0_K6251LVH0W_mg60W8ktApxAG59U,1689
55
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricingestion.html,sha256=EohTqqCwafb3ZfX0MJko4sdKaKNQmNZ7Cc7f0vTo-K8,4760
56
+ ipfabric_netbox/templates/ipfabric_netbox/ipfabricingestion.html,sha256=4cUP5KxCEtH4j5RFT68-7UJUCnJcOCxA0iBzrxwua4I,4760
56
57
  ipfabric_netbox/templates/ipfabric_netbox/ipfabricsnapshot.html,sha256=HWCGi1e0O_jL-5XnIYfCC5EQh5oSw-1-ZUpjH6-cGV0,3690
57
58
  ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html,sha256=I9K7Eob-jjNAy6lryq2QKpp-5JyR1DEJSPF3D6JKR_w,4063
58
59
  ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html,sha256=M7tig8Y42ndsipBHKVVfSFb9tSGvm7Cf4fj-B54xizs,4852
@@ -74,15 +75,14 @@ ipfabric_netbox/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
74
75
  ipfabric_netbox/tests/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
76
  ipfabric_netbox/tests/api/test_api.py,sha256=rnpGj3_cuh-QGQgOoARWuvtipDNCjcICn_Qbv2kUMQA,35304
76
77
  ipfabric_netbox/tests/test_forms.py,sha256=ZZezIMjf5UiFGzpIlfIdnmXK5Urk8SdapF5c2MMOYoA,61693
77
- ipfabric_netbox/tests/test_models.py,sha256=FFrIT5xxv_yvujKpxGjRJPNPBDF2Pqi8zbY0vxuJeQs,16043
78
+ ipfabric_netbox/tests/test_models.py,sha256=gagJKoxD-BnEMWwZ2d5uImrWJOqYgfXe23JRikcoruo,16420
78
79
  ipfabric_netbox/tests/test_views.py,sha256=KKHKA4ejTEwdy6Ce5StJxjxWVWbQ54Y1puyPeBRw1vM,87923
79
80
  ipfabric_netbox/urls.py,sha256=qF2BzZEDnPRd3opFaRfiMdaarYKFfv69iMaAbU2rsBU,2702
80
81
  ipfabric_netbox/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
- ipfabric_netbox/utilities/ipfutils.py,sha256=I5okghNJV8M82eWACz1-px46AChcSWsoOu5_DpEXojo,31886
82
+ ipfabric_netbox/utilities/ipfutils.py,sha256=wc6wVBEHKJ8RoCOrp6Fm3w08nf1xiHGGMsMYrypOcpA,38852
82
83
  ipfabric_netbox/utilities/logging.py,sha256=GYknjocMN6LQ2873_az3y0RKm29TCXaWviUIIneH-x0,3445
83
- ipfabric_netbox/utilities/nbutils.py,sha256=kFBEiJOGvr_49hJWCS2duXojx2-A9kVk0Xp_vj0ohfs,2641
84
- ipfabric_netbox/utilities/transform_map.py,sha256=GpM_7Mm6FE0qV2qbyj4YfDn0l-JkeeEHQOZkNVSSHk4,2391
84
+ ipfabric_netbox/utilities/transform_map.py,sha256=nJhEdi2DqqowrtfowNgg-FZiE3_lN0MhQvaNwHS4yXw,8979
85
85
  ipfabric_netbox/views.py,sha256=CPVtPvHcKCeCAusxjC0WvnOUKn6p5m3lgd57wOp29dI,44829
86
- ipfabric_netbox-4.3.2b1.dist-info/METADATA,sha256=woxaag3l6mQGoLU66g1Di6mOvbfVKbDtkCffGods4Xk,4754
87
- ipfabric_netbox-4.3.2b1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
88
- ipfabric_netbox-4.3.2b1.dist-info/RECORD,,
86
+ ipfabric_netbox-4.3.2b3.dist-info/METADATA,sha256=x5CPFIHt27uvIVjSJ3v38qFptMdLXYyC5y-jA6hNnDY,4789
87
+ ipfabric_netbox-4.3.2b3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
88
+ ipfabric_netbox-4.3.2b3.dist-info/RECORD,,
@@ -1,79 +0,0 @@
1
- from collections import Counter
2
- from copy import deepcopy
3
-
4
- from dcim.models import Device
5
- from dcim.models import InventoryItem
6
- from dcim.models import Manufacturer
7
-
8
- DEFAULT_DEVICE_ROLE = "Network Device"
9
- device_serial_max_length = Device._meta.get_field("serial").max_length
10
-
11
-
12
- def order_members(members):
13
- devices = {}
14
-
15
- for member in members:
16
- master_serial = member.get("sn")
17
- if master_serial and member.get("memberSn"):
18
- if master_serial in devices:
19
- devices[master_serial].append(member)
20
- else:
21
- devices[master_serial] = [member]
22
-
23
- return devices
24
-
25
-
26
- def order_devices(devices, members):
27
- hostnames = [d["hostname"] for d in devices]
28
- counter = Counter(hostnames)
29
-
30
- new_devices = []
31
-
32
- for device in devices:
33
- if counter[device["hostname"]] > 1:
34
- device["hostname"] = f"{device['hostname']} - ({device['sn']})"
35
- if child_members := members.get(device.get("sn")):
36
- for child_member in child_members:
37
- if device.get("sn") != child_member.get("memberSn"):
38
- new_device = deepcopy(device)
39
- new_device[
40
- "hostname"
41
- ] = f"{device['hostname']}/{child_member.get('member')}"
42
- new_device["model"] = child_member.get("pn")
43
- new_device["sn"] = child_member.get("memberSn")
44
- new_device["virtual_chassis"] = child_member
45
- new_devices.append(new_device)
46
- else:
47
- device["virtual_chassis"] = child_member
48
- hostnames = [d["hostname"] for d in devices]
49
- counter = Counter(hostnames)
50
-
51
- devices.extend(new_devices)
52
-
53
- return devices
54
-
55
-
56
- def create_inventory_items(device: Device, parts: list, manufacturer: Manufacturer):
57
- for part in parts:
58
- name = part.get("name", "")
59
- if len(name) > InventoryItem._meta.get_field("name").max_length:
60
- if part.get("dscr"):
61
- name = part.get("dscr")
62
- else:
63
- name = part.get("sn")
64
-
65
- defaults = {
66
- "name": name,
67
- "manufacturer": manufacturer,
68
- "serial": part.get("sn", ""),
69
- # "description": part.get('dscr', "123"),
70
- "part_id": part.get("pid", ""),
71
- "device": device,
72
- "lft": device.pk,
73
- }
74
- if part.get("dscr"):
75
- defaults["description"] = part.get("dscr")
76
-
77
- inventory_object, _ = InventoryItem.objects.update_or_create(
78
- serial=part.get("sn", ""), defaults=defaults
79
- )