netbox-plugin-dns 1.0.0__py3-none-any.whl → 1.0b2__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 (37) hide show
  1. netbox_dns/__init__.py +3 -3
  2. netbox_dns/api/serializers_/view.py +1 -6
  3. netbox_dns/fields/network.py +21 -20
  4. netbox_dns/fields/rfc2317.py +2 -2
  5. netbox_dns/filtersets/view.py +1 -1
  6. netbox_dns/filtersets/zone.py +4 -4
  7. netbox_dns/forms/record.py +2 -30
  8. netbox_dns/forms/view.py +3 -6
  9. netbox_dns/forms/zone.py +101 -70
  10. netbox_dns/graphql/types.py +4 -1
  11. netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py +2 -4
  12. netbox_dns/models/nameserver.py +1 -3
  13. netbox_dns/models/record.py +23 -24
  14. netbox_dns/models/view.py +0 -53
  15. netbox_dns/models/zone.py +46 -63
  16. netbox_dns/signals/ipam_coupling.py +2 -1
  17. netbox_dns/tables/view.py +2 -9
  18. netbox_dns/template_content.py +1 -1
  19. netbox_dns/templates/netbox_dns/record.html +1 -1
  20. netbox_dns/templates/netbox_dns/view.html +0 -4
  21. netbox_dns/templates/netbox_dns/zone.html +4 -2
  22. netbox_dns/urls.py +297 -0
  23. netbox_dns/views/record.py +18 -10
  24. {netbox_plugin_dns-1.0.0.dist-info → netbox_plugin_dns-1.0b2.dist-info}/METADATA +14 -16
  25. {netbox_plugin_dns-1.0.0.dist-info → netbox_plugin_dns-1.0b2.dist-info}/RECORD +27 -36
  26. netbox_dns/migrations/0003_default_view.py +0 -15
  27. netbox_dns/migrations/0004_create_and_assign_default_view.py +0 -26
  28. netbox_dns/migrations/0005_alter_zone_view_not_null.py +0 -18
  29. netbox_dns/urls/__init__.py +0 -17
  30. netbox_dns/urls/contact.py +0 -51
  31. netbox_dns/urls/nameserver.py +0 -69
  32. netbox_dns/urls/record.py +0 -41
  33. netbox_dns/urls/registrar.py +0 -63
  34. netbox_dns/urls/view.py +0 -39
  35. netbox_dns/urls/zone.py +0 -57
  36. {netbox_plugin_dns-1.0.0.dist-info → netbox_plugin_dns-1.0b2.dist-info}/LICENSE +0 -0
  37. {netbox_plugin_dns-1.0.0.dist-info → netbox_plugin_dns-1.0b2.dist-info}/WHEEL +0 -0
@@ -11,10 +11,11 @@ from django.urls import reverse
11
11
 
12
12
  from netbox.models import NetBoxModel
13
13
  from netbox.search import SearchIndex, register_search
14
- from netbox.plugins.utils import get_plugin_config
15
14
  from utilities.querysets import RestrictedQuerySet
16
15
  from utilities.choices import ChoiceSet
17
16
 
17
+ from netbox.plugins.utils import get_plugin_config
18
+
18
19
  from netbox_dns.fields import AddressField
19
20
  from netbox_dns.utilities import (
20
21
  arpa_to_prefix,
@@ -233,8 +234,8 @@ class Record(NetBoxModel):
233
234
  if self.type != RecordTypeChoices.CNAME:
234
235
  return None
235
236
 
236
- _zone = dns_name.from_text(self.zone.name)
237
- value_fqdn = dns_name.from_text(self.value, origin=_zone)
237
+ zone = dns_name.from_text(self.zone.name)
238
+ value_fqdn = dns_name.from_text(self.value, origin=zone)
238
239
 
239
240
  return value_fqdn.to_text()
240
241
 
@@ -286,14 +287,12 @@ class Record(NetBoxModel):
286
287
  dns_name.from_text(self.ptr_record.zone.rfc2317_parent_zone.name)
287
288
  )
288
289
 
289
- return None
290
-
291
290
  @property
292
291
  def ptr_zone(self):
293
292
  if self.type == RecordTypeChoices.A:
294
293
  ptr_zone = (
295
294
  zone.Zone.objects.filter(
296
- view=self.zone.view,
295
+ self.zone.view_filter,
297
296
  rfc2317_prefix__net_contains=self.value,
298
297
  )
299
298
  .order_by("rfc2317_prefix__net_mask_length")
@@ -305,7 +304,7 @@ class Record(NetBoxModel):
305
304
 
306
305
  ptr_zone = (
307
306
  zone.Zone.objects.filter(
308
- view=self.zone.view, arpa_network__net_contains=self.value
307
+ self.zone.view_filter, arpa_network__net_contains=self.value
309
308
  )
310
309
  .order_by("arpa_network__net_mask_length")
311
310
  .last()
@@ -419,8 +418,10 @@ class Record(NetBoxModel):
419
418
  self.rfc2317_cname_record.save(save_zone_serial=save_zone_serial)
420
419
 
421
420
  return
422
-
423
- self.remove_from_rfc2317_cname_record(save_zone_serial=save_zone_serial)
421
+ else:
422
+ self.remove_from_rfc2317_cname_record(
423
+ save_zone_serial=save_zone_serial
424
+ )
424
425
 
425
426
  rfc2317_cname_record = Record.objects.filter(
426
427
  name=cname_name,
@@ -461,14 +462,14 @@ class Record(NetBoxModel):
461
462
 
462
463
  def validate_name(self):
463
464
  try:
464
- _zone = dns_name.from_text(self.zone.name, origin=dns_name.root)
465
+ zone = dns_name.from_text(self.zone.name, origin=dns_name.root)
465
466
  name = dns_name.from_text(self.name, origin=None)
466
- fqdn = dns_name.from_text(self.name, origin=_zone)
467
+ fqdn = dns_name.from_text(self.name, origin=zone)
467
468
 
468
- _zone.to_unicode()
469
+ zone.to_unicode()
469
470
  name.to_unicode()
470
471
 
471
- self.name = name.relativize(_zone).to_text()
472
+ self.name = name.relativize(zone).to_text()
472
473
  self.fqdn = fqdn.to_text()
473
474
 
474
475
  except dns.exception.DNSException as exc:
@@ -478,7 +479,7 @@ class Record(NetBoxModel):
478
479
  }
479
480
  )
480
481
 
481
- if not fqdn.is_subdomain(_zone):
482
+ if not fqdn.is_subdomain(zone):
482
483
  raise ValidationError(
483
484
  {
484
485
  "name": f"{self.name} is not a name in {self.zone.name}",
@@ -486,7 +487,7 @@ class Record(NetBoxModel):
486
487
  )
487
488
 
488
489
  if self.type not in get_plugin_config(
489
- "netbox_dns", "tolerate_non_rfc1035_types", default=[]
490
+ "netbox_dns", "tolerate_non_rfc1035_types", default=list()
490
491
  ):
491
492
  try:
492
493
  validate_extended_hostname(
@@ -496,7 +497,7 @@ class Record(NetBoxModel):
496
497
  in get_plugin_config(
497
498
  "netbox_dns",
498
499
  "tolerate_leading_underscore_types",
499
- default=[],
500
+ default=list(),
500
501
  )
501
502
  ),
502
503
  )
@@ -694,8 +695,6 @@ class Record(NetBoxModel):
694
695
  }
695
696
  ) from None
696
697
 
697
- super().clean(*args, **kwargs)
698
-
699
698
  def save(
700
699
  self,
701
700
  *args,
@@ -733,9 +732,9 @@ class Record(NetBoxModel):
733
732
 
734
733
  super().save(*args, **kwargs)
735
734
 
736
- _zone = self.zone
737
- if self.type != RecordTypeChoices.SOA and _zone.soa_serial_auto:
738
- _zone.update_serial(save_zone_serial=save_zone_serial)
735
+ zone = self.zone
736
+ if self.type != RecordTypeChoices.SOA and zone.soa_serial_auto:
737
+ zone.update_serial(save_zone_serial=save_zone_serial)
739
738
 
740
739
  def delete(self, *args, save_zone_serial=True, **kwargs):
741
740
  if self.rfc2317_cname_record:
@@ -746,9 +745,9 @@ class Record(NetBoxModel):
746
745
 
747
746
  super().delete(*args, **kwargs)
748
747
 
749
- _zone = self.zone
750
- if _zone.soa_serial_auto:
751
- _zone.update_serial(save_zone_serial=save_zone_serial)
748
+ zone = self.zone
749
+ if zone.soa_serial_auto:
750
+ zone.update_serial(save_zone_serial=save_zone_serial)
752
751
 
753
752
 
754
753
  @register_search
netbox_dns/models/view.py CHANGED
@@ -1,11 +1,8 @@
1
1
  from django.db import models
2
2
  from django.urls import reverse
3
- from django.core.exceptions import ValidationError
4
3
 
5
4
  from netbox.models import NetBoxModel
6
5
  from netbox.search import SearchIndex, register_search
7
- from netbox.context import current_request
8
- from utilities.exceptions import AbortRequest
9
6
 
10
7
 
11
8
  class View(NetBoxModel):
@@ -17,9 +14,6 @@ class View(NetBoxModel):
17
14
  max_length=200,
18
15
  blank=True,
19
16
  )
