netbox-plugin-dns 1.3b2__py3-none-any.whl → 1.3.2__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 (40) hide show
  1. netbox_dns/__init__.py +2 -2
  2. netbox_dns/api/serializers_/dnssec_key_template.py +1 -0
  3. netbox_dns/api/serializers_/dnssec_policy.py +1 -0
  4. netbox_dns/api/serializers_/nameserver.py +5 -1
  5. netbox_dns/api/serializers_/record.py +5 -5
  6. netbox_dns/filtersets/record.py +2 -2
  7. netbox_dns/forms/zone.py +1 -1
  8. netbox_dns/graphql/filters/dnssec_key_template.py +1 -1
  9. netbox_dns/graphql/filters/dnssec_policy.py +1 -1
  10. netbox_dns/graphql/filters/nameserver.py +1 -1
  11. netbox_dns/graphql/filters/record.py +1 -1
  12. netbox_dns/graphql/filters/record_template.py +1 -1
  13. netbox_dns/graphql/filters/registrar.py +1 -1
  14. netbox_dns/graphql/filters/registration_contact.py +1 -1
  15. netbox_dns/graphql/filters/view.py +1 -1
  16. netbox_dns/graphql/filters/zone.py +1 -1
  17. netbox_dns/graphql/filters/zone_template.py +1 -1
  18. netbox_dns/graphql/types.py +2 -3
  19. netbox_dns/locale/de/LC_MESSAGES/django.mo +0 -0
  20. netbox_dns/locale/fr/LC_MESSAGES/django.mo +0 -0
  21. netbox_dns/management/commands/remove_orphaned_ptr_records.py +35 -0
  22. netbox_dns/migrations/0021_alter_record_ptr_record.py +25 -0
  23. netbox_dns/migrations/0022_alter_record_ipam_ip_address.py +26 -0
  24. netbox_dns/models/record.py +115 -46
  25. netbox_dns/models/zone.py +1 -1
  26. netbox_dns/tables/record.py +26 -13
  27. netbox_dns/tables/view.py +1 -0
  28. netbox_dns/template_content.py +13 -2
  29. netbox_dns/templates/netbox_dns/record.html +20 -11
  30. netbox_dns/utilities/ipam_dnssync.py +21 -3
  31. netbox_dns/validators/dns_name.py +1 -1
  32. netbox_dns/views/dnssec_key_template.py +3 -3
  33. netbox_dns/views/record.py +19 -3
  34. netbox_dns/views/record_template.py +3 -1
  35. netbox_dns/views/zone_template.py +5 -3
  36. {netbox_plugin_dns-1.3b2.dist-info → netbox_plugin_dns-1.3.2.dist-info}/METADATA +3 -2
  37. {netbox_plugin_dns-1.3b2.dist-info → netbox_plugin_dns-1.3.2.dist-info}/RECORD +40 -37
  38. {netbox_plugin_dns-1.3b2.dist-info → netbox_plugin_dns-1.3.2.dist-info}/WHEEL +1 -1
  39. {netbox_plugin_dns-1.3b2.dist-info → netbox_plugin_dns-1.3.2.dist-info}/licenses/LICENSE +0 -0
  40. {netbox_plugin_dns-1.3b2.dist-info → netbox_plugin_dns-1.3.2.dist-info}/top_level.txt +0 -0
netbox_dns/__init__.py CHANGED
@@ -4,7 +4,7 @@ from django.core.exceptions import ImproperlyConfigured
4
4
  from netbox.plugins import PluginConfig
5
5
  from netbox.plugins.utils import get_plugin_config
6
6
 
7
- __version__ = "1.3-beta2"
7
+ __version__ = "1.3.2"
8
8
 
9
9
 
10
10
  def _check_list(setting):
@@ -16,7 +16,7 @@ class DNSConfig(PluginConfig):
16
16
  name = "netbox_dns"
17
17
  verbose_name = _("NetBox DNS")
18
18
  description = _("NetBox plugin for DNS data")
19
- min_version = "4.2.0"
19
+ min_version = "4.3.0"
20
20
  version = __version__
21
21
  author = "Peter Eckel"
22
22
  author_email = "pete@netbox-dns.org"
@@ -62,6 +62,7 @@ class DNSSECKeyTemplateSerializer(NetBoxModelSerializer):
62
62
  help_text=_("Policies using this Key Template"),
63
63
  )
64
64
  tenant = TenantSerializer(
65
+ nested=True,
65
66
  required=False,
66
67
  allow_null=True,
67
68
  )
@@ -136,6 +136,7 @@ class DNSSECPolicySerializer(NetBoxModelSerializer):
136
136
  help_text=_("Zone templates this policy is assigned to"),
137
137
  )
138
138
  tenant = TenantSerializer(
139
+ nested=True,
139
140
  required=False,
140
141
  allow_null=True,
141
142
  )
@@ -48,4 +48,8 @@ class NameServerSerializer(NetBoxModelSerializer):
48
48
  default=None,
49
49
  help_text=_("Zones served by the authoritative nameserver"),
50
50
  )
51
- tenant = TenantSerializer(required=False, allow_null=True)
51
+ tenant = TenantSerializer(
52
+ nested=True,
53
+ required=False,
54
+ allow_null=True,
55
+ )
@@ -35,7 +35,7 @@ class RecordSerializer(NetBoxModelSerializer):
35
35
  "managed",
36
36
  "disable_ptr",
37
37
  "ptr_record",
38
- "address_record",
38
+ "address_records",
39
39
  "active",
40
40
  "custom_fields",
41
41
  "tenant",
@@ -71,14 +71,14 @@ class RecordSerializer(NetBoxModelSerializer):
71
71
  read_only=True,
72
72
  required=False,
73
73
  allow_null=True,
74
- help_text=_("PTR record generated from an address"),
74
+ help_text=_("PTR record related to an address"),
75
75
  )
