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
@@ -0,0 +1,282 @@
1
+ from django import forms
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+ from netbox.forms import (
5
+ NetBoxModelBulkEditForm,
6
+ NetBoxModelFilterSetForm,
7
+ NetBoxModelImportForm,
8
+ NetBoxModelForm,
9
+ )
10
+ from utilities.forms.fields import TagFilterField
11
+ from utilities.forms.rendering import FieldSet
12
+
13
+ from netbox_dns.models import RegistrationContact
14
+
15
+
16
+ __all__ = (
17
+ "RegistrationContactForm",
18
+ "RegistrationContactFilterForm",
19
+ "RegistrationContactImportForm",
20
+ "RegistrationContactBulkEditForm",
21
+ )
22
+
23
+
24
+ class RegistrationContactForm(NetBoxModelForm):
25
+ fieldsets = (
26
+ FieldSet(
27
+ "name",
28
+ "description",
29
+ "contact_id",
30
+ "organization",
31
+ "street",
32
+ "city",
33
+ "state_province",
34
+ "postal_code",
35
+ "country",
36
+ "phone",
37
+ "phone_ext",
38
+ "fax",
39
+ "fax_ext",
40
+ "email",
41
+ name=_("Contact"),
42
+ ),
43
+ FieldSet(
44
+ "tags",
45
+ name="Tags",
46
+ ),
47
+ )
48
+
49
+ class Meta:
50
+ model = RegistrationContact
51
+
52
+ fields = (
53
+ "name",
54
+ "description",
55
+ "contact_id",
56
+ "organization",
57
+ "street",
58
+ "city",
59
+ "state_province",
60
+ "postal_code",
61
+ "country",
62
+ "phone",
63
+ "phone_ext",
64
+ "fax",
65
+ "fax_ext",
66
+ "email",
67
+ "tags",
68
+ )
69
+
70
+
71
+ class RegistrationContactFilterForm(NetBoxModelFilterSetForm):
72
+ model = RegistrationContact
73
+
74
+ fieldsets = (
75
+ FieldSet(
76
+ "q",
77
+ "filter_id",
78
+ "tag",
79
+ ),
80
+ FieldSet(
81
+ "name",
82
+ "contact_id",
83
+ "description",
84
+ name=_("Attributes"),
85
+ ),
86
+ FieldSet(
87
+ "organization",
88
+ "street",
89
+ "city",
90
+ "state_province",
91
+ "postal_code",
92
+ "country",
93
+ name=_("Address"),
94
+ ),
95
+ FieldSet(
96
+ "phone",
97
+ "phone_ext",
98
+ "fax",
99
+ "fax_ext",
100
+ "email",
101
+ name=_("Communication"),
102
+ ),
103
+ )
104
+
105
+ name = forms.CharField(
106
+ required=False,
107
+ label=_("Name"),
108
+ )
109
+ description = forms.CharField(
110
+ required=False,
111
+ label=_("Description"),
112
+ )
113
+ contact_id = forms.CharField(
114
+ required=False,
115
+ label=_("Contact ID"),
116
+ )
117
+ organization = forms.CharField(
118
+ required=False,
119
+ label=_("Organization"),
120
+ )
121
+ street = forms.CharField(
122
+ required=False,
123
+ label=_("Street"),
124
+ )
125
+ city = forms.CharField(
126
+ required=False,
127
+ label=_("City"),
128
+ )
129
+ state_province = forms.CharField(
130
+ required=False,
131
+ label=_("State/Province"),
132
+ )
133
+ postal_code = forms.CharField(
134
+ required=False,
135
+ label=_("Postal Code"),
136
+ )
137
+ country = forms.CharField(
138
+ required=False,
139
+ label=_("Country"),
140
+ )
141
+ phone = forms.CharField(
142
+ required=False,
143
+ label=_("Phone"),
144
+ )
145
+ phone_ext = forms.CharField(
146
+ required=False,
147
+ label=_("Phone Extension"),
148
+ )
149
+ fax = forms.CharField(
150
+ required=False,
151
+ label=_("Fax"),
152
+ )
153
+ fax_ext = forms.CharField(
154
+ required=False,
155
+ label=_("Fax Extension"),
156
+ )
157
+ email = forms.CharField(
158
+ required=False,
159
+ label=_("Email Address"),
160
+ )
161
+ tag = TagFilterField(RegistrationContact)
162
+
163
+
164
+ class RegistrationContactImportForm(NetBoxModelImportForm):
165
+ class Meta:
166
+ model = RegistrationContact
167
+
168
+ fields = (
169
+ "name",
170
+ "description",
171
+ "contact_id",
172
+ "organization",
173
+ "street",
174
+ "city",
175
+ "state_province",
176
+ "postal_code",
177
+ "country",
178
+ "phone",
179
+ "phone_ext",
180
+ "fax",
181
+ "fax_ext",
182
+ "email",
183
+ "tags",
184
+ )
185
+
186
+
187
+ class RegistrationContactBulkEditForm(NetBoxModelBulkEditForm):
188
+ model = RegistrationContact
189
+
190
+ fieldsets = (
191
+ FieldSet(
192
+ "name",
193
+ "description",
194
+ name=_("Attributes"),
195
+ ),
196
+ FieldSet(
197
+ "organization",
198
+ "street",
199
+ "city",
200
+ "state_province",
201
+ "postal_code",
202
+ "country",
203
+ name=_("Address"),
204
+ ),
205
+ FieldSet(
206
+ "phone",
207
+ "phone_ext",
208
+ "fax",
209
+ "fax_ext",
210
+ "email",
211
+ name=_("Communication"),
212
+ ),
213
+ )
214
+
215
+ nullable_fields = (
216
+ "name",
217
+ "description",
218
+ "organization",
219
+ "street",
220
+ "city",
221
+ "state_province",
222
+ "postal_code",
223
+ "country",
224
+ "phone",
225
+ "phone_ext",
226
+ "fax",
227
+ "fax_ext",
228
+ "email",
229
+ )
230
+
231
+ name = forms.CharField(
232
+ required=False,
233
+ label=_("Name"),
234
+ )
235
+ description = forms.CharField(
236
+ required=False,
237
+ label=_("Description"),
238
+ )
239
+ organization = forms.CharField(
240
+ required=False,
241
+ label=_("Organization"),
242
+ )
243
+ street = forms.CharField(
244
+ required=False,
245
+ label=_("Street"),
246
+ )
247
+ city = forms.CharField(
248
+ required=False,
249
+ label=_("City"),
250
+ )
251
+ state_province = forms.CharField(
252
+ required=False,
253
+ label=_("State/Province"),
254
+ )
255
+ postal_code = forms.CharField(
256
+ required=False,
257
+ label=_("Postal Code"),
258
+ )
259
+ country = forms.CharField(
260
+ required=False,
261
+ label=_("Country"),
262
+ )
263
+ phone = forms.CharField(
264
+ required=False,
265
+ label=_("Phone"),
266
+ )
267
+ phone_ext = forms.CharField(
268
+ required=False,
269
+ label=_("Phone Extension"),
270
+ )
271
+ fax = forms.CharField(
272
+ required=False,
273
+ label=_("Fax"),
274
+ )
275
+ fax_ext = forms.CharField(
276
+ required=False,
277
+ label=_("Fax Extension"),
278
+ )
279
+ email = forms.CharField(
280
+ required=False,
281
+ label=_("Email Address"),
282
+ )
netbox_dns/forms/view.py CHANGED
@@ -1,4 +1,8 @@
1
1
  from django import forms