20
- default_view = models.BooleanField(
21
- default=False,
22
- )
23
17
  tenant = models.ForeignKey(
24
18
  to="tenancy.Tenant",
25
19
  on_delete=models.PROTECT,
@@ -30,10 +24,6 @@ class View(NetBoxModel):
30
24
 
31
25
  clone_fields = ["name", "description"]
32
26
 
33
- @classmethod
34
- def get_default_view(cls):
35
- return cls.objects.get(default_view=True)
36
-
37
27
  def get_absolute_url(self):
38
28
  return reverse("plugins:netbox_dns:view", kwargs={"pk": self.pk})
39
29
 
@@ -43,49 +33,6 @@ class View(NetBoxModel):
43
33
  class Meta:
44
34
  ordering = ("name",)
45
35
 
46
- def delete(self, *args, **kwargs):
47
- if self.default_view:
48
- if current_request.get() is not None:
49
- raise AbortRequest("The default view cannot be deleted")
50
-
51
- raise ValidationError("The default view cannot be deleted")
52
-
53
- super().delete(*args, **kwargs)
54
-
55
- def clean(self, *args, old_state=None, **kwargs):
56
- if self.pk is None:
57
- return
58
-
59
- old_state = View.objects.get(pk=self.pk)
60
-
61
- if (
62
- old_state.default_view
63
- and not self.default_view
64
- and not View.objects.filter(default_view=True).exclude(pk=self.pk).exists()
65
- ):
66
- raise ValidationError(
67
- {
68
- "default_view": "Please select a different view as default view to change this setting!"
69
- }
70
- )
71
-
72
- super().clean(*args, **kwargs)
73
-
74
- def save(self, *args, **kwargs):
75
- self.clean()
76
-
77
- old_state = None if self.pk is None else View.objects.get(pk=self.pk)
78
-
79
- super().save(*args, **kwargs)
80
-
81
- if (old_state is None and self.default_view) or (
82
- old_state is not None and self.default_view and not old_state.default_view
83
- ):
84
- other_views = View.objects.filter(default_view=True).exclude(pk=self.pk)
85
- for view in other_views:
86
- view.default_view = False
87
- view.save()
88
-
89
36
 
90
37
  @register_search
91
38
  class ViewIndex(SearchIndex):
netbox_dns/models/zone.py CHANGED
@@ -15,15 +15,15 @@ from django.db.models import Q, Max, ExpressionWrapper, BooleanField
15
15
  from django.urls import reverse
16
16
  from django.db.models.signals import m2m_changed
17
17
  from django.dispatch import receiver
18
- from django.conf import settings
19
18
 
20
19
  from netbox.models import NetBoxModel
21
20
  from netbox.search import SearchIndex, register_search
22
- from netbox.plugins.utils import get_plugin_config
23
21
  from utilities.querysets import RestrictedQuerySet
24
22
  from utilities.choices import ChoiceSet
25
23
  from ipam.models import IPAddress
26
24
 
25
+ from netbox.plugins.utils import get_plugin_config
26
+
27
27
  from netbox_dns.fields import NetworkField, RFC2317NetworkField
28
28
  from netbox_dns.utilities import (
29
29
  arpa_to_prefix,
@@ -37,11 +37,9 @@ from netbox_dns.validators import (
37
37
  )
38
38
 
39
39
  # +
40
- # This is a hack designed to break cyclic imports between View, Record and Zone
40
+ # This is a hack designed to break cyclic imports between Record and Zone
41
41
  # -
42
42
  import netbox_dns.models.record as record
43
- import netbox_dns.models.view as view
44
- import netbox_dns.models.nameserver as nameserver
45
43
 
46
44
 
47
45
  class ZoneManager(models.Manager.from_queryset(RestrictedQuerySet)):
@@ -83,7 +81,8 @@ class Zone(NetBoxModel):
83
81
  view = models.ForeignKey(
84
82
  to="View",
85
83
  on_delete=models.PROTECT,
86
- null=False,
84
+ blank=True,
85
+ null=True,
87
86
  )
88
87
  name = models.CharField(
89
88
  max_length=255,
@@ -286,20 +285,11 @@ class Zone(NetBoxModel):
286
285
  except dns_name.IDNAException:
287
286
  name = self.name
288
287
 
289
- if not self.view.default_view:
288
+ if self.view:
290
289
  return f"[{self.view}] {name}"
291
290
 
292
291
  return str(name)
293
292
 
294
- @staticmethod
295
- def get_defaults():
296
- return {
297
- field[5:]: value
298
- for field, value in settings.PLUGINS_CONFIG.get("netbox_dns").items()
299
- if field.startswith("zone_")
300
- and field not in ("zone_soa_mname", "zone_nameservers")
301
- }
302
-
303
293
  @property
304
294
  def display_name(self):
305
295
  return name_to_unicode(self.name)
@@ -327,11 +317,11 @@ class Zone(NetBoxModel):
327
317
 
328
318
  def get_rfc2317_parent_zone(self):
329
319
  if not self.is_rfc2317_zone:
330
- return None
320
+ return
331
321
 
332
322
  return (
333
323
  Zone.objects.filter(
334
- view=self.view,
324
+ self.view_filter,
335
325
  arpa_network__net_contains=self.rfc2317_prefix,
336
326
  )
337
327
  .order_by("arpa_network__net_mask_length")
@@ -352,6 +342,12 @@ class Zone(NetBoxModel):
352
342
  )
353
343
  )
354
344
 
345
+ @property
346
+ def view_filter(self):
347
+ if self.view is None:
348
+ return Q(view__isnull=True)
349
+ return Q(view=self.view)
350
+
355
351
  def record_count(self, managed=False):
356
352
  return record.Record.objects.filter(zone=self, managed=managed).count()
357
353
 
@@ -421,15 +417,19 @@ class Zone(NetBoxModel):
421
417
  if not nameservers:
422
418
  ns_errors.append(f"No nameservers are configured for zone {self}")
423
419
 
424
- for _nameserver in nameservers:
425
- name = dns_name.from_text(_nameserver.name, origin=None)
420
+ for nameserver in nameservers:
421
+ name = dns_name.from_text(nameserver.name, origin=None)
426
422
  parent = name.parent()
427
423
 
428
424
  if len(parent) < 2:
429
425
  continue
430
426
 
427
+ view_condition = (
428
+ Q(view__isnull=True) if self.view is None else Q(view_id=self.view.pk)
429
+ )
430
+
431
431
  try:
432
- ns_zone = Zone.objects.get(view_id=self.view.pk, name=parent.to_text())
432
+ ns_zone = Zone.objects.get(view_condition, name=parent.to_text())
433
433
  except ObjectDoesNotExist:
434
434
  continue
435
435
 
@@ -437,7 +437,7 @@ class Zone(NetBoxModel):
437
437
  address_records = record.Record.objects.filter(
438
438
  Q(zone=ns_zone),
439
439
  Q(status__in=record.Record.ACTIVE_STATUS_LIST),
440
- Q(Q(name=f"{_nameserver.name}.") | Q(name=relative_name)),
440
+ Q(Q(name=f"{nameserver.name}.") | Q(name=relative_name)),
441
441
  Q(
442
442
  Q(type=record.RecordTypeChoices.A)
443
443
  | Q(type=record.RecordTypeChoices.AAAA)
@@ -446,7 +446,7 @@ class Zone(NetBoxModel):
446
446
 
447
447
  if not address_records:
448
448
  ns_warnings.append(
449
- f"Nameserver {_nameserver.name} does not have an active address record in zone {ns_zone}"
449
+ f"Nameserver {nameserver.name} does not have an active address record in zone {ns_zone}"
450
450
  )
451
451
 
452
452
  return ns_warnings, ns_errors
@@ -492,6 +492,19 @@ class Zone(NetBoxModel):
492
492
  def network_from_name(self):
493
493
  return arpa_to_prefix(self.name)
494
494
 
495
+ def check_name_conflict(self):
496
+ if self.view is None:
497
+ if (
498
+ Zone.objects.exclude(pk=self.pk)
499
+ .filter(name=self.name.rstrip("."), view__isnull=True)
500
+ .exists()
501
+ ):
502
+ raise ValidationError(
503
+ {
504
+ "name": f"A zone with name {self.name} and no view already exists."
505
+ }
506
+ )
507
+
495
508
  def update_rfc2317_parent_zone(self):
496
509
  if not self.is_rfc2317_zone:
497
510
  return
@@ -542,33 +555,8 @@ class Zone(NetBoxModel):
542
555
  ptr_zone.save_soa_serial()
543
556
  ptr_zone.update_soa_record()
544
557
 
545
- def clean_fields(self, exclude=None):
546
- defaults = settings.PLUGINS_CONFIG.get("netbox_dns")
547
-
548
- if self.view_id is None:
549
- self.view_id = view.View.get_default_view().pk
550
-
551
- for field, value in self.get_defaults().items():
552
- if getattr(self, field) in (None, ""):
553
- if value not in (None, ""):
554
- setattr(self, field, value)
555
-
556
- if self.soa_mname_id is None:
557
- default_soa_mname = defaults.get("zone_soa_mname")
558
- try:
559
- self.soa_mname = nameserver.NameServer.objects.get(
560
- name=default_soa_mname
561
- )
562
- except nameserver.NameServer.DoesNotExist:
563
- raise ValidationError(
564
- f"Default soa_mname instance {default_soa_mname} does not exist"
565
- )
566
-
567
- super().clean_fields(exclude=exclude)
568
-
569
558
  def clean(self, *args, **kwargs):
570
- if self.soa_ttl is None:
571
- self.soa_ttl = self.default_ttl
559
+ self.check_name_conflict()
572
560
 
573
561
  try:
574
562
  self.name = normalize_name(self.name)
@@ -588,8 +576,6 @@ class Zone(NetBoxModel):
588
576
  }
589
577
  ) from None
590
578
 
591
- if self.soa_rname in (None, ""):
592
- raise ValidationError("soa_rname not set and no default value defined")
593
579
  try:
594
580
  dns_name.from_text(self.soa_rname, origin=dns_name.root)
595
581
  validate_fqdn(self.soa_rname)
@@ -600,13 +586,12 @@ class Zone(NetBoxModel):
600
586
  }
601
587
  ) from None
