ipfabric_netbox 4.2.1b3__py3-none-any.whl → 4.2.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 (30) hide show
  1. ipfabric_netbox/__init__.py +1 -1
  2. ipfabric_netbox/api/__init__.py +1 -0
  3. ipfabric_netbox/api/nested_serializers.py +78 -0
  4. ipfabric_netbox/api/serializers.py +90 -147
  5. ipfabric_netbox/api/urls.py +4 -4
  6. ipfabric_netbox/api/views.py +19 -18
  7. ipfabric_netbox/choices.py +12 -0
  8. ipfabric_netbox/filtersets.py +4 -67
  9. ipfabric_netbox/forms.py +140 -99
  10. ipfabric_netbox/models.py +10 -10
  11. ipfabric_netbox/tables.py +9 -30
  12. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +1 -1
  13. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync_list.html +71 -0
  14. ipfabric_netbox/tests/test_models.py +11 -47
  15. ipfabric_netbox/utilities/ipfutils.py +23 -43
  16. ipfabric_netbox/views.py +13 -17
  17. {ipfabric_netbox-4.2.1b3.dist-info → ipfabric_netbox-4.2.2.dist-info}/METADATA +6 -6
  18. {ipfabric_netbox-4.2.1b3.dist-info → ipfabric_netbox-4.2.2.dist-info}/RECORD +19 -28
  19. ipfabric_netbox/graphql/__init__.py +0 -23
  20. ipfabric_netbox/graphql/enums.py +0 -35
  21. ipfabric_netbox/graphql/filters.py +0 -317
  22. ipfabric_netbox/graphql/schema.py +0 -101
  23. ipfabric_netbox/graphql/types.py +0 -216
  24. ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +0 -31
  25. ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +0 -17
  26. ipfabric_netbox/migrations/0018_remove_type_field.py +0 -17
  27. ipfabric_netbox/tests/api/__init__.py +0 -0
  28. ipfabric_netbox/tests/api/test_api.py +0 -879
  29. ipfabric_netbox/tests/test_forms.py +0 -1440
  30. {ipfabric_netbox-4.2.1b3.dist-info → ipfabric_netbox-4.2.2.dist-info}/WHEEL +0 -0
ipfabric_netbox/forms.py CHANGED
@@ -46,6 +46,22 @@ exclude_fields = [
46
46
  "status",
47
47
  ]
48
48
 
