netbox-plugin-dns 0.22.9__py3-none-any.whl → 1.0.0__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 (115) hide show
  1. netbox_dns/__init__.py +4 -15
  2. netbox_dns/api/nested_serializers.py +4 -60
  3. netbox_dns/api/serializers.py +8 -314
  4. netbox_dns/api/serializers_/__init__.py +0 -0
  5. netbox_dns/api/serializers_/contact.py +37 -0
  6. netbox_dns/api/serializers_/nameserver.py +39 -0
  7. netbox_dns/api/serializers_/record.py +90 -0
  8. netbox_dns/api/serializers_/registrar.py +31 -0
  9. netbox_dns/api/serializers_/view.py +34 -0
  10. netbox_dns/api/serializers_/zone.py +159 -0
  11. netbox_dns/api/views.py +13 -13
  12. netbox_dns/fields/network.py +21 -22
  13. netbox_dns/fields/rfc2317.py +3 -3
  14. netbox_dns/{filters → filtersets}/contact.py +2 -1
  15. netbox_dns/filtersets/nameserver.py +37 -0
  16. netbox_dns/{filters → filtersets}/record.py +51 -6
  17. netbox_dns/{filters → filtersets}/registrar.py +2 -1
  18. netbox_dns/{filters → filtersets}/view.py +2 -2
  19. netbox_dns/filtersets/zone.py +205 -0
  20. netbox_dns/forms/contact.py +61 -33
  21. netbox_dns/forms/nameserver.py +21 -8
  22. netbox_dns/forms/record.py +65 -38
  23. netbox_dns/forms/registrar.py +45 -15
  24. netbox_dns/forms/view.py +23 -9
  25. netbox_dns/forms/zone.py +182 -188
  26. netbox_dns/graphql/__init__.py +17 -27
  27. netbox_dns/graphql/filters.py +49 -0
  28. netbox_dns/graphql/schema.py +66 -7
  29. netbox_dns/graphql/types.py +143 -0
  30. netbox_dns/management/commands/cleanup_database.py +0 -4
  31. netbox_dns/management/commands/cleanup_rrset_ttl.py +2 -4
  32. netbox_dns/management/commands/setup_coupling.py +15 -15
  33. netbox_dns/management/commands/update_soa.py +1 -1
  34. netbox_dns/migrations/0001_squashed_netbox_dns_0_15.py +0 -27
  35. netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py +558 -0
  36. netbox_dns/migrations/{0013_add_nameserver_zone_record_description.py → 0002_contact_description_registrar_description.py} +4 -9
  37. netbox_dns/migrations/0003_default_view.py +15 -0
  38. netbox_dns/migrations/0004_create_and_assign_default_view.py +26 -0
  39. netbox_dns/migrations/0005_alter_zone_view_not_null.py +18 -0
  40. netbox_dns/migrations/0020_netbox_3_4.py +1 -1
  41. netbox_dns/models/contact.py +6 -1
  42. netbox_dns/models/nameserver.py +2 -0
  43. netbox_dns/models/record.py +28 -33
  44. netbox_dns/models/registrar.py +5 -1
  45. netbox_dns/models/view.py +54 -1
  46. netbox_dns/models/zone.py +68 -54
  47. netbox_dns/navigation.py +1 -15
  48. netbox_dns/signals/ipam_coupling.py +3 -9
  49. netbox_dns/tables/contact.py +1 -0
  50. netbox_dns/tables/nameserver.py +0 -2
  51. netbox_dns/tables/registrar.py +1 -0
  52. netbox_dns/tables/view.py +9 -2
  53. netbox_dns/template_content.py +4 -8
  54. netbox_dns/templates/netbox_dns/contact.html +60 -56
  55. netbox_dns/templates/netbox_dns/nameserver.html +27 -27
  56. netbox_dns/templates/netbox_dns/record.html +92 -94
  57. netbox_dns/templates/netbox_dns/registrar.html +38 -36
  58. netbox_dns/templates/netbox_dns/view.html +25 -21
  59. netbox_dns/templates/netbox_dns/zone/base.html +5 -3
  60. netbox_dns/templates/netbox_dns/zone/child.html +3 -3
  61. netbox_dns/templates/netbox_dns/zone/managed_record.html +1 -1
  62. netbox_dns/templates/netbox_dns/zone/record.html +3 -3
  63. netbox_dns/templates/netbox_dns/zone/registration.html +26 -27
  64. netbox_dns/templates/netbox_dns/zone/rfc2317_child_zone.html +1 -1
  65. netbox_dns/templates/netbox_dns/zone.html +148 -149
  66. netbox_dns/urls/__init__.py +17 -0
  67. netbox_dns/urls/contact.py +51 -0
  68. netbox_dns/urls/nameserver.py +69 -0
  69. netbox_dns/urls/record.py +41 -0
  70. netbox_dns/urls/registrar.py +63 -0
  71. netbox_dns/urls/view.py +39 -0
  72. netbox_dns/urls/zone.py +57 -0
  73. netbox_dns/utilities/__init__.py +1 -6
  74. netbox_dns/utilities/ipam_coupling.py +2 -7
  75. netbox_dns/validators/dns_name.py +4 -9
  76. netbox_dns/validators/rfc2317.py +2 -2
  77. netbox_dns/views/contact.py +4 -4
  78. netbox_dns/views/nameserver.py +5 -5
  79. netbox_dns/views/record.py +15 -23
  80. netbox_dns/views/registrar.py +4 -4
  81. netbox_dns/views/view.py +4 -4
  82. netbox_dns/views/zone.py +7 -8
  83. {netbox_plugin_dns-0.22.9.dist-info → netbox_plugin_dns-1.0.0.dist-info}/METADATA +27 -13
  84. netbox_plugin_dns-1.0.0.dist-info/RECORD +112 -0
  85. netbox_dns/filters/nameserver.py +0 -18
  86. netbox_dns/filters/zone.py +0 -112
  87. netbox_dns/graphql/contact.py +0 -19
  88. netbox_dns/graphql/nameserver.py +0 -19
  89. netbox_dns/graphql/record.py +0 -19
  90. netbox_dns/graphql/registrar.py +0 -19
  91. netbox_dns/graphql/view.py +0 -19
  92. netbox_dns/graphql/zone.py +0 -19
  93. netbox_dns/migrations/0001_initial.py +0 -115
  94. netbox_dns/migrations/0002_zone_default_ttl.py +0 -18
  95. netbox_dns/migrations/0003_soa_managed_records.py +0 -112
  96. netbox_dns/migrations/0004_create_ptr_for_a_aaaa_records.py +0 -80
  97. netbox_dns/migrations/0005_update_ns_records.py +0 -41
  98. netbox_dns/migrations/0006_zone_soa_serial_auto.py +0 -29
  99. netbox_dns/migrations/0007_alter_zone_soa_serial_auto.py +0 -17
  100. netbox_dns/migrations/0008_zone_status_names.py +0 -21
  101. netbox_dns/migrations/0009_netbox32.py +0 -71
  102. netbox_dns/migrations/0010_update_soa_records.py +0 -58
  103. netbox_dns/migrations/0011_add_view_model.py +0 -70
  104. netbox_dns/migrations/0012_adjust_zone_and_record.py +0 -17
  105. netbox_dns/migrations/0014_add_view_description.py +0 -16
  106. netbox_dns/migrations/0015_add_record_status.py +0 -17
  107. netbox_dns/migrations/0016_cleanup_ptr_records.py +0 -38
  108. netbox_dns/migrations/0017_alter_record_ttl.py +0 -17
  109. netbox_dns/migrations/0018_zone_arpa_network.py +0 -51
  110. netbox_dns/migrations/0019_update_ns_ttl.py +0 -19
  111. netbox_dns/urls.py +0 -297
  112. netbox_plugin_dns-0.22.9.dist-info/RECORD +0 -117
  113. /netbox_dns/{filters → filtersets}/__init__.py +0 -0
  114. {netbox_plugin_dns-0.22.9.dist-info → netbox_plugin_dns-1.0.0.dist-info}/LICENSE +0 -0
  115. {netbox_plugin_dns-0.22.9.dist-info → netbox_plugin_dns-1.0.0.dist-info}/WHEEL +0 -0
