netbox-plugin-dns 1.2.3__py3-none-any.whl → 1.2.5__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 (30) hide show
  1. netbox_dns/__init__.py +2 -1
  2. netbox_dns/api/serializers_/zone.py +14 -2
  3. netbox_dns/api/serializers_/zone_template.py +9 -0
  4. netbox_dns/filtersets/zone_template.py +14 -0
  5. netbox_dns/forms/record_template.py +4 -0
  6. netbox_dns/forms/zone.py +42 -9
  7. netbox_dns/forms/zone_template.py +54 -2
  8. netbox_dns/graphql/types.py +7 -0
  9. netbox_dns/locale/de/LC_MESSAGES/django.mo +0 -0
  10. netbox_dns/migrations/0013_zonetemplate_soa_mname_zonetemplate_soa_rname.py +30 -0
  11. netbox_dns/migrations/0014_alter_unique_constraints_lowercase.py +42 -0
  12. netbox_dns/models/nameserver.py +20 -2
  13. netbox_dns/models/record.py +20 -10
  14. netbox_dns/models/record_template.py +11 -5
  15. netbox_dns/models/registrar.py +1 -0
  16. netbox_dns/models/registration_contact.py +1 -0
  17. netbox_dns/models/view.py +1 -0
  18. netbox_dns/models/zone.py +54 -21
  19. netbox_dns/models/zone_template.py +44 -10
  20. netbox_dns/tables/zone_template.py +8 -1
  21. netbox_dns/templates/netbox_dns/zonetemplate.html +8 -0
  22. netbox_dns/utilities/conversions.py +5 -0
  23. netbox_dns/utilities/dns.py +1 -2
  24. netbox_dns/utilities/ipam_dnssync.py +5 -2
  25. netbox_dns/views/record.py +17 -7
  26. {netbox_plugin_dns-1.2.3.dist-info → netbox_plugin_dns-1.2.5.dist-info}/METADATA +2 -2
  27. {netbox_plugin_dns-1.2.3.dist-info → netbox_plugin_dns-1.2.5.dist-info}/RECORD +30 -28
  28. {netbox_plugin_dns-1.2.3.dist-info → netbox_plugin_dns-1.2.5.dist-info}/WHEEL +1 -1
  29. {netbox_plugin_dns-1.2.3.dist-info → netbox_plugin_dns-1.2.5.dist-info}/LICENSE +0 -0
  30. {netbox_plugin_dns-1.2.3.dist-info → netbox_plugin_dns-1.2.5.dist-info}/top_level.txt +0 -0
netbox_dns/__init__.py CHANGED
@@ -7,7 +7,7 @@ from ipam.choices import IPAddressStatusChoices
7
7
 
8
8
  from netbox_dns.choices import RecordTypeChoices, RecordStatusChoices, ZoneStatusChoices
9
9
 
10
- __version__ = "1.2.3"
10
+ __version__ = "1.2.5"
11
11
 
12
12
 
13
13
  def _check_list(setting):
@@ -64,6 +64,7 @@ class DNSConfig(PluginConfig):
64
64
  "enforce_unique_rrset_ttl": True,
65
65
  "menu_name": "DNS",
66
66
  "top_level_menu": True,
67
+ "convert_names_to_lowercase": False,
67
68
  }
68
69
  base_url = "netbox-dns"
69
70
 
@@ -44,6 +44,12 @@ class ZoneSerializer(NetBoxModelSerializer):
44
44
  required=False,
45
45
  help_text=_("Primary nameserver for the zone"),
46
46
  )
47
+ soa_rname = serializers.CharField(
48
+ max_length=255,
49
+ read_only=False,
50
+ required=False,
51
+ help_text=_("Contact email for the zone"),
52
+ )
47
53
  rfc2317_parent_zone = NestedZoneSerializer(
48
54
  many=False,
49
55
  read_only=True,
@@ -105,6 +111,12 @@ class ZoneSerializer(NetBoxModelSerializer):
105
111
  )
106
112
  tenant = TenantSerializer(nested=True, required=False, allow_null=True)
107
113
 
114
+ def validate(self, data):
115
+ if (template := data.get("template")) is not None:
116
+ template.apply_to_zone_data(data)
117
+
118
+ return super().validate(data)
119
+
108
120
  def create(self, validated_data):
109
121
  template = validated_data.pop("template", None)
110
122
  nameservers = validated_data.pop("nameservers", None)
@@ -115,7 +127,7 @@ class ZoneSerializer(NetBoxModelSerializer):
115
127
  zone.nameservers.set(nameservers)
116
128
 
117
129
  if template is not None:
118
- template.apply_to_zone(zone)
130
+ template.apply_to_zone_relations(zone)
119
131
 
120
132
  return zone
121
133
 
@@ -129,7 +141,7 @@ class ZoneSerializer(NetBoxModelSerializer):
129
141
  zone.nameservers.set(nameservers)
130
142
 
131
143
  if template is not None:
132
- template.apply_to_zone(zone)
144
+ template.apply_to_zone_relations(zone)
133
145
 
134
146
  return zone
135
147
 
@@ -26,6 +26,13 @@ class ZoneTemplateSerializer(NetBoxModelSerializer):
26
26
  required=False,
27
27
  help_text=_("Nameservers for the zone"),
28
28
  )
29
+ soa_mname = NameServerSerializer(
30
+ nested=True,
31
+ many=False,
32
+ read_only=False,
33
+ required=False,
34
+ help_text=_("Primary nameserver for the zone"),
35
+ )
29
36
  record_templates = NestedRecordTemplateSerializer(
30
37
  many=True,
31
38
  read_only=False,
@@ -108,6 +115,8 @@ class ZoneTemplateSerializer(NetBoxModelSerializer):
108
115
  "name",
109
116
  "display",
110
117
  "nameservers",
118
+ "soa_mname",
119
+ "soa_rname",
111
120
  "description",
112
121
  "tags",
113
122
  "created",
@@ -43,6 +43,18 @@ class ZoneTemplateFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
43
43
  to_field_name="name",
44
44
  label=_("Nameserver"),
45
45
  )
46
+ soa_mname_id = django_filters.ModelMultipleChoiceFilter(
47
+ queryset=NameServer.objects.all(),
48
+ field_name="soa_mname",
49
+ to_field_name="id",
50
+ label=_("SOA MNAME ID"),
51
+ )
52
+ soa_mname = django_filters.ModelMultipleChoiceFilter(
53
+ queryset=NameServer.objects.all(),
54
+ field_name="soa_mname__name",
55
+ to_field_name="name",
56
+ label=_("SOA MNAME"),
57
+ )
46
58
  registrar_id = django_filters.ModelMultipleChoiceFilter(
47
59
  queryset=Registrar.objects.all(),
48
60
  label=_("Registrar ID"),
@@ -100,6 +112,7 @@ class ZoneTemplateFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
100
112
  "id",
101
113
  "name",
102
114
  "description",
115
+ "soa_rname",
103
116
  )
104
117
 
105
118
  def search(self, queryset, name, value):
@@ -107,6 +120,7 @@ class ZoneTemplateFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
107
120
  return queryset
