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.

Files changed (84) hide show
  1. netbox_dns/__init__.py +14 -6
  2. netbox_dns/api/nested_serializers.py +3 -2
  3. netbox_dns/api/serializers_/nameserver.py +2 -1
  4. netbox_dns/api/serializers_/record.py +5 -4
  5. netbox_dns/api/serializers_/record_template.py +2 -1
  6. netbox_dns/api/serializers_/view.py +2 -1
  7. netbox_dns/api/serializers_/zone.py +12 -11
  8. netbox_dns/api/serializers_/zone_template.py +8 -7
  9. netbox_dns/api/views.py +9 -4
  10. netbox_dns/choices/record.py +4 -2
  11. netbox_dns/choices/zone.py +8 -4
  12. netbox_dns/fields/address.py +5 -22
  13. netbox_dns/fields/network.py +2 -1
  14. netbox_dns/fields/rfc2317.py +7 -3
  15. netbox_dns/filtersets/nameserver.py +3 -2
  16. netbox_dns/filtersets/record.py +14 -9
  17. netbox_dns/filtersets/record_template.py +3 -2
  18. netbox_dns/filtersets/view.py +3 -2
  19. netbox_dns/filtersets/zone.py +24 -22
  20. netbox_dns/filtersets/zone_template.py +15 -14
  21. netbox_dns/forms/nameserver.py +41 -17
  22. netbox_dns/forms/record.py +61 -32
  23. netbox_dns/forms/record_template.py +49 -28
  24. netbox_dns/forms/registrar.py +21 -17
  25. netbox_dns/forms/registration_contact.py +37 -25
  26. netbox_dns/forms/view.py +49 -27
  27. netbox_dns/forms/zone.py +173 -120
  28. netbox_dns/forms/zone_template.py +53 -43
  29. netbox_dns/locale/de/LC_MESSAGES/django.mo +0 -0
  30. netbox_dns/locale/en/LC_MESSAGES/django.mo +0 -0
  31. netbox_dns/management/commands/rebuild_dnssync.py +14 -1
  32. netbox_dns/models/nameserver.py +6 -2
  33. netbox_dns/models/record.py +74 -40
  34. netbox_dns/models/record_template.py +17 -9
  35. netbox_dns/models/registrar.py +11 -7
  36. netbox_dns/models/registration_contact.py +23 -11
  37. netbox_dns/models/view.py +15 -6
  38. netbox_dns/models/zone.py +83 -50
  39. netbox_dns/models/zone_template.py +12 -10
  40. netbox_dns/navigation.py +30 -28
  41. netbox_dns/signals/ipam_dnssync.py +21 -14
  42. netbox_dns/tables/ipam_dnssync.py +2 -1
  43. netbox_dns/tables/nameserver.py +2 -0
  44. netbox_dns/tables/record.py +21 -11
  45. netbox_dns/tables/record_template.py +12 -5
  46. netbox_dns/tables/registrar.py +2 -0
  47. netbox_dns/tables/registration_contact.py +2 -0
  48. netbox_dns/tables/view.py +3 -1
  49. netbox_dns/tables/zone.py +15 -2
  50. netbox_dns/tables/zone_template.py +7 -0
  51. netbox_dns/templates/netbox_dns/nameserver.html +6 -5
  52. netbox_dns/templates/netbox_dns/record/managed.html +2 -1
  53. netbox_dns/templates/netbox_dns/record/related.html +26 -14
  54. netbox_dns/templates/netbox_dns/record.html +39 -20
  55. netbox_dns/templates/netbox_dns/recordtemplate.html +27 -15
  56. netbox_dns/templates/netbox_dns/registrar.html +11 -10
  57. netbox_dns/templates/netbox_dns/registrationcontact.html +16 -15
  58. netbox_dns/templates/netbox_dns/view/button.html +2 -1
  59. netbox_dns/templates/netbox_dns/view/prefix.html +7 -4
  60. netbox_dns/templates/netbox_dns/view/related.html +26 -10
  61. netbox_dns/templates/netbox_dns/view.html +11 -14
  62. netbox_dns/templates/netbox_dns/zone/base.html +2 -1
  63. netbox_dns/templates/netbox_dns/zone/child.html +3 -2
  64. netbox_dns/templates/netbox_dns/zone/record.html +3 -2
  65. netbox_dns/templates/netbox_dns/zone/registration.html +8 -7
  66. netbox_dns/templates/netbox_dns/zone.html +28 -30
  67. netbox_dns/templates/netbox_dns/zonetemplate.html +27 -17
  68. netbox_dns/utilities/ipam_dnssync.py +15 -4
  69. netbox_dns/validators/dns_name.py +11 -4
  70. netbox_dns/validators/dns_value.py +55 -9
  71. netbox_dns/validators/rfc2317.py +6 -3
  72. netbox_dns/views/nameserver.py +4 -2
  73. netbox_dns/views/record_template.py +4 -3
  74. netbox_dns/views/registrar.py +3 -1
  75. netbox_dns/views/registration_contact.py +2 -1
  76. netbox_dns/views/view.py +2 -1
  77. netbox_dns/views/zone.py +6 -4
  78. netbox_dns/views/zone_template.py +8 -7
  79. {netbox_plugin_dns-1.1.2.dist-info → netbox_plugin_dns-1.1.4.dist-info}/METADATA +2 -2
  80. netbox_plugin_dns-1.1.4.dist-info/RECORD +150 -0
  81. netbox_plugin_dns-1.1.2.dist-info/RECORD +0 -148
  82. {netbox_plugin_dns-1.1.2.dist-info → netbox_plugin_dns-1.1.4.dist-info}/LICENSE +0 -0
  83. {netbox_plugin_dns-1.1.2.dist-info → netbox_plugin_dns-1.1.4.dist-info}/WHEEL +0 -0
  84. {netbox_plugin_dns-1.1.2.dist-info → netbox_plugin_dns-1.1.4.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@
2
2
  {% load helpers %}
3
3
  {% load render_table from django_tables2 %}
4
4
  {% load perms %}
5
+ {% load i18n %}
5
6
 
6
7
  {% block content %}
7
8
  {% include 'inc/table_controls_htmx.html' with table_modal="ZoneTable_config" %}
@@ -23,12 +24,12 @@
23
24
  {% block bulk_buttons %}{% endblock %}
24
25
  {% if bulk_edit_url and perms.netbox_dns.change_zone %}
25
26
  <button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning">
26
- <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
27
+ <i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit Selected" %}
27
28
  </button>
28
29
  {% endif %}
29
30
  {% if bulk_delete_url and perms.netbox_dns.delete_zone %}
30
31
  <button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger">
31
- <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
32
+ <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete Selected" %}
32
33
  </button>
33
34
  {% endif %}
34
35
  </div>
@@ -2,6 +2,7 @@
2
2
  {% load helpers %}
3
3
  {% load render_table from django_tables2 %}
4
4
  {% load perms %}
5
+ {% load i18n %}
5
6
 
6
7
  {% block content %}
7
8
  {% include 'inc/table_controls_htmx.html' with table_modal="RecordTable_config" %}
@@ -23,12 +24,12 @@
23
24
  {% block bulk_buttons %}{% endblock %}
24
25
  {% if bulk_edit_url and perms.netbox_dns.change_record %}
25
26
  <button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning">
26
- <i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
27
+ <i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit Selected" %}
27
28
  </button>
28
29
  {% endif %}
29
30
  {% if bulk_delete_url and perms.netbox_dns.delete_record %}
30
31
  <button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger">
31
- <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
32
+ <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete Selected" %}
32
33
  </button>
33
34
  {% endif %}
34
35
  </div>
@@ -1,32 +1,33 @@
1
1
  {% extends 'netbox_dns/zone/base.html' %}
2
2
  {% load helpers %}
3
+ {% load i18n %}
3
4
 
4
5
  {% block content %}
5
6
  <div class="card">
6
- <h5 class="card-header">Domain Registration</h5>
7
+ <h5 class="card-header">{% trans "Domain Registration" %}</h5>
7
8
  <table class="table table-hover attr-table">
8
9
  <tr>
9
- <th scope="row">Registrar</th>
10
+ <th scope="row">{% trans "Registrar" %}</th>
10
11
  <td>{{ object.registrar|linkify|placeholder }}</td>
11
12
  </tr>
12
13
  <tr>
13
- <th scope="row">Registry Domain ID</th>
14
+ <th scope="row">{% trans "Registry Domain ID" %}</th>
14
15
  <td>{{ object.registry_domain_id|placeholder }}</td>
15
16
  </tr>
16
17
  <tr>
17
- <th scope="row">Registrant</th>
18
+ <th scope="row">{% trans "Registrant" %}</th>
18
19
  <td>{{ object.registrant|linkify|placeholder }}</td>
19
20
  </tr>
20
21
  <tr>
21
- <th scope="row">Administrative Contact</th>
22
+ <th scope="row">{% trans "Administrative Contact" %}</th>
22
23
  <td>{{ object.admin_c|linkify|placeholder }}</td>
23
24
  </tr>
24
25
  <tr>
25
- <th scope="row">Technical Contact</th>
26
+ <th scope="row">{% trans "Technical Contact" %}</th>
26
27
  <td>{{ object.tech_c|linkify|placeholder }}</td>
27
28
  </tr>
28
29
  <tr>
29
- <th scope="row">Billing Contact</th>
30
+ <th scope="row">{% trans "Billing Contact" %}</th>
30
31
  <td>{{ object.billing_c|linkify|placeholder }}</td>
31
32
  </tr>
32
33
  </table>
@@ -1,43 +1,41 @@
1
1
  {% extends 'netbox_dns/zone/base.html' %}
2
2
  {% load helpers %}
3
- {% load render_table from django_tables2 %}
3
+ {% load i18n %}
4
4
 
5
5
  {% block content %}
6
6
  <div class="row">
7
7
  <div class="col col-md-6">
8
8
  <div class="card">
9
- <h5 class="card-header">
10
- Zone
11
- </h5>
9
+ <h5 class="card-header">{% trans "Zone" %}</h5>
12
10
  <table class="table table-hover attr-table">
13
11
  <tr>
14
- <th scope="row">Name</th>
12
+ <th scope="row">{% trans "Name" %}</th>
15
13
  <td>{{ object.name }}</td>
16
14
  </tr>
17
15
  {% if unicode_name %}
18
16
  <tr>
19
- <th scope="row">IDN</th>
17
+ <th scope="row">{% trans "IDN" %}</th>
20
18
  <td>{{ unicode_name }}</td>
21
19
  </tr>
22
20
  {% endif %}
23
21
  {% if parent_zone %}
24
22
  <tr>
25
- <th scope="row">Parent Zone</th>
23
+ <th scope="row">{% trans "Parent Zone" %}</th>
26
24
  <td>{{ parent_zone|linkify }}</td>
27
25
  </tr>
28
26
  {% endif %}
29
27
  <tr>
30
- <th scope="row">View</th>
28
+ <th scope="row">{% trans "View" context "DNS" %}</th>
31
29
  <td>{{ object.view|linkify }}</td>
32
30
  </tr>
33
31
  {% if object.description %}
34
32
  <tr>
35
- <th scope="row">Description</th>
33
+ <th scope="row">{% trans "Description" %}</th>
36
34
  <td style="word-break:break-all;">{{ object.description }}</td>
37
35
  </tr>
38
36
  {% endif %}
39
37
  <tr>
40
- <th scope="row">Tenant</th>
38
+ <th scope="row">{% trans "Tenant" %}</th>
41
39
  <td>
42
40
  {% if object.tenant.group %}
43
41
  {{ object.tenant.group|linkify }} /
@@ -46,11 +44,11 @@
46
44
  </td>
47
45
  </tr>
48
46
  <tr>
49
- <th scope="row">Status</th>
47
+ <th scope="row">{% trans "Status" %}</th>
50
48
  <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
51
49
  </tr>
52
50
  <tr>
53
- <th scope="row">Nameservers</th>
51
+ <th scope="row">{% trans "Nameservers" %}</th>
54
52
  <td>
55
53
  <table>
56
54
  {% for nameserver in object.nameservers.all %}
@@ -60,7 +58,7 @@
60
58
  </td>
61
59
  {% if nameserver_warnings %}
62
60
  <tr>
63
- <th class="text-warning" scope="row">Warnings</th>
61
+ <th class="text-warning" scope="row">{% trans "Warnings" %}</th>
64
62
  <td>
65
63
  <table>
66
64
  {% for warning in nameserver_warnings %}
@@ -74,7 +72,7 @@
74
72
  {% endif %}
75
73
  {% if nameserver_errors %}
76
74
  <tr>
77
- <th class="text-danger" scope="row">Errors</th>
75
+ <th class="text-danger" scope="row">{% trans "Errors" %}</th>
78
76
  <td>
79
77
  <table>
80
78
  {% for error in nameserver_errors %}
@@ -88,11 +86,11 @@
88
86
  {% endif %}
89
87
  </tr>
90
88
  <tr>
91
- <th scope="row">Default TTL</th>
89
+ <th scope="row">{% trans "Default TTL" %}</th>
92
90
  <td>{{ object.default_ttl }}</td>
93
91
  </tr>
94
92
  <tr>
95
- <th scope="row">Description</th>
93
+ <th scope="row">{% trans "Description" %}</th>
96
94
  <td>{{ object.description }}</td>
97
95
  </tr>
98
96
  </table>
@@ -103,64 +101,64 @@
103
101
  </div>
104
102
  <div class="col col-md-6">
105
103
  <div class="card">
106
- <h5 class="card-header">Zone SOA</h5>
104
+ <h5 class="card-header">{% trans "Zone SOA" %}</h5>
107
105
  <table class="table table-hover attr-table">
108
106
  <tr>
109
- <th scope="row">TTL</th>
107
+ <th scope="row">{% trans "TTL" %}</th>
110
108
  <td>{{ object.soa_ttl }}</td>
111
109
  </tr>
112
110
  <tr>
113
- <th scope="row">MName</th>
111
+ <th scope="row">{% trans "MName" %}</th>
114
112
  <td>{{ object.soa_mname|linkify }}</td>
115
113
  </tr>
116
114
  <tr>
117
- <th scope="row">RName</th>
115
+ <th scope="row">{% trans "RName" %}</th>
118
116
  <td>{{ object.soa_rname }}</td>
119
117
  </tr>
120
118
  {% if object.soa_serial_auto %}
121
119
  <tr>
122
- <th scope="row">Serial (auto-generated)</th>
120
+ <th scope="row">{% trans "Serial (auto-generated)" %}</th>
123
121
  <td>{{ object.soa_serial }}</td>
124
122
  </tr>
125
123
  {% else %}
126
124
  <tr>
127
- <th scope="row">Serial</th>
125
+ <th scope="row">{% trans "Serial" context "SOA" %}</th>
128
126
  <td>{{ object.soa_serial }}</td>
129
127
  </tr>
130
128
  {% endif %}
131
129
  <tr>
132
- <th scope="row">Refresh</th>
130
+ <th scope="row">{% trans "Refresh" %}</th>
133
131
  <td>{{ object.soa_refresh }}</td>
134
132
  </tr>
135
133
  <tr>
136
- <th scope="row">Retry</th>
134
+ <th scope="row">{% trans "Retry" %}</th>
137
135
  <td>{{ object.soa_retry }}</td>
138
136
  </tr>
139
137
  <tr>
140
- <th scope="row">Expire</th>
138
+ <th scope="row">{% trans "Expire" %}</th>
141
139
  <td>{{ object.soa_expire }}</td>
142
140
  </tr>
143
141
  <tr>
144
- <th scope="row">Minimum TTL</th>
142
+ <th scope="row">{% trans "Minimum TTL" %}</th>
145
143
  <td>{{ object.soa_minimum }}</td>
146
144
  </tr>
147
145
  </table>
148
146
  </div>
149
147
  {% if object.rfc2317_prefix %}
150
148
  <div class="card">
151
- <h5 class="card-header">RFC2317</h5>
149
+ <h5 class="card-header">{% trans "RFC2317" %}</h5>
152
150
  <table class="table table-hover attr-table">
153
151
  <tr>
154
- <th scope="row">Prefix</th>
152
+ <th scope="row">{% trans "Prefix" %}</th>
155
153
  <td>{{ object.rfc2317_prefix }}</td>
156
154
  </tr>
157
155
  <tr>
158
- <th scope="row">Parent Managed</th>
156
+ <th scope="row">{% trans "Parent Managed" %}</th>
159
157
  <td>{% checkmark object.rfc2317_parent_managed %}</td>
160
158
  </tr>
161
159
  {% if object.rfc2317_parent_managed %}
162
160
  <tr>
163
- <th scope="row">Parent Zone</th>
161
+ <th scope="row">{% trans "Parent Zone" %}</th>
164
162
  <td>{{ object.rfc2317_parent_zone|linkify }}</td>
165
163
  </tr>
166
164
  {% endif %}
@@ -3,27 +3,26 @@
3
3
  {% load plugins %}
4
4
  {% load render_table from django_tables2 %}
5
5
  {% load perms %}
6
+ {% load i18n %}
6
7
 
7
8
  {% block content %}
8
9
  <div class="row">
9
10
  <div class="col col-md-6">
10
11
  <div class="card">
11
- <h5 class="card-header">
12
- Zone Template
13
- </h5>
12
+ <h5 class="card-header">{% trans "Zone Template" %}</h5>
14
13
  <table class="table table-hover attr-table">
15
14
  <tr>
16
- <th scope="row">Name</th>
15
+ <th scope="row">{% trans "Name" %}</th>
17
16
  <td>{{ object.name }}</td>
18
17
  </tr>
19
18
  {% if object.description %}
20
19
  <tr>
21
- <th scope="row">Description</th>
20
+ <th scope="row">{% trans "Description" %}</th>
22
21
  <td style="word-break:break-all;">{{ object.description }}</td>
23
22
  </tr>
24
23
  {% endif %}
25
24
  <tr>
26
- <th scope="row">Tenant</th>
25
+ <th scope="row">{% trans "Tenant" %}</th>
27
26
  <td>
28
27
  {% if object.tenant.group %}
29
28
  {{ object.tenant.group|linkify }} /
@@ -32,7 +31,7 @@
32
31
  </td>
33
32
  </tr>
34
33
  <tr>
35
- <th scope="row">Nameservers</th>
34
+ <th scope="row">{% trans "Nameservers" %}</th>
36
35
  <td>
37
36
  <table>
38
37
  {% for nameserver in object.nameservers.all %}
@@ -42,7 +41,7 @@
42
41
  </td>
43
42
  </tr>
44
43
  <tr>
45
- <th scope="row">Description</th>
44
+ <th scope="row">{% trans "Description" %}</th>
46
45
  <td>{{ object.description }}</td>
47
46
  </tr>
48
47
  </table>
@@ -53,34 +52,45 @@
53
52
  </div>
54
53
  <div class="col col-md-6">
55
54
  <div class="card">
56
- <h5 class="card-header">Domain Registration</h5>
55
+ <h5 class="card-header">{% trans "Domain Registration" %}</h5>
57
56
  <table class="table table-hover attr-table">
58
57
  <tr>
59
- <th scope="row">Registrar</th>
58
+ <th scope="row">{% trans "Registrar" %}</th>
60
59
  <td>{{ object.registrar|linkify|placeholder }}</td>
61
60
  </tr>
62
61
  <tr>
63
- <th scope="row">Registrant</th>
62
+ <th scope="row">{% trans "Registrant" %}</th>
64
63
  <td>{{ object.registrant|linkify|placeholder }}</td>
65
64
  </tr>
66
65
  <tr>
67
- <th scope="row">Administrative Contact</th>
66
+ <th scope="row">{% trans "Administrative Contact" %}</th>
68
67
  <td>{{ object.admin_c|linkify|placeholder }}</td>
69
68
  </tr>
70
69
  <tr>
71
- <th scope="row">Technical Contact</th>
70
+ <th scope="row">{% trans "Technical Contact" %}</th>
72
71
  <td>{{ object.tech_c|linkify|placeholder }}</td>
73
72
  </tr>
74
73
  <tr>
75
- <th scope="row">Billing Contact</th>
74
+ <th scope="row">{% trans "Billing Contact" %}</th>
76
75
  <td>{{ object.billing_c|linkify|placeholder }}</td>
77
76
  </tr>
78
77
  </table>
79
78
  </div>
80
79
  </div>
81
80
  </div>
82
- <div class="col col-md-12">
83
- {% include 'inc/panel_table.html' with table=record_template_table heading="Record Templates" %}
84
- </div>
81
+ {% if record_template_table %}
82
+ <div class="col col-md-12">
83
+ <div class="card">
84
+ {% if record_template_table.rows|length == 1 %}
85
+ <h2 class="card-header">{% trans "Record Template" %}</h2>
86
+ {% else %}
87
+ <h2 class="card-header">{% trans "Record Templates" %}</h2>
88
+ {% endif %}
89
+ <div class="table-responsive">
90
+ {% render_table record_template_table 'inc/table.html' %}
91
+ </div>
92
+ </div>
93
+ </div>
94
+ {% endif %}
85
95
  </div>
86
96
  {% endblock %}
@@ -158,12 +158,13 @@ def check_dns_records(ip_address, zone=None, view=None):
158
158
  record.clean(new_zone=new_zone)
159
159
 
160
160
 
161
- def update_dns_records(ip_address, view=None):
161
+ def update_dns_records(ip_address, view=None, force=False):
162
162
  from netbox_dns.models import Zone, Record
163
163
 
164
+ updated = False
165
+
164
166
  if ip_address.dns_name == "":
165
- delete_dns_records(ip_address)
166
- return
167
+ return delete_dns_records(ip_address)
167
168
 
168
169
  zones = get_zones(ip_address, view=view)
169
170
 
@@ -178,16 +179,19 @@ def update_dns_records(ip_address, view=None):
178
179
  "ipaddress_dns_disabled"
179
180
  ):