602
588
 
603
- if not self.soa_serial_auto:
604
- if self.soa_serial is None:
605
- raise ValidationError(
606
- {
607
- "soa_serial": f"soa_serial is not defined and soa_serial_auto is disabled for zone {self.name}."
608
- }
609
- )
589
+ if self.soa_serial is None and not self.soa_serial_auto:
590
+ raise ValidationError(
591
+ {
592
+ "soa_serial": f"soa_serial is not defined and soa_serial_auto is disabled for zone {self.name}."
593
+ }
594
+ )
610
595
 
611
596
  if self.is_reverse_zone:
612
597
  self.arpa_network = self.network_from_name
@@ -634,7 +619,7 @@ class Zone(NetBoxModel):
634
619
  self.rfc2317_parent_zone = None
635
620
 
636
621
  overlapping_zones = Zone.objects.filter(
637
- view=self.view,
622
+ self.view_filter,
638
623
  rfc2317_prefix__net_overlap=self.rfc2317_prefix,
639
624
  active=True,
640
625
  ).exclude(pk=self.pk)
@@ -650,8 +635,6 @@ class Zone(NetBoxModel):
650
635
  self.rfc2317_parent_managed = False
651
636
  self.rfc2317_parent_zone = None
652
637
 
653
- super().clean(*args, **kwargs)
654
-
655
638
  def save(self, *args, **kwargs):
