ipfabric_netbox 3.1.2__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.

Potentially problematic release.


This version of ipfabric_netbox might be problematic. Click here for more details.

Files changed (59) hide show
  1. ipfabric_netbox/__init__.py +42 -0
  2. ipfabric_netbox/api/__init__.py +2 -0
  3. ipfabric_netbox/api/nested_serializers.py +99 -0
  4. ipfabric_netbox/api/serializers.py +160 -0
  5. ipfabric_netbox/api/urls.py +21 -0
  6. ipfabric_netbox/api/views.py +111 -0
  7. ipfabric_netbox/choices.py +226 -0
  8. ipfabric_netbox/filtersets.py +125 -0
  9. ipfabric_netbox/forms.py +1063 -0
  10. ipfabric_netbox/jobs.py +95 -0
  11. ipfabric_netbox/migrations/0001_initial.py +342 -0
  12. ipfabric_netbox/migrations/0002_ipfabricsnapshot_status.py +17 -0
  13. ipfabric_netbox/migrations/0003_ipfabricsource_type_and_more.py +49 -0
  14. ipfabric_netbox/migrations/0004_ipfabricsync_auto_merge.py +17 -0
  15. ipfabric_netbox/migrations/0005_alter_ipfabricrelationshipfield_source_model_and_more.py +64 -0
  16. ipfabric_netbox/migrations/0006_alter_ipfabrictransformmap_target_model.py +48 -0
  17. ipfabric_netbox/migrations/__init__.py +0 -0
  18. ipfabric_netbox/models.py +874 -0
  19. ipfabric_netbox/navigation.py +62 -0
  20. ipfabric_netbox/signals.py +68 -0
  21. ipfabric_netbox/tables.py +208 -0
  22. ipfabric_netbox/template_content.py +13 -0
  23. ipfabric_netbox/templates/ipfabric_netbox/inc/diff.html +72 -0
  24. ipfabric_netbox/templates/ipfabric_netbox/inc/json.html +20 -0
  25. ipfabric_netbox/templates/ipfabric_netbox/inc/logs_pending.html +6 -0
  26. ipfabric_netbox/templates/ipfabric_netbox/inc/merge_form.html +22 -0
  27. ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_button.html +70 -0
  28. ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_modal.html +61 -0
  29. ipfabric_netbox/templates/ipfabric_netbox/inc/snapshotdata.html +60 -0
  30. ipfabric_netbox/templates/ipfabric_netbox/inc/sync_delete.html +19 -0
  31. ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_field_map.html +11 -0
  32. ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_relationship_map.html +11 -0
  33. ipfabric_netbox/templates/ipfabric_netbox/ipfabric_table.html +55 -0
  34. ipfabric_netbox/templates/ipfabric_netbox/ipfabricbranch.html +141 -0
  35. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsnapshot.html +105 -0
  36. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +111 -0
  37. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +103 -0
  38. ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +41 -0
  39. ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_list.html +17 -0
  40. ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_restore.html +59 -0
  41. ipfabric_netbox/templates/ipfabric_netbox/partials/branch_all.html +10 -0
  42. ipfabric_netbox/templates/ipfabric_netbox/partials/branch_progress.html +19 -0
  43. ipfabric_netbox/templates/ipfabric_netbox/partials/branch_status.html +1 -0
  44. ipfabric_netbox/templates/ipfabric_netbox/partials/job_logs.html +53 -0
  45. ipfabric_netbox/templates/ipfabric_netbox/partials/sync_last_branch.html +1 -0
  46. ipfabric_netbox/templates/ipfabric_netbox/sync_list.html +126 -0
  47. ipfabric_netbox/templates/static/ipfabric_netbox/css/rack.css +9 -0
  48. ipfabric_netbox/tests/__init__.py +0 -0
  49. ipfabric_netbox/tests/test_models.py +1340 -0
  50. ipfabric_netbox/urls.py +141 -0
  51. ipfabric_netbox/utilities/__init__.py +0 -0
  52. ipfabric_netbox/utilities/ipfutils.py +591 -0
  53. ipfabric_netbox/utilities/logging.py +93 -0
  54. ipfabric_netbox/utilities/nbutils.py +105 -0
  55. ipfabric_netbox/utilities/transform_map.py +35 -0
  56. ipfabric_netbox/views.py +845 -0
  57. ipfabric_netbox-3.1.2.dist-info/METADATA +88 -0
  58. ipfabric_netbox-3.1.2.dist-info/RECORD +59 -0
  59. ipfabric_netbox-3.1.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,845 @@