180
181
  record.delete()
182
+ updated = True
181
183
  continue
182
184
 
183
185
  record.update_fqdn()
184
- if not _match_data(ip_address, record):
186
+ if not _match_data(ip_address, record) or force:
185
187
  updated, deleted = record.update_from_ip_address(ip_address)
186
188
 
187
189
  if deleted:
188
190
  record.delete()
191
+ updated = True
189
192
  elif updated:
190
193
  record.save()
194
+ updated = True
191
195
 
192
196
  zones = Zone.objects.filter(pk__in=[zone.pk for zone in zones]).exclude(
193
197
  pk__in=set(
@@ -203,9 +207,13 @@ def update_dns_records(ip_address, view=None):
203
207
 
204
208
  if record is not None:
205
209
  record.save()
210
+ updated = True
211
+
212
+ return updated
206
213
 
207
214
 
208
215
  def delete_dns_records(ip_address, view=None):
216
+ deleted = False
209
217
 
210
218
  if view is None:
211
219
  address_records = ip_address.netbox_dns_records.all()
@@ -214,6 +222,9 @@ def delete_dns_records(ip_address, view=None):
214
222
 
215
223
  for record in address_records:
216
224
  record.delete()
225
+ deleted = True
226
+
227
+ return deleted
217
228
 
218
229
 
219
230
  def get_views_by_prefix(prefix):
@@ -1,6 +1,7 @@
1
1
  import re
2
2
 
3
3
  from django.core.exceptions import ValidationError
4
+ from django.utils.translation import gettext as _
4
5
 
5
6
  from netbox.plugins.utils import get_plugin_config
6
7
 
@@ -55,7 +56,9 @@ def validate_fqdn(name, always_tolerant=False):
55
56
  regex = rf"^(\*|{label})(\.{zone_label})+\.?$"
56
57
 
57
58
  if not re.match(regex, name, flags=re.IGNORECASE) or _has_invalid_double_dash(name):
58
- raise ValidationError(f"{name} is not a valid fully qualified DNS host name")
59
+ raise ValidationError(
60
+ _("{name} is not a valid fully qualified DNS host name").format(name=name)
61
+ )
59
62
 
60
63
 
61
64
  def validate_rname(name, always_tolerant=False):
@@ -63,7 +66,7 @@ def validate_rname(name, always_tolerant=False):
63
66
  regex = rf"^(\*|{label})(\\\.{label})*(\.{zone_label}){{2,}}\.?$"
64
67
 
65
68
  if not re.match(regex, name, flags=re.IGNORECASE) or _has_invalid_double_dash(name):
66
- raise ValidationError(f"{name} is not a valid RNAME")
69
+ raise ValidationError(_("{name} is not a valid RName").format(name=name))
67
70
 
68
71
 
69
72
  def validate_generic_name(
@@ -76,7 +79,9 @@ def validate_generic_name(
76
79
  regex = rf"^([*@]|(\*\.)?{label}(\.{zone_label})*\.?)$"
77
80
 
78
81
  if not re.match(regex, name, flags=re.IGNORECASE) or _has_invalid_double_dash(name):
79
- raise ValidationError(f"{name} is not a valid DNS host name")
82
+ raise ValidationError(
83
+ _("{name} is not a valid DNS host name").format(name=name)
84
+ )
80
85
 
81
86
 
82
87
  def validate_domain_name(
@@ -97,4 +102,6 @@ def validate_domain_name(
97
102
  regex = rf"^{label}(\.{zone_label})*\.?$"
98
103
 
99
104
  if not re.match(regex, name, flags=re.IGNORECASE) or _has_invalid_double_dash(name):
100
- raise ValidationError(f"{name} is not a valid DNS domain name")
105
+ raise ValidationError(
106
+ _("{name} is not a valid DNS domain name").format(name=name)
107
+ )
@@ -1,7 +1,11 @@
1
- import dns
1
+ import re
2
+ import textwrap
3
+
2
4
  from dns import rdata, name as dns_name
5
+ from dns.exception import SyntaxError
3
6
 
4
7
  from django.core.exceptions import ValidationError
8
+ from django.utils.translation import gettext as _
5
9
 
6
10
  from netbox_dns.choices import RecordClassChoices, RecordTypeChoices
7
11
  from netbox_dns.validators import (
@@ -10,27 +14,69 @@ from netbox_dns.validators import (
10
14
  validate_generic_name,
11
15
  )
12
16
 
17
+ MAX_TXT_LENGTH = 255
13
18
 
14
19
  __all__ = ("validate_record_value",)
15
20
 
16
21
 
17
- def validate_record_value(record_type, value):
22
+ def validate_record_value(record):
18
23
  def _validate_idn(name):
19
24
  try:
20
25
  name.to_unicode()
21
26
  except dns_name.IDNAException as exc:
22
27
  raise ValidationError(
23
- f"{name.to_text()} is not a valid IDN: {exc}."
24
- ) from None
28
+ "{name} is not a valid IDN: {error}.".format(
29
+ name=name.to_text(), error=exc
30
+ )
31
+ )
32
+
33
+ def _split_text_value(value):
34
+ # +
35
+ # Text values longer than 255 characters need to be broken up for TXT and
36
+ # SPF records.
37
+ # First, in case they had been split into separate strings, reassemble the
38
+ # original (long) value, then split it into chunks of a maximum length of
39
+ # 255 (preferably at word boundaries), and then build a sequence of partial
40
+ # strings enclosed in double quotes and separated by space.
41
+ #
42
+ # See https://datatracker.ietf.org/doc/html/rfc4408#section-3.1.3 for details.
43
+ # -
44
+ raw_value = "".join(re.findall(r'"([^"]+)"', value))
45
+ if not raw_value:
46
+ raw_value = value
47
+
48
+ return " ".join(
49
+ f'"{part}"'
50
+ for part in textwrap.wrap(raw_value, MAX_TXT_LENGTH, drop_whitespace=False)
51
+ )
52
+
53
+ if record.type in (RecordTypeChoices.TXT, RecordTypeChoices.SPF):
54
+ if not (record.value.isascii() and record.value.isprintable()):
55
+ raise ValidationError(
56
+ _(
57
+ "Record value {value} for a type {type} record is not a printable ASCII string."
58
+ ).format(value=record.value, type=record.type)
59
+ )
60
+
61
+ if len(record.value) <= MAX_TXT_LENGTH:
62
+ return
63
+
64
+ try:
65
+ rr = rdata.from_text(RecordClassChoices.IN, record.type, record.value)
66
+ except SyntaxError as exc:
67
+ if str(exc) == "string too long":
68
+ record.value = _split_text_value(record.value)
25
69
 
26
70
  try:
27
- rr = rdata.from_text(RecordClassChoices.IN, record_type, value)
28
- except dns.exception.SyntaxError as exc:
71
+ rr = rdata.from_text(RecordClassChoices.IN, record.type, record.value)
72
+ except SyntaxError as exc:
29
73
  raise ValidationError(
30
- f"Record value {value} is not a valid value for a {record_type} record: {exc}."
31
- ) from None
74
+ _(
75
+ "Record value {value} is not a valid value for a {type} record: {error}."
76
+ ).format(value=record.value, type=record.type, error=exc)
77
+ )
32
78
 
33
- match record_type:
79
+ match record.type:
34
80
  case RecordTypeChoices.CNAME:
35
81
  _validate_idn(rr.target)
36
82
  validate_domain_name(
@@ -1,4 +1,5 @@
1
1
  from django.core.exceptions import ValidationError
2
+ from django.utils.translation import gettext as _
2
3
 
3
4
 
4
5
  __all__ = (
@@ -11,15 +12,17 @@ __all__ = (
11
12
  def validate_prefix(prefix):
12
13
  if prefix.ip != prefix.cidr.ip:
13
14
  raise ValidationError(
14
- f"{prefix} is not a valid prefix. Did you mean {prefix.cidr}?"
15
+ _("{prefix} is not a valid prefix. Did you mean {cidr}?").format(
16
+ prefix=prefix, cidr=prefix.cidr
17
+ )
15
18
  )
16
19
 
17
20
 
18
21
  def validate_ipv4(prefix):
19
22
  if prefix.version != 4:
20
- raise ValidationError("RFC2317 requires an IPv4 prefix.")
23
+ raise ValidationError(_("RFC2317 requires an IPv4 prefix."))
21
24
 
22
25
 
23
26
  def validate_rfc2317(prefix):
24
27
  if prefix.prefixlen <= 24:
25
- raise ValidationError("RFC2317 requires at least 25 bit prefix length.")
28
+ raise ValidationError(_("RFC2317 requires at least 25 bit prefix length."))
@@ -1,5 +1,7 @@
1
1
  from dns import name as dns_name
2
2
 
3
+ from django.utils.translation import gettext_lazy as _
4
+
3
5
  from netbox.views import generic
4
6
  from utilities.views import ViewTab, register_model_view
5
7
  from tenancy.views import ObjectContactsView
@@ -91,7 +93,7 @@ class NameServerZoneListView(generic.ObjectChildrenView):
91
93
  hide_if_empty = True
92
94
 
93
95
  tab = ViewTab(
94
- label="Zones",
96
+ label=_("Zones"),
95
97
  permission="netbox_dns.view_zone",
96
98
  badge=lambda obj: obj.zones.count(),
97
99
  hide_if_empty=True,
@@ -111,7 +113,7 @@ class NameServerSOAZoneListView(generic.ObjectChildrenView):
111
113
  hide_if_empty = True
112
114
 
113
115
  tab = ViewTab(
114
- label="SOA Zones",
116
+ label=_("SOA Zones"),
115
117
  permission="netbox_dns.view_zone",
116
118
  badge=lambda obj: obj.zones_soa.count(),
117
119
  hide_if_empty=True,
@@ -46,9 +46,10 @@ class RecordTemplateView(generic.ObjectView):
46
46
  if instance.value != unicode_value:
47
47
  context["unicode_value"] = unicode_value
48
48
 
49
- context["zone_template_table"] = ZoneTemplateDisplayTable(
50
- data=instance.zone_templates.all()
51
- )
49
+ if instance.zone_templates.exists():
50
+ context["zone_template_table"] = ZoneTemplateDisplayTable(
51
+ data=instance.zone_templates.all()
52
+ )
52
53
 
53
54
  return context
54
55
 
@@ -1,3 +1,5 @@
1
+ from django.utils.translation import gettext as _
2
+
1
3
  from netbox.views import generic
2
4
 
3
5
  from utilities.views import ViewTab, register_model_view
@@ -75,7 +77,7 @@ class RegistrarZoneListView(generic.ObjectChildrenView):
75
77
  hide_if_empty = True
76
78
 
77
79
  tab = ViewTab(
78
- label="Zones",
80
+ label=_("Zones"),
79
81
  permission="netbox_dns.view_zone",
80
82
  badge=lambda obj: obj.zone_set.count(),
81
83
  hide_if_empty=True,