ipfabric_netbox 4.3.0b5__tar.gz → 4.3.0b7__tar.gz
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-4.3.0b5 → ipfabric_netbox-4.3.0b7}/PKG-INFO +1 -1
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/__init__.py +1 -1
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/api/serializers.py +5 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/api/views.py +47 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/choices.py +2 -2
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/forms.py +137 -4
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/models.py +6 -1
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/tables.py +2 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +1 -1
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/tests/api/test_api.py +149 -22
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/tests/test_views.py +214 -158
- ipfabric_netbox-4.3.0b7/ipfabric_netbox/urls.py +93 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/views.py +293 -132
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/pyproject.toml +1 -1
- ipfabric_netbox-4.3.0b5/ipfabric_netbox/urls.py +0 -177
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/README.md +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/api/__init__.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/api/urls.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/data/transform_map.json +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/exceptions.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/filtersets.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/graphql/__init__.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/graphql/enums.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/graphql/filters.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/graphql/schema.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/graphql/types.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/jobs.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0001_initial.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0001_initial_squashed_0013_switch_to_branching_plugin.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0002_ipfabricsnapshot_status.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0003_ipfabricsource_type_and_more.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0004_ipfabricsync_auto_merge.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0005_alter_ipfabricrelationshipfield_source_model_and_more.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0006_alter_ipfabrictransformmap_target_model.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0007_prepare_custom_fields.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0008_prepare_transform_maps.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0009_transformmap_changes_for_netbox_v4_2.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0010_remove_uuid_from_get_or_create.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0011_update_part_number_DCIM_inventory_item_template.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0012_remove_status_field.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0013_switch_to_branching_plugin.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0014_ipfabrictransformmapgroup_ipfabrictransformmap_group.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0015_ipfabricingestionissue.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0018_remove_type_field.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/0019_alter_ipfabrictransformmap_options_and_more.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/migrations/__init__.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/navigation.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/signals.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/template_content.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/clone_form.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/diff.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/json.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/logs_pending.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/merge_form.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_button.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_modal.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/snapshotdata.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_field_map.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_relationship_map.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabric_table.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricingestion.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsnapshot.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_list.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_restore.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmapgroup.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_all.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_progress.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_statistics.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_status.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/partials/job_logs.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/partials/object_tabs.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/ipfabric_netbox/partials/sync_last_ingestion.html +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templates/static/ipfabric_netbox/css/rack.css +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templatetags/__init__.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/tests/__init__.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/tests/api/__init__.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/tests/test_forms.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/tests/test_models.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/utilities/__init__.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/utilities/ipfutils.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/utilities/logging.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/utilities/nbutils.py +0 -0
- {ipfabric_netbox-4.3.0b5 → ipfabric_netbox-4.3.0b7}/ipfabric_netbox/utilities/transform_map.py +0 -0
|
@@ -176,6 +176,7 @@ class IPFabricSnapshotSerializer(NestedGroupModelSerializer):
|
|
|
176
176
|
|
|
177
177
|
class IPFabricSyncSerializer(NestedGroupModelSerializer):
|
|
178
178
|
snapshot_data = IPFabricSnapshotSerializer(nested=True)
|
|
179
|
+
parameters = serializers.JSONField()
|
|
179
180
|
|
|
180
181
|
class Meta:
|
|
181
182
|
model = IPFabricSync
|
|
@@ -244,3 +245,7 @@ class IPFabricIngestionIssueSerializer(NestedGroupModelSerializer):
|
|
|
244
245
|
"message",
|
|
245
246
|
"model",
|
|
246
247
|
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class EmptySerializer(serializers.Serializer):
|
|
251
|
+
pass
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
from core.api.serializers_.jobs import JobSerializer
|
|
2
|
+
from django.core.exceptions import PermissionDenied
|
|
1
3
|
from django.db import transaction
|
|
4
|
+
from django.http import HttpResponseBadRequest
|
|
5
|
+
from drf_spectacular.utils import extend_schema
|
|
2
6
|
from netbox.api.viewsets import NetBoxModelViewSet
|
|
3
7
|
from netbox.api.viewsets import NetBoxReadOnlyModelViewSet
|
|
4
8
|
from rest_framework.decorators import action
|
|
5
9
|
from rest_framework.response import Response
|
|
6
10
|
from utilities.query import count_related
|
|
7
11
|
|
|
12
|
+
from .serializers import EmptySerializer
|
|
8
13
|
from .serializers import IPFabricIngestionIssueSerializer
|
|
9
14
|
from .serializers import IPFabricIngestionSerializer
|
|
10
15
|
from .serializers import IPFabricRelationshipFieldSerializer
|
|
@@ -56,6 +61,27 @@ class IPFabricSyncViewSet(NetBoxModelViewSet):
|
|
|
56
61
|
queryset = IPFabricSync.objects.all()
|
|
57
62
|
serializer_class = IPFabricSyncSerializer
|
|
58
63
|
|
|
64
|
+
@extend_schema(
|
|
65
|
+
methods=["post"],
|
|
66
|
+
request=EmptySerializer(),
|
|
67
|
+
responses={201: JobSerializer()},
|
|
68
|
+
)
|
|
69
|
+
@action(detail=True, methods=["post"])
|
|
70
|
+
def sync(self, request, pk):
|
|
71
|
+
if not request.user.has_perm("ipfabric_netbox.sync_ipfabricsync"):
|
|
72
|
+
raise PermissionDenied(
|
|
73
|
+
"This user does not have permission to sync IPFabricSync."
|
|
74
|
+
)
|
|
75
|
+
sync = self.get_object()
|
|
76
|
+
if not sync.ready_for_sync:
|
|
77
|
+
return HttpResponseBadRequest(
|
|
78
|
+
f"Sync '{sync.name}' is not ready to be synced."
|
|
79
|
+
)
|
|
80
|
+
job = sync.enqueue_sync_job(user=request.user, adhoc=True)
|
|
81
|
+
return Response(
|
|
82
|
+
JobSerializer(job, context={"request": request}).data, status=201
|
|
83
|
+
)
|
|
84
|
+
|
|
59
85
|
|
|
60
86
|
class IPFabricIngestionViewSet(NetBoxReadOnlyModelViewSet):
|
|
61
87
|
queryset = IPFabricIngestion.objects.all()
|
|
@@ -122,3 +148,24 @@ class IPFabricSourceViewSet(NetBoxModelViewSet):
|
|
|
122
148
|
)
|
|
123
149
|
serializer_class = IPFabricSourceSerializer
|
|
124
150
|
filterset_class = IPFabricSourceFilterSet
|
|
151
|
+
|
|
152
|
+
@extend_schema(
|
|
153
|
+
methods=["post"],
|
|
154
|
+
request=EmptySerializer(),
|
|
155
|
+
responses={201: JobSerializer()},
|
|
156
|
+
)
|
|
157
|
+
@action(detail=True, methods=["post"])
|
|
158
|
+
def sync(self, request, pk):
|
|
159
|
+
if not request.user.has_perm("ipfabric_netbox.sync_ipfabricsource"):
|
|
160
|
+
raise PermissionDenied(
|
|
161
|
+
"This user does not have permission to sync IPFabricSource."
|
|
162
|
+
)
|
|
163
|
+
source = self.get_object()
|
|
164
|
+
if not source.ready_for_sync:
|
|
165
|
+
return HttpResponseBadRequest(
|
|
166
|
+
f"Source '{source.name}' is not ready to be synced."
|
|
167
|
+
)
|
|
168
|
+
job = source.enqueue_sync_job(request=request)
|
|
169
|
+
return Response(
|
|
170
|
+
JobSerializer(job, context={"request": request}).data, status=201
|
|
171
|
+
)
|
|
@@ -170,7 +170,7 @@ class IPFabricTransformMapSourceModelChoices(ChoiceSet):
|
|
|
170
170
|
VRF = "vrf"
|
|
171
171
|
PREFIX = "prefix"
|
|
172
172
|
IPADDRESS = "ipaddress"
|
|
173
|
-
|
|
173
|
+
PARTNUMBERS = "part_number"
|
|
174
174
|
|
|
175
175
|
CHOICES = (
|
|
176
176
|
(SITE, "Site", "cyan"),
|
|
@@ -182,7 +182,7 @@ class IPFabricTransformMapSourceModelChoices(ChoiceSet):
|
|
|
182
182
|
(VRF, "VRF", "gray"),
|
|
183
183
|
(PREFIX, "Prefix", "gray"),
|
|
184
184
|
(IPADDRESS, "IP Address", "gray"),
|
|
185
|
-
|
|
185
|
+
(PARTNUMBERS, "Part Number", "gray"),
|
|
186
186
|
)
|
|
187
187
|
|
|
188
188
|
|
|
@@ -7,8 +7,10 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
7
7
|
from django.core.exceptions import ValidationError
|
|
8
8
|
from django.utils import timezone
|
|
9
9
|
from django.utils.translation import gettext_lazy as _
|
|
10
|
+
from netbox.forms import NetBoxModelBulkEditForm
|
|
10
11
|
from netbox.forms import NetBoxModelFilterSetForm
|
|
11
12
|
from netbox.forms import NetBoxModelForm
|
|
13
|
+
from netbox.forms import NetBoxModelImportForm
|
|
12
14
|
from netbox.forms.mixins import SavedFiltersMixin
|
|
13
15
|
from utilities.datetime import local_now
|
|
14
16
|
from utilities.forms import add_blank_choice
|
|
@@ -16,21 +18,28 @@ from utilities.forms import ConfirmationForm
|
|
|
16
18
|
from utilities.forms import FilterForm
|
|
17
19
|
from utilities.forms import get_field_value
|
|
18
20
|
from utilities.forms.fields import CommentField
|
|
21
|
+
from utilities.forms.fields import CSVChoiceField
|
|
22
|
+
from utilities.forms.fields import CSVContentTypeField
|
|
23
|
+
from utilities.forms.fields import CSVModelChoiceField
|
|
19
24
|
from utilities.forms.fields import DynamicModelChoiceField
|
|
20
25
|
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
|
21
26
|
from utilities.forms.rendering import FieldSet
|
|
22
27
|
from utilities.forms.widgets import APISelectMultiple
|
|
28
|
+
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
|
23
29
|
from utilities.forms.widgets import DateTimePicker
|
|
24
30
|
from utilities.forms.widgets import HTMXSelect
|
|
25
31
|
from utilities.forms.widgets import NumberWithOptions
|
|
26
32
|
|
|
27
33
|
from .choices import IPFabricSnapshotStatusModelChoices
|
|
34
|
+
from .choices import IPFabricSourceTypeChoices
|
|
35
|
+
from .choices import IPFabricTransformMapSourceModelChoices
|
|
28
36
|
from .choices import required_transform_map_contenttypes
|
|
29
37
|
from .choices import transform_field_source_columns
|
|
30
38
|
from .models import IPFabricIngestion
|
|
31
39
|
from .models import IPFabricRelationshipField
|
|
32
40
|
from .models import IPFabricSnapshot
|
|
33
41
|
from .models import IPFabricSource
|
|
42
|
+
from .models import IPFabricSupportedSyncModels
|
|
34
43
|
from .models import IPFabricSync
|
|
35
44
|
from .models import IPFabricTransformField
|
|
36
45
|
from .models import IPFabricTransformMap
|
|
@@ -269,6 +278,20 @@ class IPFabricTransformMapGroupForm(NetBoxModelForm):
|
|
|
269
278
|
fields = ("name", "description")
|
|
270
279
|
|
|
271
280
|
|
|
281
|
+
class IPFabricTransformMapGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|
282
|
+
description = forms.CharField(
|
|
283
|
+
label=_("Description"), max_length=200, required=False
|
|
284
|
+
)
|
|
285
|
+
model = IPFabricTransformMapGroup
|
|
286
|
+
fields = ("description",)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class IPFabricTransformMapGroupBulkImportForm(NetBoxModelImportForm):
|
|
290
|
+
class Meta:
|
|
291
|
+
model = IPFabricTransformMapGroup
|
|
292
|
+
fields = ("name", "description")
|
|
293
|
+
|
|
294
|
+
|
|
272
295
|
class IPFabricTransformMapForm(NetBoxModelForm):
|
|
273
296
|
class Meta:
|
|
274
297
|
model = IPFabricTransformMap
|
|
@@ -278,6 +301,44 @@ class IPFabricTransformMapForm(NetBoxModelForm):
|
|
|
278
301
|
}
|
|
279
302
|
|
|
280
303
|
|
|
304
|
+
class IPFabricTransformMapBulkEditForm(NetBoxModelBulkEditForm):
|
|
305
|
+
group = forms.ModelChoiceField(
|
|
306
|
+
queryset=IPFabricTransformMapGroup.objects.all(),
|
|
307
|
+
required=False,
|
|
308
|
+
label="Target Group",
|
|
309
|
+
)
|
|
310
|
+
model = IPFabricTransformMap
|
|
311
|
+
fields = ("group",)
|
|
312
|
+
nullable_fields = ("group",)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class IPFabricTransformMapBulkImportForm(NetBoxModelImportForm):
|
|
316
|
+
source_model = CSVChoiceField(
|
|
317
|
+
label=_("Source model"),
|
|
318
|
+
choices=IPFabricTransformMapSourceModelChoices,
|
|
319
|
+
help_text=_("Models available in IP Fabric to source data from"),
|
|
320
|
+
)
|
|
321
|
+
target_model = CSVContentTypeField(
|
|
322
|
+
queryset=ContentType.objects.filter(IPFabricSupportedSyncModels),
|
|
323
|
+
required=True,
|
|
324
|
+
label=_("Target model"),
|
|
325
|
+
help_text=_(
|
|
326
|
+
"Target model to apply this transform map to (use format 'app_label.model', e.g., 'dcim.device')"
|
|
327
|
+
),
|
|
328
|
+
)
|
|
329
|
+
group = CSVModelChoiceField(
|
|
330
|
+
label=_("Group"),
|
|
331
|
+
queryset=IPFabricTransformMapGroup.objects.all(),
|
|
332
|
+
required=False,
|
|
333
|
+
to_field_name="name",
|
|
334
|
+
help_text=_("Name of assigned transform map group"),
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
class Meta:
|
|
338
|
+
model = IPFabricTransformMap
|
|
339
|
+
fields = ("name", "source_model", "target_model", "group")
|
|
340
|
+
|
|
341
|
+
|
|
281
342
|
class IPFabricTransformMapCloneForm(forms.Form):
|
|
282
343
|
name = forms.CharField(
|
|
283
344
|
required=True, label="Name", help_text="Name for the cloned transform map."
|
|
@@ -439,6 +500,23 @@ class IPFabricSourceForm(NetBoxModelForm):
|
|
|
439
500
|
return instance
|
|
440
501
|
|
|
441
502
|
|
|
503
|
+
class IPFabricSourceBulkEditForm(NetBoxModelBulkEditForm):
|
|
504
|
+
comments = CommentField()
|
|
505
|
+
type = forms.ChoiceField(
|
|
506
|
+
choices=add_blank_choice(IPFabricSourceTypeChoices),
|
|
507
|
+
required=False,
|
|
508
|
+
initial="",
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
model = IPFabricSource
|
|
512
|
+
fields = (
|
|
513
|
+
"type",
|
|
514
|
+
"url",
|
|
515
|
+
"description",
|
|
516
|
+
"comments",
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
|
|
442
520
|
class OrderedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|
443
521
|
"""A ModelMultipleChoiceField that preserves the order of the selected items."""
|
|
444
522
|
|
|
@@ -724,11 +802,66 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
724
802
|
]
|
|
725
803
|
self.instance.parameters = parameters
|
|
726
804
|
self.instance.status = DataSourceStatusChoices.NEW
|
|
805
|
+
return super().save(*args, **kwargs)
|
|
806
|
+
|
|
727
807
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
808
|
+
class IPFabricSyncBulkEditForm(NetBoxModelBulkEditForm):
|
|
809
|
+
source = forms.ModelChoiceField(
|
|
810
|
+
queryset=IPFabricSource.objects.all(),
|
|
811
|
+
required=False,
|
|
812
|
+
label=_("IP Fabric Source"),
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
snapshot_data = DynamicModelChoiceField(
|
|
816
|
+
queryset=IPFabricSnapshot.objects.filter(status="loaded"),
|
|
817
|
+
required=False,
|
|
818
|
+
label=_("Snapshot"),
|
|
819
|
+
query_params={
|
|
820
|
+
"source_id": "$source",
|
|
821
|
+
"status": "loaded",
|
|
822
|
+
},
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
update_custom_fields = forms.NullBooleanField(
|
|
826
|
+
required=False,
|
|
827
|
+
label=_("Custom Fields Updating"),
|
|
828
|
+
help_text=_("Update object custom fields where applicable."),
|
|
829
|
+
widget=BulkEditNullBooleanSelect,
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
scheduled = forms.DateTimeField(
|
|
833
|
+
required=False,
|
|
834
|
+
widget=DateTimePicker(),
|
|
835
|
+
label=_("Schedule at"),
|
|
836
|
+
help_text=_("Schedule execution of sync to a set time"),
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
interval = forms.IntegerField(
|
|
840
|
+
required=False,
|
|
841
|
+
min_value=1,
|
|
842
|
+
label=_("Recurs every"),
|
|
843
|
+
widget=NumberWithOptions(options=JobIntervalChoices),
|
|
844
|
+
help_text=_("Interval at which this sync is re-run (in minutes)"),
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
auto_merge = forms.NullBooleanField(
|
|
848
|
+
required=False,
|
|
849
|
+
label=_("Auto Merge"),
|
|
850
|
+
help_text=_("Automatically merge staged changes into NetBox"),
|
|
851
|
+
widget=BulkEditNullBooleanSelect,
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
model = IPFabricSync
|
|
855
|
+
fields = (
|
|
856
|
+
"name",
|
|
857
|
+
"source",
|
|
858
|
+
"snapshot_data",
|
|
859
|
+
"auto_merge",
|
|
860
|
+
"update_custom_fields",
|
|
861
|
+
"tags",
|
|
862
|
+
"scheduled",
|
|
863
|
+
"interval",
|
|
864
|
+
)
|
|
732
865
|
|
|
733
866
|
|
|
734
867
|
tableChoices = [
|
|
@@ -167,7 +167,7 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
167
167
|
qs = qs.exclude(pk=self.pk)
|
|
168
168
|
if qs.exists():
|
|
169
169
|
err_msg = _(
|
|
170
|
-
"A transform map with
|
|
170
|
+
f"A transform map with group '{self.group}' and target model '{self.target_model}' already exists."
|
|
171
171
|
)
|
|
172
172
|
raise ValidationError(
|
|
173
173
|
{
|
|
@@ -669,6 +669,11 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
|
|
|
669
669
|
def get_status_color(self):
|
|
670
670
|
return DataSourceStatusChoices.colors.get(self.status)
|
|
671
671
|
|
|
672
|
+
def save(self, *args, **kwargs):
|
|
673
|
+
super().save(*args, **kwargs)
|
|
674
|
+
if self.scheduled:
|
|
675
|
+
self.enqueue_sync_job()
|
|
676
|
+
|
|
672
677
|
@property
|
|
673
678
|
def ready_for_sync(self):
|
|
674
679
|
if self.status not in (DataSourceStatusChoices.SYNCING,):
|
|
@@ -45,6 +45,7 @@ DATA_BUTTON = """
|
|
|
45
45
|
|
|
46
46
|
class IPFabricRelationshipFieldTable(NetBoxTable):
|
|
47
47
|
actions = columns.ActionsColumn(actions=("edit", "delete"))
|
|
48
|
+
source_model = columns.ContentTypeColumn(verbose_name=_("Source Model"))
|
|
48
49
|
|
|
49
50
|
class Meta(NetBoxTable.Meta):
|
|
50
51
|
model = IPFabricRelationshipField
|
|
@@ -79,6 +80,7 @@ class IPFabricTransformMapGroupTable(NetBoxTable):
|
|
|
79
80
|
class IPFabricTransformMapTable(NetBoxTable):
|
|
80
81
|
name = tables.Column(linkify=True)
|
|
81
82
|
group = tables.Column(linkify=True)
|
|
83
|
+
target_model = columns.ContentTypeColumn(verbose_name=_("Target Model"))
|
|
82
84
|
|
|
83
85
|
class Meta(NetBoxTable.Meta):
|
|
84
86
|
model = IPFabricTransformMap
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
</tr>
|
|
59
59
|
<tr>
|
|
60
60
|
<th scope="row">Schedule</th>
|
|
61
|
-
<td><div>{{ object.scheduled }}</div></td>
|
|
61
|
+
<td><div>{% if scheduled_job %}<a href="{% url 'core:job' pk=scheduled_job.pk %}">{{ object.scheduled }}</a>{% else %} {{ object.scheduled | placeholder }} {% endif %}</div></td>
|
|
62
62
|
</tr>
|
|
63
63
|
<tr>
|
|
64
64
|
<th scope="row">Interval</th>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from core.choices import DataSourceStatusChoices
|
|
1
2
|
from django.contrib.contenttypes.models import ContentType
|
|
2
3
|
from django.utils import timezone
|
|
3
4
|
from rest_framework import status
|
|
@@ -15,7 +16,7 @@ from ipfabric_netbox.models import IPFabricTransformMap
|
|
|
15
16
|
from ipfabric_netbox.models import IPFabricTransformMapGroup
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
BASE = "/api/plugins/ipfabric
|
|
19
|
+
BASE = "/api/plugins/ipfabric"
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class IPFabricTransformMapGroupTest(APIViewTestCases.APIViewTestCase):
|
|
@@ -36,10 +37,10 @@ class IPFabricTransformMapGroupTest(APIViewTestCases.APIViewTestCase):
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
def _get_list_url(self):
|
|
39
|
-
return f"{BASE}transform-map-group/"
|
|
40
|
+
return f"{BASE}/transform-map-group/"
|
|
40
41
|
|
|
41
42
|
def _get_detail_url(self, instance):
|
|
42
|
-
return f"{BASE}transform-map-group/{instance.pk}/"
|
|
43
|
+
return f"{BASE}/transform-map-group/{instance.pk}/"
|
|
43
44
|
|
|
44
45
|
@classmethod
|
|
45
46
|
def setUpTestData(cls):
|
|
@@ -62,10 +63,10 @@ class IPFabricTransformMapTest(APIViewTestCases.APIViewTestCase):
|
|
|
62
63
|
]
|
|
63
64
|
|
|
64
65
|
def _get_list_url(self):
|
|
65
|
-
return f"{BASE}transform-map/"
|
|
66
|
+
return f"{BASE}/transform-map/"
|
|
66
67
|
|
|
67
68
|
def _get_detail_url(self, instance):
|
|
68
|
-
return f"{BASE}transform-map/{instance.pk}/"
|
|
69
|
+
return f"{BASE}/transform-map/{instance.pk}/"
|
|
69
70
|
|
|
70
71
|
@classmethod
|
|
71
72
|
def setUpTestData(cls):
|
|
@@ -132,10 +133,10 @@ class IPFabricTransformFieldTest(APIViewTestCases.APIViewTestCase):
|
|
|
132
133
|
]
|
|
133
134
|
|
|
134
135
|
def _get_list_url(self):
|
|
135
|
-
return f"{BASE}transform-field/"
|
|
136
|
+
return f"{BASE}/transform-field/"
|
|
136
137
|
|
|
137
138
|
def _get_detail_url(self, instance):
|
|
138
|
-
return f"{BASE}transform-field/{instance.pk}/"
|
|
139
|
+
return f"{BASE}/transform-field/{instance.pk}/"
|
|
139
140
|
|
|
140
141
|
@classmethod
|
|
141
142
|
def setUpTestData(cls):
|
|
@@ -235,10 +236,10 @@ class IPFabricRelationshipFieldTest(APIViewTestCases.APIViewTestCase):
|
|
|
235
236
|
]
|
|
236
237
|
|
|
237
238
|
def _get_list_url(self):
|
|
238
|
-
return f"{BASE}relationship-field/"
|
|
239
|
+
return f"{BASE}/relationship-field/"
|
|
239
240
|
|
|
240
241
|
def _get_detail_url(self, instance):
|
|
241
|
-
return f"{BASE}relationship-field/{instance.pk}/"
|
|
242
|
+
return f"{BASE}/relationship-field/{instance.pk}/"
|
|
242
243
|
|
|
243
244
|
@classmethod
|
|
244
245
|
def setUpTestData(cls):
|
|
@@ -340,10 +341,10 @@ class IPFabricSourceTest(APIViewTestCases.APIViewTestCase):
|
|
|
340
341
|
graphql_base_name = "ipfabric_source"
|
|
341
342
|
|
|
342
343
|
def _get_list_url(self):
|
|
343
|
-
return f"{BASE}source/"
|
|
344
|
+
return f"{BASE}/source/"
|
|
344
345
|
|
|
345
346
|
def _get_detail_url(self, instance):
|
|
346
|
-
return f"{BASE}source/{instance.pk}/"
|
|
347
|
+
return f"{BASE}/source/{instance.pk}/"
|
|
347
348
|
|
|
348
349
|
@classmethod
|
|
349
350
|
def setUpTestData(cls):
|
|
@@ -387,6 +388,66 @@ class IPFabricSourceTest(APIViewTestCases.APIViewTestCase):
|
|
|
387
388
|
},
|
|
388
389
|
]
|
|
389
390
|
|
|
391
|
+
def test_sync_action_success(self):
|
|
392
|
+
"""Test successful sync action with proper permissions and ready source."""
|
|
393
|
+
self.add_permissions(
|
|
394
|
+
"ipfabric_netbox.add_ipfabricsource",
|
|
395
|
+
"ipfabric_netbox.sync_ipfabricsource",
|
|
396
|
+
)
|
|
397
|
+
# Get the first source from setUpTestData
|
|
398
|
+
source = IPFabricSource.objects.first()
|
|
399
|
+
# Set status to make ready_for_sync return True
|
|
400
|
+
source.status = DataSourceStatusChoices.COMPLETED
|
|
401
|
+
source.save()
|
|
402
|
+
|
|
403
|
+
with self.settings(CELERY_TASK_ALWAYS_EAGER=True):
|
|
404
|
+
# Create a mock job object to simulate enqueue_sync_job response
|
|
405
|
+
from unittest.mock import Mock, patch
|
|
406
|
+
|
|
407
|
+
mock_job = Mock()
|
|
408
|
+
mock_job.id = "test-job-123"
|
|
409
|
+
mock_job.status = "queued"
|
|
410
|
+
|
|
411
|
+
with patch.object(source, "enqueue_sync_job", return_value=mock_job):
|
|
412
|
+
url = f"{BASE}/source/{source.pk}/sync/"
|
|
413
|
+
response = self.client.post(url, **self.header)
|
|
414
|
+
|
|
415
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
416
|
+
self.assertIn("id", response.data)
|
|
417
|
+
|
|
418
|
+
def test_sync_action_permission_denied(self):
|
|
419
|
+
"""Test sync action without proper permissions."""
|
|
420
|
+
# Note: Not adding sync_ipfabricsource permission
|
|
421
|
+
self.add_permissions(
|
|
422
|
+
"ipfabric_netbox.add_ipfabricsource",
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
source = IPFabricSource.objects.first()
|
|
426
|
+
url = f"{BASE}/source/{source.pk}/sync/"
|
|
427
|
+
response = self.client.post(url, **self.header)
|
|
428
|
+
|
|
429
|
+
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
430
|
+
|
|
431
|
+
def test_sync_action_not_ready(self):
|
|
432
|
+
"""Test sync action when source is not ready for sync."""
|
|
433
|
+
self.add_permissions(
|
|
434
|
+
"ipfabric_netbox.add_ipfabricsource",
|
|
435
|
+
"ipfabric_netbox.sync_ipfabricsource",
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
source = IPFabricSource.objects.first()
|
|
439
|
+
# Set status to make ready_for_sync return False
|
|
440
|
+
source.status = DataSourceStatusChoices.SYNCING
|
|
441
|
+
source.save()
|
|
442
|
+
|
|
443
|
+
url = f"{BASE}/source/{source.pk}/sync/"
|
|
444
|
+
response = self.client.post(url, **self.header)
|
|
445
|
+
|
|
446
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
447
|
+
self.assertIn(
|
|
448
|
+
f"Source '{source.name}' is not ready to be synced.", str(response.content)
|
|
449
|
+
)
|
|
450
|
+
|
|
390
451
|
|
|
391
452
|
class IPFabricSnapshotTest(
|
|
392
453
|
APIViewTestCases.GetObjectViewTestCase,
|
|
@@ -407,10 +468,10 @@ class IPFabricSnapshotTest(
|
|
|
407
468
|
]
|
|
408
469
|
|
|
409
470
|
def _get_list_url(self):
|
|
410
|
-
return f"{BASE}snapshot/"
|
|
471
|
+
return f"{BASE}/snapshot/"
|
|
411
472
|
|
|
412
473
|
def _get_detail_url(self, instance):
|
|
413
|
-
return f"{BASE}snapshot/{instance.pk}/"
|
|
474
|
+
return f"{BASE}/snapshot/{instance.pk}/"
|
|
414
475
|
|
|
415
476
|
@classmethod
|
|
416
477
|
def setUpTestData(cls):
|
|
@@ -468,7 +529,7 @@ class IPFabricSnapshotTest(
|
|
|
468
529
|
def test_sites_action_lists_all_and_filters(self):
|
|
469
530
|
self.add_permissions("ipfabric_netbox.view_ipfabricsnapshot")
|
|
470
531
|
# list all
|
|
471
|
-
url = f"{BASE}snapshot/{self.snapshots[0].pk}/sites/"
|
|
532
|
+
url = f"{BASE}/snapshot/{self.snapshots[0].pk}/sites/"
|
|
472
533
|
resp = self.client.get(url, **self.header)
|
|
473
534
|
self.assertHttpStatus(resp, status.HTTP_200_OK)
|
|
474
535
|
body = resp.json()
|
|
@@ -477,7 +538,7 @@ class IPFabricSnapshotTest(
|
|
|
477
538
|
labels = [i["name"] for i in body["results"]]
|
|
478
539
|
self.assertEqual(labels, self.snapshots[0].data["sites"])
|
|
479
540
|
# filter
|
|
480
|
-
url = f"{BASE}snapshot/{self.snapshots[0].pk}/sites/?q=site"
|
|
541
|
+
url = f"{BASE}/snapshot/{self.snapshots[0].pk}/sites/?q=site"
|
|
481
542
|
resp = self.client.get(url, **self.header)
|
|
482
543
|
self.assertHttpStatus(resp, status.HTTP_200_OK)
|
|
483
544
|
body = resp.json()
|
|
@@ -496,7 +557,7 @@ class IPFabricSnapshotTest(
|
|
|
496
557
|
IPFabricData.objects.filter(snapshot_data=self.snapshots[0]).count(), 0
|
|
497
558
|
)
|
|
498
559
|
# PATCH raw
|
|
499
|
-
url = f"{BASE}snapshot/{self.snapshots[0].pk}/raw/"
|
|
560
|
+
url = f"{BASE}/snapshot/{self.snapshots[0].pk}/raw/"
|
|
500
561
|
payload = {
|
|
501
562
|
"data": [
|
|
502
563
|
{"data": {"example": 1}, "type": "device"},
|
|
@@ -550,10 +611,10 @@ class IPFabricSyncTest(APIViewTestCases.APIViewTestCase):
|
|
|
550
611
|
}
|
|
551
612
|
|
|
552
613
|
def _get_list_url(self):
|
|
553
|
-
return f"{BASE}sync/"
|
|
614
|
+
return f"{BASE}/sync/"
|
|
554
615
|
|
|
555
616
|
def _get_detail_url(self, instance):
|
|
556
|
-
return f"{BASE}sync/{instance.pk}/"
|
|
617
|
+
return f"{BASE}/sync/{instance.pk}/"
|
|
557
618
|
|
|
558
619
|
@classmethod
|
|
559
620
|
def setUpTestData(cls):
|
|
@@ -637,6 +698,72 @@ class IPFabricSyncTest(APIViewTestCases.APIViewTestCase):
|
|
|
637
698
|
cls.create_data[1]["parameters"] = {"ipaddress": True, "prefix": True}
|
|
638
699
|
cls.create_data[2]["parameters"] = {"device": True, "interface": True}
|
|
639
700
|
|
|
701
|
+
def test_sync_action_success(self):
|
|
702
|
+
"""Test successful sync action with proper permissions and ready sync."""
|
|
703
|
+
self.add_permissions(
|
|
704
|
+
"ipfabric_netbox.add_ipfabricsync",
|
|
705
|
+
"ipfabric_netbox.sync_ipfabricsync",
|
|
706
|
+
)
|
|
707
|
+
# Get the first sync from setUpTestData
|
|
708
|
+
sync = IPFabricSync.objects.first()
|
|
709
|
+
# Set status and ensure snapshot has data to make ready_for_sync return True
|
|
710
|
+
sync.status = DataSourceStatusChoices.COMPLETED
|
|
711
|
+
sync.save()
|
|
712
|
+
|
|
713
|
+
# Ensure the snapshot has data
|
|
714
|
+
sync.snapshot_data.source.type = (
|
|
715
|
+
"local" # For local type, ready_for_sync checks are simpler
|
|
716
|
+
)
|
|
717
|
+
sync.snapshot_data.source.save()
|
|
718
|
+
|
|
719
|
+
with self.settings(CELERY_TASK_ALWAYS_EAGER=True):
|
|
720
|
+
# Create a mock job object to simulate enqueue_sync_job response
|
|
721
|
+
from unittest.mock import Mock, patch
|
|
722
|
+
|
|
723
|
+
mock_job = Mock()
|
|
724
|
+
mock_job.id = "test-sync-job-456"
|
|
725
|
+
mock_job.status = "queued"
|
|
726
|
+
|
|
727
|
+
with patch.object(sync, "enqueue_sync_job", return_value=mock_job):
|
|
728
|
+
url = f"{BASE}/sync/{sync.pk}/sync/"
|
|
729
|
+
response = self.client.post(url, **self.header)
|
|
730
|
+
|
|
731
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
732
|
+
self.assertIn("id", response.data)
|
|
733
|
+
|
|
734
|
+
def test_sync_action_permission_denied(self):
|
|
735
|
+
"""Test sync action without proper permissions."""
|
|
736
|
+
# Note: Not adding sync_ipfabricsource permission
|
|
737
|
+
self.add_permissions(
|
|
738
|
+
"ipfabric_netbox.add_ipfabricsync",
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
sync = IPFabricSync.objects.first()
|
|
742
|
+
url = f"{BASE}/sync/{sync.pk}/sync/"
|
|
743
|
+
response = self.client.post(url, **self.header)
|
|
744
|
+
|
|
745
|
+
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
746
|
+
|
|
747
|
+
def test_sync_action_not_ready(self):
|
|
748
|
+
"""Test sync action when sync is not ready for sync."""
|
|
749
|
+
self.add_permissions(
|
|
750
|
+
"ipfabric_netbox.add_ipfabricsync",
|
|
751
|
+
"ipfabric_netbox.sync_ipfabricsync",
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
sync = IPFabricSync.objects.first()
|
|
755
|
+
# Set status to make ready_for_sync return False
|
|
756
|
+
sync.status = DataSourceStatusChoices.SYNCING
|
|
757
|
+
sync.save()
|
|
758
|
+
|
|
759
|
+
url = f"{BASE}/sync/{sync.pk}/sync/"
|
|
760
|
+
response = self.client.post(url, **self.header)
|
|
761
|
+
|
|
762
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
763
|
+
self.assertIn(
|
|
764
|
+
f"Sync '{sync.name}' is not ready to be synced.", str(response.content)
|
|
765
|
+
)
|
|
766
|
+
|
|
640
767
|
|
|
641
768
|
class IPFabricIngestionTest(
|
|
642
769
|
APIViewTestCases.GetObjectViewTestCase,
|
|
@@ -653,10 +780,10 @@ class IPFabricIngestionTest(
|
|
|
653
780
|
]
|
|
654
781
|
|
|
655
782
|
def _get_list_url(self):
|
|
656
|
-
return f"{BASE}ingestion/"
|
|
783
|
+
return f"{BASE}/ingestion/"
|
|
657
784
|
|
|
658
785
|
def _get_detail_url(self, instance):
|
|
659
|
-
return f"{BASE}ingestion/{instance.pk}/"
|
|
786
|
+
return f"{BASE}/ingestion/{instance.pk}/"
|
|
660
787
|
|
|
661
788
|
@classmethod
|
|
662
789
|
def setUpTestData(cls):
|
|
@@ -754,10 +881,10 @@ class IPFabricIngestionIssueTest(
|
|
|
754
881
|
]
|
|
755
882
|
|
|
756
883
|
def _get_list_url(self):
|
|
757
|
-
return f"{BASE}ingestion-issues/"
|
|
884
|
+
return f"{BASE}/ingestion-issues/"
|
|
758
885
|
|
|
759
886
|
def _get_detail_url(self, instance):
|
|
760
|
-
return f"{BASE}ingestion-issues/{instance.pk}/"
|
|
887
|
+
return f"{BASE}/ingestion-issues/{instance.pk}/"
|
|
761
888
|
|
|
762
889
|
@classmethod
|
|
763
890
|
def setUpTestData(cls):
|