@@ -7,21 +7,14 @@ from dns import name as dns_name
7
7
  from django.core.exceptions import ValidationError
8
8
  from django.db import transaction, models
9
9
  from django.db.models import Q, ExpressionWrapper, BooleanField, Min
10
- from django.db.models.functions import Length
11
10
  from django.urls import reverse
12
11
 
13
12
  from netbox.models import NetBoxModel
14
13
  from netbox.search import SearchIndex, register_search
14
+ from netbox.plugins.utils import get_plugin_config
15
15
  from utilities.querysets import RestrictedQuerySet
16
16
  from utilities.choices import ChoiceSet
17
17
 
18
- try:
19
- # NetBox 3.5.0 - 3.5.7, 3.5.9+
20
- from extras.plugins import get_plugin_config
21
- except ImportError:
22
- # NetBox 3.5.8
23
- from extras.plugins.utils import get_plugin_config
24
-
25
18
  from netbox_dns.fields import AddressField
26
19
  from netbox_dns.utilities import (
27
20
  arpa_to_prefix,
@@ -43,7 +36,9 @@ def min_ttl(*ttl_list):
43
36
 
44
37
 
45
38
  class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
46
- """Special Manager for records providing the activity status annotation"""
39
+ """
40
+ Custom manager for records providing the activity status annotation
41
+ """
47
42
 
48
43
  def get_queryset(self):
49
44
  return (
@@ -218,7 +213,7 @@ class Record(NetBoxModel):
218
213
  name = dns_name.from_text(self.fqdn).relativize(dns_name.root).to_unicode()
219
214
  except dns_name.IDNAException:
220
215
  name = self.name
221
- except dns_name.LabelTooLong as exc:
216
+ except dns_name.LabelTooLong:
222
217
  name = f"{self.name[:59]}..."
223
218
 
224
219
  return f"{name} [{self.type}]"
@@ -231,15 +226,15 @@ class Record(NetBoxModel):
231
226
  return RecordStatusChoices.colors.get(self.status)
232
227
 
233
228
  def get_absolute_url(self):
234
- return reverse("plugins:netbox_dns:record", kwargs={"pk": self.id})
229
+ return reverse("plugins:netbox_dns:record", kwargs={"pk": self.pk})
235
230
 
236
231
  @property
237
232
  def value_fqdn(self):
238
233
  if self.type != RecordTypeChoices.CNAME:
239
234
  return None
240
235
 
241
- zone = dns_name.from_text(self.zone.name)
242
- value_fqdn = dns_name.from_text(self.value, origin=zone)
236
+ _zone = dns_name.from_text(self.zone.name)
237
+ value_fqdn = dns_name.from_text(self.value, origin=_zone)
243
238
 
244
239
  return value_fqdn.to_text()
245
240
 
@@ -291,12 +286,14 @@ class Record(NetBoxModel):
291
286
  dns_name.from_text(self.ptr_record.zone.rfc2317_parent_zone.name)
292
287
  )
293
288
 
289
+ return None
290
+
294
291
  @property
295
292
  def ptr_zone(self):
296
293
  if self.type == RecordTypeChoices.A:
297
294
  ptr_zone = (
298
295
  zone.Zone.objects.filter(
299
- self.zone.view_filter,
296
+ view=self.zone.view,
300
297
  rfc2317_prefix__net_contains=self.value,
301
298
  )
302
299
  .order_by("rfc2317_prefix__net_mask_length")
@@ -308,7 +305,7 @@ class Record(NetBoxModel):
308
305
 
309
306
  ptr_zone = (
310
307
  zone.Zone.objects.filter(
311
- self.zone.view_filter, arpa_network__net_contains=self.value
308
+ view=self.zone.view, arpa_network__net_contains=self.value
312
309
  )
313
310
  .order_by("arpa_network__net_mask_length")
314
311
  .last()
@@ -422,10 +419,8 @@ class Record(NetBoxModel):
422
419
  self.rfc2317_cname_record.save(save_zone_serial=save_zone_serial)
423
420
 
424
421
  return
425
- else:
426
- self.remove_from_rfc2317_cname_record(
427
- save_zone_serial=save_zone_serial
428
- )
422
+
423
+ self.remove_from_rfc2317_cname_record(save_zone_serial=save_zone_serial)
429
424
 
430
425
  rfc2317_cname_record = Record.objects.filter(
431
426
  name=cname_name,
@@ -466,14 +461,14 @@ class Record(NetBoxModel):
466
461
 
467
462
  def validate_name(self):
468
463
  try:
469
- zone = dns_name.from_text(self.zone.name, origin=dns_name.root)
464
+ _zone = dns_name.from_text(self.zone.name, origin=dns_name.root)
470
465
  name = dns_name.from_text(self.name, origin=None)
471
- fqdn = dns_name.from_text(self.name, origin=zone)
466
+ fqdn = dns_name.from_text(self.name, origin=_zone)
472
467
 
473
- zone.to_unicode()
468
+ _zone.to_unicode()
474
469
  name.to_unicode()
475
470
 
476
- self.name = name.relativize(zone).to_text()
471
+ self.name = name.relativize(_zone).to_text()
477
472
  self.fqdn = fqdn.to_text()
478
473
 
479
474
  except dns.exception.DNSException as exc:
@@ -483,7 +478,7 @@ class Record(NetBoxModel):
483
478
  }
484
479
  )
485
480
 
486
- if not fqdn.is_subdomain(zone):
481
+ if not fqdn.is_subdomain(_zone):
487
482
  raise ValidationError(
488
483
  {
489
484
  "name": f"{self.name} is not a name in {self.zone.name}",
@@ -491,7 +486,7 @@ class Record(NetBoxModel):
491
486
  )
492
487
 
493
488
  if self.type not in get_plugin_config(
494
- "netbox_dns", "tolerate_non_rfc1035_types", default=list()
489
+ "netbox_dns", "tolerate_non_rfc1035_types", default=[]
495
490
  ):
496
491
  try:
497
492
  validate_extended_hostname(
@@ -501,7 +496,7 @@ class Record(NetBoxModel):
501
496
  in get_plugin_config(
502
497
  "netbox_dns",
503
498
  "tolerate_leading_underscore_types",
504
- default=list(),
499
+ default=[],
505
500
  )
506
501
  ),
507
502
  )
@@ -669,7 +664,7 @@ class Record(NetBoxModel):
669
664
  if self.type == RecordTypeChoices.SOA and self.name != "@":
670
665
  raise ValidationError(
671
666
  {
672
- "name": f"SOA records are only allowed with name @ and are created automatically by NetBox DNS"
667
+ "name": "SOA records are only allowed with name @ and are created automatically by NetBox DNS"
673
668
  }
674
669
  ) from None
675
670
 
@@ -738,9 +733,9 @@ class Record(NetBoxModel):
738
733
 
739
734
  super().save(*args, **kwargs)
740
735
 
741
- zone = self.zone
742
- if self.type != RecordTypeChoices.SOA and zone.soa_serial_auto:
743
- zone.update_serial(save_zone_serial=save_zone_serial)
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)
744
739
 
745
740
  def delete(self, *args, save_zone_serial=True, **kwargs):
746
741
  if self.rfc2317_cname_record:
@@ -751,9 +746,9 @@ class Record(NetBoxModel):
751
746
 
752
747
  super().delete(*args, **kwargs)
753
748
 
754
- zone = self.zone
755
- if zone.soa_serial_auto:
756
- zone.update_serial(save_zone_serial=save_zone_serial)
749
+ _zone = self.zone
750
+ if _zone.soa_serial_auto:
751
+ _zone.update_serial(save_zone_serial=save_zone_serial)
757
752
 
758
753
 
759
754
  @register_search
@@ -13,6 +13,10 @@ class Registrar(NetBoxModel):
13
13
  unique=True,
14
14
  max_length=255,
15
15
  )