656
639
  self.full_clean()
657
640
 
@@ -676,7 +659,7 @@ class Zone(NetBoxModel):
676
659
  new_zone or name_changed or view_changed or status_changed
677
660
  ) and self.is_reverse_zone:
678
661
  zones = Zone.objects.filter(
679
- view=self.view,
662
+ self.view_filter,
680
663
  arpa_network__net_contains_or_equals=self.arpa_network,
681
664
  )
682
665
  address_records = record.Record.objects.filter(
@@ -712,7 +695,7 @@ class Zone(NetBoxModel):
712
695
  or rfc2317_changed
713
696
  ) and self.is_rfc2317_zone:
714
697
  zones = Zone.objects.filter(
715
- view=self.view,
698
+ self.view_filter,
716
699
  arpa_network__net_contains=self.rfc2317_prefix,
717
700
  )
718
701
  address_records = record.Record.objects.filter(
@@ -5,7 +5,6 @@ from rest_framework.exceptions import PermissionDenied as APIPermissionDenied
5
5
 
6
6
  from netbox.signals import post_clean
7
7
  from netbox.context import current_request
8
- from netbox.plugins.utils import get_plugin_config
9
8
  from ipam.models import IPAddress
10
9
 
11
10
  from netbox_dns.models import Zone
@@ -19,6 +18,8 @@ from netbox_dns.utilities.ipam_coupling import (
19
18
  DNSPermissionDenied,
20
19
  )
21
20
 
21
+ from netbox.plugins.utils import get_plugin_config
22
+
22
23
 
23
24
  @receiver(post_clean, sender=IPAddress)
24
25
  def ip_address_check_permissions_save(instance, **kwargs):
netbox_dns/tables/view.py CHANGED
@@ -14,12 +14,5 @@ class ViewTable(TenancyColumnsMixin, NetBoxTable):
14
14
 
15
15
  class Meta(NetBoxTable.Meta):
16
16
  model = View
17
- fields = (
18
- "name",
19
- "default_view",
20
- "description",
21
- "tenant",
22
- "tenant_group",
23
- "tags",
24
- )
25
- default_columns = ("name", "default_view")
17
+ fields = ("name", "description", "tenant", "tenant_group", "tags")
18
+ default_columns = ("name",)
@@ -117,7 +117,7 @@ class RelatedDNSObjects(PluginTemplateExtension):
117
117
  )