49
+
50
+ class IPFSiteChoiceField(forms.MultipleChoiceField):
51
+ def valid_value(self, value):
52
+ """Check to see if the provided value is a valid choice."""
53
+ text_value = str(value)
54
+ for k, v in self.choices:
55
+ if isinstance(v, (list, tuple)):
56
+ for k2, v2 in v:
57
+ if value == k2 or text_value == str(k2):
58
+ return True
59
+ else:
60
+ if value == k or text_value == str(k):
61
+ return True
62
+ return False
63
+
64
+
49
65
  dcim_parameters = {
50
66
  "site": forms.BooleanField(required=False, label=_("Sites"), initial=True),
51
67
  "manufacturer": forms.BooleanField(
@@ -62,45 +78,43 @@ dcim_parameters = {
62
78
  "virtualchassis": forms.BooleanField(
63
79
  required=False, label=_("Virtual Chassis"), initial=True
64
80
  ),
65
- "interface": forms.BooleanField(
66
- required=False, label=_("Interfaces"), initial=False
67
- ),
68
- "macaddress": forms.BooleanField(
69
- required=False, label=_("MAC Addresses"), initial=False
70
- ),
71
- "inventoryitem": forms.BooleanField(
72
- required=False, label=_("Part Numbers"), initial=False
73
- ),
81
+ "interface": forms.BooleanField(required=False, label=_("Interfaces")),
82
+ "macaddress": forms.BooleanField(required=False, label=_("MAC Addresses")),
83
+ "inventoryitem": forms.BooleanField(required=False, label=_("Part Numbers")),
74
84
  }
75
85
  ipam_parameters = {
76
- "vlan": forms.BooleanField(required=False, label=_("VLANs"), initial=False),
77
- "vrf": forms.BooleanField(required=False, label=_("VRFs"), initial=False),
78
- "prefix": forms.BooleanField(required=False, label=_("Prefixes"), initial=False),
79
- "ipaddress": forms.BooleanField(
80
- required=False, label=_("IP Addresses"), initial=False
81
- ),
86
+ "vlan": forms.BooleanField(required=False, label=_("VLANs")),
87
+ "vrf": forms.BooleanField(required=False, label=_("VRFs")),
88
+ "prefix": forms.BooleanField(required=False, label=_("Prefixes")),
89
+ "ipaddress": forms.BooleanField(required=False, label=_("IP Addresses")),
82
90
  }
83
91
  sync_parameters = {"dcim": dcim_parameters, "ipam": ipam_parameters}
84
92
 
85
93
 
86
- def source_column_choices(model: str) -> list[tuple[str, str]]:
94
+ def source_column_choices(model):
87
95
  columns = transform_field_source_columns.get(model, None)
88
96
  if columns:
89
97
  choices = [(f, f) for f in transform_field_source_columns.get(model)]
90
98
  else:
91
- # This should never happen, but better be safe than sorry
92
- choices = [] # pragma: no cover
99
+ choices = []
93
100
  return choices
94
101
 
95
102
 
96
- def str_to_list(_str: str | list) -> list[str]:
97
- if not isinstance(_str, list):
98
- return [_str]
103
+ def add_all_sites(choices):
104
+ """
105
+ Add a blank choice to the beginning of a choices list.
106
+ """
107
+ return ((None, "All Sites"),) + tuple(choices)
108
+
109
+
110
+ def str_to_list(str):
111
+ if not isinstance(str, list):
112
+ return [str]
99
113
  else:
100
- return _str
114
+ return str
101
115
 
102
116
 
103
- def list_to_choices(choices: list[str]) -> tuple[tuple[str, str], ...]:
117
+ def list_to_choices(choices):
104
118
  new_choices = ()
105
119
  for choice in choices:
106
120
  new_choices = new_choices + ((choice, choice),)
@@ -162,7 +176,7 @@ class IPFabricRelationshipFieldForm(NetBoxModelForm):
162
176
  )
163
177
  self.fields["target_field"].widget.initial = self.instance.target_field
164
178
  else:
165
- if kwargs.get("initial", {}).get("transform_map", None):
179
+ if kwargs["initial"].get("transform_map", None):
166
180
  transform_map_id = kwargs["initial"]["transform_map"]
167
181
  transform_map = IPFabricTransformMap.objects.get(
168
182
  pk=transform_map_id
@@ -244,7 +258,7 @@ class IPFabricTransformFieldForm(NetBoxModelForm):
244
258
  source_column_choices(source_fields)
245
259
  )
246
260
  else:
247
- if kwargs.get("initial", {}).get("transform_map", None):
261
+ if kwargs["initial"].get("transform_map", None):
248
262
  transform_map_id = kwargs["initial"]["transform_map"]
249
263
  transform_map = IPFabricTransformMap.objects.get(
250
264
  pk=transform_map_id
@@ -361,29 +375,22 @@ class IPFabricSourceForm(NetBoxModelForm):
361
375
  "type": HTMXSelect(),
362
376
  }
363
377
 
378
+ @property
379
+ def fieldsets(self):
380
+ fieldsets = [
381
+ FieldSet("name", "type", "url", name=_("Source")),
382
+ FieldSet("timeout", name=_("Parameters")),
383
+ ]
384
+
385
+ if self.source_type == "local":
386
+ fieldsets[1] = FieldSet("auth", "verify", "timeout", name=_("Parameters"))
387
+
388
+ return fieldsets
389
+
364
390
  def __init__(self, *args, **kwargs):
365
391
  super().__init__(*args, **kwargs)
366
392
  self.source_type = get_field_value(self, "type")
367
393
 
368
- # Set fieldsets dynamically based on source_type
369
- self.fieldsets: list[FieldSet] = []
370
- self.fieldsets.append(FieldSet("name", "type", "url", name=_("Source")))
371
- if self.source_type == "local":
372
- self.fieldsets.append(
373
- FieldSet("auth", "verify", "timeout", name=_("Parameters"))
374
- )
375
- else:
376
- self.fieldsets.append(FieldSet("timeout", name=_("Parameters")))
377
-
378
- self.fields["url"] = forms.URLField(
379
- required=True,
380
- label=_("Base URL"),
381
- widget=forms.TextInput(attrs={"class": "form-control"}),
382
- help_text=_(
383
- "for example https://myinstance.com or https://192.168.0.1 etc."
384
- ),
385
- )
386
-
387
394
  self.fields["timeout"] = forms.IntegerField(
388
395
  required=False,
389
396
  label=_("Timeout"),
@@ -444,9 +451,6 @@ class OrderedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
444
451
 
445
452
  def clean(self, value):
446
453
  qs = super().clean(value)
447
- # Handle None or empty values
448
- if not value:
449
- return qs
450
454
  clauses = " ".join(
451
455
  ["WHEN id=%s THEN %s" % (pk, i) for i, pk in enumerate(value)]
452
456
  )
@@ -484,18 +488,11 @@ class IPFabricSyncForm(NetBoxModelForm):
484
488
  sites = forms.MultipleChoiceField(
485
489
  required=False,
486
490
  label=_("Sites"),
487
- help_text=_("Defaults to all sites if none selected."),
488
491
  widget=APISelectMultiple(
489
492
  api_url="/api/plugins/ipfabric/snapshot/{{snapshot_data}}/sites/",
490
493
  ),
491
494
  )
492
495
 
493
- update_custom_fields = forms.BooleanField(
494
- required=False,
495
- label=_("Custom Fields Updating"),
496
- help_text=_("Update object custom fields where applicable."),
497
- )
498
-
499
496
  scheduled = forms.DateTimeField(
500
497
  required=False,
501
498
  widget=DateTimePicker(),
@@ -522,13 +519,41 @@ class IPFabricSyncForm(NetBoxModelForm):
522
519
  "source",
523
520
  "snapshot_data",
524
521
  "auto_merge",
525
- "update_custom_fields",
526
522
  "sites",
523
+ "type",
527
524
  "tags",
528
525
  "scheduled",
529
526
  "interval",
530
527
  )
531
- widgets = {"source": HTMXSelect()}
528
+ widgets = {
529
+ "source": HTMXSelect(),
530
+ "type": HTMXSelect(),
531
+ }
532
+
533
+ @property
534
+ def fieldsets(self):
535
+ fieldsets = [
536
+ FieldSet("name", "source", "groups", name=_("IP Fabric Source")),
537
+ ]
538
+ if self.source_type == "local":
539
+ fieldsets.append(
540
+ FieldSet("snapshot_data", "sites", name=_("Snapshot Information")),
541
+ )
542
+ else:
543
+ fieldsets.append(
544
+ FieldSet("snapshot_data", name=_("Snapshot Information")),
545
+ )
546
+ fieldsets.append(FieldSet("type", name=_("Ingestion Type")))
547
+ if self.backend_fields:
548
+ for k, v in self.backend_fields.items():
549
+ fieldsets.append(FieldSet(*v, name=f"{k.upper()} Parameters"))
550
+ fieldsets.append(
551
+ FieldSet("scheduled", "interval", name=_("Ingestion Execution Parameters"))
552
+ )
553
+ fieldsets.append(FieldSet("auto_merge", name=_("Extras")))
554
+ fieldsets.append(FieldSet("tags", name=_("Tags")))
555
+
556
+ return fieldsets
532
557
 
533
558
  def __init__(self, *args, **kwargs):
534
559
  super().__init__(*args, **kwargs)
@@ -560,13 +585,20 @@ class IPFabricSyncForm(NetBoxModelForm):
560
585
  self.initial["sites"] = self.instance.parameters.get("sites", [])
561
586
  self.initial["groups"] = self.instance.parameters.get("groups", [])
562
587
 
588
+ backend_type = get_field_value(self, "type")
589
+ backend = {}
590
+ if backend_type == "all":
591
+ backend = sync_parameters
592
+ else:
593
+ backend[backend_type] = sync_parameters.get(backend_type)
594
+
563
595
  now = local_now().strftime("%Y-%m-%d %H:%M:%S")
564
596
  self.fields["scheduled"].help_text += f" (current time: <strong>{now}</strong>)"
565
597
 
566
598
  # Add backend-specific form fields
567
599
  self.backend_fields = {}
568
600
 
569
- for k, v in sync_parameters.items():
601
+ for k, v in backend.items():
570
602
  self.backend_fields[k] = []
571
603
  for name, form_field in v.items():
572
604
  field_name = f"ipf_{name}"
@@ -575,52 +607,17 @@ class IPFabricSyncForm(NetBoxModelForm):
575
607
  if self.instance and self.instance.parameters:
576
608
  self.fields[field_name].initial = self.instance.parameters.get(name)
577
609
 
578
- # Set fieldsets dynamically based and backend_fields
579
- fieldsets = [
580
- FieldSet("name", "source", "groups", name=_("IP Fabric Source")),
581
- ]
582
- if self.source_type == "local":
583
- fieldsets.append(
584
- FieldSet("snapshot_data", "sites", name=_("Snapshot Information")),
585
- )
586
- else:
587
- fieldsets.append(
588
- FieldSet("snapshot_data", name=_("Snapshot Information")),
589
- )
590
- for k, v in self.backend_fields.items():
591
- fieldsets.append(FieldSet(*v, name=f"{k.upper()} Parameters"))
592
- fieldsets.append(
593
- FieldSet("scheduled", "interval", name=_("Ingestion Execution Parameters"))
594
- )
595
- fieldsets.append(
596
- FieldSet("auto_merge", "update_custom_fields", name=_("Extras"))
597
- )
598
- fieldsets.append(FieldSet("tags", name=_("Tags")))
599
-
600
- self.fieldsets = fieldsets
601
-
602
610
  def clean(self):
603
611
  super().clean()
612
+ snapshot = self.cleaned_data["snapshot_data"]
604
613
 
605
614
  sites = self.data.get("sites")
606
- self.fields["sites"].choices = list_to_choices(str_to_list(sites))
607
- if sites and "snapshot_data" in self.cleaned_data:
608
- snapshot = self.cleaned_data["snapshot_data"]
609
- # Check if all sites are valid - fail if any site is not found in snapshot.sites
610
- if not all(
611
- any(site in snapshot_site for snapshot_site in snapshot.sites)
612
- for site in sites
613
- ):
614
- invalid_sites = [
615
- site
616
- for site in sites
617
- if not any(
618
- site in snapshot_site for snapshot_site in snapshot.sites
619
- )
620
- ]
621
- raise ValidationError(
622
- {"sites": f"Sites {invalid_sites} not part of the snapshot."}
623
- )
615
+ choices = list_to_choices(str_to_list(sites))
616
+ self.fields["sites"].choices = choices
617
+
618
+ if sites:
619
+ if not any(y in x for x in snapshot.sites for y in sites):
620
+ raise ValidationError({"sites": f"{sites} not part of the snapshot."})
624
621
 
625
622
  scheduled_time = self.cleaned_data.get("scheduled")
626
623
  if scheduled_time and scheduled_time < local_now():
@@ -668,6 +665,50 @@ class IPFabricSyncForm(NetBoxModelForm):
668
665
  return object
669
666
 
670
667
 
668
+ # class SyncForm(forms.Form):
669
+ # def __init__(self, *args, **kwargs):
670
+ # self.snapshots = kwargs.pop("snapshot_choices", None)
671
+ # self.sites = kwargs.pop("site_choices", None)
672
+ # super(SyncForm, self).__init__(*args, **kwargs)
673
+ # if self.snapshots:
674
+ # snapshot_choices = [
675
+ # (snapshot_id, snapshot_name)
676
+ # for snapshot_name, snapshot_id in self.snapshots.values()
677
+ # ]
678
+ # self.fields["snapshot"] = forms.ChoiceField(
679
+ # label="Snapshot",
680
+ # required=True,
681
+ # choices=snapshot_choices,
682
+ # help_text="IPFabric snapshot to sync from. Defaults to $last",
683
+ # widget=forms.Select(
684
+ # attrs={
685
+ # "hx-get": reverse("plugins:ipfabric_netbox:ipfabricsync_add"),
686
+ # "hx-trigger": "change",
687
+ # "hx-target": "#modules",
688
+ # "class": "form-control",
689
+ # }
690
+ # ),
691
+ # )
692
+ # if self.sites:
693
+ # site_choices = [(site, site) for site in self.sites]
694
+ # self.fields["site"] = forms.ChoiceField(
695
+ # label="Site",
696
+ # required=False,
697
+ # choices=add_blank_choice(site_choices),
698
+ # help_text="Sites available within snapshot",
699
+ # widget=forms.Select(attrs={"class": "form-control"}),
700
+ # )
701
+ # else:
702
+ # self.fields["site"] = forms.ChoiceField(
703
+ # label="Site",
704
+ # required=False,
705
+ # choices=add_blank_choice([]),
706
+ # help_text="Sites available within snapshot",
707
+ # widget=forms.Select(
708
+ # attrs={"class": "form-control", "disabled": "disabled"}
709
+ # ),
710
+ # )
711
+
671
712
  tableChoices = [
672
713
  ("eol_details", "Inventory - EOL_DETAILS"),
673
714
  ("fans", "Inventory - FANS"),
ipfabric_netbox/models.py CHANGED
@@ -44,6 +44,7 @@ from utilities.request import NetBoxFakeRequest
44
44
  from .choices import IPFabricRawDataTypeChoices
45
45
  from .choices import IPFabricSnapshotStatusModelChoices
46
46
  from .choices import IPFabricSourceTypeChoices
47
+ from .choices import IPFabricSyncTypeChoices
47
48
  from .choices import IPFabricTransformMapSourceModelChoices
48
49
  from .choices import required_transform_map_contenttypes
49
50
  from .signals import clear_other_primary_ip
@@ -544,7 +545,9 @@ class IPFabricSource(IPFabricClient, JobsMixin, PrimaryModel):
544
545
  # post_sync.send(sender=self.__class__, instance=self)
545
546
 
546
547
 
547
- class IPFabricSnapshot(TagsMixin, ChangeLoggedModel):
548
+ class IPFabricSnapshot(models.Model):
549
+ created = models.DateTimeField(auto_now_add=True)
550
+ last_updated = models.DateTimeField(editable=False)
548
551
  source = models.ForeignKey(
549
552
  to=IPFabricSource,
550
553
  on_delete=models.CASCADE,
@@ -597,6 +600,11 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
597
600
  on_delete=models.CASCADE,
598
601
  related_name="snapshots",
599
602
  )
603
+ type = models.CharField(
604
+ max_length=50,
605
+ choices=IPFabricSyncTypeChoices,
606
+ default=IPFabricSyncTypeChoices.DCIM,
607
+ )
600
608
  status = models.CharField(
601
609
  max_length=50,
602
610
  choices=DataSourceStatusChoices,
@@ -605,7 +613,6 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
605
613
  )
606
614
  parameters = models.JSONField(blank=True, null=True)
607
615
  auto_merge = models.BooleanField(default=False)
608
- update_custom_fields = models.BooleanField(default=True)
609
616
  last_synced = models.DateTimeField(blank=True, null=True, editable=False)
610
617
  scheduled = models.DateTimeField(null=True, blank=True)
611
618
  interval = models.PositiveIntegerField(
@@ -661,10 +668,6 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
661
668
  else:
662
669
  return False
663
670
 
664
- @property
665
- def last_ingestion(self):
666
- return self.ipfabricingestion_set.last()
667
-
668
671
  @staticmethod
669
672
  def get_transform_maps(group_ids=None):
670
673
  """
@@ -886,10 +889,7 @@ class IPFabricIngestion(JobsMixin, models.Model):
886
889
  def name(self):
887
890
  if self.branch:
888
891
  return self.branch.name
889
- try:
890
- return f"{self.sync.name} (Ingestion {self.pk})"
891
- except IPFabricIngestion.sync.RelatedObjectDoesNotExist:
892
- return f"Ingestion {self.pk} (No Sync)"
892
+ return f"{self.sync.name} (Ingestion {self.pk})"
893
893
 
894
894
  def get_absolute_url(self):
895
895
  return reverse("plugins:ipfabric_netbox:ipfabricingestion", args=[self.pk])
ipfabric_netbox/tables.py CHANGED
@@ -142,41 +142,20 @@ class IPFabricSourceTable(NetBoxTable):
142
142
  default_columns = ("pk", "name", "status", "description", "snapshot_count")
143
143
 
144
144
 
145
- class IPFabricSyncTable(NetBoxTable):
146
- name = tables.Column(linkify=True)
145
+ class SyncTable(NetBoxTable):
146
+ actions = None
147
147
  status = columns.ChoiceFieldColumn()
148
148
  snapshot_name = tables.Column(
149
- verbose_name="Snapshot Name",
150
- accessor="snapshot_data",
151
- linkify=True,
152
- )
153
- last_ingestion = tables.Column(
154
- accessor="last_ingestion",
155
- verbose_name="Last Ingestion",
156
- linkify=True,
149
+ verbose_name="Snapshot Name", accessor="snapshot_data"
157
150
  )
158
151
 
159
- def render_last_ingestion(self, value: IPFabricIngestion):
160
- return getattr(value, "name", "---") if value else "---"
161
-
162
- def render_snapshot_name(self, value: IPFabricSnapshot):
163
- return getattr(value, "name", "---") if value else "---"
152
+ def render_snapshot_name(self, value):
153
+ return value.get("name", "---")
164
154
 
165
155
  class Meta(NetBoxTable.Meta):
166
156
  model = IPFabricSync
167
- fields = (
168
- "auto_merge",
169
- "id",
170
- "interval",
171
- "last_synced",
172
- "last_ingestion",
173
- "name",
174
- "scheduled",
175
- "status",
176
- "snapshot_name",
177
- "user",
178
- )
179
- default_columns = ("name", "status", "last_ingestion", "snapshot_name")
157
+ fields = ("id", "status", "snapshot_name")
158
+ default_columns = ("id", "status", "snapshot_name")
180
159
 
181
160
 
182
161
  class IPFabricIngestionChangesTable(NetBoxTable):
@@ -262,5 +241,5 @@ class IPFabricDataTable(NetBoxTable):
262
241
 
263
242
  class Meta(NetBoxTable.Meta):
264
243
  model = IPFabricData
265
- fields = ("snapshot_data", "JSON")
266
- default_columns = ("snapshot_data", "JSON")
244
+ fields = ("snapshot_data", "type", "JSON")
245
+ default_columns = ("snapshot_data", "type", "JSON")
@@ -52,7 +52,7 @@
52
52
  <td>{{ object.description|placeholder }}</td>
53
53
  </tr>
54
54
  <tr>
55
- <th scope="row">Base URL</th>
55
+ <th scope="row">URL</th>
56
56
  <td>
57
57
  <a href="{{ object.url }}">{{ object.url }}</a>
58
58
  </td>
@@ -0,0 +1,71 @@
1
+ {% extends 'base/layout.html' %}
2
+ {% load buttons %}
3
+ {% load helpers %}
4
+ {% load perms %}
5
+
6
+ {% block title %}Ingestion{% endblock %}
7
+
8
+ {% block tabs %}
9
+ <ul class="nav nav-tabs px-3">
10
+ <li class="nav-item" role="presentation">
11
+ <a class="nav-link active" role="tab">Ingestion</a>
12
+ </li>
13
+ </ul>
14
+ {% endblock tabs %}
15
+
16
+ {% block controls %}
17
+ <div class="controls">
18
+ <div class="control-group">
19
+ {% block extra_controls %}{% endblock %}
20
+ {% add_button model %}
21
+ </div>
22
+ </div>
23
+ {% endblock controls %}
24
+
25
+ {% block content %}
26
+ <div class="tab-content">
27
+ {% for sync in syncs %}
28
+ <div class="card">
29
+ <h5 class="card-header d-flex justify-content-between" id="module{{ module.pk }}">
30
+ <div>
31
+ <i class="mdi mdi-cloud-sync"></i><i class="mdi mdi-sync"></i> <a href="{% url 'plugins:ipfabric_netbox:ipfabricsync' pk=sync.pk %}">{{ sync.name}}</a>
32
+ </div>
33
+ {% if perms.ipfabric_netbox.delete_ipfabricsync %}
34
+ <a href="{% url 'plugins:ipfabric_netbox:ipfabricsync_delete' pk=sync.pk %}" class="btn btn-danger btn-sm">
35
+ <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
36
+ </a>
37
+ {% endif %}
38
+
39
+ </h5>
40
+ <div class="card-body">
41
+ {% include 'inc/sync_warning.html' with object=module %}
42
+ <table class="table table-hover table-headings reports">
43
+ <thead>
44
+ <tr>
45
+ <th width="200">Source</th>
46
+ <th width="400">Snapshot</th>
47
+ <th>Status</th>
48
+ <th>Last Run</th>
49
+ </tr>
50
+ </thead>
51
+ <tbody>
52
+ <tr>
53
+ <td><a href="{% url 'plugins:ipfabric_netbox:ipfabricsource' pk=sync.snapshot_data.source.pk %}">{{ sync.snapshot_data.source.name }}</a></td>
54
+ <td><a href="{% url 'plugins:ipfabric_netbox:ipfabricsnapshot' pk=sync.snapshot_data.pk %}">{{ sync.snapshot_data.name}}</a></td>
55
+ <td>{% badge sync.get_status_display last_job.get_status_color %}</td>
56
+ <td>{{sync.last_synced}}</td>
57
+ </tr>
58
+ </tbody>
59
+ </table>
60
+ </div>
61
+ </div>
62
+ {% empty %}
63
+ <div class="alert alert-info" role="alert">
64
+ <h4 class="alert-heading">Sync Jobs Settings Found</h4>
65
+ {% if perms.extras.add_reportmodule %}
66
+ Get started by <a href="{% url 'plugins:ipfabric_netbox:ipfabricsync_add' %}">creating a sync</a> from an IP Fabric source.
67
+ {% endif %}
68
+ </div>
69
+ {% endfor %}
70
+ </div>
71
+ {% endblock content %}
@@ -8,7 +8,6 @@ from django.test import TestCase
8
8
  from django.utils import timezone
9
9
 
10
10
  from ipfabric_netbox.choices import IPFabricSnapshotStatusModelChoices
11
- from ipfabric_netbox.models import IPFabricIngestion
12
11
  from ipfabric_netbox.models import IPFabricSnapshot
13
12
  from ipfabric_netbox.models import IPFabricSource
14
13
  from ipfabric_netbox.models import IPFabricSync
@@ -119,9 +118,9 @@ class IPFabricTransformMapModelTestCase(TestCase):
119
118
  )
120
119
  sync = IPFabricSync.objects.create(
121
120
  name="ingest",
121
+ type="dcim",
122
122
  status="new",
123
123
  snapshot_data=snapshot,
124
- update_custom_fields=True,
125
124
  parameters={
126
125
  "vrf": False,
127
126
  "site": True,
@@ -139,8 +138,6 @@ class IPFabricTransformMapModelTestCase(TestCase):
139
138
  },
140
139
  )
141
140
 
142
- ingestion = IPFabricIngestion.objects.create(sync=sync)
143
-
144
141
  runner = IPFabricSyncRunner(
145
142
  settings={
146
143
  "site": True,
@@ -155,11 +152,7 @@ class IPFabricTransformMapModelTestCase(TestCase):
155
152
  "snapshot_id": "12dd8c61-129c-431a-b98b-4c9211571f89",
156
153
  },
157
154
  sync=sync,
158
- ingestion=ingestion,
159
155
  )
160
- # Need to monkeypatch since we are not in active Branch (different schema)
161
- # Using default schema "default" here since we are in "test_netbox" DB
162
- runner.get_db_connection_name = lambda: "default"
163
156
 
164
157
  site_data = {
165
158
  "siteName": "MPLS",
@@ -173,9 +166,7 @@ class IPFabricTransformMapModelTestCase(TestCase):
173
166
  "networksCount": 6,
174
167
  }
175
168
 
176
- self.site = runner.sync_item(
177
- item=site_data, app_label="dcim", model="site", cf=sync.update_custom_fields
178
- )
169
+ self.site = runner.get_model_or_update("dcim", "site", site_data)
179
170
 
180
171
  device_data = {
181
172
  "id": "961251111",
@@ -213,36 +204,14 @@ class IPFabricTransformMapModelTestCase(TestCase):
213
204
  "slug": None,
214
205
  }
215
206
 
216
- self.mf_obj = runner.sync_item(
217
- item=device_data,
218
- app_label="dcim",
219
- model="manufacturer",
220
- cf=sync.update_custom_fields,
221
- )
222
- self.dt_obj = runner.sync_item(
223
- item=device_data,
224
- app_label="dcim",
225
- model="devicetype",
226
- cf=sync.update_custom_fields,
227
- )
228
- self.platform = runner.sync_item(
229
- item=device_data,
230
- app_label="dcim",
231
- model="platform",
232
- cf=sync.update_custom_fields,
233
- )
234
- self.role = runner.sync_item(
235
- item=device_data,
236
- app_label="dcim",
237
- model="devicerole",
238
- cf=sync.update_custom_fields,
239
- )
240
- self.device_object = runner.sync_item(
241
- item=device_data,
242
- app_label="dcim",
243
- model="device",
244
- cf=sync.update_custom_fields,
245
- )
207
+ self.mf_obj = runner.get_model_or_update("dcim", "manufacturer", device_data)
208
+ self.dt_obj = runner.get_model_or_update("dcim", "devicetype", device_data)
209
+
210
+ self.platform = runner.get_model_or_update("dcim", "platform", device_data)
211
+
212
+ self.role = runner.get_model_or_update("dcim", "devicerole", device_data)
213
+
214
+ self.device_object = runner.get_model_or_update("dcim", "device", device_data)
246
215
 
247
216
  def test_transform_map(self):
248
217
  site_transform_map = IPFabricTransformMap.objects.get(name="Site Transform Map")
@@ -414,10 +383,5 @@ class IPFabricTransformMapModelTestCase(TestCase):
414
383
  )
415
384
  transform_field.template = "{{ object.hostname }} - test"
416
385
  transform_field.save()
417
- device_object = runner.sync_item(
418
- item=device_data,
419
- app_label="dcim",
420
- model="device",
421
- cf=sync.update_custom_fields,
422
- )
386
+ device_object = runner.get_model_or_update("dcim", "device", device_data)
423
387
  self.assertEqual(device_object.name, "L21PE152 - test")