16
+ description = models.CharField(
17
+ blank=True,
18
+ max_length=200,
19
+ )
16
20
  iana_id = models.IntegerField(
17
21
  verbose_name="IANA ID",
18
22
  null=True,
@@ -43,7 +47,7 @@ class Registrar(NetBoxModel):
43
47
  )
44
48
 
45
49
  def get_absolute_url(self):
46
- return reverse("plugins:netbox_dns:registrar", kwargs={"pk": self.id})
50
+ return reverse("plugins:netbox_dns:registrar", kwargs={"pk": self.pk})
47
51
 
48
52
  def __str__(self):
49
53
  return str(self.name)
netbox_dns/models/view.py CHANGED
@@ -1,8 +1,11 @@
1
1
  from django.db import models
2
2
  from django.urls import reverse
3
+ from django.core.exceptions import ValidationError
3
4
 
4
5
  from netbox.models import NetBoxModel
5
6
  from netbox.search import SearchIndex, register_search
7
+ from netbox.context import current_request
8
+ from utilities.exceptions import AbortRequest
6
9
 
7
10
 
8
11
  class View(NetBoxModel):
@@ -14,6 +17,9 @@ class View(NetBoxModel):
14
17
  max_length=200,
15
18
  blank=True,
16
19
  )
20
+ default_view = models.BooleanField(
21
+ default=False,
22
+ )
17
23
  tenant = models.ForeignKey(
18
24
  to="tenancy.Tenant",
19
25
  on_delete=models.PROTECT,
@@ -24,8 +30,12 @@ class View(NetBoxModel):
24
30
 
25
31
  clone_fields = ["name", "description"]
26
32
 
33
+ @classmethod
34
+ def get_default_view(cls):
35
+ return cls.objects.get(default_view=True)
36
+
27
37
  def get_absolute_url(self):
28
- return reverse("plugins:netbox_dns:view", kwargs={"pk": self.id})
38
+ return reverse("plugins:netbox_dns:view", kwargs={"pk": self.pk})
29
39
 
30
40
  def __str__(self):
31
41
  return str(self.name)
@@ -33,6 +43,49 @@ class View(NetBoxModel):
33
43
  class Meta:
34
44
  ordering = ("name",)
35
45
 
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
+
36
89
 
37
90
  @register_search
38
91
  class ViewIndex(SearchIndex):
netbox_dns/models/zone.py CHANGED
@@ -15,20 +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
18
19
 
19
20
  from netbox.models import NetBoxModel
20
21
  from netbox.search import SearchIndex, register_search
22
+ from netbox.plugins.utils import get_plugin_config
21
23
  from utilities.querysets import RestrictedQuerySet
22
24
  from utilities.choices import ChoiceSet
23
25
  from ipam.models import IPAddress
24
26
 
25
- try:
26
- # NetBox 3.5.0 - 3.5.7, 3.5.9+
27
- from extras.plugins import get_plugin_config
28
- except ImportError:
29
- # NetBox 3.5.8
30
- from extras.plugins.utils import get_plugin_config
31
-
32
27
  from netbox_dns.fields import NetworkField, RFC2317NetworkField
33
28
  from netbox_dns.utilities import (
34
29
  arpa_to_prefix,
@@ -42,13 +37,17 @@ from netbox_dns.validators import (
42
37
  )
43
38
 
44
39
  # +
45
- # This is a hack designed to break cyclic imports between Record and Zone
40
+ # This is a hack designed to break cyclic imports between View, Record and Zone
46
41
  # -
47
42
  import netbox_dns.models.record as record
43
+ import netbox_dns.models.view as view
44
+ import netbox_dns.models.nameserver as nameserver
48
45
 
49
46
 
50
47
  class ZoneManager(models.Manager.from_queryset(RestrictedQuerySet)):
51
- """Special Manager for zones providing the activity status annotation"""
48
+ """
49
+ Custom manager for zones providing the activity status annotation
50
+ """
52
51
 
53
52
  def get_queryset(self):
54
53
  return (
@@ -84,8 +83,7 @@ class Zone(NetBoxModel):
84
83
  view = models.ForeignKey(
85
84
  to="View",
86
85
  on_delete=models.PROTECT,
87
- blank=True,
88
- null=True,
86
+ null=False,
89
87
  )
90
88
  name = models.CharField(
91
89
  max_length=255,
@@ -288,11 +286,20 @@ class Zone(NetBoxModel):
288
286
  except dns_name.IDNAException:
289
287
  name = self.name
290
288
 
291
- if self.view:
289
+ if not self.view.default_view:
292
290
  return f"[{self.view}] {name}"
293
291
 
294
292
  return str(name)
295
293
 
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
+
296
303
  @property
297
304
  def display_name(self):
298
305
  return name_to_unicode(self.name)
@@ -320,11 +327,11 @@ class Zone(NetBoxModel):
320
327
 
321
328
  def get_rfc2317_parent_zone(self):
322
329
  if not self.is_rfc2317_zone:
323
- return
330
+ return None
324
331
 
325
332
  return (
326
333
  Zone.objects.filter(
327
- self.view_filter,
334
+ view=self.view,
328
335
  arpa_network__net_contains=self.rfc2317_prefix,
329
336
  )
330
337
  .order_by("arpa_network__net_mask_length")
@@ -345,12 +352,6 @@ class Zone(NetBoxModel):
345
352
  )
346
353
  )
347
354
 
348
- @property
349
- def view_filter(self):
350
- if self.view is None:
351
- return Q(view__isnull=True)
352
- return Q(view=self.view)
353
-
354
355
  def record_count(self, managed=False):
355
356
  return record.Record.objects.filter(zone=self, managed=managed).count()
356
357
 
@@ -420,19 +421,15 @@ class Zone(NetBoxModel):
420
421
  if not nameservers:
421
422
  ns_errors.append(f"No nameservers are configured for zone {self}")
422
423
 
423
- for nameserver in nameservers:
424
- name = dns_name.from_text(nameserver.name, origin=None)
424
+ for _nameserver in nameservers:
425
+ name = dns_name.from_text(_nameserver.name, origin=None)
425
426
  parent = name.parent()
426
427
 
427
428
  if len(parent) < 2:
428
429
  continue
429
430
 
430
- view_condition = (
431
- Q(view__isnull=True) if self.view is None else Q(view_id=self.view.pk)
432
- )
433
-
434
431
  try:
435
- ns_zone = Zone.objects.get(view_condition, name=parent.to_text())
432
+ ns_zone = Zone.objects.get(view_id=self.view.pk, name=parent.to_text())
436
433
  except ObjectDoesNotExist:
437
434
  continue
438
435
 
@@ -440,7 +437,7 @@ class Zone(NetBoxModel):
440
437
  address_records = record.Record.objects.filter(
441
438
  Q(zone=ns_zone),
442
439
  Q(status__in=record.Record.ACTIVE_STATUS_LIST),
443
- Q(Q(name=f"{nameserver.name}.") | Q(name=relative_name)),
440
+ Q(Q(name=f"{_nameserver.name}.") | Q(name=relative_name)),
444
441
  Q(
445
442
  Q(type=record.RecordTypeChoices.A)
446
443
  | Q(type=record.RecordTypeChoices.AAAA)
@@ -449,13 +446,13 @@ class Zone(NetBoxModel):
449
446
 
450
447
  if not address_records:
451
448
  ns_warnings.append(
452
- 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}"
453
450
  )
454
451
 
455
452
  return ns_warnings, ns_errors
456
453
 
457
454
  def get_auto_serial(self):
458
- records = record.Record.objects.filter(zone=self).exclude(
455
+ records = record.Record.objects.filter(zone_id=self.pk).exclude(
459
456
  type=record.RecordTypeChoices.SOA
460
457
  )
461
458
  if records:
@@ -495,19 +492,6 @@ class Zone(NetBoxModel):
495
492
  def network_from_name(self):
496
493
  return arpa_to_prefix(self.name)
497
494
 
498
- def check_name_conflict(self):
499
- if self.view is None:
500
- if (
501
- Zone.objects.exclude(pk=self.pk)
502
- .filter(name=self.name.rstrip("."), view__isnull=True)
503
- .exists()
504
- ):
505
- raise ValidationError(
506
- {
507
- "name": f"A zone with name {self.name} and no view already exists."
508
- }
509
- )
510
-
511
495
  def update_rfc2317_parent_zone(self):
512
496
  if not self.is_rfc2317_zone:
513
497
  return
@@ -558,8 +542,33 @@ class Zone(NetBoxModel):
558
542
  ptr_zone.save_soa_serial()
559
543
  ptr_zone.update_soa_record()
560
544
 
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
+
561
569
  def clean(self, *args, **kwargs):
562
- self.check_name_conflict()
570
+ if self.soa_ttl is None:
571
+ self.soa_ttl = self.default_ttl
563
572
 
564
573
  try:
565
574
  self.name = normalize_name(self.name)
@@ -579,6 +588,8 @@ class Zone(NetBoxModel):
579
588
  }
580
589
  ) from None
581
590
 
591
+ if self.soa_rname in (None, ""):
592
+ raise ValidationError("soa_rname not set and no default value defined")
582
593
  try:
583
594
  dns_name.from_text(self.soa_rname, origin=dns_name.root)
584
595
  validate_fqdn(self.soa_rname)
@@ -589,12 +600,13 @@ class Zone(NetBoxModel):
589
600
  }
590
601
  ) from None
591
602
 
592
- if self.soa_serial is None and not self.soa_serial_auto:
593
- raise ValidationError(
594
- {
595
- "soa_serial": f"soa_serial is not defined and soa_serial_auto is disabled for zone {self.name}."
596
- }
597
- )
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
+ )
598
610
 
