netbox-plugin-dns 1.1.4__py3-none-any.whl → 1.1.6__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.

Files changed (41) hide show
  1. netbox_dns/__init__.py +18 -4
  2. netbox_dns/api/views.py +2 -2
  3. netbox_dns/filtersets/record.py +0 -1
  4. netbox_dns/filtersets/zone.py +1 -2
  5. netbox_dns/forms/record.py +12 -8
  6. netbox_dns/forms/view.py +2 -3
  7. netbox_dns/forms/zone.py +8 -9
  8. netbox_dns/forms/zone_template.py +5 -5
  9. netbox_dns/graphql/types.py +37 -0
  10. netbox_dns/locale/de/LC_MESSAGES/django.mo +0 -0
  11. netbox_dns/management/commands/cleanup_database.py +4 -6
  12. netbox_dns/management/commands/rebuild_dnssync.py +3 -1
  13. netbox_dns/migrations/0011_rename_related_fields.py +63 -0
  14. netbox_dns/mixins/object_modification.py +4 -0
  15. netbox_dns/models/nameserver.py +5 -9
  16. netbox_dns/models/record.py +23 -38
  17. netbox_dns/models/record_template.py +4 -4
  18. netbox_dns/models/registration_contact.py +1 -1
  19. netbox_dns/models/view.py +2 -3
  20. netbox_dns/models/zone.py +96 -48
  21. netbox_dns/tables/record.py +14 -2
  22. netbox_dns/tables/zone.py +1 -2
  23. netbox_dns/template_content.py +16 -0
  24. netbox_dns/templates/netbox_dns/record.html +12 -0
  25. netbox_dns/templates/netbox_dns/view.html +1 -1
  26. netbox_dns/templates/netbox_dns/zone/delegation_record.html +18 -0
  27. netbox_dns/templates/netbox_dns/zone.html +1 -1
  28. netbox_dns/utilities/__init__.py +1 -0
  29. netbox_dns/utilities/dns.py +12 -0
  30. netbox_dns/utilities/ipam_dnssync.py +10 -13
  31. netbox_dns/views/nameserver.py +5 -5
  32. netbox_dns/views/record.py +44 -11
  33. netbox_dns/views/registrar.py +3 -3
  34. netbox_dns/views/registration_contact.py +2 -2
  35. netbox_dns/views/view.py +4 -4
  36. netbox_dns/views/zone.py +50 -21
  37. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.6.dist-info}/METADATA +2 -1
  38. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.6.dist-info}/RECORD +41 -38
  39. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.6.dist-info}/WHEEL +1 -1
  40. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.6.dist-info}/LICENSE +0 -0
  41. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.6.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import ipaddress
2
+ import netaddr
2
3
 
3
4
  import dns
4
5
  from dns import name as dns_name
@@ -23,11 +24,6 @@ from netbox_dns.validators import validate_generic_name, validate_record_value
23
24
  from netbox_dns.mixins import ObjectModificationMixin
24
25
  from netbox_dns.choices import RecordTypeChoices, RecordStatusChoices
25
26
 
26
- # +
27
- # This is a hack designed to break cyclic imports between Record and Zone
28
- # -
29
- from netbox_dns.models import zone
30
-
31
27
 