76
- address_record = NestedRecordSerializer(
77
- many=False,
76
+ address_records = NestedRecordSerializer(
77
+ many=True,
78
78
  read_only=True,
79
79
  required=False,
80
80
  allow_null=True,
81
- help_text=_("Address record defining the PTR"),
81
+ help_text=_("Address records related to the PTR"),
82
82
  )
83
83
  zone = NestedZoneSerializer(
84
84
  many=False,
@@ -63,10 +63,10 @@ class RecordFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
63
63
  label=_("View the Parent Zone belongs to"),
64
64
  )
65
65
  address_record_id = django_filters.ModelMultipleChoiceFilter(
66
- field_name="address_record",
66
+ field_name="address_records",
67
67
  queryset=Record.objects.all(),
68
68
  to_field_name="id",
69
- label=_("Address Record"),
69
+ label=_("Address Records"),
70
70
  )
71
71
  ptr_record_id = django_filters.ModelMultipleChoiceFilter(
72
72
  field_name="ptr_record",
netbox_dns/forms/zone.py CHANGED
@@ -534,7 +534,7 @@ class ZoneFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
534
534
  )
535
535
  parental_agents = forms.GenericIPAddressField(
536
536
  required=False,
537
- label=_("Parental Agent"),
537
+ label=_("Parental Agents"),
538
538
  )
539
539
  registrar_id = DynamicModelMultipleChoiceField(
540
540
  queryset=Registrar.objects.all(),
@@ -23,7 +23,7 @@ from netbox_dns.models import DNSSECKeyTemplate
23
23
  __all__ = ("NetBoxDNSDNSSECKeyTemplateFilter",)
24
24
 
25
25
 
26
- @strawberry_django.filter(DNSSECKeyTemplate, lookups=True)
26
+ @strawberry_django.filter_type(DNSSECKeyTemplate, lookups=True)
27
27
  class NetBoxDNSDNSSECKeyTemplateFilter(
28
28
  ContactFilterMixin, TenancyFilterMixin, NetBoxModelFilterMixin
29
29
  ):
@@ -21,7 +21,7 @@ from netbox_dns.graphql.filter_lookups import PolicyDigestArrayLookup
21
21
  __all__ = ("NetBoxDNSDNSSECPolicyFilter", "PolicyDigestArrayLookup")
22
22
 
23
23
 
24
- @strawberry_django.filter(DNSSECPolicy, lookups=True)
24
+ @strawberry_django.filter_type(DNSSECPolicy, lookups=True)
25
25
  class NetBoxDNSDNSSECPolicyFilter(
26
26
  ContactFilterMixin, TenancyFilterMixin, NetBoxModelFilterMixin
27
27
  ):
@@ -16,7 +16,7 @@ from netbox_dns.models import NameServer
16
16
  __all__ = ("NetBoxDNSNameServerFilter",)
17
17
 
18
18
 
19
- @strawberry_django.filter(NameServer, lookups=True)
19
+ @strawberry_django.filter_type(NameServer, lookups=True)
20
20
  class NetBoxDNSNameServerFilter(
21
21
  ContactFilterMixin, TenancyFilterMixin, NetBoxModelFilterMixin
22
22
  ):
@@ -24,7 +24,7 @@ from netbox_dns.models import Record
24
24
  __all__ = ("NetBoxDNSRecordFilter",)
25
25
 
26
26
 
27
- @strawberry_django.filter(Record, lookups=True)
27
+ @strawberry_django.filter_type(Record, lookups=True)
28
28
  class NetBoxDNSRecordFilter(
29
29
  ContactFilterMixin, TenancyFilterMixin, NetBoxModelFilterMixin
30
30
  ):
@@ -20,7 +20,7 @@ from netbox_dns.models import RecordTemplate
20
20
  __all__ = ("NetBoxDNSRecordTemplateFilter",)
21
21
 
22
22
 
23
- @strawberry_django.filter(RecordTemplate, lookups=True)
23
+ @strawberry_django.filter_type(RecordTemplate, lookups=True)
24
24
  class NetBoxDNSRecordTemplateFilter(
25
25
  ContactFilterMixin, TenancyFilterMixin, NetBoxModelFilterMixin
26
26
  ):
@@ -15,7 +15,7 @@ from netbox_dns.models import Registrar
15
15
  __all__ = ("NetBoxDNSRegistrarFilter",)
16
16
 
17
17
 
18
- @strawberry_django.filter(Registrar, lookups=True)
18
+ @strawberry_django.filter_type(Registrar, lookups=True)
19
19
  class NetBoxDNSRegistrarFilter(NetBoxModelFilterMixin):
20
20
  name: FilterLookup[str] | None = strawberry_django.filter_field()
21
21
  description: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -9,7 +9,7 @@ from netbox_dns.models import RegistrationContact
9
9
  __all__ = ("NetBoxDNSRegistrationContactFilter",)
10
10
 
11
11
 
12
- @strawberry_django.filter(RegistrationContact, lookups=True)
12
+ @strawberry_django.filter_type(RegistrationContact, lookups=True)
13
13
  class NetBoxDNSRegistrationContactFilter(NetBoxModelFilterMixin):
14
14
  name: FilterLookup[str] | None = strawberry_django.filter_field()
15
15
  description: FilterLookup[str] | None = strawberry_django.filter_field()
@@ -16,7 +16,7 @@ from netbox_dns.models import View
16
16
  __all__ = ("NetBoxDNSViewFilter",)
17
17
 
18
18
 
19
- @strawberry_django.filter(View, lookups=True)
19
+ @strawberry_django.filter_type(View, lookups=True)
20
20
  class NetBoxDNSViewFilter(
21
21
  ContactFilterMixin, TenancyFilterMixin, NetBoxModelFilterMixin
22
22
  ):
@@ -23,7 +23,7 @@ from netbox_dns.models import Zone
23
23
  __all__ = ("NetBoxDNSZoneFilter",)
24
24
 
25
25
 
26
- @strawberry_django.filter(Zone, lookups=True)
26
+ @strawberry_django.filter_type(Zone, lookups=True)
27
27
  class NetBoxDNSZoneFilter(
28
28
  ContactFilterMixin, TenancyFilterMixin, NetBoxModelFilterMixin
29
29
  ):
@@ -21,7 +21,7 @@ from netbox_dns.models import ZoneTemplate
21
21
  __all__ = ("NetBoxDNSZoneTemplateFilter",)
22
22
 
23
23
 
24
- @strawberry_django.filter(ZoneTemplate, lookups=True)
24
+ @strawberry_django.filter_type(ZoneTemplate, lookups=True)
25
25
  class NetBoxDNSZoneTemplateFilter(
26
26
  ContactFilterMixin, TenancyFilterMixin, NetBoxModelFilterMixin
27
27
  ):
@@ -165,10 +165,9 @@ class NetBoxDNSRecordType(NetBoxObjectType):
165
165
  Annotated["NetBoxDNSRecordType", strawberry.lazy("netbox_dns.graphql.types")]
166
166
  | None
167
167
  )
168
- address_record: (
168
+ address_records: List[
169
169
  Annotated["NetBoxDNSRecordType", strawberry.lazy("netbox_dns.graphql.types")]
170
- | None
171
- )
170
+ ]
172
171
  rfc2317_ptr_records: List[
173
172
  Annotated["NetBoxDNSRecordType", strawberry.lazy("netbox_dns.graphql.types")]
174
173
  ]
Binary file
Binary file
@@ -0,0 +1,35 @@
1
+ from django.core.management.base import BaseCommand
2
+
3
+ from netbox_dns.models import Record
4
+ from netbox_dns.choices import RecordTypeChoices
5
+
6
+
7
+ class Command(BaseCommand):
8
+ help = "Remove managed PTR records without an address record"
9
+
10
+ def handle(self, *model_names, **options):
11
+ orphaned_ptr_records = Record.objects.filter(
12
+ type=RecordTypeChoices.PTR,
13
+ address_records__isnull=True,
14
+ managed=True,
15
+ )
16
+
17
+ if not orphaned_ptr_records.exists():
18
+ if options.get("verbosity") >= 1:
19
+ self.stdout.write("No orphaned PTR records found")
20
+ return
21
+
22
+ if options.get("verbosity") >= 1:
23
+ self.stdout.write(
24
+ f"Removing {orphaned_ptr_records.count()} orphaned PTR record(s) ..."
25
+ )
26
+
27
+ for record in orphaned_ptr_records:
28
+ if options.get("verbosity") >= 2:
29
+ self.stdout.write(
30
+ f"removing PTR record {record} from zone {record.zone}"
31
+ )
32
+ record.delete()
33
+
34
+ if options.get("verbosity") >= 1:
35
+ self.stdout.write("... done.")
@@ -0,0 +1,25 @@
1
+ # Generated by Django 5.2 on 2025-05-13 09:16
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", "0020_remove_dnssecpolicy_parental_agents_and_more"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="record",
16
+ name="ptr_record",
17
+ field=models.ForeignKey(
18
+ blank=True,
19
+ null=True,
20
+ on_delete=django.db.models.deletion.SET_NULL,
21
+ related_name="address_records",
22
+ to="netbox_dns.record",
23
+ ),
24
+ ),
25
+ ]
@@ -0,0 +1,26 @@
1
+ # Generated by Django 5.2.1 on 2025-05-19 19:34
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
+ ("ipam", "0081_remove_service_device_virtual_machine_add_parent_gfk_index"),
11
+ ("netbox_dns", "0021_alter_record_ptr_record"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AlterField(
16
+ model_name="record",
17
+ name="ipam_ip_address",
18
+ field=models.ForeignKey(
19
+ blank=True,
20
+ null=True,
21
+ on_delete=django.db.models.deletion.SET_NULL,
22
+ related_name="netbox_dns_records",
23
+ to="ipam.ipaddress",
24
+ ),
25
+ ),
26
+ ]
@@ -6,7 +6,7 @@ from dns import name as dns_name
6
6
  from dns import rdata
7
7
 
8
8
  from django.core.exceptions import ValidationError
9
- from django.db import transaction, models
9
+ from django.db import models
10
10
  from django.db.models import Q, ExpressionWrapper, BooleanField, Min
11
11
  from django.conf import settings
12
12
  from django.utils.translation import gettext_lazy as _
@@ -28,7 +28,6 @@ from netbox_dns.choices import (
28
28
  RecordClassChoices,
29
29
  )
30
30
 
31
-
32
31
  __all__ = (
33
32
  "Record",
34
33
  "RecordIndex",
@@ -143,6 +142,11 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
143
142
  "tenant",
144
143
  )
145
144
 
145
+ def __init__(self, *args, **kwargs):
146
+ super().__init__(*args, **kwargs)
147
+
148
+ self._cleanup_ptr_record = None
149
+
146
150
  def __str__(self):
147
151
  try:
148
152
  fqdn = dns_name.from_text(
@@ -206,11 +210,11 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
206
210
  null=False,
207
211
  default=False,
208
212
  )