599
611
  if self.is_reverse_zone:
600
612
  self.arpa_network = self.network_from_name
@@ -603,7 +615,7 @@ class Zone(NetBoxModel):
603
615
  if self.arpa_network is not None:
604
616
  raise ValidationError(
605
617
  {
606
- "rfc2317_prefix": f"A regular reverse zone can not be used as an RFC2317 zone."
618
+ "rfc2317_prefix": "A regular reverse zone can not be used as an RFC2317 zone."
607
619
  }
608
620
  )
609
621
 
@@ -622,7 +634,7 @@ class Zone(NetBoxModel):
622
634
  self.rfc2317_parent_zone = None
623
635
 
624
636
  overlapping_zones = Zone.objects.filter(
625
- self.view_filter,
637
+ view=self.view,
626
638
  rfc2317_prefix__net_overlap=self.rfc2317_prefix,
627
639
  active=True,
628
640
  ).exclude(pk=self.pk)
@@ -638,6 +650,8 @@ class Zone(NetBoxModel):
638
650
  self.rfc2317_parent_managed = False
639
651
  self.rfc2317_parent_zone = None
640
652
 
653
+ super().clean(*args, **kwargs)
654
+
641
655
  def save(self, *args, **kwargs):
642
656
  self.full_clean()
643
657
 