32
28
  __all__ = (
33
29
  "Record",
@@ -105,14 +101,8 @@ class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
105
101
  .annotate(
106
102
  active=ExpressionWrapper(
107
103
  Q(
108
- Q(zone__status__in=ZONE_ACTIVE_STATUS_LIST)
109
- & Q(
110
- Q(address_record__isnull=True)
111
- | Q(
112
- address_record__zone__status__in=ZONE_ACTIVE_STATUS_LIST
113
- )
114
- )
115
- & Q(status__in=RECORD_ACTIVE_STATUS_LIST)
104
+ zone__status__in=ZONE_ACTIVE_STATUS_LIST,
105
+ status__in=RECORD_ACTIVE_STATUS_LIST,
116
106
  ),
117
107
  output_field=BooleanField(),
118
108
  )
@@ -134,6 +124,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
134
124
  verbose_name=_("Zone"),
135
125
  to="Zone",
136
126
  on_delete=models.CASCADE,
127
+ related_name="records",
137
128
  )
138
129
  fqdn = models.CharField(
139
130
  verbose_name=_("FQDN"),
@@ -269,7 +260,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
269
260
 
270
261
  @property
271
262
  def value_fqdn(self):
272
- if self.type != RecordTypeChoices.CNAME:
263
+ if self.type not in (RecordTypeChoices.CNAME, RecordTypeChoices.NS):
273
264
  return None
274
265
 
275
266
  _zone = dns_name.from_text(self.zone.name)
@@ -331,8 +322,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
331
322
  def ptr_zone(self):
332
323
  if self.type == RecordTypeChoices.A:
333
324
  ptr_zone = (
334
- zone.Zone.objects.filter(
335
- view=self.zone.view,
325
+ self.zone.view.zones.filter(
336
326
  rfc2317_prefix__net_contains=self.value,
337
327
  )
338
328
  .order_by("rfc2317_prefix__net_mask_length")
@@ -343,15 +333,17 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
343
333
  return ptr_zone
344
334
 
345
335
  ptr_zone = (
346
- zone.Zone.objects.filter(
347
- view=self.zone.view, arpa_network__net_contains=self.value
348
- )
336
+ self.zone.view.zones.filter(arpa_network__net_contains=self.value)
349
337
  .order_by("arpa_network__net_mask_length")
350
338
  .last()
351
339
  )
352
340
 
353
341
  return ptr_zone
354
342
 
343
+ @property
344
+ def is_delegation_record(self):
345
+ return self in self.zone.delegation_records
346
+
355
347
  def update_ptr_record(self, update_rfc2317_cname=True, save_zone_serial=True):
356
348
  ptr_zone = self.ptr_zone
357
349
 
@@ -461,10 +453,9 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
461
453
 
462
454
  self.remove_from_rfc2317_cname_record(save_zone_serial=save_zone_serial)
463
455
 
464
- rfc2317_cname_record = Record.objects.filter(
456
+ rfc2317_cname_record = self.zone.rfc2317_parent_zone.records.filter(
465
457
  name=cname_name,
466
458
  type=RecordTypeChoices.CNAME,
467
- zone=self.zone.rfc2317_parent_zone,
468
459
  managed=True,
469
460
  value=self.fqdn,
470
461
  ).first()
@@ -612,8 +603,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
612
603
  if new_zone is None:
613
604
  new_zone = self.zone
614
605
 
615
- records = Record.objects.filter(
616
- zone=new_zone,
606
+ records = new_zone.records.filter(
617
607
  name=self.name,
618
608
  type=self.type,
619
609
  value=self.value,
@@ -649,8 +639,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
649
639
  if not get_plugin_config("netbox_dns", "dnssync_conflict_deactivate", False):
650
640
  return
651
641
 
652
- records = Record.objects.filter(
653
- zone=self.zone,
642
+ records = self.zone.records.filter(
654
643
  name=self.name,
655
644
  type=self.type,
656
645
  value=self.value,
@@ -678,8 +667,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
678
667
  return
679
668
 
680
669
  records = (
681
- Record.objects.filter(
682
- zone=self.zone,
670
+ self.zone.records.filter(
683
671
  name=self.name,
684
672
  type=self.type,
685
673
  )
@@ -722,8 +710,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
722
710
  ttl = self.ttl
723
711
 
724
712
  records = (
725
- Record.objects.filter(
726
- zone=self.zone,
713
+ self.zone.records.filter(
727
714
  name=self.name,
728
715
  type=self.type,
729
716
  )
@@ -751,9 +738,9 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
751
738
  if not self.is_active:
752
739
  return
753
740
 
754
- records = Record.objects.filter(
755
- name=self.name, zone=self.zone, active=True
756
- ).exclude(pk=self.pk)
741
+ records = self.zone.records.filter(name=self.name, active=True).exclude(
742
+ pk=self.pk
743
+ )
757
744
 
758
745
  if self.type == RecordTypeChoices.A and not self.disable_ptr:
759
746
  ptr_zone = self.ptr_zone
@@ -770,8 +757,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
770
757
  )
771
758
 
772
759
  if (
773
- Record.objects.filter(
774
- zone=ptr_cname_zone,
760
+ ptr_cname_zone.records.filter(
775
761
  name=ptr_cname_name,
776
762
  active=True,
777
763
  )
@@ -855,7 +841,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
855
841
  self.ip_address = self.address_from_name
856
842
 
857
843
  elif self.is_address_record:
858
- self.ip_address = self.value
844
+ self.ip_address = netaddr.IPAddress(self.value)
859
845
  else:
860
846
  self.ip_address = None
861
847
 
@@ -873,9 +859,8 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
873
859
  if changed_fields is None or changed_fields:
874
860
  super().save(*args, **kwargs)
875
861
 
876
- _zone = self.zone
877
- if self.type != RecordTypeChoices.SOA and _zone.soa_serial_auto:
878
- _zone.update_serial(save_zone_serial=save_zone_serial)
862
+ if self.type != RecordTypeChoices.SOA and self.zone.soa_serial_auto:
863
+ self.zone.update_serial(save_zone_serial=save_zone_serial)
879
864
 
880
865
  def delete(self, *args, save_zone_serial=True, **kwargs):
881
866
  if self.rfc2317_cname_record:
@@ -133,17 +133,17 @@ class RecordTemplate(NetBoxModel):
133
133
  {
134
134
  "record_name": exc,
135
135
  }
136
- ) from None
136
+ )
137
137
 
138
138
  def validate_value(self):
139
139
  try:
140
140
  validate_record_value(self)
141
141
  except ValidationError as exc:
142
- raise ValidationError({"value": exc}) from None
142
+ raise ValidationError({"value": exc})
143
143
 
144
144
  def matching_records(self, zone):
145
- return Record.objects.filter(
146
- zone=zone, name=self.record_name, type=self.type, value=self.value
145
+ return zone.records.filter(
146
+ name=self.record_name, type=self.type, value=self.value
147
147
  )
148
148
 
149
149
  def create_record(self, zone):
@@ -137,7 +137,7 @@ class RegistrationContact(NetBoxModel):
137
137
  @property
138
138
  def zones(self):
139
139
  return (
140
- set(self.zone_set.all())
140
+ set(self.registrant_zones.all())
141
141
  | set(self.admin_c_zones.all())
142
142
  | set(self.tech_c_zones.all())
143
143
  | set(self.billing_c_zones.all())
netbox_dns/models/view.py CHANGED
@@ -2,7 +2,6 @@ from django.db import models
2
2
  from django.urls import reverse
3
3
  from django.core.exceptions import ValidationError
4
4
  from django.utils.translation import gettext_lazy as _
5
- from django.utils.translation import pgettext_lazy as _p
6
5
 
7
6
  from netbox.models import NetBoxModel
8
7
  from netbox.models.features import ContactsMixin
@@ -77,8 +76,8 @@ class View(ObjectModificationMixin, ContactsMixin, NetBoxModel):
77
76
  return str(self.name)
78
77
 
79
78
  class Meta:
80
- verbose_name = _p("DNS", "View")
81
- verbose_name_plural = _p("DNS", "Views")
79
+ verbose_name = _("View")
80
+ verbose_name_plural = _("Views")
82
81
 
83
82
  ordering = ("name",)
84
83
 
netbox_dns/models/zone.py CHANGED
@@ -12,12 +12,12 @@ from django.core.validators import (
12
12
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
13
13
  from django.db import models, transaction
14
14
  from django.db.models import Q, Max, ExpressionWrapper, BooleanField
15
- from django.urls import reverse
15
+ from django.db.models.functions import Length
16
16
  from django.db.models.signals import m2m_changed
17
+ from django.urls import reverse
17
18
  from django.dispatch import receiver
18
19
  from django.conf import settings
19
20
  from django.utils.translation import gettext_lazy as _
20
- from django.utils.translation import pgettext_lazy as _p
21
21
 
22
22
  from netbox.models import NetBoxModel
23
23
  from netbox.models.features import ContactsMixin
@@ -35,6 +35,7 @@ from netbox_dns.utilities import (
35
35
  arpa_to_prefix,
36
36
  name_to_unicode,
37
37
  normalize_name,
38
+ get_parent_zone_names,
38
39
  NameFormatError,
39
40
  )
40
41
  from netbox_dns.validators import (
@@ -84,9 +85,10 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
84
85
  self._ip_addresses_checked = False
85
86
 
86
87
  view = models.ForeignKey(
87
- verbose_name=_p("DNS", "View"),
88
+ verbose_name=_("View"),
88
89
  to="View",
89
90
  on_delete=models.PROTECT,
91
+ related_name="zones",
90
92
  null=False,
91
93
  )
92
94
  name = models.CharField(
@@ -120,7 +122,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
120
122
  soa_mname = models.ForeignKey(
121
123
  verbose_name=_("SOA MName"),
122
124
  to="NameServer",
123
- related_name="zones_soa",
125
+ related_name="soa_zones",
124
126
  on_delete=models.PROTECT,
125
127
  blank=False,
126
128
  null=False,
@@ -189,6 +191,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
189
191
  verbose_name=_("Registrar"),
190
192
  to="Registrar",
191
193
  on_delete=models.SET_NULL,
194
+ related_name="zones",
192
195
  blank=True,
193
196
  null=True,
194
197
  )
@@ -202,6 +205,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
202
205
  verbose_name=_("Registrant"),
203
206
  to="RegistrationContact",
204
207
  on_delete=models.SET_NULL,
208
+ related_name="registrant_zones",
205
209
  blank=True,
206
210
  null=True,
207
211
  )
@@ -298,6 +302,10 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
298
302
 
299
303
  return str(name)
300
304
 
305
+ @property
306
+ def fqdn(self):
307
+ return f"{self.name}."
308
+
301
309
  @staticmethod
302
310
  def get_defaults():
303
311
  default_fields = (
@@ -363,10 +371,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
363
371
  return None
364
372
 
365
373
  return (
366
- Zone.objects.filter(
367
- view=self.view,
368
- arpa_network__net_contains=self.rfc2317_prefix,
369
- )
374
+ self.view.zones.filter(arpa_network__net_contains=self.rfc2317_prefix)
370
375
  .order_by("arpa_network__net_mask_length")
371
376
  .last()
372
377
  )
@@ -387,25 +392,71 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
387
392
 
388
393
  @property
389
394
  def child_zones(self):
390
- return Zone.objects.filter(
391
- name__iregex=rf"^[^.]+\.{re.escape(self.name)}$", view=self.view
392
- )
395
+ return self.view.zones.filter(name__iregex=rf"^[^.]+\.{re.escape(self.name)}$")
396
+
397
+ @property
398
+ def descendant_zones(self):
399
+ return self.view.zones.filter(name__endswith=f".{self.name}")
393
400
 
394
401
  @property
395
402
  def parent_zone(self):
396
- parent_name = (
397
- dns_name.from_text(self.name).parent().relativize(dns_name.root).to_text()
398
- )
399
403
  try:
400
- return Zone.objects.get(name=parent_name, view=self.view)
401
- except Zone.DoesNotExist:
404
+ return self.view.zones.get(name=get_parent_zone_names(self.name)[-1])
405
+ except (Zone.DoesNotExist, IndexError):
402
406
  return None
403
407
 
404
- def record_count(self, managed=False):
405
- return Record.objects.filter(zone=self, managed=managed).count()
408
+ @property
409
+ def ancestor_zones(self):
410
+ return (
411
+ self.view.zones.annotate(name_length=Length("name"))
412
+ .filter(name__in=get_parent_zone_names(self.name))
413
+ .order_by("name_length")
414
+ )
406
415
 
407
- def rfc2317_child_zone_count(self):
408
- return Zone.objects.filter(rfc2317_parent_zone=self).count()
416
+ @property
417
+ def delegation_records(self):
418
+ descendant_zone_names = [
419
+ f"{name}." for name in self.descendant_zones.values_list("name", flat=True)
420
+ ]
421
+
422
+ ns_records = (
423
+ self.records.filter(type=RecordTypeChoices.NS)
424
+ .exclude(fqdn=self.fqdn)
425
+ .filter(fqdn__in=descendant_zone_names)
426
+ )
427
+ ns_values = [record.value_fqdn for record in ns_records]
428
+
429
+ return (
430
+ ns_records
431
+ | self.records.filter(type=RecordTypeChoices.DS)
432
+ | self.records.filter(
433
+ type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
434
+ fqdn__in=ns_values,
435
+ )
436
+ )
437
+
438
+ @property
439
+ def ancestor_delegation_records(self):
440
+ ancestor_zones = self.ancestor_zones
441
+
442
+ ns_records = Record.objects.filter(
443
+ type=RecordTypeChoices.NS, zone__in=ancestor_zones, fqdn=self.fqdn
444
+ )
445
+ ns_values = [record.value_fqdn for record in ns_records]
446
+
447
+ ds_records = Record.objects.filter(
448
+ type=RecordTypeChoices.DS, zone__in=ancestor_zones, fqdn=self.fqdn
449
+ )
450
+
451
+ return (
452
+ ns_records
453
+ | ds_records
454
+ | Record.objects.filter(
455
+ zone__in=ancestor_zones,
456
+ type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
457
+ fqdn__in=ns_values,
458
+ )
459
+ )
409
460
 
410
461
  def update_soa_record(self):
411
462
  soa_name = "@"
@@ -423,7 +474,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
423
474
  )
424
475
 
425
476
  try:
426
- soa_record = self.record_set.get(type=RecordTypeChoices.SOA, name=soa_name)
477
+ soa_record = self.records.get(type=RecordTypeChoices.SOA, name=soa_name)
427
478
 
428
479
  if soa_record.ttl != soa_ttl or soa_record.value != soa_rdata.to_text():
429
480
  soa_record.ttl = soa_ttl
@@ -446,9 +497,10 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
446
497
 
447
498
  nameservers = [f"{nameserver.name}." for nameserver in self.nameservers.all()]
448
499
 
449
- self.record_set.filter(type=RecordTypeChoices.NS, managed=True).exclude(
450
- value__in=nameservers
451
- ).delete()
500
+ for ns_record in self.records.filter(
501
+ type=RecordTypeChoices.NS, managed=True
502
+ ).exclude(value__in=nameservers):
503
+ ns_record.delete()
452
504
 
453
505
  for ns in nameservers:
454
506
  Record.raw_objects.update_or_create(
@@ -483,8 +535,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
483
535
  continue
484
536
 
485
537
  relative_name = name.relativize(parent).to_text()
486
- address_records = Record.objects.filter(
487
- Q(zone=ns_zone),
538
+ address_records = ns_zone.records.filter(
488
539
  Q(status__in=RECORD_ACTIVE_STATUS_LIST),
489
540
  Q(Q(name=f"{_nameserver.name}.") | Q(name=relative_name)),
490
541
  Q(Q(type=RecordTypeChoices.A) | Q(type=RecordTypeChoices.AAAA)),
@@ -574,9 +625,9 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
574
625
  self.rfc2317_parent_zone = rfc2317_parent_zone
575
626
  self.save(update_fields=["rfc2317_parent_zone"])
576
627
 
577
- ptr_records = self.record_set.filter(
578
- type=RecordTypeChoices.PTR
579
- ).prefetch_related("zone", "rfc2317_cname_record")
628
+ ptr_records = self.records.filter(type=RecordTypeChoices.PTR).prefetch_related(
629
+ "zone", "rfc2317_cname_record"
630
+ )
580
631
  ptr_zones = {ptr_record.zone for ptr_record in ptr_records}
581
632
 
582
633
  if self.rfc2317_parent_managed:
@@ -641,7 +692,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
641
692
  {
642
693
  "name": str(exc),
643
694
  }
644
- ) from None
695
+ )
645
696
 
646
697
  try:
647
698
  validate_domain_name(self.name, zone_name=True)
@@ -650,7 +701,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
650
701
  {
651
702
  "name": exc,
652
703
  }
653
- ) from None
704
+ )
654
705
 
655
706
  if self.soa_rname in (None, ""):
656
707
  raise ValidationError(_("soa_rname not set and no default value defined"))
@@ -662,7 +713,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
662
713
  {
663
714
  "soa_rname": exc,
664
715
  }
665
- ) from None
716
+ )
666
717
 
667
718
  if not self.soa_serial_auto:
668
719
  if self.soa_serial is None:
@@ -703,7 +754,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
703
754
  or old_view_id != self.view_id
704
755
  ):
705
756
  ip_addresses = IPAddress.objects.filter(
706
- netbox_dns_records__in=self.record_set.filter(
757
+ netbox_dns_records__in=self.records.filter(
707
758
  ipam_ip_address__isnull=False
708
759
  )
709
760
  )
@@ -746,8 +797,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
746
797
  else:
747
798
  self.rfc2317_parent_zone = None
748
799
 
749
- overlapping_zones = Zone.objects.filter(
750
- view=self.view,
800
+ overlapping_zones = self.view.zones.filter(
751
801
  rfc2317_prefix__net_overlap=self.rfc2317_prefix,
752
802
  active=True,
753
803
  ).exclude(pk=self.pk)
@@ -780,9 +830,8 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
780
830
  if (
781
831
  changed_fields is None or {"name", "view", "status"} & changed_fields
782
832
  ) and self.is_reverse_zone:
783
- zones = Zone.objects.filter(
784
- view=self.view,
785
- arpa_network__net_contains_or_equals=self.arpa_network,
833
+ zones = self.view.zones.filter(
834
+ arpa_network__net_contains_or_equals=self.arpa_network
786
835
  )
787
836
  address_records = Record.objects.filter(
788
837
  Q(ptr_record__isnull=True) | Q(ptr_record__zone__in=zones),
@@ -811,9 +860,8 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
811
860
  or {"name", "view", "status", "rfc2317_prefix", "rfc2317_parent_managed"}
812
861
  & changed_fields
813
862
  ) and self.is_rfc2317_zone:
814
- zones = Zone.objects.filter(
815
- view=self.view,
816
- arpa_network__net_contains=self.rfc2317_prefix,
863
+ zones = self.view.zones.filter(
864
+ arpa_network__net_contains=self.rfc2317_prefix
817
865
  )
818
866
  address_records = Record.objects.filter(
819
867
  Q(ptr_record__isnull=True)
@@ -836,14 +884,14 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
836
884
  self.update_rfc2317_parent_zone()
837
885
 
838
886
  elif changed_fields is not None and {"name", "view", "status"} & changed_fields:
839
- for address_record in self.record_set.filter(
887
+ for address_record in self.records.filter(
840
888
  type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
841
889
  ipam_ip_address__isnull=True,
842
890
  ):
843
891
  address_record.save(update_fields=["ptr_record"])
844
892
 
845
893
  if changed_fields is not None and "name" in changed_fields:
846
- for _record in self.record_set.filter(ipam_ip_address__isnull=True):
894
+ for _record in self.records.filter(ipam_ip_address__isnull=True):
847
895
  _record.save(
848
896
  update_fields=["fqdn"],
849
897
  save_zone_serial=False,
@@ -853,7 +901,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
853
901
 
854
902
  if changed_fields is None or {"name", "view"} & changed_fields:
855
903
  ip_addresses = IPAddress.objects.filter(
856
- netbox_dns_records__in=self.record_set.filter(
904
+ netbox_dns_records__in=self.records.filter(
857
905
  ipam_ip_address__isnull=False
858
906
  )
859
907
  )
@@ -867,13 +915,13 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
867
915
 
868
916
  def delete(self, *args, **kwargs):
869
917
  with transaction.atomic():
870
- address_records = self.record_set.filter(
918
+ address_records = self.records.filter(
871
919
  ptr_record__isnull=False
872
920
  ).prefetch_related("ptr_record")
873
921
  for address_record in address_records:
874
922
  address_record.ptr_record.delete()
875
923
 
876
- ptr_records = self.record_set.filter(address_record__isnull=False)
924
+ ptr_records = self.records.filter(address_record__isnull=False)
877
925
  update_records = list(
878
926
  Record.objects.filter(ptr_record__in=ptr_records).values_list(
879
927
  "pk", flat=True
@@ -894,12 +942,12 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
894
942
  cname_zone.update_soa_record()
895
943
 
896
944
  rfc2317_child_zones = list(
897
- self.rfc2317_child_zones.all().values_list("pk", flat=True)
945
+ self.rfc2317_child_zones.values_list("pk", flat=True)
898
946
  )
899
947
 
900
948
  ipam_ip_addresses = list(
901
949
  IPAddress.objects.filter(
902
- netbox_dns_records__in=self.record_set.filter(
950
+ netbox_dns_records__in=self.records.filter(
903
951
  ipam_ip_address__isnull=False
904
952
  )
905
953
  )
@@ -1,7 +1,6 @@
1
1
  import django_tables2 as tables
2
2
  from django.utils.html import format_html
3
3
  from django.utils.translation import gettext_lazy as _
4
- from django.utils.translation import pgettext_lazy as _p
5
4
 
6
5
 
7
6
  from netbox.tables import (
@@ -20,6 +19,7 @@ __all__ = (
20
19
  "RecordTable",
21
20
  "ManagedRecordTable",
22
21
  "RelatedRecordTable",
22
+ "DelegationRecordTable",
23
23
  )
24
24
 
25
25
 
@@ -29,7 +29,7 @@ class RecordBaseTable(TenancyColumnsMixin, NetBoxTable):
29
29
  linkify=True,
30
30
  )
31
31
  view = tables.Column(
32
- verbose_name=_p("DNS", "View"),
32
+ verbose_name=_("View"),
33
33
  accessor="zone__view",
34
34
  linkify=True,
35
35
  )
@@ -162,3 +162,15 @@ class RelatedRecordTable(RecordBaseTable):
162
162
  "type",
163
163
  "value",
164
164
  )
165
+
166
+
167
+ class DelegationRecordTable(RecordBaseTable):
168
+ class Meta(NetBoxTable.Meta):
169
+ model = Record
170
+ fields = ()
171
+ default_columns = (
172
+ "name",
173
+ "zone",
174
+ "type",
175
+ "value",
176
+ )
netbox_dns/tables/zone.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import django_tables2 as tables
2
2
  from django.utils.translation import gettext_lazy as _
3
- from django.utils.translation import pgettext_lazy as _p
4
3
 
5
4
  from netbox.tables import (
6
5
  ChoiceFieldColumn,
@@ -21,7 +20,7 @@ class ZoneTable(TenancyColumnsMixin, NetBoxTable):
21
20
  linkify=True,
22
21
  )
23
22
  view = tables.Column(
24
- verbose_name=_p("DNS", "View"),
23
+ verbose_name=_("View"),
25
24
  linkify=True,
26
25
  )
27
26
  soa_mname = tables.Column(
@@ -1,8 +1,12 @@
1
+ import django_tables2 as tables
2
+
1
3
  from django.conf import settings
2
4
  from django.urls import reverse
3
5
 
4
6
  from netbox.plugins.utils import get_plugin_config
5
7
  from netbox.plugins import PluginTemplateExtension
8
+ from utilities.tables import register_table_column
9
+ from ipam.tables import IPAddressTable
6
10
 
7
11
  from netbox_dns.models import Record
8
12
  from netbox_dns.choices import RecordTypeChoices
@@ -114,8 +118,20 @@ class IPRelatedDNSRecords(PluginTemplateExtension):
114
118
  )
115
119
 
116
120
 
121
+ address_records = tables.ManyToManyColumn(
122
+ verbose_name="DNS Address Records",
123
+ accessor="netbox_dns_records",
124
+ linkify_item=True,
125
+ transform=lambda obj: (
126
+ obj.fqdn.rstrip(".")
127
+ if obj.zone.view.default_view
128
+ else f"[{obj.zone.view.name}] {obj.fqdn.rstrip('.')}"
129
+ ),
130
+ )
131
+
117
132
  if not settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
118
133
  template_extensions = [RelatedDNSRecords, RelatedDNSViews]
134
+ register_table_column(address_records, "address_records", IPAddressTable)
119
135
  else:
120
136
  template_extensions = []
121
137
 
@@ -43,6 +43,12 @@
43
43
  <th scope="row">{% trans "Name" %}</th>
44
44
  <td style="word-break:break-all;">{{ object.name }}</td>
45
45
  </tr>
46
+ {% if mask_warning %}
47
+ <tr class="text-warning">
48
+ <th scope="row">{% trans "Warning" %}</th>
49
+ <td>{{ mask_warning }}</td>
50
+ </tr>
51
+ {% endif %}
46
52
  {% if unicode_name %}
47
53
  <tr>
48
54
  <th scope="row">{% trans "IDN" %}</th>
@@ -76,6 +82,12 @@
76
82
  <th scope="row">{% trans "Value" %}</th>
77
83
  <td style="word-break:break-all;">{{ object.value }}</td>
78
84
  </tr>
85
+ {% if cname_warning %}
86
+ <tr class="text-warning">
87
+ <th scope="row">{% trans "Warning" %}</th>
88
+ <td>{{ cname_warning }}</td>
89
+ </tr>
90
+ {% endif %}
79
91
  {% if unicode_value %}
80
92
  <tr>
81
93
  <th scope="row">{% trans "Unicode Value" %}</th>