209
- ptr_record = models.OneToOneField(
213
+ ptr_record = models.ForeignKey(
210
214
  verbose_name=_("PTR Record"),
211
215
  to="self",
212
216
  on_delete=models.SET_NULL,
213
- related_name="address_record",
217
+ related_name="address_records",
214
218
  null=True,
215
219
  blank=True,
216
220
  )
@@ -241,7 +245,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
241
245
  ipam_ip_address = models.ForeignKey(
242
246
  verbose_name=_("IPAM IP Address"),
243
247
  to="ipam.IPAddress",
244
- on_delete=models.CASCADE,
248
+ on_delete=models.SET_NULL,
245
249
  related_name="netbox_dns_records",
246
250
  blank=True,
247
251
  null=True,
@@ -255,6 +259,14 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
255
259
  blank=True,
256
260
  )
257
261
 
262
+ @property
263
+ def cleanup_ptr_record(self):
264
+ return self._cleanup_ptr_record
265
+
266
+ @cleanup_ptr_record.setter
267
+ def cleanup_ptr_record(self, ptr_record):
268
+ self._cleanup_ptr_record = ptr_record
269
+
258
270
  @property
259
271
  def display_name(self):
260
272
  return name_to_unicode(self.name)
@@ -348,21 +360,42 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
348
360
  def is_delegation_record(self):
349
361
  return self in self.zone.delegation_records
350
362
 
363
+ def refresh_ptr_record(
364
+ self, ptr_record=None, update_rfc2317_cname=True, save_zone_serial=True
365
+ ):
366
+ if ptr_record is None:
367
+ return
368
+
369
+ if not ptr_record.address_records.exists():
370
+ if ptr_record.rfc2317_cname_record is not None:
371
+ ptr_record.remove_from_rfc2317_cname_record()
372
+
373
+ ptr_record.delete(save_zone_serial=save_zone_serial)
374
+
375
+ elif update_rfc2317_cname:
376
+ ptr_record.update_rfc2317_cname_record(save_zone_serial=save_zone_serial)
377
+
351
378
  def update_ptr_record(self, update_rfc2317_cname=True, save_zone_serial=True):
352
379
  ptr_zone = self.ptr_zone
353
380
 
381
+ # +
382
+ # Check whether a PTR record is optioned for and return if that is not the
383
+ # case.
384
+ # -
354
385
  if (
355
386
  ptr_zone is None
356
387
  or self.disable_ptr
357
388
  or not self.is_active
358
389
  or self.name.startswith("*")
359
390
  ):
360
- if self.ptr_record is not None:
361
- with transaction.atomic():
362
- self.ptr_record.delete()
363
- self.ptr_record = None
391
+ self.cleanup_ptr_record = self.ptr_record
392
+ self.ptr_record = None
364
393
  return
365
394
 
395
+ # +
396
+ # Determine the ptr_name and ptr_value related to the ptr_zone. RFC2317
397
+ # PTR names and zones need to be handled differently.
398
+ # -
366
399
  if ptr_zone.is_rfc2317_zone:
367
400
  ptr_name = self.rfc2317_ptr_name
368
401
  else:
@@ -371,51 +404,76 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
371
404
  .relativize(dns_name.from_text(ptr_zone.name))
372
405
  .to_text()
373
406
  )
374
-
375
407
  ptr_value = self.fqdn
376
- ptr_record = self.ptr_record
377
408
 
378
- if ptr_record is not None:
409
+ # +
410
+ # If there is an existing and matching PTR record there is nothing to be done.
411
+ # -
412
+ if (ptr_record := self.ptr_record) is not None:
379
413
  if (
380
- not ptr_record.zone.is_rfc2317_zone
381
- and ptr_record.rfc2317_cname_record is not None
414
+ ptr_record.zone == ptr_zone
415
+ and ptr_record.name == ptr_name
416
+ and ptr_record.value == ptr_value
417
+ and ptr_record.ttl == self.ttl
382
418
  ):
419
+ return
420
+
421
+ # +
422
+ # If there is an RFC2317 CNAME for the PTR record and it is either
423
+ # not required or needs to be changed, remove it.
424
+ # -
425
+ if (
426
+ ptr_record.zone.pk != ptr_zone.pk or not ptr_record.zone.is_rfc2317_zone
427
+ ) and ptr_record.rfc2317_cname_record is not None:
383
428
  ptr_record.rfc2317_cname_record.delete(
384
429
  save_zone_serial=save_zone_serial
385
430
  )
386
-
387
- with transaction.atomic():
388
- if ptr_record is not None:
389
- if ptr_record.zone.pk != ptr_zone.pk:
390
- if ptr_record.rfc2317_cname_record is not None:
391
- ptr_record.rfc2317_cname_record.delete()
392
- ptr_record.delete(save_zone_serial=save_zone_serial)
393
- ptr_record = None
394
-
395
- else:
396
- if (
397
- ptr_record.name != ptr_name
398
- or ptr_record.value != ptr_value
399
- or ptr_record.ttl != self.ttl
400
- ):
401
- ptr_record.name = ptr_name
402
- ptr_record.value = ptr_value
403
- ptr_record.ttl = self.ttl
404
- ptr_record.save(save_zone_serial=save_zone_serial)
405
-
406
- if ptr_record is None:
407
- ptr_record = Record(
408
- zone_id=ptr_zone.pk,
409
- type=RecordTypeChoices.PTR,
410
- name=ptr_name,
411
- ttl=self.ttl,
412
- value=ptr_value,
413
- managed=True,
414
- )
431
+ ptr_record.rfc2317_cname_record = None
432
+
433
+ # +
434
+ # If the PTR record is used exclusively by the address record it can be
435
+ # modified to match the new name, zone, value and TTL.
436
+ # -
437
+ if ptr_record.address_records.count() == 1:
438
+ ptr_record.zone = ptr_zone
439
+ ptr_record.name = ptr_name
440
+ ptr_record.value = ptr_value
441
+ ptr_record.ttl = self.ttl
415
442
  ptr_record.save(
416
443
  update_rfc2317_cname=update_rfc2317_cname,
417
444
  save_zone_serial=save_zone_serial,
418
445
  )
446
+ return
447
+
448
+ # +
449
+ # Either there was no PTR record or the existing PTR record could not be re-used,
450
+ # so we need to either get find a matching PTR record or create a new one.
451
+ # -
452
+ try:
453
+ ptr_record = Record.objects.get(
454
+ name=ptr_name,
455
+ zone=ptr_zone,
456
+ type=RecordTypeChoices.PTR,
457
+ value=ptr_value,
458
+ )
459
+
460
+ # +
461
+ # If no existing PTR record could be found in the database, create a new
462
+ # one from scratch.
463
+ # -
464
+ except Record.DoesNotExist:
465
+ ptr_record = Record(
466
+ zone_id=ptr_zone.pk,
467
+ type=RecordTypeChoices.PTR,
468
+ name=ptr_name,
469
+ ttl=self.ttl,
470
+ value=ptr_value,
471
+ managed=True,
472
+ )
473
+ ptr_record.save(
474
+ update_rfc2317_cname=update_rfc2317_cname,
475
+ save_zone_serial=save_zone_serial,
476
+ )
419
477
 
420
478
  self.ptr_record = ptr_record
421
479
 
@@ -905,13 +963,19 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
905
963
  save_zone_serial=save_zone_serial,
906
964
  )
907
965
  elif self.ptr_record is not None:
908
- self.ptr_record.delete()
966
+ self.cleanup_ptr_record = self.ptr_record
909
967
  self.ptr_record = None
910
968
 
911
969
  changed_fields = self.changed_fields
912
970
  if changed_fields is None or changed_fields:
913
971
  super().save(*args, **kwargs)
914
972
 
973
+ self.refresh_ptr_record(
974
+ self.cleanup_ptr_record,
975
+ update_rfc2317_cname=update_rfc2317_cname,
976
+ save_zone_serial=save_zone_serial,
977
+ )
978
+
915
979
  if self.type != RecordTypeChoices.SOA and self.zone.soa_serial_auto:
916
980
  self.zone.update_serial(save_zone_serial=save_zone_serial)
917
981
 
@@ -919,11 +983,16 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
919
983
  if self.rfc2317_cname_record:
920
984
  self.remove_from_rfc2317_cname_record(save_zone_serial=save_zone_serial)
921
985
 
922
- if self.ptr_record:
923
- self.ptr_record.delete()
986
+ ptr_record = self.ptr_record
924
987
 
925
988
  super().delete(*args, **kwargs)
926
989
 
990
+ self.refresh_ptr_record(
991
+ ptr_record,
992
+ update_rfc2317_cname=True,
993
+ save_zone_serial=save_zone_serial,
994
+ )
995
+
927
996
  _zone = self.zone
928
997
  if _zone.soa_serial_auto:
929
998
  _zone.update_serial(save_zone_serial=save_zone_serial)
netbox_dns/models/zone.py CHANGED
@@ -1036,7 +1036,7 @@ class Zone(ObjectModificationMixin, ContactsMixin, NetBoxModel):
1036
1036
  for address_record in address_records:
1037
1037
  address_record.ptr_record.delete()
1038
1038
 
