netbox-plugin-dns 0.22.4__py3-none-any.whl → 0.22.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of netbox-plugin-dns might be problematic. Click here for more details.

netbox_dns/__init__.py CHANGED
@@ -10,7 +10,7 @@ except ImportError:
10
10
  # NetBox 3.5.8
11
11
  from extras.plugins.utils import get_plugin_config
12
12
 
13
- __version__ = "0.22.4"
13
+ __version__ = "0.22.6"
14
14
 
15
15
 
16
16
  class DNSConfig(PluginConfig):
@@ -38,47 +38,11 @@ class DNSConfig(PluginConfig):
38
38
  ],
39
39
  "tolerate_non_rfc1035_types": [],
40
40
  "enable_root_zones": False,
41
- "enforce_unique_records": False,
41
+ "enforce_unique_records": True,
42
+ "enforce_unique_rrset_ttl": True,
42
43
  }
43
44
  base_url = "netbox-dns"
44
45
 
45
- def ready(self):
46
- #
47
- # Check if required custom fields exist for IPAM coupling
48
- #
49
- if get_plugin_config("netbox_dns", "feature_ipam_coupling"):
50
- from extras.models import CustomField
51
- from ipam.models import IPAddress
52
- from django.contrib.contenttypes.models import ContentType
53
-
54
- try:
55
- objtype = ContentType.objects.get_for_model(IPAddress)
56
- required_cf = (
57
- "ipaddress_dns_record_name",
58
- "ipaddress_dns_record_ttl",
59
- "ipaddress_dns_record_disable_ptr",
60
- "ipaddress_dns_zone_id",
61
- )
62
-
63
- if CustomField.objects.filter(
64
- name__in=required_cf, content_types=objtype
65
- ).count() < len(required_cf):
66
- print(
67
- "WARNING: 'feature_ipam_coupling' is enabled, but the required"
68
- " custom fields for IPAM DNS coupling are missing. Please run"
69
- " the Django management command 'setup_coupling' to create the"
70
- " missing custom fields.",
71
- file=sys.stderr,
72
- )
73
- except OperationalError as exc:
74
- print(
75
- "WARNING: Unable to connect to PostgreSQL, cannot check custom fields"
76
- " for feature_ipam_coupling",
77
- file=sys.stderr,
78
- )
79
-
80
- super().ready()
81
-
82
46
 
83
47
  #
84
48
  # Initialize plugin config
@@ -272,6 +272,7 @@ class RegistrarSerializer(NetBoxModelSerializer):
272
272
  "display",
273
273
  "name",
274
274
  "iana_id",
275
+ "address",
275
276
  "referral_url",
276
277
  "whois_server",
277
278
  "abuse_email",
@@ -12,6 +12,7 @@ class RegistrarFilter(NetBoxModelFilterSet):
12
12
  "id",
13
13
  "name",
14
14
  "iana_id",
15
+ "address",
15
16
  "referral_url",
16
17
  "whois_server",
17
18
  "abuse_email",