@@ -662,7 +676,7 @@ class Zone(NetBoxModel):
662
676
  new_zone or name_changed or view_changed or status_changed
663
677
  ) and self.is_reverse_zone:
664
678
  zones = Zone.objects.filter(
665
- self.view_filter,
679
+ view=self.view,
666
680
  arpa_network__net_contains_or_equals=self.arpa_network,
667
681
  )
668
682
  address_records = record.Record.objects.filter(
@@ -698,7 +712,7 @@ class Zone(NetBoxModel):
698
712
  or rfc2317_changed
699
713
  ) and self.is_rfc2317_zone:
700
714
  zones = Zone.objects.filter(
701
- self.view_filter,
715
+ view=self.view,
702
716
  arpa_network__net_contains=self.rfc2317_prefix,
703
717
  )
704
718
  address_records = record.Record.objects.filter(
netbox_dns/navigation.py CHANGED
@@ -1,6 +1,4 @@
1
- from extras.plugins import PluginMenuButton, PluginMenuItem
2
- from extras.plugins import PluginMenu
3
- from utilities.choices import ButtonColorChoices
1
+ from netbox.plugins import PluginMenuButton, PluginMenuItem, PluginMenu
4
2
 
5
3
  view_menu_item = PluginMenuItem(
6
4
  link="plugins:netbox_dns:view_list",
@@ -11,14 +9,12 @@ view_menu_item = PluginMenuItem(
11
9
  "plugins:netbox_dns:view_add",
12
10
  "Add",
13
11
  "mdi mdi-plus-thick",
14
- ButtonColorChoices.GREEN,
15
12
  permissions=["netbox_dns.add_view"],
16
13
  ),
17
14
  PluginMenuButton(
18
15
  "plugins:netbox_dns:view_import",
19
16
  "Import",
20
17
  "mdi mdi-upload",
21
- ButtonColorChoices.CYAN,
22
18
  permissions=["netbox_dns.add_view"],
23
19
  ),
24
20
  ),
@@ -33,14 +29,12 @@ zone_menu_item = PluginMenuItem(
33
29
  "plugins:netbox_dns:zone_add",
34
30
  "Add",
35
31
  "mdi mdi-plus-thick",
36
- ButtonColorChoices.GREEN,
37
32
  permissions=["netbox_dns.add_zone"],
38
33
  ),
39
34
  PluginMenuButton(
40
35
  "plugins:netbox_dns:zone_import",
41
36
  "Import",
42
37
  "mdi mdi-upload",
43
- ButtonColorChoices.CYAN,
44
38
  permissions=["netbox_dns.add_zone"],
45
39
  ),
46
40
  ),
