netbox-plugin-dns 1.1.0__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 +6 -9
- netbox_dns/api/nested_serializers.py +16 -17
- netbox_dns/api/serializers.py +1 -1
- netbox_dns/api/serializers_/{registration_contact.py → contact.py} +5 -5
- netbox_dns/api/serializers_/record.py +0 -1
- netbox_dns/api/serializers_/zone.py +5 -5
- netbox_dns/api/serializers_/zone_template.py +5 -5
- netbox_dns/api/urls.py +2 -2
- netbox_dns/api/views.py +35 -9
- netbox_dns/fields/ipam.py +3 -0
- netbox_dns/filtersets/__init__.py +1 -1
- netbox_dns/filtersets/{registration_contact.py → contact.py} +4 -4
- netbox_dns/filtersets/record.py +1 -1
- netbox_dns/filtersets/zone.py +15 -15
- netbox_dns/filtersets/zone_template.py +15 -15
- netbox_dns/forms/__init__.py +1 -1
- netbox_dns/forms/{registration_contact.py → contact.py} +16 -16
- netbox_dns/forms/view.py +7 -95
- netbox_dns/forms/zone.py +17 -22
- netbox_dns/forms/zone_template.py +13 -13
- netbox_dns/graphql/__init__.py +2 -2
- netbox_dns/graphql/filters.py +5 -5
- netbox_dns/graphql/schema.py +44 -24
- netbox_dns/graphql/types.py +12 -40
- netbox_dns/management/commands/{setup_dnssync.py → setup_autodns.py} +28 -48
- netbox_dns/migrations/{0008_view_prefixes.py → 0007_view_prefixes.py} +1 -1
- netbox_dns/models/__init__.py +1 -1
- netbox_dns/models/{registration_contact.py → contact.py} +9 -15
- netbox_dns/models/nameserver.py +3 -8
- netbox_dns/models/record.py +28 -93
- netbox_dns/models/record_template.py +1 -4
- netbox_dns/models/registrar.py +1 -7
- netbox_dns/models/view.py +2 -9
- netbox_dns/models/zone.py +22 -59
- netbox_dns/models/zone_template.py +9 -12
- netbox_dns/navigation.py +7 -7
- netbox_dns/signals/ipam_autodns.py +138 -0
- netbox_dns/tables/__init__.py +1 -1
- netbox_dns/tables/{registration_contact.py → contact.py} +6 -5
- netbox_dns/tables/nameserver.py +7 -1
- netbox_dns/tables/record.py +30 -43
- netbox_dns/tables/record_template.py +17 -0
- netbox_dns/tables/registrar.py +2 -0
- netbox_dns/tables/view.py +8 -1
- netbox_dns/tables/zone.py +15 -0
- netbox_dns/tables/zone_template.py +16 -2
- netbox_dns/template_content.py +2 -14
- netbox_dns/templates/netbox_dns/{registrationcontact.html → contact.html} +1 -1
- netbox_dns/templates/netbox_dns/view.html +7 -3
- netbox_dns/urls/__init__.py +2 -2
- netbox_dns/urls/contact.py +51 -0
- 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 +20 -12
- netbox_dns/urls/zone.py +46 -8
- netbox_dns/urls/zone_template.py +26 -16
- netbox_dns/utilities/__init__.py +1 -1
- netbox_dns/utilities/{ipam_dnssync.py → ipam_autodns.py} +32 -122
- netbox_dns/validators/dns_name.py +0 -9
- netbox_dns/views/__init__.py +1 -1
- netbox_dns/views/contact.py +95 -0
- netbox_dns/views/nameserver.py +3 -7
- netbox_dns/views/record.py +7 -12
- netbox_dns/views/record_template.py +1 -1
- netbox_dns/views/registrar.py +1 -0
- netbox_dns/views/view.py +2 -32
- netbox_dns/views/zone.py +6 -7
- netbox_dns/views/zone_template.py +2 -2
- {netbox_plugin_dns-1.1.0.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/METADATA +2 -3
- netbox_plugin_dns-1.1.0b1.dist-info/RECORD +140 -0
- netbox_dns/management/commands/rebuild_dnssync.py +0 -18
- netbox_dns/migrations/0007_alter_ordering_options.py +0 -25
- netbox_dns/migrations/0009_rename_contact_registrationcontact.py +0 -27
- netbox_dns/signals/ipam_dnssync.py +0 -224
- netbox_dns/tables/ipam_dnssync.py +0 -11
- netbox_dns/templates/netbox_dns/view/button.html +0 -9
- netbox_dns/templates/netbox_dns/view/prefix.html +0 -41
- netbox_dns/urls/registration_contact.py +0 -60
- netbox_dns/views/registration_contact.py +0 -94
- netbox_plugin_dns-1.1.0.dist-info/RECORD +0 -146
- {netbox_plugin_dns-1.1.0.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/LICENSE +0 -0
- {netbox_plugin_dns-1.1.0.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/WHEEL +0 -0
netbox_dns/models/zone.py
CHANGED
|
@@ -3,8 +3,9 @@ from math import ceil
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
|
|
5
5
|
from dns import name as dns_name
|
|
6
|
-
from dns.exception import DNSException
|
|
7
6
|
from dns.rdtypes.ANY import SOA
|
|
7
|
+
from dns.exception import DNSException
|
|
8
|
+
|
|
8
9
|
from django.core.validators import (
|
|
9
10
|
MinValueValidator,
|
|
10
11
|
MaxValueValidator,
|
|
@@ -18,7 +19,6 @@ from django.dispatch import receiver
|
|
|
18
19
|
from django.conf import settings
|
|
19
20
|
|
|
20
21
|
from netbox.models import NetBoxModel
|
|
21
|
-
from netbox.models.features import ContactsMixin
|
|
22
22
|
from netbox.search import SearchIndex, register_search
|
|
23
23
|
from netbox.plugins.utils import get_plugin_config
|
|
24
24
|
from utilities.querysets import RestrictedQuerySet
|
|
@@ -28,7 +28,6 @@ from netbox_dns.choices import RecordClassChoices, RecordTypeChoices, ZoneStatus
|
|
|
28
28
|
from netbox_dns.fields import NetworkField, RFC2317NetworkField
|
|
29
29
|
from netbox_dns.utilities import (
|
|
30
30
|
update_dns_records,
|
|
31
|
-
check_dns_records,
|
|
32
31
|
get_ip_addresses_by_zone,
|
|
33
32
|
arpa_to_prefix,
|
|
34
33
|
name_to_unicode,
|
|
@@ -36,7 +35,7 @@ from netbox_dns.utilities import (
|
|
|
36
35
|
NameFormatError,
|
|
37
36
|
)
|
|
38
37
|
from netbox_dns.validators import (
|
|
39
|
-
|
|
38
|
+
validate_fqdn,
|
|
40
39
|
validate_domain_name,
|
|
41
40
|
)
|
|
42
41
|
from netbox_dns.mixins import ObjectModificationMixin
|
|
@@ -69,7 +68,7 @@ class ZoneManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
|
69
68
|
)
|
|
70
69
|
|
|
71
70
|
|
|
72
|
-
class Zone(ObjectModificationMixin,
|
|
71
|
+
class Zone(ObjectModificationMixin, NetBoxModel):
|
|
73
72
|
ACTIVE_STATUS_LIST = (ZoneStatusChoices.STATUS_ACTIVE,)
|
|
74
73
|
|
|
75
74
|
def __init__(self, *args, **kwargs):
|
|
@@ -78,7 +77,6 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
78
77
|
super().__init__(*args, **kwargs)
|
|
79
78
|
|
|
80
79
|
self._soa_serial_dirty = False
|
|
81
|
-
self._ip_addresses_checked = False
|
|
82
80
|
|
|
83
81
|
view = models.ForeignKey(
|
|
84
82
|
to="View",
|
|
@@ -192,7 +190,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
192
190
|
null=True,
|
|
193
191
|
)
|
|
194
192
|
registrant = models.ForeignKey(
|
|
195
|
-
to="
|
|
193
|
+
to="Contact",
|
|
196
194
|
on_delete=models.SET_NULL,
|
|
197
195
|
verbose_name="Registrant",
|
|
198
196
|
help_text="The owner of the domain",
|
|
@@ -200,7 +198,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
200
198
|
null=True,
|
|
201
199
|
)
|
|
202
200
|
admin_c = models.ForeignKey(
|
|
203
|
-
to="
|
|
201
|
+
to="Contact",
|
|
204
202
|
on_delete=models.SET_NULL,
|
|
205
203
|
verbose_name="Admin Contact",
|
|
206
204
|
related_name="admin_c_zones",
|
|
@@ -209,16 +207,16 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
209
207
|
null=True,
|
|
210
208
|
)
|
|
211
209
|
tech_c = models.ForeignKey(
|
|
212
|
-
to="
|
|
210
|
+
to="Contact",
|
|
213
211
|
on_delete=models.SET_NULL,
|
|
214
|
-
verbose_name="
|
|
212
|
+
verbose_name="Tech Contact",
|
|
215
213
|
related_name="tech_c_zones",
|
|
216
214
|
help_text="The technical contact for the domain",
|
|
217
215
|
blank=True,
|
|
218
216
|
null=True,
|
|
219
217
|
)
|
|
220
218
|
billing_c = models.ForeignKey(
|
|
221
|
-
to="
|
|
219
|
+
to="Contact",
|
|
222
220
|
on_delete=models.SET_NULL,
|
|
223
221
|
verbose_name="Billing Contact",
|
|
224
222
|
related_name="billing_c_zones",
|
|
@@ -249,7 +247,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
249
247
|
|
|
250
248
|
objects = ZoneManager()
|
|
251
249
|
|
|
252
|
-
clone_fields =
|
|
250
|
+
clone_fields = [
|
|
253
251
|
"view",
|
|
254
252
|
"name",
|
|
255
253
|
"status",
|
|
@@ -263,12 +261,9 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
263
261
|
"soa_expire",
|
|
264
262
|
"soa_minimum",
|
|
265
263
|
"description",
|
|
266
|
-
|
|
264
|
+
]
|
|
267
265
|
|
|
268
266
|
class Meta:
|
|
269
|
-
verbose_name = "Zone"
|
|
270
|
-
verbose_name_plural = "Zones"
|
|
271
|
-
|
|
272
267
|
ordering = (
|
|
273
268
|
"view",
|
|
274
269
|
"name",
|
|
@@ -284,7 +279,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
284
279
|
else:
|
|
285
280
|
try:
|
|
286
281
|
name = dns_name.from_text(self.name, origin=None).to_unicode()
|
|
287
|
-
except
|
|
282
|
+
except dns_name.IDNAException:
|
|
288
283
|
name = self.name
|
|
289
284
|
|
|
290
285
|
try:
|
|
@@ -312,14 +307,6 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
312
307
|
def soa_serial_dirty(self, soa_serial_dirty):
|
|
313
308
|
self._soa_serial_dirty = soa_serial_dirty
|
|
314
309
|
|
|
315
|
-
@property
|
|
316
|
-
def ip_addresses_checked(self):
|
|
317
|
-
return self._ip_addresses_checked
|
|
318
|
-
|
|
319
|
-
@ip_addresses_checked.setter
|
|
320
|
-
def ip_addresses_checked(self, ip_addresses_checked):
|
|
321
|
-
self._ip_addresses_checked = ip_addresses_checked
|
|
322
|
-
|
|
323
310
|
@property
|
|
324
311
|
def display_name(self):
|
|
325
312
|
return name_to_unicode(self.name)
|
|
@@ -633,7 +620,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
633
620
|
raise ValidationError("soa_rname not set and no default value defined")
|
|
634
621
|
try:
|
|
635
622
|
dns_name.from_text(self.soa_rname, origin=dns_name.root)
|
|
636
|
-
|
|
623
|
+
validate_fqdn(self.soa_rname)
|
|
637
624
|
except (DNSException, ValidationError) as exc:
|
|
638
625
|
raise ValidationError(
|
|
639
626
|
{
|
|
@@ -653,7 +640,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
653
640
|
old_zone = Zone.objects.get(pk=self.pk)
|
|
654
641
|
if not self.soa_serial_auto:
|
|
655
642
|
self.check_soa_serial_increment(old_zone.soa_serial, self.soa_serial)
|
|
656
|
-
|
|
643
|
+
else:
|
|
657
644
|
try:
|
|
658
645
|
self.check_soa_serial_increment(
|
|
659
646
|
old_zone.soa_serial, self.get_auto_serial()
|
|
@@ -665,26 +652,6 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
665
652
|
}
|
|
666
653
|
)
|
|
667
654
|
|
|
668
|
-
if (
|
|
669
|
-
not self.ip_addresses_checked
|
|
670
|
-
and old_zone.name != self.name
|
|
671
|
-
or old_zone.view != self.view
|
|
672
|
-
):
|
|
673
|
-
ip_addresses = IPAddress.objects.filter(
|
|
674
|
-
netbox_dns_records__in=self.record_set.filter(
|
|
675
|
-
ipam_ip_address__isnull=False
|
|
676
|
-
)
|
|
677
|
-
)
|
|
678
|
-
ip_addresses |= get_ip_addresses_by_zone(self)
|
|
679
|
-
|
|
680
|
-
for ip_address in ip_addresses.distinct():
|
|
681
|
-
try:
|
|
682
|
-
check_dns_records(ip_address, zone=self)
|
|
683
|
-
except ValidationError as exc:
|
|
684
|
-
raise ValidationError(exc.messages)
|
|
685
|
-
|
|
686
|
-
self.ip_addresses_checked = True
|
|
687
|
-
|
|
688
655
|
if self.is_reverse_zone:
|
|
689
656
|
self.arpa_network = self.network_from_name
|
|
690
657
|
|
|
@@ -799,13 +766,12 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
799
766
|
|
|
800
767
|
elif changed_fields is not None and {"name", "view", "status"} & changed_fields:
|
|
801
768
|
for address_record in self.record_set.filter(
|
|
802
|
-
type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA)
|
|
803
|
-
ipam_ip_address__isnull=True,
|
|
769
|
+
type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA)
|
|
804
770
|
):
|
|
805
771
|
address_record.save(update_fields=["ptr_record"])
|
|
806
772
|
|
|
807
773
|
if changed_fields is not None and "name" in changed_fields:
|
|
808
|
-
for _record in self.record_set.
|
|
774
|
+
for _record in self.record_set.all():
|
|
809
775
|
_record.save(
|
|
810
776
|
update_fields=["fqdn"],
|
|
811
777
|
save_zone_serial=False,
|
|
@@ -814,14 +780,13 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
814
780
|
)
|
|
815
781
|
|
|
816
782
|
if changed_fields is None or {"name", "view"} & changed_fields:
|
|
817
|
-
|
|
818
|
-
|
|
783
|
+
update_ip_addresses = IPAddress.objects.filter(
|
|
784
|
+
pk__in=self.record_set.filter(
|
|
819
785
|
ipam_ip_address__isnull=False
|
|
820
|
-
)
|
|
786
|
+
).values_list("ipam_ip_address", flat=True)
|
|
821
787
|
)
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
for ip_address in ip_addresses.distinct():
|
|
788
|
+
update_ip_addresses |= get_ip_addresses_by_zone(self)
|
|
789
|
+
for ip_address in update_ip_addresses:
|
|
825
790
|
update_dns_records(ip_address)
|
|
826
791
|
|
|
827
792
|
self.save_soa_serial()
|
|
@@ -864,9 +829,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
864
829
|
netbox_dns_records__in=self.record_set.filter(
|
|
865
830
|
ipam_ip_address__isnull=False
|
|
866
831
|
)
|
|
867
|
-
)
|
|
868
|
-
.distinct()
|
|
869
|
-
.values_list("pk", flat=True)
|
|
832
|
+
).values_list("pk", flat=True)
|
|
870
833
|
)
|
|
871
834
|
|
|
872
835
|
super().delete(*args, **kwargs)
|
|
@@ -47,7 +47,7 @@ class ZoneTemplate(NetBoxModel):
|
|
|
47
47
|
null=True,
|
|
48
48
|
)
|
|
49
49
|
registrant = models.ForeignKey(
|
|
50
|
-
to="
|
|
50
|
+
to="Contact",
|
|
51
51
|
on_delete=models.SET_NULL,
|
|
52
52
|
related_name="+",
|
|
53
53
|
help_text="The owner of the domain",
|
|
@@ -55,7 +55,7 @@ class ZoneTemplate(NetBoxModel):
|
|
|
55
55
|
null=True,
|
|
56
56
|
)
|
|
57
57
|
admin_c = models.ForeignKey(
|
|
58
|
-
to="
|
|
58
|
+
to="Contact",
|
|
59
59
|
on_delete=models.SET_NULL,
|
|
60
60
|
verbose_name="Admin contact",
|
|
61
61
|
related_name="+",
|
|
@@ -64,7 +64,7 @@ class ZoneTemplate(NetBoxModel):
|
|
|
64
64
|
null=True,
|
|
65
65
|
)
|
|
66
66
|
tech_c = models.ForeignKey(
|
|
67
|
-
to="
|
|
67
|
+
to="Contact",
|
|
68
68
|
on_delete=models.SET_NULL,
|
|
69
69
|
verbose_name="Tech contact",
|
|
70
70
|
related_name="+",
|
|
@@ -73,7 +73,7 @@ class ZoneTemplate(NetBoxModel):
|
|
|
73
73
|
null=True,
|
|
74
74
|
)
|
|
75
75
|
billing_c = models.ForeignKey(
|
|
76
|
-
to="
|
|
76
|
+
to="Contact",
|
|
77
77
|
on_delete=models.SET_NULL,
|
|
78
78
|
verbose_name="Billing contact",
|
|
79
79
|
related_name="+",
|
|
@@ -82,7 +82,7 @@ class ZoneTemplate(NetBoxModel):
|
|
|
82
82
|
null=True,
|
|
83
83
|
)
|
|
84
84
|
|
|
85
|
-
clone_fields =
|
|
85
|
+
clone_fields = [
|
|
86
86
|
"description",
|
|
87
87
|
"nameservers",
|
|
88
88
|
"record_templates",
|
|
@@ -92,22 +92,19 @@ class ZoneTemplate(NetBoxModel):
|
|
|
92
92
|
"admin_c",
|
|
93
93
|
"tech_c",
|
|
94
94
|
"billing_c",
|
|
95
|
-
|
|
95
|
+
]
|
|
96
96
|
|
|
97
|
-
template_fields =
|
|
97
|
+
template_fields = [
|
|
98
98
|
"tenant",
|
|
99
99
|
"registrar",
|
|
100
100
|
"registrant",
|
|
101
101
|
"admin_c",
|
|
102
102
|
"tech_c",
|
|
103
103
|
"billing_c",
|
|
104
|
-
|
|
104
|
+
]
|
|
105
105
|
|
|
106
106
|
class Meta:
|
|
107
|
-
|
|
108
|
-
verbose_name_plural = "Zone Templates"
|
|
109
|
-
|
|
110
|
-
ordering = ("name",)
|
|
107
|
+
ordering = ["name"]
|
|
111
108
|
|
|
112
109
|
def __str__(self):
|
|
113
110
|
return str(self.name)
|
netbox_dns/navigation.py
CHANGED
|
@@ -151,21 +151,21 @@ registrar_menu_item = PluginMenuItem(
|
|
|
151
151
|
)
|
|
152
152
|
|
|
153
153
|
contact_menu_item = PluginMenuItem(
|
|
154
|
-
link="plugins:netbox_dns:
|
|
155
|
-
link_text="
|
|
156
|
-
permissions=["netbox_dns.
|
|
154
|
+
link="plugins:netbox_dns:contact_list",
|
|
155
|
+
link_text="Contacts",
|
|
156
|
+
permissions=["netbox_dns.view_contact"],
|
|
157
157
|
buttons=(
|
|
158
158
|
PluginMenuButton(
|
|
159
|
-
"plugins:netbox_dns:
|
|
159
|
+
"plugins:netbox_dns:contact_add",
|
|
160
160
|
"Add",
|
|
161
161
|
"mdi mdi-plus-thick",
|
|
162
|
-
permissions=["netbox_dns.
|
|
162
|
+
permissions=["netbox_dns.add_contact"],
|
|
163
163
|
),
|
|
164
164
|
PluginMenuButton(
|
|
165
|
-
"plugins:netbox_dns:
|
|
165
|
+
"plugins:netbox_dns:contact_import",
|
|
166
166
|
"Import",
|
|
167
167
|
"mdi mdi-upload",
|
|
168
|
-
permissions=["netbox_dns.
|
|
168
|
+
permissions=["netbox_dns.add_contact"],
|
|
169
169
|
),
|
|
170
170
|
),
|
|
171
171
|
)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from netaddr import IPNetwork
|
|
2
|
+
|
|
3
|
+
from django.dispatch import receiver
|
|
4
|
+
from django.db.models.signals import pre_delete, pre_save, post_save, m2m_changed
|
|
5
|
+
from django.core.exceptions import ValidationError
|
|
6
|
+
|
|
7
|
+
from netbox.context import current_request
|
|
8
|
+
from netbox.signals import post_clean
|
|
9
|
+
from ipam.models import IPAddress, Prefix
|
|
10
|
+
from utilities.exceptions import AbortRequest
|
|
11
|
+
|
|
12
|
+
from netbox_dns.models import view as _view
|
|
13
|
+
from netbox_dns.utilities import (
|
|
14
|
+
update_dns_records,
|
|
15
|
+
delete_dns_records,
|
|
16
|
+
get_views_by_prefix,
|
|
17
|
+
get_ip_addresses_by_prefix,
|
|
18
|
+
get_ip_addresses_by_view,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@receiver(post_clean, sender=IPAddress)
|
|
23
|
+
def ipam_autodns_ipaddress_post_clean(instance, **kwargs):
|
|
24
|
+
if not isinstance(instance.address, IPNetwork):
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
update_dns_records(instance, commit=False)
|
|
29
|
+
except ValidationError as exc:
|
|
30
|
+
if hasattr(exc, "error_dict"):
|
|
31
|
+
for field in ("name", "ttl", "value", "type"):
|
|
32
|
+
value = exc.error_dict.pop(field, None)
|
|
33
|
+
if value is not None:
|
|
34
|
+
raise ValidationError({"dns_name": value})
|
|
35
|
+
|
|
36
|
+
raise exc
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@receiver(pre_delete, sender=IPAddress)
|
|
40
|
+
def ipam_autodns_ipaddress_pre_delete(instance, **kwargs):
|
|
41
|
+
delete_dns_records(instance)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@receiver(pre_save, sender=IPAddress)
|
|
45
|
+
def ipam_autodns_ipaddress_post_save(instance, **kwargs):
|
|
46
|
+
update_dns_records(instance, commit=False)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@receiver(post_save, sender=IPAddress)
|
|
50
|
+
def ipam_autodns_ipaddress_post_save(instance, **kwargs):
|
|
51
|
+
update_dns_records(instance)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@receiver(pre_save, sender=Prefix)
|
|
55
|
+
def ipam_autodns_prefix_pre_save(instance, **kwargs):
|
|
56
|
+
"""
|
|
57
|
+
Changes that modify the prefix hierarchy cannot be validated properly before
|
|
58
|
+
commiting them. So the solution in this case is to remove a prefix whose
|
|
59
|
+
VRF or network has changed from all views it currently is assigned to.
|
|
60
|
+
"""
|
|
61
|
+
if instance.pk is None or not instance.netbox_dns_views.exists():
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
saved_prefix = Prefix.objects.get(pk=instance.pk)
|
|
65
|
+
if saved_prefix.prefix != instance.prefix or saved_prefix.vrf != instance.vrf:
|
|
66
|
+
for view in saved_prefix.netbox_dns_views.all():
|
|
67
|
+
view.prefixes.remove(saved_prefix)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@receiver(pre_delete, sender=Prefix)
|
|
71
|
+
def ipam_autodns_prefix_pre_delete(instance, **kwargs):
|
|
72
|
+
parent = instance.get_parents().last()
|
|
73
|
+
request = current_request.get()
|
|
74
|
+
|
|
75
|
+
if parent is not None and get_views_by_prefix(instance) != get_views_by_prefix(
|
|
76
|
+
parent
|
|
77
|
+
):
|
|
78
|
+
try:
|
|
79
|
+
for prefix in instance.get_children().filter(
|
|
80
|
+
_depth=instance.depth + 1, netbox_dns_views__isnull=True
|
|
81
|
+
):
|
|
82
|
+
for ip_address in get_ip_addresses_by_prefix(prefix):
|
|
83
|
+
update_dns_records(ip_address, commit=False)
|
|
84
|
+
except ValidationError as exc:
|
|
85
|
+
if request is not None:
|
|
86
|
+
raise AbortRequest(
|
|
87
|
+
f"Prefix deletion would cause DNS errors: {exc.messages[0]} "
|
|
88
|
+
"Please review DNS View assignments for this and the parent prefix"
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
raise exc
|
|
92
|
+
|
|
93
|
+
# +
|
|
94
|
+
# CAUTION: This only works because the NetBox workaround for an ancient
|
|
95
|
+
# Django bug (see https://code.djangoproject.com/ticket/17688) has already
|
|
96
|
+
# removed the relations between the prefix and the views when this signal
|
|
97
|
+
# handler runs.
|
|
98
|
+
#
|
|
99
|
+
# Should anything be fixed, this code will stop working and need to be
|
|
100
|
+
# revisited.
|
|
101
|
+
#
|
|
102
|
+
# The NetBox workaround only works for requests, not for model level
|
|
103
|
+
# operations. The following code replicates it for non-requests.
|
|
104
|
+
# -
|
|
105
|
+
if request is None:
|
|
106
|
+
for view in instance.netbox_dns_views.all():
|
|
107
|
+
view.snapshot()
|
|
108
|
+
view.prefixes.remove(instance)
|
|
109
|
+
|
|
110
|
+
for ip_address in get_ip_addresses_by_prefix(instance):
|
|
111
|
+
update_dns_records(ip_address)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@receiver(m2m_changed, sender=_view.View.prefixes.through)
|
|
115
|
+
def ipam_autodns_view_prefix_changed(**kwargs):
|
|
116
|
+
action = kwargs.get("action")
|
|
117
|
+
request = current_request.get()
|
|
118
|
+
|
|
119
|
+
# +
|
|
120
|
+
# Handle all post_add and post_remove signals except the ones directly
|
|
121
|
+
# handled by the pre_delete handler for the Prefix model.
|
|
122
|
+
#
|
|
123
|
+
# Yes. This IS ugly.
|
|
124
|
+
# -
|
|
125
|
+
if action not in ("post_add", "post_remove") or (
|
|
126
|
+
request is not None
|
|
127
|
+
and action == "post_remove"
|
|
128
|
+
and (
|
|
129
|
+
request.path.startswith("/ipam/prefixes/")
|
|
130
|
+
or request.path.startswith("/api/ipam/prefixes/")
|
|
131
|
+
)
|
|
132
|
+
):
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
check_view = action != "post_remove"
|
|
136
|
+
for prefix in Prefix.objects.filter(pk__in=kwargs.get("pk_set")):
|
|
137
|
+
for ip_address in get_ip_addresses_by_prefix(prefix, check_view=check_view):
|
|
138
|
+
update_dns_records(ip_address)
|
netbox_dns/tables/__init__.py
CHANGED
|
@@ -2,23 +2,24 @@ import django_tables2 as tables
|
|
|
2
2
|
|
|
3
3
|
from netbox.tables import NetBoxTable, TagColumn
|
|
4
4
|
|
|
5
|
-
from netbox_dns.models import
|
|
5
|
+
from netbox_dns.models import Contact
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
__all__ = ("
|
|
8
|
+
__all__ = ("ContactTable",)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class ContactTable(NetBoxTable):
|
|
12
12
|
contact_id = tables.Column(
|
|
13
13
|
linkify=True,
|
|
14
14
|
)
|
|
15
15
|
tags = TagColumn(
|
|
16
|
-
url_name="plugins:netbox_dns:
|
|
16
|
+
url_name="plugins:netbox_dns:contact_list",
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
class Meta(NetBoxTable.Meta):
|
|
20
|
-
model =
|
|
20
|
+
model = Contact
|
|
21
21
|
fields = (
|
|
22
|
+
"contact_id",
|
|
22
23
|
"name",
|
|
23
24
|
"description",
|
|
24
25
|
"organization",
|
netbox_dns/tables/nameserver.py
CHANGED
|
@@ -22,7 +22,13 @@ class NameServerTable(TenancyColumnsMixin, NetBoxTable):
|
|
|
22
22
|
|
|
23
23
|
class Meta(NetBoxTable.Meta):
|
|
24
24
|
model = NameServer
|
|
25
|
-
fields = (
|
|
25
|
+
fields = (
|
|
26
|
+
"name",
|
|
27
|
+
"description",
|
|
28
|
+
"tags",
|
|
29
|
+
"tenant",
|
|
30
|
+
"tenant_group",
|
|
31
|
+
)
|
|
26
32
|
default_columns = (
|
|
27
33
|
"name",
|
|
28
34
|
"tags",
|
netbox_dns/tables/record.py
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import django_tables2 as tables
|
|
2
|
-
from django.utils.html import format_html
|
|
3
|
-
|
|
4
2
|
|
|
5
3
|
from netbox.tables import (
|
|
6
4
|
NetBoxTable,
|
|
@@ -13,10 +11,6 @@ from tenancy.tables import TenancyColumnsMixin
|
|
|
13
11
|
from netbox_dns.models import Record
|
|
14
12
|
from netbox_dns.utilities import value_to_unicode
|
|
15
13
|
|
|
16
|
-
import logging
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger("netbox_dns")
|
|
19
|
-
|
|
20
14
|
|
|
21
15
|
__all__ = (
|
|
22
16
|
"RecordTable",
|
|
@@ -29,18 +23,10 @@ class RecordBaseTable(TenancyColumnsMixin, NetBoxTable):
|
|
|
29
23
|
zone = tables.Column(
|
|
30
24
|
linkify=True,
|
|
31
25
|
)
|
|
32
|
-
view = tables.Column(
|
|
33
|
-
accessor="zone__view",
|
|
34
|
-
linkify=True,
|
|
35
|
-
)
|
|
36
26
|
type = tables.Column()
|
|
37
27
|
name = tables.Column(
|
|
38
28
|
linkify=True,
|
|
39
29
|
)
|
|
40
|
-
fqdn = tables.Column(
|
|
41
|
-
verbose_name="FQDN",
|
|
42
|
-
linkify=True,
|
|
43
|
-
)
|
|
44
30
|
value = tables.TemplateColumn(
|
|
45
31
|
template_code="{{ value|truncatechars:64 }}",
|
|
46
32
|
)
|
|
@@ -79,8 +65,20 @@ class RecordTable(RecordBaseTable):
|
|
|
79
65
|
class Meta(NetBoxTable.Meta):
|
|
80
66
|
model = Record
|
|
81
67
|
fields = (
|
|
68
|
+
"name",
|
|
69
|
+
"zone",
|
|
70
|
+
"ttl",
|
|
71
|
+
"type",
|
|
72
|
+
"value",
|
|
73
|
+
"unicode_value",
|
|
82
74
|
"status",
|
|
75
|
+
"disable_ptr",
|
|
76
|
+
"ptr_record",
|
|
77
|
+
"tags",
|
|
78
|
+
"active",
|
|
83
79
|
"description",
|
|
80
|
+
"tenant",
|
|
81
|
+
"tenant_group",
|
|
84
82
|
)
|
|
85
83
|
default_columns = (
|
|
86
84
|
"name",
|
|
@@ -102,16 +100,21 @@ class ManagedRecordTable(RecordBaseTable):
|
|
|
102
100
|
verbose_name="IPAM IP Address",
|
|
103
101
|
linkify=True,
|
|
104
102
|
)
|
|
105
|
-
related_ip_address = tables.Column(
|
|
106
|
-
verbose_name="Related IP Address",
|
|
107
|
-
empty_values=(),
|
|
108
|
-
orderable=False,
|
|
109
|
-
)
|
|
110
103
|
actions = ActionsColumn(actions=("changelog",))
|
|
111
104
|
|
|
112
105
|
class Meta(NetBoxTable.Meta):
|
|
113
106
|
model = Record
|
|
114
|
-
fields = (
|
|
107
|
+
fields = (
|
|
108
|
+
"name",
|
|
109
|
+
"zone",
|
|
110
|
+
"ttl",
|
|
111
|
+
"type",
|
|
112
|
+
"value",
|
|
113
|
+
"unicode_value",
|
|
114
|
+
"address_record",
|
|
115
|
+
"ipam_ip_address",
|
|
116
|
+
"active",
|
|
117
|
+
)
|
|
115
118
|
default_columns = (
|
|
116
119
|
"name",
|
|
117
120
|
"zone",
|
|
@@ -121,35 +124,19 @@ class ManagedRecordTable(RecordBaseTable):
|
|
|
121
124
|
"active",
|
|
122
125
|
)
|
|
123
126
|
|
|
124
|
-
def render_related_ip_address(self, record):
|
|
125
|
-
if record.ipam_ip_address is not None:
|
|
126
|
-
address = record.ipam_ip_address
|
|
127
|
-
elif (
|
|
128
|
-
hasattr(record, "address_record")
|
|
129
|
-
and record.address_record.ipam_ip_address is not None
|
|
130
|
-
):
|
|
131
|
-
address = record.address_record.ipam_ip_address
|
|
132
|
-
else:
|
|
133
|
-
return format_html("—")
|
|
134
|
-
|
|
135
|
-
return format_html(f"<a href='{address.get_absolute_url()}'>{address}</a>")
|
|
136
|
-
|
|
137
|
-
def value_related_ip_address(self, record):
|
|
138
|
-
if record.ipam_ip_address is not None:
|
|
139
|
-
return record.ipam_ip_address
|
|
140
|
-
elif (
|
|
141
|
-
hasattr(record, "address_record")
|
|
142
|
-
and record.address_record.ipam_ip_address is not None
|
|
143
|
-
):
|
|
144
|
-
return record.address_record.ipam_ip_address
|
|
145
|
-
|
|
146
127
|
|
|
147
128
|
class RelatedRecordTable(RecordBaseTable):
|
|
148
129
|
actions = ActionsColumn(actions=())
|
|
149
130
|
|
|
150
131
|
class Meta(NetBoxTable.Meta):
|
|
151
132
|
model = Record
|
|
152
|
-
fields = (
|
|
133
|
+
fields = (
|
|
134
|
+
"name",
|
|
135
|
+
"zone",
|
|
136
|
+
"type",
|
|
137
|
+
"value",
|
|
138
|
+
"unicode_value",
|
|
139
|
+
)
|
|
153
140
|
default_columns = (
|
|
154
141
|
"name",
|
|
155
142
|
"zone",
|
|
@@ -43,8 +43,18 @@ class RecordTemplateTable(TenancyColumnsMixin, NetBoxTable):
|
|
|
43
43
|
class Meta(NetBoxTable.Meta):
|
|
44
44
|
model = RecordTemplate
|
|
45
45
|
fields = (
|
|
46
|
+
"name",
|
|
47
|
+
"record_name",
|
|
48
|
+
"ttl",
|
|
49
|
+
"type",
|
|
50
|
+
"value",
|
|
51
|
+
"unicode_value",
|
|
46
52
|
"status",
|
|
53
|
+
"disable_ptr",
|
|
54
|
+
"tags",
|
|
47
55
|
"description",
|
|
56
|
+
"tenant",
|
|
57
|
+
"tenant_group",
|
|
48
58
|
)
|
|
49
59
|
default_columns = (
|
|
50
60
|
"name",
|
|
@@ -62,7 +72,14 @@ class RecordTemplateDisplayTable(RecordTemplateTable):
|
|
|
62
72
|
class Meta(NetBoxTable.Meta):
|
|
63
73
|
model = RecordTemplate
|
|
64
74
|
fields = (
|
|
75
|
+
"name",
|
|
76
|
+
"record_name",
|
|
77
|
+
"ttl",
|
|
78
|
+
"type",
|
|
79
|
+
"value",
|
|
80
|
+
"unicode_value",
|
|
65
81
|
"status",
|
|
82
|
+
"disable_ptr",
|
|
66
83
|
"description",
|
|
67
84
|
)
|
|
68
85
|
default_columns = (
|
netbox_dns/tables/registrar.py
CHANGED
|
@@ -19,11 +19,13 @@ class RegistrarTable(NetBoxTable):
|
|
|
19
19
|
class Meta(NetBoxTable.Meta):
|
|
20
20
|
model = Registrar
|
|
21
21
|
fields = (
|
|
22
|
+
"name",
|
|
22
23
|
"description",
|
|
23
24
|
"iana_id",
|
|
24
25
|
"referral_url",
|
|
25
26
|
"whois_server",
|
|
26
27
|
"abuse_email",
|
|
27
28
|
"abuse_phone",
|
|
29
|
+
"tags",
|
|
28
30
|
)
|
|
29
31
|
default_columns = ("name", "iana_id", "referral_url")
|