ipfabric_netbox 4.3.2b9__py3-none-any.whl → 4.3.2b11__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.
- ipfabric_netbox/__init__.py +1 -1
- ipfabric_netbox/api/serializers.py +112 -7
- ipfabric_netbox/api/urls.py +6 -0
- ipfabric_netbox/api/views.py +23 -0
- ipfabric_netbox/choices.py +74 -40
- ipfabric_netbox/data/endpoint.json +52 -0
- ipfabric_netbox/data/filters.json +51 -0
- ipfabric_netbox/data/transform_map.json +190 -176
- ipfabric_netbox/exceptions.py +7 -5
- ipfabric_netbox/filtersets.py +310 -41
- ipfabric_netbox/forms.py +330 -80
- ipfabric_netbox/graphql/__init__.py +6 -0
- ipfabric_netbox/graphql/enums.py +5 -5
- ipfabric_netbox/graphql/filters.py +56 -4
- ipfabric_netbox/graphql/schema.py +28 -0
- ipfabric_netbox/graphql/types.py +61 -1
- ipfabric_netbox/jobs.py +12 -1
- ipfabric_netbox/migrations/0022_prepare_for_filters.py +182 -0
- ipfabric_netbox/migrations/0023_populate_filters_data.py +303 -0
- ipfabric_netbox/migrations/0024_finish_filters.py +29 -0
- ipfabric_netbox/migrations/0025_add_vss_chassis_endpoint.py +166 -0
- ipfabric_netbox/models.py +432 -17
- ipfabric_netbox/navigation.py +98 -24
- ipfabric_netbox/tables.py +194 -9
- ipfabric_netbox/templates/ipfabric_netbox/htmx_list.html +5 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions.html +59 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions_content.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/endpoint_filters_with_selector.html +54 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricendpoint.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilter.html +51 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression_edit.html +150 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +1 -1
- ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +16 -2
- ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +68 -0
- ipfabric_netbox/tests/api/test_api.py +333 -13
- ipfabric_netbox/tests/test_filtersets.py +2592 -0
- ipfabric_netbox/tests/test_forms.py +1349 -74
- ipfabric_netbox/tests/test_models.py +242 -34
- ipfabric_netbox/tests/test_views.py +2031 -26
- ipfabric_netbox/urls.py +35 -0
- ipfabric_netbox/utilities/endpoint.py +83 -0
- ipfabric_netbox/utilities/filters.py +88 -0
- ipfabric_netbox/utilities/ipfutils.py +393 -377
- ipfabric_netbox/utilities/logging.py +7 -7
- ipfabric_netbox/utilities/transform_map.py +144 -5
- ipfabric_netbox/views.py +719 -5
- {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/METADATA +2 -2
- {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/RECORD +50 -33
- {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/WHEEL +1 -1
ipfabric_netbox/urls.py
CHANGED
|
@@ -46,6 +46,41 @@ urlpatterns = (
|
|
|
46
46
|
"ingestion/<int:pk>/",
|
|
47
47
|
include(get_model_urls("ipfabric_netbox", "ipfabricingestion")),
|
|
48
48
|
),
|
|
49
|
+
# Endpoint
|
|
50
|
+
path(
|
|
51
|
+
"filter/",
|
|
52
|
+
include(get_model_urls("ipfabric_netbox", "ipfabricfilter", detail=False)),
|
|
53
|
+
),
|
|
54
|
+
path(
|
|
55
|
+
"filter/<int:pk>/",
|
|
56
|
+
include(get_model_urls("ipfabric_netbox", "ipfabricfilter")),
|
|
57
|
+
),
|
|
58
|
+
# Endpoint
|
|
59
|
+
path(
|
|
60
|
+
"filter-expression/",
|
|
61
|
+
include(
|
|
62
|
+
get_model_urls("ipfabric_netbox", "ipfabricfilterexpression", detail=False)
|
|
63
|
+
),
|
|
64
|
+
),
|
|
65
|
+
# Test endpoint for unsaved expressions (no pk required)
|
|
66
|
+
path(
|
|
67
|
+
"filter-expression/test/",
|
|
68
|
+
views.IPFabricFilterExpressionTestView.as_view(),
|
|
69
|
+
name="ipfabricfilterexpression_test_no_pk",
|
|
70
|
+
),
|
|
71
|
+
path(
|
|
72
|
+
"filter-expression/<int:pk>/",
|
|
73
|
+
include(get_model_urls("ipfabric_netbox", "ipfabricfilterexpression")),
|
|
74
|
+
),
|
|
75
|
+
# Endpoint
|
|
76
|
+
path(
|
|
77
|
+
"endpoint/",
|
|
78
|
+
include(get_model_urls("ipfabric_netbox", "ipfabricendpoint", detail=False)),
|
|
79
|
+
),
|
|
80
|
+
path(
|
|
81
|
+
"endpoint/<int:pk>/",
|
|
82
|
+
include(get_model_urls("ipfabric_netbox", "ipfabricendpoint")),
|
|
83
|
+
),
|
|
49
84
|
# Transform Map Group
|
|
50
85
|
path(
|
|
51
86
|
"transform-map-group/",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import importlib.resources
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from netbox.settings import django_apps
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# region Endpoint Creation
|
|
8
|
+
|
|
9
|
+
# These functions are used in the migration file to prepare the endpoints
|
|
10
|
+
# Because of this we have to use historical models
|
|
11
|
+
# see https://docs.djangoproject.com/en/5.1/topics/migrations/#historical-models
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_endpoints(data, apps: django_apps = None, db_alias: str = "default") -> None:
|
|
15
|
+
apps = apps or django_apps
|
|
16
|
+
IPFabricEndpoint = apps.get_model("ipfabric_netbox", "IPFabricEndpoint")
|
|
17
|
+
for endpoint_data in data:
|
|
18
|
+
IPFabricEndpoint.objects.using(db_alias).create(**endpoint_data)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_endpoint_data() -> dict:
|
|
22
|
+
for data_file in importlib.resources.files("ipfabric_netbox.data").iterdir():
|
|
23
|
+
if data_file.name != "endpoint.json":
|
|
24
|
+
continue
|
|
25
|
+
with open(data_file, "rb") as data_file:
|
|
26
|
+
return json.load(data_file)
|
|
27
|
+
raise FileNotFoundError("'endpoint.json' not found in installed package")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# endregion Endpoint Creation
|
|
31
|
+
|
|
32
|
+
# region Endpoint Updating
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class EndpointRecord:
|
|
36
|
+
"""Record for endpoint creation/deletion in migrations."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, name: str, description: str, endpoint: str):
|
|
39
|
+
self.name = name
|
|
40
|
+
self.description = description
|
|
41
|
+
self.endpoint = endpoint
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def do_endpoint_change(
|
|
45
|
+
apps, schema_editor, endpoints: tuple[EndpointRecord, ...], forward: bool = True
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Apply endpoint changes, `forward` determines direction.
|
|
49
|
+
|
|
50
|
+
Forward: Creates endpoints if they don't exist (idempotent using get_or_create).
|
|
51
|
+
Reverse: Deletes endpoints by endpoint path.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
apps: Django apps registry (historical models)
|
|
55
|
+
schema_editor: Database schema editor
|
|
56
|
+
endpoints: Tuple of EndpointRecord objects to process
|
|
57
|
+
forward: True for forward migration (create), False for reverse (delete)
|
|
58
|
+
"""
|
|
59
|
+
IPFabricEndpoint = apps.get_model("ipfabric_netbox", "IPFabricEndpoint")
|
|
60
|
+
db_alias = schema_editor.connection.alias
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
for endpoint_record in endpoints:
|
|
64
|
+
if forward:
|
|
65
|
+
# Forward: Create endpoint if it doesn't exist (idempotent)
|
|
66
|
+
IPFabricEndpoint.objects.using(db_alias).get_or_create(
|
|
67
|
+
endpoint=endpoint_record.endpoint,
|
|
68
|
+
defaults={
|
|
69
|
+
"name": endpoint_record.name,
|
|
70
|
+
"description": endpoint_record.description,
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
# Reverse: Delete endpoint by endpoint path
|
|
75
|
+
IPFabricEndpoint.objects.using(db_alias).filter(
|
|
76
|
+
endpoint=endpoint_record.endpoint
|
|
77
|
+
).delete()
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
print(f"Error applying endpoint changes: {e}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# endregion
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import importlib.resources
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from netbox.settings import django_apps
|
|
5
|
+
|
|
6
|
+
from ..choices import IPFabricSourceTypeChoices
|
|
7
|
+
|
|
8
|
+
# region Filter Expression
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_filter_expression_test_candidates(expression_instance):
|
|
12
|
+
"""
|
|
13
|
+
Get potential LOCAL sources and endpoints from filters associated with a filter expression.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
expression_instance: An IPFabricFilterExpression instance
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
tuple: (sources_set, endpoints_set) - Sets of IPFabricSource and IPFabricEndpoint objects
|
|
20
|
+
"""
|
|
21
|
+
if not expression_instance or not expression_instance.pk:
|
|
22
|
+
return set(), set()
|
|
23
|
+
|
|
24
|
+
sources = set()
|
|
25
|
+
endpoints = set()
|
|
26
|
+
|
|
27
|
+
# Query all filters that use this expression
|
|
28
|
+
for filter_obj in expression_instance.filters.all():
|
|
29
|
+
# Get LOCAL sources from syncs
|
|
30
|
+
for sync in filter_obj.syncs.all():
|
|
31
|
+
if (
|
|
32
|
+
sync.snapshot_data
|
|
33
|
+
and sync.snapshot_data.source
|
|
34
|
+
and sync.snapshot_data.source.type == IPFabricSourceTypeChoices.LOCAL
|
|
35
|
+
):
|
|
36
|
+
sources.add(sync.snapshot_data.source)
|
|
37
|
+
|
|
38
|
+
# Get endpoints
|
|
39
|
+
for endpoint in filter_obj.endpoints.all():
|
|
40
|
+
endpoints.add(endpoint)
|
|
41
|
+
|
|
42
|
+
return sources, endpoints
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# endregion Filter Expression
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# region Filter Creation
|
|
49
|
+
|
|
50
|
+
# These functions are used in the migration file to prepare the filters
|
|
51
|
+
# Because of this we have to use historical models
|
|
52
|
+
# see https://docs.djangoproject.com/en/5.1/topics/migrations/#historical-models
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_filters(data, apps: django_apps = None, db_alias: str = "default") -> None:
|
|
56
|
+
apps = apps or django_apps
|
|
57
|
+
IPFabricEndpoint = apps.get_model("ipfabric_netbox", "IPFabricEndpoint")
|
|
58
|
+
IPFabricFilter = apps.get_model("ipfabric_netbox", "IPFabricFilter")
|
|
59
|
+
IPFabricFilterExpression = apps.get_model(
|
|
60
|
+
"ipfabric_netbox", "IPFabricFilterExpression"
|
|
61
|
+
)
|
|
62
|
+
for expressions_data in data["expressions"]:
|
|
63
|
+
IPFabricFilterExpression.objects.using(db_alias).create(**expressions_data)
|
|
64
|
+
for filter_data in data["filters"]:
|
|
65
|
+
filter_data["filter_type"] = "and"
|
|
66
|
+
endpoints = [
|
|
67
|
+
IPFabricEndpoint.objects.filter(endpoint=endpoint).first()
|
|
68
|
+
for endpoint in filter_data.pop("endpoints", [])
|
|
69
|
+
]
|
|
70
|
+
expressions = [
|
|
71
|
+
IPFabricFilterExpression.objects.filter(name=expr).first()
|
|
72
|
+
for expr in filter_data.pop("expressions", [])
|
|
73
|
+
]
|
|
74
|
+
_filter = IPFabricFilter.objects.using(db_alias).create(**filter_data)
|
|
75
|
+
_filter.endpoints.set(endpoints)
|
|
76
|
+
_filter.expressions.set(expressions)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_filter_data() -> dict:
|
|
80
|
+
for data_file in importlib.resources.files("ipfabric_netbox.data").iterdir():
|
|
81
|
+
if data_file.name != "filters.json":
|
|
82
|
+
continue
|
|
83
|
+
with open(data_file, "rb") as data_file:
|
|
84
|
+
return json.load(data_file)
|
|
85
|
+
raise FileNotFoundError("'filters.json' not found in installed package")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# endregion Filter Creation
|