netbox-plugin-dns 1.1.4__py3-none-any.whl → 1.1.5__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 (35) hide show
  1. netbox_dns/__init__.py +18 -4
  2. netbox_dns/api/views.py +1 -1
  3. netbox_dns/filtersets/record.py +0 -1
  4. netbox_dns/filtersets/zone.py +1 -2
  5. netbox_dns/forms/record.py +11 -7
  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/locale/de/LC_MESSAGES/django.mo +0 -0
  10. netbox_dns/models/nameserver.py +4 -8
  11. netbox_dns/models/record.py +18 -34
  12. netbox_dns/models/record_template.py +4 -4
  13. netbox_dns/models/view.py +2 -3
  14. netbox_dns/models/zone.py +78 -32
  15. netbox_dns/tables/record.py +14 -2
  16. netbox_dns/tables/zone.py +1 -2
  17. netbox_dns/template_content.py +16 -0
  18. netbox_dns/templates/netbox_dns/record.html +12 -0
  19. netbox_dns/templates/netbox_dns/view.html +1 -1
  20. netbox_dns/templates/netbox_dns/zone/delegation_record.html +18 -0
  21. netbox_dns/templates/netbox_dns/zone.html +1 -1
  22. netbox_dns/utilities/__init__.py +1 -0
  23. netbox_dns/utilities/dns.py +12 -0
  24. netbox_dns/utilities/ipam_dnssync.py +10 -13
  25. netbox_dns/views/nameserver.py +3 -3
  26. netbox_dns/views/record.py +44 -11
  27. netbox_dns/views/registrar.py +1 -1
  28. netbox_dns/views/registration_contact.py +1 -1
  29. netbox_dns/views/view.py +2 -2
  30. netbox_dns/views/zone.py +49 -20
  31. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.5.dist-info}/METADATA +2 -1
  32. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.5.dist-info}/RECORD +35 -33
  33. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.5.dist-info}/WHEEL +1 -1
  34. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.5.dist-info}/LICENSE +0 -0
  35. {netbox_plugin_dns-1.1.4.dist-info → netbox_plugin_dns-1.1.5.dist-info}/top_level.txt +0 -0
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,7 +85,7 @@ 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,
90
91
  null=False,
@@ -298,6 +299,10 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
298
299
 
299
300
  return str(name)
300
301
 
302
+ @property
303
+ def fqdn(self):
304
+ return f"{self.name}."
305
+
301
306
  @staticmethod
302
307
  def get_defaults():