108
121
  qs_filter = (
109
122
  Q(name__icontains=value)
123
+ | Q(soa_rname__icontains=value)
110
124
  | Q(registrar__name__icontains=value)
111
125
  | Q(registry_domain_id__icontains=value)
112
126
  | Q(registrant__name__icontains=value)
@@ -191,6 +191,10 @@ class RecordTemplateImportForm(NetBoxModelImportForm):
191
191
  class RecordTemplateBulkEditForm(NetBoxModelBulkEditForm):
192
192
  model = RecordTemplate
193
193
 
194
+ record_name = forms.CharField(
195
+ required=False,
196
+ label=_("Record Name"),
197
+ )
194
198
  type = forms.ChoiceField(
195
199
  choices=add_blank_choice(RecordSelectableTypeChoices),
196
200
  required=False,
netbox_dns/forms/zone.py CHANGED
@@ -1,3 +1,5 @@
1
+ from packaging.version import Version
2
+
1
3
  from django import forms
2
4
  from django.db import transaction
3
5
  from django.conf import settings
@@ -20,6 +22,7 @@ from utilities.forms.fields import (
20
22
  CSVModelMultipleChoiceField,
21
23
  DynamicModelChoiceField,
22
24
  )
25
+ from utilities.release import load_release_data
23
26
  from utilities.forms.widgets import BulkEditNullBooleanSelect
24
27
  from utilities.forms.rendering import FieldSet
25
28
  from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
@@ -47,6 +50,8 @@ __all__ = (
47
50
  "ZoneBulkEditForm",
48
51
  )
49
52
 
53
+ QUICK_ADD = Version(load_release_data().version) >= Version("4.2.5")
54
+
50
55
 
51
56
  class RollbackTransaction(Exception):
52
57
  pass
@@ -66,13 +71,20 @@ class ZoneTemplateUpdateMixin:
66
71
  self.cleaned_data["tags"] = template.tags.all()
67
72
 
68
73
  for field in template.template_fields:
69
- if (
70
- self.cleaned_data.get(field) is None
71
- and getattr(template, field) is not None
72
- ):
74
+ if self.cleaned_data.get(field) in (None, "") and getattr(
75
+ template, field
76
+ ) not in (None, ""):
73
77
  self.cleaned_data[field] = getattr(template, field)
74
78
 
75
- template_error = None
79
+ if self.cleaned_data.get("soa_mname") is None:
80
+ self.add_error(
81
+ "soa_mname",
82
+ _("soa_mname not set and no template or default value defined"),
83
+ )
84
+
85
+ if self.errors:
86
+ return
87
+
76
88
  saved_events_queue = events_queue.get()
77
89
 
78
90
  try:
@@ -106,13 +118,20 @@ class ZoneTemplateUpdateMixin:
106
118
  raise RollbackTransaction
107
119
 
108
120
  except ValidationError as exc:
109
- self.add_error("template", exc.messages)
121
+ if hasattr(exc, "error_dict"):
122
+ for field_name in self.fields.keys():
123
+ exc.error_dict.pop(field_name, None)
124
+ errors = exc.error_dict.values()
125
+ else:
126
+ errors = exc.messages
127
+
128
+ for error in errors:
129
+ self.add_error("template", error)
130
+
110
131
  except RollbackTransaction:
111
132
  pass
112
133
 
113
134
  events_queue.set(saved_events_queue)
114
- if template_error is not None:
115
- raise ValidationError({"template": template_error})
116
135
 
117
136
  return self.cleaned_data
118
137
 
@@ -126,6 +145,12 @@ class ZoneTemplateUpdateMixin:
126
145
 
127
146
 
128
147
  class ZoneForm(ZoneTemplateUpdateMixin, TenancyForm, NetBoxModelForm):
148
+ view = DynamicModelChoiceField(
149
+ queryset=View.objects.all(),
150
+ required=True,
151
+ label=_("View"),
152
+ quick_add=QUICK_ADD,
153
+ )
129
154
  name = forms.CharField(
130
155
  required=True,
131
156
  label=_("Name"),
@@ -144,6 +169,7 @@ class ZoneForm(ZoneTemplateUpdateMixin, TenancyForm, NetBoxModelForm):
144
169
  queryset=NameServer.objects.all(),
145
170
  required=False,
146
171
  label=_("Nameservers"),
172
+ quick_add=QUICK_ADD,
147
173
  )
148
174
  default_ttl = forms.IntegerField(
149
175
  required=False,
@@ -161,8 +187,15 @@ class ZoneForm(ZoneTemplateUpdateMixin, TenancyForm, NetBoxModelForm):
161
187
  validators=[MinValueValidator(1)],
162
188
  label=_("SOA TTL"),
163
189
  )
190
+ soa_mname = DynamicModelChoiceField(
191
+ queryset=NameServer.objects.all(),
192
+ help_text=_("Primary nameserver this zone"),
193
+ required=False,
194
+ label=_("SOA MName"),
195
+ quick_add=QUICK_ADD,
196
+ )
164
197
  soa_rname = forms.CharField(
165
- required=True,
198
+ required=False,
166
199
  help_text=_("Mailbox of the zone's administrator"),
167
200
  label=_("SOA RName"),
168
201
  )
@@ -1,3 +1,5 @@
1
+ from packaging.version import Version
2
+
1
3
  from django import forms
2
4
  from django.utils.translation import gettext_lazy as _
3
5
 
@@ -7,6 +9,7 @@ from netbox.forms import (
7
9
  NetBoxModelImportForm,
8
10
  NetBoxModelForm,
9
11
  )
12
+ from utilities.release import load_release_data
10
13
  from utilities.forms.fields import (
11
14
  DynamicModelMultipleChoiceField,
12
15
  TagFilterField,
@@ -34,19 +37,30 @@ __all__ = (
34
37
  "ZoneTemplateBulkEditForm",
35
38
  )
36
39
 
40
+ QUICK_ADD = Version(load_release_data().version) >= Version("4.2.5")
41
+
37
42
 
38
43
  class ZoneTemplateForm(TenancyForm, NetBoxModelForm):
39
44
  nameservers = DynamicModelMultipleChoiceField(
40
45
  queryset=NameServer.objects.all(),
41
46
  required=False,
47
+ quick_add=QUICK_ADD,
48
+ )
49
+ soa_mname = DynamicModelChoiceField(
50
+ queryset=NameServer.objects.all(),
51
+ required=False,
52
+ label=_("MName"),
53
+ quick_add=QUICK_ADD,
42
54
  )
43
55
  record_templates = DynamicModelMultipleChoiceField(
44
56
  queryset=RecordTemplate.objects.all(),
45
57
  required=False,
58
+ quick_add=QUICK_ADD,
46
59
  )
47
60
 
48
61
  fieldsets = (
49
62
  FieldSet("name", "description", "nameservers", name=_("Zone Template")),
63
+ FieldSet("soa_mname", "soa_rname", name=_("SOA")),
50
64
  FieldSet("record_templates", name=_("Record Templates")),
51
65
  FieldSet(
52
66
  "registrar",
@@ -66,6 +80,8 @@ class ZoneTemplateForm(TenancyForm, NetBoxModelForm):
66
80
  fields = (
67
81
  "name",
68
82
  "nameservers",
83
+ "soa_mname",
84
+ "soa_rname",
69
85
  "record_templates",
70
86
  "description",
71
87
  "registrar",
@@ -77,6 +93,9 @@ class ZoneTemplateForm(TenancyForm, NetBoxModelForm):
77
93
  "tenant",
78
94
  "tags",
79
95
  )
96
+ labels = {
97
+ "soa_rname": _("RName"),
98
+ }
80
99
 
81
100
 
82
101
  class ZoneTemplateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
@@ -84,6 +103,7 @@ class ZoneTemplateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
84
103
  fieldsets = (
85
104
  FieldSet("q", "filter_id", "tag"),
86
105
  FieldSet("name", "nameserver_id", "description", name=_("Attributes")),
106
+ FieldSet("soa_mname_id", "soa_rname", name=_("SOA")),
87
107
  FieldSet("record_template_id", name=_("Record Templates")),
88
108
  FieldSet(
89
109
  "registrar_id",
@@ -105,6 +125,15 @@ class ZoneTemplateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
105
125
  required=False,
106
126
  label=_("Nameservers"),
107
127
  )
128
+ soa_mname_id = DynamicModelMultipleChoiceField(
129
+ queryset=NameServer.objects.all(),
130
+ required=False,
131
+ label=_("MName"),
132
+ )
133
+ soa_rname = forms.CharField(
134
+ required=False,
135
+ label=_("RName"),
136
+ )
108
137
  record_template_id = DynamicModelMultipleChoiceField(
109
138
  queryset=RecordTemplate.objects.all(),
110
139
  required=False,
@@ -148,11 +177,17 @@ class ZoneTemplateImportForm(NetBoxModelImportForm):
148
177
  required=False,
149
178
  label=_("Nameservers"),
150
179
  )
180
+ soa_mname = CSVModelChoiceField(
181
+ queryset=NameServer.objects.all(),
182
+ to_field_name="name",
183
+ required=False,
184
+ label=_("SOA MName"),
185
+ )
151
186
  record_templates = CSVModelMultipleChoiceField(
152
187
  queryset=RecordTemplate.objects.all(),
153
188
  to_field_name="name",
154
189
  required=False,
155
- label=_("Record Remplates"),
190
+ label=_("Record Templates"),
156
191
  )
157
192
  registrar = CSVModelChoiceField(
158
193
  queryset=Registrar.objects.all(),
@@ -212,6 +247,8 @@ class ZoneTemplateImportForm(NetBoxModelImportForm):
212
247
  fields = (
213
248
  "name",
214
249
  "nameservers",
250
+ "soa_mname",
251
+ "soa_rname",
215
252
  "record_templates",
216
253
  "description",
217
254
  "registrar",
@@ -230,12 +267,20 @@ class ZoneTemplateBulkEditForm(NetBoxModelBulkEditForm):
230
267
  required=False,
231
268
  label=_("Nameservers"),
232
269
  )
270
+ soa_mname = DynamicModelChoiceField(
271
+ queryset=NameServer.objects.all(),
272
+ required=False,
273
+ label=_("MName"),
274
+ )
275
+ soa_rname = forms.CharField(max_length=255, required=False, label=_("RName"))
233
276
  record_templates = DynamicModelMultipleChoiceField(
234
277
  queryset=RecordTemplate.objects.all(),
235
278
  required=False,
236
279
  label=_("Record Templates"),
237
280
  )
238
- description = forms.CharField(max_length=200, required=False)
281
+ description = forms.CharField(
282
+ max_length=200, required=False, label=_("Description")
283
+ )
239
284
  registrar = DynamicModelChoiceField(
240
285
  queryset=Registrar.objects.all(),
241
286
  required=False,
@@ -280,6 +325,11 @@ class ZoneTemplateBulkEditForm(NetBoxModelBulkEditForm):
280
325
  "description",
281
326
  name=_("Attributes"),
282
327
  ),
328
+ FieldSet(
329
+ "soa_mname",
330
+ "soa_rname",
331
+ name=_("SOA"),
332
+ ),
283
333
  FieldSet(
284
334
  "record_templates",
285
335
  name=_("Record Templates"),
@@ -298,6 +348,8 @@ class ZoneTemplateBulkEditForm(NetBoxModelBulkEditForm):
298
348
  nullable_fields = (
299
349
  "description",
300
350
  "nameservers",
351
+ "soa_mname",
352
+ "soa_rname",
301
353
  "record_templates",
302
354
  "registrar",
303
355
  "registrant",
@@ -219,6 +219,13 @@ class NetBoxDNSZoneTemplateType(NetBoxObjectType):
219
219
  "NetBoxDNSNameServerType", strawberry.lazy("netbox_dns.graphql.types")
220
220
  ]
221
221
  ]
222
+ soa_mname: (
223
+ Annotated[
224
+ "NetBoxDNSNameServerType", strawberry.lazy("netbox_dns.graphql.types")
225
+ ]
226
+ | None
227
+ )
228
+ soa_rname: str | None
222
229
  record_templates: List[
223
230
  Annotated[
224
231
  "NetBoxDNSRecordTemplateType", strawberry.lazy("netbox_dns.graphql.types")
Binary file
@@ -0,0 +1,30 @@
1
+ # Generated by Django 5.1.5 on 2025-02-17 11:37
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("netbox_dns", "0012_natural_ordering"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name="zonetemplate",
16
+ name="soa_mname",
17
+ field=models.ForeignKey(
18
+ blank=True,
19
+ null=True,
20
+ on_delete=django.db.models.deletion.PROTECT,
21
+ related_name="+",
22
+ to="netbox_dns.nameserver",
23
+ ),
24
+ ),
25
+ migrations.AddField(
26
+ model_name="zonetemplate",
27
+ name="soa_rname",
28
+ field=models.CharField(blank=True, max_length=255),
29
+ ),
30
+ ]
@@ -0,0 +1,42 @@
1
+ # Generated by Django 5.1.6 on 2025-02-27 19:36
2
+
3
+ import django.db.models.functions.text
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("extras", "0122_charfield_null_choices"),
11
+ ("netbox_dns", "0013_zonetemplate_soa_mname_zonetemplate_soa_rname"),
12
+ ("tenancy", "0017_natural_ordering"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AlterUniqueTogether(
17
+ name="zone",
18
+ unique_together=set(),
19
+ ),
20
+ migrations.AlterField(
21
+ model_name="nameserver",
22
+ name="name",
23
+ field=models.CharField(db_collation="natural_sort", max_length=255),
24
+ ),
25
+ migrations.AddConstraint(
26
+ model_name="nameserver",
27
+ constraint=models.UniqueConstraint(
28
+ django.db.models.functions.text.Lower("name"),
29
+ name="name_unique_ci",
30
+ violation_error_message="There is already a nameserver with this name",
31
+ ),
32
+ ),
33
+ migrations.AddConstraint(
34
+ model_name="zone",
35
+ constraint=models.UniqueConstraint(
36
+ django.db.models.functions.text.Lower("name"),
37
+ models.F("view"),
38
+ name="name_view_unique_ci",
39
+ violation_error_message="There is already a zone with the same name in this view",
40
+ ),
41
+ ),
42
+ ]
@@ -2,13 +2,15 @@ from dns import name as dns_name
2
2
 
3
3
  from django.core.exceptions import ValidationError
4
4
  from django.db import models, transaction
5
- from django.db.models import Q
5
+ from django.db.models import Q, UniqueConstraint
6
+ from django.db.models.functions import Lower
6
7
  from django.urls import reverse
7
8
  from django.utils.translation import gettext_lazy as _
8
9
 
9
10
  from netbox.models import NetBoxModel
10
11
  from netbox.search import SearchIndex, register_search
11
12
  from netbox.models.features import ContactsMixin
13
+ from netbox.plugins.utils import get_plugin_config
12
14
 
13
15
  from netbox_dns.utilities import (
14
16
  name_to_unicode,
@@ -29,7 +31,6 @@ __all__ = (
29
31
  class NameServer(ObjectModificationMixin, ContactsMixin, NetBoxModel):
30
32
  name = models.CharField(
31
33
  verbose_name=_("Name"),
32
- unique=True,
33
34
  max_length=255,
34
35
  db_collation="natural_sort",
35
36
  )
@@ -59,6 +60,16 @@ class NameServer(ObjectModificationMixin, ContactsMixin, NetBoxModel):
59
60
 
60
61
  ordering = ("name",)
61
62
 
63
+ constraints = [
64
+ UniqueConstraint(
65
+ Lower("name"),
66
+ name="name_unique_ci",
67
+ violation_error_message=_(
68
+ "There is already a nameserver with this name"
69
+ ),
70
+ ),
71
+ ]
72
+
62
73
  def __str__(self):
63
74
  try:
64
75
  return dns_name.from_text(self.name, origin=None).to_unicode()
@@ -69,9 +80,16 @@ class NameServer(ObjectModificationMixin, ContactsMixin, NetBoxModel):
69
80
  def display_name(self):
70
81
  return name_to_unicode(self.name)
71
82
 
83
+ # TODO: Remove in version 1.3.0 (NetBox #18555)
72
84
  def get_absolute_url(self):
73
85
  return reverse("plugins:netbox_dns:nameserver", kwargs={"pk": self.pk})
74
86
 
87
+ def clean_fields(self, exclude=None):
88
+ if get_plugin_config("netbox_dns", "convert_names_to_lowercase", False):
89
+ self.name = self.name.lower()
90
+
91
+ super().clean_fields(exclude=exclude)
92
+
75
93
  def clean(self, *args, **kwargs):
76
94
  try:
77
95
  self.name = normalize_name(self.name)
@@ -263,6 +263,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
263
263
  def get_status_color(self):
264
264
  return RecordStatusChoices.colors.get(self.status)
265
265
 
266
+ # TODO: Remove in version 1.3.0 (NetBox #18555)
266
267
  def get_absolute_url(self):
267
268
  return reverse("plugins:netbox_dns:record", kwargs={"pk": self.pk})
268
269
 
@@ -370,9 +371,11 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
370
371
  if ptr_zone.is_rfc2317_zone:
371
372
  ptr_name = self.rfc2317_ptr_name
372
373
  else:
373
- ptr_name = dns_name.from_text(
374
- ipaddress.ip_address(self.value).reverse_pointer
375
- ).relativize(dns_name.from_text(ptr_zone.name))
374
+ ptr_name = (
375
+ dns_name.from_text(ipaddress.ip_address(self.value).reverse_pointer)
376
+ .relativize(dns_name.from_text(ptr_zone.name))
377
+ .to_text()
378
+ )
376
379
 
377
380
  ptr_value = self.fqdn
378
381
  ptr_record = self.ptr_record
@@ -439,12 +442,16 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
439
442
 
440
443
  def update_rfc2317_cname_record(self, save_zone_serial=True):
441
444
  if self.zone.rfc2317_parent_managed:
442
- cname_name = dns_name.from_text(
443
- ipaddress.ip_address(self.ip_address).reverse_pointer
444
- ).relativize(dns_name.from_text(self.zone.rfc2317_parent_zone.name))
445
+ cname_name = (
446
+ dns_name.from_text(
447
+ ipaddress.ip_address(self.ip_address).reverse_pointer
448
+ )
449
+ .relativize(dns_name.from_text(self.zone.rfc2317_parent_zone.name))
450
+ .to_text()
451
+ )
445
452
 
446
453
  if self.rfc2317_cname_record is not None:
447
- if self.rfc2317_cname_record.name == cname_name.to_text():
454
+ if self.rfc2317_cname_record.name == cname_name:
448
455
  self.rfc2317_cname_record.zone = self.zone.rfc2317_parent_zone
449
456
  self.rfc2317_cname_record.value = self.fqdn
450
457
  self.rfc2317_cname_record.ttl = min_ttl(
@@ -612,7 +619,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
612
619
  new_zone = self.zone
613
620
 
614
621
  records = new_zone.records.filter(
615
- name=self.name,
622
+ name__iexact=self.name,
616
623
  type=self.type,
617
624
  value=self.value,
618
625
  status__in=RECORD_ACTIVE_STATUS_LIST,
@@ -769,9 +776,12 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
769
776
  record.ttl = ttl
770
777
  record.save(update_fields=["ttl"], update_rrset_ttl=False)
771
778
 
772
- def clean_fields(self, *args, **kwargs):
779
+ def clean_fields(self, exclude=None):
773
780
  self.type = self.type.upper()
774
- super().clean_fields(*args, **kwargs)
781
+ if get_plugin_config("netbox_dns", "convert_names_to_lowercase", False):
782
+ self.name = self.name.lower()
783
+
784
+ super().clean_fields(exclude=exclude)
775
785
 
776
786
  def clean(self, *args, new_zone=None, **kwargs):
777
787
  self.validate_name(new_zone=new_zone)
@@ -104,6 +104,7 @@ class RecordTemplate(NetBoxModel):
104
104
  def get_status_color(self):
105
105
  return RecordStatusChoices.colors.get(self.status)
106
106
 
107
+ # TODO: Remove in version 1.3.0 (NetBox #18555)
107
108
  def get_absolute_url(self):
108
109
  return reverse("plugins:netbox_dns:recordtemplate", kwargs={"pk": self.pk})
109
110
 
@@ -163,17 +164,22 @@ class RecordTemplate(NetBoxModel):
163
164
  record = Record.objects.create(**record_data)
164
165
  except ValidationError as exc:
165
166
  raise ValidationError(
166
- _("Error while processing record template {template}: {error}").format(
167
- template=self, error=exc.messages[0]
168
- )
167
+ {
168
+ None: _(
169
+ "Error while processing record template {template}: {error}"
170
+ ).format(template=self, error=exc.messages[0])
171
+ }
169
172
  )
170
173
 
171
174
  if tags := self.tags.all():
172
175
  record.tags.set(tags)
173
176
 
174
- def clean_fields(self, *args, **kwargs):
177
+ def clean_fields(self, exclude=None):
175
178
  self.type = self.type.upper()
176
- super().clean_fields(*args, **kwargs)
179
+ if get_plugin_config("netbox_dns", "convert_names_to_lowercase", False):
180
+ self.record_name = self.record_name.lower()
181
+
182
+ super().clean_fields(exclude=exclude)
177
183
 
178
184
  def clean(self, *args, **kwargs):
179
185
  self.validate_name()
@@ -57,6 +57,7 @@ class Registrar(NetBoxModel):
57
57
  blank=True,
58
58
  )
59
59
 
60
+ # TODO: Remove in version 1.3.0 (NetBox #18555)
60
61
  def get_absolute_url(self):
61
62
  return reverse("plugins:netbox_dns:registrar", kwargs={"pk": self.pk})
62
63
 
@@ -118,6 +118,7 @@ class RegistrationContact(NetBoxModel):
118
118
  "tags",
119
119
  )
120
120
 
121
+ # TODO: Remove in version 1.3.0 (NetBox #18555)
121
122
  def get_absolute_url(self):
122
123
  return reverse("plugins:netbox_dns:registrationcontact", kwargs={"pk": self.pk})
123
124
 
netbox_dns/models/view.py CHANGED
@@ -71,6 +71,7 @@ class View(ObjectModificationMixin, ContactsMixin, NetBoxModel):
71
71
  def get_default_view(cls):
72
72
  return cls.objects.get(default_view=True)
73
73
 
74
+ # TODO: Remove in version 1.3.0 (NetBox #18555)
74
75
  def get_absolute_url(self):
75
76
  return reverse("plugins:netbox_dns:view", kwargs={"pk": self.pk})
76
77
 
netbox_dns/models/zone.py CHANGED
@@ -11,8 +11,8 @@ from django.core.validators import (
11
11
  )
12
12
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
13
13
  from django.db import models, transaction
14
- from django.db.models import Q, Max, ExpressionWrapper, BooleanField
15
- from django.db.models.functions import Length
14
+ from django.db.models import Q, Max, ExpressionWrapper, BooleanField, UniqueConstraint
15
+ from django.db.models.functions import Length, Lower
16
16
  from django.db.models.signals import m2m_changed
17
17
  from django.urls import reverse
18
18
  from django.dispatch import receiver
@@ -36,6 +36,7 @@ from netbox_dns.utilities import (
36
36
  name_to_unicode,
37
37
  normalize_name,
38
38
  get_parent_zone_names,
39
+ regex_from_list,
39
40
  NameFormatError,
40
41
  )
41
42
  from netbox_dns.validators import (
@@ -282,10 +283,16 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
282
283
  "view",
283
284
  "name",
284
285
  )
285
- unique_together = (
286
- "view",
287
- "name",
288
- )
286
+ constraints = [
287
+ UniqueConstraint(
288
+ Lower("name"),
289
+ "view",
290
+ name="name_view_unique_ci",
291
+ violation_error_message=_(
292
+ "There is already a zone with the same name in this view"
293
+ ),
294
+ ),
295
+ ]
289
296
 
290
297
  def __str__(self):
291
298
  if self.name == "." and get_plugin_config("netbox_dns", "enable_root_zones"):
@@ -350,6 +357,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
350
357
  def get_status_color(self):
351
358
  return ZoneStatusChoices.colors.get(self.status)
352
359
 
360
+ # TODO: Remove in version 1.3.0 (NetBox #18555)
353
361
  def get_absolute_url(self):
354
362
  return reverse("plugins:netbox_dns:zone", kwargs={"pk": self.pk})
355
363
 
@@ -398,12 +406,14 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
398
406
 
399
407
  @property
400
408
  def descendant_zones(self):
401
- return self.view.zones.filter(name__endswith=f".{self.name}")
409
+ return self.view.zones.filter(name__iendswith=f".{self.name}")
402
410
 
403
411
  @property
404
412
  def parent_zone(self):
405
413
  try:
406
- return self.view.zones.get(name=get_parent_zone_names(self.name)[-1])
414
+ return self.view.zones.get(
415
+ name__iexact=get_parent_zone_names(self.name)[-1]
416
+ )
407
417
  except (Zone.DoesNotExist, IndexError):
408
418
  return None
409
419
 
@@ -411,20 +421,24 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
411
421
  def ancestor_zones(self):
412
422
  return (
413
423
  self.view.zones.annotate(name_length=Length("name"))
414
- .filter(name__in=get_parent_zone_names(self.name))
424
+ .filter(name__iregex=regex_from_list(get_parent_zone_names(self.name)))
415
425
  .order_by("name_length")
416
426
  )
417
427
 
418
428
  @property
419
429
  def delegation_records(self):
420
430
  descendant_zone_names = [
421
- f"{name}." for name in self.descendant_zones.values_list("name", flat=True)
431
+ rf"{name}."
432
+ for name in (
433
+ name.lower()
434
+ for name in self.descendant_zones.values_list("name", flat=True)
435
+ )
422
436
  ]
423
437
 
424
438
  ns_records = (
425
439
  self.records.filter(type=RecordTypeChoices.NS)
426
- .exclude(fqdn=self.fqdn)
427
- .filter(fqdn__in=descendant_zone_names)
440
+ .exclude(fqdn__iexact=self.fqdn)
441
+ .filter(fqdn__iregex=regex_from_list(descendant_zone_names))
428
442
  )
429
443
  ns_values = [record.value_fqdn for record in ns_records]
430
444
 
@@ -662,6 +676,9 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
662
676
  def clean_fields(self, exclude=None):
663
677
  defaults = settings.PLUGINS_CONFIG.get("netbox_dns")
664
678
 
679
+ if get_plugin_config("netbox_dns", "convert_names_to_lowercase", False):
680
+ self.name = self.name.lower()
681
+
665
682
  if self.view_id is None:
666
683
  self.view_id = View.get_default_view().pk
667
684
 
@@ -670,15 +687,25 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
670
687
  if value not in (None, ""):
671
688
  setattr(self, field, value)
672
689
 
673
- if self.soa_mname_id is None:
674
- default_soa_mname = defaults.get("zone_soa_mname")
675
- try:
676
- self.soa_mname = NameServer.objects.get(name=default_soa_mname)
677
- except NameServer.DoesNotExist:
678
- raise ValidationError(
679
- _("Default soa_mname instance {nameserver} does not exist").format(
680
- nameserver=default_soa_mname
690
+ if self.soa_mname_id is None and "soa_mname" not in exclude:
691
+ if default_soa_mname := defaults.get("zone_soa_mname"):
692
+ try:
693
+ self.soa_mname = NameServer.objects.get(name=default_soa_mname)
694
+ except NameServer.DoesNotExist:
695
+ raise ValidationError(
696
+ {
697
+ "soa_mname": _(
698
+ "Default soa_mname instance {nameserver} does not exist"
699
+ ).format(nameserver=default_soa_mname)
700
+ }
681
701
  )
702
+ else:
703
+ raise ValidationError(
704
+ {
705
+ "soa_mname": _(
706
+ "soa_mname not set and no template or default value defined"
707
+ )
708
+ }
682
709
  )
683
710
 
684
711
  super().clean_fields(exclude=exclude)
@@ -706,7 +733,13 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
706
733
  )
707
734
 
708
735
  if self.soa_rname in (None, ""):
709
- raise ValidationError(_("soa_rname not set and no default value defined"))
736
+ raise ValidationError(
737
+ {
738
+ "soa_rname": _(
739
+ "soa_rname not set and no template or default value defined"
740
+ ),
741
+ }
742
+ )
710
743
  try:
711
744
  dns_name.from_text(self.soa_rname, origin=dns_name.root)
712
745
  validate_rname(self.soa_rname)
@@ -1,10 +1,16 @@
1
+ from dns import name as dns_name
2
+ from dns.exception import DNSException
3
+
1
4
  from django.db import models
2
5
  from django.urls import reverse
3
6
  from django.utils.translation import gettext_lazy as _
7
+ from django.core.exceptions import ValidationError
4
8
 
5
9
  from netbox.models import NetBoxModel
6
10
  from netbox.search import SearchIndex, register_search
7
11
 
12
+ from netbox_dns.validators import validate_rname
13
+
8
14
 
9
15
  __all__ = (
10
16
  "ZoneTemplate",
@@ -30,6 +36,19 @@ class ZoneTemplate(NetBoxModel):
30
36
  related_name="+",
31
37
  blank=True,
32
38
  )
39
+ soa_mname = models.ForeignKey(
40
+ verbose_name=_("SOA MName"),
41
+ to="NameServer",
42
+ related_name="+",
43
+ on_delete=models.PROTECT,
44
+ blank=True,
45
+ null=True,
46
+ )
47
+ soa_rname = models.CharField(
48
+ verbose_name=_("SOA RName"),
49
+ max_length=255,
50
+ blank=True,
51
+ )
33
52
  record_templates = models.ManyToManyField(
34
53
  verbose_name=_("Record Templates"),
35
54
  to="RecordTemplate",
@@ -98,6 +117,8 @@ class ZoneTemplate(NetBoxModel):
98
117
  )
99
118
 
100
119
  template_fields = (
120
+ "soa_mname",
121
+ "soa_rname",
101
122
  "tenant",
102
123
  "registrar",
103
124
  "registrant",
@@ -115,31 +136,44 @@ class ZoneTemplate(NetBoxModel):
115
136
  def __str__(self):
116
137
  return str(self.name)
117
138
 
139
+ # TODO: Remove in version 1.3.0 (NetBox #18555)
118
140
  def get_absolute_url(self):
119
141
  return reverse("plugins:netbox_dns:zonetemplate", kwargs={"pk": self.pk})
120
142
 
121
- def apply_to_zone(self, zone):
143
+ def apply_to_zone_data(self, data):
144
+ fields_changed = False
145
+ for field in self.template_fields:
146
+ if data.get(field) in (None, "") and getattr(self, field) not in (None, ""):
147
+ fields_changed = True
148
+ data[field] = getattr(self, field)
149
+
150
+ return fields_changed
151
+
152
+ def apply_to_zone_relations(self, zone):
122
153
  if not zone.nameservers.all() and self.nameservers.all():
123
154
  zone.nameservers.set(self.nameservers.all())
124
155
 
125
156
  if not zone.tags.all() and self.tags.all():
126
157
  zone.tags.set(self.tags.all())
127
158
 
128
- fields_changed = True
129
- for field in self.template_fields:
130
- if getattr(zone, field) is None and getattr(self, field) is not None:
131
- fields_changed = True
132
- setattr(zone, field, getattr(self, field))
133
-
134
- if fields_changed:
135
- zone.save()
136
-
137
159
  self.create_records(zone)
138
160
 
139
161
  def create_records(self, zone):
140
162
  for record_template in self.record_templates.all():
141
163
  record_template.create_record(zone=zone)
142
164
 
165
+ def clean(self, *args, **kwargs):
166
+ if self.soa_rname:
167
+ try:
168
+ dns_name.from_text(self.soa_rname, origin=dns_name.root)
169
+ validate_rname(self.soa_rname)
170
+ except (DNSException, ValidationError) as exc:
171
+ raise ValidationError(
172
+ {
173
+ "soa_rname": exc,
174
+ }
175
+ )
176
+
143
177
 
144
178
  @register_search
145
179
  class ZoneTemplateIndex(SearchIndex):
@@ -18,6 +18,10 @@ class ZoneTemplateTable(TenancyColumnsMixin, NetBoxTable):
18
18
  verbose_name=_("Name"),
19
19
  linkify=True,
20
20
  )
21
+ soa_mname = tables.Column(
22
+ verbose_name=_("SOA MName"),
23
+ linkify=True,
24
+ )
21
25
  tags = TagColumn(
22
26
  url_name="plugins:netbox_dns:zonetemplate_list",
23
27
  )
@@ -44,7 +48,10 @@ class ZoneTemplateTable(TenancyColumnsMixin, NetBoxTable):
44
48
 
45
49
  class Meta(NetBoxTable.Meta):
46
50
  model = ZoneTemplate
47
- fields = ("description",)
51
+ fields = (
52
+ "soa_rname",
53
+ "description",
54
+ )
48
55
  default_columns = (
49
56
  "name",
50
57
  "tags",
@@ -42,6 +42,14 @@
42
42
  </table>
43
43
  </td>
44
44
  </tr>
45
+ <tr>
46
+ <th scope="row">{% trans "SOA MName" %}</th>
47
+ <td>{{ object.soa_mname|linkify }}</td>
48
+ </tr>
49
+ <tr>
50
+ <th scope="row">{% trans "SOA RName" %}</th>
51
+ <td>{{ object.soa_rname }}</td>
52
+ </tr>
45
53
  </table>
46
54
  </div>
47
55
 
@@ -14,6 +14,7 @@ __all__ = (
14
14
  "value_to_unicode",
15
15
  "normalize_name",
16
16
  "network_to_reverse",
17
+ "regex_from_list",
17
18
  )
18
19
 
19
20
 
@@ -106,3 +107,7 @@ def network_to_reverse(network):
106
107
 
107
108
  if labels:
108
109
  return ".".join(ip_network[0].reverse_dns.split(".")[-labels:])
110
+
111
+
112
+ def regex_from_list(names):
113
+ return f"^({'|'.join(re.escape(name) for name in names)})$"
@@ -1,11 +1,10 @@
1
1
  from dns import name as dns_name
2
2
 
3
-
4
3
  __all__ = ("get_parent_zone_names",)
5
4
 
6
5
 
7
6
  def get_parent_zone_names(name, min_labels=1, include_self=False):
8
- fqdn = dns_name.from_text(name)
7
+ fqdn = dns_name.from_text(name.lower())
9
8
  return [
10
9
  fqdn.split(i)[1].to_text().rstrip(".")
11
10
  for i in range(min_labels + 1, len(fqdn.labels) + include_self)
@@ -13,6 +13,7 @@ from ipam.models import IPAddress, Prefix
13
13
  from netbox_dns.choices import RecordStatusChoices
14
14
 
15
15
  from .dns import get_parent_zone_names
16
+ from .conversions import regex_from_list
16
17
 
17
18
 
18
19
  __all__ = (
@@ -90,8 +91,10 @@ def get_zones(ip_address, view=None, old_zone=None):
90
91
 
91
92
  zones = Zone.objects.filter(
92
93
  view__in=views,
93
- name__in=get_parent_zone_names(
94
- ip_address.dns_name, min_labels=min_labels, include_self=True
94
+ name__iregex=regex_from_list(
95
+ get_parent_zone_names(
96
+ ip_address.dns_name, min_labels=min_labels, include_self=True
97
+ )
95
98
  ),
96
99
  active=True,
97
100
  )
@@ -16,7 +16,11 @@ from netbox_dns.forms import (
16
16
  from netbox_dns.models import Record, Zone
17
17
  from netbox_dns.choices import RecordTypeChoices
18
18
  from netbox_dns.tables import RecordTable, ManagedRecordTable, RelatedRecordTable
19
- from netbox_dns.utilities import value_to_unicode, get_parent_zone_names
19
+ from netbox_dns.utilities import (
20
+ value_to_unicode,
21
+ get_parent_zone_names,
22
+ regex_from_list,
23
+ )
20
24
 
21
25
 
22
26
  __all__ = (
@@ -74,7 +78,9 @@ class RecordView(generic.ObjectView):
74
78
  )
75
79
 
76
80
  if instance.zone.view.zones.filter(
77
- name__in=get_parent_zone_names(instance.value_fqdn, min_labels=1),
81
+ name__iregex=regex_from_list(
82
+ get_parent_zone_names(instance.value_fqdn, min_labels=1)
83
+ ),
78
84
  active=True,
79
85
  ).exists():
80
86
  raise (
@@ -97,7 +103,9 @@ class RecordView(generic.ObjectView):
97
103
  )
98
104
 
99
105
  parent_zones = instance.zone.view.zones.filter(
100
- name__in=get_parent_zone_names(instance.fqdn, include_self=True),
106
+ name__iregex=regex_from_list(
107
+ get_parent_zone_names(instance.fqdn, include_self=True)
108
+ ),
101
109
  )
102
110
 
103
111
  for parent_zone in parent_zones:
@@ -148,10 +156,12 @@ class RecordView(generic.ObjectView):
148
156
 
149
157
  if Zone.objects.filter(
150
158
  active=True,
151
- name__in=get_parent_zone_names(
152
- instance.fqdn,
153
- min_labels=len(fqdn) - len(name),
154
- include_self=True,
159
+ name__iregex=regex_from_list(
160
+ get_parent_zone_names(
161
+ instance.fqdn,
162
+ min_labels=len(fqdn) - len(name),
163
+ include_self=True,
164
+ )
155
165
  ),
156
166
  ).exists():
157
167
  context["mask_warning"] = _(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: netbox-plugin-dns
3
- Version: 1.2.3
3
+ Version: 1.2.5
4
4
  Summary: NetBox DNS is a NetBox plugin for managing DNS data.
5
5
  Author-email: Peter Eckel <pete@netbox-dns.org>
6
6
  Project-URL: Homepage, https://github.com/peteeckel/netbox-plugin-dns
@@ -99,7 +99,7 @@ PLUGINS = [
99
99
  ]
100
100
  ```
101
101
 
102
- To permanently keep the plugin installed when updating NetBox via `update.sh`:
102
+ To permanently keep the plugin installed when updating NetBox via `upgrade.sh`:
103
103
 
104
104
  ```
105
105
  echo netbox-plugin-dns >> ~/netbox/local_requirements.txt
@@ -1,4 +1,4 @@
1
- netbox_dns/__init__.py,sha256=SxQkUEgRHEb5inend06jFceNW7xYaqbIBvrnxPCD9hA,3053
1
+ netbox_dns/__init__.py,sha256=nnD4iq2_TUmMshut8Z3viAIlrY0RSc9YH6fllV11R4E,3098
2
2
  netbox_dns/apps.py,sha256=JCW5eS-AQBUubDJve1DjP-IRFKTFGQh1NLGWzJpC5MI,151
3
3
  netbox_dns/navigation.py,sha256=36clAzlWftW94_VZ3EHu8_btzzA_dah50CLTfoov-O4,6226
4
4
  netbox_dns/template_content.py,sha256=T06L7-m4eGrLMeGsCvPpQLAGfn3S2FL7z0Cd1hhbisY,4225
@@ -15,8 +15,8 @@ netbox_dns/api/serializers_/record_template.py,sha256=WAHua_O7v8IB7QL_hOPWjItMtA
15
15
  netbox_dns/api/serializers_/registrar.py,sha256=xLIaeBJ5ckV1Jf-uyCTFcvsLlsRMlpDtIg6q79vXZic,842
16
16
  netbox_dns/api/serializers_/registration_contact.py,sha256=3IGWW5xB9XEBGApCGZCZIxpCmy1Y5jQUbA4GzmtaCik,1024
17
17
  netbox_dns/api/serializers_/view.py,sha256=nnWeQugoqMdn-NGGC7ykbVPwmBrcBma_ZKwdDxUgJ24,1809
18
- netbox_dns/api/serializers_/zone.py,sha256=ELAis8nj9PZlge6zqTQ23P0vC4IJBDa3hh5kZ9T8G_4,5003
19
- netbox_dns/api/serializers_/zone_template.py,sha256=w0TsrqS_DgIIAUozCC-gc9lsQ67lpVkvbyphyuRzq6Q,3847
18
+ netbox_dns/api/serializers_/zone.py,sha256=CctZ4aABInB7taj5MvWIF8KKAA6qsc0d1UyBPHdoOTc,5367
19
+ netbox_dns/api/serializers_/zone_template.py,sha256=NQFycXBNcHSMPID62o6w6EKbPkj_xCfNhXCUiwt2xic,4087
20
20
  netbox_dns/choices/__init__.py,sha256=jOVs2VGV5SVADRlqVnrFeAy26i8BIeEAbGpiX7K8bL8,42
21
21
  netbox_dns/choices/record.py,sha256=ZSpyiZE2YCsF2wF53A5DFWgwCIhkFhgOKt__RJ0KxSk,2084
22
22
  netbox_dns/choices/zone.py,sha256=Vblm5RUtNtPNkULh8U1NxBMme1iHPllD6B6LkQkWZW4,621
@@ -33,21 +33,21 @@ netbox_dns/filtersets/registrar.py,sha256=Wh_l-IXRHnJhW7Pyokp3czQZISDKzXnWeSQKp5
33
33
  netbox_dns/filtersets/registration_contact.py,sha256=903sOcHPRCI0dVzqn1i0pn5VPr_4YpHPh5QE2-akR-Y,1139
34
34
  netbox_dns/filtersets/view.py,sha256=IlQz3k2J_N6eSbT9op0KOu3sKLrn-HTsJCcrIqoYgyY,1047
35
35
  netbox_dns/filtersets/zone.py,sha256=zl39SOiYIZxAi3G1wx0s9UEIgh8hG9Bdb46qIXLwMr8,6334
36
- netbox_dns/filtersets/zone_template.py,sha256=Sm40P33IhN0sOqtjz4JzoBbEK-dTLpfQqYGcM_Xb7KM,3870
36
+ netbox_dns/filtersets/zone_template.py,sha256=So-sxWeDhlm-DTtujYp5B_gDbnAVUHnLdRZgw7cOc4o,4347
37
37
  netbox_dns/forms/__init__.py,sha256=axENVF9vX9BtDKCNxrapRjye1NnygUg9BS0BBj6a0io,209
38
38
  netbox_dns/forms/nameserver.py,sha256=GJe3ece4yIGwMtLZ6wQihBrJu1dk_ZSiwX-vSU0fRa0,3397
39
39
  netbox_dns/forms/record.py,sha256=QNGLqWprhsGFTSlH2YAe-SHmCx1K1QbT_osAhCegyJg,8252
40
- netbox_dns/forms/record_template.py,sha256=uN6ZSepNilQuqyfPpW-pMfmTRWo0IrDxp1LdOrlAo5A,6240
40
+ netbox_dns/forms/record_template.py,sha256=UcB-AlK-ZDoNmIMJhUrxfr76oGkwJ8d7JhkDj9vbMDI,6337
41
41
  netbox_dns/forms/registrar.py,sha256=GaRH3w5zlhrpwy_U0pxlrl1DrAEaMB78MUlnGxBRwZI,3949
42
42
  netbox_dns/forms/registration_contact.py,sha256=IhNAqElY7hOdpDG0jwWMdy3y2mB43xmjUhj3lsgJ3SE,5906
43
43
  netbox_dns/forms/view.py,sha256=GacwKHXSDvxQEs-d3ys7rietqA_MzpSd0XjWaSsIbU0,10339
44
- netbox_dns/forms/zone.py,sha256=FEKDdlyK_dmaVNbqU6aTrqaHRv5DtDmTOtTacPLRopg,25222
45
- netbox_dns/forms/zone_template.py,sha256=49vhM-Lc4JAGZD-al4QpPDLfwmpu82JNuX-bxpwabmc,8609
44
+ netbox_dns/forms/zone.py,sha256=b63kCukS4uFgkxGnQ_h-i8d-d8GaSINfppDJlrBuXJA,26195
45
+ netbox_dns/forms/zone_template.py,sha256=P7jdEz0MI_tjD_fuVDuKOIFCInqGI4opf7l_qaDmG1g,10098
46
46
  netbox_dns/graphql/__init__.py,sha256=jghYD6uOSAis6YyLbtI3YJGZfwPw1uL2FBRsHs1EhNk,514
47
47
  netbox_dns/graphql/filters.py,sha256=fHCjFIwbPBJJMk2W7HI8LhrfFhCtQtCM9IE8ZMgVafc,1766
48
48
  netbox_dns/graphql/schema.py,sha256=q9DQ_hfRB0e6Znq4-IS6UEeTOfMkZmrWkwxcAql1uOA,2270
49
- netbox_dns/graphql/types.py,sha256=8DjYxWOfjmS5HFW-eaQLl9E12pbg2xlWNwV5CH6ptdY,8092
50
- netbox_dns/locale/de/LC_MESSAGES/django.mo,sha256=0ij8AzrkWdwtUejXTOTdJJcIRweZfQT3iWjAXrf6hyM,20335
49
+ netbox_dns/graphql/types.py,sha256=zAH8bkMCQdp9_g8HzBdrxSS0spxLwvqHhMA61kp65gk,8268
50
+ netbox_dns/locale/de/LC_MESSAGES/django.mo,sha256=L0qwlTBiL4M5IRoN33eejQRgzP11oinumTdGzrsfKEA,20148
51
51
  netbox_dns/locale/en/LC_MESSAGES/django.mo,sha256=GDnSZkfHs3yjtTsll7dksEEej4B50F8pc9RGytZNubM,393
52
52
  netbox_dns/management/commands/cleanup_database.py,sha256=1-tAl0Sht80qaNZyfFyUW19Eh9gBUuc7GdbHN4aemGU,5935
53
53
  netbox_dns/management/commands/cleanup_rrset_ttl.py,sha256=UFRURLBcFeGHUS2lrYFv7UWIebjI72aG1EUQJt0XsXw,2046
@@ -67,6 +67,8 @@ netbox_dns/migrations/0009_rename_contact_registrationcontact.py,sha256=-8IknnaI
67
67
  netbox_dns/migrations/0010_view_ip_address_filter.py,sha256=uARQADJB7u1vpx0TBlOfGTkqMCF4xZclMhITESHm-ok,420
68
68
  netbox_dns/migrations/0011_rename_related_fields.py,sha256=j9lI-QBmTSzOrAxDl02SdgHZtv9nRfJ3cZX_wjj5urM,1881
69
69
  netbox_dns/migrations/0012_natural_ordering.py,sha256=h5XVSmRwisUqz5OJzkBW41dwHIBlu08zqG2-1mxiiw4,2725
70
+ netbox_dns/migrations/0013_zonetemplate_soa_mname_zonetemplate_soa_rname.py,sha256=Y6TdD_dUZ-Pb1kuRR3l3kSwObn_Cpcmp3tm75qSkc5g,795
71
+ netbox_dns/migrations/0014_alter_unique_constraints_lowercase.py,sha256=Ueesv7uoB2ZQ1-7kG_qsMlPv0mn3mdDeI8OoAKIschM,1409
70
72
  netbox_dns/migrations/0020_netbox_3_4.py,sha256=UMcHdn8ZAuQjUaM_3rEGpktYrM0TuvhccD7Jt7WQnPs,1271
71
73
  netbox_dns/migrations/0021_record_ip_address.py,sha256=EqdhWXmq7aiK4X79xTRUZng3zFncCl-8JoO65HqlJKw,3244
72
74
  netbox_dns/migrations/0022_search.py,sha256=KW1ffEZ4-0dppGQ_KD1EN7iw8eQJOnDco-xfJFRZqKQ,172
@@ -81,14 +83,14 @@ netbox_dns/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
81
83
  netbox_dns/mixins/__init__.py,sha256=LxTEfpod_RHCyMtnzDljv0_dwqp2z3Q6tqbXW8LTGD8,35
82
84
  netbox_dns/mixins/object_modification.py,sha256=AR64fU5f7g-scNAj9b54eSoS9dpjyOpqrxXVXPcOhY8,1807
83
85
  netbox_dns/models/__init__.py,sha256=iTTJNgUfPAmU4n41usqDSGPvncd4Wpsb9f43ryVDDOs,209
84
- netbox_dns/models/nameserver.py,sha256=GKCWPKqg8WLVQS6UYRUyTdVb_865p2io06yYp5Z9b80,3407
85
- netbox_dns/models/record.py,sha256=7LCLc3mLbgHx5yCMzIItQTB5Jzg8P-1uWGhfYeGpN4U,29415
86
- netbox_dns/models/record_template.py,sha256=PC4369q_LIJkImp1_jhiTTwy083MXIGpGADnbDHMbqI,5104
87
- netbox_dns/models/registrar.py,sha256=bjgYgeUtWGg_seDRN1-VV4Pe450ZK85lbALo4J_Zuic,1890
88
- netbox_dns/models/registration_contact.py,sha256=AkpNy9KbFV9YrISdepqZA1ZfckZSA9u_vfPUAf5Z4H8,3773
89
- netbox_dns/models/view.py,sha256=1OGbol3Ekg1G7c6kPRLwTaLW_ugzNozewnaN2SpyOqc,4756
90
- netbox_dns/models/zone.py,sha256=gq2gnF_ykzZ9kLtiHSm1Lwku-uWU_iFv-0HPlH54JzI,32267
91
- netbox_dns/models/zone_template.py,sha256=kH16CdFk7OpjSiKfJb3bsBi--Shp2V1Fd7jVRJtbl_4,3945
86
+ netbox_dns/models/nameserver.py,sha256=ivZpIVfgQLdDhrtqYPi-zRbygVgl3aff2FMsq1M3qA8,4044
87
+ netbox_dns/models/record.py,sha256=ot2f5LVxj4ZjNanE29y-30iUK4YZS7-0-37ds3hWtjo,29716
88
+ netbox_dns/models/record_template.py,sha256=kt-_sMFSMKmuKU8voVqz1-Lh7Wi7lPcA2ExPFQYLoxM,5345
89
+ netbox_dns/models/registrar.py,sha256=L5tbO8rtOa0VCs_y90nHYLKSRKBnnUhh_6sxZ3Mm2kk,1942
90
+ netbox_dns/models/registration_contact.py,sha256=O7T1clUjuilZnDjvhJKaHZdmNEF4aLg2h8K5p4llWOs,3825
91
+ netbox_dns/models/view.py,sha256=gQvKNr_FmhG2EMz2T8kWbdK4b8CyqI-Qc67-Dgrx2SI,4808
92
+ netbox_dns/models/zone.py,sha256=GhFtsOkA0zPB0VMfXtqFgJZrnrLul-SqgouZbMBcc50,33465
93
+ netbox_dns/models/zone_template.py,sha256=QjjOvSZktH_6l64bCZzVudnL1s9qU6_ZVDkhrhW1zqc,4970
92
94
  netbox_dns/signals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
95
  netbox_dns/signals/ipam_dnssync.py,sha256=1zhlf4cMcJLlFosX7YzyqVYdFFHV4MFwTz5KCdL8xQc,7730
94
96
  netbox_dns/tables/__init__.py,sha256=axENVF9vX9BtDKCNxrapRjye1NnygUg9BS0BBj6a0io,209
@@ -100,7 +102,7 @@ netbox_dns/tables/registrar.py,sha256=XQtJj0c4O4gpCdUp903GSD0tIuARmJw13Nwosw9pTF
100
102
  netbox_dns/tables/registration_contact.py,sha256=n_FKE90j6KNgPKRVq1WXg4vnOzFE238oXsi_NYVAU9M,931
101
103
  netbox_dns/tables/view.py,sha256=gsuWQWAk3RstNIKzBDOHNHR2D3ykX6WigYLMj0VhQFs,1148
102
104
  netbox_dns/tables/zone.py,sha256=_WihxcaUoQ2pgNyufXau8-yDqgIUMU6HAmbK5jxfIFM,1965
103
- netbox_dns/tables/zone_template.py,sha256=l9MC03E0UE_cZoh7YI4DsiccvaUxZZZwf-AAZ7OhgC4,1504
105
+ netbox_dns/tables/zone_template.py,sha256=6Cls0YZ1sI1nyQn9Yu2EMW5pTwOAcHsqNxhc6WuqXac,1647
104
106
  netbox_dns/templates/netbox_dns/nameserver.html,sha256=MawPiuAmjFrbv0zRi-7xkm8vr-dT1tlEno8EcoQ9peU,1714
105
107
  netbox_dns/templates/netbox_dns/record.html,sha256=1KBT4xDooTX9kt1cUoPD2-6QnMizPmbItA0JAAgRzfw,6550
106
108
  netbox_dns/templates/netbox_dns/recordtemplate.html,sha256=a29PAUl-KI_I1lxWpVdPp2loJtzgis9DG9erOWrOZM0,3708
@@ -108,7 +110,7 @@ netbox_dns/templates/netbox_dns/registrar.html,sha256=4kJuj3biiDxQrIMQEQUEmF4iGR
108
110
  netbox_dns/templates/netbox_dns/registrationcontact.html,sha256=sljVp_MrPSJRc2vJCPFXq9MiWOw4wjbr1kI_YStBntw,3094
109
111
  netbox_dns/templates/netbox_dns/view.html,sha256=1MuzOYNQezRrryNjlklgxErjGTFoVnwqcxf4qceuglw,3320
110
112
  netbox_dns/templates/netbox_dns/zone.html,sha256=Ci8MbZgd34vJh67F44_f7Tb4VvV4N14-H-Zh6-qDZsM,6894
111
- netbox_dns/templates/netbox_dns/zonetemplate.html,sha256=rN8fSO7hp2KHTiExstMTyNEhniJFZgkpCFs8UHPAri0,3570
113
+ netbox_dns/templates/netbox_dns/zonetemplate.html,sha256=iE2Dzl3v9AqjUmPuqA5jhPnO94RWxtJgwX1NAr-wimE,3898
112
114
  netbox_dns/templates/netbox_dns/record/managed.html,sha256=uwpxQTxyfAXkWqThLT-T2ZssKNUhXTDDMnLWJSVuDNU,119
113
115
  netbox_dns/templates/netbox_dns/record/related.html,sha256=R59aPhE4CyIZtTH0ncwDyS6_wAe_Y-oZjuN_j4qk8iA,1158
114
116
  netbox_dns/templates/netbox_dns/view/button.html,sha256=EMOB5x78XpyfN1qi-pY1CKKKLjyHo9rFUa4Uhq6rFMc,322
@@ -125,24 +127,24 @@ netbox_dns/templates/netbox_dns/zone/rfc2317_child_zone.html,sha256=rWlmb3zRQbLY
125
127
  netbox_dns/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
128
  netbox_dns/templatetags/netbox_dns.py,sha256=DND1DMPzv636Rak3M6Hor_Vw6pjqUfSTquofIw4dIsA,223
127
129
  netbox_dns/utilities/__init__.py,sha256=cSGf-nGaRWx9b-Xrh3dLMJYoWNsZ6FF-qdmV4F1uOgg,74
128
- netbox_dns/utilities/conversions.py,sha256=FXvYCo7WLtvcf63zoQBkb4rr_TxxzXz8imZAfKGpk7E,2596
129
- netbox_dns/utilities/dns.py,sha256=QKST49UkCw7n2GyrN3wU5ap6Cw98t1SZxFYJlyG2x70,315
130
- netbox_dns/utilities/ipam_dnssync.py,sha256=tFphPVluDUS3-4NsUW1_D1dDksA3AgIozf7JAoTIE_w,9533
130
+ netbox_dns/utilities/conversions.py,sha256=eKA17FSU-Us3cfda9DAgtZgmr3r2o5UbJ_1giD3LLvE,2713
131
+ netbox_dns/utilities/dns.py,sha256=UBiyQe8thiOTnKOmU9e2iRHHnGF9toVLe4efU623kX4,322
132
+ netbox_dns/utilities/ipam_dnssync.py,sha256=_yuHoah_QN-opsZB51yGCkwjkij7nrmTgKHUZ-bQrBI,9625
131
133
  netbox_dns/validators/__init__.py,sha256=Mr8TvmcJTa8Pubj8TzbFBKfbHhEmGcr5JdQvczEJ39A,72
132
134
  netbox_dns/validators/dns_name.py,sha256=Sil68Av49jfZPzgFMV_1qEcLnuuAWXmbxfAJPDXUsGg,3766
133
135
  netbox_dns/validators/dns_value.py,sha256=-mc62mth-hlbPUPe_RlCR7vo1KSD6_gQDXiE8rjB-Cc,5206
134
136
  netbox_dns/validators/rfc2317.py,sha256=uKkwxpakiFFKdYA0qy8WSlEnbFwJD4MDw6gGV4F6skg,706
135
137
  netbox_dns/views/__init__.py,sha256=axENVF9vX9BtDKCNxrapRjye1NnygUg9BS0BBj6a0io,209
136
138
  netbox_dns/views/nameserver.py,sha256=6lHg8fqBjc_SoITzFj1FiRARpPF7nSn9knAZxe9x5Rg,3932
137
- netbox_dns/views/record.py,sha256=quFf9BIQJIb8uodD8-7HBmOHBH7xe-Vu8UMr3Q5jZNo,6496
139
+ netbox_dns/views/record.py,sha256=6tOTC7BbQ5XOC7wr94LjFMR3epOi47HP5qIETNvj5sE,6715
138
140
  netbox_dns/views/record_template.py,sha256=CbSyckBvyEvcZCeZgK3q0fJsa1_5HbwUflh_iM7JjH0,3134
139
141
  netbox_dns/views/registrar.py,sha256=Um_2wnzmP2bqbdMUhBPhny2My0R8fMXScQ9GLiTCrvg,2808
140
142
  netbox_dns/views/registration_contact.py,sha256=c9KrNkfFNsb55pL74A5rN1CNx32M82V6mdwBYduNxas,3596
141
143
  netbox_dns/views/view.py,sha256=VfrKaLC9D_KNZNmRyFVohRlmMlMbtblAuPgNg0LNyf8,3421
142
144
  netbox_dns/views/zone.py,sha256=W66Miyaf4RKW-8z5wMrerrtmHclhht3h-lPqTWFpiOw,7163
143
145
  netbox_dns/views/zone_template.py,sha256=IIW1lr6RQmhShtqJu6A6LnHdxdBrkkZQHxIDSTqQeyc,2705
144
- netbox_plugin_dns-1.2.3.dist-info/LICENSE,sha256=I3tDu11bZfhFm3EkV4zOD5TmWgLjnUNLEFwrdjniZYs,1112
145
- netbox_plugin_dns-1.2.3.dist-info/METADATA,sha256=i_uFK9pSBeoBpPTNaT8o53wWBO_sJVDB_GYL2GWZQkM,7635
146
- netbox_plugin_dns-1.2.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
147
- netbox_plugin_dns-1.2.3.dist-info/top_level.txt,sha256=sA1Rwl1mRKvMC6XHe2ylZ1GF-Q1NGd08XedK9Y4xZc4,11
148
- netbox_plugin_dns-1.2.3.dist-info/RECORD,,
146
+ netbox_plugin_dns-1.2.5.dist-info/LICENSE,sha256=I3tDu11bZfhFm3EkV4zOD5TmWgLjnUNLEFwrdjniZYs,1112
147
+ netbox_plugin_dns-1.2.5.dist-info/METADATA,sha256=CmCyW64s9xmp49ADdEBc48ns2IOYXysCOA8zL9-Ad_0,7636
148
+ netbox_plugin_dns-1.2.5.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
149
+ netbox_plugin_dns-1.2.5.dist-info/top_level.txt,sha256=sA1Rwl1mRKvMC6XHe2ylZ1GF-Q1NGd08XedK9Y4xZc4,11
150
+ netbox_plugin_dns-1.2.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5