ipfabric_netbox 4.2.2b2__py3-none-any.whl → 4.2.2b4__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.

ipfabric_netbox/urls.py CHANGED
@@ -15,18 +15,13 @@ urlpatterns = (
15
15
  views.IPFabricSourceBulkDeleteView.as_view(),
16
16
  name="ipfabricsource_bulk_delete",
17
17
  ),
18
- path(
19
- "source/<int:pk>/delete/",
20
- views.IPFabricSourceDeleteView.as_view(),
21
- name="ipfabricsource_delete",
22
- ),
23
18
  path(
24
19
  "source/<int:pk>/", include(get_model_urls("ipfabric_netbox", "ipfabricsource"))
25
20
  ),
26
21
  path(
27
- "source/delete/",
28
- views.IPFabricSyncBulkDeleteView.as_view(),
29
- name="ipfabricsync_bulk_delete",
22
+ "source/<int:pk>/delete/",
23
+ views.IPFabricSourceDeleteView.as_view(),
24
+ name="ipfabricsource_delete",
30
25
  ),
31
26
  # Snapshot
32
27
  path(
@@ -48,20 +43,26 @@ urlpatterns = (
48
43
  views.IPFabricSnapshotDeleteView.as_view(),
49
44
  name="ipfabricsnapshot_delete",
50
45
  ),
51
- path(
52
- "data/<int:pk>/delete",
53
- views.IPFabricSnapshotDataDeleteView.as_view(),
54
- name="ipfabricdata_delete",
55
- ),
56
- path("data/<int:pk>/", include(get_model_urls("ipfabric_netbox", "ipfabricdata"))),
46
+ # Snapshot Data
57
47
  path(
58
48
  "data/delete",
59
49
  views.IPFabricSnapshotDataBulkDeleteView.as_view(),
60
50
  name="ipfabricdata_bulk_delete",
61
51
  ),
52
+ path("data/<int:pk>/", include(get_model_urls("ipfabric_netbox", "ipfabricdata"))),
53
+ path(
54
+ "data/<int:pk>/delete",
55
+ views.IPFabricSnapshotDataDeleteView.as_view(),
56
+ name="ipfabricdata_delete",
57
+ ),
62
58
  # Sync
63
59
  path("sync/", views.IPFabricSyncListView.as_view(), name="ipfabricsync_list"),
64
60
  path("sync/add/", views.IPFabricSyncEditView.as_view(), name="ipfabricsync_add"),
61
+ path(
62
+ "sync/delete/",
63
+ views.IPFabricSyncBulkDeleteView.as_view(),
64
+ name="ipfabricsync_bulk_delete",
65
+ ),
65
66
  path("sync/<int:pk>/", include(get_model_urls("ipfabric_netbox", "ipfabricsync"))),
66
67
  path(
67
68
  "sync/<int:pk>/delete/",
@@ -8,10 +8,10 @@ from django.apps import apps as django_apps
8
8
  # see https://docs.djangoproject.com/en/5.1/topics/migrations/#historical-models
9
9
 
10
10
 
11
- def build_fields(data, apps):
11
+ def build_fields(data, apps, db_alias):
12
12
  ContentType = apps.get_model("contenttypes", "ContentType")
13
13
  if "target_model" in data:
14
- ct = ContentType.objects.get_for_model(
14
+ ct = ContentType.objects.db_manager(db_alias).get_for_model(
15
15
  apps.get_model(
16
16
  data["target_model"]["app_label"],
17
17
  data["target_model"]["model"],
@@ -19,7 +19,7 @@ def build_fields(data, apps):
19
19
  )
20
20
  data["target_model"] = ct
21
21
  elif "source_model" in data:
22
- ct = ContentType.objects.get_for_model(
22
+ ct = ContentType.objects.db_manager(db_alias).get_for_model(
23
23
  apps.get_model(
24
24
  data["source_model"]["app_label"],
25
25
  data["source_model"]["model"],
@@ -29,7 +29,7 @@ def build_fields(data, apps):
29
29
  return data
30
30
 
31
31
 
32
- def build_transform_maps(data, apps: django_apps = None):
32
+ def build_transform_maps(data, apps: django_apps = None, db_alias: str = "default"):
33
33
  apps = apps or django_apps
34
34
  IPFabricTransformMap = apps.get_model("ipfabric_netbox", "IPFabricTransformMap")
35
35
  IPFabricTransformField = apps.get_model("ipfabric_netbox", "IPFabricTransformField")
@@ -37,14 +37,16 @@ def build_transform_maps(data, apps: django_apps = None):
37
37
  "ipfabric_netbox", "IPFabricRelationshipField"
38
38
  )
39
39
  for tm in data:
40
- field_data = build_fields(tm["data"], apps)
41
- tm_obj = IPFabricTransformMap.objects.create(**field_data)
40
+ field_data = build_fields(tm["data"], apps, db_alias)
41
+ tm_obj = IPFabricTransformMap.objects.using(db_alias).create(**field_data)
42
42
  for fm in tm["field_maps"]:
43
- field_data = build_fields(fm, apps)
44
- IPFabricTransformField.objects.create(transform_map=tm_obj, **field_data)
43
+ field_data = build_fields(fm, apps, db_alias)
44
+ IPFabricTransformField.objects.using(db_alias).create(
45
+ transform_map=tm_obj, **field_data
46
+ )
45
47
  for rm in tm["relationship_maps"]:
46
- relationship_data = build_fields(rm, apps)
47
- IPFabricRelationshipField.objects.create(
48
+ relationship_data = build_fields(rm, apps, db_alias)
49
+ IPFabricRelationshipField.objects.using(db_alias).create(
48
50
  transform_map=tm_obj, **relationship_data
49
51
  )
50
52
 
ipfabric_netbox/views.py CHANGED
@@ -170,7 +170,7 @@ class IPFabricTransformMapRestoreView(generic.ObjectListView):
170
170
  table = IPFabricTransformMapTable
171
171
 
172
172
  def get_required_permission(self):
173
- return "ipfabric_netbox.tm_restore"
173
+ return "ipfabric_netbox.restore_ipfabrictransformmap"
174
174
 
175
175
  def get(self, request):
176
176
  if request.htmx:
@@ -197,6 +197,7 @@ class IPFabricTransformMapRestoreView(generic.ObjectListView):
197
197
  "dependent_objects": dependent_objects,
198
198
  },
199
199
  )
200
+ return redirect(reverse("plugins:ipfabric_netbox:ipfabrictransformmap_list"))
200
201
 
201
202
  def post(self, request):
202
203
  IPFabricTransformMap.objects.filter(group__isnull=True).delete()
@@ -218,7 +219,7 @@ class IPFabricTransformMapCloneView(BaseObjectView):
218
219
  form = IPFabricTransformMapCloneForm
219
220
 
220
221
  def get_required_permission(self):
221
- return "ipfabric_netbox.ipfabrictransformmap_add"
222
+ return "ipfabric_netbox.clone_ipfabrictransformmap"
222
223
 
223
224
  def get(self, request, pk):
224
225
  obj = get_object_or_404(self.queryset, pk=pk)
@@ -252,36 +253,40 @@ class IPFabricTransformMapCloneView(BaseObjectView):
252
253
  relationships = IPFabricRelationshipField.objects.filter(
253
254
  transform_map=obj
254
255
  )
255
- # Clone the transform map
256
- new_map = obj
257
- new_map.pk = None
258
- new_map.id = None
259
- new_map.name = form.cleaned_data["name"]
260
- new_map.group = form.cleaned_data["group"]
256
+ # Clone the transform map - create a proper copy using Django model copying
257
+ new_map = IPFabricTransformMap(
258
+ name=form.cleaned_data["name"],
259
+ source_model=obj.source_model,
260
+ target_model=obj.target_model,
261
+ group=form.cleaned_data["group"],
262
+ )
261
263
  new_map.full_clean()
262
264
  new_map.save()
263
- new_map.refresh_from_db()
264
265
 
265
266
  # Clone related transform fields
266
267
  if form.cleaned_data["clone_fields"]:
267
268
  for field in fields:
268
- field.pk = None
269
- field.id = None
270
- field.transform_map = new_map
271
- field.full_clean()
272
- field.save()
269
+ IPFabricTransformField.objects.create(
270
+ transform_map=new_map,
271
+ source_field=field.source_field,
272
+ target_field=field.target_field,
273
+ coalesce=field.coalesce,
274
+ template=field.template,
275
+ )
273
276
 
274
277
  # Clone related relationship fields
275
278
  if form.cleaned_data["clone_relationships"]:
276
279
  for rel in relationships:
277
- rel.pk = None
278
- rel.id = None
279
- rel.transform_map = new_map
280
- rel.full_clean()
281
- rel.save()
280
+ IPFabricRelationshipField.objects.create(
281
+ transform_map=new_map,
282
+ source_model=rel.source_model,
283
+ target_field=rel.target_field,
284
+ coalesce=rel.coalesce,
285
+ template=rel.template,
286
+ )
282
287
 
283
288
  return_url = reverse(
284
- "plugins:ipfabric_netbox:ipfabrictransformmap", args=[obj.pk]
289
+ "plugins:ipfabric_netbox:ipfabrictransformmap", args=[new_map.pk]
285
290
  )
286
291
  if request.htmx:
287
292
  response = HttpResponse()
@@ -289,7 +294,7 @@ class IPFabricTransformMapCloneView(BaseObjectView):
289
294
  return response
290
295
  return redirect(return_url)
291
296
  except ValidationError as err:
292
- if not err.error_dict:
297
+ if not hasattr(err, "error_dict") or not err.error_dict:
293
298
  form.add_error(None, err)
294
299
  else:
295
300
  # This serves to show errors in the form directly
@@ -299,6 +304,8 @@ class IPFabricTransformMapCloneView(BaseObjectView):
299
304
  else:
300
305
  form.add_error(None, error)
301
306
  if request.htmx:
307
+ viewname = get_viewname(self.queryset.model, action="clone")
308
+ form_url = reverse(viewname, kwargs={"pk": obj.pk})
302
309
  response = render(
303
310
  request,
304
311
  "ipfabric_netbox/inc/clone_form.html",
@@ -306,6 +313,7 @@ class IPFabricTransformMapCloneView(BaseObjectView):
306
313
  "form": form,
307
314
  "object": obj,
308
315
  "pk": pk,
316
+ "form_url": form_url,
309
317
  },
310
318
  )
311
319
  response["X-Debug-HTMX-Partial"] = "true"
@@ -316,6 +324,7 @@ class IPFabricTransformMapCloneView(BaseObjectView):
316
324
  {
317
325
  "form": form,
318
326
  "object": obj,
327
+ "pk": pk,
319
328
  },
320
329
  )
321
330
 
@@ -429,15 +438,13 @@ class IPFabricSnapshotDataBulkDeleteView(generic.BulkDeleteView):
429
438
  path="json",
430
439
  kwargs={},
431
440
  )
432
- class IPFabricSnapshotDataJSONView(LoginRequiredMixin, View):
441
+ class IPFabricSnapshotDataJSONView(generic.ObjectView):
442
+ queryset = IPFabricData.objects.all()
433
443
  template_name = "ipfabric_netbox/inc/json.html"
434
444
 
435
445
  def get(self, request, **kwargs):
436
- print(kwargs)
437
- # change_id = kwargs.get("change_pk", None)
438
-
446
+ data = get_object_or_404(IPFabricData, pk=kwargs.get("pk"))
439
447
  if request.htmx:
440
- data = get_object_or_404(IPFabricData, pk=kwargs.get("pk"))
441
448
  return render(
442
449
  request,
443
450
  self.template_name,
@@ -445,19 +452,13 @@ class IPFabricSnapshotDataJSONView(LoginRequiredMixin, View):
445
452
  "object": data,
446
453
  },
447
454
  )
448
-
449
- # return render(
450
- # request,
451
- # self.template_name,
452
- # {
453
- # "change": change,
454
- # "prechange_data": prechange_data,
455
- # "postchange_data": postchange_data,
456
- # "diff_added": diff_added,
457
- # "diff_removed": diff_removed,
458
- # "size": "lg",
459
- # },
460
- # )
455
+ return render(
456
+ request,
457
+ self.template_name,
458
+ {
459
+ "object": data,
460
+ },
461
+ )
461
462
 
462
463
 
463
464
  # Source
@@ -498,7 +499,7 @@ class IPFabricSourceSyncView(BaseObjectView):
498
499
  queryset = IPFabricSource.objects.all()
499
500
 
500
501
  def get_required_permission(self):
501
- return "ipfabric_netbox.sync_source"
502
+ return "ipfabric_netbox.sync_ipfabricsource"
502
503
 
503
504
  def get(self, request, pk):
504
505
  ipfabricsource = get_object_or_404(self.queryset, pk=pk)
@@ -543,13 +544,13 @@ class IPFabricSyncEditView(generic.ObjectEditView):
543
544
  @register_model_view(IPFabricSync)
544
545
  class IPFabricSyncView(generic.ObjectView):
545
546
  queryset = IPFabricSync.objects.all()
546
- actions = ("edit",)
547
547
 
548
548
  def get(self, request, **kwargs):
549
- instance = self.get_object(**kwargs)
550
- last_ingestion = instance.ipfabricingestion_set.last()
551
-
549
+ # Handle HTMX requests separately
552
550
  if request.htmx:
551
+ instance = self.get_object(**kwargs)
552
+ last_ingestion = instance.ipfabricingestion_set.last()
553
+
553
554
  response = render(
554
555
  request,
555
556
  "ipfabric_netbox/partials/sync_last_ingestion.html",
@@ -564,15 +565,8 @@ class IPFabricSyncView(generic.ObjectView):
564
565
  response["HX-Refresh"] = "true"
565
566
  return response
566
567
 
567
- return render(
568
- request,
569
- self.get_template_name(),
570
- {
571
- "object": instance,
572
- "tab": self.tab,
573
- **self.get_extra_context(request, instance),
574
- },
575
- )
568
+ # For regular requests, use the parent method which includes actions
569
+ return super().get(request, **kwargs)
576
570
 
577
571
  def get_extra_context(self, request, instance):
578
572
  if request.GET.get("format") in ["json", "yaml"]:
@@ -594,7 +588,7 @@ class IPFabricStartSyncView(BaseObjectView):
594
588
  queryset = IPFabricSync.objects.all()
595
589
 
596
590
  def get_required_permission(self):
597
- return "ipfabric_netbox.start_sync"
591
+ return "ipfabric_netbox.start_ipfabricsync"
598
592
 
599
593
  def get(self, request, pk):
600
594
  ipfabric = get_object_or_404(self.queryset, pk=pk)
@@ -617,7 +611,7 @@ class IPFabricSyncDeleteView(generic.ObjectDeleteView):
617
611
  class IPFabricSyncBulkDeleteView(generic.BulkDeleteView):
618
612
  queryset = IPFabricSync.objects.all()
619
613
  filterset = IPFabricSnapshotFilterSet
620
- table = IPFabricSnapshotTable
614
+ table = IPFabricSyncTable
621
615
 
622
616
 
623
617
  @register_model_view(IPFabricSync, "transformmaps")
@@ -640,6 +634,26 @@ class IPFabricTransformMapTabView(generic.ObjectChildrenView):
640
634
  )
641
635
 
642
636
 
637
+ @register_model_view(IPFabricSync, "ingestion")
638
+ class IPFabricIngestionTabView(generic.ObjectChildrenView):
639
+ queryset = IPFabricSync.objects.all()
640
+ child_model = IPFabricIngestion
641
+ table = IPFabricIngestionTable
642
+ filterset = IPFabricIngestionFilterSet
643
+ tab = ViewTab(
644
+ label="Ingestions",
645
+ badge=lambda obj: IPFabricIngestion.objects.filter(sync=obj).count(),
646
+ permission="ipfabric_netbox.view_ipfabricingestion",
647
+ )
648
+
649
+ def get_children(self, request, parent):
650
+ return self.child_model.objects.filter(sync=parent).annotate(
651
+ description=models.F("branch__description"),
652
+ user=models.F("sync__user__username"),
653
+ staged_changes=models.Count(models.F("branch__changediff")),
654
+ )
655
+
656
+
643
657
  # Ingestion
644
658
  class IPFabricIngestionListView(generic.ObjectListView):
645
659
  queryset = IPFabricIngestion.objects.annotate(
@@ -691,13 +705,12 @@ class IPFabricIngestionLogView(LoginRequiredMixin, View):
691
705
 
692
706
  def get(self, request, **kwargs):
693
707
  ingestion_id = kwargs.get("pk")
708
+ ingestion = annotate_statistics(IPFabricIngestion.objects).get(pk=ingestion_id)
709
+ data = ingestion.get_statistics()
710
+ data["object"] = ingestion
711
+ data["job"] = ingestion.jobs.first()
712
+
694
713
  if request.htmx:
695
- ingestion = annotate_statistics(IPFabricIngestion.objects).get(
696
- pk=ingestion_id
697
- )
698
- data = ingestion.get_statistics()
699
- data["object"] = ingestion
700
- data["job"] = ingestion.jobs.first()
701
714
  response = render(
702
715
  request,
703
716
  self.template_name,
@@ -706,7 +719,7 @@ class IPFabricIngestionLogView(LoginRequiredMixin, View):
706
719
  if ingestion.job.completed:
707
720
  response["HX-Refresh"] = "true"
708
721
  return response
709
- return render(request, self.template_name)
722
+ return render(request, self.template_name, data)
710
723
 
711
724
 
712
725
  @register_model_view(IPFabricIngestion)
@@ -729,7 +742,7 @@ class IPFabricIngestionMergeView(BaseObjectView):
729
742
  form = IPFabricIngestionMergeForm
730
743
 
731
744
  def get_required_permission(self):
732
- return "ipfabric_netbox.merge_ingestion"
745
+ return "ipfabric_netbox.merge_ipfabricingestion"
733
746
 
734
747
  def get(self, request, pk):
735
748
  obj = get_object_or_404(self.queryset, pk=pk)
@@ -763,7 +776,16 @@ class IPFabricIngestionMergeView(BaseObjectView):
763
776
  )
764
777
  messages.success(request, f"Queued job #{job.pk} to sync {ingestion}")
765
778
  return redirect(ingestion.get_absolute_url())
766
- raise ValidationError("Form is not valid.")
779
+
780
+ # Handle invalid form - add form errors to messages and redirect back
781
+ for field, errors in form.errors.items():
782
+ for error in errors:
783
+ messages.error(request, f"{field}: {error}")
784
+ if form.non_field_errors():
785
+ for error in form.non_field_errors():
786
+ messages.error(request, error)
787
+
788
+ return redirect(ingestion.get_absolute_url())
767
789
 
768
790
 
769
791
  @register_model_view(
@@ -775,8 +797,10 @@ class IPFabricIngestionMergeView(BaseObjectView):
775
797
  class IPFabricIngestionChangesDiffView(LoginRequiredMixin, View):
776
798
  template_name = "ipfabric_netbox/inc/diff.html"
777
799
 
778
- def get(self, request, model, **kwargs):
779
- def _return_empty():
800
+ def get(self, request, **kwargs):
801
+ change_id = kwargs.get("change_pk", None)
802
+
803
+ if not request.htmx or not change_id:
780
804
  return render(
781
805
  request,
782
806
  self.template_name,
@@ -790,13 +814,6 @@ class IPFabricIngestionChangesDiffView(LoginRequiredMixin, View):
790
814
  },
791
815
  )
792
816
 
793
- change_id = kwargs.get("change_pk", None)
794
-
795
- if not request.htmx:
796
- return _return_empty()
797
- if not change_id:
798
- return _return_empty()
799
-
800
817
  change = ChangeDiff.objects.get(pk=change_id)
801
818
  if change.original and change.modified:
802
819
  diff_added = shallow_compare_dict(
@@ -866,34 +883,15 @@ class IPFabricIngestionDeleteView(generic.ObjectDeleteView):
866
883
  queryset = IPFabricIngestion.objects.all()
867
884
 
868
885
 
869
- @register_model_view(IPFabricSync, "ingestion")
870
- class IPFabricIngestionTabView(generic.ObjectChildrenView):
871
- queryset = IPFabricSync.objects.all()
872
- child_model = IPFabricIngestion
873
- table = IPFabricIngestionTable
874
- filterset = IPFabricIngestionFilterSet
875
- template_name = "generic/object_children.html"
876
- tab = ViewTab(
877
- label="Ingestions",
878
- badge=lambda obj: IPFabricIngestion.objects.filter(sync=obj).count(),
879
- permission="ipfabric_netbox.view_ipfabricingestion",
880
- )
881
-
882
- def get_children(self, request, parent):
883
- return self.child_model.objects.filter(sync=parent).annotate(
884
- description=models.F("branch__description"),
885
- user=models.F("sync__user__username"),
886
- staged_changes=models.Count(models.F("branch__changediff")),
887
- )
888
-
889
-
890
886
  @register_model_view(Device, "ipfabric")
891
- class IPFabricTable(View):
887
+ class IPFabricTable(generic.ObjectView):
892
888
  template_name = "ipfabric_netbox/ipfabric_table.html"
893
889
  tab = ViewTab("IP Fabric", permission="ipfabric_netbox.view_devicetable")
890
+ queryset = Device.objects.all()
894
891
 
895
- def get(self, request, pk):
896
- device = get_object_or_404(Device, pk=pk)
892
+ def get_extra_context(self, request, instance):
893
+ """Process form and prepare table data for the template."""
894
+ device = instance
897
895
  form = (
898
896
  IPFabricTableForm(request.GET)
899
897
  if "table" in request.GET
@@ -901,43 +899,48 @@ class IPFabricTable(View):
901
899
  )
902
900
  restrict_form_fields(form, request.user)
903
901
  data = None
902
+ source = None
904
903
 
905
904
  if form.is_valid():
906
- table = form.cleaned_data["table"]
905
+ table_name = form.cleaned_data["table"]
907
906
  test = {
908
907
  "True": True,
909
908
  "False": False,
910
909
  }
911
910
  cache_enable = test.get(form.cleaned_data["cache_enable"])
912
- snapshot_id = ""
911
+ source = form.cleaned_data.get("source")
913
912
 
914
913
  if not form.cleaned_data["snapshot_data"]:
915
914
  snapshot_id = "$last"
916
- source = IPFabricSource.objects.get(
917
- pk=device.custom_field_data["ipfabric_source"]
915
+ source = (
916
+ source
917
+ or IPFabricSource.objects.filter(
918
+ pk=device.custom_field_data.get("ipfabric_source")
919
+ ).first()
920
+ or IPFabricSource.get_for_site(device.site).first()
918
921
  )
919
-
920
922
  else:
921
923
  snapshot_id = form.cleaned_data["snapshot_data"].snapshot_id
922
- source = form.cleaned_data["snapshot_data"].source
923
-
924
- source.parameters["snapshot_id"] = snapshot_id
925
- source.parameters["base_url"] = source.url
926
-
927
- cache_key = (
928
- f"ipfabric_{table}_{device.serial}_{source.parameters['snapshot_id']}"
929
- )
930
- if cache_enable:
931
- data = cache.get(cache_key)
932
-
933
- if not data:
934
- try:
935
- ipf = IPFabric(parameters=source.parameters)
936
- raw_data, columns = ipf.get_table_data(table=table, device=device)
937
- data = {"data": raw_data, "columns": columns}
938
- cache.set(cache_key, data, 60 * 60 * 24)
939
- except Exception as e:
940
- messages.error(request, e)
924
+ source = source or form.cleaned_data["snapshot_data"].source
925
+
926
+ if source is not None:
927
+ source.parameters["snapshot_id"] = snapshot_id
928
+ source.parameters["base_url"] = source.url
929
+
930
+ cache_key = f"ipfabric_{table_name}_{device.serial}_{source.parameters['snapshot_id']}"
931
+ if cache_enable:
932
+ data = cache.get(cache_key)
933
+
934
+ if not data:
935
+ try:
936
+ ipf = IPFabric(parameters=source.parameters)
937
+ raw_data, columns = ipf.get_table_data(
938
+ table=table_name, device=device
939
+ )
940
+ data = {"data": raw_data, "columns": columns}
941
+ cache.set(cache_key, data, 60 * 60 * 24)
942
+ except Exception as e:
943
+ messages.error(request, e)
941
944
 
942
945
  if not data:
943
946
  data = {"data": [], "columns": []}
@@ -952,31 +955,34 @@ class IPFabricTable(View):
952
955
  },
953
956
  ).configure(table)
954
957
 
958
+ if not source:
959
+ if source_id := device.custom_field_data.get("ipfabric_source"):
960
+ source = IPFabricSource.objects.filter(pk=source_id).first()
961
+ else:
962
+ source = IPFabricSource.get_for_site(device.site).first()
963
+
964
+ return {
965
+ "source": source,
966
+ "form": form,
967
+ "table": table,
968
+ }
969
+
970
+ def get(self, request, **kwargs):
971
+ """Handle GET requests, with special handling for HTMX table updates."""
972
+ # For HTMX requests, we only need to return the table HTML
955
973
  if request.htmx:
974
+ device = get_object_or_404(Device, pk=kwargs.get("pk"))
975
+ context = self.get_extra_context(request, device)
956
976
  return render(
957
977
  request,
958
978
  "htmx/table.html",
959
979
  {
960
- "table": table,
980
+ "table": context["table"],
961
981
  },
962
982
  )
963
983
 
964
- source = None
965
-
966
- if source_id := device.custom_field_data["ipfabric_source"]:
967
- source = IPFabricSource.objects.get(pk=source_id)
968
-
969
- return render(
970
- request,
971
- self.template_name,
972
- {
973
- "object": device,
974
- "source": source,
975
- "tab": self.tab,
976
- "form": form,
977
- "table": table,
978
- },
979
- )
984
+ # For regular requests, use the parent's get() method which will call get_extra_context()
985
+ return super().get(request, **kwargs)
980
986
 
981
987
 
982
988
  @register_model_view(
@@ -1047,4 +1053,12 @@ class IPFabricSourceTopology(LoginRequiredMixin, View):
1047
1053
  "error": error,
1048
1054
  },
1049
1055
  )
1050
- return None
1056
+ return render(
1057
+ request,
1058
+ self.template_name,
1059
+ {
1060
+ "site": site,
1061
+ "size": "xl",
1062
+ "time": timezone.now(),
1063
+ },
1064
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipfabric_netbox
3
- Version: 4.2.2b2
3
+ Version: 4.2.2b4
4
4
  Summary: NetBox plugin to sync IP Fabric data into NetBox
5
5
  License: MIT
6
6
  Keywords: netbox,ipfabric,plugin,sync
@@ -24,7 +24,7 @@ Requires-Dist: ipfabric (>=6.6.4) ; extra != "ipfabric_6_10" and extra != "ipfab
24
24
  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"
25
25
  Requires-Dist: ipfabric (>=7.2.0,<7.3.0) ; extra != "ipfabric_6_10" and extra != "ipfabric_7_0" and extra == "ipfabric_7_2" and extra != "ipfabric_7_3"
26
26
  Requires-Dist: ipfabric (>=7.3.0,<7.4.0) ; extra != "ipfabric_6_10" and extra != "ipfabric_7_0" and extra != "ipfabric_7_2" and extra == "ipfabric_7_3"
27
- Requires-Dist: netboxlabs-netbox-branching (>=0.5.5,<0.6.0)
27
+ Requires-Dist: netboxlabs-netbox-branching (==0.7.0)
28
28
  Requires-Dist: netutils
29
29
  Project-URL: Bug Tracker, https://gitlab.com/ip-fabric/integrations/ipfabric-netbox-sync/-/issues
30
30
  Project-URL: Homepage, https://gitlab.com/ip-fabric/integrations/ipfabric-netbox-sync
@@ -65,7 +65,9 @@ These are the required NetBox versions for corresponding plugin version. Any oth
65
65
 
66
66
  | Netbox Version | Plugin Version |
67
67
  |----------------|----------------|
68
- | 4.3.0 and up | 4.0.0 and up |
68
+ | 4.4.0 and up | 4.3.0 and up |
69
+ | 4.3.0 - 4.3.7 | 4.2.2 |
70
+ | 4.3.0 - 4.3.6 | 4.0.0 - 4.2.1 |
69
71
  | 4.2.4 - 4.2.9 | 3.2.2 - 3.2.4 |
70
72
  | 4.2.0 - 4.2.3 | 3.2.0 |
71
73
  | 4.1.5 - 4.1.11 | 3.1.1 - 3.1.3 |