303
308
  default_fields = (
@@ -363,10 +368,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
363
368
  return None
364
369
 
365
370
  return (
366
- Zone.objects.filter(
367
- view=self.view,
368
- arpa_network__net_contains=self.rfc2317_prefix,
369
- )
371
+ self.view.zone_set.filter(arpa_network__net_contains=self.rfc2317_prefix)
370
372
  .order_by("arpa_network__net_mask_length")
371
373
  .last()
372
374
  )
@@ -387,25 +389,73 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
387
389
 
388
390
  @property
389
391
  def child_zones(self):
390
- return Zone.objects.filter(
391
- name__iregex=rf"^[^.]+\.{re.escape(self.name)}$", view=self.view
392
+ return self.view.zone_set.filter(
393
+ name__iregex=rf"^[^.]+\.{re.escape(self.name)}$"
392
394
  )
393
395
 
396
+ @property
397
+ def descendant_zones(self):
398
+ return self.view.zone_set.filter(name__endswith=f".{self.name}")
399
+
394
400
  @property
395
401
  def parent_zone(self):
396
- parent_name = (
397
- dns_name.from_text(self.name).parent().relativize(dns_name.root).to_text()
398
- )
399
402
  try:
400
- return Zone.objects.get(name=parent_name, view=self.view)
401
- except Zone.DoesNotExist:
403
+ return self.view.zone_set.get(name=get_parent_zone_names(self.name)[-1])
404
+ except (Zone.DoesNotExist, IndexError):
402
405
  return None
403
406
 
404
- def record_count(self, managed=False):
405
- return Record.objects.filter(zone=self, managed=managed).count()
407
+ @property
408
+ def ancestor_zones(self):
409
+ return (
410
+ self.view.zone_set.annotate(name_length=Length("name"))
411
+ .filter(name__in=get_parent_zone_names(self.name))
412
+ .order_by("name_length")
413
+ )
406
414
 
407
- def rfc2317_child_zone_count(self):
408
- return Zone.objects.filter(rfc2317_parent_zone=self).count()
415
+ @property
416
+ def delegation_records(self):
417
+ descendant_zone_names = [
418
+ f"{name}." for name in self.descendant_zones.values_list("name", flat=True)
419
+ ]
420
+
421
+ ns_records = (
422
+ self.record_set.filter(type=RecordTypeChoices.NS)
423
+ .exclude(fqdn=self.fqdn)
424
+ .filter(fqdn__in=descendant_zone_names)
425
+ )
426
+ ns_values = [record.value_fqdn for record in ns_records]
427
+
428
+ return (
429
+ ns_records
430
+ | self.record_set.filter(type=RecordTypeChoices.DS)
431
+ | self.record_set.filter(
432
+ type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
433
+ fqdn__in=ns_values,
434
+ )
435
+ )
436
+
437
+ @property
438
+ def ancestor_delegation_records(self):
439
+ ancestor_zones = self.ancestor_zones
440
+
441
+ ns_records = Record.objects.filter(
442
+ type=RecordTypeChoices.NS, zone__in=ancestor_zones, fqdn=self.fqdn
443
+ )
444
+ ns_values = [record.value_fqdn for record in ns_records]
445
+
446
+ ds_records = Record.objects.filter(
447
+ type=RecordTypeChoices.DS, zone__in=ancestor_zones, fqdn=self.fqdn
448
+ )
449
+
450
+ return (
451
+ ns_records
452
+ | ds_records
453
+ | Record.objects.filter(
454
+ zone__in=ancestor_zones,
455
+ type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
456
+ fqdn__in=ns_values,
457
+ )
458
+ )
409
459
 
410
460
  def update_soa_record(self):
411
461
  soa_name = "@"
@@ -483,8 +533,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
483
533
  continue
484
534
 
485
535
  relative_name = name.relativize(parent).to_text()
486
- address_records = Record.objects.filter(
487
- Q(zone=ns_zone),
536
+ address_records = ns_zone.record_set.filter(
488
537
  Q(status__in=RECORD_ACTIVE_STATUS_LIST),
489
538
  Q(Q(name=f"{_nameserver.name}.") | Q(name=relative_name)),
490
539
  Q(Q(type=RecordTypeChoices.A) | Q(type=RecordTypeChoices.AAAA)),
@@ -641,7 +690,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
641
690
  {
642
691
  "name": str(exc),
643
692
  }
644
- ) from None
693
+ )
645
694
 
646
695
  try:
647
696
  validate_domain_name(self.name, zone_name=True)
@@ -650,7 +699,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
650
699
  {
651
700
  "name": exc,
652
701
  }
653
- ) from None
702
+ )
654
703
 
655
704
  if self.soa_rname in (None, ""):
656
705
  raise ValidationError(_("soa_rname not set and no default value defined"))
@@ -662,7 +711,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
662
711
  {
663
712
  "soa_rname": exc,
664
713
  }
665
- ) from None
714
+ )
666
715
 
667
716
  if not self.soa_serial_auto:
668
717
  if self.soa_serial is None:
@@ -746,8 +795,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
746
795
  else:
747
796
  self.rfc2317_parent_zone = None
748
797
 
