netbox-plugin-dns 1.1.2__py3-none-any.whl → 1.1.4__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.
- netbox_dns/__init__.py +14 -6
- netbox_dns/api/nested_serializers.py +3 -2
- netbox_dns/api/serializers_/nameserver.py +2 -1
- netbox_dns/api/serializers_/record.py +5 -4
- netbox_dns/api/serializers_/record_template.py +2 -1
- netbox_dns/api/serializers_/view.py +2 -1
- netbox_dns/api/serializers_/zone.py +12 -11
- netbox_dns/api/serializers_/zone_template.py +8 -7
- netbox_dns/api/views.py +9 -4
- netbox_dns/choices/record.py +4 -2
- netbox_dns/choices/zone.py +8 -4
- netbox_dns/fields/address.py +5 -22
- netbox_dns/fields/network.py +2 -1
- netbox_dns/fields/rfc2317.py +7 -3
- netbox_dns/filtersets/nameserver.py +3 -2
- netbox_dns/filtersets/record.py +14 -9
- netbox_dns/filtersets/record_template.py +3 -2
- netbox_dns/filtersets/view.py +3 -2
- netbox_dns/filtersets/zone.py +24 -22
- netbox_dns/filtersets/zone_template.py +15 -14
- netbox_dns/forms/nameserver.py +41 -17
- netbox_dns/forms/record.py +61 -32
- netbox_dns/forms/record_template.py +49 -28
- netbox_dns/forms/registrar.py +21 -17
- netbox_dns/forms/registration_contact.py +37 -25
- netbox_dns/forms/view.py +49 -27
- netbox_dns/forms/zone.py +173 -120
- netbox_dns/forms/zone_template.py +53 -43
- netbox_dns/locale/de/LC_MESSAGES/django.mo +0 -0
- netbox_dns/locale/en/LC_MESSAGES/django.mo +0 -0
- netbox_dns/management/commands/rebuild_dnssync.py +14 -1
- netbox_dns/models/nameserver.py +6 -2
- netbox_dns/models/record.py +74 -40
- netbox_dns/models/record_template.py +17 -9
- netbox_dns/models/registrar.py +11 -7
- netbox_dns/models/registration_contact.py +23 -11
- netbox_dns/models/view.py +15 -6
- netbox_dns/models/zone.py +83 -50
- netbox_dns/models/zone_template.py +12 -10
- netbox_dns/navigation.py +30 -28
- netbox_dns/signals/ipam_dnssync.py +21 -14
- netbox_dns/tables/ipam_dnssync.py +2 -1
- netbox_dns/tables/nameserver.py +2 -0
- netbox_dns/tables/record.py +21 -11
- netbox_dns/tables/record_template.py +12 -5
- netbox_dns/tables/registrar.py +2 -0
- netbox_dns/tables/registration_contact.py +2 -0
- netbox_dns/tables/view.py +3 -1
- netbox_dns/tables/zone.py +15 -2
- netbox_dns/tables/zone_template.py +7 -0
- netbox_dns/templates/netbox_dns/nameserver.html +6 -5
- netbox_dns/templates/netbox_dns/record/managed.html +2 -1
- netbox_dns/templates/netbox_dns/record/related.html +26 -14
- netbox_dns/templates/netbox_dns/record.html +39 -20
- netbox_dns/templates/netbox_dns/recordtemplate.html +27 -15
- netbox_dns/templates/netbox_dns/registrar.html +11 -10
- netbox_dns/templates/netbox_dns/registrationcontact.html +16 -15
- netbox_dns/templates/netbox_dns/view/button.html +2 -1
- netbox_dns/templates/netbox_dns/view/prefix.html +7 -4
- netbox_dns/templates/netbox_dns/view/related.html +26 -10
- netbox_dns/templates/netbox_dns/view.html +11 -14
- netbox_dns/templates/netbox_dns/zone/base.html +2 -1
- netbox_dns/templates/netbox_dns/zone/child.html +3 -2
- netbox_dns/templates/netbox_dns/zone/record.html +3 -2
- netbox_dns/templates/netbox_dns/zone/registration.html +8 -7
- netbox_dns/templates/netbox_dns/zone.html +28 -30
- netbox_dns/templates/netbox_dns/zonetemplate.html +27 -17
- netbox_dns/utilities/ipam_dnssync.py +15 -4
- netbox_dns/validators/dns_name.py +11 -4
- netbox_dns/validators/dns_value.py +55 -9
- netbox_dns/validators/rfc2317.py +6 -3
- netbox_dns/views/nameserver.py +4 -2
- netbox_dns/views/record_template.py +4 -3
- netbox_dns/views/registrar.py +3 -1
- netbox_dns/views/registration_contact.py +2 -1
- netbox_dns/views/view.py +2 -1
- netbox_dns/views/zone.py +6 -4
- netbox_dns/views/zone_template.py +8 -7
- {netbox_plugin_dns-1.1.2.dist-info → netbox_plugin_dns-1.1.4.dist-info}/METADATA +2 -2
- netbox_plugin_dns-1.1.4.dist-info/RECORD +150 -0
- netbox_plugin_dns-1.1.2.dist-info/RECORD +0 -148
- {netbox_plugin_dns-1.1.2.dist-info → netbox_plugin_dns-1.1.4.dist-info}/LICENSE +0 -0
- {netbox_plugin_dns-1.1.2.dist-info → netbox_plugin_dns-1.1.4.dist-info}/WHEEL +0 -0
- {netbox_plugin_dns-1.1.2.dist-info → netbox_plugin_dns-1.1.4.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from django import forms
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
3
|
|
|
3
4
|
from netbox.forms import (
|
|
4
5
|
NetBoxModelBulkEditForm,
|
|
@@ -14,7 +15,7 @@ from utilities.forms.fields import (
|
|
|
14
15
|
DynamicModelChoiceField,
|
|
15
16
|
)
|
|
16
17
|
from utilities.forms.rendering import FieldSet
|
|
17
|
-
from tenancy.models import Tenant
|
|
18
|
+
from tenancy.models import Tenant, TenantGroup
|
|
18
19
|
from tenancy.forms import TenancyForm, TenancyFilterForm
|
|
19
20
|
|
|
20
21
|
from netbox_dns.models import (
|
|
@@ -45,18 +46,18 @@ class ZoneTemplateForm(TenancyForm, NetBoxModelForm):
|
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
fieldsets = (
|
|
48
|
-
FieldSet("name", "description", "nameservers", name="Zone Template"),
|
|
49
|
-
FieldSet("record_templates", name="Record Templates"),
|
|
49
|
+
FieldSet("name", "description", "nameservers", name=_("Zone Template")),
|
|
50
|
+
FieldSet("record_templates", name=_("Record Templates")),
|
|
50
51
|
FieldSet(
|
|
51
52
|
"registrar",
|
|
52
53
|
"registrant",
|
|
53
54
|
"admin_c",
|
|
54
55
|
"tech_c",
|
|
55
56
|
"billing_c",
|
|
56
|
-
name="Domain Registration",
|
|
57
|
+
name=_("Domain Registration"),
|
|
57
58
|
),
|
|
58
|
-
FieldSet("
|
|
59
|
-
FieldSet("
|
|
59
|
+
FieldSet("tenant_group", "tenant", name=_("Tenancy")),
|
|
60
|
+
FieldSet("tags", name=_("Tags")),
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
class Meta:
|
|
@@ -67,13 +68,14 @@ class ZoneTemplateForm(TenancyForm, NetBoxModelForm):
|
|
|
67
68
|
"nameservers",
|
|
68
69
|
"record_templates",
|
|
69
70
|
"description",
|
|
70
|
-
"tags",
|
|
71
71
|
"registrar",
|
|
72
72
|
"registrant",
|
|
73
73
|
"admin_c",
|
|
74
74
|
"tech_c",
|
|
75
75
|
"billing_c",
|
|
76
|
+
"tenant_group",
|
|
76
77
|
"tenant",
|
|
78
|
+
"tags",
|
|
77
79
|
)
|
|
78
80
|
|
|
79
81
|
|
|
@@ -81,32 +83,32 @@ class ZoneTemplateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|
|
81
83
|
model = ZoneTemplate
|
|
82
84
|
fieldsets = (
|
|
83
85
|
FieldSet("q", "filter_id", "tag"),
|
|
84
|
-
FieldSet("name", "nameserver_id", "description", name="Attributes"),
|
|
85
|
-
FieldSet("record_template_id", name="Record Templates"),
|
|
86
|
+
FieldSet("name", "nameserver_id", "description", name=_("Attributes")),
|
|
87
|
+
FieldSet("record_template_id", name=_("Record Templates")),
|
|
86
88
|
FieldSet(
|
|
87
89
|
"registrar_id",
|
|
88
90
|
"registrant_id",
|
|
89
91
|
"admin_c_id",
|
|
90
92
|
"tech_c_id",
|
|
91
93
|
"billing_c_id",
|
|
92
|
-
name="Registration",
|
|
94
|
+
name=_("Registration"),
|
|
93
95
|
),
|
|
94
|
-
FieldSet("tenant_group_id", "tenant_id", name="Tenancy"),
|
|
96
|
+
FieldSet("tenant_group_id", "tenant_id", name=_("Tenancy")),
|
|
95
97
|
)
|
|
96
98
|
|
|
97
99
|
name = forms.CharField(
|
|
98
100
|
required=False,
|
|
99
|
-
label="Template
|
|
101
|
+
label=_("Template Name"),
|
|
100
102
|
)
|
|
101
103
|
nameserver_id = DynamicModelMultipleChoiceField(
|
|
102
104
|
queryset=NameServer.objects.all(),
|
|
103
105
|
required=False,
|
|
104
|
-
label="Nameservers",
|
|
106
|
+
label=_("Nameservers"),
|
|
105
107
|
)
|
|
106
108
|
record_template_id = DynamicModelMultipleChoiceField(
|
|
107
109
|
queryset=RecordTemplate.objects.all(),
|
|
108
110
|
required=False,
|
|
109
|
-
label="Record
|
|
111
|
+
label=_("Record Templates"),
|
|
110
112
|
)
|
|
111
113
|
description = forms.CharField(
|
|
112
114
|
required=False,
|
|
@@ -114,27 +116,27 @@ class ZoneTemplateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|
|
114
116
|
registrar_id = DynamicModelMultipleChoiceField(
|
|
115
117
|
queryset=Registrar.objects.all(),
|
|
116
118
|
required=False,
|
|
117
|
-
label="Registrar",
|
|
119
|
+
label=_("Registrar"),
|
|
118
120
|
)
|
|
119
121
|
registrant_id = DynamicModelMultipleChoiceField(
|
|
120
122
|
queryset=RegistrationContact.objects.all(),
|
|
121
123
|
required=False,
|
|
122
|
-
label="Registrant",
|
|
124
|
+
label=_("Registrant"),
|
|
123
125
|
)
|
|
124
126
|
admin_c_id = DynamicModelMultipleChoiceField(
|
|
125
127
|
queryset=RegistrationContact.objects.all(),
|
|
126
128
|
required=False,
|
|
127
|
-
label="
|
|
129
|
+
label=_("Administrative Contact"),
|
|
128
130
|
)
|
|
129
131
|
tech_c_id = DynamicModelMultipleChoiceField(
|
|
130
132
|
queryset=RegistrationContact.objects.all(),
|
|
131
133
|
required=False,
|
|
132
|
-
label="
|
|
134
|
+
label=_("Technical Contact"),
|
|
133
135
|
)
|
|
134
136
|
billing_c_id = DynamicModelMultipleChoiceField(
|
|
135
137
|
queryset=RegistrationContact.objects.all(),
|
|
136
138
|
required=False,
|
|
137
|
-
label="Billing
|
|
139
|
+
label=_("Billing Contact"),
|
|
138
140
|
)
|
|
139
141
|
tag = TagFilterField(ZoneTemplate)
|
|
140
142
|
|
|
@@ -144,64 +146,64 @@ class ZoneTemplateImportForm(NetBoxModelImportForm):
|
|
|
144
146
|
queryset=NameServer.objects.all(),
|
|
145
147
|
to_field_name="name",
|
|
146
148
|
required=False,
|
|
147
|
-
|
|
149
|
+
label=_("Nameservers"),
|
|
148
150
|
)
|
|
149
151
|
record_templates = CSVModelMultipleChoiceField(
|
|
150
152
|
queryset=RecordTemplate.objects.all(),
|
|
151
153
|
to_field_name="name",
|
|
152
154
|
required=False,
|
|
153
|
-
|
|
155
|
+
label=_("Record Remplates"),
|
|
154
156
|
)
|
|
155
157
|
registrar = CSVModelChoiceField(
|
|
156
158
|
queryset=Registrar.objects.all(),
|
|
157
159
|
required=False,
|
|
158
160
|
to_field_name="name",
|
|
159
|
-
help_text="Registrar the domain is registered with",
|
|
160
161
|
error_messages={
|
|
161
|
-
"invalid_choice": "Registrar not found.",
|
|
162
|
+
"invalid_choice": _("Registrar not found."),
|
|
162
163
|
},
|
|
164
|
+
label=_("Registrar"),
|
|
163
165
|
)
|
|
164
166
|
registrant = CSVModelChoiceField(
|
|
165
167
|
queryset=RegistrationContact.objects.all(),
|
|
166
168
|
required=False,
|
|
167
169
|
to_field_name="contact_id",
|
|
168
|
-
help_text="Owner of the domain",
|
|
169
170
|
error_messages={
|
|
170
|
-
"invalid_choice": "Registrant contact ID not found",
|
|
171
|
+
"invalid_choice": _("Registrant contact ID not found"),
|
|
171
172
|
},
|
|
173
|
+
label=_("Registrant"),
|
|
172
174
|
)
|
|
173
175
|
admin_c = CSVModelChoiceField(
|
|
174
176
|
queryset=RegistrationContact.objects.all(),
|
|
175
177
|
required=False,
|
|
176
178
|
to_field_name="contact_id",
|
|
177
|
-
help_text="Administrative contact for the domain",
|
|
178
179
|
error_messages={
|
|
179
|
-
"invalid_choice": "Administrative contact ID not found",
|
|
180
|
+
"invalid_choice": _("Administrative contact ID not found"),
|
|
180
181
|
},
|
|
182
|
+
label=_("Administrative Contact"),
|
|
181
183
|
)
|
|
182
184
|
tech_c = CSVModelChoiceField(
|
|
183
185
|
queryset=RegistrationContact.objects.all(),
|
|
184
186
|
required=False,
|
|
185
187
|
to_field_name="contact_id",
|
|
186
|
-
help_text="Technical contact for the domain",
|
|
187
188
|
error_messages={
|
|
188
|
-
"invalid_choice": "Technical contact ID not found",
|
|
189
|
+
"invalid_choice": _("Technical contact ID not found"),
|
|
189
190
|
},
|
|
191
|
+
label=_("Technical Contact"),
|
|
190
192
|
)
|
|
191
193
|
billing_c = CSVModelChoiceField(
|
|
192
194
|
queryset=RegistrationContact.objects.all(),
|
|
193
195
|
required=False,
|
|
194
196
|
to_field_name="contact_id",
|
|
195
|
-
help_text="Billing contact for the domain",
|
|
196
197
|
error_messages={
|
|
197
|
-
"invalid_choice": "Billing contact ID not found",
|
|
198
|
+
"invalid_choice": _("Billing contact ID not found"),
|
|
198
199
|
},
|
|
200
|
+
label=_("Billing Contact"),
|
|
199
201
|
)
|
|
200
202
|
tenant = CSVModelChoiceField(
|
|
201
203
|
queryset=Tenant.objects.all(),
|
|
202
204
|
required=False,
|
|
203
205
|
to_field_name="name",
|
|
204
|
-
|
|
206
|
+
label=_("Tenant"),
|
|
205
207
|
)
|
|
206
208
|
|
|
207
209
|
class Meta:
|
|
@@ -226,42 +228,49 @@ class ZoneTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
226
228
|
nameservers = DynamicModelMultipleChoiceField(
|
|
227
229
|
queryset=NameServer.objects.all(),
|
|
228
230
|
required=False,
|
|
231
|
+
label=_("Nameservers"),
|
|
229
232
|
)
|
|
230
233
|
record_templates = DynamicModelMultipleChoiceField(
|
|
231
234
|
queryset=RecordTemplate.objects.all(),
|
|
232
235
|
required=False,
|
|
236
|
+
label=_("Record Templates"),
|
|
233
237
|
)
|
|
234
238
|
description = forms.CharField(max_length=200, required=False)
|
|
235
239
|
registrar = DynamicModelChoiceField(
|
|
236
240
|
queryset=Registrar.objects.all(),
|
|
237
241
|
required=False,
|
|
242
|
+
label=_("Registrar"),
|
|
238
243
|
)
|
|
239
244
|
registrant = DynamicModelChoiceField(
|
|
240
245
|
queryset=RegistrationContact.objects.all(),
|
|
241
246
|
required=False,
|
|
247
|
+
label=_("Registrant"),
|
|
242
248
|
)
|
|
243
249
|
admin_c = DynamicModelChoiceField(
|
|
244
250
|
queryset=RegistrationContact.objects.all(),
|
|
245
251
|
required=False,
|
|
246
|
-
label="Administrative Contact",
|
|
252
|
+
label=_("Administrative Contact"),
|
|
247
253
|
)
|
|
248
254
|
tech_c = DynamicModelChoiceField(
|
|
249
255
|
queryset=RegistrationContact.objects.all(),
|
|
250
256
|
required=False,
|
|
251
|
-
label="Technical Contact",
|
|
257
|
+
label=_("Technical Contact"),
|
|
252
258
|
)
|
|
253
259
|
billing_c = DynamicModelChoiceField(
|
|
254
260
|
queryset=RegistrationContact.objects.all(),
|
|
255
261
|
required=False,
|
|
256
|
-
label="Billing Contact",
|
|
262
|
+
label=_("Billing Contact"),
|
|
257
263
|
)
|
|
258
|
-
|
|
264
|
+
tenant_group = DynamicModelChoiceField(
|
|
265
|
+
queryset=TenantGroup.objects.all(),
|
|
266
|
+
required=False,
|
|
267
|
+
label=_("Tenant Group"),
|
|
268
|
+
)
|
|
269
|
+
tenant = DynamicModelChoiceField(
|
|
259
270
|
queryset=Tenant.objects.all(),
|
|
260
271
|
required=False,
|
|
261
|
-
|
|
262
|
-
help_text="Assigned tenant",
|
|
272
|
+
label=_("Tenant"),
|
|
263
273
|
)
|
|
264
|
-
tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
|
265
274
|
|
|
266
275
|
model = ZoneTemplate
|
|
267
276
|
|
|
@@ -269,11 +278,11 @@ class ZoneTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
269
278
|
FieldSet(
|
|
270
279
|
"nameservers",
|
|
271
280
|
"description",
|
|
272
|
-
name="Attributes",
|
|
281
|
+
name=_("Attributes"),
|
|
273
282
|
),
|
|
274
283
|
FieldSet(
|
|
275
284
|
"record_templates",
|
|
276
|
-
name="Record Templates",
|
|
285
|
+
name=_("Record Templates"),
|
|
277
286
|
),
|
|
278
287
|
FieldSet(
|
|
279
288
|
"registrar",
|
|
@@ -281,9 +290,9 @@ class ZoneTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
281
290
|
"admin_c",
|
|
282
291
|
"tech_c",
|
|
283
292
|
"billing_c",
|
|
284
|
-
name="Domain Registration",
|
|
293
|
+
name=_("Domain Registration"),
|
|
285
294
|
),
|
|
286
|
-
FieldSet("tenant_group", "tenant", name="Tenancy"),
|
|
295
|
+
FieldSet("tenant_group", "tenant", name=_("Tenancy")),
|
|
287
296
|
)
|
|
288
297
|
|
|
289
298
|
nullable_fields = (
|
|
@@ -295,4 +304,5 @@ class ZoneTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
295
304
|
"admin_c",
|
|
296
305
|
"tech_c",
|
|
297
306
|
"billing_c",
|
|
307
|
+
"tenant",
|
|
298
308
|
)
|
|
Binary file
|
|
Binary file
|
|
@@ -8,6 +8,13 @@ from netbox_dns.utilities import update_dns_records
|
|
|
8
8
|
class Command(BaseCommand):
|
|
9
9
|
help = "Rebuild DNSsync relationships between IP addresses and records"
|
|
10
10
|
|
|
11
|
+
def add_arguments(self, parser):
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"--force",
|
|
14
|
+
action="store_true",
|
|
15
|
+
help="Update records even if DNS name was not changed (required for rebuilding filtered views",
|
|
16
|
+
)
|
|
17
|
+
|
|
11
18
|
def handle(self, *model_names, **options):
|
|
12
19
|
ip_addresses = IPAddress.objects.all()
|
|
13
20
|
for ip_address in ip_addresses:
|
|
@@ -15,4 +22,10 @@ class Command(BaseCommand):
|
|
|
15
22
|
self.stdout.write(
|
|
16
23
|
f"Updating DNS records for IP Address {ip_address}, VRF {ip_address.vrf}"
|
|
17
24
|
)
|
|
18
|
-
|
|
25
|
+
if (
|
|
26
|
+
update_dns_records(ip_address, force=options.get("force"))
|
|
27
|
+
and options.get("verbosity") >= 1
|
|
28
|
+
):
|
|
29
|
+
self.stdout.write(
|
|
30
|
+
f"Updated DNS records for IP Address {ip_address}, VRF {ip_address.vrf}"
|
|
31
|
+
)
|
netbox_dns/models/nameserver.py
CHANGED
|
@@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
|
|
|
4
4
|
from django.db import models, transaction
|
|
5
5
|
from django.db.models import Q
|
|
6
6
|
from django.urls import reverse
|
|
7
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
8
|
|
|
8
9
|
from netbox.models import NetBoxModel
|
|
9
10
|
from netbox.search import SearchIndex, register_search
|
|
@@ -29,14 +30,17 @@ __all__ = (
|
|
|
29
30
|
|
|
30
31
|
class NameServer(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
31
32
|
name = models.CharField(
|
|
33
|
+
verbose_name=_("Name"),
|
|
32
34
|
unique=True,
|
|
33
35
|
max_length=255,
|
|
34
36
|
)
|
|
35
37
|
description = models.CharField(
|
|
38
|
+
verbose_name=_("Description"),
|
|
36
39
|
max_length=200,
|
|
37
40
|
blank=True,
|
|
38
41
|
)
|
|
39
42
|
tenant = models.ForeignKey(
|
|
43
|
+
verbose_name=_("Tenant"),
|
|
40
44
|
to="tenancy.Tenant",
|
|
41
45
|
on_delete=models.PROTECT,
|
|
42
46
|
related_name="netbox_dns_nameservers",
|
|
@@ -50,8 +54,8 @@ class NameServer(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
50
54
|
)
|
|
51
55
|
|
|
52
56
|
class Meta:
|
|
53
|
-
verbose_name = "Nameserver"
|
|
54
|
-
verbose_name_plural = "Nameservers"
|
|
57
|
+
verbose_name = _("Nameserver")
|
|
58
|
+
verbose_name_plural = _("Nameservers")
|
|
55
59
|
|
|
56
60
|
ordering = ("name",)
|
|
57
61
|
|
netbox_dns/models/record.py
CHANGED
|
@@ -8,6 +8,7 @@ from django.db import transaction, models
|
|
|
8
8
|
from django.db.models import Q, ExpressionWrapper, BooleanField, Min
|
|
9
9
|
from django.urls import reverse
|
|
10
10
|
from django.conf import settings
|
|
11
|
+
from django.utils.translation import gettext_lazy as _
|
|
11
12
|
|
|
12
13
|
from netbox.models import NetBoxModel
|
|
13
14
|
from ipam.models import IPAddress
|
|
@@ -33,6 +34,9 @@ __all__ = (
|
|
|
33
34
|
"RecordIndex",
|
|
34
35
|
)
|
|
35
36
|
|
|
37
|
+
ZONE_ACTIVE_STATUS_LIST = get_plugin_config("netbox_dns", "zone_active_status")
|
|
38
|
+
RECORD_ACTIVE_STATUS_LIST = get_plugin_config("netbox_dns", "record_active_status")
|
|
39
|
+
|
|
36
40
|
|
|
37
41
|
def min_ttl(*ttl_list):
|
|
38
42
|
return min((ttl for ttl in ttl_list if ttl is not None), default=None)
|
|
@@ -101,14 +105,14 @@ class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
|
101
105
|
.annotate(
|
|
102
106
|
active=ExpressionWrapper(
|
|
103
107
|
Q(
|
|
104
|
-
Q(zone__status__in=
|
|
108
|
+
Q(zone__status__in=ZONE_ACTIVE_STATUS_LIST)
|
|
105
109
|
& Q(
|
|
106
110
|
Q(address_record__isnull=True)
|
|
107
111
|
| Q(
|
|
108
|
-
address_record__zone__status__in=
|
|
112
|
+
address_record__zone__status__in=ZONE_ACTIVE_STATUS_LIST
|
|
109
113
|
)
|
|
110
114
|
)
|
|
111
|
-
& Q(status__in=
|
|
115
|
+
& Q(status__in=RECORD_ACTIVE_STATUS_LIST)
|
|
112
116
|
),
|
|
113
117
|
output_field=BooleanField(),
|
|
114
118
|
)
|
|
@@ -117,66 +121,73 @@ class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
|
117
121
|
|
|
118
122
|
|
|
119
123
|
class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
120
|
-
ACTIVE_STATUS_LIST = (RecordStatusChoices.STATUS_ACTIVE,)
|
|
121
|
-
|
|
122
124
|
unique_ptr_qs = Q(
|
|
123
125
|
Q(disable_ptr=False),
|
|
124
126
|
Q(Q(type=RecordTypeChoices.A) | Q(type=RecordTypeChoices.AAAA)),
|
|
125
127
|
)
|
|
126
128
|
|
|
127
129
|
name = models.CharField(
|
|
130
|
+
verbose_name=_("Name"),
|
|
128
131
|
max_length=255,
|
|
129
132
|
)
|
|
130
133
|
zone = models.ForeignKey(
|
|
131
|
-
"Zone",
|
|
134
|
+
verbose_name=_("Zone"),
|
|
135
|
+
to="Zone",
|
|
132
136
|
on_delete=models.CASCADE,
|
|
133
137
|
)
|
|
134
138
|
fqdn = models.CharField(
|
|
139
|
+
verbose_name=_("FQDN"),
|
|
135
140
|
max_length=255,
|
|
136
141
|
null=True,
|
|
137
142
|
blank=True,
|
|
138
143
|
default=None,
|
|
139
144
|
)
|
|
140
145
|
type = models.CharField(
|
|
146
|
+
verbose_name=_("Type"),
|
|
141
147
|
choices=RecordTypeChoices,
|
|
142
148
|
max_length=10,
|
|
143
149
|
)
|
|
144
150
|
value = models.CharField(
|
|
151
|
+
verbose_name=_("Value"),
|
|
145
152
|
max_length=65535,
|
|
146
153
|
)
|
|
147
154
|
status = models.CharField(
|
|
155
|
+
verbose_name=_("Status"),
|
|
148
156
|
max_length=50,
|
|
149
157
|
choices=RecordStatusChoices,
|
|
150
158
|
default=RecordStatusChoices.STATUS_ACTIVE,
|
|
151
159
|
blank=False,
|
|
152
160
|
)
|
|
153
161
|
ttl = models.PositiveIntegerField(
|
|
154
|
-
verbose_name="TTL",
|
|
162
|
+
verbose_name=_("TTL"),
|
|
155
163
|
null=True,
|
|
156
164
|
blank=True,
|
|
157
165
|
)
|
|
158
166
|
managed = models.BooleanField(
|
|
167
|
+
verbose_name=_("Managed"),
|
|
159
168
|
null=False,
|
|
160
169
|
default=False,
|
|
161
170
|
)
|
|
162
171
|
ptr_record = models.OneToOneField(
|
|
163
|
-
"
|
|
172
|
+
verbose_name="PTR Record",
|
|
173
|
+
to="self",
|
|
164
174
|
on_delete=models.SET_NULL,
|
|
165
175
|
related_name="address_record",
|
|
166
|
-
verbose_name="PTR record",
|
|
167
176
|
null=True,
|
|
168
177
|
blank=True,
|
|
169
178
|
)
|
|
170
179
|
disable_ptr = models.BooleanField(
|
|
171
|
-
verbose_name="Disable PTR",
|
|
172
|
-
help_text="Disable PTR record creation",
|
|
180
|
+
verbose_name=_("Disable PTR"),
|
|
181
|
+
help_text=_("Disable PTR record creation"),
|
|
173
182
|
default=False,
|
|
174
183
|
)
|
|
175
184
|
description = models.CharField(
|
|
185
|
+
verbose_name=_("Description"),
|
|
176
186
|
max_length=200,
|
|
177
187
|
blank=True,
|
|
178
188
|
)
|
|
179
189
|
tenant = models.ForeignKey(
|
|
190
|
+
verbose_name=_("Tenant"),
|
|
180
191
|
to="tenancy.Tenant",
|
|
181
192
|
on_delete=models.PROTECT,
|
|
182
193
|
related_name="netbox_dns_records",
|
|
@@ -184,13 +195,13 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
184
195
|
null=True,
|
|
185
196
|
)
|
|
186
197
|
ip_address = AddressField(
|
|
187
|
-
verbose_name="Related IP Address",
|
|
188
|
-
help_text="IP address related to an address (A/AAAA) or PTR record",
|
|
198
|
+
verbose_name=_("Related IP Address"),
|
|
199
|
+
help_text=_("IP address related to an address (A/AAAA) or PTR record"),
|
|
189
200
|
blank=True,
|
|
190
201
|
null=True,
|
|
191
202
|
)
|
|
192
203
|
ipam_ip_address = models.ForeignKey(
|
|
193
|
-
verbose_name="IPAM IP Address",
|
|
204
|
+
verbose_name=_("IPAM IP Address"),
|
|
194
205
|
to="ipam.IPAddress",
|
|
195
206
|
on_delete=models.CASCADE,
|
|
196
207
|
related_name="netbox_dns_records",
|
|
@@ -198,10 +209,10 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
198
209
|
null=True,
|
|
199
210
|
)
|
|
200
211
|
rfc2317_cname_record = models.ForeignKey(
|
|
201
|
-
"
|
|
212
|
+
verbose_name=_("RFC2317 CNAME Record"),
|
|
213
|
+
to="self",
|
|
202
214
|
on_delete=models.SET_NULL,
|
|
203
215
|
related_name="rfc2317_ptr_records",
|
|
204
|
-
verbose_name="RFC2317 CNAME record",
|
|
205
216
|
null=True,
|
|
206
217
|
blank=True,
|
|
207
218
|
)
|
|
@@ -221,8 +232,8 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
221
232
|
)
|
|
222
233
|
|
|
223
234
|
class Meta:
|
|
224
|
-
verbose_name = "Record"
|
|
225
|
-
verbose_name_plural = "Records"
|
|
235
|
+
verbose_name = _("Record")
|
|
236
|
+
verbose_name_plural = _("Records")
|
|
226
237
|
|
|
227
238
|
ordering = (
|
|
228
239
|
"fqdn",
|
|
@@ -285,8 +296,8 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
285
296
|
@property
|
|
286
297
|
def is_active(self):
|
|
287
298
|
return (
|
|
288
|
-
self.status in
|
|
289
|
-
and self.zone.status in
|
|
299
|
+
self.status in RECORD_ACTIVE_STATUS_LIST
|
|
300
|
+
and self.zone.status in ZONE_ACTIVE_STATUS_LIST
|
|
290
301
|
)
|
|
291
302
|
|
|
292
303
|
@property
|
|
@@ -537,7 +548,9 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
537
548
|
if not fqdn.is_subdomain(_zone):
|
|
538
549
|
raise ValidationError(
|
|
539
550
|
{
|
|
540
|
-
"name":
|
|
551
|
+
"name": _("{name} is not a name in {zone}").format(
|
|
552
|
+
name=self.name, zone=zone.name
|
|
553
|
+
),
|
|
541
554
|
}
|
|
542
555
|
)
|
|
543
556
|
|
|
@@ -581,13 +594,13 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
581
594
|
{
|
|
582
595
|
"name": exc,
|
|
583
596
|
}
|
|
584
|
-
)
|
|
597
|
+
)
|
|
585
598
|
|
|
586
599
|
def validate_value(self):
|
|
587
600
|
try:
|
|
588
|
-
validate_record_value(self
|
|
601
|
+
validate_record_value(self)
|
|
589
602
|
except ValidationError as exc:
|
|
590
|
-
raise ValidationError({"value": exc})
|
|
603
|
+
raise ValidationError({"value": exc})
|
|
591
604
|
|
|
592
605
|
def check_unique_record(self, new_zone=None):
|
|
593
606
|
if not get_plugin_config("netbox_dns", "enforce_unique_records", False):
|
|
@@ -604,7 +617,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
604
617
|
name=self.name,
|
|
605
618
|
type=self.type,
|
|
606
619
|
value=self.value,
|
|
607
|
-
status__in=
|
|
620
|
+
status__in=RECORD_ACTIVE_STATUS_LIST,
|
|
608
621
|
)
|
|
609
622
|
|
|
610
623
|
if not self._state.adding:
|
|
@@ -621,9 +634,13 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
621
634
|
|
|
622
635
|
raise ValidationError(
|
|
623
636
|
{
|
|
624
|
-
"value":
|
|
637
|
+
"value": _(
|
|
638
|
+
"There is already an active {type} record for name {name} in zone {zone} with value {value}."
|
|
639
|
+
).format(
|
|
640
|
+
type=self.type, name=self.name, zone=self.zone, value=self.value
|
|
641
|
+
)
|
|
625
642
|
}
|
|
626
|
-
)
|
|
643
|
+
)
|
|
627
644
|
|
|
628
645
|
def handle_conflicting_address_records(self):
|
|
629
646
|
if self.ipam_ip_address is None or not self.is_active:
|
|
@@ -637,7 +654,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
637
654
|
name=self.name,
|
|
638
655
|
type=self.type,
|
|
639
656
|
value=self.value,
|
|
640
|
-
status__in=
|
|
657
|
+
status__in=RECORD_ACTIVE_STATUS_LIST,
|
|
641
658
|
ipam_ip_address__isnull=True,
|
|
642
659
|
)
|
|
643
660
|
|
|
@@ -680,9 +697,16 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
680
697
|
conflicting_ttls = ", ".join({str(record.ttl) for record in records})
|
|
681
698
|
raise ValidationError(
|
|
682
699
|
{
|
|
683
|
-
"ttl":
|
|
700
|
+
"ttl": _(
|
|
701
|
+
"There is at least one active {type} record for name {name} in zone {zone} and TTL is different ({ttls})."
|
|
702
|
+
).format(
|
|
703
|
+
type=self.type,
|
|
704
|
+
name=self.name,
|
|
705
|
+
zone=self.zone,
|
|
706
|
+
ttls=conflicting_ttls,
|
|
707
|
+
)
|
|
684
708
|
}
|
|
685
|
-
)
|
|
709
|
+
)
|
|
686
710
|
|
|
687
711
|
def update_rrset_ttl(self, ttl=None):
|
|
688
712
|
if self._state.adding:
|
|
@@ -760,24 +784,30 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
760
784
|
):
|
|
761
785
|
raise ValidationError(
|
|
762
786
|
{
|
|
763
|
-
"value":
|
|
787
|
+
"value": _(
|
|
788
|
+
"There is already an active record for name {name} in zone {zone}, RFC2317 CNAME is not allowed."
|
|
789
|
+
).format(name=ptr_cname_name, zone=ptr_cname_zone)
|
|
764
790
|
}
|
|
765
|
-
)
|
|
791
|
+
)
|
|
766
792
|
|
|
767
793
|
if self.type == RecordTypeChoices.SOA and self.name != "@":
|
|
768
794
|
raise ValidationError(
|
|
769
795
|
{
|
|
770
|
-
"name":
|
|
796
|
+
"name": _(
|
|
797
|
+
"SOA records are only allowed with name @ and are created automatically by NetBox DNS"
|
|
798
|
+
)
|
|
771
799
|
}
|
|
772
|
-
)
|
|
800
|
+
)
|
|
773
801
|
|
|
774
802
|
if self.type == RecordTypeChoices.CNAME:
|
|
775
803
|
if records.exclude(type=RecordTypeChoices.NSEC).exists():
|
|
776
804
|
raise ValidationError(
|
|
777
805
|
{
|
|
778
|
-
"type":
|
|
806
|
+
"type": _(
|
|
807
|
+
"There is already an active record for name {name} in zone {zone}, CNAME is not allowed."
|
|
808
|
+
).format(name=self.name, zone=self.zone)
|
|
779
809
|
}
|
|
780
|
-
)
|
|
810
|
+
)
|
|
781
811
|
|
|
782
812
|
elif (
|
|
783
813
|
records.filter(type=RecordTypeChoices.CNAME).exists()
|
|
@@ -785,17 +815,21 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
785
815
|
):
|
|
786
816
|
raise ValidationError(
|
|
787
817
|
{
|
|
788
|
-
"type":
|
|
818
|
+
"type": _(
|
|
819
|
+
"There is already an active CNAME record for name {name} in zone {zone}, no other record allowed."
|
|
820
|
+
).format(name=self.name, zone=self.zone)
|
|
789
821
|
}
|
|
790
|
-
)
|
|
822
|
+
)
|
|
791
823
|
|
|
792
824
|
elif self.type in RecordTypeChoices.SINGLETONS:
|
|
793
825
|
if records.filter(type=self.type).exists():
|
|
794
826
|
raise ValidationError(
|
|
795
827
|
{
|
|
796
|
-
"type":
|
|
828
|
+
"type": _(
|
|
829
|
+
"There is already an active {type} record for name {name} in zone {zone}, more than one are not allowed."
|
|
830
|
+
).format(type=self.type, name=self.name, zone=self.zone)
|
|
797
831
|
}
|
|
798
|
-
)
|
|
832
|
+
)
|
|
799
833
|
|
|
800
834
|
super().clean(*args, **kwargs)
|
|
801
835
|
|