@@ -55,14 +49,12 @@ nameserver_menu_item = PluginMenuItem(
55
49
  "plugins:netbox_dns:nameserver_add",
56
50
  "Add",
57
51
  "mdi mdi-plus-thick",
58
- ButtonColorChoices.GREEN,
59
52
  permissions=["netbox_dns.add_nameserver"],
60
53
  ),
61
54
  PluginMenuButton(
62
55
  "plugins:netbox_dns:nameserver_import",
63
56
  "Import",
64
57
  "mdi mdi-upload",
65
- ButtonColorChoices.CYAN,
66
58
  permissions=["netbox_dns.add_nameserver"],
67
59
  ),
68
60
  ),
@@ -77,14 +69,12 @@ record_menu_item = PluginMenuItem(
77
69
  "plugins:netbox_dns:record_add",
78
70
  "Add",
79
71
  "mdi mdi-plus-thick",
80
- ButtonColorChoices.GREEN,
81
72
  permissions=["netbox_dns.add_record"],
82
73
  ),
83
74
  PluginMenuButton(
84
75
  "plugins:netbox_dns:record_import",
85
76
  "Import",
86
77
  "mdi mdi-upload",
87
- ButtonColorChoices.CYAN,
88
78
  permissions=["netbox_dns.add_record"],
89
79
  ),
