netbox-plugin-dns 0.21.4__py3-none-any.whl → 1.4.7__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.
Files changed (232) hide show
  1. netbox_dns/__init__.py +106 -41
  2. netbox_dns/api/field_serializers.py +25 -0
  3. netbox_dns/api/nested_serializers.py +95 -52
  4. netbox_dns/api/serializers.py +14 -296
  5. netbox_dns/api/serializers_/__init__.py +0 -0
  6. netbox_dns/api/serializers_/dnssec_key_template.py +69 -0
  7. netbox_dns/api/serializers_/dnssec_policy.py +165 -0
  8. netbox_dns/api/serializers_/nameserver.py +56 -0
  9. netbox_dns/api/serializers_/prefix.py +18 -0
  10. netbox_dns/api/serializers_/record.py +105 -0
  11. netbox_dns/api/serializers_/record_template.py +71 -0
  12. netbox_dns/api/serializers_/registrar.py +45 -0
  13. netbox_dns/api/serializers_/registration_contact.py +50 -0
  14. netbox_dns/api/serializers_/view.py +81 -0
  15. netbox_dns/api/serializers_/zone.py +247 -0
  16. netbox_dns/api/serializers_/zone_template.py +157 -0
  17. netbox_dns/api/urls.py +13 -2
  18. netbox_dns/api/views.py +96 -58
  19. netbox_dns/choices/__init__.py +4 -0
  20. netbox_dns/choices/dnssec_key_template.py +67 -0
  21. netbox_dns/choices/dnssec_policy.py +40 -0
  22. netbox_dns/choices/record.py +104 -0
  23. netbox_dns/choices/utilities.py +4 -0
  24. netbox_dns/choices/zone.py +119 -0
  25. netbox_dns/fields/__init__.py +4 -0
  26. netbox_dns/fields/address.py +22 -16
  27. netbox_dns/fields/choice_array.py +33 -0
  28. netbox_dns/fields/ipam.py +15 -0
  29. netbox_dns/fields/network.py +42 -18
  30. netbox_dns/fields/rfc2317.py +97 -0
  31. netbox_dns/fields/timeperiod.py +33 -0
  32. netbox_dns/filters.py +7 -0
  33. netbox_dns/filtersets/__init__.py +12 -0
  34. netbox_dns/filtersets/dnssec_key_template.py +57 -0
  35. netbox_dns/filtersets/dnssec_policy.py +101 -0
  36. netbox_dns/filtersets/nameserver.py +46 -0
  37. netbox_dns/filtersets/record.py +135 -0
  38. netbox_dns/filtersets/record_template.py +59 -0
  39. netbox_dns/{filters → filtersets}/registrar.py +8 -1
  40. netbox_dns/{filters/contact.py → filtersets/registration_contact.py} +9 -3
  41. netbox_dns/filtersets/view.py +45 -0
  42. netbox_dns/filtersets/zone.py +254 -0
  43. netbox_dns/filtersets/zone_template.py +165 -0
  44. netbox_dns/forms/__init__.py +5 -1
  45. netbox_dns/forms/dnssec_key_template.py +250 -0
  46. netbox_dns/forms/dnssec_policy.py +654 -0
  47. netbox_dns/forms/nameserver.py +121 -27
  48. netbox_dns/forms/record.py +215 -104
  49. netbox_dns/forms/record_template.py +285 -0
  50. netbox_dns/forms/registrar.py +108 -31
  51. netbox_dns/forms/registration_contact.py +282 -0
  52. netbox_dns/forms/view.py +331 -20
  53. netbox_dns/forms/zone.py +769 -373
  54. netbox_dns/forms/zone_template.py +463 -0
  55. netbox_dns/graphql/__init__.py +25 -22
  56. netbox_dns/graphql/enums.py +41 -0
  57. netbox_dns/graphql/filter_lookups.py +13 -0
  58. netbox_dns/graphql/filters/__init__.py +12 -0
  59. netbox_dns/graphql/filters/dnssec_key_template.py +63 -0
  60. netbox_dns/graphql/filters/dnssec_policy.py +124 -0
  61. netbox_dns/graphql/filters/nameserver.py +32 -0
  62. netbox_dns/graphql/filters/record.py +89 -0
  63. netbox_dns/graphql/filters/record_template.py +55 -0
  64. netbox_dns/graphql/filters/registrar.py +30 -0
  65. netbox_dns/graphql/filters/registration_contact.py +27 -0
  66. netbox_dns/graphql/filters/view.py +28 -0
  67. netbox_dns/graphql/filters/zone.py +147 -0
  68. netbox_dns/graphql/filters/zone_template.py +97 -0
  69. netbox_dns/graphql/schema.py +89 -7
  70. netbox_dns/graphql/types.py +355 -0
  71. netbox_dns/locale/de/LC_MESSAGES/django.mo +0 -0
  72. netbox_dns/locale/en/LC_MESSAGES/django.mo +0 -0
  73. netbox_dns/locale/fr/LC_MESSAGES/django.mo +0 -0
  74. netbox_dns/management/commands/cleanup_database.py +175 -156
  75. netbox_dns/management/commands/cleanup_rrset_ttl.py +64 -0
  76. netbox_dns/management/commands/rebuild_dnssync.py +23 -0
  77. netbox_dns/management/commands/setup_dnssync.py +140 -0
  78. netbox_dns/migrations/0001_squashed_netbox_dns_0_15.py +0 -27
  79. netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py +557 -0
  80. netbox_dns/migrations/{0013_add_nameserver_zone_record_description.py → 0002_contact_description_registrar_description.py} +4 -9
  81. netbox_dns/migrations/0003_default_view.py +15 -0
  82. netbox_dns/migrations/0004_create_and_assign_default_view.py +26 -0
  83. netbox_dns/migrations/0005_alter_zone_view_not_null.py +18 -0
  84. netbox_dns/migrations/0006_templating.py +172 -0
  85. netbox_dns/migrations/0007_alter_ordering_options.py +25 -0
  86. netbox_dns/migrations/0008_view_prefixes.py +18 -0
  87. netbox_dns/migrations/0009_rename_contact_registrationcontact.py +36 -0
  88. netbox_dns/migrations/0010_view_ip_address_filter.py +18 -0
  89. netbox_dns/migrations/0011_rename_related_fields.py +63 -0
  90. netbox_dns/migrations/0012_natural_ordering.py +88 -0
  91. netbox_dns/migrations/0013_zonetemplate_soa_mname_zonetemplate_soa_rname.py +30 -0
  92. netbox_dns/migrations/0014_alter_unique_constraints_lowercase.py +42 -0
  93. netbox_dns/migrations/0015_dnssec.py +168 -0
  94. netbox_dns/migrations/{0015_add_record_status.py → 0016_dnssec_policy_status.py} +5 -4
  95. netbox_dns/migrations/0017_dnssec_policy_zone_zone_template.py +41 -0
  96. netbox_dns/migrations/0018_zone_domain_status_zone_expiration_date.py +23 -0
  97. netbox_dns/migrations/0019_dnssecpolicy_parental_agents.py +25 -0
  98. netbox_dns/migrations/0020_netbox_3_4.py +1 -1
  99. netbox_dns/migrations/0020_remove_dnssecpolicy_parental_agents_and_more.py +29 -0
  100. netbox_dns/migrations/0021_alter_record_ptr_record.py +25 -0
  101. netbox_dns/migrations/0021_record_ip_address.py +1 -1
  102. netbox_dns/migrations/0022_alter_record_ipam_ip_address.py +26 -0
  103. netbox_dns/migrations/0023_disable_ptr_false.py +27 -0
  104. netbox_dns/migrations/0024_zonetemplate_parental_agents.py +25 -0
  105. netbox_dns/migrations/0025_remove_zone_inline_signing_and_more.py +22 -0
  106. netbox_dns/migrations/0026_alter_dnssecpolicy_nsec3_opt_out.py +18 -0
  107. netbox_dns/migrations/0026_domain_registration.py +1 -1
  108. netbox_dns/migrations/0027_zone_comments.py +18 -0
  109. netbox_dns/migrations/0028_alter_zone_default_ttl_alter_zone_soa_minimum_and_more.py +54 -0
  110. netbox_dns/migrations/0028_rfc2317_fields.py +44 -0
  111. netbox_dns/migrations/0029_alter_registrationcontact_street.py +18 -0
  112. netbox_dns/migrations/0029_record_fqdn.py +30 -0
  113. netbox_dns/mixins/__init__.py +1 -0
  114. netbox_dns/mixins/object_modification.py +57 -0
  115. netbox_dns/models/__init__.py +5 -1
  116. netbox_dns/models/dnssec_key_template.py +114 -0
  117. netbox_dns/models/dnssec_policy.py +203 -0
  118. netbox_dns/models/nameserver.py +61 -30
  119. netbox_dns/models/record.py +781 -234
  120. netbox_dns/models/record_template.py +198 -0
  121. netbox_dns/models/registrar.py +34 -15
  122. netbox_dns/models/{contact.py → registration_contact.py} +72 -43
  123. netbox_dns/models/view.py +129 -9
  124. netbox_dns/models/zone.py +806 -242
  125. netbox_dns/models/zone_template.py +209 -0
  126. netbox_dns/navigation.py +176 -76
  127. netbox_dns/signals/__init__.py +0 -0
  128. netbox_dns/signals/dnssec.py +32 -0
  129. netbox_dns/signals/ipam_dnssync.py +216 -0
  130. netbox_dns/tables/__init__.py +5 -1
  131. netbox_dns/tables/dnssec_key_template.py +49 -0
  132. netbox_dns/tables/dnssec_policy.py +140 -0
  133. netbox_dns/tables/ipam_dnssync.py +12 -0
  134. netbox_dns/tables/nameserver.py +14 -17
  135. netbox_dns/tables/record.py +117 -59
  136. netbox_dns/tables/record_template.py +91 -0
  137. netbox_dns/tables/registrar.py +20 -10
  138. netbox_dns/tables/{contact.py → registration_contact.py} +22 -11
  139. netbox_dns/tables/view.py +47 -3
  140. netbox_dns/tables/zone.py +62 -31
  141. netbox_dns/tables/zone_template.py +78 -0
  142. netbox_dns/template_content.py +124 -38
  143. netbox_dns/templates/netbox_dns/dnsseckeytemplate.html +70 -0
  144. netbox_dns/templates/netbox_dns/dnssecpolicy.html +163 -0
  145. netbox_dns/templates/netbox_dns/nameserver.html +31 -28
  146. netbox_dns/templates/netbox_dns/record/managed.html +2 -1
  147. netbox_dns/templates/netbox_dns/record/related.html +17 -6
  148. netbox_dns/templates/netbox_dns/record.html +140 -93
  149. netbox_dns/templates/netbox_dns/recordtemplate.html +96 -0
  150. netbox_dns/templates/netbox_dns/registrar.html +41 -34
  151. netbox_dns/templates/netbox_dns/registrationcontact.html +76 -0
  152. netbox_dns/templates/netbox_dns/view/button.html +10 -0
  153. netbox_dns/templates/netbox_dns/view/prefix.html +44 -0
  154. netbox_dns/templates/netbox_dns/view/related.html +33 -0
  155. netbox_dns/templates/netbox_dns/view.html +62 -18
  156. netbox_dns/templates/netbox_dns/zone/base.html +6 -3
  157. netbox_dns/templates/netbox_dns/zone/child.html +6 -5
  158. netbox_dns/templates/netbox_dns/zone/child_zone.html +18 -0
  159. netbox_dns/templates/netbox_dns/zone/delegation_record.html +18 -0
  160. netbox_dns/templates/netbox_dns/zone/managed_record.html +1 -1
  161. netbox_dns/templates/netbox_dns/zone/record.html +6 -5
  162. netbox_dns/templates/netbox_dns/zone/registration.html +43 -24
  163. netbox_dns/templates/netbox_dns/zone/rfc2317_child_zone.html +18 -0
  164. netbox_dns/templates/netbox_dns/zone.html +178 -119
  165. netbox_dns/templates/netbox_dns/zonetemplate/child.html +46 -0
  166. netbox_dns/templates/netbox_dns/zonetemplate.html +124 -0
  167. netbox_dns/templatetags/netbox_dns.py +10 -0
  168. netbox_dns/urls.py +50 -210
  169. netbox_dns/utilities/__init__.py +3 -0
  170. netbox_dns/{utilities.py → utilities/conversions.py} +55 -7
  171. netbox_dns/utilities/dns.py +11 -0
  172. netbox_dns/utilities/ipam_dnssync.py +370 -0
  173. netbox_dns/validators/__init__.py +4 -0
  174. netbox_dns/validators/dns_name.py +116 -0
  175. netbox_dns/validators/dns_value.py +147 -0
  176. netbox_dns/validators/dnssec.py +148 -0
  177. netbox_dns/validators/rfc2317.py +28 -0
  178. netbox_dns/views/__init__.py +5 -1
  179. netbox_dns/views/dnssec_key_template.py +78 -0
  180. netbox_dns/views/dnssec_policy.py +146 -0
  181. netbox_dns/views/nameserver.py +34 -15
  182. netbox_dns/views/record.py +156 -15
  183. netbox_dns/views/record_template.py +93 -0
  184. netbox_dns/views/registrar.py +32 -13
  185. netbox_dns/views/registration_contact.py +101 -0
  186. netbox_dns/views/view.py +58 -14
  187. netbox_dns/views/zone.py +130 -33
  188. netbox_dns/views/zone_template.py +82 -0
  189. netbox_plugin_dns-1.4.7.dist-info/METADATA +132 -0
  190. netbox_plugin_dns-1.4.7.dist-info/RECORD +201 -0
  191. {netbox_plugin_dns-0.21.4.dist-info → netbox_plugin_dns-1.4.7.dist-info}/WHEEL +2 -1
  192. {netbox_plugin_dns-0.21.4.dist-info → netbox_plugin_dns-1.4.7.dist-info/licenses}/LICENSE +2 -1
  193. netbox_plugin_dns-1.4.7.dist-info/top_level.txt +1 -0
  194. netbox_dns/filters/__init__.py +0 -6
  195. netbox_dns/filters/nameserver.py +0 -18
  196. netbox_dns/filters/record.py +0 -53
  197. netbox_dns/filters/view.py +0 -18
  198. netbox_dns/filters/zone.py +0 -112
  199. netbox_dns/forms/contact.py +0 -211
  200. netbox_dns/graphql/contact.py +0 -19
  201. netbox_dns/graphql/nameserver.py +0 -19
  202. netbox_dns/graphql/record.py +0 -19
  203. netbox_dns/graphql/registrar.py +0 -19
  204. netbox_dns/graphql/view.py +0 -19
  205. netbox_dns/graphql/zone.py +0 -19
  206. netbox_dns/management/commands/setup_coupling.py +0 -75
  207. netbox_dns/management/commands/update_soa.py +0 -22
  208. netbox_dns/middleware.py +0 -226
  209. netbox_dns/migrations/0001_initial.py +0 -115
  210. netbox_dns/migrations/0002_zone_default_ttl.py +0 -18
  211. netbox_dns/migrations/0003_soa_managed_records.py +0 -112
  212. netbox_dns/migrations/0004_create_ptr_for_a_aaaa_records.py +0 -80
  213. netbox_dns/migrations/0005_update_ns_records.py +0 -41
  214. netbox_dns/migrations/0006_zone_soa_serial_auto.py +0 -29
  215. netbox_dns/migrations/0007_alter_zone_soa_serial_auto.py +0 -17
  216. netbox_dns/migrations/0008_zone_status_names.py +0 -21
  217. netbox_dns/migrations/0009_netbox32.py +0 -71
  218. netbox_dns/migrations/0010_update_soa_records.py +0 -58
  219. netbox_dns/migrations/0011_add_view_model.py +0 -70
  220. netbox_dns/migrations/0012_adjust_zone_and_record.py +0 -17
  221. netbox_dns/migrations/0014_add_view_description.py +0 -16
  222. netbox_dns/migrations/0016_cleanup_ptr_records.py +0 -38
  223. netbox_dns/migrations/0017_alter_record_ttl.py +0 -17
  224. netbox_dns/migrations/0018_zone_arpa_network.py +0 -51
  225. netbox_dns/migrations/0019_update_ns_ttl.py +0 -19
  226. netbox_dns/templates/netbox_dns/contact.html +0 -71
  227. netbox_dns/templates/netbox_dns/related_dns_objects.html +0 -21
  228. netbox_dns/templatetags/view_helpers.py +0 -15
  229. netbox_dns/validators.py +0 -57
  230. netbox_dns/views/contact.py +0 -83
  231. netbox_plugin_dns-0.21.4.dist-info/METADATA +0 -101
  232. netbox_plugin_dns-0.21.4.dist-info/RECORD +0 -110
