netbox-plugin-dns 1.0.7__py3-none-any.whl → 1.1.0b1__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 netbox-plugin-dns might be problematic. Click here for more details.
- netbox_dns/__init__.py +21 -5
- netbox_dns/api/nested_serializers.py +16 -17
- netbox_dns/api/serializers.py +1 -0
- netbox_dns/api/serializers_/prefix.py +18 -0
- netbox_dns/api/serializers_/record.py +0 -1
- netbox_dns/api/serializers_/view.py +34 -2
- netbox_dns/api/urls.py +3 -0
- netbox_dns/api/views.py +36 -0
- netbox_dns/fields/__init__.py +1 -0
- netbox_dns/fields/ipam.py +18 -0
- netbox_dns/filtersets/record.py +1 -1
- netbox_dns/filtersets/view.py +16 -0
- netbox_dns/forms/view.py +116 -4
- netbox_dns/forms/zone.py +0 -8
- netbox_dns/graphql/schema.py +40 -16
- netbox_dns/graphql/types.py +1 -0
- netbox_dns/management/commands/setup_autodns.py +120 -0
- netbox_dns/migrations/0007_view_prefixes.py +18 -0
- netbox_dns/models/__init__.py +0 -2
- netbox_dns/models/contact.py +3 -9
- netbox_dns/models/nameserver.py +3 -8
- netbox_dns/models/record.py +71 -17
- netbox_dns/models/record_template.py +1 -4
- netbox_dns/models/registrar.py +1 -7
- netbox_dns/models/view.py +7 -9
- netbox_dns/models/zone.py +28 -29
- netbox_dns/models/zone_template.py +5 -8
- netbox_dns/signals/ipam_autodns.py +138 -0
- netbox_dns/tables/contact.py +1 -0
- netbox_dns/tables/nameserver.py +7 -1
- netbox_dns/tables/record.py +30 -10
- netbox_dns/tables/record_template.py +17 -0
- netbox_dns/tables/registrar.py +2 -0
- netbox_dns/tables/view.py +32 -3
- netbox_dns/tables/zone.py +15 -0
- netbox_dns/tables/zone_template.py +16 -2
- netbox_dns/template_content.py +28 -39
- netbox_dns/templates/netbox_dns/record.html +6 -6
- netbox_dns/templates/netbox_dns/view/related.html +17 -0
- netbox_dns/templates/netbox_dns/view.html +29 -0
- netbox_dns/urls/contact.py +32 -10
- netbox_dns/urls/nameserver.py +38 -14
- netbox_dns/urls/record.py +19 -7
- netbox_dns/urls/record_template.py +27 -18
- netbox_dns/urls/registrar.py +35 -11
- netbox_dns/urls/view.py +22 -8
- netbox_dns/urls/zone.py +46 -8
- netbox_dns/urls/zone_template.py +26 -16
- netbox_dns/utilities/__init__.py +2 -74
- netbox_dns/utilities/conversions.py +83 -0
- netbox_dns/utilities/ipam_autodns.py +205 -0
- netbox_dns/validators/dns_name.py +0 -9
- netbox_dns/views/contact.py +1 -0
- netbox_dns/views/nameserver.py +3 -7
- netbox_dns/views/record.py +2 -9
- netbox_dns/views/record_template.py +1 -1
- netbox_dns/views/registrar.py +1 -0
- netbox_dns/views/view.py +1 -6
- netbox_dns/views/zone.py +6 -7
- netbox_dns/views/zone_template.py +2 -2
- {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/METADATA +2 -2
- {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/RECORD +64 -61
- netbox_dns/management/commands/setup_coupling.py +0 -109
- netbox_dns/migrations/0007_alter_ordering_options.py +0 -25
- netbox_dns/signals/ipam_coupling.py +0 -168
- netbox_dns/templates/netbox_dns/related_dns_objects.html +0 -21
- netbox_dns/utilities/ipam_coupling.py +0 -112
- {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/LICENSE +0 -0
- {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/WHEEL +0 -0
netbox_dns/__init__.py
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
|
|
1
3
|
from netbox.plugins import PluginConfig
|
|
4
|
+
from ipam.choices import IPAddressStatusChoices
|
|
5
|
+
|
|
6
|
+
from netbox_dns.choices import RecordTypeChoices
|
|
2
7
|
|
|
3
|
-
__version__ = "1.0
|
|
8
|
+
__version__ = "1.1.0-beta1"
|
|
4
9
|
|
|
5
10
|
|
|
6
11
|
class DNSConfig(PluginConfig):
|
|
7
12
|
name = "netbox_dns"
|
|
8
13
|
verbose_name = "NetBox DNS"
|
|
9
14
|
description = "NetBox plugin for DNS data"
|
|
10
|
-
min_version = "4.
|
|
15
|
+
min_version = "4.1.0-beta1"
|
|
11
16
|
version = __version__
|
|
12
17
|
author = "Peter Eckel"
|
|
13
18
|
author_email = "pete@netbox-dns.org"
|
|
@@ -20,13 +25,18 @@ class DNSConfig(PluginConfig):
|
|
|
20
25
|
"zone_soa_retry": 7200,
|
|
21
26
|
"zone_soa_expire": 2419200,
|
|
22
27
|
"zone_soa_minimum": 3600,
|
|
23
|
-
"
|
|
28
|
+
"autodns_disabled": False,
|
|
29
|
+
"autodns_ipaddress_active_status": [
|
|
30
|
+
IPAddressStatusChoices.STATUS_ACTIVE,
|
|
31
|
+
IPAddressStatusChoices.STATUS_DHCP,
|
|
32
|
+
IPAddressStatusChoices.STATUS_SLAAC,
|
|
33
|
+
],
|
|
24
34
|
"tolerate_characters_in_zone_labels": "",
|
|
25
35
|
"tolerate_underscores_in_labels": False,
|
|
26
36
|
"tolerate_underscores_in_hostnames": False, # Deprecated, will be removed in 1.2.0
|
|
27
37
|
"tolerate_leading_underscore_types": [
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
RecordTypeChoices.TXT,
|
|
39
|
+
RecordTypeChoices.SRV,
|
|
30
40
|
],
|
|
31
41
|
"tolerate_non_rfc1035_types": [],
|
|
32
42
|
"enable_root_zones": False,
|
|
@@ -37,6 +47,12 @@ class DNSConfig(PluginConfig):
|
|
|
37
47
|
}
|
|
38
48
|
base_url = "netbox-dns"
|
|
39
49
|
|
|
50
|
+
def ready(self):
|
|
51
|
+
super().ready()
|
|
52
|
+
|
|
53
|
+
if not settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
|
|
54
|
+
from netbox_dns.signals import ipam_autodns
|
|
55
|
+
|
|
40
56
|
|
|
41
57
|
#
|
|
42
58
|
# Initialize plugin config
|
|
@@ -59,22 +59,6 @@ class NestedZoneSerializer(WritableNestedSerializer):
|
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
class NestedZoneTemplateSerializer(WritableNestedSerializer):
|
|
63
|
-
url = serializers.HyperlinkedIdentityField(
|
|
64
|
-
view_name="plugins-api:netbox_dns-api:zonetemplate-detail"
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
class Meta:
|
|
68
|
-
model = ZoneTemplate
|
|
69
|
-
fields = (
|
|
70
|
-
"id",
|
|
71
|
-
"url",
|
|
72
|
-
"name",
|
|
73
|
-
"display",
|
|
74
|
-
"description",
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
62
|
class NestedRecordSerializer(WritableNestedSerializer):
|
|
79
63
|
url = serializers.HyperlinkedIdentityField(
|
|
80
64
|
view_name="plugins-api:netbox_dns-api:record-detail"
|
|
@@ -102,7 +86,6 @@ class NestedRecordSerializer(WritableNestedSerializer):
|
|
|
102
86
|
"status",
|
|
103
87
|
"ttl",
|
|
104
88
|
"zone",
|
|
105
|
-
"managed",
|
|
106
89
|
"active",
|
|
107
90
|
]
|
|
108
91
|
|
|
@@ -126,3 +109,19 @@ class NestedRecordTemplateSerializer(WritableNestedSerializer):
|
|
|
126
109
|
"ttl",
|
|
127
110
|
"description",
|
|
128
111
|
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class NestedZoneTemplateSerializer(WritableNestedSerializer):
|
|
115
|
+
url = serializers.HyperlinkedIdentityField(
|
|
116
|
+
view_name="plugins-api:netbox_dns-api:zonetemplate-detail"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
class Meta:
|
|
120
|
+
model = ZoneTemplate
|
|
121
|
+
fields = (
|
|
122
|
+
"id",
|
|
123
|
+
"url",
|
|
124
|
+
"name",
|
|
125
|
+
"display",
|
|
126
|
+
"description",
|
|
127
|
+
)
|
netbox_dns/api/serializers.py
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from rest_framework.serializers import HyperlinkedIdentityField
|
|
2
|
+
|
|
3
|
+
from ipam.api.serializers import PrefixSerializer as IPAMPrefixSerializer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = ("PrefixSerializer",)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PrefixSerializer(IPAMPrefixSerializer):
|
|
10
|
+
url = HyperlinkedIdentityField(view_name="plugins-api:netbox_dns-api:prefix-detail")
|
|
11
|
+
|
|
12
|
+
def to_representation(self, instance):
|
|
13
|
+
representation = super().to_representation(instance)
|
|
14
|
+
|
|
15
|
+
if instance.vrf is not None:
|
|
16
|
+
representation["display"] += f" [{instance.vrf.name}]"
|
|
17
|
+
|
|
18
|
+
return representation
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from rest_framework import serializers
|
|
2
2
|
|
|
3
3
|
from netbox.api.serializers import NetBoxModelSerializer
|
|
4
|
-
from tenancy.api.
|
|
4
|
+
from tenancy.api.serializers import TenantSerializer
|
|
5
|
+
from ipam.api.serializers import PrefixSerializer
|
|
5
6
|
|
|
6
7
|
from netbox_dns.models import View
|
|
7
8
|
|
|
@@ -16,8 +17,38 @@ class ViewSerializer(NetBoxModelSerializer):
|
|
|
16
17
|
default_view = serializers.BooleanField(
|
|
17
18
|
read_only=True,
|
|
18
19
|
)
|
|
20
|
+
prefixes = PrefixSerializer(
|
|
21
|
+
many=True,
|
|
22
|
+
nested=True,
|
|
23
|
+
read_only=False,
|
|
24
|
+
required=False,
|
|
25
|
+
help_text="IPAM Prefixes assigned to the View",
|
|
26
|
+
)
|
|
27
|
+
tenant = TenantSerializer(
|
|
28
|
+
nested=True,
|
|
29
|
+
required=False,
|
|
30
|
+
allow_null=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def create(self, validated_data):
|
|
34
|
+
prefixes = validated_data.pop("prefixes", None)
|
|
35
|
+
|
|
36
|
+
view = super().create(validated_data)
|
|
37
|
+
|
|
38
|
+
if prefixes is not None:
|
|
39
|
+
view.prefixes.set(prefixes)
|
|
40
|
+
|
|
41
|
+
return view
|
|
42
|
+
|
|
43
|
+
def update(self, instance, validated_data):
|
|
44
|
+
prefixes = validated_data.pop("prefixes", None)
|
|
45
|
+
|
|
46
|
+
view = super().update(instance, validated_data)
|
|
47
|
+
|
|
48
|
+
if prefixes is not None:
|
|
49
|
+
view.prefixes.set(prefixes)
|
|
19
50
|
|
|
20
|
-
|
|
51
|
+
return view
|
|
21
52
|
|
|
22
53
|
class Meta:
|
|
23
54
|
model = View
|
|
@@ -33,5 +64,6 @@ class ViewSerializer(NetBoxModelSerializer):
|
|
|
33
64
|
"last_updated",
|
|
34
65
|
"custom_fields",
|
|
35
66
|
"tenant",
|
|
67
|
+
"prefixes",
|
|
36
68
|
)
|
|
37
69
|
brief_fields = ("id", "url", "display", "name", "default_view", "description")
|
netbox_dns/api/urls.py
CHANGED
|
@@ -10,6 +10,7 @@ from netbox_dns.api.views import (
|
|
|
10
10
|
ContactViewSet,
|
|
11
11
|
ZoneTemplateViewSet,
|
|
12
12
|
RecordTemplateViewSet,
|
|
13
|
+
PrefixViewSet,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
router = NetBoxRouter()
|
|
@@ -24,4 +25,6 @@ router.register("contacts", ContactViewSet)
|
|
|
24
25
|
router.register("zonetemplates", ZoneTemplateViewSet)
|
|
25
26
|
router.register("recordtemplates", RecordTemplateViewSet)
|
|
26
27
|
|
|
28
|
+
router.register("prefixes", PrefixViewSet)
|
|
29
|
+
|
|
27
30
|
urlpatterns = router.urls
|
netbox_dns/api/views.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
from rest_framework import serializers
|
|
2
|
+
from rest_framework.decorators import action
|
|
3
|
+
from rest_framework.response import Response
|
|
2
4
|
from rest_framework.routers import APIRootView
|
|
3
5
|
|
|
6
|
+
from ipam.models import Prefix
|
|
7
|
+
|
|
4
8
|
from netbox.api.viewsets import NetBoxModelViewSet
|
|
5
9
|
|
|
6
10
|
from netbox_dns.api.serializers import (
|
|
@@ -12,6 +16,7 @@ from netbox_dns.api.serializers import (
|
|
|
12
16
|
ContactSerializer,
|
|
13
17
|
ZoneTemplateSerializer,
|
|
14
18
|
RecordTemplateSerializer,
|
|
19
|
+
PrefixSerializer,
|
|
15
20
|
)
|
|
16
21
|
from netbox_dns.filtersets import (
|
|
17
22
|
ViewFilterSet,
|
|
@@ -45,6 +50,12 @@ class ViewViewSet(NetBoxModelViewSet):
|
|
|
45
50
|
serializer_class = ViewSerializer
|
|
46
51
|
filterset_class = ViewFilterSet
|
|
47
52
|
|
|
53
|
+
@action(detail=True, methods=["get"])
|
|
54
|
+
def views(self, request, pk=None):
|
|
55
|
+
views = View.objects.filter(zone=pk)
|
|
56
|
+
serializer = ViewSerializer(views, many=True, context={"request": request})
|
|
57
|
+
return Response(serializer.data)
|
|
58
|
+
|
|
48
59
|
|
|
49
60
|
class ZoneViewSet(NetBoxModelViewSet):
|
|
50
61
|
queryset = Zone.objects.prefetch_related(
|
|
@@ -58,12 +69,32 @@ class ZoneViewSet(NetBoxModelViewSet):
|
|
|
58
69
|
serializer_class = ZoneSerializer
|
|
59
70
|
filterset_class = ZoneFilterSet
|
|
60
71
|
|
|
72
|
+
@action(detail=True, methods=["get"])
|
|
73
|
+
def records(self, request, pk=None):
|
|
74
|
+
records = Record.objects.filter(zone=pk)
|
|
75
|
+
serializer = RecordSerializer(records, many=True, context={"request": request})
|
|
76
|
+
return Response(serializer.data)
|
|
77
|
+
|
|
78
|
+
@action(detail=True, methods=["get"])
|
|
79
|
+
def nameservers(self, request, pk=None):
|
|
80
|
+
nameservers = NameServer.objects.filter(zones__id=pk)
|
|
81
|
+
serializer = NameServerSerializer(
|
|
82
|
+
nameservers, many=True, context={"request": request}
|
|
83
|
+
)
|
|
84
|
+
return Response(serializer.data)
|
|
85
|
+
|
|
61
86
|
|
|
62
87
|
class NameServerViewSet(NetBoxModelViewSet):
|
|
63
88
|
queryset = NameServer.objects.prefetch_related("zones", "tenant")
|
|
64
89
|
serializer_class = NameServerSerializer
|
|
65
90
|
filterset_class = NameServerFilterSet
|
|
66
91
|
|
|
92
|
+
@action(detail=True, methods=["get"])
|
|
93
|
+
def zones(self, request, pk=None):
|
|
94
|
+
zones = Zone.objects.filter(nameservers__id=pk)
|
|
95
|
+
serializer = ZoneSerializer(zones, many=True, context={"request": request})
|
|
96
|
+
return Response(serializer.data)
|
|
97
|
+
|
|
67
98
|
|
|
68
99
|
class RecordViewSet(NetBoxModelViewSet):
|
|
69
100
|
queryset = Record.objects.all().prefetch_related("zone", "zone__view", "tenant")
|
|
@@ -124,3 +155,8 @@ class RecordTemplateViewSet(NetBoxModelViewSet):
|
|
|
124
155
|
queryset = RecordTemplate.objects.all()
|
|
125
156
|
serializer_class = RecordTemplateSerializer
|
|
126
157
|
filterset_class = RecordTemplateFilterSet
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class PrefixViewSet(NetBoxModelViewSet):
|
|
161
|
+
queryset = Prefix.objects.all()
|
|
162
|
+
serializer_class = PrefixSerializer
|
netbox_dns/fields/__init__.py
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
|
2
|
+
from utilities.forms.widgets import APISelectMultiple
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = ("PrefixDynamicModelMultipleChoiceField",)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PrefixDynamicModelMultipleChoiceField(DynamicModelMultipleChoiceField):
|
|
9
|
+
def __init__(self, *args, **kwargs):
|
|
10
|
+
super().__init__(*args, **kwargs)
|
|
11
|
+
|
|
12
|
+
widget = APISelectMultiple(api_url="/api/plugins/netbox-dns/prefixes")
|
|
13
|
+
|
|
14
|
+
def label_from_instance(self, obj):
|
|
15
|
+
if obj.vrf:
|
|
16
|
+
return f"{str(obj.prefix)} [{obj.vrf.name}]"
|
|
17
|
+
|
|
18
|
+
return str(obj.prefix)
|
netbox_dns/filtersets/record.py
CHANGED
|
@@ -121,7 +121,7 @@ class RecordFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|
|
121
121
|
if not value.strip():
|
|
122
122
|
return queryset
|
|
123
123
|
qs_filter = (
|
|
124
|
-
Q(
|
|
124
|
+
Q(name__icontains=value)
|
|
125
125
|
| Q(value__icontains=value)
|
|
126
126
|
| Q(zone__name__icontains=value)
|
|
127
127
|
)
|
netbox_dns/filtersets/view.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import django_filters
|
|
2
|
+
|
|
1
3
|
from django.db.models import Q
|
|
2
4
|
|
|
3
5
|
from netbox.filtersets import NetBoxModelFilterSet
|
|
4
6
|
from tenancy.filtersets import TenancyFilterSet
|
|
7
|
+
from ipam.models import Prefix
|
|
5
8
|
|
|
6
9
|
from netbox_dns.models import View
|
|
7
10
|
|
|
@@ -10,6 +13,19 @@ __all__ = ("ViewFilterSet",)
|
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class ViewFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|
16
|
+
prefix_id = django_filters.ModelMultipleChoiceFilter(
|
|
17
|
+
queryset=Prefix.objects.all(),
|
|
18
|
+
field_name="prefixes",
|
|
19
|
+
to_field_name="id",
|
|
20
|
+
label="Prefixes ID",
|
|
21
|
+
)
|
|
22
|
+
prefix = django_filters.ModelMultipleChoiceFilter(
|
|
23
|
+
queryset=Prefix.objects.all(),
|
|
24
|
+
field_name="prefixes__prefix",
|
|
25
|
+
to_field_name="prefix",
|
|
26
|
+
label="Prefix",
|
|
27
|
+
)
|
|
28
|
+
|
|
13
29
|
class Meta:
|
|
14
30
|
model = View
|
|
15
31
|
fields = ("id", "name", "default_view", "description")
|
netbox_dns/forms/view.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
from django import forms
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from django.core.exceptions import ValidationError
|
|
4
|
+
from django.db.models import Q, Count
|
|
2
5
|
|
|
3
6
|
from netbox.forms import (
|
|
4
7
|
NetBoxModelBulkEditForm,
|
|
@@ -9,14 +12,23 @@ from netbox.forms import (
|
|
|
9
12
|
from utilities.forms.fields import (
|
|
10
13
|
TagFilterField,
|
|
11
14
|
CSVModelChoiceField,
|
|
15
|
+
CSVModelMultipleChoiceField,
|
|
12
16
|
DynamicModelChoiceField,
|
|
17
|
+
DynamicModelMultipleChoiceField,
|
|
13
18
|
)
|
|
14
19
|
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
|
|
15
20
|
from utilities.forms.rendering import FieldSet
|
|
16
21
|
from tenancy.models import Tenant
|
|
17
22
|
from tenancy.forms import TenancyForm, TenancyFilterForm
|
|
23
|
+
from ipam.models import Prefix
|
|
18
24
|
|
|
19
25
|
from netbox_dns.models import View
|
|
26
|
+
from netbox_dns.fields import PrefixDynamicModelMultipleChoiceField
|
|
27
|
+
from netbox_dns.utilities import (
|
|
28
|
+
get_ip_addresses_by_prefix,
|
|
29
|
+
update_dns_records,
|
|
30
|
+
get_views_by_prefix,
|
|
31
|
+
)
|
|
20
32
|
|
|
21
33
|
|
|
22
34
|
__all__ = (
|
|
@@ -27,22 +39,102 @@ __all__ = (
|
|
|
27
39
|
)
|
|
28
40
|
|
|
29
41
|
|
|
30
|
-
class
|
|
42
|
+
class ViewPrefixUpdateMixin:
|
|
43
|
+
def clean(self, *args, **kwargs):
|
|
44
|
+
super().clean(*args, **kwargs)
|
|
45
|
+
|
|
46
|
+
if self.instance.pk is None or "prefixes" not in self.changed_data:
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
prefixes = self.cleaned_data.get("prefixes")
|
|
50
|
+
old_prefixes = View.objects.get(pk=self.instance.pk).prefixes.all()
|
|
51
|
+
|
|
52
|
+
for prefix in prefixes.difference(old_prefixes):
|
|
53
|
+
for ip_address in get_ip_addresses_by_prefix(prefix, check_view=False):
|
|
54
|
+
try:
|
|
55
|
+
update_dns_records(ip_address, commit=False, view=self.instance)
|
|
56
|
+
except ValidationError as exc:
|
|
57
|
+
self.add_error("prefixes", exc.messages)
|
|
58
|
+
|
|
59
|
+
# +
|
|
60
|
+
# Determine the prefixes that, when removed from the view, have no direct view
|
|
61
|
+
# assignment left. These prefixes will potentially inherit from a different view,
|
|
62
|
+
# which means that they have to be validated against different zones.
|
|
63
|
+
# -
|
|
64
|
+
check_prefixes = set(
|
|
65
|
+
old_prefixes.annotate(view_count=Count("netbox_dns_views")).filter(
|
|
66
|
+
Q(view_count=1, netbox_dns_views=self.instance)
|
|
67
|
+
| Q(netbox_dns_views__isnull=True)
|
|
68
|
+
)
|
|
69
|
+
) - set(prefixes)
|
|
70
|
+
|
|
71
|
+
for check_prefix in check_prefixes:
|
|
72
|
+
# +
|
|
73
|
+
# Check whether the prefix will get a new view by inheritance from its
|
|
74
|
+
# parent. If that's the case, the IP addresses need to be checked.
|
|
75
|
+
# -
|
|
76
|
+
if (parent := check_prefix.get_parents().last()) is None:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
for view in get_views_by_prefix(parent):
|
|
80
|
+
if view == self.instance:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
for ip_address in get_ip_addresses_by_prefix(
|
|
84
|
+
check_prefix, check_view=False
|
|
85
|
+
):
|
|
86
|
+
try:
|
|
87
|
+
update_dns_records(ip_address, commit=False, view=view)
|
|
88
|
+
except ValidationError as exc:
|
|
89
|
+
self.add_error("prefixes", exc.messages)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ViewForm(ViewPrefixUpdateMixin, TenancyForm, NetBoxModelForm):
|
|
93
|
+
def __init__(self, *args, **kwargs):
|
|
94
|
+
super().__init__(*args, **kwargs)
|
|
95
|
+
|
|
96
|
+
if settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
|
|
97
|
+
del self.fields["prefixes"]
|
|
98
|
+
|
|
99
|
+
prefixes = PrefixDynamicModelMultipleChoiceField(
|
|
100
|
+
queryset=Prefix.objects.all(),
|
|
101
|
+
required=False,
|
|
102
|
+
label="IPAM Prefixes",
|
|
103
|
+
context={
|
|
104
|
+
"depth": None,
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
31
108
|
fieldsets = (
|
|
32
109
|
FieldSet("name", "default_view", "description", "tags", name="View"),
|
|
110
|
+
FieldSet("prefixes"),
|
|
33
111
|
FieldSet("tenant_group", "tenant", name="Tenancy"),
|
|
34
112
|
)
|
|
35
113
|
|
|
36
114
|
class Meta:
|
|
37
115
|
model = View
|
|
38
|
-
fields = (
|
|
116
|
+
fields = (
|
|
117
|
+
"name",
|
|
118
|
+
"default_view",
|
|
119
|
+
"description",
|
|
120
|
+
"tags",
|
|
121
|
+
"tenant",
|
|
122
|
+
"prefixes",
|
|
123
|
+
)
|
|
39
124
|
|
|
40
125
|
|
|
41
126
|
class ViewFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|
127
|
+
def __init__(self, *args, **kwargs):
|
|
128
|
+
super().__init__(*args, **kwargs)
|
|
129
|
+
|
|
130
|
+
if settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
|
|
131
|
+
del self.fields["prefix_id"]
|
|
132
|
+
|
|
42
133
|
model = View
|
|
43
134
|
fieldsets = (
|
|
44
135
|
FieldSet("q", "filter_id", "tag"),
|
|
45
136
|
FieldSet("name", "default_view", "description", name="Attributes"),
|
|
137
|
+
FieldSet("prefix_id"),
|
|
46
138
|
FieldSet("tenant_group_id", "tenant_id", name="Tenancy"),
|
|
47
139
|
)
|
|
48
140
|
|
|
@@ -56,10 +148,30 @@ class ViewFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|
|
56
148
|
description = forms.CharField(
|
|
57
149
|
required=False,
|
|
58
150
|
)
|
|
151
|
+
prefix_id = PrefixDynamicModelMultipleChoiceField(
|
|
152
|
+
queryset=Prefix.objects.all(),
|
|
153
|
+
required=False,
|
|
154
|
+
label="Prefix",
|
|
155
|
+
context={
|
|
156
|
+
"depth": None,
|
|
157
|
+
},
|
|
158
|
+
)
|
|
59
159
|
tag = TagFilterField(View)
|
|
60
160
|
|
|
61
161
|
|
|
62
|
-
class ViewImportForm(NetBoxModelImportForm):
|
|
162
|
+
class ViewImportForm(ViewPrefixUpdateMixin, NetBoxModelImportForm):
|
|
163
|
+
def __init__(self, *args, **kwargs):
|
|
164
|
+
super().__init__(*args, **kwargs)
|
|
165
|
+
|
|
166
|
+
if settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
|
|
167
|
+
del self.fields["prefixes"]
|
|
168
|
+
|
|
169
|
+
prefixes = CSVModelMultipleChoiceField(
|
|
170
|
+
queryset=Prefix.objects.all(),
|
|
171
|
+
to_field_name="id",
|
|
172
|
+
required=False,
|
|
173
|
+
help_text="Prefix IDs assigned to the view",
|
|
174
|
+
)
|
|
63
175
|
tenant = CSVModelChoiceField(
|
|
64
176
|
queryset=Tenant.objects.all(),
|
|
65
177
|
to_field_name="name",
|
|
@@ -69,7 +181,7 @@ class ViewImportForm(NetBoxModelImportForm):
|
|
|
69
181
|
|
|
70
182
|
class Meta:
|
|
71
183
|
model = View
|
|
72
|
-
fields = ("name", "description", "tenant", "tags")
|
|
184
|
+
fields = ("name", "description", "prefixes", "tenant", "tags")
|
|
73
185
|
|
|
74
186
|
|
|
75
187
|
class ViewBulkEditForm(NetBoxModelBulkEditForm):
|
netbox_dns/forms/zone.py
CHANGED
|
@@ -81,14 +81,6 @@ class ZoneTemplateUpdateMixin:
|
|
|
81
81
|
else:
|
|
82
82
|
zone_data = self.cleaned_data.copy()
|
|
83
83
|
|
|
84
|
-
custom_fields = dict()
|
|
85
|
-
for key, value in zone_data.copy().items():
|
|
86
|
-
if key.startswith("cf_"):
|
|
87
|
-
custom_fields[key[3:]] = value
|
|
88
|
-
zone_data.pop(key)
|
|
89
|
-
if custom_fields:
|
|
90
|
-
zone_data["custom_field_data"] = custom_fields
|
|
91
|
-
|
|
92
84
|
zone_data.pop("template", None)
|
|
93
85
|
zone_data.pop("tenant_group", None)
|
|
94
86
|
zone_data.pop("_init_time", None)
|
netbox_dns/graphql/schema.py
CHANGED
|
@@ -25,55 +25,79 @@ from .types import (
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
@strawberry.type
|
|
28
|
+
@strawberry.type
|
|
29
29
|
class NetBoxDNSNameServerQuery:
|
|
30
|
-
|
|
30
|
+
@strawberry.field
|
|
31
|
+
def netbox_dns_nameserver(self, id: int) -> NetBoxDNSNameServerType:
|
|
32
|
+
return NameServer.objects.get(pk=id)
|
|
33
|
+
|
|
31
34
|
netbox_dns_nameserver_list: List[NetBoxDNSNameServerType] = (
|
|
32
35
|
strawberry_django.field()
|
|
33
36
|
)
|
|
34
37
|
|
|
35
38
|
|
|
36
|
-
@strawberry.type
|
|
39
|
+
@strawberry.type
|
|
37
40
|
class NetBoxDNSViewQuery:
|
|
38
|
-
|
|
41
|
+
@strawberry.field
|
|
42
|
+
def netbox_dns_view(self, id: int) -> NetBoxDNSViewType:
|
|
43
|
+
return View.objects.get(pk=id)
|
|
44
|
+
|
|
39
45
|
netbox_dns_view_list: List[NetBoxDNSViewType] = strawberry_django.field()
|
|
40
46
|
|
|
41
47
|
|
|
42
|
-
@strawberry.type
|
|
48
|
+
@strawberry.type
|
|
43
49
|
class NetBoxDNSZoneQuery:
|
|
44
|
-
|
|
50
|
+
@strawberry.field
|
|
51
|
+
def netbox_dns_zone(self, id: int) -> NetBoxDNSZoneType:
|
|
52
|
+
return Zone.objects.get(pk=id)
|
|
53
|
+
|
|
45
54
|
netbox_dns_zone_list: List[NetBoxDNSZoneType] = strawberry_django.field()
|
|
46
55
|
|
|
47
56
|
|
|
48
|
-
@strawberry.type
|
|
57
|
+
@strawberry.type
|
|
49
58
|
class NetBoxDNSRecordQuery:
|
|
50
|
-
|
|
59
|
+
@strawberry.field
|
|
60
|
+
def netbox_dns_record(self, id: int) -> NetBoxDNSRecordType:
|
|
61
|
+
return Record.objects.get(pk=id)
|
|
62
|
+
|
|
51
63
|
netbox_dns_record_list: List[NetBoxDNSRecordType] = strawberry_django.field()
|
|
52
64
|
|
|
53
65
|
|
|
54
|
-
@strawberry.type
|
|
66
|
+
@strawberry.type
|
|
55
67
|
class NetBoxDNSContactQuery:
|
|
56
|
-
|
|
68
|
+
@strawberry.field
|
|
69
|
+
def netbox_dns_contact(self, id: int) -> NetBoxDNSContactType:
|
|
70
|
+
return Contact.objects.get(pk=id)
|
|
71
|
+
|
|
57
72
|
netbox_dns_contact_list: List[NetBoxDNSContactType] = strawberry_django.field()
|
|
58
73
|
|
|
59
74
|
|
|
60
|
-
@strawberry.type
|
|
75
|
+
@strawberry.type
|
|
61
76
|
class NetBoxDNSRegistrarQuery:
|
|
62
|
-
|
|
77
|
+
@strawberry.field
|
|
78
|
+
def netbox_dns_registrar(self, id: int) -> NetBoxDNSRegistrarType:
|
|
79
|
+
return Registrar.objects.get(pk=id)
|
|
80
|
+
|
|
63
81
|
netbox_dns_registrar_list: List[NetBoxDNSRegistrarType] = strawberry_django.field()
|
|
64
82
|
|
|
65
83
|
|
|
66
|
-
@strawberry.type
|
|
84
|
+
@strawberry.type
|
|
67
85
|
class NetBoxDNSZoneTemplateQuery:
|
|
68
|
-
|
|
86
|
+
@strawberry.field
|
|
87
|
+
def netbox_dns_zone_template(self, id: int) -> NetBoxDNSZoneTemplateType:
|
|
88
|
+
return ZoneTemplate.objects.get(pk=id)
|
|
89
|
+
|
|
69
90
|
netbox_dns_zone_template_list: List[NetBoxDNSZoneTemplateType] = (
|
|
70
91
|
strawberry_django.field()
|
|
71
92
|
)
|
|
72
93
|
|
|
73
94
|
|
|
74
|
-
@strawberry.type
|
|
95
|
+
@strawberry.type
|
|
75
96
|
class NetBoxDNSRecordTemplateQuery:
|
|
76
|
-
|
|
97
|
+
@strawberry.field
|
|
98
|
+
def netbox_dns_record_template(self, id: int) -> NetBoxDNSRecordTemplateType:
|
|
99
|
+
return RecordTemplate.objects.get(pk=id)
|
|
100
|
+
|
|
77
101
|
netbox_dns_record_template_list: List[NetBoxDNSRecordTemplateType] = (
|
|
78
102
|
strawberry_django.field()
|
|
79
103
|
)
|
netbox_dns/graphql/types.py
CHANGED
|
@@ -40,6 +40,7 @@ class NetBoxDNSViewType(NetBoxObjectType):
|
|
|
40
40
|
name: str
|
|
41
41
|
description: str
|
|
42
42
|
tenant: Annotated["TenantType", strawberry.lazy("tenancy.graphql.types")] | None
|
|
43
|
+
prefixes: List[Annotated["PrefixType", strawberry.lazy("ipam.graphql.types")]]
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
@strawberry_django.type(Zone, fields="__all__", filters=NetBoxDNSZoneFilter)
|