118
118
 
119
119
 
120
- template_extensions = []
120
+ template_extensions = list()
121
121
 
122
122
  if version.parse(settings.VERSION) < version.parse("3.7.0"):
123
123
  template_extensions.append(RelatedDNSObjects)
@@ -56,7 +56,7 @@
56
56
  <td><a href="{% url 'plugins:netbox_dns:zone_records' pk=object.zone.pk %}">{{ object.zone }}</a></td>
57
57
  {% endif %}
58
58
  </tr>
59
- {% if object.tenant %}
59
+ {% if not object.managed %}
60
60
  <tr>
61
61
  <th scope="row">Tenant</th>
62
62
  <td>
@@ -10,10 +10,6 @@
10
10
  <th scope="row">Name</th>
11
11
  <td>{{ object.name }}</td>
12
12
  </tr>
13
- <tr>
14
- <th scope="row">Default View</th>
15
- <td>{% checkmark object.default_view %}</td>
16
- </tr>
17
13
  {% if object.description %}
18
14
  <tr>
19
15
  <th scope="row">Description</th>
@@ -20,6 +20,7 @@
20
20
  <td>{{ unicode_name }}</td>
21
21
  </tr>
22
22
  {% endif %}
23
+ {% if object.view %}
23
24
  <tr>
24
25
  <th scope="row">View</th>
25
26
  <td>
@@ -28,6 +29,7 @@
28
29
  </a>
29
30
  </td>
30
31
  </tr>
32
+ {% endif %}
31
33
  {% if object.description %}
32
34
  <tr>
33
35
  <th scope="row">Description</th>
@@ -114,11 +116,11 @@
114
116
  <td>{{ object.soa_ttl }}</td>
115
117
  </tr>
116
118
  <tr>
117
- <th scope="row">MName</th>
119
+ <th scope="row">Primary Nameserver</th>
118
120
  <td><a href="{% url 'plugins:netbox_dns:nameserver' pk=object.soa_mname.pk %}">{{ object.soa_mname }}</a></td>
119
121
  </tr>
120
122
  <tr>
121
- <th scope="row">RName</th>
123
+ <th scope="row">Responsible</th>
122
124
  <td>{{ object.soa_rname }}</td>
123
125
  </tr>
124
126
  {% if object.soa_serial_auto %}