749
- overlapping_zones = Zone.objects.filter(
750
- view=self.view,
798
+ overlapping_zones = self.view.zone_set.filter(
751
799
  rfc2317_prefix__net_overlap=self.rfc2317_prefix,
752
800
  active=True,
753
801
  ).exclude(pk=self.pk)
@@ -780,9 +828,8 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
780
828
  if (
781
829
  changed_fields is None or {"name", "view", "status"} & changed_fields
782
830
  ) and self.is_reverse_zone:
783
- zones = Zone.objects.filter(
784
- view=self.view,
785
- arpa_network__net_contains_or_equals=self.arpa_network,
831
+ zones = self.view.zone_set.filter(
832
+ arpa_network__net_contains_or_equals=self.arpa_network
786
833
  )
787
834
  address_records = Record.objects.filter(
788
835
  Q(ptr_record__isnull=True) | Q(ptr_record__zone__in=zones),
@@ -811,9 +858,8 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
811
858
  or {"name", "view", "status", "rfc2317_prefix", "rfc2317_parent_managed"}
812
859
  & changed_fields
813
860
  ) and self.is_rfc2317_zone:
814
- zones = Zone.objects.filter(
815
- view=self.view,
816
- arpa_network__net_contains=self.rfc2317_prefix,
861
+ zones = self.view.zone_set.filter(
862
+ arpa_network__net_contains=self.rfc2317_prefix
817
863
  )
818
864
  address_records = Record.objects.filter(
819
865
  Q(ptr_record__isnull=True)
@@ -894,7 +940,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
894
940
  cname_zone.update_soa_record()
895
941
 
896
942
  rfc2317_child_zones = list(
897
- self.rfc2317_child_zones.all().values_list("pk", flat=True)
943
+ self.rfc2317_child_zones.values_list("pk", flat=True)
898
944
  )
899
945
 
900
946
  ipam_ip_addresses = list(
@@ -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>
@@ -5,7 +5,7 @@
5
5
  <div class="row">
6
6
  <div class="col col-md-6">
7
7
  <div class="card">
8
- <h5 class="card-header">{% trans "View" context "DNS" %}</h5>
8
+ <h5 class="card-header">{% trans "View" %}</h5>
9
9
  <table class="table table-hover attr-table">
10
10
  <tr>
11
11
  <th scope="row">{% trans "Name" %}</th>
@@ -0,0 +1,18 @@
1
+ {% extends 'netbox_dns/zone/base.html' %}
2
+ {% load helpers %}
3
+ {% load render_table from django_tables2 %}
4
+ {% load perms %}
5
+
6
+ {% block content %}
7
+ {% include 'inc/table_controls_htmx.html' with table_modal="DelegationRecordTable_config" %}
8
+ <div class="card">
9
+ <div class="htmx-container table-responsive" id="object_list">
10
+ {% include 'htmx/table.html' %}
11
+ </div>
12
+ </div>
13
+ {% endblock %}
14
+
15
+ {% block modals %}
16
+ {{ block.super }}
17
+ {% table_config_form table %}
18
+ {% endblock modals %}
@@ -25,7 +25,7 @@
25
25
  </tr>
26
26
  {% endif %}
27
27
  <tr>
28
- <th scope="row">{% trans "View" context "DNS" %}</th>
28
+ <th scope="row">{% trans "View" %}</th>
29
29
  <td>{{ object.view|linkify }}</td>
30
30
  </tr>
31
31
  {% if object.description %}
@@ -1,2 +1,3 @@
1
+ from .dns import *
1
2
  from .conversions import *
2
3
  from .ipam_dnssync import *
@@ -0,0 +1,12 @@
1
+ from dns import name as dns_name
2
+
3
+
4
+ __all__ = ("get_parent_zone_names",)
5
+
6
+
7
+ def get_parent_zone_names(name, min_labels=1, include_self=False):
8
+ fqdn = dns_name.from_text(name)
9
+ return [
10
+ fqdn.split(i)[1].to_text().rstrip(".")
11
+ for i in range(min_labels + 1, len(fqdn.labels) + include_self)
12
+ ]
@@ -12,6 +12,8 @@ from ipam.models import IPAddress, Prefix
12
12
 
13
13
  from netbox_dns.choices import RecordStatusChoices
14
14
 
15
+ from .dns import get_parent_zone_names
16
+
15
17
 
16
18
  __all__ = (
17
19
  "get_zones",
@@ -85,15 +87,12 @@ def get_zones(ip_address, view=None, old_zone=None):
85
87
  min_labels = settings.PLUGINS_CONFIG["netbox_dns"].get(
86
88
  "dnssync_minimum_zone_labels", 2
87
89
  )
88
- fqdn = dns_name.from_text(ip_address.dns_name)
89
- zone_name_candidates = [
90
- fqdn.split(i)[1].to_text().rstrip(".")
91
- for i in range(min_labels + 1, len(fqdn.labels) + 1)
92
- ]
93
90
 
94
91
  zones = Zone.objects.filter(
95
92
  view__in=views,
96
- name__in=zone_name_candidates,
93
+ name__in=get_parent_zone_names(
94
+ ip_address.dns_name, min_labels=min_labels, include_self=True
95
+ ),
97
96
  active=True,
98
97
  )
99
98
 
@@ -194,9 +193,7 @@ def update_dns_records(ip_address, view=None, force=False):
194
193
  updated = True
195
194
 
196
195
  zones = Zone.objects.filter(pk__in=[zone.pk for zone in zones]).exclude(
197
- pk__in=set(
198
- ip_address.netbox_dns_records.all().values_list("zone", flat=True)
199
- )
196
+ pk__in=set(ip_address.netbox_dns_records.values_list("zone", flat=True))
200
197
  )
201
198
 
202
199
  for zone in zones:
@@ -213,18 +210,18 @@ def update_dns_records(ip_address, view=None, force=False):
213
210
 
214
211
 
215
212
  def delete_dns_records(ip_address, view=None):
216
- deleted = False
217
-
218
213
  if view is None:
219
214
  address_records = ip_address.netbox_dns_records.all()
220
215
  else:
221
216
  address_records = ip_address.netbox_dns_records.filter(zone__view=view)
222
217
 
218
+ if not address_records.exists():
219
+ return False
220
+
223
221
  for record in address_records:
224
222
  record.delete()
225
- deleted = True
226
223
 
227
- return deleted
224
+ return True
228
225
 
229
226
 
230
227
  def get_views_by_prefix(prefix):
@@ -36,7 +36,7 @@ class NameServerListView(generic.ObjectListView):
36
36
 
37
37
 
38
38
  class NameServerView(generic.ObjectView):
39
- queryset = NameServer.objects.all().prefetch_related("zones")
39
+ queryset = NameServer.objects.prefetch_related("zones")
40
40
 
41
41
  def get_extra_context(self, request, instance):
42
42
  name = dns_name.from_text(instance.name)
@@ -85,7 +85,7 @@ class NameServerContactsView(ObjectContactsView):
85
85
 
86
86
  @register_model_view(NameServer, "zones")
87
87
  class NameServerZoneListView(generic.ObjectChildrenView):
88
- queryset = NameServer.objects.all().prefetch_related("zones")
88
+ queryset = NameServer.objects.prefetch_related("zones")
89
89
  child_model = Zone
90
90
  table = ZoneTable
91
91
  filterset = ZoneFilterSet
@@ -105,7 +105,7 @@ class NameServerZoneListView(generic.ObjectChildrenView):
105
105
 
106
106
  @register_model_view(NameServer, "soa_zones")
107
107
  class NameServerSOAZoneListView(generic.ObjectChildrenView):
108
- queryset = NameServer.objects.all().prefetch_related("zones_soa")
108
+ queryset = NameServer.objects.prefetch_related("zones_soa")
109
109
  child_model = Zone
110
110
  table = ZoneTable
111
111
  filterset = ZoneFilterSet
@@ -1,5 +1,7 @@
1
1
  from dns import name as dns_name
2
2
 
3
+ from django.utils.translation import gettext_lazy as _
4
+
3
5
  from netbox.views import generic
4
6
  from utilities.views import register_model_view
5
7
  from tenancy.views import ObjectContactsView
@@ -14,7 +16,7 @@ from netbox_dns.forms import (
14
16
  from netbox_dns.models import Record, Zone
15
17
  from netbox_dns.choices import RecordTypeChoices
16
18
  from netbox_dns.tables import RecordTable, ManagedRecordTable, RelatedRecordTable
17
- from netbox_dns.utilities import value_to_unicode
19
+ from netbox_dns.utilities import value_to_unicode, get_parent_zone_names
18
20
 
19
21
 
20
22
  __all__ = (
@@ -29,6 +31,10 @@ __all__ = (
29
31
  )
30
32
 
31
33
 
34
+ class CNAMEWarning(Exception):
35
+ pass
36
+
37
+
32
38
  class RecordListView(generic.ObjectListView):
33
39
  queryset = Record.objects.filter(managed=False).prefetch_related(
34
40
  "zone", "ptr_record"
@@ -50,7 +56,7 @@ class ManagedRecordListView(generic.ObjectListView):
50
56
 
51
57
 
52
58
  class RecordView(generic.ObjectView):
53
- queryset = Record.objects.all().prefetch_related("zone", "ptr_record")
59
+ queryset = Record.objects.prefetch_related("zone", "ptr_record")
54
60
 
55
61
  def get_value_records(self, instance):
56
62
  value_fqdn = dns_name.from_text(instance.value_fqdn)
@@ -64,6 +70,18 @@ class RecordView(generic.ObjectView):
64
70
  data=cname_targets,
65
71
  )
66
72
 
73
+ if instance.zone.view.zone_set.filter(
74
+ name__in=get_parent_zone_names(instance.value_fqdn, min_labels=1),
75
+ active=True,
76
+ ).exists():
77
+ raise (
78
+ CNAMEWarning(
79
+ _(
80
+ "There is no matching target record for CNAME value {value}"
81
+ ).format(value=instance.value_fqdn)
82
+ )
83
+ )
84
+
67
85
  return None
68
86
 
69
87
  def get_cname_records(self, instance):
@@ -75,14 +93,8 @@ class RecordView(generic.ObjectView):
75
93
  )
76
94
  )
77
95
 
78
- fqdn = dns_name.from_text(instance.fqdn)
79
- parent_zone_names = [
80
- fqdn.split(length)[1].to_text().rstrip(".")
81
- for length in range(1, len(fqdn) + 1)
82
- ]
83
-
84
- parent_zones = Zone.objects.filter(
85
- view=instance.zone.view, name__in=parent_zone_names
96
+ parent_zones = instance.zone.view.zone_set.filter(
97
+ name__in=get_parent_zone_names(instance.fqdn, include_self=True),
86
98
  )
87
99
 
88
100
  for parent_zone in parent_zones:
@@ -118,10 +130,31 @@ class RecordView(generic.ObjectView):
118
130
  context["unicode_value"] = unicode_value
119
131
 
120
132
  if instance.type == RecordTypeChoices.CNAME:
121
- context["cname_target_table"] = self.get_value_records(instance)
133
+ try:
134
+ context["cname_target_table"] = self.get_value_records(instance)
135
+ except CNAMEWarning as exc:
136
+ context["cname_warning"] = str(exc)
122
137
  else:
123
138
  context["cname_table"] = self.get_cname_records(instance)
124
139
 
140
+ if not instance.managed:
141
+ name = dns_name.from_text(instance.name, origin=None)
142
+
143
+ if not instance.is_delegation_record:
144
+ fqdn = dns_name.from_text(instance.fqdn)
145
+
146
+ if Zone.objects.filter(
147
+ active=True,
148
+ name__in=get_parent_zone_names(
149
+ instance.fqdn,
150
+ min_labels=len(fqdn) - len(name),
151
+ include_self=True,
152
+ ),
153
+ ).exists():
154
+ context["mask_warning"] = _(
155
+ "Record is masked by a child zone and may not be visible in DNS"
156
+ )
157
+
125
158
  return context
126
159
 
127
160
 
@@ -69,7 +69,7 @@ class RegistrarBulkDeleteView(generic.BulkDeleteView):
69
69
 
70
70
  @register_model_view(Registrar, "zones")
71
71
  class RegistrarZoneListView(generic.ObjectChildrenView):
72
- queryset = Registrar.objects.all().prefetch_related("zone_set")
72
+ queryset = Registrar.objects.prefetch_related("zone_set")
73
73
  child_model = Zone
74
74
  table = ZoneTable
75
75
  filterset = ZoneFilterSet
@@ -70,7 +70,7 @@ class RegistrationContactBulkDeleteView(generic.BulkDeleteView):
70
70
 
71
71
  @register_model_view(RegistrationContact, "zones")
72
72
  class RegistrationContactZoneListView(generic.ObjectChildrenView):
73
- queryset = RegistrationContact.objects.all().prefetch_related(
73
+ queryset = RegistrationContact.objects.prefetch_related(
74
74
  "zone_set", "admin_c_zones", "tech_c_zones", "billing_c_zones"
75
75
  )
76
76
  child_model = Zone
netbox_dns/views/view.py CHANGED
@@ -31,7 +31,7 @@ __all__ = (
31
31
 
32
32
 
33
33
  class ViewView(generic.ObjectView):
34
- queryset = View.objects.all().prefetch_related("zone_set")
34
+ queryset = View.objects.prefetch_related("zone_set")
35
35
 
36
36
 
37
37
  class ViewListView(generic.ObjectListView):
@@ -89,7 +89,7 @@ class ViewPrefixEditView(generic.ObjectEditView):
89
89
 
90
90
  @register_model_view(View, "zones")
91
91
  class ViewZoneListView(generic.ObjectChildrenView):
92
- queryset = View.objects.all().prefetch_related("zone_set")
92
+ queryset = View.objects.prefetch_related("zone_set")
93
93
  child_model = Zone
94
94
  table = ZoneTable
95
95
  filterset = ZoneFilterSet