@@ -17,6 +17,7 @@ class RegistrarForm(NetBoxModelForm):
17
17
  fields = (
18
18
  "name",
19
19
  "iana_id",
20
+ "address",
20
21
  "referral_url",
21
22
  "whois_server",
22
23
  "abuse_email",
@@ -29,12 +30,18 @@ class RegistrarFilterForm(NetBoxModelFilterSetForm):
29
30
  model = Registrar
30
31
  fieldsets = (
31
32
  (None, ("q", "name", "iana_id", "tags")),
32
- ("Contact", ("referral_url", "whois_server", "abuse_email", "abuse_phone")),
33
+ (
34
+ "Contact",
35
+ ("address", "referral_url", "whois_server", "abuse_email", "abuse_phone"),
36
+ ),
33
37
  )
34
38
 
35
39
  name = forms.CharField(
36
40
  required=False,
37
41
  )
42
+ address = forms.CharField(
43
+ required=False,
44
+ )
38
45
  iana_id = forms.IntegerField(
39
46
  required=False,
40
47
  label="IANA ID",
@@ -64,6 +71,7 @@ class RegistrarImportForm(NetBoxModelImportForm):
64
71
  fields = (
65
72
  "name",
66
73
  "iana_id",
74
+ "address",
67
75
  "referral_url",
68
76
  "whois_server",
69
77
  "abuse_email",
@@ -96,6 +104,7 @@ class RegistrarBulkEditForm(NetBoxModelBulkEditForm):
96
104
  (
97
105
  None,
98
106
  (
107
+ "address",
99
108
  "referral_url",
100
109
  "whois_server",
101
110
  "abuse_email",
@@ -105,6 +114,7 @@ class RegistrarBulkEditForm(NetBoxModelBulkEditForm):
105
114
  )
106
115
 
107
116
  nullable_fields = (
117
+ "address",
108
118
  "referral_url",
109
119
  "whois_server",
110
120
  "abuse_email",
netbox_dns/forms/zone.py CHANGED
@@ -315,7 +315,7 @@ class ZoneImportForm(NetBoxModelImportForm):
315
315
  required=False,
316
316
  help_text="Mailbox of the zone's administrator",
317
317
  )
318
- soa_serial_auto = forms.BooleanField(
318
+ soa_serial_auto = forms.NullBooleanField(
319
319
  required=False,
320
320
  help_text="Generate the SOA serial",
321
321
  )
@@ -443,26 +443,6 @@ class ZoneImportForm(NetBoxModelImportForm):
443
443
  def clean_soa_rname(self):
444
444
  return self._clean_field_with_defaults("soa_rname")
445
445
 
446
- def clean_soa_serial_auto(self):
447
- try:
448
- return self._clean_field_with_defaults("soa_serial_auto")
449
- except ValidationError:
450
- if self.cleaned_data["soa_serial"] or self._get_default_value("soa_serial"):
451
- return None
452
-
453
- raise
454
-
455
- def clean_soa_serial(self):
456
- try:
457
- return self._clean_field_with_defaults("soa_serial")
458
- except ValidationError:
459
- if self.cleaned_data["soa_serial_auto"] or self._get_default_value(
460
- "soa_serial_auto"
461
- ):
462
- return None
463
-
464
- raise
465
-
466
446
  def clean_soa_refresh(self):
467
447
  return self._clean_field_with_defaults("soa_refresh")
468
448
 
@@ -475,6 +455,36 @@ class ZoneImportForm(NetBoxModelImportForm):
475
455
  def clean_soa_minimum(self):
476
456
  return self._clean_field_with_defaults("soa_minimum")
477
457
 
458
+ def clean(self, *args, **kwargs):
459
+ super().clean(*args, **kwargs)
460
+
461
+ soa_serial_auto = self.cleaned_data.get("soa_serial_auto")
462
+ soa_serial = self.cleaned_data.get("soa_serial")
463
+
464
+ if soa_serial is None:
465
+ soa_serial = self._get_default_value("soa_serial")
466
+
467
+ if soa_serial_auto is None:
468
+ if self._get_default_value("soa_serial_auto") is not None:
469
+ soa_serial_auto = self._get_default_value("soa_serial_auto")
470
+
471
+ elif soa_serial is not None:
472
+ soa_serial_auto = False
473
+
474
+ else:
475
+ raise ValidationError(
476
+ "SOA Serial Auto not set and no default value and SOA Serial available"
477
+ )
478
+
479
+ if "soa_serial_auto" in self.cleaned_data:
480
+ self.cleaned_data["soa_serial_auto"] = soa_serial_auto
481
+
482
+ if "soa_serial" in self.cleaned_data:
483
+ if soa_serial_auto:
484
+ self.cleaned_data["soa_serial"] = None
485
+ else:
486
+ self.cleaned_data["soa_serial"] = soa_serial
487
+
478
488
  class Meta:
479
489
  model = Zone
480
490
 
@@ -0,0 +1,62 @@
1
+ from django.core.management.base import BaseCommand
2
+ from django.db.models import Max, Min
3
+
4
+ from netbox_dns.models import (
5
+ Record,
6
+ RecordTypeChoices,
7
+ )
8
+
9
+
10
+ class Command(BaseCommand):
11
+ help = "Clean up the TTLs for RRSets"
12
+
13
+ def add_arguments(self, parser):
14
+ min_max = parser.add_mutually_exclusive_group()
15
+ min_max.add_argument(
16
+ "--min",
17
+ action="store_true",
18
+ help="Use the minimum TTL of an RRSet for all Records",
19
+ )
20
+ min_max.add_argument(
21
+ "--max",
22
+ action="store_true",
23
+ help="Use the maximum TTL of an RRSet for all Records",
24
+ )
25
+
26
+ def handle(self, *model_names, **options):
27
+ self.cleanup_rrset_ttl(**options)
28
+
29
+ self.stdout.write("RRSet cleanup completed.")
30
+
31
+ def cleanup_rrset_ttl(self, **options):
32
+ verbose = options.get("verbosity") > 1
33
+
34
+ ttl_records = (
35
+ Record.objects.filter(ttl__isnull=False)
36
+ .exclude(type=RecordTypeChoices.SOA)
37
+ .exclude(type=RecordTypeChoices.PTR, maanged=True)
38
+ )
39
+ for record in ttl_records:
40
+ records = Record.objects.filter(
41
+ name=record.name,
42
+ zone=record.zone,
43
+ type=record.type,
44
+ ).exclude(type=RecordTypeChoices.PTR, maanged=True)
45
+
46
+ if records.count() == 1:
47
+ if options.get("verbosity") > 2:
48
+ self.stdout.write(f"Ignoring single record {record.id} ({record})")
49
+ continue
50
+
51
+ if options.get("max"):
52
+ ttl = records.aggregate(Max("ttl")).get("ttl__max")
53
+ else:
54
+ ttl = records.aggregate(Min("ttl")).get("ttl__min")
55
+
56
+ for record in records.exclude(ttl=ttl):
57
+ if options.get("verbosity") > 1:
58
+ self.stdout.write(
59
+ f"Updating TTL for record {record.id} ({record}) to {ttl}"
60
+ )
61
+ record.ttl = ttl
62
+ record.save(update_fields=["ttl"], update_rrset_ttl=False)
@@ -6,7 +6,7 @@ from dns import name as dns_name
6
6
 
7
7
  from django.core.exceptions import ValidationError
8
8
  from django.db import transaction, models
9
- from django.db.models import Q, ExpressionWrapper, BooleanField
9
+ from django.db.models import Q, ExpressionWrapper, BooleanField, Min
10
10
  from django.db.models.functions import Length
11
11
  from django.urls import reverse
12
12
 
@@ -38,6 +38,10 @@ from netbox_dns.validators import (
38
38
  import netbox_dns.models.zone as zone
39
39
 
40
40
 
41
+ def min_ttl(*ttl_list):
42
+ return min((ttl for ttl in ttl_list if ttl is not None), default=None)
43
+
44
+
41
45
  class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
42
46
  """Special Manager for records providing the activity status annotation"""
43
47
 
@@ -207,7 +211,8 @@ class Record(NetBoxModel):
207
211
  try:
208
212
  name = (
209
213
  dns_name.from_text(
210
- self.name, origin=dns_name.from_text(self.zone.name, origin=None)
214
+ str(self.name),
215
+ origin=dns_name.from_text(self.zone.name, origin=None),
211
216
  )
212
217
  .relativize(dns_name.root)
213
218
  .to_unicode()
@@ -236,6 +241,16 @@ class Record(NetBoxModel):
236
241
 
237
242
  return name.to_text()
238
243
 
244
+ @property
245
+ def value_fqdn(self):
246
+ if self.type != RecordTypeChoices.CNAME:
247
+ return None
248
+
249
+ zone = dns_name.from_text(self.zone.name)
250
+ value_fqdn = dns_name.from_text(self.value, origin=zone)
251
+
252
+ return value_fqdn.to_text()
253
+
239
254
  @property
240
255
  def address_from_name(self):
241
256
  prefix = arpa_to_prefix(self.fqdn)
@@ -309,7 +324,7 @@ class Record(NetBoxModel):
309
324
 
310
325
  return ptr_zone
311
326
 
312
- def update_ptr_record(self, update_rfc2317_cname=True):
327
+ def update_ptr_record(self, update_rfc2317_cname=True, save_zone_serial=True):
313
328
  ptr_zone = self.ptr_zone
314
329
 
315
330
  if (
@@ -339,14 +354,16 @@ class Record(NetBoxModel):
339
354
  not ptr_record.zone.is_rfc2317_zone
340
355
  and ptr_record.rfc2317_cname_record is not None
341
356
  ):
342
- ptr_record.rfc2317_cname_record.delete()
357
+ ptr_record.rfc2317_cname_record.delete(
358
+ save_zone_serial=save_zone_serial
359
+ )
343
360
 
344
361
  with transaction.atomic():
345
362
  if ptr_record is not None:
346
363
  if ptr_record.zone.pk != ptr_zone.pk:
347
364
  if ptr_record.rfc2317_cname_record is not None:
348
365
  ptr_record.rfc2317_cname_record.delete()
349
- ptr_record.delete()
366
+ ptr_record.delete(save_zone_serial=save_zone_serial)
350
367
  ptr_record = None
351
368
 
352
369
  else:
@@ -358,7 +375,7 @@ class Record(NetBoxModel):
358
375
  ptr_record.name = ptr_name
359
376
  ptr_record.value = ptr_value
360
377
  ptr_record.ttl = self.ttl
361
- ptr_record.save()
378
+ ptr_record.save(save_zone_serial=save_zone_serial)
362
379
 
363
380
  if ptr_record is None:
364
381
  ptr_record = Record(
@@ -369,11 +386,14 @@ class Record(NetBoxModel):
369
386
  value=ptr_value,
370
387
  managed=True,
371
388
  )
372
- ptr_record.save(update_rfc2317_cname=update_rfc2317_cname)
389
+ ptr_record.save(
390
+ update_rfc2317_cname=update_rfc2317_cname,
391
+ save_zone_serial=save_zone_serial,
392
+ )
373
393
 
374
394
  self.ptr_record = ptr_record
375
395
 
376
- def update_rfc2317_cname_record(self):
396
+ def update_rfc2317_cname_record(self, save_zone_serial=True):
377
397
  if self.zone.rfc2317_parent_managed:
378
398
  cname_name = dns_name.from_text(
379
399
  ipaddress.ip_address(self.ip_address).reverse_pointer
@@ -383,7 +403,13 @@ class Record(NetBoxModel):
383
403
  self.rfc2317_cname_record.name = cname_name
384
404
  self.rfc2317_cname_record.zone = self.zone.rfc2317_parent_zone
385
405
  self.rfc2317_cname_record.value = self.fqdn
386
- self.rfc2317_cname_record.save()
406
+ self.rfc2317_cname_record.ttl = min_ttl(
407
+ self.rfc2317_cname_record.rfc2317_ptr_records.exclude(pk=self.pk)
408
+ .aggregate(Min("ttl"))
409
+ .get("ttl__min"),
410
+ self.ttl,
411
+ )
412
+ self.rfc2317_cname_record.save(save_zone_serial=save_zone_serial)
387
413
  else:
388
414
  rfc2317_cname_record = Record.objects.filter(
389
415
  name=cname_name,
@@ -392,20 +418,34 @@ class Record(NetBoxModel):
392
418
  managed=True,
393
419
  value=self.fqdn,
394
420
  ).first()
395
- if rfc2317_cname_record is None:
396
- rfc2317_cname_record = Record.objects.create(
421
+
422
+ if rfc2317_cname_record is not None:
423
+ rfc2317_cname_record.ttl = min_ttl(
424
+ rfc2317_cname_record.rfc2317_ptr_records.exclude(pk=self.pk)
425
+ .aggregate(Min("ttl"))
426
+ .get("ttl__min"),
427
+ self.ttl,
428
+ )
429
+ rfc2317_cname_record.save(
430
+ update_fields=["ttl"], save_zone_serial=save_zone_serial
431
+ )
432
+
433
+ else:
434
+ rfc2317_cname_record = Record(
397
435
  name=cname_name,
398
436
  type=RecordTypeChoices.CNAME,
399
437
  zone=self.zone.rfc2317_parent_zone,
400
438
  managed=True,
401
439
  value=self.fqdn,
440
+ ttl=self.ttl,
402
441
  )
442
+ rfc2317_cname_record.save(save_zone_serial=save_zone_serial)
403
443
 
404
444
  self.rfc2317_cname_record = rfc2317_cname_record
405
445
 
406
446
  else:
407
447
  if self.rfc2317_cname_record is not None:
408
- self.rfc2317_cname_record.delete()
448
+ self.rfc2317_cname_record.delete(save_zone_serial=save_zone_serial)
409
449
  self.rfc2317_cname_record = None
410
450
 
411
451
  def validate_name(self):
@@ -475,7 +515,7 @@ class Record(NetBoxModel):
475
515
  }
476
516
  ) from None
477
517
 
478
- def check_unique(self):
518
+ def check_unique_record(self):
479
519
  if not get_plugin_config("netbox_dns", "enforce_unique_records", False):
480
520
  return
481
521
 
@@ -489,6 +529,10 @@ class Record(NetBoxModel):
489
529
  value=self.value,
490
530
  status__in=Record.ACTIVE_STATUS_LIST,
491
531
  )
532
+
533
+ if self.pk is not None:
534
+ records = records.exclude(pk=self.pk)
535
+
492
536
  if len(records):
493
537
  raise ValidationError(
494
538
  {
@@ -496,6 +540,64 @@ class Record(NetBoxModel):
496
540
  }
497
541
  ) from None
498
542
 
543
+ def check_unique_rrset_ttl(self):
544
+ if self.pk is not None:
545
+ return
546
+
547
+ if not get_plugin_config("netbox_dns", "enforce_unique_rrset_ttl", False):
548
+ return
549
+
550
+ if self.type == RecordTypeChoices.PTR and self.managed:
551
+ return
552
+
553
+ records = (
554
+ Record.objects.filter(
555
+ zone=self.zone,
556
+ name=self.name,
557
+ type=self.type,
558
+ )
559
+ .exclude(ttl=self.ttl)
560
+ .exclude(type=RecordTypeChoices.PTR, managed=True)
561
+ )
562
+
563
+ if not records.exists():
564
+ return
565
+
566
+ conflicting_ttls = ", ".join(set(str(record.ttl) for record in records))
567
+ raise ValidationError(
568
+ {
569
+ "ttl": f"There is at least one active {self.type} record for name {self.name} in zone {self.zone} and TTL is different ({conflicting_ttls})."
570
+ }
571
+ ) from None
572
+
573
+ def update_rrset_ttl(self, ttl=None):
574
+ if self.pk is None:
575
+ return
576
+
577
+ if not get_plugin_config("netbox_dns", "enforce_unique_rrset_ttl", False):
578
+ return
579
+
580
+ if self.type == RecordTypeChoices.PTR and self.managed:
581
+ return
582
+
583
+ if ttl is None:
584
+ ttl = self.ttl
585
+
586
+ records = (
587
+ Record.objects.filter(
588
+ zone=self.zone,
589
+ name=self.name,
590
+ type=self.type,
591
+ )
592
+ .exclude(pk=self.pk)
593
+ .exclude(ttl=ttl)
594
+ .exclude(type=RecordTypeChoices.PTR, managed=True)
595
+ )
596
+
597
+ for record in records:
598
+ record.ttl = ttl
599
+ record.save(update_fields=["ttl"], update_rrset_ttl=False)
600
+
499
601
  def clean_fields(self, *args, **kwargs):
500
602
  self.type = self.type.upper()
501
603
  super().clean_fields(*args, **kwargs)
@@ -503,7 +605,9 @@ class Record(NetBoxModel):
503
605
  def clean(self, *args, **kwargs):
504
606
  self.validate_name()
505
607
  self.validate_value()
506
- self.check_unique()
608
+ self.check_unique_record()
609
+ if self.pk is None:
610
+ self.check_unique_rrset_ttl()
507
611
 
508
612
  if not self.is_active:
509
613
  return
@@ -578,14 +682,24 @@ class Record(NetBoxModel):
578
682
  }
579
683
  ) from None
580
684
 
581
- def save(self, *args, update_rfc2317_cname=True, **kwargs):
685
+ def save(
686
+ self,
687
+ *args,
688
+ update_rfc2317_cname=True,
689
+ save_zone_serial=True,
690
+ update_rrset_ttl=True,
691
+ **kwargs,
692
+ ):
582
693
  self.full_clean()
583
694
 
695
+ if self.pk is not None and update_rrset_ttl:
696
+ self.update_rrset_ttl()
697
+
584
698
  if self.is_ptr_record:
585
699
  if self.zone.is_rfc2317_zone:
586
700
  self.ip_address = self.address_from_rfc2317_name
587
701
  if update_rfc2317_cname:
588
- self.update_rfc2317_cname_record()
702
+ self.update_rfc2317_cname_record(save_zone_serial=save_zone_serial)
589
703
  else:
590
704
  self.ip_address = self.address_from_name
591
705
 
@@ -595,7 +709,10 @@ class Record(NetBoxModel):
595
709
  self.ip_address = None
596
710
 
597
711
  if self.is_address_record:
598
- self.update_ptr_record(update_rfc2317_cname=update_rfc2317_cname)
712
+ self.update_ptr_record(
713
+ update_rfc2317_cname=update_rfc2317_cname,
714
+ save_zone_serial=save_zone_serial,
715
+ )
599
716
  elif self.ptr_record is not None:
600
717
  self.ptr_record.delete()
601
718
  self.ptr_record = None
@@ -604,15 +721,24 @@ class Record(NetBoxModel):
604
721
 
605
722
  zone = self.zone
606
723
  if self.type != RecordTypeChoices.SOA and zone.soa_serial_auto:
607
- zone.update_serial()
724
+ zone.update_serial(save_zone_serial=save_zone_serial)
608
725
 
609
- def delete(self, *args, **kwargs):
726
+ def delete(self, *args, save_zone_serial=True, **kwargs):
610
727
  if self.rfc2317_cname_record:
611
- if (
612
- self.rfc2317_cname_record.pk
613
- and self.rfc2317_cname_record.rfc2317_ptr_records.count() == 1
614
- ):
615
- self.rfc2317_cname_record.delete()
728
+ if self.rfc2317_cname_record.pk:
729
+ if self.rfc2317_cname_record.rfc2317_ptr_records.count() == 1:
730
+ self.rfc2317_cname_record.delete()
731
+ else:
732
+ self.rfc2317_cname_record.ttl = (
733
+ self.rfc2317_cname_record.rfc2317_ptr_records.exclude(
734
+ pk=self.pk
735
+ )
736
+ .aggregate(Min("ttl"))
737
+ .get("ttl__min")
738
+ )
739
+ self.rfc2317_cname_record.save(
740
+ update_fields=["ttl"], save_zone_serial=save_zone_serial
741
+ )
616
742
 
617
743
  if self.ptr_record:
618
744
  self.ptr_record.delete()
@@ -621,7 +747,7 @@ class Record(NetBoxModel):
621
747
 
622
748
  zone = self.zone
623
749
  if zone.soa_serial_auto:
624
- zone.update_serial()
750
+ zone.update_serial(save_zone_serial=save_zone_serial)
625
751
 
626
752
 
627
753
  @register_search
netbox_dns/models/zone.py CHANGED
@@ -249,6 +249,8 @@ class Zone(NetBoxModel):
249
249
  null=True,
250
250
  )
251
251
 
252
+ soa_serial_dirty = False
253
+
252
254
  objects = ZoneManager()
253
255
 
254
256
  clone_fields = [
@@ -470,11 +472,24 @@ class Zone(NetBoxModel):
470
472
 
471
473
  return soa_serial
472
474
 
473
- def update_serial(self):
475
+ def update_serial(self, save_zone_serial=True):
476
+ if not self.soa_serial_auto:
477
+ return
478
+
474
479
  self.last_updated = datetime.now()
475
480
  self.soa_serial = ceil(datetime.now().timestamp())
476
- self.update_soa_record()
477
- super().save()
481
+
482
+ if save_zone_serial:
483
+ super().save(update_fields=["soa_serial", "last_updated"])
484
+ self.soa_serial_dirty = False
485
+ self.update_soa_record()
486
+ else:
487
+ self.soa_serial_dirty = True
488
+
489
+ def save_soa_serial(self):
490
+ if self.soa_serial_auto and self.soa_serial_dirty:
491
+ super().save(update_fields=["soa_serial", "last_updated"])
492
+ self.soa_serial_dirty = False
478
493
 
479
494
  @property
480
495
  def network_from_name(self):
@@ -503,28 +518,45 @@ class Zone(NetBoxModel):
503
518
  if rfc2317_parent_zone is None:
504
519
  self.rfc2317_parent_managed = False
505
520
  self.rfc2317_parent_zone = None
506
- self.save()
521
+ self.save(
522
+ update_fields=["rfc2317_parent_zone", "rfc2317_parent_managed"]
523
+ )
507
524
 
508
525
  elif self.rfc2317_parent_zone != rfc2317_parent_zone:
509
526
  self.rfc2317_parent_zone = rfc2317_parent_zone
510
- self.save()
527
+ self.save(update_fields=["rfc2317_parent_zone"])
511
528
 
512
- ptr_records = self.record_set.filter(type=record.RecordTypeChoices.PTR)
529
+ ptr_records = self.record_set.filter(
530
+ type=record.RecordTypeChoices.PTR
531
+ ).prefetch_related("zone", "rfc2317_cname_record")
532
+ ptr_zones = {ptr_record.zone for ptr_record in ptr_records}
513
533
 
514
534
  if self.rfc2317_parent_managed:
515
535
  for ptr_record in ptr_records:
516
- ptr_record.save()
536
+ ptr_record.save(save_zone_serial=False)
517
537
 
538
+ self.rfc2317_parent_zone.save_soa_serial()
539
+ self.rfc2317_parent_zone.update_soa_record()
518
540
  else:
519
541
  cname_records = {
520
542
  ptr_record.rfc2317_cname_record
521
543
  for ptr_record in ptr_records
522
544
  if ptr_record.rfc2317_cname_record is not None
523
545
  }
546
+ cname_zones = {cname_record.zone for cname_record in cname_records}
547
+
524
548
  for ptr_record in ptr_records:
525
- ptr_record.save(update_rfc2317_cname=False)
549
+ ptr_record.save(update_rfc2317_cname=False, save_zone_serial=False)
526
550
  for cname_record in cname_records:
527
- cname_record.delete()
551
+ cname_record.delete(save_zone_serial=False)
552
+
553
+ for cname_zone in cname_zones:
554
+ cname_zone.save_soa_serial()
555
+ cname_zone.update_soa_record()
556
+
557
+ for ptr_zone in ptr_zones:
558
+ ptr_zone.save_soa_serial()
559
+ ptr_zone.update_soa_record()
528
560
 
529
561
  def clean(self, *args, **kwargs):
530
562
  self.check_name_conflict()
@@ -643,7 +675,12 @@ class Zone(NetBoxModel):
643
675
  )
644
676
 
645
677
  for address_record in address_records:
646
- address_record.save(update_fields=["ptr_record"])
678
+ address_record.save(
679
+ update_fields=["ptr_record"], save_zone_serial=False
680
+ )
681
+
682
+ for zone in zones:
683
+ zone.save_soa_serial()
647
684
 
648
685
  if self.arpa_network.version == 4:
649
686
  rfc2317_child_zones = Zone.objects.filter(
@@ -674,9 +711,14 @@ class Zone(NetBoxModel):
674
711
 
675
712
  for address_record in address_records:
676
713
  address_record.save(
677
- update_fields=["ptr_record"], update_rfc2317_cname=False
714
+ update_fields=["ptr_record"],
715
+ update_rfc2317_cname=False,
716
+ save_zone_serial=False,
678
717
  )
679
718
 
719
+ for zone in zones:
720
+ zone.save_soa_serial()
721
+
680
722
  self.update_rfc2317_parent_zone()
681
723
 
682
724
  elif name_changed or view_changed or status_changed:
@@ -696,11 +738,14 @@ class Zone(NetBoxModel):
696
738
  ip.dns_name = f'{ip.custom_field_data["ipaddress_dns_record_name"]}.{self.name}'
697
739
  ip.save(update_fields=["dns_name"])
698
740
 
741
+ self.save_soa_serial()
699
742
  self.update_soa_record()
700
743
 
701
744
  def delete(self, *args, **kwargs):
702
745
  with transaction.atomic():
703
- address_records = self.record_set.filter(ptr_record__isnull=False)
746
+ address_records = self.record_set.filter(
747
+ ptr_record__isnull=False
748
+ ).prefetch_related("ptr_record")
704
749
  for address_record in address_records:
705
750
  address_record.ptr_record.delete()
706
751
 
@@ -717,8 +762,13 @@ class Zone(NetBoxModel):
717
762
  for ptr_record in ptr_records
718
763
  if ptr_record.rfc2317_cname_record is not None
719
764
  }
765
+ cname_zones = {cname_record.zone for cname_record in cname_records}
766
+
720
767
  for cname_record in cname_records:
721
- cname_record.delete()
768
+ cname_record.delete(save_zone_serial=False)
769
+ for cname_zone in cname_zones:
770
+ cname_zone.save_soa_serial()
771
+ cname_zone.update_soa_record()
722
772
 
723
773
  rfc2317_child_zones = [
724
774
  child_zone.pk for child_zone in self.rfc2317_child_zones.all()
@@ -735,11 +785,25 @@ class Zone(NetBoxModel):
735
785
 
736
786
  super().delete(*args, **kwargs)
737
787
 
738
- for address_record in record.Record.objects.filter(pk__in=update_records):
739
- address_record.save(update_fields=["ptr_record"])
740
-
741
- for child_zone in Zone.objects.filter(pk__in=rfc2317_child_zones):
742
- child_zone.update_rfc2317_parent_zone()
788
+ address_records = record.Record.objects.filter(
789
+ pk__in=update_records
790
+ ).prefetch_related("zone")
791
+
792
+ for address_record in address_records:
793
+ address_record.save(save_zone_serial=False)
794
+ for address_zone in {address_record.zone for address_record in address_records}:
795
+ address_zone.save_soa_serial()
796
+ address_zone.update_soa_record()
797
+
798
+ rfc2317_child_zones = Zone.objects.filter(pk__in=rfc2317_child_zones)
799
+ if rfc2317_child_zones:
800
+ for child_zone in rfc2317_child_zones:
801
+ child_zone.update_rfc2317_parent_zone()
802
+
803
+ new_rfc2317_parent_zone = rfc2317_child_zones.first().rfc2317_parent_zone
804
+ if new_rfc2317_parent_zone is not None:
805
+ new_rfc2317_parent_zone.save_soa_serial()
806
+ new_rfc2317_parent_zone.update_soa_record()
743
807
 
744
808
 
745
809
  @receiver(m2m_changed, sender=Zone.nameservers.through)
@@ -112,12 +112,6 @@
112
112
  <td><a href="{% url 'ipam:ipaddress' pk=object.ipam_ip_address.pk %}">{{ object.ipam_ip_address }}</td>
113
113
  </tr>
114
114
  {% endif %}
115
- {% if object.rfc2317_cname_record %}
116
- <tr>
117
- <th scope="row">RFC2317 CNAME Record</th>
118
- <td><a href="{% url 'plugins:netbox_dns:record' pk=object.rfc2317_cname_record.pk %}">{{ object.rfc2317_cname_record }}</td>
119
- </tr>
120
- {% endif %}
121
115
  <tr>
122
116
  <th scope="row">Status</th>
123
117
  <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
@@ -131,23 +125,13 @@
131
125
  </table>
132
126
  </div>
133
127
  </div>
134
- {% if object.rfc2317_ptr_records.all|length %}
128
+ {% if cname_target_table and cname_target_table.rows|length > 0 %}
135
129
  <div class="card">
136
- <h5 class="card-header">RFC2317 Targets</h5>
137
- <div class="card-body">
138
- <table class="table table-hover attr-table">
139
- <tr>
140
- <th>Address Record</th>
141
- <th>PTR Record</th>
142
- </tr>
143
- {% for record in object.rfc2317_ptr_records.all %}
144
- <tr>
145
- <td><a href="{% url 'plugins:netbox_dns:record' pk=record.address_record.pk %}">{{ record.address_record }}</td>
146
- <td><a href="{% url 'plugins:netbox_dns:record' pk=record.pk %}">{{ record }}</td>
147
- </td>
148
- {% endfor %}
149
- </table>
150
- </div>
130
+ {% include 'inc/panel_table.html' with table=cname_target_table heading='CNAME Targets' %}
131
+ </div>
132
+ {% elif cname_table and cname_table.rows|length > 0 %}
133
+ <div class="card">
134
+ {% include 'inc/panel_table.html' with table=cname_table heading='CNAMEs' %}
151
135
  </div>
152
136
  {% endif %}
153
137
  {% if not object.managed %}
@@ -22,6 +22,10 @@
22
22
  <h5 class="card-header">Contact Details</h5>
23
23
  <div class="card-body">
24
24
  <table class="table table-hover attr-table">
25
+ <tr>
26
+ <th scope="row">Address</th>
27
+ <td>{{ object.address }}</td>
28
+ </tr>
25
29
  <tr>
26
30
  <th scope="row">Referral URL</th>
27
31
  <td><a href="{{ object.referral_url }}">{{ object.referral_url }}</a></td>
@@ -120,10 +120,17 @@
120
120
  <th scope="row">Responsible</th>
121
121
  <td>{{ object.soa_rname }}</td>
122
122
  </tr>
123
+ {% if object.soa_serial_auto %}
124
+ <tr>
125
+ <th scope="row">Serial (auto-generated)</th>
126
+ <td>{{ object.soa_serial }}</td>
127
+ </tr>
128
+ {% else %}
123
129
  <tr>
124
130
  <th scope="row">Serial</th>
125
131
  <td>{{ object.soa_serial }}</td>
126
132
  </tr>
133
+ {% endif %}
127
134
  <tr>
128
135
  <th scope="row">Refresh</th>
129
136
  <td>{{ object.soa_refresh }}</td>
@@ -1,5 +1,8 @@
1
1
  from dns import name as dns_name
2
2
 
3
+ from django.db.models import Q
4
+ from django.db.models.functions import Length
5
+
3
6
  from netbox.views import generic
4
7
 
5
8
  from netbox_dns.filters import RecordFilter
@@ -9,8 +12,8 @@ from netbox_dns.forms import (
9
12
  RecordForm,
10
13
  RecordBulkEditForm,
11
14
  )
12
- from netbox_dns.models import Record
13
- from netbox_dns.tables import RecordTable, ManagedRecordTable
15
+ from netbox_dns.models import Record, RecordTypeChoices, Zone
16
+ from netbox_dns.tables import RecordTable, ManagedRecordTable, RelatedRecordTable
14
17
  from netbox_dns.utilities import value_to_unicode
15
18
 
16
19
 
@@ -37,6 +40,72 @@ class ManagedRecordListView(generic.ObjectListView):
37
40
  class RecordView(generic.ObjectView):
38
41
  queryset = Record.objects.all().prefetch_related("zone", "ptr_record")
39
42
 
43
+ def get_value_records(self, instance):
44
+ value_fqdn = dns_name.from_text(instance.value_fqdn)
45
+ value_zone_names = [
46
+ value_fqdn.split(length)[1].to_text().rstrip(".")
47
+ for length in range(2, len(value_fqdn))
48
+ ]
49
+
50
+ value_zone = (
51
+ Zone.objects.filter(instance.zone.view_filter, name__in=value_zone_names)
52
+ .order_by(Length("name").desc())
53
+ .first()
54
+ )
55
+ if not value_zone:
56
+ return None
57
+
58
+ value_name = value_fqdn.relativize(dns_name.from_text(value_zone.name))
59
+ cname_targets = Record.objects.filter(zone=value_zone, name=value_name)
60
+
61
+ if cname_targets:
62
+ return RelatedRecordTable(
63
+ data=cname_targets,
64
+ )
65
+
66
+ return None
67
+
68
+ def get_cname_records(self, instance):
69
+ view_filter = (
70
+ Q(zone__view__isnull=True)
71
+ if instance.zone.view is None
72
+ else Q(zone__view=instance.zone.view)
73
+ )
74
+ cname_records = set(
75
+ Record.objects.filter(
76
+ view_filter, value=instance.fqdn, type=RecordTypeChoices.CNAME
77
+ )
78
+ )
79
+
80
+ fqdn = dns_name.from_text(instance.fqdn)
81
+ parent_zone_names = [
82
+ fqdn.split(length)[1].to_text().rstrip(".")
83
+ for length in range(1, len(fqdn))
84
+ ]
85
+
86
+ parent_zones = Zone.objects.filter(
87
+ instance.zone.view_filter, name__in=parent_zone_names
88
+ )
89
+
90
+ for parent_zone in parent_zones:
91
+ parent_cname_records = Record.objects.filter(
92
+ view_filter, type=RecordTypeChoices.CNAME, zone=parent_zone
93
+ )
94
+ cname_records = cname_records.union(
95
+ set(
96
+ record
97
+ for record in parent_cname_records
98
+ if record.value_fqdn == instance.fqdn
99
+ )
100
+ )
101
+
102
+ if cname_records:
103
+ return RelatedRecordTable(
104
+ data=cname_records,
105
+ )
106
+
107
+ return None
108
+
40
109
  def get_extra_context(self, request, instance):
41
110
  context = {}
42
111
 
@@ -48,6 +117,11 @@ class RecordView(generic.ObjectView):
48
117
  if instance.value != unicode_value:
49
118
  context["unicode_value"] = unicode_value
50
119
 
120
+ if instance.type == RecordTypeChoices.CNAME:
121
+ context["cname_target_table"] = self.get_value_records(instance)
122
+ else:
123
+ context["cname_table"] = self.get_cname_records(instance)
124
+
51
125
  return context
52
126
 
53
127
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: netbox-plugin-dns
3
- Version: 0.22.4
3
+ Version: 0.22.6
4
4
  Summary: NetBox DNS is a NetBox plugin for managing DNS data.
5
5
  Home-page: https://github.com/peteeckel/netbox-plugin-dns
6
6
  License: MIT
@@ -8,6 +8,7 @@ Keywords: netbox,netbox-plugin,dns
8
8
  Author: Peter Eckel
9
9
  Author-email: pete@netbox-dns.org
10
10
  Requires-Python: >=3.8,<4.0
11
+ Classifier: Development Status :: 5 - Production/Stable
11
12
  Classifier: License :: OSI Approved :: MIT License
12
13
  Classifier: Programming Language :: Python :: 3
13
14
  Classifier: Programming Language :: Python :: 3.8
@@ -19,16 +20,15 @@ Requires-Dist: dnspython (>=2.2.1,<3.0.0)
19
20
  Project-URL: Repository, https://github.com/peteeckel/netbox-plugin-dns
20
21
  Description-Content-Type: text/markdown
21
22
 
22
- <h1 align="center">NetBox DNS</h1>
23
-
24
- <p align="center"><i>NetBox DNS is a NetBox plugin for managing DNS views, zones, name servers and records.</i></p>
23
+ # NetBox DNS
24
+ The NetBox DNS plugin enables NetBox to manage operational DNS data such as name servers, zones, records and views, as well as registration data for domains. It can automate tasks like creating PTR records, generating zone serial numbers, NS and SOA records, as well as validate names and values values for resource records to ensure zone data is consistent, current and conforming to the relevant RFCs.
25
25
 
26
26
  <div align="center">
27
27
  <a href="https://pypi.org/project/netbox-plugin-dns/"><img src="https://img.shields.io/pypi/v/netbox-plugin-dns" alt="PyPi"/></a>
28
- <a href="https://github.com/peteeckel/netbox-plugin-dns/stargazers"><img src="https://img.shields.io/github/stars/peteeckel/netbox-plugin-dns" alt="Stars Badge"/></a>
29
- <a href="https://github.com/peteeckel/netbox-plugin-dns/network/members"><img src="https://img.shields.io/github/forks/peteeckel/netbox-plugin-dns" alt="Forks Badge"/></a>
30
- <a href="https://github.com/peteeckel/netbox-plugin-dns/pulls"><img src="https://img.shields.io/github/issues-pr/peteeckel/netbox-plugin-dns" alt="Pull Requests Badge"/></a>
28
+ <a href="https://github.com/peteeckel/netbox-plugin-dns/stargazers"><img src="https://img.shields.io/github/stars/peteeckel/netbox-plugin-dns?style=flat" alt="Stars Badge"/></a>
29
+ <a href="https://github.com/peteeckel/netbox-plugin-dns/network/members"><img src="https://img.shields.io/github/forks/peteeckel/netbox-plugin-dns?style=flat" alt="Forks Badge"/></a>
31
30
  <a href="https://github.com/peteeckel/netbox-plugin-dns/issues"><img src="https://img.shields.io/github/issues/peteeckel/netbox-plugin-dns" alt="Issues Badge"/></a>
31
+ <a href="https://github.com/peteeckel/netbox-plugin-dns/pulls"><img src="https://img.shields.io/github/issues-pr/peteeckel/netbox-plugin-dns" alt="Pull Requests Badge"/></a>
32
32
  <a href="https://github.com/peteeckel/netbox-plugin-dns/graphs/contributors"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/peteeckel/netbox-plugin-dns?color=2b9348"></a>
33
33
  <a href="https://github.com/peteeckel/netbox-plugin-dns/blob/master/LICENSE"><img src="https://img.shields.io/github/license/peteeckel/netbox-plugin-dns?color=2b9348" alt="License Badge"/></a>
34
34
  <a href="https://pepy.tech/project/netbox-plugin-dns"><img alt="Downloads" src="https://static.pepy.tech/badge/netbox-plugin-dns"></a>
@@ -40,10 +40,10 @@ Description-Content-Type: text/markdown
40
40
 
41
41
  * Manage name servers, zones and records
42
42
  * Automatically generate SOA and NS records for zones
43
- * Automatically create and update PTR records for IPv4 and IPv6 records
44
- * Optionally organize DNS zones in views for split horizon DNS and multi-site deployments
45
- * Optionally maintain domain registrar and registrant information for zones
46
- * Maintain RFC2317 reverse zones for IPv4 prefixes with a network mask length longer than 24 bits
43
+ * Automatically create and update PTR records for IPv4 and IPv6 address records
44
+ * Organize DNS zones in views for split horizon DNS and multi-site deployments
45
+ * Manage domain registrar and registrant information for domains related to zones
46
+ * Manage RFC2317 reverse zones for IPv4 prefixes with a network mask length longer than 24 bits
47
47
 
48
48
  NetBox DNS is using the standardized NetBox plugin interface, so it also takes advantage of the NetBox tagging and change log features.
49
49
 
@@ -1,6 +1,6 @@
1
- netbox_dns/__init__.py,sha256=cK957HlhJU2BDxxdHNq9c-NTm6OhvtfZuQH_DLp2BDs,2777
1
+ netbox_dns/__init__.py,sha256=_MWBhj_SFYmwsvZRS4rBlGcGT5kgLacAUhdzHZ1gZ5I,1287
2
2
  netbox_dns/api/nested_serializers.py,sha256=XB7bcCjVMPYrumJWgRicj06PukQ2UCBjdr84AIUJuVQ,3291
3
- netbox_dns/api/serializers.py,sha256=03BYJMUjIK3edvJv1kcU8jt440kIm-YH6ZAw2T374HM,8160
3
+ netbox_dns/api/serializers.py,sha256=H5Vm1O6C4SsxHVzdooYiu5ak3ykxXo-L1mT6ISWVRV8,8183
4
4
  netbox_dns/api/urls.py,sha256=R9VmmWtdrjvr35i5d_SfZK2lGn6JzmPuWEKTQlZ8MJo,575
5
5
  netbox_dns/api/views.py,sha256=DjovvTfS4F2Fq2Ahea6f4LBJiWr01ajk-wSZHNTya5I,3527
6
6
  netbox_dns/apps.py,sha256=JCW5eS-AQBUubDJve1DjP-IRFKTFGQh1NLGWzJpC5MI,151
@@ -12,16 +12,16 @@ netbox_dns/filters/__init__.py,sha256=Aw8HrCTjaJfu5JSwJsQRHfOUz4zKwAmZNByT9q6BrF
12
12
  netbox_dns/filters/contact.py,sha256=_onZ6G2KKgfvm9Emg_kng2RPETRMfbTyIR8Tvs9d2YM,1027
13
13
  netbox_dns/filters/nameserver.py,sha256=sDKsrluSmUZ0aTw_wagYJAh1g5fKzyzHXbJu6DaK1b8,522
14
14
  netbox_dns/filters/record.py,sha256=DH1x4bNkBAN_gVNklAjorcgHoJmH_AXEf1sBNvJS6Kc,1691
15
- netbox_dns/filters/registrar.py,sha256=kAvE2qNah0t5KtW-QniSw1HyhUgNnlvNSyVpHdUbSng,888
15
+ netbox_dns/filters/registrar.py,sha256=8UXJXUb0XmkPyPQ2oACAhzhmZtAmvM2c4hTWMDLuBEo,911
16
16
  netbox_dns/filters/view.py,sha256=Sji4DKy0VKk8BLEdk8xCe8su3rUBtXeJUsUAee0IsOs,497
17
17
  netbox_dns/filters/zone.py,sha256=gNKTLll0nvdkzjEVl5c4O1hncU13IFVKn61iXThLYJA,3556
18
18
  netbox_dns/forms/__init__.py,sha256=Aw8HrCTjaJfu5JSwJsQRHfOUz4zKwAmZNByT9q6BrFU,136
19
19
  netbox_dns/forms/contact.py,sha256=Z8llYOpcGns8ZerOLsldpY836pfjO7427rmAZ4T8Kyg,4492
20
20
  netbox_dns/forms/nameserver.py,sha256=LUYV_tna667flgPdQevDFniMdXZUYRUG8iqCx7HKdxQ,2027
21
21
  netbox_dns/forms/record.py,sha256=TAQ-rZ55lmWE_piogiE5TwTv1v4r5T2yZpNT8K7GrPU,6171
22
- netbox_dns/forms/registrar.py,sha256=9yGh1BSUbgdSGxM1QTOUeff8ExThX6QnDh_GpVTHjX4,2437
22
+ netbox_dns/forms/registrar.py,sha256=Vkw8cafXQRDHf7K7OFPn62R-_BzjNOofnhLmL2Hw7mg,2636
23
23
  netbox_dns/forms/view.py,sha256=KEWlUo8-oXP_QsqT5fW_UK-ZoX9g-f4CqRnJ_P6LHco,1627
24
- netbox_dns/forms/zone.py,sha256=gJTDPq9WLYCa8nHDzjWrLRFmTEBM4JFqgAtHhV1L-Zk,21241
24
+ netbox_dns/forms/zone.py,sha256=Y9KfeSCzFrJU1LxQjxPcLANu3NEcaHjvxnyMqgna_FY,21702
25
25
  netbox_dns/graphql/__init__.py,sha256=v3lLvVoP-JQbEZ1NY49u1TNqvanF-4TqOtjHXrbsTvU,811
26
26
  netbox_dns/graphql/contact.py,sha256=2iyuvCxG09CLDUMhan5vR3uFZxCVzKNZBrotr_zoUVY,497
27
27
  netbox_dns/graphql/nameserver.py,sha256=mlQw1Po_Ax_fjyyXVBetyxlFLrCqmptYDgOspZvYtP4,527
@@ -31,6 +31,7 @@ netbox_dns/graphql/schema.py,sha256=D_dDusogaI55tmGx_dr1owsgzXS5fd2S-kPZ7xcXxs8,
31
31
  netbox_dns/graphql/view.py,sha256=S_61hYlQCtPQen1lI1UQs38UBWKQTaWfUxzlbpO07zA,467
32
32
  netbox_dns/graphql/zone.py,sha256=QDBxocezhLSHBGDV4RJnmarBfOsiUTeE9KzBGJ3gJi8,467
33
33
  netbox_dns/management/commands/cleanup_database.py,sha256=eggyMZrRg--cXDQJ-boofHboG1gJTs8j-oldhn6EuCo,6050
34
+ netbox_dns/management/commands/cleanup_rrset_ttl.py,sha256=gCvwH6hGw32YJtcy_pEWIP29PSRMW23-qzB8SHEdixQ,2077
34
35
  netbox_dns/management/commands/setup_coupling.py,sha256=Yn1nffPR7fBgB6WWBdDqdnp3k8z1QK8k6qi9xbx4U6Y,4580
35
36
  netbox_dns/management/commands/update_soa.py,sha256=qvlApMngTVpauj0CU0yeOy9r3lxxDciKorMxFsyvQhs,661
36
37
  netbox_dns/migrations/0001_initial.py,sha256=R9FbIQ7nO1ROb12NL8YQDkFQuP1r6-TtMcPwg4rwjus,4153
@@ -66,10 +67,10 @@ netbox_dns/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
66
67
  netbox_dns/models/__init__.py,sha256=Q7UIEe2vGh18AZN4er6CykciwXPQGgUq0L-9718wZqU,182
67
68
  netbox_dns/models/contact.py,sha256=Mzj4SR3fczOE96etWjObJElk0RVQetNuXZVtm8sS1h8,2849
68
69
  netbox_dns/models/nameserver.py,sha256=SjKUbMRNE3TSDzmxbKFR9b4AfYPS0UPj0QeO7XpwNRk,2941
69
- netbox_dns/models/record.py,sha256=sWyWkB0qVJyN8I5oliaRkaVEz7FTqehlkpPBXvyIsAE,19608
70
+ netbox_dns/models/record.py,sha256=KbfSl9Np2kV_qozUB9zgoir3cwutYLvlxPa2q9X-9Jk,23896
70
71
  netbox_dns/models/registrar.py,sha256=ByKHeqH5KfswqOLjya8DZExJ1omSKFHMfCjIIYfnwTo,1416
71
72
  netbox_dns/models/view.py,sha256=ljs3Q2xQR63ZOSyja5H7DEdFbm7MX2ZjlR6uNVrAsVo,920
72
- netbox_dns/models/zone.py,sha256=AwVOImYf55vHHeTWaVnupI2uPSaq9lD48XrR-u1eK7A,23843
73
+ netbox_dns/models/zone.py,sha256=Ek7Jui3AldNHVAHowbQhC47QoXv0IgkhLzFziN14UUI,26333
73
74
  netbox_dns/navigation.py,sha256=IutEr_TcPgDGzqTT1ZzV4IUABLSOFU9v364BfpCqbro,4587
74
75
  netbox_dns/signals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
76
  netbox_dns/signals/ipam_coupling.py,sha256=xbb37_77ZqtgT3mKXZp18QRduqZuiAuYGPk8R9d7SVc,5727
@@ -85,8 +86,8 @@ netbox_dns/templates/netbox_dns/contact.html,sha256=3qHGChgLYfqUgO3_z9jQ-lFIGS0Z
85
86
  netbox_dns/templates/netbox_dns/nameserver.html,sha256=3AJVbsuhKg4Jy74rlvwrGSHd_IoDRdLT_XuK4EH3Djg,1623
86
87
  netbox_dns/templates/netbox_dns/record/managed.html,sha256=G6LPG1koUGuzUiwYdv1okdVa4sKaofiQegDBnsFL0kA,89
87
88
  netbox_dns/templates/netbox_dns/record/related.html,sha256=Aqor8uGcuHQTHjlX-Xmni2Yp4N7lOBrMOqQiszrQOC0,742
88
- netbox_dns/templates/netbox_dns/record.html,sha256=1XOp9Rf7KiV736RKGh7A8LAYkrfI7veG5SsEhaeMghw,6613
89
- netbox_dns/templates/netbox_dns/registrar.html,sha256=Ijy8w0P3UPiyVFkIjk92d4n3GCqsm-0kQjk-QJ-2BCw,1956
89
+ netbox_dns/templates/netbox_dns/record.html,sha256=jq1kueOd5EN1myZt8RbXl4rjoTuxWezBnVv36i74QC8,5835
90
+ netbox_dns/templates/netbox_dns/registrar.html,sha256=rSShbH68nP0r8EUHn0-TZOsUj6pg7hmfvM7h2tqouUA,2130
90
91
  netbox_dns/templates/netbox_dns/related_dns_objects.html,sha256=KSzlnw1cStrJa3poKkwrt_ycIH0oH0STWIHRNy3ks4g,806
91
92
  netbox_dns/templates/netbox_dns/view.html,sha256=_lDjd2xY3upfGpNpemXXMzgsaKZlX3-PzPPAdYkIjvs,1350
92
93
  netbox_dns/templates/netbox_dns/zone/base.html,sha256=pucf_b7iGg4hCXqwIbR0_WtEQdeH2LQtTCRTSoeBhFc,460
@@ -95,7 +96,7 @@ netbox_dns/templates/netbox_dns/zone/managed_record.html,sha256=5P85eJuQOB7omih2
95
96
  netbox_dns/templates/netbox_dns/zone/record.html,sha256=1oIRGXOZAjwmTMkTgArfKyVrmL54Sh_IN7IAF3qYEKM,2218
96
97
  netbox_dns/templates/netbox_dns/zone/registration.html,sha256=3R5uqLXZxIJkp9_LVnTTgTV61mITfDPDDNofV7s4d1k,1283
97
98
  netbox_dns/templates/netbox_dns/zone/rfc2317_child_zone.html,sha256=kxIrhn0gghb4l1eZhpsICdfkSYegqSXR9zFIWiZLypA,527
98
- netbox_dns/templates/netbox_dns/zone.html,sha256=mB-oyl7vGgtr-DksgoiU7WSx-88_oD52s4YQBrOIajc,6895
99
+ netbox_dns/templates/netbox_dns/zone.html,sha256=_lQhr00_V8ThadlTuTm5zPkcvyjYQxxYGHQsfTOgDCs,7187
99
100
  netbox_dns/urls.py,sha256=NxqvnRxLM6VwUBTY6KA8u-PRjNklmBai7gy6NXc-1xo,9172
100
101
  netbox_dns/utilities/__init__.py,sha256=8DqDx-99kejykwaIt_29W5KgY8RvWBOk6526vrf22UQ,1967
101
102
  netbox_dns/utilities/ipam_coupling.py,sha256=LhdFafF4OAOU8rgROOqY5Dt7XDT_SUZP2JuIqCQYVqw,3573
@@ -105,11 +106,11 @@ netbox_dns/validators/rfc2317.py,sha256=bgqDwdqwpf5d1mt24Wrr8z8t2IYbt5RovNr6l2nv
105
106
  netbox_dns/views/__init__.py,sha256=Aw8HrCTjaJfu5JSwJsQRHfOUz4zKwAmZNByT9q6BrFU,136
106
107
  netbox_dns/views/contact.py,sha256=6-oCfK98-submcUTmi0ejw7QBscNn3S9bnS0oUTXOaY,2235
107
108
  netbox_dns/views/nameserver.py,sha256=Wa8CQ19P5uPNLMIYkj_U82wmwdp5gZoBWZnOR4ZExa0,2990
108
- netbox_dns/views/record.py,sha256=2xzZnCWmCZIyEVjQfYo0rByhBNItPxNFiehOwDl8U6w,2507
109
+ netbox_dns/views/record.py,sha256=p6TOV2dGg5uNPtk2pSqLzMRXpgC-fazpwYe2mIRSTt8,4896
109
110
  netbox_dns/views/registrar.py,sha256=aznSKt1L5tILMLGgcZiBR7u7B8rNl-jM1B2-N0fTeK8,2072
110
111
  netbox_dns/views/view.py,sha256=uUvtlNEh5MYoEALvWWaCOqj_Zj8dpGOL2PUyg-UPfEA,1895
111
112
  netbox_dns/views/zone.py,sha256=SyttTAgrPPzf1jIT1B4RexCLdXYjSmPIZsefO_zog1Q,4587
112
- netbox_plugin_dns-0.22.4.dist-info/LICENSE,sha256=tziMJKpkMbySr09L6bIwsu7Ca9ICoqpMO3yAXgEMQA4,1076
113
- netbox_plugin_dns-0.22.4.dist-info/METADATA,sha256=o5FB0XTzm8-eqyN2NwnsmaDVI-qY3fCOH5B3kJgBPxo,4228
114
- netbox_plugin_dns-0.22.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
115
- netbox_plugin_dns-0.22.4.dist-info/RECORD,,
113
+ netbox_plugin_dns-0.22.6.dist-info/LICENSE,sha256=tziMJKpkMbySr09L6bIwsu7Ca9ICoqpMO3yAXgEMQA4,1076
114
+ netbox_plugin_dns-0.22.6.dist-info/METADATA,sha256=v4cYHLNbhWBpdmkaefeFSQbmIqg5TxJfeI4SJpUsq4M,4572
115
+ netbox_plugin_dns-0.22.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
116
+ netbox_plugin_dns-0.22.6.dist-info/RECORD,,