90
80
  ),
@@ -105,14 +95,12 @@ registrar_menu_item = PluginMenuItem(
105
95
  "plugins:netbox_dns:registrar_add",
106
96
  "Add",
107
97
  "mdi mdi-plus-thick",
108
- ButtonColorChoices.GREEN,
109
98
  permissions=["netbox_dns.add_registrar"],
110
99
  ),
111
100
  PluginMenuButton(
112
101
  "plugins:netbox_dns:registrar_import",
113
102
  "Import",
114
103
  "mdi mdi-upload",
115
- ButtonColorChoices.CYAN,
116
104
  permissions=["netbox_dns.add_registrar"],
117
105
  ),
118
106
  ),
@@ -127,14 +115,12 @@ contact_menu_item = PluginMenuItem(
127
115
  "plugins:netbox_dns:contact_add",
128
116
  "Add",
129
117
  "mdi mdi-plus-thick",
130
- ButtonColorChoices.GREEN,
131
118
  permissions=["netbox_dns.add_contact"],
132
119
  ),
133
120
  PluginMenuButton(
134
121
  "plugins:netbox_dns:contact_import",
135
122
  "Import",
136
123
  "mdi mdi-upload",
137
- ButtonColorChoices.CYAN,
138
124
  permissions=["netbox_dns.add_contact"],
139
125
  ),
