netbox-plugin-dns 1.1.0b2__py3-none-any.whl → 1.1.0b4__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
@@ -5,7 +5,7 @@ from ipam.choices import IPAddressStatusChoices
5
5
 
6
6
  from netbox_dns.choices import RecordTypeChoices
7
7
 
8
- __version__ = "1.1.0b2"
8
+ __version__ = "1.1.0b4"
9
9
 
10
10
 
11
11
  class DNSConfig(PluginConfig):
@@ -25,13 +25,13 @@ class DNSConfig(PluginConfig):
25
25
  "zone_soa_retry": 7200,
26
26
  "zone_soa_expire": 2419200,
27
27
  "zone_soa_minimum": 3600,
28
- "autodns_disabled": False,
29
- "autodns_ipaddress_active_status": [
28
+ "dnssync_disabled": False,
29
+ "dnssync_ipaddress_active_status": [
30
30
  IPAddressStatusChoices.STATUS_ACTIVE,
31
31
  IPAddressStatusChoices.STATUS_DHCP,
32
32
  IPAddressStatusChoices.STATUS_SLAAC,
33
33
  ],
34
- "autodns_conflict_deactivate": False,
34
+ "dnssync_conflict_deactivate": False,
35
35
  "tolerate_characters_in_zone_labels": "",
36
36
  "tolerate_underscores_in_labels": False,
37
37
  "tolerate_underscores_in_hostnames": False, # Deprecated, will be removed in 1.2.0
@@ -51,8 +51,9 @@ class DNSConfig(PluginConfig):
51
51
  def ready(self):
52
52
  super().ready()
53
53
 
54
- if not settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
55
- from netbox_dns.signals import ipam_autodns
54
+ if not settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
55
+ import netbox_dns.signals.ipam_dnssync
56
+ import netbox_dns.tables.ipam_dnssync
56
57
 
57
58
 
58
59
  #
@@ -121,7 +121,7 @@ class RecordFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
121
121
  if not value.strip():
122
122
  return queryset
123
123
  qs_filter = (
124
- Q(name__icontains=value)
124
+ Q(fqdn__icontains=value)
125
125
  | Q(value__icontains=value)
126
126
  | Q(zone__name__icontains=value)
127
127
  )
netbox_dns/forms/view.py CHANGED
@@ -21,12 +21,12 @@ from utilities.forms.rendering import FieldSet
21
21
  from tenancy.models import Tenant
22
22
  from tenancy.forms import TenancyForm, TenancyFilterForm
23
23
  from ipam.models import Prefix
24
+ from netbox.context import current_request
24
25
 
25
26
  from netbox_dns.models import View
26
27
  from netbox_dns.fields import PrefixDynamicModelMultipleChoiceField
27
28
  from netbox_dns.utilities import (
28
29
  check_dns_records,
29
- update_dns_records,
30
30
  get_ip_addresses_by_prefix,
31
31
  get_views_by_prefix,
32
32
  )
@@ -37,6 +37,7 @@ __all__ = (
37
37
  "ViewFilterForm",
38
38
  "ViewImportForm",
39
39
  "ViewBulkEditForm",
40
+ "ViewPrefixEditForm",
40
41
  )
41
42
 
42
43
 
@@ -94,9 +95,24 @@ class ViewForm(ViewPrefixUpdateMixin, TenancyForm, NetBoxModelForm):
94
95
  def __init__(self, *args, **kwargs):
95
96
  super().__init__(*args, **kwargs)
96
97
 
97
- if settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
98
+ if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
98
99
  del self.fields["prefixes"]
99
100
 
101
+ if request := current_request.get():
102
+ if not request.user.has_perm("ipam.view_prefix"):
103
+ self._saved_prefixes = self.initial["prefixes"]
104
+ self.initial["prefixes"] = []
105
+ self.fields["prefixes"].disabled = True
106
+ self.fields["prefixes"].widget.attrs[
107
+ "placeholder"
108
+ ] = "You do not have permission to modify assigned prefixes"
109
+
110
+ def clean_prefixes(self):
111
+ if hasattr(self, "_saved_prefixes"):
112
+ return self._saved_prefixes
113
+
114
+ return self.cleaned_data["prefixes"]
115
+
100
116
  prefixes = PrefixDynamicModelMultipleChoiceField(
101
117
  queryset=Prefix.objects.all(),
102
118
  required=False,
@@ -128,7 +144,7 @@ class ViewFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
128
144
  def __init__(self, *args, **kwargs):
129
145
  super().__init__(*args, **kwargs)
130
146
 
131
- if settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
147
+ if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
132
148
  del self.fields["prefix_id"]
133
149
 
134
150
  model = View
@@ -164,7 +180,7 @@ class ViewImportForm(ViewPrefixUpdateMixin, NetBoxModelImportForm):
164
180
  def __init__(self, *args, **kwargs):
165
181
  super().__init__(*args, **kwargs)
166
182
 
167
- if settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
183
+ if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
168
184
  del self.fields["prefixes"]
169
185
 
170
186
  prefixes = CSVModelMultipleChoiceField(
@@ -201,3 +217,74 @@ class ViewBulkEditForm(NetBoxModelBulkEditForm):
201
217
  )
202
218
 
203
219
  nullable_fields = ("description", "tenant")
220
+
221
+
222
+ class ViewPrefixEditForm(forms.ModelForm):
223
+ views = DynamicModelMultipleChoiceField(
224
+ queryset=View.objects.all(),
225
+ required=False,
226
+ label="Assigned DNS Views",
227
+ help_text="Explicitly assigning DNS views overrides all inherited views for this prefix",
228
+ )
229
+
230
+ class Meta:
231
+ model = Prefix
232
+ fields = ("views",)
233
+
234
+ def __init__(self, *args, **kwargs):
235
+ super().__init__(*args, **kwargs)
236
+
237
+ self.initial["views"] = self.instance.netbox_dns_views.all()
238
+ self._permission_denied = False
239
+
240
+ if request := current_request.get():
241
+ if not request.user.has_perm("netbox_dns.change_view"):
242
+ self._permission_denied = True
243
+ self.initial["views"] = []
244
+ self.fields["views"].disabled = True
245
+ self.fields["views"].widget.attrs[
246
+ "placeholder"
247
+ ] = "You do not have permission to modify assigned views"
248
+
249
+ def clean(self, *args, **kwargs):
250
+ if self._permission_denied:
251
+ return
252
+
253
+ prefix = self.instance
254
+
255
+ super().clean(*args, **kwargs)
256
+
257
+ views = self.cleaned_data.get("views")
258
+ old_views = prefix.netbox_dns_views.all()
259
+
260
+ check_views = View.objects.none()
261
+
262
+ if not views.exists():
263
+ if (parent := prefix.get_parents().last()) is not None:
264
+ check_views = parent.netbox_dns_views.all().difference(old_views)
265
+
266
+ else:
267
+ check_views = views.difference(old_views)
268
+
269
+ for view in check_views:
270
+ try:
271
+ for ip_address in get_ip_addresses_by_prefix(prefix, check_view=False):
272
+ check_dns_records(ip_address, view=view)
273
+ except ValidationError as exc:
274
+ self.add_error("views", exc.messages)
275
+
276
+ def save(self, *args, **kwargs):
277
+ prefix = self.instance
278
+
279
+ if self._permission_denied:
280
+ return prefix
281
+
282
+ old_views = prefix.netbox_dns_views.all()
283
+ views = self.cleaned_data.get("views")
284
+
285
+ for view in views.difference(old_views):
286
+ view.prefixes.add(prefix)
287
+ for view in old_views.difference(views):
288
+ view.prefixes.remove(prefix)
289
+
290
+ return prefix
netbox_dns/forms/zone.py CHANGED
@@ -81,6 +81,14 @@ class ZoneTemplateUpdateMixin:
81
81
  else:
82
82
  zone_data = self.cleaned_data.copy()
83
83
 
84
+ custom_fields = {}
85
+ for key, value in zone_data.copy().items():
86
+ if key.startswith("cf_"):
87
+ custom_fields[key[3:]] = value
88
+ zone_data.pop(key)
89
+ if custom_fields:
90
+ zone_data["custom_field_data"] = custom_fields
91
+
84
92
  zone_data.pop("template", None)
85
93
  zone_data.pop("tenant_group", None)
86
94
  zone_data.pop("_init_time", None)
@@ -97,10 +105,7 @@ class ZoneTemplateUpdateMixin:
97
105
  raise RollbackTransaction
98
106
 
99
107
  except ValidationError as exc:
100
- if isinstance(exc, dict):
101
- template_error = item.value()
102
- else:
103
- template_error = [exc]
108
+ self.add_error("template", exc.messages)
104
109
  except RollbackTransaction:
105
110
  pass
106
111
 
@@ -4,6 +4,8 @@ import strawberry
4
4
  import strawberry_django
5
5
 
6
6
  from netbox.graphql.types import NetBoxObjectType
7
+ from tenancy.graphql.types import TenantType
8
+ from ipam.graphql.types import IPAddressType, PrefixType
7
9
  from netbox.graphql.scalars import BigInt
8
10
 
9
11
  from netbox_dns.models import (
@@ -7,7 +7,7 @@ from ipam.models import IPAddress
7
7
 
8
8
 
9
9
  class Command(BaseCommand):
10
- help = "Setup IPAddress custom fields for IPAM AutoDNS"
10
+ help = "Setup IPAddress custom fields for IPAM DNSsync"
11
11
 
12
12
  def add_arguments(self, parser):
13
13
  parser.add_argument(
@@ -19,7 +19,7 @@ class Command(BaseCommand):
19
19
 
20
20
  if options.get("remove"):
21
21
  if options.get("verbosity"):
22
- self.stdout.write(f"Trying to remove IPAM AutoDNS custom fields")
22
+ self.stdout.write("Trying to remove IPAM DNSsync custom fields")
23
23
  for cf in (
24
24
  "ipaddress_dns_disabled",
25
25
  "ipaddress_dns_record_ttl",
@@ -30,7 +30,7 @@ class Command(BaseCommand):
30
30
  name=cf, object_types=ipaddress_object_type
31
31
  ).delete()
32
32
  if options.get("verbosity"):
33
- self.stdout.write(f"Custom field '{cf}' removed")
33
+ self.stdout.write(f"Removed custom field '{cf}'")
34
34
  except CustomField.DoesNotExist:
35
35
  pass
36
36
  return
@@ -39,7 +39,7 @@ class Command(BaseCommand):
39
39
  # Remove pre-existing IPAM Coupling custom fields
40
40
  # -
41
41
  if options.get("verbosity") >= 2:
42
- self.stdout.write(f"Trying to remove obsolete IPAM Coupling custom fields")
42
+ self.stdout.write("Trying to remove obsolete IPAM Coupling custom fields")
43
43
  for cf in (
44
44
  "ipaddress_dns_record_name",
45
45
  "ipaddress_dns_zone_id",
@@ -54,25 +54,25 @@ class Command(BaseCommand):
54
54
  pass
55
55
 
56
56
  if options.get("verbosity") >= 2:
57
- self.stdout.write(f"Creating IPAM AutoDNS custom fields")
57
+ self.stdout.write("Creating IPAM DNSsync custom fields")
58
58
 
59
59
  if not CustomField.objects.filter(
60
60
  name="ipaddress_dns_disabled",
61
61
  type=CustomFieldTypeChoices.TYPE_BOOLEAN,
62
62
  object_types=ipaddress_object_type,
63
63
  ).exists():
64
- cf_autodns_disabled = CustomField.objects.create(
64
+ cf_dnssync_disabled = CustomField.objects.create(
65
65
  name="ipaddress_dns_disabled",
66
- label="Disable AutoDNS",
66
+ label="Disable DNSsync",
67
67
  description="Disable DNS address and pointer record generation for this address",
68
68
  type=CustomFieldTypeChoices.TYPE_BOOLEAN,
69
69
  required=False,
70
70
  default=False,
71
- group_name="AutoDNS",
71
+ group_name="DNSsync",
72
72
  is_cloneable=True,
73
73
  weight=100,
74
74
  )
75
- cf_autodns_disabled.object_types.set([ipaddress_object_type])
75
+ cf_dnssync_disabled.object_types.set([ipaddress_object_type])
76
76
  if options.get("verbosity"):
77
77
  self.stdout.write("Created custom field 'ipaddress_dns_disabled'")
78
78
 
@@ -82,8 +82,9 @@ class Command(BaseCommand):
82
82
  type=CustomFieldTypeChoices.TYPE_INTEGER,
83
83
  object_types=ipaddress_object_type,
84
84
  )
85
- if cf_ttl.group_name != "AutoDNS":
86
- cf_ttl.group_name = "AutoDNS"
85
+ if cf_ttl.group_name != "DNSsync":
86
+ cf_ttl.group_name = "DNSsync"
87
+ cf_ttl.description = ("TTL for DNS records created for this address",)
87
88
  cf_ttl.save()
88
89
  if options.get("verbosity"):
89
90
  self.stdout.write("Updated custom field 'ipaddress_dns_record_ttl'")
@@ -96,7 +97,7 @@ class Command(BaseCommand):
96
97
  validation_minimum=0,
97
98
  validation_maximum=2147483647,
98
99
  required=False,
99
- group_name="AutoDNS",
100
+ group_name="DNSsync",
100
101
  is_cloneable=True,
101
102
  weight=200,
102
103
  )
@@ -110,8 +111,11 @@ class Command(BaseCommand):
110
111
  type=CustomFieldTypeChoices.TYPE_BOOLEAN,
111
112
  object_types=ipaddress_object_type,
112
113
  )
113
- if cf_disable_ptr.group_name != "AutoDNS":
114
- cf_disable_ptr.group_name = "AutoDNS"
114
+ if cf_disable_ptr.group_name != "DNSsync":
115
+ cf_disable_ptr.group_name = "DNSsync"
116
+ cf_disable_ptr.description = (
117
+ "Disable DNS PTR record generation for this address",
118
+ )
115
119
  cf_disable_ptr.save()
116
120
  if options.get("verbosity"):
117
121
  self.stdout.write(
@@ -125,7 +129,7 @@ class Command(BaseCommand):
125
129
  type=CustomFieldTypeChoices.TYPE_BOOLEAN,
126
130
  required=False,
127
131
  default=False,
128
- group_name="AutoDNS",
132
+ group_name="DNSsync",
129
133
  is_cloneable=True,
130
134
  weight=300,
131
135
  )
@@ -58,7 +58,7 @@ def record_data_from_ip_address(ip_address, zone):
58
58
  RecordStatusChoices.STATUS_ACTIVE
59
59
  if ip_address.status
60
60
  in settings.PLUGINS_CONFIG["netbox_dns"].get(
61
- "autodns_ipaddress_active_status", []
61
+ "dnssync_ipaddress_active_status", []
62
62
  )
63
63
  else RecordStatusChoices.STATUS_INACTIVE
64
64
  ),
@@ -461,8 +461,11 @@ class Record(ObjectModificationMixin, NetBoxModel):
461
461
  self.rfc2317_cname_record.delete(save_zone_serial=save_zone_serial)
462
462
  self.rfc2317_cname_record = None
463
463
 
464
- def update_from_ip_address(self, ip_address):
465
- data = record_data_from_ip_address(ip_address, self.zone)
464
+ def update_from_ip_address(self, ip_address, zone=None):
465
+ if zone is None:
466
+ zone = self.zone
467
+
468
+ data = record_data_from_ip_address(ip_address, zone)
466
469
 
467
470
  if data is None:
468
471
  self.delete()
@@ -490,9 +493,12 @@ class Record(ObjectModificationMixin, NetBoxModel):
490
493
  **data,
491
494
  )
492
495
 
493
- def validate_name(self):
496
+ def validate_name(self, new_zone=None):
497
+ if new_zone is None:
498
+ new_zone = self.zone
499
+
494
500
  try:
495
- _zone = dns_name.from_text(self.zone.name, origin=dns_name.root)
501
+ _zone = dns_name.from_text(new_zone.name, origin=dns_name.root)
496
502
  name = dns_name.from_text(self.name, origin=None)
497
503
  fqdn = dns_name.from_text(self.name, origin=_zone)
498
504
 
@@ -512,7 +518,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
512
518
  if not fqdn.is_subdomain(_zone):
513
519
  raise ValidationError(
514
520
  {
515
- "name": f"{self.name} is not a name in {self.zone.name}",
521
+ "name": f"{self.name} is not a name in {new_zone.name}",
516
522
  }
517
523
  )
518
524
 
@@ -544,15 +550,18 @@ class Record(ObjectModificationMixin, NetBoxModel):
544
550
  except ValidationError as exc:
545
551
  raise ValidationError({"value": exc}) from None
546
552
 
547
- def check_unique_record(self):
553
+ def check_unique_record(self, new_zone=None):
548
554
  if not get_plugin_config("netbox_dns", "enforce_unique_records", False):
549
555
  return
550
556
 
551
557
  if not self.is_active:
552
558
  return
553
559
 
560
+ if new_zone is None:
561
+ new_zone = self.zone
562
+
554
563
  records = Record.objects.filter(
555
- zone=self.zone,
564
+ zone=new_zone,
556
565
  name=self.name,
557
566
  type=self.type,
558
567
  value=self.value,
@@ -567,7 +576,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
567
576
  if not records.filter(
568
577
  ipam_ip_address__isnull=True
569
578
  ).exists() or get_plugin_config(
570
- "netbox_dns", "autodns_conflict_deactivate", False
579
+ "netbox_dns", "dnssync_conflict_deactivate", False
571
580
  ):
572
581
  return
573
582
 
@@ -581,7 +590,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
581
590
  if self.ipam_ip_address is None or not self.is_active:
582
591
  return
583
592
 
584
- if not get_plugin_config("netbox_dns", "autodns_conflict_deactivate", False):
593
+ if not get_plugin_config("netbox_dns", "dnssync_conflict_deactivate", False):
585
594
  return
586
595
 
587
596
  records = Record.objects.filter(
@@ -624,7 +633,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
624
633
  if not records.exists():
625
634
  return
626
635
 
627
- conflicting_ttls = ", ".join(set(str(record.ttl) for record in records))
636
+ conflicting_ttls = ", ".join({str(record.ttl) for record in records})
628
637
  raise ValidationError(
629
638
  {
630
639
  "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})."
@@ -664,10 +673,10 @@ class Record(ObjectModificationMixin, NetBoxModel):
664
673
  self.type = self.type.upper()
665
674
  super().clean_fields(*args, **kwargs)
666
675
 
667
- def clean(self, *args, **kwargs):
668
- self.validate_name()
676
+ def clean(self, *args, new_zone=None, **kwargs):
677
+ self.validate_name(new_zone=new_zone)
669
678
  self.validate_value()
670
- self.check_unique_record()
679
+ self.check_unique_record(new_zone=new_zone)
671
680
  if self.pk is None:
672
681
  self.check_unique_rrset_ttl()
673
682
 
netbox_dns/models/zone.py CHANGED
@@ -654,13 +654,18 @@ class Zone(ObjectModificationMixin, NetBoxModel):
654
654
  )
655
655
 
656
656
  if old_zone.name != self.name or old_zone.view != self.view:
657
- update_ip_addresses = IPAddress.objects.filter(
658
- pk__in=self.record_set.filter(
657
+ for ip_address in get_ip_addresses_by_zone(self):
658
+ try:
659
+ check_dns_records(ip_address, zone=self)
660
+ except ValidationError as exc:
661
+ raise ValidationError(exc.messages)
662
+
663
+ ip_addresses = IPAddress.objects.filter(
664
+ netbox_dns_records__in=self.record_set.filter(
659
665
  ipam_ip_address__isnull=False
660
- ).values_list("ipam_ip_address", flat=True)
666
+ )
661
667
  )
662
- update_ip_addresses |= get_ip_addresses_by_zone(self)
663
- for ip_address in update_ip_addresses:
668
+ for ip_address in ip_addresses:
664
669
  try:
665
670
  check_dns_records(ip_address, zone=self)
666
671
  except ValidationError as exc:
@@ -1,5 +1,6 @@
1
1
  from netaddr import IPNetwork
2
2
 
3
+ from django.conf import settings
3
4
  from django.dispatch import receiver
4
5
  from django.db.models.signals import pre_delete, pre_save, post_save, m2m_changed
5
6
  from django.core.exceptions import ValidationError
@@ -17,21 +18,55 @@ from netbox_dns.utilities import (
17
18
  delete_dns_records,
18
19
  get_views_by_prefix,
19
20
  get_ip_addresses_by_prefix,
20
- get_ip_addresses_by_view,
21
21
  )
22
22
 
23
- AUTODNS_CUSTOM_FIELDS = {
23
+ DNSSYNC_CUSTOM_FIELDS = {
24
24
  "ipaddress_dns_disabled": False,
25
25
  "ipaddress_dns_record_ttl": None,
26
26
  "ipaddress_dns_record_disable_ptr": False,
27
27
  }
28
28
 
29
+ IPADDRESS_ACTIVE_STATUS = settings.PLUGINS_CONFIG["netbox_dns"][
30
+ "dnssync_ipaddress_active_status"
31
+ ]
32
+ ENFORCE_UNIQUE_RECORDS = settings.PLUGINS_CONFIG["netbox_dns"]["enforce_unique_records"]
33
+
29
34
 
30
35
  @receiver(post_clean, sender=IPAddress)
31
- def ipam_autodns_ipaddress_post_clean(instance, **kwargs):
36
+ def ipam_dnssync_ipaddress_post_clean(instance, **kwargs):
32
37
  if not isinstance(instance.address, IPNetwork):
33
38
  return
34
39
 
40
+ if instance.custom_field_data.get("ipaddress_dns_disabled"):
41
+ return
42
+
43
+ # +
44
+ # Check for uniqueness of IP address and dns_name. If unique records are
45
+ # enforced, report an error when trying to create the same IP address with
46
+ # the same dns_name. Ignore existing IP addresses that have their CF
47
+ # "ipaddress_dns_disabled" set to "True".
48
+ # -
49
+ duplicate_addresses = IPAddress.objects.filter(
50
+ address=instance.address,
51
+ vrf=instance.vrf,
52
+ dns_name=instance.dns_name,
53
+ status__in=IPADDRESS_ACTIVE_STATUS,
54
+ )
55
+ if instance.pk is not None:
56
+ duplicate_addresses = duplicate_addresses.exclude(pk=instance.pk)
57
+
58
+ if ENFORCE_UNIQUE_RECORDS and instance.status in IPADDRESS_ACTIVE_STATUS:
59
+ for ip_address in duplicate_addresses.only("custom_field_data"):
60
+ if not ip_address.custom_field_data.get("ipaddress_dns_disabled"):
61
+ raise ValidationError(
62
+ {
63
+ "dns_name": "Unique DNS records are enforced and there is already "
64
+ f"an active IP address {instance.address} with DNS name {instance.dns_name}. "
65
+ "Plesase choose a different name or disable record creation for this "
66
+ "IP address."
67
+ }
68
+ )
69
+
35
70
  # +
36
71
  # Check NetBox DNS record permission for changes to IPAddress custom fields
37
72
  #
@@ -42,24 +77,26 @@ def ipam_autodns_ipaddress_post_clean(instance, **kwargs):
42
77
  instance.pk is not None
43
78
  and any(
44
79
  (
45
- cf_data.get(cf)
46
- != IPAddress.objects.get(pk=instance.pk).custom_field_data.get(cf)
47
- for cf in AUTODNS_CUSTOM_FIELDS.keys()
80
+ cf_data.get(cf, cf_default)
81
+ != IPAddress.objects.get(pk=instance.pk).custom_field_data.get(
82
+ cf, cf_default
83
+ )
84
+ for cf, cf_default in DNSSYNC_CUSTOM_FIELDS.items()
48
85
  )
49
86
  )
50
- and not check_record_permission(request)
87
+ and not check_record_permission()
51
88
  ) or (
52
89
  instance.pk is None
53
90
  and any(
54
91
  (
55
- cf_data.get(cf) != cf_default
56
- for cf, cf_default in AUTODNS_CUSTOM_FIELDS.items()
92
+ cf_data.get(cf, cf_default) != cf_default
93
+ for cf, cf_default in DNSSYNC_CUSTOM_FIELDS.items()
57
94
  )
58
95
  )
59
- and not check_record_permission(request, change=False, delete=False)
96
+ and not check_record_permission(change=False, delete=False)
60
97
  ):
61
98
  raise ValidationError(
62
- f"User '{request.user}' is not allowed to alter AutoDNS custom fields"
99
+ f"User '{request.user}' is not allowed to alter DNSsync custom fields"
63
100
  )
64
101
 
65
102
  try:
@@ -69,38 +106,51 @@ def ipam_autodns_ipaddress_post_clean(instance, **kwargs):
69
106
 
70
107
 
71
108
  @receiver(pre_delete, sender=IPAddress)
72
- def ipam_autodns_ipaddress_pre_delete(instance, **kwargs):
109
+ def ipam_dnssync_ipaddress_pre_delete(instance, **kwargs):
73
110
  delete_dns_records(instance)
74
111
 
75
112
 
76
113
  @receiver(pre_save, sender=IPAddress)
77
- def ipam_autodns_ipaddress_pre_save(instance, **kwargs):
114
+ def ipam_dnssync_ipaddress_pre_save(instance, **kwargs):
78
115
  check_dns_records(instance)
79
116
 
80
117
 
81
118
  @receiver(post_save, sender=IPAddress)
82
- def ipam_autodns_ipaddress_post_save(instance, **kwargs):
119
+ def ipam_dnssync_ipaddress_post_save(instance, **kwargs):
83
120
  update_dns_records(instance)
84
121
 
85
122
 
86
123
  @receiver(pre_save, sender=Prefix)
87
- def ipam_autodns_prefix_pre_save(instance, **kwargs):
124
+ def ipam_dnssync_prefix_pre_save(instance, **kwargs):
88
125
  """
89
126
  Changes that modify the prefix hierarchy cannot be validated properly before
90
- commiting them. So the solution in this case is to remove a prefix whose
91
- VRF or network has changed from all views it currently is assigned to.
127
+ commiting them. So the solution in this case is to ask the user to deassign
128
+ the prefix from any views it is assigned to and retry.
92
129
  """
130
+ request = current_request.get()
131
+
93
132
  if instance.pk is None or not instance.netbox_dns_views.exists():
94
133
  return
95
134
 
96
- saved_prefix = Prefix.objects.get(pk=instance.pk)
135
+ saved_prefix = Prefix.objects.prefetch_related("netbox_dns_views").get(
136
+ pk=instance.pk
137
+ )
97
138
  if saved_prefix.prefix != instance.prefix or saved_prefix.vrf != instance.vrf:
98
- for view in saved_prefix.netbox_dns_views.all():
99
- view.prefixes.remove(saved_prefix)
139
+ dns_views = ", ".join([view.name for view in instance.netbox_dns_views.all()])
140
+ if request is not None:
141
+ raise AbortRequest(
142
+ f"This prefix is currently assigned to the following DNS views: {dns_views}"
143
+ f"Please deassign it from these views before making changes to the prefix "
144
+ f"or VRF."
145
+ )
146
+
147
+ raise ValidationError(
148
+ f"Prefix is assigned to DNS views {dns_views}. Prefix and VRF must not be changed"
149
+ )
100
150
 
101
151
 
102
152
  @receiver(pre_delete, sender=Prefix)
103
- def ipam_autodns_prefix_pre_delete(instance, **kwargs):
153
+ def ipam_dnssync_prefix_pre_delete(instance, **kwargs):
104
154
  parent = instance.get_parents().last()
105
155
  request = current_request.get()
106
156
 
@@ -144,7 +194,7 @@ def ipam_autodns_prefix_pre_delete(instance, **kwargs):
144
194
 
145
195
 
146
196
  @receiver(m2m_changed, sender=_view.View.prefixes.through)
147
- def ipam_autodns_view_prefix_changed(**kwargs):
197
+ def ipam_dnssync_view_prefix_changed(**kwargs):
148
198
  action = kwargs.get("action")
149
199
  request = current_request.get()
150
200
 
@@ -0,0 +1,11 @@
1
+ import django_tables2 as tables
2
+
3
+ from ipam.tables import PrefixTable
4
+ from utilities.tables import register_table_column
5
+
6
+ views = tables.ManyToManyColumn(
7
+ verbose_name="DNS Views",
8
+ linkify_item=True,
9
+ )
10
+
11
+ register_table_column(views, "netbox_dns_views", PrefixTable)
@@ -1,9 +1,10 @@
1
1
  from django.conf import settings
2
+ from django.urls import reverse
2
3
 
3
4
  from netbox.plugins.utils import get_plugin_config
4
5
  from netbox.plugins import PluginTemplateExtension
5
6
 
6
- from netbox_dns.models import Record, Zone, View, NameServer
7
+ from netbox_dns.models import Record
7
8
  from netbox_dns.choices import RecordTypeChoices
8
9
  from netbox_dns.tables import RelatedRecordTable, RelatedViewTable
9
10
  from netbox_dns.utilities import get_views_by_prefix
@@ -63,6 +64,17 @@ class RelatedDNSViews(PluginTemplateExtension):
63
64
  extra_context=context,
64
65
  )
65
66
 
67
+ def buttons(self):
68
+ return self.render(
69
+ "netbox_dns/view/button.html",
70
+ extra_context={
71
+ "url": reverse(
72
+ "plugins:netbox_dns:prefix_views",
73
+ kwargs={"pk": self.context.get("object").pk},
74
+ ),
75
+ },
76
+ )
77
+
66
78
 
67
79
  class IPRelatedDNSRecords(PluginTemplateExtension):
68
80
  model = "ipam.ipaddress"
@@ -102,7 +114,7 @@ class IPRelatedDNSRecords(PluginTemplateExtension):
102
114
  )
103
115
 
104
116
 
105
- if not settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
117
+ if not settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
106
118
  template_extensions = [RelatedDNSRecords, RelatedDNSViews]
107
119
  else:
108
120
  template_extensions = []
@@ -0,0 +1,9 @@
1
+ {% load perms %}
2
+
3
+ {% if perms.netbox_dns.change_view %}
4
+ <a href="{{ url }}?return_url={{ object.get_absolute_url }}">
5
+ <button type="submit" class="btn btn-primary" name="assign-view">
6
+ <i class="mdi mdi-eye-outline" aria-hidden="true"></i> DNS Views
7
+ </button>
8
+ </a>
9
+ {% endif %}
@@ -0,0 +1,41 @@
1
+ {% extends 'generic/_base.html' %}
2
+
3
+ {% block title %}
4
+ Configure DNS views for {{ object|meta:"verbose_name" }} {{ object }} {% if object.vrf %}[{{ object.vrf }}]{% endif %}
5
+ {% endblock title %}
6
+
7
+ {% block content %}
8
+ <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
9
+
10
+ <form action="" method="post" enctype="multipart/form-data" class="object-edit mt-5">
11
+ {% csrf_token %}
12
+
13
+ <div id="form_fields" hx-disinherit="hx-select hx-swap">
14
+ {% if inherited_from %}
15
+ <div class="card">
16
+ <table class="table table-hover attr-table">
17
+ <th>Views inherited from prefix {{ inherited_from }} {% if inherited_from.vrf %}[{{ inherited_from.vrf }}] {% endif %}</th>
18
+ {% for view in inherited_views %}
19
+ <tr><td>{{ view|linkify }}</td><td>{{ view.description }}</td></tr>
20
+ {% endfor %}
21
+ </table>
22
+ </div>
23
+ {% endif %}
24
+ {% block form %}
25
+ {% include 'htmx/form.html' %}
26
+ {% endblock form %}
27
+ </div>
28
+
29
+ <div class="text-end my-3">
30
+ {% block buttons %}
31
+ <a href="{{ return_url }}" class="btn btn-outline-secondary">Cancel</a>
32
+ <button type="submit" name="_update" class="btn btn-primary">Save</button>
33
+ {% endblock buttons %}
34
+ </div>
35
+ </form>
36
+ </div>
37
+ {% endblock content %}
38
+
39
+ {% block modals %}
40
+ {% include 'inc/htmx_modal.html' with size='lg' %}
41
+ {% endblock %}
@@ -35,7 +35,7 @@
35
35
  </div>
36
36
  <div class="col col-md-6">
37
37
  {% include 'inc/panels/tags.html' %}
38
- {% if not settings.PLUGINS_CONFIG.netbox_dns.autodns_disabled %}
38
+ {% if not settings.PLUGINS_CONFIG.netbox_dns.dnssync_disabled %}
39
39
  <div class="card">
40
40
  <h5 class="card-header">
41
41
  IPAM Prefixes
netbox_dns/urls/view.py CHANGED
@@ -12,6 +12,7 @@ from netbox_dns.views import (
12
12
  ViewBulkEditView,
13
13
  ViewBulkDeleteView,
14
14
  ViewZoneListView,
15
+ ViewPrefixEditView,
15
16
  )
16
17
 
17
18
  view_urlpatterns = [
@@ -36,4 +37,9 @@ view_urlpatterns = [
36
37
  name="view_changelog",
37
38
  kwargs={"model": View},
38
39
  ),
40
+ path(
41
+ "prefixes/<int:pk>/assign-views/",
42
+ ViewPrefixEditView.as_view(),
43
+ name="prefix_views",
44
+ ),
39
45
  ]
@@ -1,2 +1,2 @@
1
1
  from .conversions import *
2
- from .ipam_autodns import *
2
+ from .ipam_dnssync import *
@@ -13,6 +13,7 @@ from ipam.models import IPAddress, Prefix
13
13
  from netbox_dns.models import zone as _zone
14
14
  from netbox_dns.models import record as _record
15
15
  from netbox_dns.models import view as _view
16
+ from netbox_dns.choices import RecordStatusChoices
16
17
 
17
18
 
18
19
  __all__ = (
@@ -46,13 +47,19 @@ def _get_record_status(ip_address):
46
47
  RecordStatusChoices.STATE_ACTIVE
47
48
  if ip_address.status
48
49
  in settings.PLUGINS_CONFIG["netbox_dns"].get(
49
- "autodns_ipaddress_active_status", []
50
+ "dnssync_ipaddress_active_status", []
50
51
  )
51
52
  else RecordStatusChoices.STATUS_INACTIVE
52
53
  )
53
54
 
54
55
 
55
- def get_zones(ip_address, view=None):
56
+ def _valid_entry(ip_address, zone):
57
+ return zone.view in _get_assigned_views(ip_address) and dns_name.from_text(
58
+ ip_address.dns_name
59
+ ).is_subdomain(dns_name.from_text(zone.name))
60
+
61
+
62
+ def get_zones(ip_address, view=None, old_zone=None):
56
63
  if view is None:
57
64
  views = _get_assigned_views(ip_address)
58
65
  if not views:
@@ -72,10 +79,13 @@ def get_zones(ip_address, view=None):
72
79
  active=True,
73
80
  )
74
81
 
75
- if not zones:
76
- return []
77
-
78
82
  zone_map = defaultdict(list)
83
+
84
+ if old_zone is not None:
85
+ zones = zones.exclude(pk=old_zone.pk)
86
+ if _valid_entry(ip_address, old_zone):
87
+ zone_map[old_zone.view].append(old_zone)
88
+
79
89
  for zone in zones:
80
90
  zone_map[zone.view].append(zone)
81
91
 
@@ -91,35 +101,49 @@ def check_dns_records(ip_address, zone=None, view=None):
91
101
 
92
102
  if zone is None:
93
103
  zones = get_zones(ip_address, view=view)
94
- else:
95
- zones = [zone]
96
-
97
- if ip_address.pk is not None:
98
- for record in ip_address.netbox_dns_records.filter(zone__in=zones):
99
- if (
100
- record.fqdn != ip_address.dns_name
101
- or record.value != ip_address.address.ip
102
- or record.status != _get_record_status(ip_address)
103
- ):
104
- record.update_from_ip_address(ip_address)
105
104
 
106
- if record is not None:
107
- record.clean()
105
+ if ip_address.pk is not None:
106
+ for record in ip_address.netbox_dns_records.filter(zone__in=zones):
107
+ if (
108
+ record.fqdn != ip_address.dns_name
109
+ or record.value != ip_address.address.ip
110
+ or record.status != _get_record_status(ip_address)
111
+ ):
112
+ record.update_from_ip_address(ip_address)
113
+
114
+ if record is not None:
115
+ record.clean()
116
+
117
+ zones = _zone.Zone.objects.filter(
118
+ pk__in=[zone.pk for zone in zones]
119
+ ).exclude(
120
+ pk__in=set(
121
+ ip_address.netbox_dns_records.all().values_list("zone", flat=True)
122
+ )
123
+ )
108
124
 
109
- zones = _zone.Zone.objects.filter(pk__in=[zone.pk for zone in zones]).exclude(
110
- pk__in=set(
111
- ip_address.netbox_dns_records.all().values_list("zone", flat=True)
125
+ for zone in zones:
126
+ record = _record.Record.create_from_ip_address(
127
+ ip_address,
128
+ zone,
112
129
  )
113
- )
114
130
 
115
- for zone in zones:
116
- record = _record.Record.create_from_ip_address(
117
- ip_address,
118
- zone,
119
- )
131
+ if record is not None:
132
+ record.clean()
133
+
134
+ if ip_address.pk is None:
135
+ return
136
+
137
+ try:
138
+ new_zone = get_zones(ip_address, old_zone=zone)[0]
139
+ except IndexError:
140
+ return
141
+
142
+ for record in ip_address.netbox_dns_records.filter(zone=zone):
143
+ record.update_from_ip_address(ip_address, new_zone)
120
144
 
121
145
  if record is not None:
122
- record.clean()
146
+ record.clean(new_zone=new_zone)
123
147
 
124
148
 
125
149
  def update_dns_records(ip_address):
@@ -240,7 +264,7 @@ def get_ip_addresses_by_zone(zone):
240
264
 
241
265
 
242
266
  def check_record_permission(add=True, change=True, delete=True):
243
- checks = locals()
267
+ checks = locals().copy()
244
268
 
245
269
  request = current_request.get()
246
270
 
@@ -249,8 +273,8 @@ def check_record_permission(add=True, change=True, delete=True):
249
273
 
250
274
  return all(
251
275
  (
252
- request.user.has_perm(f"nebox_dns.{perm}_record")
253
- for perm, check in locals().items()
276
+ request.user.has_perm(f"netbox_dns.{perm}_record")
277
+ for perm, check in checks.items()
254
278
  if check
255
279
  )
256
280
  )
@@ -90,11 +90,11 @@ class RecordView(generic.ObjectView):
90
90
  zone=parent_zone,
91
91
  )
92
92
  cname_records = cname_records.union(
93
- set(
93
+ {
94
94
  record
95
95
  for record in parent_cname_records
96
96
  if record.value_fqdn == instance.fqdn
97
- )
97
+ }
98
98
  )
99
99
 
100
100
  if cname_records:
netbox_dns/views/view.py CHANGED
@@ -1,11 +1,19 @@
1
1
  from utilities.views import ViewTab, register_model_view
2
2
 
3
3
  from netbox.views import generic
4
+ from ipam.models import Prefix
4
5
 
5
6
  from netbox_dns.models import View, Zone
6
7
  from netbox_dns.filtersets import ViewFilterSet, ZoneFilterSet
7
- from netbox_dns.forms import ViewForm, ViewFilterForm, ViewImportForm, ViewBulkEditForm
8
+ from netbox_dns.forms import (
9
+ ViewForm,
10
+ ViewFilterForm,
11
+ ViewImportForm,
12
+ ViewBulkEditForm,
13
+ ViewPrefixEditForm,
14
+ )
8
15
  from netbox_dns.tables import ViewTable, ZoneTable
16
+ from netbox_dns.utilities import get_views_by_prefix
9
17
 
10
18
 
11
19
  __all__ = (
@@ -17,6 +25,7 @@ __all__ = (
17
25
  "ViewBulkEditView",
18
26
  "ViewBulkDeleteView",
19
27
  "ViewZoneListView",
28
+ "ViewPrefixEditView",
20
29
  )
21
30
 
22
31
 
@@ -61,6 +70,22 @@ class ViewBulkDeleteView(generic.BulkDeleteView):
61
70
  table = ViewTable
62
71
 
63
72
 
73
+ class ViewPrefixEditView(generic.ObjectEditView):
74
+ queryset = Prefix.objects.all()
75
+ form = ViewPrefixEditForm
76
+ template_name = "netbox_dns/view/prefix.html"
77
+
78
+ def get_extra_context(self, request, instance):
79
+ parents = instance.get_parents()
80
+ if parents:
81
+ return {
82
+ "inherited_views": get_views_by_prefix(parents.last()),
83
+ "inherited_from": parents.filter(netbox_dns_views__isnull=False).last(),
84
+ }
85
+
86
+ return {}
87
+
88
+
64
89
  @register_model_view(View, "zones")
65
90
  class ViewZoneListView(generic.ObjectChildrenView):
66
91
  queryset = View.objects.all().prefetch_related("zone_set")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: netbox-plugin-dns
3
- Version: 1.1.0b2
3
+ Version: 1.1.0b4
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
@@ -1,4 +1,4 @@
1
- netbox_dns/__init__.py,sha256=Jdvrhj7X8a6ZTLJf0EYzWJyhawP_uJc3GSdg5USr3nw,1835
1
+ netbox_dns/__init__.py,sha256=o6LleCsdMgaES4LaRbzvKSTH7jBS01AoIZNiKwxv97Y,1880
2
2
  netbox_dns/api/nested_serializers.py,sha256=-ZhAiyf-8UHlkcBomBp1J7ci1dSwrxWRbbfskD-D_yQ,3172
3
3
  netbox_dns/api/serializers.py,sha256=u-kQurUftGkUGAMh-VkMgXPebLYeZq9WDz9uKzkk2No,370
4
4
  netbox_dns/api/serializers_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -25,7 +25,7 @@ netbox_dns/fields/rfc2317.py,sha256=24qNNLbI-SGlsKqGaLNaVk8EHFrju65YTET3O-8XgTc,
25
25
  netbox_dns/filtersets/__init__.py,sha256=zvHYWy23FFmK4vxLpoMo-OD5OQBtcTUV_HG-5LCtvQE,197
26
26
  netbox_dns/filtersets/contact.py,sha256=VnlNX6dyUlEbj7tz9cgRKSWQOdg7OqP32cD2IyD5hu8,1091
27
27
  netbox_dns/filtersets/nameserver.py,sha256=I7RoIUcgXyIoMrhuS0dJr8uPi0AdNj6D3G6t2oSiQ7s,1147
28
- netbox_dns/filtersets/record.py,sha256=nr-oLCnaceiQua5tL-_teYj54X-VMXQeYihbCjykaJY,3750
28
+ netbox_dns/filtersets/record.py,sha256=Jt3RlI4MIfeY5xYkLse1y1rzmKCdmuk-CRAxxaNVYbY,3750
29
29
  netbox_dns/filtersets/record_template.py,sha256=jGSjGFEnNSoxtUd7diV8wEhw8qZclz2dKLSqyVC7Djo,1548
30
30
  netbox_dns/filtersets/registrar.py,sha256=Wh_l-IXRHnJhW7Pyokp3czQZISDKzXnWeSQKp512Drc,977
31
31
  netbox_dns/filtersets/view.py,sha256=sGUhmyr66GY2CVXOFX2g5evDt0nbU6XPPCwptvnicHQ,993
@@ -37,16 +37,16 @@ netbox_dns/forms/nameserver.py,sha256=LHomCHmFcASobaD3Z7yhAyA24h-LrYImVMz-EUXbwK
37
37
  netbox_dns/forms/record.py,sha256=svBVAFy-egDEPLcRWkxNi_1bkabKmWgJ87pmdNt6dh4,7155
38
38
  netbox_dns/forms/record_template.py,sha256=Q77p9sExJ8Xbl-Co2Px2R0At5O3naQJwx4pnino6i2o,5573
39
39
  netbox_dns/forms/registrar.py,sha256=FMnvrcq62R3wNp_2ZUEk3v_PIav0KrWPATaJ7_9KFAo,3758
40
- netbox_dns/forms/view.py,sha256=CSMpWmjKQdfBMILdLLyOjY6o27W3mZgAVIBGjCiPS78,6144
41
- netbox_dns/forms/zone.py,sha256=ZbsYcWX-t1luqBsLj4vec0IZG0lmCwGW5nzVh77qJrw,23164
40
+ netbox_dns/forms/view.py,sha256=DkfZOLkCClTF-7pLMiZE8J1Z9_oxmeBiEWZ7-_yTZro,9045
41
+ netbox_dns/forms/zone.py,sha256=-ww_4nepCwVnsCsJdowtzz2HPmKLqNVedHUi5QC8uoM,23445
42
42
  netbox_dns/forms/zone_template.py,sha256=UNykid5pRB_ydy40j2DzRlBXp3_QAOqdqxdUojKYTd4,8161
43
43
  netbox_dns/graphql/__init__.py,sha256=ZZSsx-VM108tB_FrcVy3uGGhtmePpkXnY5U1ytnoTvE,490
44
44
  netbox_dns/graphql/filters.py,sha256=6Ot_d1e7h5lVXVVBB3hyWUql94K3zsK9Tjb3RVJqluw,1706
45
45
  netbox_dns/graphql/schema.py,sha256=P-oQ8ei3sC6XLhgCa_riRbRTrMkPCVTJXkGv0U2rPYw,2682
46
- netbox_dns/graphql/types.py,sha256=4ewWOqEbWtCBiU9bdIm_6CIm6MKAM6szCAXSvokpqWg,6108
46
+ netbox_dns/graphql/types.py,sha256=Obr9JDsMJlMaJLKbTau7prZUYI6pQj6aIpT-nT4JnZs,6210
47
47
  netbox_dns/management/commands/cleanup_database.py,sha256=kfnyybudwKGigjJmrOwafPWSUasZr9jQsxN4eWAgMvY,5969
48
48
  netbox_dns/management/commands/cleanup_rrset_ttl.py,sha256=UFRURLBcFeGHUS2lrYFv7UWIebjI72aG1EUQJt0XsXw,2046
49
- netbox_dns/management/commands/setup_autodns.py,sha256=kX1T7VjMZ0BPuT_zte6VO6xqOSpYJXaYNfu5dxlrZLc,5507
49
+ netbox_dns/management/commands/setup_dnssync.py,sha256=qtVj6egSjclaQbuI60hLfl-zg89VJVbX-TB17f1k77Y,5730
50
50
  netbox_dns/management/commands/update_soa.py,sha256=Rj_Xk-qpwkAVRubVnM5OqSTwgzi93E0PqjwGb3rYjf0,660
51
51
  netbox_dns/migrations/0001_squashed_netbox_dns_0_15.py,sha256=3U0810NWSHPu2dTSHpfzlleDgwMS04FhJ_CkO76SDaw,10283
52
52
  netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py,sha256=ML6Hp17lrXiaG0eUlBjKMm6HUNhw0AHPnKrb9AN-F6E,20279
@@ -72,17 +72,18 @@ netbox_dns/mixins/object_modification.py,sha256=JbGi8a52wkZ3fFBlfat590CfqRJcEWxB
72
72
  netbox_dns/models/__init__.py,sha256=wjwNsRttUVYQHZODZi806a_iUDoq_o7mdKObqh1N7N4,300
73
73
  netbox_dns/models/contact.py,sha256=oNLyD_6TOTNQQTcCvv6TAC7OkzPTMIRy2NP5nwNKaNg,3009
74
74
  netbox_dns/models/nameserver.py,sha256=yKo4Fwqnv5VtTndU2px7tRS3voF3Cal7OWQ6AImLwl0,3208
75
- netbox_dns/models/record.py,sha256=99Ys3daxvTJuUP6NeTJrEx8Du5C8c70zk8R7S_8YWAs,25703
75
+ netbox_dns/models/record.py,sha256=LgJJvL85hKrN9a3fpq90k9oit8oqNvzwbrA-MVve8DE,25963
76
76
  netbox_dns/models/record_template.py,sha256=3t9VceviX3kNIo5o0VPVFupLFDqPxpHIVLp5U3pBKB4,4661
77
77
  netbox_dns/models/registrar.py,sha256=T_oMUlTWTDixOVlIbEZGvOBdvUrKxRkkS41xgM2Oee8,1557
78
78
  netbox_dns/models/view.py,sha256=SYmhNYyRCv0rSCK5jrHtug4QgfWCBbjsAjZEEHk02QU,2873
79
- netbox_dns/models/zone.py,sha256=clKeoQ7ECYMKwIbqXnszOkuCRGC1_Q6N3q5xTUg8yoY,28905
79
+ netbox_dns/models/zone.py,sha256=C1f6uGKGeD_FKtFhWXiUO7gKF19pzu-9-pj0txP8R1E,29063
80
80
  netbox_dns/models/zone_template.py,sha256=lkiSIfx8KM0Cs3Mb3dLBxKbSpcssVUzQiSmD5W46was,3753
81
81
  netbox_dns/navigation.py,sha256=EITDZkbpu4KCC9u4Noj7OORWnkL3EYT2RIRvYlTw34Q,5961
82
82
  netbox_dns/signals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
- netbox_dns/signals/ipam_autodns.py,sha256=HJkDU7BZHGFb02YlZHtCTRyKQiR3ZxSwempo-NdQ3o4,5779
83
+ netbox_dns/signals/ipam_dnssync.py,sha256=uGK8R_pEKZJ4STTYgw2y5j0g6T0pQZbI9HZLmtfdOwM,7878
84
84
  netbox_dns/tables/__init__.py,sha256=s41w4o77tIwmhnLjsOsg08R9m3wrlomkkfCLTVQuPzc,196
85
85
  netbox_dns/tables/contact.py,sha256=sPs7d1ZhVC5dOS37dPYFqebNd7WGvsV_eYzX_TMcbzY,804
86
+ netbox_dns/tables/ipam_dnssync.py,sha256=7gxf6nXn2zgpnpCr5E5qebXv7QCzrgIqfSzyka53USU,272
86
87
  netbox_dns/tables/nameserver.py,sha256=fFiE-yH-_GyRDaV4SVw094r6uH58Kx56NSWDGaMR58g,764
87
88
  netbox_dns/tables/record.py,sha256=0Yg0qwZ8Vjz6pkZnmof4ZK1Hsvk9DNEzmJwoIwJZJFQ,3189
88
89
  netbox_dns/tables/record_template.py,sha256=16Lu-WDjs2m9Uxx6WkURL39NlyJ8lWzj5Kl6C6lz3E8,2159
@@ -90,7 +91,7 @@ netbox_dns/tables/registrar.py,sha256=M-ckyQUs6dqjTCPf7bAr6UuLEA-q9f9CxKW7yp3rGo
90
91
  netbox_dns/tables/view.py,sha256=jf2S4TiOdMq6-wWk0ndR1uBJpkOx_f3pqAuM1nSXTBo,1178
91
92
  netbox_dns/tables/zone.py,sha256=IeCiflrQBn1INV_PxoTySWQrDalykY4mDSG76VXC5WM,1877
92
93
  netbox_dns/tables/zone_template.py,sha256=70hvS-xpeaLkcM6y0R9xsUMQVKgTgZJaWWNd99BfmzI,1479
93
- netbox_dns/template_content.py,sha256=Lhrse5_Aw6L5xaemTD8-ZlTyFqJ-fVqp97TSRT6E7ek,3390
94
+ netbox_dns/template_content.py,sha256=YzE-ZJlERhFybrUKJrmNwHk8_8RPNexkV66x62Sbzic,3718
94
95
  netbox_dns/templates/netbox_dns/contact.html,sha256=fMHAQyLXIxohKoCTxFEnKetl9UVXeQgjasfpv_JONaw,2855
95
96
  netbox_dns/templates/netbox_dns/nameserver.html,sha256=DpTdetQVV_jKThDbi62LvbhiCay-1QxR-yiJEiPFm4w,1554
96
97
  netbox_dns/templates/netbox_dns/record/managed.html,sha256=G6LPG1koUGuzUiwYdv1okdVa4sKaofiQegDBnsFL0kA,89
@@ -98,8 +99,10 @@ netbox_dns/templates/netbox_dns/record/related.html,sha256=Aqor8uGcuHQTHjlX-Xmni
98
99
  netbox_dns/templates/netbox_dns/record.html,sha256=o3z_D6Fqqn7nx1IwPXKQ75ZaPhU6kae0WpaWa3UMcxQ,5211
99
100
  netbox_dns/templates/netbox_dns/recordtemplate.html,sha256=9tkXtKqa5p3LdOU9REm99WSFwGJaH8OczpIqXZuXMcg,3099
100
101
  netbox_dns/templates/netbox_dns/registrar.html,sha256=O5veGmW59Pf5yN25ihPLvRIkA2P7xmSGv0G3NrRG8vI,2152
102
+ netbox_dns/templates/netbox_dns/view/button.html,sha256=oXKNyPtY8XIu2sxtZWpFRXKXv862407ESyUQ4YsWCGE,292
103
+ netbox_dns/templates/netbox_dns/view/prefix.html,sha256=HD8f4mnbzFOXDj3Y_yq8yEeDpz_yFud8ZMpqbxzCEnA,1445
101
104
  netbox_dns/templates/netbox_dns/view/related.html,sha256=W9Ie2aOsFkWyYtBnZn38seQDBmyJkV9dqFDG-Dq3yMk,736
102
- netbox_dns/templates/netbox_dns/view.html,sha256=NSEfPSHPLw5yjUSat9N_KYKF5FezmTlCXqPC6FYiK9E,2479
105
+ netbox_dns/templates/netbox_dns/view.html,sha256=zqf42FGdNudoIIWCaCe9bmP_VOZDbv3GjS-qJNMKPVY,2479
103
106
  netbox_dns/templates/netbox_dns/zone/base.html,sha256=n_E4aVYdGeZZl-ARE8sb4DgAAgPs92X1UEFepX3xIlM,495
104
107
  netbox_dns/templates/netbox_dns/zone/child.html,sha256=kH56PJFBGCjiRdIh7zCtClnZdfOChqN_sYslsyoz5gU,2147
105
108
  netbox_dns/templates/netbox_dns/zone/child_zone.html,sha256=b9CSGWEfWT7hLQ80gApMnu7mXM8w2LT-3UaOYe6HIRQ,510
@@ -115,12 +118,12 @@ netbox_dns/urls/nameserver.py,sha256=BBbY-wqPqCquvLLv1_JhqToj7oDHhPNGCWHt0IfjBNM
115
118
  netbox_dns/urls/record.py,sha256=bDprohTso1N0GtPXH4X3TNHnkxopiOSQFXWItifEZ_k,1432
116
119
  netbox_dns/urls/record_template.py,sha256=Z-7aA-rPIxRBCmXNUiQcHIgjYfai28Tf_sLtkl2ihDk,1827
117
120
  netbox_dns/urls/registrar.py,sha256=u6B0zGGYNUJIKTo9uGiUeZLPD0QMGaQOAPShGEy4NaA,1728
118
- netbox_dns/urls/view.py,sha256=8AeBnOHWusXXQs4JXpNfMSHqszXAY1GDXGWmNsMulQ8,1327
121
+ netbox_dns/urls/view.py,sha256=jz5ANOOLCMAcWermTZYGq9BvnP02jpKGL6hCm33C47Q,1478
119
122
  netbox_dns/urls/zone.py,sha256=rmB1BkzmWNG06ILUf-39Aj6-SBFkwQouyixMQiamqPc,2005
120
123
  netbox_dns/urls/zone_template.py,sha256=w3Gu8qfLCWyHofeLkGZd1HpYSlcslomVlBQJZyqh8kk,1690
121
- netbox_dns/utilities/__init__.py,sha256=M9T8PUFlGddtENzEznHAPbEsz1VFrPcmbD-BGLCsvB4,55
124
+ netbox_dns/utilities/__init__.py,sha256=mmR0JdH1DJVhUKesnO3CZFj0Rw_wrsMPoYTpOOKHl9I,55
122
125
  netbox_dns/utilities/conversions.py,sha256=NS37SoMqXc13wNWRkKnLfyQbVi6QKD33fu5ovTKRo74,1979
123
- netbox_dns/utilities/ipam_autodns.py,sha256=Wit1MoJbPlYIUMfft13u7LY_Lr_zOb13DGkxIS-n-98,7338
126
+ netbox_dns/utilities/ipam_dnssync.py,sha256=LIUCigDdobPikneY-td3ydY2iK-iDDe75VGxcCBLdP8,8188
124
127
  netbox_dns/validators/__init__.py,sha256=Mr8TvmcJTa8Pubj8TzbFBKfbHhEmGcr5JdQvczEJ39A,72
125
128
  netbox_dns/validators/dns_name.py,sha256=B4A0BOW5pKDjjukvksriRtnLzkYTx_pFjh7eqKo6PBE,3069
126
129
  netbox_dns/validators/dns_value.py,sha256=y2Zga4hmywqDrTBXcMC-sWaFbw4eoY8pySq7cWnMP8Y,2822
@@ -128,13 +131,13 @@ netbox_dns/validators/rfc2317.py,sha256=ivylEiNKlmX2x41rwqDrFkD5CFf9FtpNEfsKHX_p
128
131
  netbox_dns/views/__init__.py,sha256=s41w4o77tIwmhnLjsOsg08R9m3wrlomkkfCLTVQuPzc,196
129
132
  netbox_dns/views/contact.py,sha256=qM9F6MQBvO8ERR7quGLdQ5kW4roNLJ61As8m0qQTapg,2471
130
133
  netbox_dns/views/nameserver.py,sha256=DFr0eybMshc1FW06g4cy9Nk4VRMxRqakI5KtHFiAVRc,3286
131
- netbox_dns/views/record.py,sha256=fHMafCC14C7d6oXbXc2vN-T70OAOaTY77_m3Dct-oiQ,4590
134
+ netbox_dns/views/record.py,sha256=Qv-Yf9CxlHz9C9-8hy-_WVbRL5f6opXUHJD4SsDcVGo,4587
132
135
  netbox_dns/views/record_template.py,sha256=BkemTBEramLhYqB6HrA80sNgtduW1ZOJwbYs3i7srik,2510
133
136
  netbox_dns/views/registrar.py,sha256=yRQgFm3vgBD21ZQex9asjs0QWegvSHlcyHXLnjvc5xs,2324
134
- netbox_dns/views/view.py,sha256=I_hVZYFJF8GTnlUKPrTgBk_x9UDCbZXM8R7U5Bhizjs,2107
137
+ netbox_dns/views/view.py,sha256=iXBJTc3JD5cD5z0RTcHVTtYV-KNIJGneeoxymXChdUE,2759
135
138
  netbox_dns/views/zone.py,sha256=SKhf_WHcFVpKqFTuUMf-Dmxu1AwFHBeo_DtD8UGFrJ8,5483
136
139
  netbox_dns/views/zone_template.py,sha256=qvXl-bpc1fMc1WFngynj4-Q3-JJDgKdT-r54s4M1D0s,2118
137
- netbox_plugin_dns-1.1.0b2.dist-info/LICENSE,sha256=I3tDu11bZfhFm3EkV4zOD5TmWgLjnUNLEFwrdjniZYs,1112
138
- netbox_plugin_dns-1.1.0b2.dist-info/METADATA,sha256=V0TQOjdcjkw1vAf_Ih85cV2vr0xgG_10VEJkn8a2BBw,6406
139
- netbox_plugin_dns-1.1.0b2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
140
- netbox_plugin_dns-1.1.0b2.dist-info/RECORD,,
140
+ netbox_plugin_dns-1.1.0b4.dist-info/LICENSE,sha256=I3tDu11bZfhFm3EkV4zOD5TmWgLjnUNLEFwrdjniZYs,1112
141
+ netbox_plugin_dns-1.1.0b4.dist-info/METADATA,sha256=ktOXrD3WonLsUqWDbTg_n8w_ItTFfW7NB_60nuMcnwY,6406
142
+ netbox_plugin_dns-1.1.0b4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
143
+ netbox_plugin_dns-1.1.0b4.dist-info/RECORD,,