1039
- ptr_records = self.records.filter(address_record__isnull=False)
1039
+ ptr_records = self.records.filter(address_records__isnull=False)
1040
1040
  update_records = list(
1041
1041
  Record.objects.filter(ptr_record__in=ptr_records).values_list(
1042
1042
  "pk", flat=True
@@ -116,9 +116,15 @@ class ManagedRecordTable(RecordBaseTable):
116
116
  "active",
117
117
  )
118
118
 
119
- address_record = tables.Column(
120
- verbose_name=_("Address Record"),
119
+ address_records = tables.ManyToManyColumn(
120
+ verbose_name=_("Address Records"),
121
121
  linkify=True,
122
+ linkify_item=True,
123
+ transform=lambda obj: (
124
+ obj.fqdn.rstrip(".")
125
+ if obj.zone.view.default_view
126
+ else f"[{obj.zone.view.name}] {obj.fqdn.rstrip('.')}"
127
+ ),
122
128
  )
123
129
  ipam_ip_address = tables.Column(
124
130
  verbose_name=_("IPAM IP Address"),
@@ -134,12 +140,16 @@ class ManagedRecordTable(RecordBaseTable):
134
140
  def render_related_ip_address(self, record):
135
141
  if record.ipam_ip_address is not None:
136
142
  address = record.ipam_ip_address
137
- elif (
138
- hasattr(record, "address_record")
139
- and record.address_record.ipam_ip_address is not None
140
- ):
141
- address = record.address_record.ipam_ip_address
142
- else:
143
+ elif hasattr(record, "address_records"):
144
+ address_record = record.address_records.filter(
145
+ ipam_ip_address__isnull=False
146
+ ).first()
147
+ if address_record is not None:
148
+ address = address_record.ipam_ip_address
149
+ else:
150
+ address = None
151
+
152
+ if address is None:
143
153
  return format_html("—")
144
154
 
145
155
  return format_html(f"<a href='{address.get_absolute_url()}'>{address}</a>")
@@ -147,11 +157,14 @@ class ManagedRecordTable(RecordBaseTable):
147
157
  def value_related_ip_address(self, record):
148
158
  if record.ipam_ip_address is not None:
149
159
  return record.ipam_ip_address
150
- elif (
151
- hasattr(record, "address_record")
152
- and record.address_record.ipam_ip_address is not None
153
- ):
154
- return record.address_record.ipam_ip_address
160
+ elif hasattr(record, "address_records"):
161
+ address_record = record.address_records.filter(
162
+ ipam_ip_address__isnull=False
163
+ ).first()
164
+ if address_record is not None:
165
+ return address_record.ipam_ip_address
166
+
167
+ return None
155
168
 
156
169
 
157
170
  class RelatedRecordTable(RecordBaseTable):
netbox_dns/tables/view.py CHANGED
@@ -57,4 +57,5 @@ class RelatedViewTable(TenancyColumnsMixin, NetBoxTable):
57
57
  name = tables.Column(
58
58
  linkify=True,
59
59
  )
60
+ tags = TagColumn(url_name="plugins:netbox_dns:view_list")
60
61
  actions = ActionsColumn(actions=())
@@ -20,6 +20,7 @@ class RelatedDNSRecords(PluginTemplateExtension):
20
20
 
21
21
  def right_page(self):
22
22
  ip_address = self.context.get("object")
23
+ request = self.context.get("request")
23
24
 
24
25
  address_records = ip_address.netbox_dns_records.all()
25
26
  pointer_records = [
@@ -32,6 +33,7 @@ class RelatedDNSRecords(PluginTemplateExtension):
32
33
  address_record_table = RelatedRecordTable(
33
34
  data=address_records,
34
35
  )
36
+ address_record_table.configure(request)
35
37
  else:
36
38
  address_record_table = None
37
39
 
@@ -39,6 +41,7 @@ class RelatedDNSRecords(PluginTemplateExtension):
39
41
  pointer_record_table = RelatedRecordTable(
40
42
  data=pointer_records,
41
43
  )
44
+ pointer_record_table.configure(request)
42
45
  else:
43
46
  pointer_record_table = None
44
47
 
@@ -56,11 +59,16 @@ class RelatedDNSViews(PluginTemplateExtension):
56
59
 
57
60
  def right_page(self):
58
61
  prefix = self.context.get("object")
62
+ request = self.context.get("request")
59
63
 
60
64
  if assigned_views := prefix.netbox_dns_views.all():
61
- context = {"assigned_views": RelatedViewTable(data=assigned_views)}
65
+ assigned_views_table = RelatedViewTable(data=assigned_views)
66
+ assigned_views_table.configure(request)
67
+ context = {"assigned_views": assigned_views_table}
62
68
  elif inherited_views := get_views_by_prefix(prefix):
63
- context = {"inherited_views": RelatedViewTable(data=inherited_views)}
69
+ inherited_views_table = RelatedViewTable(data=inherited_views)
70
+ inherited_views_table.configure(request)
71
+ context = {"inherited_views": inherited_views_table}
64
72
  else:
65
73
  context = {}
66
74
 
@@ -86,6 +94,7 @@ class IPRelatedDNSRecords(PluginTemplateExtension):
86
94
 
87
95
  def right_page(self):
88
96
  ip_address = self.context.get("object")
97
+ request = self.context.get("request")
89
98
 
90
99
  address_records = Record.objects.filter(
91
100
  type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
@@ -100,6 +109,7 @@ class IPRelatedDNSRecords(PluginTemplateExtension):
100
109
  address_record_table = RelatedRecordTable(
101
110
  data=address_records,
102
111
  )
112
+ address_record_table.configure(request)
103
113
  else:
104
114
  address_record_table = None
105
115
 
@@ -107,6 +117,7 @@ class IPRelatedDNSRecords(PluginTemplateExtension):
107
117
  pointer_record_table = RelatedRecordTable(
108
118
  data=pointer_records,
109
119
  )
120
+ pointer_record_table.configure(request)
110
121
  else:
111
122
  pointer_record_table = None
112
123
 
@@ -6,6 +6,13 @@
6
6
  {% load i18n %}
7
7
 
8
8
  {% block control-buttons %}
9
+ {% if perms.netbox_dns.add_record and object.type != "CNAME" %}
10
+ <a href="{% url 'plugins:netbox_dns:record_add' %}?view={{ object.zone.view.pk }}&zone={{ object.zone.pk }}&value={{ object.fqdn }}&type=CNAME&return_url={{ object.get_absolute_url }}">
11
+ <button type="submit" class="btn btn-primary" name="add-record">
12
+ <i class="mdi mdi-plus-thick" aria-hidden="true"></i>{% trans "Add CNAME" %}
13
+ </button>
14
+ </a>
15
+ {% endif %}
9
16
  {% if object.managed %}
10
17
  {% else %}
11
18
  {{ block.super }}
@@ -116,22 +123,24 @@
116
123
  <td>{{ object.ptr_record|linkify }}</td>
117
124
  </tr>
118
125
  {% endif %}
119
- {% if object.address_record %}
120
- <tr>
121
- <th scope="row">{% trans "Address Record" %}</th>
122
- <td>{{ object.address_record|linkify }}</td>
123
- </tr>
124
- {% if object.address_record.ipam_ip_address %}
126
+ {% if object.address_records.exists %}
125
127
  <tr>
126
- <th scope="row">{% trans "IPAM IP Address" %}</th>
127
- <td>{{ object.address_record.ipam_ip_address|linkify }}</td>
128
+ <th scope="row">{% trans "Address Records" %}</th>
129
+ <td>
130
+ <table>
131
+ {% for address_record in object.address_records.all %}
132
+ <tr>
133
+ <td>{{ address_record|linkify:"name" }}.{{ address_record.zone|linkify:"name" }}</td>
134
+ </tr>
135
+ {% endfor %}
136
+ </table>
137
+ </td>
128
138
  </tr>
129
139
  {% endif %}
130
- {% endif %}
131
- {% if object.ipam_ip_address %}
140
+ {% if ipam_ip_address %}
132
141
  <tr>
133
142
  <th scope="row">{% trans "IPAM IP Address" %}</th>
134
- <td>{{ object.ipam_ip_address|linkify }}</td>
143
+ <td>{{ ipam_ip_address|linkify }}</td>
135
144
  </tr>
136
145
  {% endif %}
137
146
  <tr>
@@ -10,7 +10,7 @@ from django.db.models import Q
10
10
  from netbox.context import current_request
11
11
  from ipam.models import IPAddress, Prefix
12
12
 
13
- from netbox_dns.choices import RecordStatusChoices
13
+ from netbox_dns.choices import RecordStatusChoices, RecordTypeChoices
14
14
 
15
15
  from .dns import get_parent_zone_names
16
16
  from .conversions import regex_from_list
@@ -213,10 +213,28 @@ def update_dns_records(ip_address, view=None, force=False):
213
213
 
214
214
 
215
215
  def delete_dns_records(ip_address, view=None):
216
- if view is None:
216
+ from netbox_dns.models import Record
217
+
218
+ if current_request.get() is None:
217
219
  address_records = ip_address.netbox_dns_records.all()
218
220
  else:
219
- address_records = ip_address.netbox_dns_records.filter(zone__view=view)
221
+ # +
222
+ # This is a dirty hack made necessary by NetBox grand idea of manipulating
223
+ # objects in its event handling code, removing references to related objects
224
+ # in pre_delete() before our pre_delete() handler has the chance to handle
225
+ # them.
226
+ #
227
+ # TODO: Find something better. This is really awful.
228
+ # -
229
+ address_records = Record.objects.filter(
230
+ type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
231
+ managed=True,
232
+ ip_address=ip_address.address.ip,
233
+ ipam_ip_address__isnull=True,
234
+ )
235
+
236
+ if view is not None:
237
+ address_records &= Record.objects.filter(zone__view=view)
220
238
 
221
239
  if not address_records.exists():
222
240
  return False
@@ -49,7 +49,7 @@ def _get_label(tolerate_leading_underscores=False, always_tolerant=False):
49
49
 
50
50
 
51
51
  def _has_invalid_double_dash(name):
52
- return bool(re.findall(r"\b(?!xn)..--", name, re.IGNORECASE))
52
+ return bool(re.findall(r"(^|\.)(?!xn)..--", name, re.IGNORECASE))
53
53
 
54
54
 
55
55
  def validate_fqdn(name, always_tolerant=False):
@@ -37,9 +37,9 @@ class DNSSECKeyTemplateView(generic.ObjectView):
37
37
 
38
38
  def get_extra_context(self, request, instance):
39
39
  if instance.policies.exists():
40
- return {
41
- "policy_table": DNSSECPolicyDisplayTable(data=instance.policies.all())
42
- }
40
+ policy_table = DNSSECPolicyDisplayTable(data=instance.policies.all())
41
+ policy_table.configure(request)
42
+ return {"policy_table": policy_table}
43
43
 
44
44
  return {}
45
45
 
@@ -51,7 +51,7 @@ class RecordListView(generic.ObjectListView):
51
51
  @register_model_view(Record, "list_managed", path="managed", detail=False)
52
52
  class ManagedRecordListView(generic.ObjectListView):
53
53
  queryset = Record.objects.filter(managed=True).prefetch_related(
54
- "ipam_ip_address", "address_record"
54
+ "ipam_ip_address", "address_records"
55
55
  )
56
56
  filterset = RecordFilterSet
57
57
  filterset_form = RecordFilterForm
@@ -141,11 +141,26 @@ class RecordView(generic.ObjectView):
141
141
 
142
142
  if instance.type == RecordTypeChoices.CNAME:
143
143
  try:
144
- context["cname_target_table"] = self.get_value_records(instance)
144
+ cname_target_table = self.get_value_records(instance)
145
+ if cname_target_table is not None:
146
+ cname_target_table.configure(request)
147
+ context["cname_target_table"] = cname_target_table
145
148
  except CNAMEWarning as exc:
146
149
  context["cname_warning"] = str(exc)
147
150
  else:
148
- context["cname_table"] = self.get_cname_records(instance)
151
+ cname_table = self.get_cname_records(instance)
152
+ if cname_table is not None:
153
+ cname_table.configure(request)
154
+ context["cname_table"] = cname_table
155
+
156
+ if instance.ipam_ip_address is not None:
157
+ context["ipam_ip_address"] = instance.ipam_ip_address
158
+ elif instance.address_records is not None:
159
+ address_record = instance.address_records.filter(
160
+ ipam_ip_address__isnull=False
161
+ ).first()
162
+ if address_record is not None:
163
+ context["ipam_ip_address"] = address_record.ipam_ip_address
149
164
 
150
165
  if not instance.managed:
151
166
  name = dns_name.from_text(instance.name, origin=None)
@@ -155,6 +170,7 @@ class RecordView(generic.ObjectView):
155
170
 
156
171
  if Zone.objects.filter(
157
172
  active=True,
173
+ view=instance.zone.view,
158
174
  name__iregex=regex_from_list(
159
175
  get_parent_zone_names(
160
176
  instance.fqdn,
@@ -50,9 +50,11 @@ class RecordTemplateView(generic.ObjectView):
50
50
  context["unicode_value"] = unicode_value
51
51
 
52
52
  if instance.zone_templates.exists():
53
- context["zone_template_table"] = ZoneTemplateDisplayTable(
53
+ zone_template_table = ZoneTemplateDisplayTable(
54
54
  data=instance.zone_templates.all()
55
55
  )
56
+ zone_template_table.configure(request)
57
+ context["zone_template_table"] = zone_template_table
56
58
 
57
59
  return context
58
60
 
@@ -37,10 +37,12 @@ class ZoneTemplateView(generic.ObjectView):
37
37
 
38
38
  def get_extra_context(self, request, instance):
39
39
  if instance.record_templates.exists():
40
+ record_template_table = RecordTemplateDisplayTable(
41
+ data=instance.record_templates.all()
42
+ )
43
+ record_template_table.configure(request)
40
44
  return {
41
- "record_template_table": RecordTemplateDisplayTable(
42
- data=instance.record_templates.all()
43
- )
45
+ "record_template_table": record_template_table,
44
46
  }
45
47
 
46
48
  return {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-plugin-dns
3
- Version: 1.3b2
3
+ Version: 1.3.2
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
@@ -8,7 +8,7 @@ Project-URL: Documentation, https://github.com/peteeckel/netbox-plugin-dns/blob/
8
8
  Project-URL: Repository, https://github.com/peteeckel/netbox-plugin-dns
9
9
  Project-URL: Issues, https://github.com/peteeckel/netbox-plugin-dns/issues
10
10
  Keywords: netbox,netbox-plugin,dns
11
- Classifier: Development Status :: 4 - Beta
11
+ Classifier: Development Status :: 5 - Production/Stable
12
12
  Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
@@ -52,6 +52,7 @@ The main focus of the plugin is to ensure the quality of the data stored in it.
52
52
  * Support for [RFC 2317](https://datatracker.ietf.org/doc/html/rfc2317) delegation of PTR zones for IPv4 subnets longer than 24 bits
53
53
  * Templating for zones and records enables faster creations of zones with given boilerplate object relations, such as name servers, tags, tenants or registration information, or records like standard SPF or MX records that are the same for a subset of zones
54
54
  * IPAM DNSsync can be used to automatically create address and pointer records for IP addresses by assigning prefixes to DNS views. When an IP address has a DNS name assigned and there are zones with matching names in the DNS views linked to the IP address' prefix, a matching DNS record will be created in these zones
55
+ * DNSSEC support for storing configuration data relevant to DNSSEC operation in NetBox DNS
55
56
 
56
57
  Other main features include:
57
58
 
@@ -1,7 +1,7 @@
1
- netbox_dns/__init__.py,sha256=OL4QOmcjmSwmWCCPgxxTAr5DuCfF8MleRIsmiMJOsfY,4894
1
+ netbox_dns/__init__.py,sha256=EjxvbIOO4SlZ6nP_tL6bjBWj5tImlXFh_mPUGQjpHkE,4890
2
2
  netbox_dns/apps.py,sha256=JCW5eS-AQBUubDJve1DjP-IRFKTFGQh1NLGWzJpC5MI,151
3
3
  netbox_dns/navigation.py,sha256=u90MwWBySg1Z9yfZEdvUctYWEkab5z1Y3019J7U_-3g,7741
4
- netbox_dns/template_content.py,sha256=irgHJe91TnmmL9K1Xnv07uGmOeJMn9zTrIKtJev88XI,4283
4
+ netbox_dns/template_content.py,sha256=nwjbWkMc02vpTmcFQdiAA1TdopJiZ0MkRy6qa18_wLI,4848
5
5
  netbox_dns/urls.py,sha256=wrse8l5scD-jz_O7WY0YXRlYPzpkL-0-kyAd-wCPtbQ,2596
6
6
  netbox_dns/api/field_serializers.py,sha256=nVZ6d69DWagONDwbYCP2j3cmL-x9lryitF1wThEJxyI,725
7
7
  netbox_dns/api/nested_serializers.py,sha256=7ViTZBNMEZ7-NAGn5LXnd7AWxt7TbpFdAq1pcCPtTK8,3695
@@ -9,11 +9,11 @@ netbox_dns/api/serializers.py,sha256=OHrpfJkBn6D1y6b3nIxBEFyE50U5p-Aqv4lBojMEFgk
9
9
  netbox_dns/api/urls.py,sha256=-kQaei47yZeGbDpQ9RaFaFlFb682ThuPA5h321_2cgM,1000
10
10
  netbox_dns/api/views.py,sha256=w71SRyZue5zPD1C64TIr496nYFA_ARjHTlpSVFTZ76o,4522
11
11
  netbox_dns/api/serializers_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- netbox_dns/api/serializers_/dnssec_key_template.py,sha256=Uz0uJEDuLJd4nO2yKX50ko2FMhiZP8Ye2KsHzq_23Rc,1633
13
- netbox_dns/api/serializers_/dnssec_policy.py,sha256=wNWd_LmXiFsegsL0J75gIAffeHIFV5_KGENyGGk5uwI,4301
14
- netbox_dns/api/serializers_/nameserver.py,sha256=CXwrzUQi_q4XYY9NHCAuinRHVVIYY049voNSG7jw46k,1244
12
+ netbox_dns/api/serializers_/dnssec_key_template.py,sha256=gZNv8hdpTc_X0rhfuKfUcsLzV_l6CV_tTzElw6tyEGw,1654
13
+ netbox_dns/api/serializers_/dnssec_policy.py,sha256=DOGtQDiLLMmH2TvmVp9w-enZtrXygiSwfiREWk2IjH0,4322
14
+ netbox_dns/api/serializers_/nameserver.py,sha256=0ItKFCmDVqMbw9i5LandYMZTpJAGClu6lejaWvpLbBY,1288
15
15
  netbox_dns/api/serializers_/prefix.py,sha256=kZ1DjDly6VFZamXSxGa57YC6MfZZcI5S7jmGBkVB2_I,551
16
- netbox_dns/api/serializers_/record.py,sha256=p33Nw1TV5pvlcEmEVt5Q_4HxLo8D0cop45FP5kNCmgI,2570
16
+ netbox_dns/api/serializers_/record.py,sha256=wHs86KwLCgeC_7ORw2b0gvlpAustwfBSfbPva1yPb-k,2570
17
17
  netbox_dns/api/serializers_/record_template.py,sha256=imRRPkCXQMCpXUUk58kB2KwXC6SyAo4HN18pSZyLpHI,1682
18
18
  netbox_dns/api/serializers_/registrar.py,sha256=ul_6SJVqxvTE2ysXBy52U59oTzwmaD781URH1l1OW9o,927
19
19
  netbox_dns/api/serializers_/registration_contact.py,sha256=P_aoG_1rriHn4KkwTx5Dhw37Rn3VY2whg0vAIm55cjk,1108
@@ -37,7 +37,7 @@ netbox_dns/filtersets/__init__.py,sha256=bKppz_w3X2xNNHOcxZZiIO7zSkDaNTrZJ__k1U7
37
37
  netbox_dns/filtersets/dnssec_key_template.py,sha256=dFaNGYdGXghe_uDMedOPxrMGxhDu4gHXwSQ6VLSY7gk,1625
38
38
  netbox_dns/filtersets/dnssec_policy.py,sha256=FRmHulpWmglf89J6bzQYUVFFxvt0mvp29kp7LHA5080,3071
39
39
  netbox_dns/filtersets/nameserver.py,sha256=4hkFsohhvBptFwY9_LJJN8_8KkMhCPsFujzIlDhlnqw,1251
40
- netbox_dns/filtersets/record.py,sha256=PWNNVIzur_5sJDJ2t6hqThg3TaJjEeMOWtw_uAp9NsA,3914
40
+ netbox_dns/filtersets/record.py,sha256=7asGcd2zePhnIDAvuVE-aKAo_NNM0Pm06epXzEql4y4,3916
41
41
  netbox_dns/filtersets/record_template.py,sha256=Pc4P479MqfgdUMvBK39ic2nsV_gg4GT_tbdVcCL9wSI,1605
42
42
  netbox_dns/filtersets/registrar.py,sha256=6QrsrWXu19bMa99DzwwXqfNqxpvTG0JwGEpybhOSYps,978
43
43
  netbox_dns/filtersets/registration_contact.py,sha256=42j7NkwkbSEfomCDekkIE9ZWNug_qfvmTO_D1e-fFIw,1140
@@ -53,30 +53,31 @@ netbox_dns/forms/record_template.py,sha256=5yuY2ppV2diEOdm_IN3QSLLEdWkOkWZOYRtOh
53
53
  netbox_dns/forms/registrar.py,sha256=oLMcXJOpt0F02a2Aga6A45rja7TvI18nTCZb_Dx_8t0,4038
54
54
  netbox_dns/forms/registration_contact.py,sha256=GtUmHzPmAFNRt81rTgJbmb5TMoEJ-mpmjltkuyppJwc,6157
55
55
  netbox_dns/forms/view.py,sha256=KZ2enzbqAEElt3b5C02kMJwnIDEjdQf_BsgMuMqKP50,10836
56
- netbox_dns/forms/zone.py,sha256=-BU1IqEyZ8LC82OMtnsEwnn2AL2SvfsoTYIxlZdyZP0,29920
56
+ netbox_dns/forms/zone.py,sha256=Z6YypsV9tKdnNPWekLZFZVGoHz8UBO2Hylnium5M26c,29921
57
57
  netbox_dns/forms/zone_template.py,sha256=ACx8YJirsrGMMQExVL-aEOaitsUYMyNBL793CoQZrWQ,11716
58
58
  netbox_dns/graphql/__init__.py,sha256=0xg_5d1PPFTadBOZo752t5sfZeLFrqs2jM51Rbf8ti4,652
59
59
  netbox_dns/graphql/enums.py,sha256=vC-v24AuNbaGoekLTDu1PBVbnR1aYeX6LmvrZkfd2F4,1453
60
60
  netbox_dns/graphql/filter_lookups.py,sha256=P6wW2JrtkzUiIx6mJz_DvwYg5Sov68IKAx0zVQfuvYY,355
61
61
  netbox_dns/graphql/schema.py,sha256=KlbJmlfQEqZhvb6-cYmq94mrMFcQoCh3MldaUD5eVV4,2904
62
- netbox_dns/graphql/types.py,sha256=119sdeUAFIII85LZluBTKeZDEH5pnQ3JSYwUJOUVkAk,10192
62
+ netbox_dns/graphql/types.py,sha256=H03lx5YTkxLevGtTHGV5VkdKbTJbgvUYPGqBWl3X8Q4,10182
63
63
  netbox_dns/graphql/filters/__init__.py,sha256=bKppz_w3X2xNNHOcxZZiIO7zSkDaNTrZJ__k1U7rKik,275
64
- netbox_dns/graphql/filters/dnssec_key_template.py,sha256=EPZqXaQyZ6UAnUgKy6_NjxbhXDbOI8oM7I62BuwGzR0,2071
65
- netbox_dns/graphql/filters/dnssec_policy.py,sha256=MImrsCGjNKxqCRB8HGOoNKyqxRuZyhq3su_nT5hdxW4,4651
66
- netbox_dns/graphql/filters/nameserver.py,sha256=iiBQ_1_id-eVgDjtcCYCnVlMG4QeP-lz3p3ZuoGaW3k,1045
67
- netbox_dns/graphql/filters/record.py,sha256=RqJNgLcFJ-Q4ZxV8fqAKXAPpjl6yDxSJz-pWGHIsoS8,3308
68
- netbox_dns/graphql/filters/record_template.py,sha256=3kIeIoPzl3RV7yE-h8Bdc7Lt7VXbtK86uOARFM8UmDo,1814
69
- netbox_dns/graphql/filters/registrar.py,sha256=hiGBl4Fkv_yy8f-I7P25ZAFLrK4Z4hRt9C9hBNSGT14,1153
70
- netbox_dns/graphql/filters/registration_contact.py,sha256=Gwwj55846y90xJiVIdBfPDqrKX_9tjxy6UwfXPbIFJo,1397
71
- netbox_dns/graphql/filters/view.py,sha256=dNjGoSj5aKuQmofRDEIhxTs07ZWc8T0uheI6FOPTafQ,929
72
- netbox_dns/graphql/filters/zone.py,sha256=y5262E4Xqghny8-oUgkt0pod5KtPvZjxAsEowJ5HCdU,5248
73
- netbox_dns/graphql/filters/zone_template.py,sha256=I7_NFFkI8dsVMgIS5PV-VBkoxGZg2w74G3XXq2Fmdco,3095
74
- netbox_dns/locale/de/LC_MESSAGES/django.mo,sha256=2aWnoyYtInSC7wQTIAXO7E4oNxo_g281o8-42-Ilkr8,30000
64
+ netbox_dns/graphql/filters/dnssec_key_template.py,sha256=hK7KgikQOC-BMp88PR1moHQboWd-DS59GwQ0TJADdLM,2076
65
+ netbox_dns/graphql/filters/dnssec_policy.py,sha256=hu2_W74PDxN4QLRQTFd-qh_qf55ztoWtShTiVhOuvD8,4656
66
+ netbox_dns/graphql/filters/nameserver.py,sha256=sDVIrRXBeyscGfQSost4SqrBnBQ_ZIK13GjaXkKAcCw,1050
67
+ netbox_dns/graphql/filters/record.py,sha256=v3-8zyVrA0Hl4AC9d4SkYAQLHpQ1StIP-ws8xM_5Qb4,3313
68
+ netbox_dns/graphql/filters/record_template.py,sha256=sCAm-ct3aXjz9XNtAfUhPXaXRRHIaiXq_oQ4oFzxFY8,1819
69
+ netbox_dns/graphql/filters/registrar.py,sha256=4tv1nlZU41-sOsNXf3-JyZESpAdktsDj7JTr1hv0aG4,1158
70
+ netbox_dns/graphql/filters/registration_contact.py,sha256=feCw4zZd2UQ9895Gg0PJ4R-oGm90ss5XB6ve3B9JlVg,1402
71
+ netbox_dns/graphql/filters/view.py,sha256=ozXGNJ0ELri2FAnQXPHDs3--Hznzx4ZF5mH264nr-DI,934
72
+ netbox_dns/graphql/filters/zone.py,sha256=2mqPq4jikCWhbGRbIIcKbCXRtA9QiH6rXb5ufk1DAFE,5253
73
+ netbox_dns/graphql/filters/zone_template.py,sha256=6ZxBN_VPXvBDGXXwcYlkX6wJXx_IlMAZIEDX5-ARO_4,3100
74
+ netbox_dns/locale/de/LC_MESSAGES/django.mo,sha256=lwJghKXnXryPoDpC8TpvN2QyzY8vCBfF-Bmf_MWhZYs,29944
75
75
  netbox_dns/locale/en/LC_MESSAGES/django.mo,sha256=GDnSZkfHs3yjtTsll7dksEEej4B50F8pc9RGytZNubM,393
76
- netbox_dns/locale/fr/LC_MESSAGES/django.mo,sha256=XjWCnhwjI4Iwht_2JpPLB2btIg6f4ED8F3LqYPwNffA,30023
76
+ netbox_dns/locale/fr/LC_MESSAGES/django.mo,sha256=vL8TcVYyBiDFZ3GVdZe4Kkc42mmtbMF3fot8maY-Q_s,30088
77
77
  netbox_dns/management/commands/cleanup_database.py,sha256=1-tAl0Sht80qaNZyfFyUW19Eh9gBUuc7GdbHN4aemGU,5935
78
78
  netbox_dns/management/commands/cleanup_rrset_ttl.py,sha256=UFRURLBcFeGHUS2lrYFv7UWIebjI72aG1EUQJt0XsXw,2046
79
79
  netbox_dns/management/commands/rebuild_dnssync.py,sha256=Tcl385u6kJTX47SvSyRzKm1RIx4nYRYCMcKr3uVnV60,1246
80
+ netbox_dns/management/commands/remove_orphaned_ptr_records.py,sha256=vYYvI2yr4U1EiAoWm26PkULzaz3rr_Yfaw2eg9i4oIw,1142
80
81
  netbox_dns/management/commands/setup_dnssync.py,sha256=qtVj6egSjclaQbuI60hLfl-zg89VJVbX-TB17f1k77Y,5730
81
82
  netbox_dns/management/commands/update_soa.py,sha256=Rj_Xk-qpwkAVRubVnM5OqSTwgzi93E0PqjwGb3rYjf0,660
82
83
  netbox_dns/migrations/0001_squashed_netbox_dns_0_15.py,sha256=3U0810NWSHPu2dTSHpfzlleDgwMS04FhJ_CkO76SDaw,10283
@@ -101,7 +102,9 @@ netbox_dns/migrations/0018_zone_domain_status_zone_expiration_date.py,sha256=P_J
101
102
  netbox_dns/migrations/0019_dnssecpolicy_parental_agents.py,sha256=ENbj3lhsCYHBflUnb2LrfUyTxwvPwcACmL8sUsm7P7A,655
102
103
  netbox_dns/migrations/0020_netbox_3_4.py,sha256=UMcHdn8ZAuQjUaM_3rEGpktYrM0TuvhccD7Jt7WQnPs,1271
103
104
  netbox_dns/migrations/0020_remove_dnssecpolicy_parental_agents_and_more.py,sha256=Fq2Tv-yz4TAY0yzyVs6j79ztIJa1wOm5oSdbPosYdh0,756
105
+ netbox_dns/migrations/0021_alter_record_ptr_record.py,sha256=arSb_EVRX0RjuldPpFTjfyLHPmzQbyzHq9qw1It50c0,656
104
106
  netbox_dns/migrations/0021_record_ip_address.py,sha256=EqdhWXmq7aiK4X79xTRUZng3zFncCl-8JoO65HqlJKw,3244
107
+ netbox_dns/migrations/0022_alter_record_ipam_ip_address.py,sha256=KKmB3moxPJBKytOBymy0tS6ABIKjpNd5YY6cG--Lluk,727
105
108
  netbox_dns/migrations/0022_search.py,sha256=KW1ffEZ4-0dppGQ_KD1EN7iw8eQJOnDco-xfJFRZqKQ,172
106
109
  netbox_dns/migrations/0023_alter_record_value.py,sha256=4_4v8YZzU8_jadJqIUUjH6SIhNTeALWhclozTqYDmv0,378
107
110
  netbox_dns/migrations/0024_tenancy.py,sha256=3kc5l5_AyfhOI6g6mbCfReUAbSgb2DAv0MDMZqJ-3YQ,1745
@@ -117,12 +120,12 @@ netbox_dns/models/__init__.py,sha256=CuwFENIVUv0FNMDlY18Am-mvN5kBGkPOGavCP0cle7c
117
120
  netbox_dns/models/dnssec_key_template.py,sha256=Nv0vjdkOFWMptRYR1sT60bM6D8n_SnCpPZhI7WE5_UQ,2588
118
121
  netbox_dns/models/dnssec_policy.py,sha256=REsE8p04bgJVF8yJuWuUITXpsZmvVlXWyQuJ9I6dEMs,5268
119
122
  netbox_dns/models/nameserver.py,sha256=oVfyc_iWRzxVE2tIhfRb1Vuj2gZmlfFFzEtXj9ZEr6s,3848
120
- netbox_dns/models/record.py,sha256=wmKwKKtzd9WQ1oa3G6WThPX9E2lTGsnckzdw_FpFZo4,29614
123
+ netbox_dns/models/record.py,sha256=2HSxvroCGhvb7eg4qSXl1ACN7yzGGywbyBjhPR6siEk,31867
121
124
  netbox_dns/models/record_template.py,sha256=Qr43_YZm1z3Od1cBdDY9wpNlV-UCzvpn2c6_dDzFzN8,5145
122
125
  netbox_dns/models/registrar.py,sha256=-ozazecvd-oryEoDlOUvTWhEQKKQp3my6YVTEzWlUuI,1747
123
126
  netbox_dns/models/registration_contact.py,sha256=9ehnTjg8KUrUYJKRRu2SaJX-NE5dO4wy90FRPlT2ys4,3620
124
127
  netbox_dns/models/view.py,sha256=pwo7i8gtukIRgAC1A4rm58jcEpIbsSW_IUq6vSv-mRo,4618
125
- netbox_dns/models/zone.py,sha256=FIGoFs-JM6Su4JZW-zBe3J2IIZUGZTxm6HOFhQyTDZg,35641
128
+ netbox_dns/models/zone.py,sha256=olGZyUsSF0orwefxNUtilsxByr3UceDdF8g8QIYCcsE,35642
126
129
  netbox_dns/models/zone_template.py,sha256=aUkMtWL7SAcmmBd-w33Z5HN6N9LlSlKmYNwf9MlwNq4,5051
127
130
  netbox_dns/signals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
131
  netbox_dns/signals/dnssec.py,sha256=o4MOEg6ftxoDWFAhDtajkXzb7Nb6KuUHjtx1zNu7C1w,1040
@@ -132,17 +135,17 @@ netbox_dns/tables/dnssec_key_template.py,sha256=5Y4S3Q_RtDdUHI4KxY37Trm7uKQCFUcq
132
135
  netbox_dns/tables/dnssec_policy.py,sha256=6GIhoecku3BjYwx7wsOzzkZ0uEVZk5bA63foJ3_g9sw,3332
133
136
  netbox_dns/tables/ipam_dnssync.py,sha256=7IK95XlA2ter6gsHqXjXPd6WubpOxrV-O5-UT6R1CKU,330
134
137
  netbox_dns/tables/nameserver.py,sha256=NFFHwwW174x_XskHF9oBllnI22PGV0J78mklrJF_psw,741
135
- netbox_dns/tables/record.py,sha256=r6gcZA4iocZDkHR-vZIGSqNV7iO2Q-SHv01f6c4MhIY,4307
138
+ netbox_dns/tables/record.py,sha256=DRe0voUIlqi3Q4CHDp-xgzCqgnSG9ynBhNPO8vnzHQw,4777
136
139
  netbox_dns/tables/record_template.py,sha256=ueNSya2Zyrm_zU1Z-ZxwhsJIfxBtYXXoi2pSX-l4Bt8,1977
137
140
  netbox_dns/tables/registrar.py,sha256=yI4n0jq7igYwa-zs6YT6YVV2FxkgpfkSAsA_iPzel7A,776
138
141
  netbox_dns/tables/registration_contact.py,sha256=PeXp5l2WC5qIwctNdk_WP9LRDq_7bnoUlSS3OS6-SDs,980
139
- netbox_dns/tables/view.py,sha256=4EfI8SZRaCuf8LbnLi1L3Qx2upHnbKBONKF7aS0Ty_Q,1256
142
+ netbox_dns/tables/view.py,sha256=BFM50EgudRvcB32hqi0I5qA5nLBfXLzHbt_2q4_KeJA,1318
140
143
  netbox_dns/tables/zone.py,sha256=0lsfI3N9rY11Cxgr-t0PReyNRSnB10JOh-jMVZ4qEBA,2434
141
144
  netbox_dns/tables/zone_template.py,sha256=90geASGP6jSqKnvx2t-DaSxNZpp7j67lH1t0UzsKZ1Q,1755
142
145
  netbox_dns/templates/netbox_dns/dnsseckeytemplate.html,sha256=dSEyHgUp0k_5JSdR4s4m_7Rom67TqvRIQN0zbQKYfjE,2839
143
146
  netbox_dns/templates/netbox_dns/dnssecpolicy.html,sha256=dXHAt8ISsSWv-vK_kaJMcAzUBh4TZ2hAmeaYa47u0PU,7694
144
147
  netbox_dns/templates/netbox_dns/nameserver.html,sha256=MawPiuAmjFrbv0zRi-7xkm8vr-dT1tlEno8EcoQ9peU,1714
145
- netbox_dns/templates/netbox_dns/record.html,sha256=1KBT4xDooTX9kt1cUoPD2-6QnMizPmbItA0JAAgRzfw,6550
148
+ netbox_dns/templates/netbox_dns/record.html,sha256=aFvbIEhhfp0AH5tRDNSgc2nQIymGgnVilk5Q8lCpKKw,7055
146
149
  netbox_dns/templates/netbox_dns/recordtemplate.html,sha256=a29PAUl-KI_I1lxWpVdPp2loJtzgis9DG9erOWrOZM0,3708
147
150
  netbox_dns/templates/netbox_dns/registrar.html,sha256=4kJuj3biiDxQrIMQEQUEmF4iGRE4psr6Fh0CBP1evz8,2308
148
151
  netbox_dns/templates/netbox_dns/registrationcontact.html,sha256=sljVp_MrPSJRc2vJCPFXq9MiWOw4wjbr1kI_YStBntw,3094
@@ -168,25 +171,25 @@ netbox_dns/templatetags/netbox_dns.py,sha256=DND1DMPzv636Rak3M6Hor_Vw6pjqUfSTquo
168
171
  netbox_dns/utilities/__init__.py,sha256=cSGf-nGaRWx9b-Xrh3dLMJYoWNsZ6FF-qdmV4F1uOgg,74
169
172
  netbox_dns/utilities/conversions.py,sha256=qYnzecmR28l8Je_H0vFvzJ2sikTiEiyxr6drl_aRocg,3016
170
173
  netbox_dns/utilities/dns.py,sha256=UBiyQe8thiOTnKOmU9e2iRHHnGF9toVLe4efU623kX4,322
171
- netbox_dns/utilities/ipam_dnssync.py,sha256=_yuHoah_QN-opsZB51yGCkwjkij7nrmTgKHUZ-bQrBI,9625
174
+ netbox_dns/utilities/ipam_dnssync.py,sha256=cJ1P1RuLE_MmlF7yHuLXiv5TNBtinzj2dL7U4S7eAWw,10318
172
175
  netbox_dns/validators/__init__.py,sha256=X0hPZlC3VZcXMcvXKZ2_5LSoEJdXPNSBr4QtEIFSBJ0,94
173
- netbox_dns/validators/dns_name.py,sha256=Sil68Av49jfZPzgFMV_1qEcLnuuAWXmbxfAJPDXUsGg,3766
176
+ netbox_dns/validators/dns_name.py,sha256=1MKnYAmkSTIQGf6zInqkpbIj5SCeCM0YGKmYOqFzUK4,3770
174
177
  netbox_dns/validators/dns_value.py,sha256=cADhgTohXAtOLPzaoMKO9DahEUiDanpdiuKonrwFw0E,5278
175
178
  netbox_dns/validators/dnssec.py,sha256=FzWLXX3qwS9ZMaLWHaBJStwJ_D96wp7GI4LYoKjoegc,4909
176
179
  netbox_dns/validators/rfc2317.py,sha256=uKkwxpakiFFKdYA0qy8WSlEnbFwJD4MDw6gGV4F6skg,706
177
180
  netbox_dns/views/__init__.py,sha256=tObkTOHw_6kVtEcwdyshN0Ql-8VGhwsgQw7owL_s2lI,273
178
- netbox_dns/views/dnssec_key_template.py,sha256=2psuQ7IFdomZbw04wseH2YAeybwwl_fY_7Tc4Mi9MyQ,2640
181
+ netbox_dns/views/dnssec_key_template.py,sha256=06omBc2RCTltGH078mNLLwz3A_y9gqrtQ08ex1XEwlg,2694
179
182
  netbox_dns/views/dnssec_policy.py,sha256=ZvkCVfvI78w8e9c0m8lSWgRwg-jg07PM8Od0HwAVjGI,4427
180
183
  netbox_dns/views/nameserver.py,sha256=cLB0W1UnnvEmYdoCYH5HmjrFhbEZ1gWxJTKdMS-wuh0,3564
181
- netbox_dns/views/record.py,sha256=e7pH_312djse7d-YSpIfzzOk5jKBDnSpsCDu0ESvBfM,6371
182
- netbox_dns/views/record_template.py,sha256=p9bg3HoXfKxut-t7gw8xkC-lv3D9Q_B3BC0eIBVbOhU,2936
184
+ netbox_dns/views/record.py,sha256=cPvsc20EUFcVidQ9YGIdsSTLHZKYzZD3JcV9bFv7iak,7123
185
+ netbox_dns/views/record_template.py,sha256=Cye1rjlpAewRgIv7QGD7o5n-knjzqjEUJzZHVxtGX4s,3041
183
186
  netbox_dns/views/registrar.py,sha256=gYpMyz3rRJDmBfEeRfVENvR6fdWXN1y0XbN4JBlXoHc,2625
184
187
  netbox_dns/views/registration_contact.py,sha256=5bJWjNBisqCkBo6d2TJyyBJlc95WM7VcSA6wsEB184k,3383
185
188
  netbox_dns/views/view.py,sha256=xLXt7sKrda3FpNXsBSJk8L8P2XhZ1sVb5OOXovCsKEU,3089
186
189
  netbox_dns/views/zone.py,sha256=tJiQWMmcuWX6OBtsflQwoTNVjp_GzeZ3-jmywRp74ZU,7065
187
- netbox_dns/views/zone_template.py,sha256=bc0oRftfWi_46UirR3zx9JW4b1E1c2Ump2q8rEQJXug,2449
188
- netbox_plugin_dns-1.3b2.dist-info/licenses/LICENSE,sha256=I3tDu11bZfhFm3EkV4zOD5TmWgLjnUNLEFwrdjniZYs,1112
189
- netbox_plugin_dns-1.3b2.dist-info/METADATA,sha256=e3pBY2nDxmGSPMOV-hfUX9Kr-_zU3CAUJOYp3BZ6W4E,7683
190
- netbox_plugin_dns-1.3b2.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
191
- netbox_plugin_dns-1.3b2.dist-info/top_level.txt,sha256=sA1Rwl1mRKvMC6XHe2ylZ1GF-Q1NGd08XedK9Y4xZc4,11
192
- netbox_plugin_dns-1.3b2.dist-info/RECORD,,
190
+ netbox_dns/views/zone_template.py,sha256=5P9DT3XBRL-TiM5zFhBTMlMusL4bP2jTu3GHxKz5ojc,2553
191
+ netbox_plugin_dns-1.3.2.dist-info/licenses/LICENSE,sha256=I3tDu11bZfhFm3EkV4zOD5TmWgLjnUNLEFwrdjniZYs,1112
192
+ netbox_plugin_dns-1.3.2.dist-info/METADATA,sha256=q6Zuz8KKMmZYwfUL2eaTCF_viJG4S5qm98MBm76nUQ8,7787
193
+ netbox_plugin_dns-1.3.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
194
+ netbox_plugin_dns-1.3.2.dist-info/top_level.txt,sha256=sA1Rwl1mRKvMC6XHe2ylZ1GF-Q1NGd08XedK9Y4xZc4,11
195
+ netbox_plugin_dns-1.3.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.0)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5