netbox_dns/forms/zone.py CHANGED
@@ -1,8 +1,10 @@
1
1
  from django import forms
2
+ from django.db import models, transaction
2
3
  from django.conf import settings
3
- from django.core.exceptions import ValidationError
4
4
  from django.core.validators import MinValueValidator, MaxValueValidator
5
- from django.urls import reverse_lazy
5
+ from django.core.exceptions import ValidationError
6
+ from django.contrib.postgres.forms import SimpleArrayField
7
+ from django.utils.translation import gettext_lazy as _
6
8
 
7
9
  from netbox.forms import (
8
10
  NetBoxModelBulkEditForm,
@@ -10,141 +12,277 @@ from netbox.forms import (
10
12
  NetBoxModelImportForm,
11
13
  NetBoxModelForm,
12
14
  )
15
+ from netbox.context import events_queue
13
16
  from utilities.forms.fields import (
14
17
  DynamicModelMultipleChoiceField,
15
18
  TagFilterField,
16
19
  CSVChoiceField,
17
20
  CSVModelChoiceField,
21
+ CSVModelMultipleChoiceField,
18
22
  DynamicModelChoiceField,
23
+ CommentField,
19
24
  )
20
- from utilities.forms.widgets import BulkEditNullBooleanSelect, APISelect
21
- from utilities.forms import add_blank_choice
22
- from tenancy.models import Tenant
25
+ from utilities.forms.widgets import BulkEditNullBooleanSelect, DatePicker, HTMXSelect
26
+ from utilities.forms.rendering import FieldSet
27
+ from utilities.forms import (
28
+ BOOLEAN_WITH_BLANK_CHOICES,
29
+ add_blank_choice,
30
+ get_field_value,
31
+ )
32
+ from tenancy.models import Tenant, TenantGroup
23
33
  from tenancy.forms import TenancyForm, TenancyFilterForm
24
34
 
25
35
  from netbox_dns.models import (
26
36
  View,
27
37
  Zone,
28
- ZoneStatusChoices,
29
38
  NameServer,
30
39
  Registrar,
31
- Contact,
40
+ RegistrationContact,
41
+ ZoneTemplate,
42
+ DNSSECPolicy,
32
43
  )
33
- from netbox_dns.utilities import name_to_unicode
44
+ from netbox_dns.choices import ZoneStatusChoices, ZoneEPPStatusChoices
45
+ from netbox_dns.utilities import name_to_unicode, network_to_reverse
46
+ from netbox_dns.fields import RFC2317NetworkFormField, TimePeriodField
47
+ from netbox_dns.validators import validate_ipv4, validate_prefix, validate_rfc2317
34
48
 
35
49
 
36
- class ZoneForm(TenancyForm, NetBoxModelForm):
37
- nameservers = DynamicModelMultipleChoiceField(
38
- queryset=NameServer.objects.all(),
39
- required=False,
40
- )
41
- default_ttl = forms.IntegerField(
42
- required=False,
43
- label="Default TTL",
44
- help_text="Default TTL for new records in this zone",
45
- validators=[MinValueValidator(1)],
46
- )
47
- soa_ttl = forms.IntegerField(
48
- required=True,
49
- label="SOA TTL",
50
- help_text="TTL for the SOA record of the zone",
51
- validators=[MinValueValidator(1)],
52
- )
53
- soa_rname = forms.CharField(
54
- required=True,
55
- label="SOA Responsible",
56
- help_text="Mailbox of the zone's administrator",
57
- )
58
- soa_serial_auto = forms.BooleanField(
59
- required=False,
60
- label="Generate SOA Serial",
61
- help_text="Automatically generate the SOA Serial",
62
- )
63
- soa_serial = forms.IntegerField(
64
- required=False,
65
- label="SOA Serial",
66
- help_text="Serial number of the current zone data version",
67
- validators=[MinValueValidator(1)],
68
- )
69
- soa_refresh = forms.IntegerField(
70
- required=True,
71
- label="SOA Refresh",
72
- help_text="Refresh interval for secondary name servers",
73
- validators=[MinValueValidator(1)],
74
- )
75
- soa_retry = forms.IntegerField(
76
- required=True,
77
- label="SOA Retry",
78
- help_text="Retry interval for secondary name servers",
79
- validators=[MinValueValidator(1)],
80
- )
81
- soa_expire = forms.IntegerField(
82
- required=True,
83
- label="SOA Expire",
84
- help_text="Expire time after which the zone is considered unavailable",
85
- validators=[MinValueValidator(1)],
86
- )
87
- soa_minimum = forms.IntegerField(
88
- required=True,
89
- label="SOA Minimum TTL",
90
- help_text="Minimum TTL for negative results, e.g. NXRRSET",
91
- validators=[MinValueValidator(1)],
92
- )
93
- fieldsets = (
94
- (
95
- "Zone",
96
- (
97
- "view",
98
- "name",
99
- "status",
100
- "nameservers",
101
- "default_ttl",
102
- "description",
103
- ),
104
- ),
105
- (
106
- "SOA",
107
- (
108
- "soa_ttl",
50
+ __all__ = (
51
+ "ZoneForm",
52
+ "ZoneFilterForm",
53
+ "ZoneImportForm",
54
+ "ZoneBulkEditForm",
55
+ )
56
+
57
+
58
+ class RollbackTransaction(Exception):
59
+ pass
60
+
61
+
62
+ class ZoneTemplateUpdateMixin:
63
+ def _check_soa_mname(self):
64
+ if (
65
+ self.cleaned_data.get("soa_mname") is None
66
+ and "soa_mname" in self.fields.keys()
67
+ ):
68
+ self.add_error(
109
69
  "soa_mname",
110
- "soa_rname",
111
- "soa_refresh",
112
- "soa_retry",
113
- "soa_expire",
114
- "soa_minimum",
115
- "soa_serial_auto",
116
- "soa_serial",
70
+ _("soa_mname not set and no template or default value defined"),
71
+ )
72
+
73
+ def clean(self, *args, **kwargs):
74
+ super().clean(*args, **kwargs)
75
+
76
+ if (template := self.cleaned_data.get("template")) is None:
77
+ self._check_soa_mname()
78
+ return
79
+
80
+ if not self.cleaned_data.get("nameservers") and template.nameservers.all():
81
+ self.cleaned_data["nameservers"] = template.nameservers.all()
82
+
83
+ if not self.cleaned_data.get("tags") and template.tags.all():
84
+ self.cleaned_data["tags"] = template.tags.all()
85
+
86
+ for field in template.template_fields:
87
+ if self.cleaned_data.get(field) in (None, "", []) and getattr(
88
+ template, field
89
+ ) not in (None, "", []):
90
+ self.cleaned_data[field] = getattr(template, field)
91
+
92
+ self._check_soa_mname()
93
+
94
+ if self.errors:
95
+ return
96
+
97
+ saved_events_queue = events_queue.get()
98
+
99
+ try:
100
+ with transaction.atomic():
101
+ if self.instance.id is not None:
102
+ zone = super().save(*args, **kwargs)
103
+ else:
104
+ zone_data = self.cleaned_data.copy()
105
+
106
+ custom_fields = {}
107
+ for key, value in zone_data.copy().items():
108
+ if key.startswith("cf_"):
109
+ if isinstance(value, models.Model):
110
+ custom_fields[key[3:]] = value.pk
111
+ elif isinstance(value, models.QuerySet):
112
+ custom_fields[key[3:]] = list(
113
+ value.values_list("pk", flat=True)
114
+ )
115
+ else:
116
+ custom_fields[key[3:]] = value
117
+ zone_data.pop(key)
118
+ if custom_fields:
119
+ zone_data["custom_field_data"] = custom_fields
120
+
121
+ zone_data.pop("template", None)
122
+ zone_data.pop("tenant_group", None)
123
+ zone_data.pop("_init_time", None)
124
+
125
+ nameservers = zone_data.pop("nameservers")
126
+ tags = zone_data.pop("tags")
127
+
128
+ zone = Zone.objects.create(**zone_data)
129
+
130
+ zone.nameservers.set(nameservers)
131
+ zone.tags.set(tags)
132
+
133
+ template.create_records(zone)
134
+ raise RollbackTransaction
135
+
136
+ except ValidationError as exc:
137
+ if hasattr(exc, "error_dict"):
138
+ for field_name in self.fields.keys():
139
+ exc.error_dict.pop(field_name, None)
140
+ errors = exc.error_dict.values()
141
+ else:
142
+ errors = exc.messages
143
+
144
+ for error in errors:
145
+ self.add_error("template", error)
146
+
147
+ except RollbackTransaction:
148
+ pass
149
+
150
+ events_queue.set(saved_events_queue)
151
+
152
+ return self.cleaned_data
153
+
154
+ def save(self, *args, **kwargs):
155
+ zone = super().save(*args, **kwargs)
156
+
157
+ if (template := self.cleaned_data.get("template")) is not None:
158
+ template.create_records(zone)
159
+
160
+ return zone
161
+
162
+
163
+ class ZoneForm(ZoneTemplateUpdateMixin, TenancyForm, NetBoxModelForm):
164
+ class Meta:
165
+ model = Zone
166
+
167
+ fields = (
168
+ "name",
169
+ "view",
170
+ "status",
171
+ "template",
172
+ "nameservers",
173
+ "default_ttl",
174
+ "description",
175
+ "soa_ttl",
176
+ "soa_mname",
177
+ "soa_rname",
178
+ "soa_serial_auto",
179
+ "soa_serial",
180
+ "soa_refresh",
181
+ "soa_retry",
182
+ "soa_expire",
183
+ "soa_minimum",
184
+ "rfc2317_prefix",
185
+ "rfc2317_parent_managed",
186
+ "dnssec_policy",
187
+ "parental_agents",
188
+ "registrar",
189
+ "registry_domain_id",
190
+ "expiration_date",
191
+ "domain_status",
192
+ "registrant",
193
+ "admin_c",
194
+ "tech_c",
195
+ "billing_c",
196
+ "tenant_group",
197
+ "tenant",
198
+ "comments",
199
+ "tags",
200
+ )
201
+
202
+ labels = {
203
+ "soa_serial_auto": _("Generate SOA Serial"),
204
+ "rfc2317_parent_managed": _("RFC2317 Parent Managed"),
205
+ }
206
+
207
+ help_texts = {
208
+ "soa_serial_auto": _("Automatically generate the SOA serial number"),
209
+ "rfc2317_parent_managed": _(
210
+ "IPv4 reverse zone for delegating the RFC2317 PTR records is managed in NetBox DNS"
117
211
  ),
212
+ }
213
+
214
+ widgets = {
215
+ "dnssec_policy": HTMXSelect(),
216
+ "registrar": HTMXSelect(),
217
+ "expiration_date": DatePicker,
218
+ }
219
+
220
+ fieldsets = (
221
+ FieldSet(
222
+ "view",
223
+ "name",
224
+ "template",
225
+ "status",
226
+ "nameservers",
227
+ "default_ttl",
228
+ "description",
229
+ name=_("Zone"),
118
230
  ),
119
- (
120
- "Domain Registration",
121
- (
122
- "registrar",
123
- "registry_domain_id",
124
- "registrant",
125
- "admin_c",
126
- "tech_c",
127
- "billing_c",
128
- ),
231
+ FieldSet(
232
+ "soa_ttl",
233
+ "soa_mname",
234
+ "soa_rname",
235
+ "soa_refresh",
236
+ "soa_retry",
237
+ "soa_expire",
238
+ "soa_minimum",
239
+ "soa_serial_auto",
240
+ "soa_serial",
241
+ name=_("SOA"),
242
+ ),
243
+ FieldSet(
244
+ "dnssec_policy",
245
+ "parental_agents",
246
+ name=_("DNSSEC"),
247
+ ),
248
+ FieldSet(
249
+ "rfc2317_prefix",
250
+ "rfc2317_parent_managed",
251
+ name=_("RFC2317"),
252
+ ),
253
+ FieldSet(
254
+ "registrar",
255
+ "registry_domain_id",
256
+ "expiration_date",
257
+ "domain_status",
258
+ "registrant",
259
+ "admin_c",
260
+ "tech_c",
261
+ "billing_c",
262
+ name=_("Domain Registration"),
263
+ ),
264
+ FieldSet(
265
+ "tenant_group",
266
+ "tenant",
267
+ name=_("Tenancy"),
268
+ ),
269
+ FieldSet(
270
+ "tags",
271
+ name=_("Tags"),
129
272
  ),
130
- ("Tags", ("tags",)),
131
- ("Tenancy", ("tenant_group", "tenant")),
132
273
  )
133
274
 
134
275
  def __init__(self, *args, **kwargs):
135
- """Override the __init__ method in order to provide the initial value for the default fields"""
136
276
  super().__init__(*args, **kwargs)
137
277
 
138
278
  initial_name = self.initial.get("name")
139
279
  if initial_name:
140
280
  self.initial["name"] = name_to_unicode(initial_name)
141
281
 
142
- defaults = settings.PLUGINS_CONFIG.get("netbox_dns")
143
-
144
- def _initialize(initial, setting):
145
- if initial.get(setting, None) in (None, ""):
146
- initial[setting] = defaults.get(f"zone_{setting}", None)
282
+ if self.initial.get("view") is None:
283
+ self.initial["view"] = View.get_default_view()
147
284
 
285
+ defaults = settings.PLUGINS_CONFIG.get("netbox_dns")
148
286
  for setting in (
149
287
  "default_ttl",
150
288
  "soa_ttl",
@@ -155,16 +293,14 @@ class ZoneForm(TenancyForm, NetBoxModelForm):
155
293
  "soa_expire",
156
294
  "soa_minimum",
157
295
  ):
158
- _initialize(self.initial, setting)
159
-
160
- if self.initial.get("soa_ttl", None) is None:
161
- self.initial["soa_ttl"] = self.initial.get("default_ttl", None)
296
+ if self.initial.get(setting) in (None, ""):
297
+ self.initial[setting] = defaults.get(f"zone_{setting}")
162
298
 
163
- if self.initial.get("soa_serial_auto"):
164
- self.initial["soa_serial"] = None
299
+ if self.initial.get("soa_ttl") is None:
300
+ self.initial["soa_ttl"] = self.initial.get("default_ttl")
165
301
 
166
- if self.initial.get("soa_mname", None) in (None, ""):
167
- default_soa_mname = defaults.get("zone_soa_mname", None)
302
+ if self.initial.get("soa_mname") is None:
303
+ default_soa_mname = defaults.get("zone_soa_mname")
168
304
  if default_soa_mname is not None:
169
305
  try:
170
306
  self.initial["soa_mname"] = NameServer.objects.get(
@@ -180,6 +316,106 @@ class ZoneForm(TenancyForm, NetBoxModelForm):
180
316
  name__in=default_nameservers
181
317
  )
182
318
 
319
+ if not get_field_value(self, "dnssec_policy"):
320
+ del self.fields["parental_agents"]
321
+
322
+ if not get_field_value(self, "registrar"):
323
+ del self.fields["registry_domain_id"]
324
+ del self.fields["expiration_date"]
325
+ del self.fields["domain_status"]
326
+
327
+ view = DynamicModelChoiceField(
328
+ queryset=View.objects.all(),
329
+ required=True,
330
+ label=_("View"),
331
+ )
332
+ name = forms.CharField(
333
+ required=True,
334
+ label=_("Name"),
335
+ )
336
+ template = DynamicModelChoiceField(
337
+ queryset=ZoneTemplate.objects.all(),
338
+ required=False,
339
+ label=_("Template"),
340
+ )
341
+ status = forms.ChoiceField(
342
+ choices=ZoneStatusChoices,
343
+ required=False,
344
+ label=_("Status"),
345
+ )
346
+ nameservers = DynamicModelMultipleChoiceField(
347
+ queryset=NameServer.objects.all(),
348
+ required=False,
349
+ label=_("Nameservers"),
350
+ )
351
+ default_ttl = TimePeriodField(
352
+ required=False,
353
+ help_text=_("Default TTL for new records in this zone"),
354
+ validators=[MinValueValidator(1)],
355
+ label=_("Default TTL"),
356
+ )
357
+ soa_ttl = TimePeriodField(
358
+ required=True,
359
+ help_text=_("TTL for the SOA record of the zone"),
360
+ validators=[MinValueValidator(1)],
361
+ label=_("SOA TTL"),
362
+ )
363
+ soa_mname = DynamicModelChoiceField(
364
+ queryset=NameServer.objects.all(),
365
+ help_text=_("Primary nameserver this zone"),
366
+ required=False,
367
+ label=_("SOA MName"),
368
+ )
369
+ soa_rname = forms.CharField(
370
+ required=False,
371
+ help_text=_("Mailbox of the zone's administrator"),
372
+ label=_("SOA RName"),
373
+ )
374
+ soa_refresh = TimePeriodField(
375
+ required=True,
376
+ help_text=_("Refresh interval for secondary nameservers"),
377
+ validators=[MinValueValidator(1)],
378
+ label=_("SOA Refresh"),
379
+ )
380
+ soa_retry = TimePeriodField(
381
+ required=True,
382
+ help_text=_("Retry interval for secondary nameservers"),
383
+ validators=[MinValueValidator(1)],
384
+ label=_("SOA Retry"),
385
+ )
386
+ soa_expire = TimePeriodField(
387
+ required=True,
388
+ validators=[MinValueValidator(1)],
389
+ help_text=_("Expire time after which the zone is considered unavailable"),
390
+ label=_("SOA Expire"),
391
+ )
392
+ soa_minimum = TimePeriodField(
393
+ required=True,
394
+ help_text=_("Minimum TTL for negative results, e.g. NXRRSET, NXDOMAIN"),
395
+ validators=[MinValueValidator(1)],
396
+ label=_("SOA Minimum TTL"),
397
+ )
398
+ soa_serial = forms.IntegerField(
399
+ required=False,
400
+ validators=[MinValueValidator(1)],
401
+ label=_("SOA Serial"),
402
+ )
403
+
404
+ parental_agents = SimpleArrayField(
405
+ required=False,
406
+ base_field=forms.GenericIPAddressField(),
407
+ label=_("Parental Agents"),
408
+ )
409
+
410
+ rfc2317_prefix = RFC2317NetworkFormField(
411
+ required=False,
412
+ validators=[validate_ipv4, validate_prefix, validate_rfc2317],
413
+ help_text=_("RFC2317 IPv4 prefix with a length of at least 25 bits"),
414
+ label=_("RFC2317 Prefix"),
415
+ )
416
+
417
+ comments = CommentField()
418
+
183
419
  def clean_default_ttl(self):
184
420
  return (
185
421
  self.cleaned_data["default_ttl"]
@@ -187,17 +423,206 @@ class ZoneForm(TenancyForm, NetBoxModelForm):
187
423
  else self.initial["default_ttl"]
188
424
  )
189
425
 
426
+ def clean_name(self):
427
+ name = self.cleaned_data["name"]
428
+ if reverse_name := network_to_reverse(name):
429
+ return reverse_name
430
+ else:
431
+ return name
432
+
433
+
434
+ class ZoneFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
435
+ model = Zone
436
+
437
+ fieldsets = (
438
+ FieldSet(
439
+ "q",
440
+ "filter_id",
441
+ "tag",
442
+ ),
443
+ FieldSet(
444
+ "view_id",
445
+ "status",
446
+ "name",
447
+ "nameserver_id",
448
+ "active",
449
+ "description",
450
+ name=_("Attributes"),
451
+ ),
452
+ FieldSet(
453
+ "soa_mname_id",
454
+ "soa_rname",
455
+ "soa_serial_auto",
456
+ name=_("SOA"),
457
+ ),
458
+ FieldSet(
459
+ "dnssec_policy_id",
460
+ "inline_signing",
461
+ "parental_agents",
462
+ name=_("DNSSEC"),
463
+ ),
464
+ FieldSet(
465
+ "rfc2317_prefix",
466
+ "rfc2317_parent_managed",
467
+ "rfc2317_parent_zone_id",
468
+ name=_("RFC2317"),
469
+ ),
470
+ FieldSet(
471
+ "registrar_id",
472
+ "registry_domain_id",
473
+ "expiration_date_before",
474
+ "expiration_date_after",
475
+ "domain_status",
476
+ "registrant_id",
477
+ "admin_c_id",
478
+ "tech_c_id",
479
+ "billing_c_id",
480
+ name=_("Registration"),
481
+ ),
482
+ FieldSet(
483
+ "tenant_group_id",
484
+ "tenant_id",
485
+ name=_("Tenancy"),
486
+ ),
487
+ )
488
+
489
+ view_id = DynamicModelMultipleChoiceField(
490
+ queryset=View.objects.all(),
491
+ required=False,
492
+ label=_("View"),
493
+ )
494
+ status = forms.MultipleChoiceField(
495
+ choices=ZoneStatusChoices,
496
+ required=False,
497
+ label=_("Status"),
498
+ )
499
+ name = forms.CharField(
500
+ required=False,
501
+ label=_("Name"),
502
+ )
503
+ nameserver_id = DynamicModelMultipleChoiceField(
504
+ queryset=NameServer.objects.all(),
505
+ required=False,
506
+ null_option=_("None"),
507
+ label=_("Nameservers"),
508
+ )
509
+ active = forms.NullBooleanField(
510
+ required=False,
511
+ widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
512
+ label=_("Active"),
513
+ )
514
+ description = forms.CharField(
515
+ required=False,
516
+ label=_("Description"),
517
+ )
518
+ soa_mname_id = DynamicModelMultipleChoiceField(
519
+ queryset=NameServer.objects.all(),
520
+ required=False,
521
+ label=_("MName"),
522
+ )
523
+ soa_rname = forms.CharField(
524
+ required=False,
525
+ label=_("RName"),
526
+ )
527
+ soa_serial_auto = forms.NullBooleanField(
528
+ required=False,
529
+ widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
530
+ label=_("Generate SOA Serial"),
531
+ )
532
+ rfc2317_prefix = RFC2317NetworkFormField(
533
+ required=False,
534
+ label=_("Prefix"),
535
+ )
536
+ rfc2317_parent_managed = forms.NullBooleanField(
537
+ required=False,
538
+ widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
539
+ label=_("Parent Managed"),
540
+ )
541
+ rfc2317_parent_zone_id = DynamicModelMultipleChoiceField(
542
+ queryset=Zone.objects.all(),
543
+ required=False,
544
+ null_option=_("None"),
545
+ label=_("Parent Zone"),
546
+ )
547
+ dnssec_policy_id = DynamicModelMultipleChoiceField(
548
+ queryset=DNSSECPolicy.objects.all(),
549
+ required=False,
550
+ null_option=_("None"),
551
+ label=_("DNSSEC Policy"),
552
+ )
553
+ inline_signing = forms.NullBooleanField(
554
+ required=False,
555
+ widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
556
+ label=_("DNSSEC Policy uses inline signing"),
557
+ )
558
+ parental_agents = forms.GenericIPAddressField(
559
+ required=False,
560
+ label=_("Parental Agents"),
561
+ )
562
+ registrar_id = DynamicModelMultipleChoiceField(
563
+ queryset=Registrar.objects.all(),
564
+ required=False,
565
+ null_option=_("None"),
566
+ label=_("Registrar"),
567
+ )
568
+ registry_domain_id = forms.CharField(
569
+ required=False,
570
+ label=_("Registry Domain ID"),
571
+ )
572
+ expiration_date_after = forms.DateField(
573
+ required=False,
574
+ label=_("Expiration Date after"),
575
+ widget=DatePicker,
576
+ )
577
+ expiration_date_before = forms.DateField(
578
+ required=False,
579
+ label=_("Expiration Date before"),
580
+ widget=DatePicker,
581
+ )
582
+ domain_status = forms.MultipleChoiceField(
583
+ choices=ZoneEPPStatusChoices,
584
+ required=False,
585
+ label=_("Domain Status"),
586
+ )
587
+ registrant_id = DynamicModelMultipleChoiceField(
588
+ queryset=RegistrationContact.objects.all(),
589
+ required=False,
590
+ null_option=_("None"),
591
+ label=_("Registrant"),
592
+ )
593
+ admin_c_id = DynamicModelMultipleChoiceField(
594
+ queryset=RegistrationContact.objects.all(),
595
+ required=False,
596
+ null_option=_("None"),
597
+ label=_("Administrative Contact"),
598
+ )
599
+ tech_c_id = DynamicModelMultipleChoiceField(
600
+ queryset=RegistrationContact.objects.all(),
601
+ required=False,
602
+ null_option=_("None"),
603
+ label=_("Technical Contact"),
604
+ )
605
+ billing_c_id = DynamicModelMultipleChoiceField(
606
+ queryset=RegistrationContact.objects.all(),
607
+ required=False,
608
+ null_option=_("None"),
609
+ label=_("Billing Contact"),
610
+ )
611
+ tag = TagFilterField(Zone)
612
+
613
+
614
+ class ZoneImportForm(ZoneTemplateUpdateMixin, NetBoxModelImportForm):
190
615
  class Meta:
191
616
  model = Zone
192
617
 
193
618
  fields = (
194
- "name",
195
619
  "view",
620
+ "name",
196
621
  "status",
622
+ "template",
197
623
  "nameservers",
198
624
  "default_ttl",
199
625
  "description",
200
- "tags",
201
626
  "soa_ttl",
202
627
  "soa_mname",
203
628
  "soa_rname",
@@ -207,442 +632,413 @@ class ZoneForm(TenancyForm, NetBoxModelForm):
207
632
  "soa_retry",
208
633
  "soa_expire",
209
634
  "soa_minimum",
635
+ "dnssec_policy",
636
+ "parental_agents",
637
+ "rfc2317_prefix",
638
+ "rfc2317_parent_managed",
210
639
  "registrar",
211
640
  "registry_domain_id",
641
+ "expiration_date",
642
+ "domain_status",
212
643
  "registrant",
213
644
  "admin_c",
214
645
  "tech_c",
215
646
  "billing_c",
216
647
  "tenant",
648
+ "comments",
649
+ "tags",
217
650
  )
218
- help_texts = {
219
- "view": "View the zone belongs to",
220
- "soa_mname": "Primary name server for the zone",
221
- }
222
651
 
223
-
224
- class ZoneFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
225
- model = Zone
226
- fieldsets = (
227
- (None, ("q", "filter_id", "tag")),
228
- ("Attributes", ("view_id", "status", "name", "nameservers")),
229
- ("Tenant", ("tenant_group_id", "tenant_id")),
230
- )
231
-
232
- view_id = DynamicModelMultipleChoiceField(
233
- queryset=View.objects.all(),
234
- required=False,
235
- label="View",
236
- )
237
- status = forms.ChoiceField(
238
- choices=add_blank_choice(ZoneStatusChoices),
239
- required=False,
240
- )
241
- name = forms.CharField(
242
- required=False,
243
- label="Name",
244
- )
245
- nameservers = DynamicModelMultipleChoiceField(
246
- queryset=NameServer.objects.all(),
247
- required=False,
248
- )
249
- tag = TagFilterField(Zone)
250
-
251
-
252
- class ZoneImportForm(NetBoxModelImportForm):
253
652
  view = CSVModelChoiceField(
254
653
  queryset=View.objects.all(),
255
654
  required=False,
256
655
  to_field_name="name",
257
- help_text="View the zone belongs to",
258
656
  error_messages={
259
- "invalid_choice": "View not found.",
657
+ "invalid_choice": _("View %(value)s not found"),
260
658
  },
659
+ label=_("View"),
261
660
  )
262
661
  status = CSVChoiceField(
263
662
  choices=ZoneStatusChoices,
264
663
  required=False,
265
- help_text="Zone status",
664
+ label=_("Status"),
266
665
  )
267
- default_ttl = forms.IntegerField(
666
+ nameservers = CSVModelMultipleChoiceField(
667
+ queryset=NameServer.objects.all(),
668
+ to_field_name="name",
268
669
  required=False,
269
- help_text="Default TTL",
670
+ label=_("Nameservers"),
270
671
  )
271
- soa_ttl = forms.IntegerField(
672
+ default_ttl = TimePeriodField(
272
673
  required=False,
273
- help_text="TTL for the SOA record of the zone",
674
+ label=_("Default TTL"),
675
+ )
676
+ soa_ttl = TimePeriodField(
677
+ required=False,
678
+ help_text=_("TTL for the SOA record of the zone"),
679
+ label=_("SOA TTL"),
274
680
  )
275
681
  soa_mname = CSVModelChoiceField(
276
682
  queryset=NameServer.objects.all(),
277
683
  required=False,
278
684
  to_field_name="name",
279
- help_text="Primary name server for the zone",
280
685
  error_messages={
281
- "invalid_choice": "Nameserver not found.",
686
+ "invalid_choice": _("Nameserver %(value)s not found"),
282
687
  },
688
+ help_text=_("Primary nameserver for the zone"),
689
+ label=_("SOA MName"),
283
690
  )
284
691
  soa_rname = forms.CharField(
285
692
  required=False,
286
- help_text="Mailbox of the zone's administrator",
693
+ help_text=_("Mailbox of the zone's administrator"),
694
+ label=_("SOA RName"),
287
695
  )
288
696
  soa_serial_auto = forms.BooleanField(
289
697
  required=False,
290
- help_text="Generate the SOA serial",
698
+ label=_("Generate SOA Serial"),
291
699
  )
292
700
  soa_serial = forms.IntegerField(
293
701
  required=False,
294
- help_text="Serial number of the current zone data version",
702
+ label=_("SOA Serial"),
295
703
  )
296
- soa_refresh = forms.IntegerField(
704
+ soa_refresh = TimePeriodField(
297
705
  required=False,
298
- help_text="Refresh interval for secondary name servers",
706
+ help_text=_("Refresh interval for secondary nameservers"),
707
+ label=_("SOA Refresh"),
299
708
  )
300
- soa_retry = forms.IntegerField(
709
+ soa_retry = TimePeriodField(
301
710
  required=False,
302
- help_text="Retry interval for secondary name servers",
711
+ help_text=_("Retry interval for secondary nameservers"),
712
+ label=_("SOA Retry"),
303
713
  )
304
- soa_expire = forms.IntegerField(
714
+ soa_expire = TimePeriodField(
305
715
  required=False,
306
- help_text="Expire time after which the zone is considered unavailable",
716
+ help_text=_("Expire time after which the zone is considered unavailable"),
717
+ label=_("SOA Expire"),
307
718
  )
308
- soa_minimum = forms.IntegerField(
719
+ soa_minimum = TimePeriodField(
309
720
  required=False,
310
- help_text="Minimum TTL for negative results, e.g. NXRRSET",
721
+ help_text=_("Minimum TTL for negative results, e.g. NXRRSET, NXDOMAIN"),
722
+ label=_("SOA Minimum TTL"),
723
+ )
724
+ rfc2317_prefix = RFC2317NetworkFormField(
725
+ required=False,
726
+ help_text=_("RFC2317 IPv4 prefix with a length of at least 25 bits"),
727
+ label=_("RFC2317 Prefix"),
728
+ )
729
+ rfc2317_parent_managed = forms.BooleanField(
730
+ required=False,
731
+ help_text=_(
732
+ "IPv4 reverse zone for delegating the RFC2317 PTR records is managed in NetBox DNS"
733
+ ),
734
+ label=_("RFC2317 Parent Managed"),
735
+ )
736
+ dnssec_policy = CSVModelChoiceField(
737
+ queryset=DNSSECPolicy.objects.all(),
738
+ required=False,
739
+ to_field_name="name",
740
+ error_messages={
741
+ "invalid_choice": _("DNSSEC policy %(value)s not found"),
742
+ },
743
+ label=_("DNSSEC Policy"),
311
744
  )
312
745
  registrar = CSVModelChoiceField(
313
746
  queryset=Registrar.objects.all(),
314
747
  required=False,
315
748
  to_field_name="name",
316
- help_text="Registrar the domain is registered with",
317
749
  error_messages={
318
- "invalid_choice": "Registrar not found.",
750
+ "invalid_choice": _("Registrar %(value)s not found"),
319
751
  },
752
+ label=_("Registrar"),
320
753
  )
321
754
  registry_domain_id = forms.CharField(
322
755
  required=False,
323
- help_text="Domain ID assigned by the registry",
756
+ label=_("Registry Domain ID"),
757
+ )
758
+ domain_status = CSVChoiceField(
759
+ choices=ZoneEPPStatusChoices,
760
+ required=False,
761
+ label=_("Domain Status"),
324
762
  )
325
763
  registrant = CSVModelChoiceField(
326
- queryset=Contact.objects.all(),
764
+ queryset=RegistrationContact.objects.all(),
327
765
  required=False,
328
766
  to_field_name="contact_id",
329
- help_text="Owner of the domain",
330
767
  error_messages={
331
- "invalid_choice": "Registrant contact ID not found",
768
+ "invalid_choice": _("Registrant contact ID %(value)s not found"),
332
769
  },
770
+ label=_("Registrant"),
333
771
  )
334
772
  admin_c = CSVModelChoiceField(
335
- queryset=Contact.objects.all(),
773
+ queryset=RegistrationContact.objects.all(),
336
774
  required=False,
337
775
  to_field_name="contact_id",
338
- help_text="Administrative contact for the domain",
339
776
  error_messages={
340
- "invalid_choice": "Administrative contact ID not found",
777
+ "invalid_choice": _("Administrative contact ID %(value)s not found"),
341
778
  },
779
+ label=_("Administrative Contact"),
342
780
  )
343
781
  tech_c = CSVModelChoiceField(
344
- queryset=Contact.objects.all(),
782
+ queryset=RegistrationContact.objects.all(),
345
783
  required=False,
346
784
  to_field_name="contact_id",
347
- help_text="Technical contact for the domain",
348
785
  error_messages={
349
- "invalid_choice": "Technical contact ID not found",
786
+ "invalid_choice": _("Technical contact ID %(value)s not found"),
350
787
  },
788
+ label=_("Technical Contact"),
351
789
  )
352
790
  billing_c = CSVModelChoiceField(
353
- queryset=Contact.objects.all(),
791
+ queryset=RegistrationContact.objects.all(),
354
792
  required=False,
355
793
  to_field_name="contact_id",
356
- help_text="Billing contact for the domain",
357
794
  error_messages={
358
- "invalid_choice": "Billing contact ID not found",
795
+ "invalid_choice": _("Billing contact ID not found"),
359
796
  },
797
+ label=_("Billing Contact"),
360
798
  )
361
799
  tenant = CSVModelChoiceField(
362
800
  queryset=Tenant.objects.all(),
363
801
  required=False,
364
802
  to_field_name="name",
365
- help_text="Assigned tenant",
803
+ label=_("Tenant"),
804
+ )
805
+ template = CSVModelChoiceField(
806
+ queryset=ZoneTemplate.objects.all(),
807
+ required=False,
808
+ to_field_name="name",
809
+ label=_("Template"),
366
810
  )
367
811
 
368
- def _get_default_value(self, field):
369
- _default_values = settings.PLUGINS_CONFIG.get("netbox_dns", {})
370
- if _default_values.get("zone_soa_ttl", None) is None:
371
- _default_values["zone_soa_ttl"] = _default_values.get(
372
- "zone_default_ttl", None
373
- )
374
-
375
- return _default_values.get(f"zone_{field}", None)
376
-
377
- def _clean_field_with_defaults(self, field):
378
- if self.cleaned_data[field]:
379
- value = self.cleaned_data[field]
380
- else:
381
- value = self._get_default_value(field)
382
-
383
- if value is None:
384
- raise ValidationError(f"{field} not set and no default value available")
385
-
386
- return value
387
-
388
- def clean_default_ttl(self):
389
- return self._clean_field_with_defaults("default_ttl")
390
-
391
- def clean_soa_ttl(self):
392
- return self._clean_field_with_defaults("soa_ttl")
393
-
394
- def clean_soa_mname(self):
395
- soa_mname = self._clean_field_with_defaults("soa_mname")
396
- if isinstance(soa_mname, str):
397
- try:
398
- soa_mname = NameServer.objects.get(name=soa_mname)
399
- except NameServer.DoesNotExist:
400
- raise ValidationError(f"Default name server {soa_mname} does not exist")
401
-
402
- return soa_mname
403
-
404
- def clean_soa_rname(self):
405
- return self._clean_field_with_defaults("soa_rname")
406
-
407
- def clean_soa_serial_auto(self):
408
- try:
409
- return self._clean_field_with_defaults("soa_serial_auto")
410
- except ValidationError:
411
- if self.cleaned_data["soa_serial"] or self._get_default_value("soa_serial"):
412
- return None
413
-
414
- raise
415
-
416
- def clean_soa_serial(self):
417
- try:
418
- return self._clean_field_with_defaults("soa_serial")
419
- except ValidationError:
420
- if self.cleaned_data["soa_serial_auto"] or self._get_default_value(
421
- "soa_serial_auto"
422
- ):
423
- return None
812
+ def clean_view(self):
813
+ view = self.cleaned_data.get("view")
814
+ if view is None:
815
+ return View.get_default_view()
424
816
 
425
- raise
817
+ return view
426
818
 
427
- def clean_soa_refresh(self):
428
- return self._clean_field_with_defaults("soa_refresh")
819
+ def clean_nameservers(self):
820
+ nameservers = self.cleaned_data.get("nameservers")
821
+ zone_defaults = settings.PLUGINS_CONFIG.get("netbox_dns")
429
822
 
430
- def clean_soa_retry(self):
431
- return self._clean_field_with_defaults("soa_retry")
823
+ if (
824
+ self.data.get("nameservers") is None
825
+ and zone_defaults.get("zone_nameservers") is not None
826
+ ):
827
+ nameservers = NameServer.objects.filter(
828
+ name__in=zone_defaults.get("zone_nameservers")
829
+ )
432
830
 
433
- def clean_soa_expire(self):
434
- return self._clean_field_with_defaults("soa_expire")
831
+ return nameservers
435
832
 
436
- def clean_soa_minimum(self):
437
- return self._clean_field_with_defaults("soa_minimum")
438
833
 
439
- class Meta:
440
- model = Zone
834
+ class ZoneBulkEditForm(NetBoxModelBulkEditForm):
835
+ model = Zone
441
836
 
442
- fields = (
837
+ fieldsets = (
838
+ FieldSet(
443
839
  "view",
444
- "name",
445
840
  "status",
841
+ "nameservers",
446
842
  "default_ttl",
447
843
  "description",
844
+ name=_("Attributes"),
845
+ ),
846
+ FieldSet(
448
847
  "soa_ttl",
449
848
  "soa_mname",
450
849
  "soa_rname",
451
- "soa_serial_auto",
452
- "soa_serial",
453
850
  "soa_refresh",
454
851
  "soa_retry",
455
852
  "soa_expire",
456
853
  "soa_minimum",
854
+ "soa_serial_auto",
855
+ "soa_serial",
856
+ name=_("SOA"),
857
+ ),
858
+ FieldSet(
859
+ "dnssec_policy",
860
+ "parental_agents",
861
+ name=_("DNSSEC"),
862
+ ),
863
+ FieldSet(
864
+ "rfc2317_prefix",
865
+ "rfc2317_parent_managed",
866
+ name=_("RFC2317"),
867
+ ),
868
+ FieldSet(
457
869
  "registrar",
458
870
  "registry_domain_id",
871
+ "expiration_date",
872
+ "domain_status",
459
873
  "registrant",
460
874
  "admin_c",
461
875
  "tech_c",
462
876
  "billing_c",
877
+ name=_("Domain Registration"),
878
+ ),
879
+ FieldSet(
880
+ "tenant_group",
463
881
  "tenant",
464
- )
882
+ name=_("Tenancy"),
883
+ ),
884
+ )
465
885
 
886
+ nullable_fields = (
887
+ "description",
888
+ "nameservers",
889
+ "rfc2317_prefix",
890
+ "registrar",
891
+ "expiration_date",
892
+ "domain_status",
893
+ "registry_domain_id",
894
+ "registrant",
895
+ "admin_c",
896
+ "tech_c",
897
+ "billing_c",
898
+ "tenant",
899
+ "comments",
900
+ )
466
901
 
467
- class ZoneBulkEditForm(NetBoxModelBulkEditForm):
468
902
  view = DynamicModelChoiceField(
469
903
  queryset=View.objects.all(),
470
904
  required=False,
471
- label="View",
472
- widget=APISelect(
473
- attrs={"data-url": reverse_lazy("plugins-api:netbox_dns-api:view-list")}
474
- ),
905
+ label=_("View"),
475
906
  )
476
907
  status = forms.ChoiceField(
477
908
  choices=add_blank_choice(ZoneStatusChoices),
478
909
  required=False,
910
+ label=_("Status"),
479
911
  )
480
912
  nameservers = DynamicModelMultipleChoiceField(
481
913
  queryset=NameServer.objects.all(),
482
914
  required=False,
915
+ label=_("Nameservers"),
483
916
  )
484
- default_ttl = forms.IntegerField(
917
+ default_ttl = TimePeriodField(
485
918
  required=False,
486
- label="Default TTL",
487
919
  validators=[MinValueValidator(1)],
920
+ label=_("Default TTL"),
921
+ )
922
+ description = forms.CharField(
923
+ max_length=200,
924
+ required=False,
925
+ label=_("Description"),
488
926
  )
489
- description = forms.CharField(max_length=200, required=False)
490
- soa_ttl = forms.IntegerField(
927
+ soa_ttl = TimePeriodField(
491
928
  required=False,
492
- label="SOA TTL",
493
929
  validators=[MinValueValidator(1)],
930
+ label=_("SOA TTL"),
494
931
  )
495
932
  soa_mname = DynamicModelChoiceField(
496
933
  queryset=NameServer.objects.all(),
497
934
  required=False,
498
- label="SOA Primary Nameserver",
499
- widget=APISelect(
500
- attrs={
501
- "data-url": reverse_lazy("plugins-api:netbox_dns-api:nameserver-list")
502
- }
503
- ),
935
+ label=_("SOA MName"),
504
936
  )
505
937
  soa_rname = forms.CharField(
506
938
  required=False,
507
- label="SOA Responsible",
939
+ label=_("SOA RName"),
508
940
  )
509
941
  soa_serial_auto = forms.NullBooleanField(
510
942
  required=False,
511
943
  widget=BulkEditNullBooleanSelect(),
512
- label="Generate SOA Serial",
944
+ label=_("Generate SOA Serial"),
513
945
  )
514
946
  soa_serial = forms.IntegerField(
515
947
  required=False,
516
- label="SOA Serial",
517
948
  validators=[MinValueValidator(1), MaxValueValidator(4294967295)],
949
+ label=_("SOA Serial"),
518
950
  )
519
- soa_refresh = forms.IntegerField(
951
+ soa_refresh = TimePeriodField(
520
952
  required=False,
521
- label="SOA Refresh",
522
953
  validators=[MinValueValidator(1)],
954
+ label=_("SOA Refresh"),
523
955
  )
524
- soa_retry = forms.IntegerField(
956
+ soa_retry = TimePeriodField(
525
957
  required=False,
526
- label="SOA Retry",
527
958
  validators=[MinValueValidator(1)],
959
+ label=_("SOA Retry"),
528
960
  )
529
- soa_expire = forms.IntegerField(
961
+ soa_expire = TimePeriodField(
530
962
  required=False,
531
- label="SOA Expire",
532
963
  validators=[MinValueValidator(1)],
964
+ label=_("SOA Expire"),
533
965
  )
534
- soa_minimum = forms.IntegerField(
966
+ soa_minimum = TimePeriodField(
535
967
  required=False,
536
- label="SOA Minimum TTL",
537
968
  validators=[MinValueValidator(1)],
969
+ label=_("SOA Minimum TTL"),
970
+ )
971
+ rfc2317_prefix = RFC2317NetworkFormField(
972
+ required=False,
973
+ validators=[validate_ipv4, validate_prefix, validate_rfc2317],
974
+ help_text=_("RFC2317 IPv4 prefix with a length of at least 25 bits"),
975
+ label=_("RFC2317 Prefix"),
976
+ )
977
+ rfc2317_parent_managed = forms.NullBooleanField(
978
+ required=False,
979
+ widget=BulkEditNullBooleanSelect(),
980
+ help_text=_(
981
+ "IPv4 reverse zone for delegating the RFC2317 PTR records is managed in NetBox DNS"
982
+ ),
983
+ label=_("RFC2317 Parent Managed"),
984
+ )
985
+ dnssec_policy = DynamicModelChoiceField(
986
+ queryset=DNSSECPolicy.objects.all(),
987
+ required=False,
988
+ label=_("DNSSEC Policy"),
989
+ )
990
+ parental_agents = SimpleArrayField(
991
+ required=False,
992
+ base_field=forms.GenericIPAddressField(),
993
+ label=_("Parental Agents"),
538
994
  )
539
995
  registrar = DynamicModelChoiceField(
540
996
  queryset=Registrar.objects.all(),
541
997
  required=False,
542
- widget=APISelect(
543
- attrs={
544
- "data-url": reverse_lazy("plugins-api:netbox_dns-api:registrar-list")
545
- }
546
- ),
998
+ label=_("Registrar"),
547
999
  )
548
1000
  registry_domain_id = forms.CharField(
549
1001
  required=False,
550
- label="Registry Domain ID",
1002
+ label=_("Registry Domain ID"),
1003
+ )
1004
+ expiration_date = forms.DateField(
1005
+ required=False,
1006
+ label=_("Expiration Date"),
1007
+ widget=DatePicker,
1008
+ )
1009
+ domain_status = forms.ChoiceField(
1010
+ choices=add_blank_choice(ZoneEPPStatusChoices),
1011
+ required=False,
1012
+ label=_("Domain Status"),
551
1013
  )
552
1014
  registrant = DynamicModelChoiceField(
553
- queryset=Contact.objects.all(),
1015
+ queryset=RegistrationContact.objects.all(),
554
1016
  required=False,
555
- widget=APISelect(
556
- attrs={"data-url": reverse_lazy("plugins-api:netbox_dns-api:contact-list")}
557
- ),
1017
+ label=_("Registrant"),
558
1018
  )
559
1019
  admin_c = DynamicModelChoiceField(
560
- queryset=Contact.objects.all(),
1020
+ queryset=RegistrationContact.objects.all(),
561
1021
  required=False,
562
- label="Administrative Contact",
563
- widget=APISelect(
564
- attrs={"data-url": reverse_lazy("plugins-api:netbox_dns-api:contact-list")}
565
- ),
1022
+ label=_("Administrative Contact"),
566
1023
  )
567
1024
  tech_c = DynamicModelChoiceField(
568
- queryset=Contact.objects.all(),
1025
+ queryset=RegistrationContact.objects.all(),
569
1026
  required=False,
570
- label="Technical Contact",
571
- widget=APISelect(
572
- attrs={"data-url": reverse_lazy("plugins-api:netbox_dns-api:contact-list")}
573
- ),
1027
+ label=_("Technical Contact"),
574
1028
  )
575
1029
  billing_c = DynamicModelChoiceField(
576
- queryset=Contact.objects.all(),
1030
+ queryset=RegistrationContact.objects.all(),
577
1031
  required=False,
578
- label="Billing Contact",
579
- widget=APISelect(
580
- attrs={"data-url": reverse_lazy("plugins-api:netbox_dns-api:contact-list")}
581
- ),
1032
+ label=_("Billing Contact"),
582
1033
  )
583
- tenant = CSVModelChoiceField(
584
- queryset=Tenant.objects.all(),
1034
+ tenant_group = DynamicModelChoiceField(
1035
+ queryset=TenantGroup.objects.all(),
585
1036
  required=False,
586
- to_field_name="name",
587
- help_text="Assigned tenant",
588
- )
589
- tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
590
-
591
- model = Zone
592
-
593
- fieldsets = (
594
- (
595
- None,
596
- (
597
- "view",
598
- "status",
599
- "nameservers",
600
- "default_ttl",
601
- "description",
602
- "tenant",
603
- ),
604
- ),
605
- (
606
- "SOA",
607
- (
608
- "soa_ttl",
609
- "soa_mname",
610
- "soa_rname",
611
- "soa_serial_auto",
612
- "soa_serial",
613
- "soa_refresh",
614
- "soa_retry",
615
- "soa_expire",
616
- "soa_minimum",
617
- ),
618
- ),
619
- (
620
- "Domain Registration",
621
- (
622
- "registrar",
623
- "registry_domain_id",
624
- "registrant",
625
- "admin_c",
626
- "tech_c",
627
- "billing_c",
628
- ),
629
- ),
1037
+ label=_("Tenant Group"),
630
1038
  )
631
- nullable_fields = (
632
- "view",
633
- "description",
634
- "registrar",
635
- "registry_domain_id",
636
- "registrant",
637
- "admin_c",
638
- "tech_c",
639
- "billing_c",
1039
+ tenant = DynamicModelChoiceField(
1040
+ queryset=Tenant.objects.all(),
1041
+ required=False,
1042
+ label=_("Tenant"),
640
1043
  )
641
-
642
- def clean(self):
643
- """
644
- If soa_serial_auto is True, set soa_serial to None.
645
- """
646
- cleaned_data = super().clean()
647
- if cleaned_data.get("soa_serial_auto"):
648
- cleaned_data["soa_serial"] = None
1044
+ comments = CommentField()