2
+ from django.conf import settings
3
+ from django.core.exceptions import ValidationError, FieldError
4
+ from django.db.models import Q, Count
5
+ from django.utils.translation import gettext_lazy as _
2
6
 
3
7
  from netbox.forms import (
4
8
  NetBoxModelBulkEditForm,
@@ -9,58 +13,365 @@ from netbox.forms import (
9
13
  from utilities.forms.fields import (
10
14
  TagFilterField,
11
15
  CSVModelChoiceField,
16
+ CSVModelMultipleChoiceField,
12
17
  DynamicModelChoiceField,
18
+ DynamicModelMultipleChoiceField,
13
19
  )
14
- from tenancy.models import Tenant
20
+ from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
21
+ from utilities.forms.rendering import FieldSet
22
+ from utilities.forms.fields import JSONField
23
+ from tenancy.models import Tenant, TenantGroup
15
24
  from tenancy.forms import TenancyForm, TenancyFilterForm
25
+ from ipam.models import Prefix, IPAddress
26
+ from netbox.context import current_request
16
27
 
17
28
  from netbox_dns.models import View
29
+ from netbox_dns.fields import PrefixDynamicModelMultipleChoiceField
30
+ from netbox_dns.utilities import (
31
+ check_dns_records,
32
+ get_ip_addresses_by_prefix,
33
+ get_views_by_prefix,
34
+ get_query_from_filter,
35
+ )
18
36
 
19
37
 
20
- class ViewForm(TenancyForm, NetBoxModelForm):
21
- fieldsets = (
22
- ("View", ("name", "description", "tags")),
23
- ("Tenancy", ("tenant_group", "tenant")),
24
- )
38
+ __all__ = (
39
+ "ViewForm",
40
+ "ViewFilterForm",
41
+ "ViewImportForm",
42
+ "ViewBulkEditForm",
43
+ "ViewPrefixEditForm",
44
+ )
45
+
46
+
47
+ class ViewPrefixUpdateMixin:
48
+ def clean(self, *args, **kwargs):
49
+ super().clean(*args, **kwargs)
50
+
51
+ if self.instance._state.adding or "prefixes" not in self.changed_data:
52
+ return
53
+
54
+ prefixes = self.cleaned_data.get("prefixes")
55
+ old_prefixes = View.objects.get(pk=self.instance.pk).prefixes.all()
56
+
57
+ for prefix in prefixes.difference(old_prefixes):
58
+ for ip_address in get_ip_addresses_by_prefix(prefix, check_view=False):
59
+ try:
60
+ check_dns_records(ip_address, view=self.instance)
61
+ except ValidationError as exc:
62
+ self.add_error("prefixes", exc.messages)
63
+
64
+ # +
65
+ # Determine the prefixes that, when removed from the view, have no direct view
66
+ # assignment left. These prefixes will potentially inherit from a different view,
67
+ # which means that they have to be validated against different zones.
68
+ # -
69
+ check_prefixes = set(
70
+ old_prefixes.annotate(view_count=Count("netbox_dns_views")).filter(
71
+ Q(view_count=1, netbox_dns_views=self.instance)
72
+ | Q(netbox_dns_views__isnull=True)
73
+ )
74
+ ) - set(prefixes)
75
+
76
+ for check_prefix in check_prefixes:
77
+ # +
78
+ # Check whether the prefix will get a new view by inheritance from its
79
+ # parent. If that's the case, the IP addresses need to be checked.
80
+ # -
81
+ if (parent := check_prefix.get_parents().last()) is None:
82
+ continue
25
83
 
84
+ for view in get_views_by_prefix(parent):
85
+ if view == self.instance:
86
+ continue
87
+
88
+ for ip_address in get_ip_addresses_by_prefix(
89
+ check_prefix, check_view=False
90
+ ):
91
+ try:
92
+ check_dns_records(ip_address, view=view)
93
+ except ValidationError as exc:
94
+ self.add_error("prefixes", exc.messages)
95
+
96
+
97
+ class ViewForm(ViewPrefixUpdateMixin, TenancyForm, NetBoxModelForm):
26
98
  class Meta:
27
99
  model = View
28
- fields = ("name", "description", "tags", "tenant")
100
+
101
+ fields = (
102
+ "name",
103
+ "default_view",
104
+ "description",
105
+ "prefixes",
106
+ "ip_address_filter",
107
+ "tenant_group",
108
+ "tenant",
109
+ "tags",
110
+ )
111
+
112
+ fieldsets = (
113
+ FieldSet(
114
+ "name",
115
+ "default_view",
116
+ "description",
117
+ name=_("View"),
118
+ ),
119
+ FieldSet(
120
+ "prefixes",
121
+ "ip_address_filter",
122
+ ),
123
+ FieldSet(
124
+ "tenant_group",
125
+ "tenant",
126
+ name=_("Tenancy"),
127
+ ),
128
+ FieldSet(
129
+ "tags",
130
+ name=_("Tags"),
131
+ ),
132
+ )
133
+
134
+ def __init__(self, *args, **kwargs):
135
+ super().__init__(*args, **kwargs)
136
+
137
+ if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
138
+ del self.fields["prefixes"]
139
+ del self.fields["ip_address_filter"]
140
+
141
+ if request := current_request.get():
142
+ if not request.user.has_perm("ipam.view_prefix"):
143
+ self._saved_prefixes = self.initial["prefixes"]
144
+ self.initial["prefixes"] = []
145
+ self.fields["prefixes"].disabled = True
146
+ self.fields["prefixes"].widget.attrs["placeholder"] = _(
147
+ "You do not have permission to modify assigned prefixes"
148
+ )
149
+
150
+ prefixes = PrefixDynamicModelMultipleChoiceField(
151
+ queryset=Prefix.objects.all(),
152
+ required=False,
153
+ context={
154
+ "depth": None,
155
+ },
156
+ label=_("IPAM Prefixes"),
157
+ )
158
+ ip_address_filter = JSONField(
159
+ required=False,
160
+ help_text=_("Specify criteria for address record creation in JSON form"),
161
+ label=_("IP Address Filter"),
162
+ )
163
+
164
+ def clean_prefixes(self):
165
+ if hasattr(self, "_saved_prefixes"):
166
+ return self._saved_prefixes
167
+
168
+ return self.cleaned_data["prefixes"]
169
+
170
+ def clean_ip_address_filter(self):
171
+ ip_address_filter = self.cleaned_data.get("ip_address_filter")
172
+
173
+ try:
174
+ IPAddress.objects.filter(get_query_from_filter(ip_address_filter)).exists()
175
+ except (FieldError, ValueError) as exc:
176
+ self.add_error(
177
+ "ip_address_filter",
178
+ _("Invalid filter for IPAddress: {error}").format(error=exc),
179
+ )
180
+
181
+ return ip_address_filter
29
182
 
30
183
 
31
184
  class ViewFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
32
185
  model = View
186
+
33
187
  fieldsets = (
34
- (None, ("q", "filter_id", "tag")),
35
- ("Attributes", ("name",)),
36
- ("Tenant", ("tenant_group_id", "tenant_id")),
188
+ FieldSet(
189
+ "q",
190
+ "filter_id",
191
+ "tag",
192
+ ),
193
+ FieldSet(
194
+ "name",
195
+ "default_view",
196
+ "description",
197
+ name=_("Attributes"),
198
+ ),
199
+ FieldSet(
200
+ "prefix_id",
201
+ ),
202
+ FieldSet(
203
+ "tenant_group_id",
204
+ "tenant_id",
205
+ name=_("Tenancy"),
206
+ ),
37
207
  )
38
208
 
209
+ def __init__(self, *args, **kwargs):
210
+ super().__init__(*args, **kwargs)
211
+
212
+ if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
213
+ del self.fields["prefix_id"]
214
+
39
215
  name = forms.CharField(
40
216
  required=False,
41
- label="Name",
217
+ )
218
+ default_view = forms.NullBooleanField(
219
+ required=False,
220
+ widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
221
+ )
222
+ description = forms.CharField(
223
+ required=False,
224
+ )
225
+ prefix_id = PrefixDynamicModelMultipleChoiceField(
226
+ queryset=Prefix.objects.all(),
227
+ required=False,
228
+ context={
229
+ "depth": None,
230
+ },
231
+ label=_("Prefix"),
42
232
  )
43
233
  tag = TagFilterField(View)
44
234
 
45
235
 
46
- class ViewImportForm(NetBoxModelImportForm):
236
+ class ViewImportForm(ViewPrefixUpdateMixin, NetBoxModelImportForm):
237
+ class Meta:
238
+ model = View
239
+
240
+ fields = (
241
+ "name",
242
+ "description",
243
+ "prefixes",
244
+ "tenant",
245
+ "tags",
246
+ )
247
+
248
+ def __init__(self, *args, **kwargs):
249
+ super().__init__(*args, **kwargs)
250
+
251
+ if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
252
+ del self.fields["prefixes"]
253
+
254
+ prefixes = CSVModelMultipleChoiceField(
255
+ queryset=Prefix.objects.all(),
256
+ to_field_name="id",
257
+ required=False,
258
+ help_text=_("Prefix IDs assigned to the view"),
259
+ label=_("Prefixes"),
260
+ )
47
261
  tenant = CSVModelChoiceField(
48
262
  queryset=Tenant.objects.all(),
49
263
  to_field_name="name",
50
264
  required=False,
51
- help_text="Assigned tenant",
265
+ label=_("Tenant"),
52
266
  )
53
267
 
54
- class Meta:
55
- model = View
56
- fields = ("name", "description", "tenant")
57
-
58
268
 
59
269
  class ViewBulkEditForm(NetBoxModelBulkEditForm):
60
270
  model = View
61
271
 
62
- description = forms.CharField(max_length=200, required=False)
63
- tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
272
+ fieldsets = (
273
+ FieldSet(
274
+ "description",
275
+ name=_("Attributes"),
276
+ ),
277
+ FieldSet(
278
+ "tenant_group",
279
+ "tenant",
280
+ name=_("Tenancy"),
281
+ ),
282
+ )
283
+
284
+ nullable_fields = (
285
+ "description",
286
+ "tenant",
287
+ )
288
+
289
+ description = forms.CharField(
290
+ max_length=200,
291
+ required=False,
292
+ label=_("Description"),
293
+ )
294
+ tenant_group = DynamicModelChoiceField(
295
+ queryset=TenantGroup.objects.all(),
296
+ required=False,
297
+ label=_("Tenant Group"),
298
+ )
299
+ tenant = DynamicModelChoiceField(
300
+ queryset=Tenant.objects.all(),
301
+ required=False,
302
+ label=_("Tenant"),
303
+ )
304
+
64
305
 
306
+ class ViewPrefixEditForm(forms.ModelForm):
65
307
  class Meta:
66
- nullable_fields = ("description", "tenant")
308
+ model = Prefix
309
+
310
+ fields = ("views",)
311
+
312
+ def __init__(self, *args, **kwargs):
313
+ super().__init__(*args, **kwargs)
314
+
315
+ self.initial["views"] = self.instance.netbox_dns_views.all()
316
+ self._permission_denied = False
317
+
318
+ if request := current_request.get():
319
+ if not request.user.has_perm("netbox_dns.change_view"):
320
+ self._permission_denied = True
321
+ self.initial["views"] = []
322
+ self.fields["views"].disabled = True
323
+ self.fields["views"].widget.attrs["placeholder"] = _(
324
+ "You do not have permission to modify assigned views"
325
+ )
326
+
327
+ views = DynamicModelMultipleChoiceField(
328
+ queryset=View.objects.all(),
329
+ required=False,
330
+ help_text=_(
331
+ "Explicitly assigning DNS views overrides all inherited views for this prefix"
332
+ ),
333
+ label=_("Assigned DNS Views"),
334
+ )
335
+
336
+ def clean(self, *args, **kwargs):
337
+ if self._permission_denied:
338
+ return
339
+
340
+ prefix = self.instance
341
+
342
+ super().clean(*args, **kwargs)
343
+
344
+ views = self.cleaned_data.get("views")
345
+ old_views = prefix.netbox_dns_views.all()
346
+
347
+ check_views = View.objects.none()
348
+
349
+ if not views.exists():
350
+ if (parent := prefix.get_parents().last()) is not None:
351
+ check_views = parent.netbox_dns_views.difference(old_views)
352
+
353
+ else:
354
+ check_views = views.difference(old_views)
355
+
356
+ for view in check_views:
357
+ try:
358
+ for ip_address in get_ip_addresses_by_prefix(prefix, check_view=False):
359
+ check_dns_records(ip_address, view=view)
360
+ except ValidationError as exc:
361
+ self.add_error("views", exc.messages)
362
+
363
+ def save(self, *args, **kwargs):
364
+ prefix = self.instance
365
+
366
+ if self._permission_denied:
367
+ return prefix
368
+
369
+ old_views = prefix.netbox_dns_views.all()
370
+ views = self.cleaned_data.get("views")
371
+
372
+ for view in views.difference(old_views):
373
+ view.prefixes.add(prefix)
374
+ for view in old_views.difference(views):
375
+ view.prefixes.remove(prefix)
376
+
377
+ return prefix