140
126
  ),
@@ -5,6 +5,7 @@ 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
8
9
  from ipam.models import IPAddress
9
10
 
10
11
  from netbox_dns.models import Zone
@@ -18,13 +19,6 @@ from netbox_dns.utilities.ipam_coupling import (
18
19
  DNSPermissionDenied,
19
20
  )
20
21
 
21
- try:
22
- # NetBox 3.5.0 - 3.5.7, 3.5.9+
23
- from extras.plugins import get_plugin_config
24
- except ImportError:
25
- # NetBox 3.5.8
26
- from extras.plugins.utils import get_plugin_config
27
-
28
22
 
29
23
  @receiver(post_clean, sender=IPAddress)
30
24
  def ip_address_check_permissions_save(instance, **kwargs):
@@ -39,14 +33,14 @@ def ip_address_check_permissions_save(instance, **kwargs):
39
33
  return
40
34
 
41
35
  try:
42
- if instance.id is None:
36
+ if instance.pk is None:
43
37
  record = new_address_record(instance)
44
38
  if record is not None:
45
39
  record.full_clean()
46
40
  check_permission(request, "netbox_dns.add_record", record)
47
41
 
48
42
  else:
49
- if not dns_changed(IPAddress.objects.get(pk=instance.id), instance):
43
+ if not dns_changed(IPAddress.objects.get(pk=instance.pk), instance):
50
44
  return
51
45
 
52
46
  record = get_address_record(instance)