1
+ import requests
2
+ from dcim.models import Device
3
+ from dcim.models import Site
4
+ from django.contrib import messages
5
+ from django.contrib.auth.mixins import LoginRequiredMixin
6
+ from django.core.cache import cache
7
+ from django.db import models
8
+ from django.shortcuts import get_object_or_404
9
+ from django.shortcuts import redirect
10
+ from django.shortcuts import render
11
+ from django.urls import reverse
12
+ from django.utils import timezone
13
+ from django.views.generic import View
14
+ from django_tables2 import RequestConfig
15
+ from extras.choices import ChangeActionChoices
16
+ from ipfabric.diagrams import Network
17
+ from ipfabric.diagrams import NetworkSettings
18
+ from netbox.staging import StagedChange
19
+ from netbox.views import generic
20
+ from netbox.views.generic.base import BaseObjectView
21
+ from utilities.data import shallow_compare_dict
22
+ from utilities.forms import ConfirmationForm
23
+ from utilities.paginator import EnhancedPaginator
24
+ from utilities.paginator import get_paginate_count
25
+ from utilities.query import count_related
26
+ from utilities.serialization import serialize_object
27
+ from utilities.views import get_viewname
28
+ from utilities.views import register_model_view
29
+ from utilities.views import ViewTab
30
+
31
+ from .filtersets import IPFabricBranchFilterSet
32
+ from .filtersets import IPFabricDataFilterSet
33
+ from .filtersets import IPFabricSnapshotFilterSet
34
+ from .filtersets import IPFabricSourceFilterSet
35
+ from .filtersets import IPFabricStagedChangeFilterSet
36
+ from .forms import IPFabricBranchFilterForm
37
+ from .forms import IPFabricRelationshipFieldForm
38
+ from .forms import IPFabricSnapshotFilterForm
39
+ from .forms import IPFabricSourceFilterForm
40
+ from .forms import IPFabricSourceForm
41
+ from .forms import IPFabricSyncForm
42
+ from .forms import IPFabricTableForm
43
+ from .forms import IPFabricTransformFieldForm
44
+ from .forms import IPFabricTransformMapForm
45
+ from .models import IPFabricBranch
46
+ from .models import IPFabricData
47
+ from .models import IPFabricRelationshipField
48
+ from .models import IPFabricSnapshot
49
+ from .models import IPFabricSource
50
+ from .models import IPFabricSync
51
+ from .models import IPFabricTransformField
52
+ from .models import IPFabricTransformMap
53
+ from .tables import BranchTable
54
+ from .tables import DeviceIPFTable
55
+ from .tables import IPFabricDataTable
56
+ from .tables import IPFabricRelationshipFieldTable
57
+ from .tables import IPFabricSnapshotTable
58
+ from .tables import IPFabricSourceTable
59
+ from .tables import IPFabricTransformFieldTable
60
+ from .tables import IPFabricTransformMapTable
61
+ from .tables import StagedChangesTable
62
+ from .utilities.ipfutils import IPFabric
63
+ from .utilities.transform_map import BuildTransformMaps
64
+
65
+
66
+ # Transform Map Relationship Field
67
+
68
+
69
+ @register_model_view(IPFabricRelationshipField, "edit")
70
+ class IPFabricRelationshipFieldEditView(generic.ObjectEditView):
71
+ queryset = IPFabricRelationshipField.objects.all()
72
+ form = IPFabricRelationshipFieldForm
73
+ default_return_url = "plugins:ipfabric_netbox:ipfabricrelationshipfield_list"
74
+
75
+
76
+ class IPFabricRelationshipFieldDeleteView(generic.ObjectDeleteView):
77
+ queryset = IPFabricRelationshipField.objects.all()
78
+ default_return_url = "plugins:ipfabric_netbox:ipfabricrelationshipfield_list"
79
+
80
+
81
+ @register_model_view(IPFabricTransformMap, "relationships")
82
+ class IPFabricTransformRelationshipView(generic.ObjectChildrenView):
83
+ queryset = IPFabricTransformMap.objects.all()
84
+ child_model = IPFabricRelationshipField
85
+ table = IPFabricRelationshipFieldTable
86
+ template_name = "ipfabric_netbox/inc/transform_map_relationship_map.html"
87
+ tab = ViewTab(
88
+ label="Relationship Maps",
89
+ badge=lambda obj: IPFabricRelationshipField.objects.filter(
90
+ transform_map=obj
91
+ ).count(),
92
+ permission="ipfabric_netbox.view_ipfabricrelationshipfield",
93
+ )
94
+
95
+ def get_children(self, request, parent):
96
+ return self.child_model.objects.filter(transform_map=parent)
97
+
98
+
99
+ class IPFabricRelationshipFieldListView(generic.ObjectListView):
100
+ queryset = IPFabricRelationshipField.objects.all()
101
+ table = IPFabricRelationshipFieldTable
102
+
103
+
104
+ # Transform Map
105
+
106
+
107
+ class IPFabricTransformMapListView(generic.ObjectListView):
108
+ queryset = IPFabricTransformMap.objects.all()
109
+ table = IPFabricTransformMapTable
110
+ template_name = "ipfabric_netbox/ipfabrictransformmap_list.html"
111
+
112
+
113
+ class IPFabricTransformMapRestoreView(generic.ObjectListView):
114
+ queryset = IPFabricTransformMap.objects.all()
115
+ table = IPFabricTransformMapTable
116
+
117
+ def get_required_permission(self):
118
+ return "ipfabric_netbox.tm_restore"
119
+
120
+ def get(self, request):
121
+ if request.htmx:
122
+ viewname = get_viewname(self.queryset.model, action="restore")
123
+ form_url = reverse(viewname)
124
+ form = ConfirmationForm(initial=request.GET)
125
+ dependent_objects = {
126
+ IPFabricTransformMap: IPFabricTransformMap.objects.all(),
127
+ IPFabricTransformField: IPFabricTransformField.objects.all(),
128
+ IPFabricRelationshipField: IPFabricRelationshipField.objects.all(),
129
+ }
130
+ print(dependent_objects)
131
+ return render(
132
+ request,
133
+ "ipfabric_netbox/ipfabrictransformmap_restore.html",
134
+ {
135
+ "form": form,
136
+ "form_url": form_url,
137
+ "dependent_objects": dependent_objects,
138
+ },
139
+ )
140
+
141
+ def post(self, request):
142
+ IPFabricTransformMap.objects.all().delete()
143
+ try:
144
+ data = requests.get(
145
+ "https://gitlab.com/ip-fabric/integrations/ipfabric-netbox/-/raw/main/scripts/transform_map.json"
146
+ ).json()
147
+ except Exception as e:
148
+ messages.error(request, e)
149
+ BuildTransformMaps(data=data)
150
+ return redirect("plugins:ipfabric_netbox:ipfabrictransformmap_list")
151
+
152
+
153
+ @register_model_view(IPFabricTransformMap, "edit")
154
+ class IPFabricTransformMapEditView(generic.ObjectEditView):
155
+ queryset = IPFabricTransformMap.objects.all()
156
+ form = IPFabricTransformMapForm
157
+ default_return_url = "plugins:ipfabric_netbox:ipfabrictransformmap_list"
158
+
159
+
160
+ class IPFabricTransformMapDeleteView(generic.ObjectDeleteView):
161
+ queryset = IPFabricTransformMap.objects.all()
162
+ default_return_url = "plugins:ipfabric_netbox:ipfabrictransformmap_list"
163
+
164
+
165
+ class IPFabricTransformMapBulkDeleteView(generic.BulkDeleteView):
166
+ queryset = IPFabricTransformMap.objects.all()
167
+ table = IPFabricTransformMapTable
168
+
169
+
170
+ @register_model_view(IPFabricTransformMap)
171
+ class IPFabricTransformMapView(generic.ObjectView):
172
+ queryset = IPFabricTransformMap.objects.all()
173
+
174
+
175
+ # Transform Map Field
176
+
177
+
178
+ class IPFabricTransformFieldListView(generic.ObjectListView):
179
+ queryset = IPFabricTransformField.objects.all()
180
+ table = IPFabricTransformFieldTable
181
+
182
+
183
+ @register_model_view(IPFabricTransformField, "edit")
184
+ class IPFabricTransformFieldEditView(generic.ObjectEditView):
185
+ queryset = IPFabricTransformField.objects.all()
186
+ form = IPFabricTransformFieldForm
187
+
188
+
189
+ class IPFabricTransformFieldDeleteView(generic.ObjectDeleteView):
190
+ queryset = IPFabricTransformField.objects.all()
191
+
192
+
193
+ @register_model_view(IPFabricTransformMap, "fields")
194
+ class IPFabricTransformFieldView(generic.ObjectChildrenView):
195
+ queryset = IPFabricTransformMap.objects.all()
196
+ child_model = IPFabricTransformField
197
+ table = IPFabricTransformFieldTable
198
+ template_name = "ipfabric_netbox/inc/transform_map_field_map.html"
199
+ tab = ViewTab(
200
+ label="Field Maps",
201
+ badge=lambda obj: IPFabricTransformField.objects.filter(
202
+ transform_map=obj
203
+ ).count(),
204
+ permission="ipfabric_netbox.view_ipfabrictransformfield",
205
+ )
206
+
207
+ def get_children(self, request, parent):
208
+ return self.child_model.objects.filter(transform_map=parent)
209
+
210
+
211
+ # Snapshot
212
+
213
+
214
+ class IPFabricSnapshotListView(generic.ObjectListView):
215
+ queryset = IPFabricSnapshot.objects.all()
216
+ table = IPFabricSnapshotTable
217
+ filterset = IPFabricSnapshotFilterSet
218
+ filterset_form = IPFabricSnapshotFilterForm
219
+
220
+
221
+ @register_model_view(IPFabricSnapshot)
222
+ class IPFabricSnapshotView(generic.ObjectView):
223
+ queryset = IPFabricSnapshot.objects.all()
224
+
225
+
226
+ class IPFabricSnapshotDeleteView(generic.ObjectDeleteView):
227
+ queryset = IPFabricSnapshot.objects.all()
228
+
229
+
230
+ class IPFabricSnapshotBulkDeleteView(generic.BulkDeleteView):
231
+ queryset = IPFabricSnapshot.objects.all()
232
+ filterset = IPFabricSnapshotFilterSet
233
+ table = IPFabricSnapshotTable
234
+
235
+
236
+ @register_model_view(IPFabricSnapshot, "data")
237
+ class IPFabricSnapshotRawView(generic.ObjectChildrenView):
238
+ queryset = IPFabricSnapshot.objects.all()
239
+ child_model = IPFabricData
240
+ table = IPFabricDataTable
241
+ template_name = "ipfabric_netbox/inc/snapshotdata.html"
242
+ tab = ViewTab(
243
+ label="Raw Data",
244
+ badge=lambda obj: IPFabricData.objects.filter(snapshot_data=obj).count(),
245
+ permission="ipfabric_netbox.view_ipfabricsnapshot",
246
+ hide_if_empty=True,
247
+ )
248
+
249
+ def get_children(self, request, parent):
250
+ return self.child_model.objects.filter(snapshot_data=parent)
251
+
252
+
253
+ class IPFabricSnapshotDataDeleteView(generic.ObjectDeleteView):
254
+ queryset = IPFabricData.objects.all()
255
+
256
+
257
+ class IPFabricSnapshotDataBulkDeleteView(generic.BulkDeleteView):
258
+ queryset = IPFabricData.objects.all()
259
+ filterset = IPFabricDataFilterSet
260
+ table = IPFabricDataTable
261
+
262
+
263
+ @register_model_view(
264
+ IPFabricData,
265
+ name="data",
266
+ path="json",
267
+ kwargs={},
268
+ )
269
+ class IPFabricSnapshotDataJSONView(LoginRequiredMixin, View):
270
+ template_name = "ipfabric_netbox/inc/json.html"
271
+
272
+ def get(self, request, **kwargs):
273
+ print(kwargs)
274
+ # change_id = kwargs.get("change_pk", None)
275
+
276
+ if request.htmx:
277
+ data = get_object_or_404(IPFabricData, pk=kwargs.get("pk"))
278
+ return render(
279
+ request,
280
+ self.template_name,
281
+ {
282
+ "object": data,
283
+ },
284
+ )
285
+
286
+ # return render(
287
+ # request,
288
+ # self.template_name,
289
+ # {
290
+ # "change": change,
291
+ # "prechange_data": prechange_data,
292
+ # "postchange_data": postchange_data,
293
+ # "diff_added": diff_added,
294
+ # "diff_removed": diff_removed,
295
+ # "size": "lg",
296
+ # },
297
+ # )
298
+
299
+
300
+ # Source
301
+
302
+
303
+ class IPFabricSourceListView(generic.ObjectListView):
304
+ queryset = IPFabricSource.objects.annotate(
305
+ snapshot_count=count_related(IPFabricSnapshot, "source")
306
+ )
307
+ filterset = IPFabricSourceFilterSet
308
+ filterset_form = IPFabricSourceFilterForm
309
+ table = IPFabricSourceTable
310
+
311
+
312
+ @register_model_view(IPFabricSource, "edit")
313
+ class IPFabricSourceEditView(generic.ObjectEditView):
314
+ queryset = IPFabricSource.objects.all()
315
+ form = IPFabricSourceForm
316
+
317
+
318
+ @register_model_view(IPFabricSource)
319
+ class IPFabricSourceView(generic.ObjectView):
320
+ queryset = IPFabricSource.objects.all()
321
+
322
+ def get_extra_context(self, request, instance):
323
+ related_models = (
324
+ (
325
+ IPFabricSnapshot.objects.restrict(request.user, "view").filter(
326
+ source=instance
327
+ ),
328
+ "source_id",
329
+ ),
330
+ )
331
+
332
+ job = instance.jobs.order_by("id").last()
333
+ data = {"related_models": related_models, "job": job}
334
+ if job:
335
+ data["job_results"] = job.data
336
+ return data
337
+
338
+
339
+ @register_model_view(IPFabricSource, "sync")
340
+ class IPFabricSourceSyncView(BaseObjectView):
341
+ queryset = IPFabricSource.objects.all()
342
+
343
+ def get_required_permission(self):
344
+ return "ipfabric_netbox.sync_source"
345
+
346
+ def get(self, request, pk):
347
+ ipfabricsource = get_object_or_404(self.queryset, pk=pk)
348
+ return redirect(ipfabricsource.get_absolute_url())
349
+
350
+ def post(self, request, pk):
351
+ ipfabricsource = get_object_or_404(self.queryset, pk=pk)
352
+ job = ipfabricsource.enqueue_sync_job(request=request)
353
+
354
+ messages.success(request, f"Queued job #{job.pk} to sync {ipfabricsource}")
355
+ return redirect(ipfabricsource.get_absolute_url())
356
+
357
+
358
+ @register_model_view(IPFabricSource, "delete")
359
+ class IPFabricSourceDeleteView(generic.ObjectDeleteView):
360
+ queryset = IPFabricSource.objects.all()
361
+
362
+
363
+ class IPFabricSourceBulkDeleteView(generic.BulkDeleteView):
364
+ queryset = IPFabricSource.objects.all()
365
+ filterset = IPFabricSourceFilterSet
366
+ table = IPFabricSourceTable
367
+
368
+
369
+ # Sync
370
+
371
+
372
+ class IPFabricSyncListView(View):
373
+ def get(self, request):
374
+ syncs = IPFabricSync.objects.prefetch_related("snapshot_data")
375
+ return render(
376
+ request,
377
+ "ipfabric_netbox/sync_list.html",
378
+ {"model": IPFabricSync, "syncs": syncs},
379
+ )
380
+
381
+
382
+ @register_model_view(IPFabricSync, "edit")
383
+ class IPFabricSyncEditView(generic.ObjectEditView):
384
+ queryset = IPFabricSync.objects.all()
385
+ form = IPFabricSyncForm
386
+
387
+ def alter_object(self, obj, request, url_args, url_kwargs):
388
+ obj.user = request.user
389
+ return obj
390
+
391
+
392
+ @register_model_view(IPFabricSync)
393
+ class IPFabricSyncView(generic.ObjectView):
394
+ queryset = IPFabricSync.objects.all()
395
+ actions = ("edit",)
396
+
397
+ def get(self, request, **kwargs):
398
+ instance = self.get_object(**kwargs)
399
+ last_branch = instance.ipfabricbranch_set.last()
400
+
401
+ if request.htmx:
402
+ response = render(
403
+ request,
404
+ "ipfabric_netbox/partials/sync_last_branch.html",
405
+ {"last_branch": last_branch},
406
+ )
407
+
408
+ if instance.status not in ["queued", "syncing"]:
409
+ messages.success(
410
+ request,
411
+ f"Ingestion ({instance.name}) {instance.status}. Branch {last_branch.name} {last_branch.job.status}.",
412
+ )
413
+ response["HX-Refresh"] = "true"
414
+ return response
415
+
416
+ return render(
417
+ request,
418
+ self.get_template_name(),
419
+ {
420
+ "object": instance,
421
+ "tab": self.tab,
422
+ **self.get_extra_context(request, instance),
423
+ },
424
+ )
425
+
426
+ def get_extra_context(self, request, instance):
427
+ last_branch = instance.ipfabricbranch_set.last()
428
+
429
+ if request.GET.get("format") in ["json", "yaml"]:
430
+ format = request.GET.get("format")
431
+ if request.user.is_authenticated:
432
+ request.user.config.set("data_format", format, commit=True)
433
+ elif request.user.is_authenticated:
434
+ format = request.user.config.get("data_format", "json")
435
+ else:
436
+ format = "json"
437
+
438
+ last_branch = instance.ipfabricbranch_set.last()
439
+
440
+ return {"format": format, "last_branch": last_branch}
441
+
442
+
443
+ @register_model_view(IPFabricSync, "delete")
444
+ class IPFabricSyncDeleteView(generic.ObjectDeleteView):
445
+ queryset = IPFabricSync.objects.all()
446
+ default_return_url = "plugins:ipfabric_netbox:ipfabricsync_list"
447
+
448
+ def get(self, request, pk):
449
+ obj = get_object_or_404(self.queryset, pk=pk)
450
+
451
+ if request.htmx:
452
+ viewname = get_viewname(self.queryset.model, action="delete")
453
+ form_url = reverse(viewname, kwargs={"pk": obj.pk})
454
+ form = ConfirmationForm(initial=request.GET)
455
+ return render(
456
+ request,
457
+ "ipfabric_netbox/inc/sync_delete.html",
458
+ {
459
+ "object": obj,
460
+ "object_type": self.queryset.model._meta.verbose_name,
461
+ "form": form,
462
+ "form_url": form_url,
463
+ **self.get_extra_context(request, obj),
464
+ },
465
+ )
466
+
467
+
468
+ class IPFabricSyncBulkDeleteView(generic.BulkDeleteView):
469
+ queryset = IPFabricSync.objects.all()
470
+ filterset = IPFabricSnapshotFilterSet
471
+ table = IPFabricSnapshotTable
472
+
473
+
474
+ # Ingestion
475
+
476
+
477
+ @register_model_view(IPFabricSync, "sync")
478
+ class IPFabricIngestSyncView(BaseObjectView):
479
+ queryset = IPFabricSync.objects.all()
480
+
481
+ def get_required_permission(self):
482
+ return "ipfabric_netbox.sync_ingest"
483
+
484
+ def get(self, request, pk):
485
+ ipfabric = get_object_or_404(self.queryset, pk=pk)
486
+ return redirect(ipfabric.get_absolute_url())
487
+
488
+ def post(self, request, pk):
489
+ ipfabric = get_object_or_404(self.queryset, pk=pk)
490
+ job = ipfabric.enqueue_sync_job(user=request.user, adhoc=True)
491
+
492
+ messages.success(request, f"Queued job #{job.pk} to sync {ipfabric}")
493
+ return redirect(ipfabric.get_absolute_url())
494
+
495
+
496
+ # Branch
497
+
498
+
499
+ class IPFabricBranchListView(generic.ObjectListView):
500
+ queryset = IPFabricBranch.objects.all()
501
+ filterset = IPFabricBranchFilterSet
502
+ filterset_form = IPFabricBranchFilterForm
503
+ table = BranchTable
504
+
505
+
506
+ @register_model_view(
507
+ IPFabricBranch,
508
+ name="logs",
509
+ path="logs",
510
+ )
511
+ class IPFabricBranchLogView(LoginRequiredMixin, View):
512
+ template_name = "ipfabric_netbox/partials/branch_all.html"
513
+
514
+ def get(self, request, **kwargs):
515
+ branch_id = kwargs.get("pk")
516
+ if request.htmx:
517
+ branch = IPFabricBranch.objects.get(pk=branch_id)
518
+ data = branch.get_statistics()
519
+ data["object"] = branch
520
+ data["job"] = branch.job
521
+ response = render(
522
+ request,
523
+ self.template_name,
524
+ data,
525
+ )
526
+ if branch.job.completed:
527
+ response["HX-Refresh"] = "true"
528
+ return response
529
+ else:
530
+ return response
531
+
532
+
533
+ @register_model_view(IPFabricBranch)
534
+ class IPFabricBranchView(generic.ObjectView):
535
+ queryset = IPFabricBranch.objects.annotate(
536
+ num_created=models.Count(
537
+ "staged_changes",
538
+ filter=models.Q(staged_changes__action=ChangeActionChoices.ACTION_CREATE)
539
+ & ~models.Q(staged_changes__object_type__model="objectchange"),
540
+ ),
541
+ num_updated=models.Count(
542
+ "staged_changes",
543
+ filter=models.Q(staged_changes__action=ChangeActionChoices.ACTION_UPDATE)
544
+ & ~models.Q(staged_changes__object_type__model="objectchange"),
545
+ ),
546
+ num_deleted=models.Count(
547
+ "staged_changes",
548
+ filter=models.Q(staged_changes__action=ChangeActionChoices.ACTION_DELETE)
549
+ & ~models.Q(staged_changes__object_type__model="objectchange"),
550
+ ),
551
+ )
552
+
553
+ def get_extra_context(self, request, instance):
554
+ data = instance.get_statistics()
555
+ return data
556
+
557
+
558
+ @register_model_view(IPFabricBranch, "merge")
559
+ class IPFabricBranchMergeView(BaseObjectView):
560
+ queryset = IPFabricBranch.objects.all()
561
+ template_name = "ipfabric_netbox/inc/merge_form.html"
562
+
563
+ def get_required_permission(self):
564
+ return "ipfabric_netbox.merge_branch"
565
+
566
+ def get(self, request, pk):
567
+ obj = get_object_or_404(self.queryset, pk=pk)
568
+
569
+ if request.htmx:
570
+ viewname = get_viewname(self.queryset.model, action="merge")
571
+ form_url = reverse(viewname, kwargs={"pk": obj.pk})
572
+ form = ConfirmationForm(initial=request.GET)
573
+ return render(
574
+ request,
575
+ "ipfabric_netbox/inc/merge_form.html",
576
+ {
577
+ "object": obj,
578
+ "object_type": self.queryset.model._meta.verbose_name,
579
+ "form": form,
580
+ "form_url": form_url,
581
+ **self.get_extra_context(request, obj),
582
+ },
583
+ )
584
+
585
+ return redirect(obj.get_absolute_url())
586
+
587
+ def post(self, request, pk):
588
+ ipfabric = get_object_or_404(self.queryset, pk=pk)
589
+ job = ipfabric.enqueue_merge_job(user=request.user)
590
+
591
+ messages.success(request, f"Queued job #{job.pk} to sync {ipfabric}")
592
+ return redirect(ipfabric.get_absolute_url())
593
+
594
+
595
+ @register_model_view(
596
+ IPFabricBranch,
597
+ name="change_diff",
598
+ path="change/<int:change_pk>",
599
+ kwargs={"model": IPFabricBranch},
600
+ )
601
+ class IPFabricBranchChangesDiffView(LoginRequiredMixin, View):
602
+ template_name = "ipfabric_netbox/inc/diff.html"
603
+
604
+ def get(self, request, model, **kwargs):
605
+ change_id = kwargs.get("change_pk", None)
606
+
607
+ if request.htmx:
608
+ if change_id:
609
+ change = StagedChange.objects.get(pk=change_id)
610
+ if hasattr(change.object, "pk"):
611
+ prechange_data = serialize_object(change.object, resolve_tags=False)
612
+ prechange_data = dict(sorted(prechange_data.items()))
613
+ else:
614
+ prechange_data = None
615
+
616
+ if hasattr(change, "data"):
617
+ postchange_data = dict(sorted(change.data.items()))
618
+
619
+ if prechange_data and postchange_data:
620
+ diff_added = shallow_compare_dict(
621
+ prechange_data or dict(),
622
+ postchange_data or dict(),
623
+ exclude=["last_updated"],
624
+ )
625
+ diff_removed = (
626
+ {x: prechange_data.get(x) for x in diff_added}
627
+ if prechange_data
628
+ else {}
629
+ )
630
+ else:
631
+ diff_added = None
632
+ diff_removed = None
633
+
634
+ return render(
635
+ request,
636
+ self.template_name,
637
+ {
638
+ "change": change,
639
+ "prechange_data": prechange_data,
640
+ "postchange_data": postchange_data,
641
+ "diff_added": diff_added,
642
+ "diff_removed": diff_removed,
643
+ "size": "lg",
644
+ },
645
+ )
646
+
647
+
648
+ @register_model_view(IPFabricBranch, "change")
649
+ class IPFabricBranchChangesView(generic.ObjectChildrenView):
650
+ queryset = IPFabricBranch.objects.all()
651
+ child_model = StagedChange
652
+ table = StagedChangesTable
653
+ filterset = IPFabricStagedChangeFilterSet
654
+ template_name = "generic/object_children.html"
655
+ tab = ViewTab(
656
+ label="Changes",
657
+ badge=lambda obj: StagedChange.objects.filter(branch=obj).count(),
658
+ permission="ipfabric_netbox.view_ipfabricbranch",
659
+ )
660
+
661
+ def get_children(self, request, parent):
662
+ return self.child_model.objects.filter(branch=parent)
663
+
664
+
665
+ @register_model_view(IPFabricBranch, "delete")
666
+ class IPFabricBranchDeleteView(generic.ObjectDeleteView):
667
+ queryset = IPFabricBranch.objects.all()
668
+
669
+
670
+ @register_model_view(IPFabricSync, "branch")
671
+ class IPFabricBranchTabView(generic.ObjectChildrenView):
672
+ queryset = IPFabricSync.objects.all()
673
+ child_model = IPFabricBranch
674
+ table = BranchTable
675
+ filterset = IPFabricBranchFilterSet
676
+ template_name = "generic/object_children.html"
677
+ tab = ViewTab(
678
+ label="Branches",
679
+ badge=lambda obj: IPFabricBranch.objects.filter(sync=obj).count(),
680
+ permission="ipfabric_netbox.view_ipfabricbranch",
681
+ )
682
+
683
+ def get_children(self, request, parent):
684
+ return self.child_model.objects.filter(sync=parent)
685
+
686
+
687
+ @register_model_view(Device, "ipfabric")
688
+ class IPFabricTable(View):
689
+ template_name = "ipfabric_netbox/ipfabric_table.html"
690
+ tab = ViewTab("IP Fabric", permission="ipfabric_netbox.view_devicetable")
691
+
692
+ def get(self, request, pk):
693
+ device = get_object_or_404(Device, pk=pk)
694
+ form = (
695
+ IPFabricTableForm(request.GET)
696
+ if "table" in request.GET
697
+ else IPFabricTableForm()
698
+ )
699
+ data = None
700
+
701
+ if form.is_valid():
702
+ table = form.cleaned_data["table"]
703
+ test = {
704
+ "True": True,
705
+ "False": False,
706
+ }
707
+ cache_enable = test.get(form.cleaned_data["cache_enable"])
708
+ snapshot_id = ""
709
+
710
+ if not form.cleaned_data["snapshot_data"]:
711
+ snapshot_id = "$last"
712
+ source = IPFabricSource.objects.get(
713
+ pk=device.custom_field_data["ipfabric_source"]
714
+ )
715
+
716
+ else:
717
+ snapshot_id = form.cleaned_data["snapshot_data"].snapshot_id
718
+ source = form.cleaned_data["snapshot_data"].source
719
+
720
+ source.parameters["snapshot_id"] = snapshot_id
721
+ source.parameters["base_url"] = source.url
722
+
723
+ cache_key = (
724
+ f"ipfabric_{table}_{device.serial}_{source.parameters['snapshot_id']}"
725
+ )
726
+ if cache_enable:
727
+ data = cache.get(cache_key)
728
+
729
+ if not data:
730
+ try:
731
+ ipf = IPFabric(parameters=source.parameters)
732
+ raw_data, columns = ipf.get_table_data(table=table, device=device)
733
+ data = {"data": raw_data, "columns": columns}
734
+ cache.set(cache_key, data, 60 * 60 * 24)
735
+ except Exception as e:
736
+ messages.error(request, e)
737
+
738
+ if not data:
739
+ data = {"data": [], "columns": []}
740
+
741
+ table = DeviceIPFTable(data["data"], extra_columns=data["columns"])
742
+
743
+ RequestConfig(
744
+ request,
745
+ {
746
+ "paginator_class": EnhancedPaginator,
747
+ "per_page": get_paginate_count(request),
748
+ },
749
+ ).configure(table)
750
+
751
+ if request.htmx:
752
+ return render(
753
+ request,
754
+ "htmx/table.html",
755
+ {
756
+ "table": table,
757
+ },
758
+ )
759
+
760
+ source = None
761
+
762
+ if source_id := device.custom_field_data["ipfabric_source"]:
763
+ source = IPFabricSource.objects.get(pk=source_id)
764
+
765
+ return render(
766
+ request,
767
+ self.template_name,
768
+ {
769
+ "object": device,
770
+ "source": source,
771
+ "tab": self.tab,
772
+ "form": form,
773
+ "table": table,
774
+ },
775
+ )
776
+
777
+
778
+ @register_model_view(
779
+ IPFabricSource,
780
+ name="topology",
781
+ path="topology/<int:site>",
782
+ kwargs={"snapshot": ""},
783
+ )
784
+ class IPFabricSourceTopology(LoginRequiredMixin, View):
785
+ template_name = "ipfabric_netbox/inc/site_topology_modal.html"
786
+
787
+ def get(self, request, pk, site, **kwargs):
788
+ if request.htmx:
789
+ try:
790
+ site = get_object_or_404(Site, pk=site)
791
+ source_id = request.GET.get("source")
792
+ if not source_id:
793
+ raise Exception("Source ID not available in request.")
794
+ source = get_object_or_404(IPFabricSource, pk=source_id)
795
+ snapshot = request.GET.get("snapshot")
796
+ if not snapshot:
797
+ raise Exception("Snapshot ID not available in request.")
798
+
799
+ source.parameters.update(
800
+ {"snapshot_id": snapshot, "base_url": source.url}
801
+ )
802
+
803
+ ipf = IPFabric(parameters=source.parameters)
804
+ snapshot_data = ipf.ipf.snapshots.get(snapshot)
805
+ if not snapshot_data:
806
+ raise Exception(
807
+ f"Snapshot ({snapshot}) not available in IP Fabric." # noqa E713
808
+ )
809
+
810
+ sites = ipf.ipf.inventory.sites.all(
811
+ filters={"siteName": ["eq", site.name]}
812
+ )
813
+ if not sites:
814
+ raise Exception(
815
+ f"{site.name} not available in snapshot ({snapshot})." # noqa E713
816
+ )
817
+
818
+ net = Network(sites=site.name, all_network=False)
819
+ settings = NetworkSettings()
820
+ settings.hide_protocol("xdp")
821
+ settings.hiddenDeviceTypes.extend(["transit", "cloud"])
822
+
823
+ link = ipf.ipf.diagram.share_link(net, graph_settings=settings)
824
+ svg_data = ipf.ipf.diagram.svg(net, graph_settings=settings).decode(
825
+ "utf-8"
826
+ )
827
+ error = None
828
+ except Exception as e:
829
+ error = e
830
+ svg_data = link = None
831
+
832
+ return render(
833
+ request,
834
+ self.template_name,
835
+ {
836
+ "site": site,
837
+ "source": source,
838
+ "svg": svg_data,
839
+ "size": "xl",
840
+ "link": link,
841
+ "time": timezone.now(),
842
+ "snapshot": snapshot_data,
843
+ "error": error,
844
+ },
845
+ )