netbox-ddns 1.5.0__py3-none-any.whl → 1.7.0__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.
netbox_ddns/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- VERSION = '1.5.0'
1
+ VERSION = '1.7.0'
2
2
 
3
3
  try:
4
4
  from netbox.plugins import PluginConfig
@@ -12,8 +12,8 @@ class NetBoxDDNSConfig(PluginConfig):
12
12
  name = 'netbox_ddns'
13
13
  verbose_name = 'Dynamic DNS'
14
14
  version = VERSION
15
- min_version = '4.0.0'
16
- max_version = '4.2.999'
15
+ min_version = '4.4.0'
16
+ max_version = '4.5.999'
17
17
  author = 'Sander Steffann'
18
18
  author_email = 'sander@steffann.nl'
19
19
  description = 'Dynamic DNS Connector for NetBox'
@@ -26,7 +26,7 @@ class ServerSerializer(NetBoxModelSerializer):
26
26
  class ZoneSerializer(NetBoxModelSerializer):
27
27
  class Meta:
28
28
  model = Zone
29
- fields = ('name', 'ttl', 'server')
29
+ fields = ('name', 'ttl', 'server', 'protocol')
30
30
 
31
31
 
32
32
  class ReverseZoneSerializer(NetBoxModelSerializer):
@@ -9,7 +9,7 @@ from django_rq import job
9
9
  from dns import rcode
10
10
  from netaddr import ip
11
11
 
12
- from netbox_ddns.models import ACTION_CREATE, ACTION_DELETE, DNSStatus, RCODE_NO_ZONE, ReverseZone, Zone
12
+ from netbox_ddns.models import ACTION_CREATE, ACTION_DELETE, DNSStatus, RCODE_NO_ZONE, ReverseZone, Zone, Protocol
13
13
  from netbox_ddns.utils import get_soa
14
14
 
15
15
  logger = logging.getLogger('netbox_ddns')
@@ -28,6 +28,17 @@ def status_update(output: List[str], operation: str, response) -> None:
28
28
  output.append(message)
29
29
 
30
30
 
31
+ def send_dns_update(update, server, protocol):
32
+ """Send DNS update via TCP or UDP. Returns response or None on unknown protocol."""
33
+ if protocol == Protocol.TCP:
34
+ return dns.query.tcp(update, server.address, port=server.server_port)
35
+ elif protocol == Protocol.UDP:
36
+ return dns.query.udp(update, server.address, port=server.server_port)
37
+ else:
38
+ logger.error(f"Unknown protocol {protocol} for server {server}")
39
+ return None
40
+
41
+
31
42
  def create_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSStatus], output: List[str]):
32
43
  if status:
33
44
  status.forward_action = ACTION_CREATE
@@ -38,6 +49,7 @@ def create_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
38
49
 
39
50
  # Check the SOA, we don't want to write to a parent zone if it has delegated authority
40
51
  soa = get_soa(zone.name)
52
+ protocol = zone.server.protocol
41
53
  if soa == zone.name:
42
54
  record_type = 'A' if address.version == 4 else 'AAAA'
43
55
  update = zone.server.create_update(zone.name)
@@ -47,7 +59,9 @@ def create_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
47
59
  record_type,
48
60
  str(address)
49
61
  )
50
- response = dns.query.udp(update, zone.server.address, port=zone.server.server_port)
62
+ response = send_dns_update(update, zone.server, protocol)
63
+ if response is None:
64
+ return
51
65
  status_update(output, f'Adding {dns_name} {record_type} {address}', response)
52
66
  if status:
53
67
  status.forward_rcode = response.rcode()
@@ -72,6 +86,7 @@ def delete_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
72
86
 
73
87
  # Check the SOA, we don't want to write to a parent zone if it has delegated authority
74
88
  soa = get_soa(zone.name)
89
+ protocol = zone.server.protocol
75
90
  if soa == zone.name:
76
91
  record_type = 'A' if address.version == 4 else 'AAAA'
77
92
  update = zone.server.create_update(zone.name)
@@ -80,7 +95,9 @@ def delete_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
80
95
  record_type,
81
96
  str(address)
82
97
  )
83
- response = dns.query.udp(update, zone.server.address, port=zone.server.server_port)
98
+ response = send_dns_update(update, zone.server, protocol)
99
+ if response is None:
100
+ return
84
101
  status_update(output, f'Deleting {dns_name} {record_type} {address}', response)
85
102
  if status:
86
103
  status.forward_rcode = response.rcode()
@@ -106,6 +123,7 @@ def create_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
106
123
 
107
124
  # Check the SOA, we don't want to write to a parent zone if it has delegated authority
108
125
  soa = get_soa(record_name)
126
+ protocol = zone.server.protocol
109
127
  if soa == zone.name:
110
128
  update = zone.server.create_update(zone.name)
111
129
  update.add(
@@ -114,7 +132,9 @@ def create_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
114
132
  'ptr',
115
133
  dns_name
116
134
  )
117
- response = dns.query.udp(update, zone.server.address, port=zone.server.server_port)
135
+ response = send_dns_update(update, zone.server, protocol)
136
+ if response is None:
137
+ return
118
138
  status_update(output, f'Adding {record_name} PTR {dns_name}', response)
119
139
  if status:
120
140
  status.reverse_rcode = response.rcode()
@@ -140,6 +160,7 @@ def delete_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
140
160
 
141
161
  # Check the SOA, we don't want to write to a parent zone if it has delegated authority
142
162
  soa = get_soa(record_name)
163
+ protocol = zone.server.protocol
143
164
  if soa == zone.name:
144
165
  update = zone.server.create_update(zone.name)
145
166
  update.delete(
@@ -147,7 +168,9 @@ def delete_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta
147
168
  'ptr',
148
169
  dns_name
149
170
  )
150
- response = dns.query.udp(update, zone.server.address, port=zone.server.server_port)
171
+ response = send_dns_update(update, zone.server, protocol)
172
+ if response is None:
173
+ return
151
174
  status_update(output, f'Deleting {record_name} PTR {dns_name}', response)
152
175
  if status:
153
176
  status.reverse_rcode = response.rcode()
netbox_ddns/forms.py CHANGED
@@ -34,13 +34,13 @@ class ZoneBulkEditForm(NetBoxModelBulkEditForm):
34
34
 
35
35
  class ServerForm(NetBoxModelForm):
36
36
  fieldsets = (
37
- FieldSet('server', 'server_port', name='Server'),
37
+ FieldSet('server', 'server_port', 'protocol', name='Server'),
38
38
  FieldSet('tsig_key_name', 'tsig_algorithm', "tsig_key", name='Authentication'),
39
39
  )
40
40
 
41
41
  class Meta:
42
42
  model = Server
43
- fields = ('server', 'server_port', 'tsig_key_name', 'tsig_algorithm', "tsig_key")
43
+ fields = ('server', 'server_port', 'protocol', 'tsig_key_name', 'tsig_algorithm', "tsig_key")
44
44
 
45
45
 
46
46
  class ExtraDNSNameIPAddressForm(NetBoxModelForm):
@@ -0,0 +1,19 @@
1
+ # Generated by Django 5.2.5 on 2025-09-01 09:17
2
+
3
+ import netbox_ddns.validators
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('netbox_ddns', '0013_reversezone_created_reversezone_custom_field_data_and_more'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name='extradnsname',
16
+ name='name',
17
+ field=models.CharField(max_length=255, validators=[netbox_ddns.validators.DNSNameValidator()]),
18
+ ),
19
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 5.2.6 on 2025-10-01 14:44
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('netbox_ddns', '0014_alter_extradnsname_name'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='server',
15
+ name='protocol',
16
+ field=models.CharField(default='udp', max_length=3),
17
+ ),
18
+ ]
netbox_ddns/models.py CHANGED
@@ -18,7 +18,7 @@ from ipam.fields import IPNetworkField
18
18
  from ipam.models import IPAddress
19
19
  from utilities.querysets import RestrictedQuerySet
20
20
  from .utils import normalize_fqdn
21
- from .validators import HostnameAddressValidator, HostnameValidator, validate_base64, MinValueValidator, \
21
+ from .validators import DNSNameValidator, HostnameAddressValidator, HostnameValidator, validate_base64, MinValueValidator, \
22
22
  MaxValueValidator
23
23
 
24
24
  logger = logging.getLogger('netbox_ddns')
@@ -64,6 +64,9 @@ def get_rcode_display(code):
64
64
  else:
65
65
  return _('Unknown response: {}').format(code)
66
66
 
67
+ class Protocol(models.TextChoices):
68
+ UDP = 'udp', _('UDP')
69
+ TCP = 'tcp', _('TCP')
67
70
 
68
71
  class Server(NetBoxModel):
69
72
  server = models.CharField(
@@ -95,6 +98,12 @@ class Server(NetBoxModel):
95
98
  validators=[validate_base64],
96
99
  help_text=_('in base64 notation'),
97
100
  )
101
+ protocol = models.CharField(
102
+ verbose_name=_('Protocol'),
103
+ max_length=3,
104
+ choices=Protocol.choices,
105
+ default=Protocol.UDP,
106
+ )
98
107
 
99
108
  class Meta:
100
109
  unique_together = (
@@ -119,7 +128,10 @@ class Server(NetBoxModel):
119
128
 
120
129
  @property
121
130
  def address(self) -> Optional[str]:
122
- return socket.gethostbyname(self.server)
131
+ addrinfo = socket.getaddrinfo(self.server, self.server_port, proto=socket.IPPROTO_UDP)
132
+ for family, _, _, _, sockaddr in addrinfo:
133
+ if family in (socket.AF_INET, socket.AF_INET6) and sockaddr[0]:
134
+ return sockaddr[0]
123
135
 
124
136
  def create_update(self, zone: str) -> dns.update.Update:
125
137
  return dns.update.Update(
@@ -374,7 +386,7 @@ class ExtraDNSName(NetBoxModel):
374
386
  name = models.CharField(
375
387
  verbose_name=_('DNS name'),
376
388
  max_length=255,
377
- validators=[HostnameValidator()],
389
+ validators=[DNSNameValidator()],
378
390
  )
379
391
 
380
392
  last_update = models.DateTimeField(
netbox_ddns/navigation.py CHANGED
@@ -4,6 +4,10 @@ menu = PluginMenu(
4
4
  label='DDNS',
5
5
  groups=(
6
6
  ('Configuration', (
7
+ PluginMenuItem(
8
+ link='plugins:netbox_ddns:managed_dns_name_list',
9
+ link_text='DNS Names',
10
+ ),
7
11
  PluginMenuItem(
8
12
  link='plugins:netbox_ddns:server_list',
9
13
  link_text='DDNS Servers',
netbox_ddns/tables.py CHANGED
@@ -5,6 +5,20 @@ from netbox_ddns.models import ExtraDNSName, Server, ReverseZone, Zone
5
5
 
6
6
  from netbox.tables import NetBoxTable
7
7
 
8
+ MANAGED_DNS_ACTIONS = """
9
+ <a href="{{ record.view_url }}" class="btn btn-sm btn-outline-primary" title="View">
10
+ <i class="mdi mdi-eye"></i>
11
+ </a>
12
+ <a href="{{ record.edit_url }}" class="btn btn-sm btn-outline-warning" title="Edit">
13
+ <i class="mdi mdi-pencil"></i>
14
+ </a>
15
+ {% if record.delete_url %}
16
+ <a href="{{ record.delete_url }}" class="btn btn-sm btn-outline-danger" title="Delete">
17
+ <i class="mdi mdi-delete"></i>
18
+ </a>
19
+ {% endif %}
20
+ """
21
+
8
22
  FORWARD_DNS = """
9
23
  {% if record.forward_action is not None %}
10
24
  {{ record.get_forward_action_display }}:
@@ -38,7 +52,7 @@ class ServerTable(NetBoxTable):
38
52
 
39
53
  class Meta(NetBoxTable.Meta):
40
54
  model = Server
41
- fields = ("id", "server", "server_port", "tsig_key_name", "tsig_algorithm")
55
+ fields = ("id", "server", "server_port", "protocol", "tsig_key_name", "tsig_algorithm")
42
56
 
43
57
 
44
58
  class ExtraDNSNameTable(NetBoxTable):
@@ -49,3 +63,34 @@ class ExtraDNSNameTable(NetBoxTable):
49
63
  class Meta(NetBoxTable.Meta):
50
64
  model = ExtraDNSName
51
65
  fields = ('id', 'name', 'ip_address', 'last_update', 'forward_dns')
66
+
67
+
68
+ class ManagedDNSNameTable(tables.Table):
69
+ """Table for primary DNS names (from IPAddress.dns_name)."""
70
+ dns_name = tables.Column(
71
+ accessor='dns_name',
72
+ linkify=lambda record: record.view_url,
73
+ )
74
+ ip_address = tables.Column(
75
+ accessor='ip_address',
76
+ linkify=lambda record: record.ip_address.get_absolute_url(),
77
+ )
78
+ zone = tables.Column(
79
+ accessor='zone',
80
+ linkify=lambda record: record.zone.get_absolute_url() if record.zone else None,
81
+ )
82
+ forward_status = tables.TemplateColumn(
83
+ template_code='{{ record.forward_status_html|safe }}',
84
+ verbose_name='Forward DNS',
85
+ )
86
+ actions = tables.TemplateColumn(
87
+ template_code=MANAGED_DNS_ACTIONS,
88
+ verbose_name='Actions',
89
+ orderable=False,
90
+ )
91
+
92
+ def render_zone(self, value):
93
+ return value if value else '—'
94
+
95
+ class Meta:
96
+ attrs = {'class': 'table table-hover object-list'}
@@ -6,7 +6,10 @@ from . import tables
6
6
 
7
7
 
8
8
  class ReverseZoneRecreate(PluginTemplateExtension):
9
+ # NetBox up to 4.1
9
10
  model = 'netbox_ddns.reversezone'
11
+ # NetBox from 4.2, required to be present as of NetBox 4.3
12
+ models = [model]
10
13
 
11
14
  def buttons(self):
12
15
  """
@@ -20,7 +23,10 @@ class ReverseZoneRecreate(PluginTemplateExtension):
20
23
 
21
24
 
22
25
  class ZoneRecreate(PluginTemplateExtension):
26
+ # NetBox up to 4.1
23
27
  model = 'netbox_ddns.zone'
28
+ # NetBox from 4.2, required to be present as of NetBox 4.3
29
+ models = [model]
24
30
 
25
31
  def buttons(self):
26
32
  """
@@ -34,7 +40,10 @@ class ZoneRecreate(PluginTemplateExtension):
34
40
 
35
41
 
36
42
  class DNSInfo(PluginTemplateExtension):
43
+ # NetBox up to 4.1
37
44
  model = 'ipam.ipaddress'
45
+ # NetBox from 4.2, required to be present as of NetBox 4.3
46
+ models = [model]
38
47
 
39
48
  def buttons(self):
40
49
  """
@@ -0,0 +1,17 @@
1
+ {% extends 'base/layout.html' %}
2
+ {% load render_table from django_tables2 %}
3
+
4
+ {% block title %}DNS Names{% endblock %}
5
+
6
+ {% block content %}
7
+ <div class="row">
8
+ <div class="col col-12">
9
+ <div class="card">
10
+ <h5 class="card-header">DNS Names</h5>
11
+ <div class="card-body table-responsive">
12
+ {% render_table table 'inc/table.html' %}
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ {% endblock content %}
@@ -19,6 +19,12 @@
19
19
  {{ object.server_port }}
20
20
  </td>
21
21
  </tr>
22
+ <tr>
23
+ <th scope="row">Protocol</th>
24
+ <td>
25
+ {{ object.protocol }}
26
+ </td>
27
+ </tr>
22
28
  </table>
23
29
  </div>
24
30
  <div class="card">
@@ -65,4 +71,4 @@
65
71
  </div>
66
72
  </div>
67
73
 
68
- {% endblock content %}
74
+ {% endblock content %}
netbox_ddns/urls.py CHANGED
@@ -1,10 +1,16 @@
1
1
  from django.urls import path, include
2
2
 
3
3
  from utilities.urls import get_model_urls
4
- from .views import ExtraDNSNameCreateView, IPAddressDNSNameRecreateView, \
5
- UpdateForwardZone, UpdateReverseZone
4
+ from .views import (
5
+ ExtraDNSNameCreateView,
6
+ IPAddressDNSNameRecreateView,
7
+ ManagedDNSNameListView,
8
+ UpdateForwardZone,
9
+ UpdateReverseZone,
10
+ )
6
11
 
7
12
  urlpatterns = [
13
+ path('managed-dns-names/', ManagedDNSNameListView.as_view(), name='managed_dns_name_list'),
8
14
 
9
15
  path('extra-dns-names/', include(get_model_urls('netbox_ddns', 'extradnsname', detail=False))),
10
16
  path('extra-dns-names/<int:pk>/', include(get_model_urls('netbox_ddns', 'extradnsname'))),
netbox_ddns/utils.py CHANGED
@@ -1,6 +1,101 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING, List, Optional
3
+
1
4
  import dns.rdatatype
2
5
  import dns.resolver
3
6
 
7
+ if TYPE_CHECKING:
8
+ from ipam.models import IPAddress
9
+ from netbox_ddns.models import Zone
10
+
11
+
12
+ @dataclass
13
+ class ManagedDNSNameRow:
14
+ """Primary DNS name from IPAddress, with optional zone."""
15
+ dns_name: str
16
+ ip_address: 'IPAddress'
17
+ zone: Optional['Zone']
18
+ obj: 'IPAddress'
19
+ view_url: str = ''
20
+ edit_url: str = ''
21
+ delete_url: Optional[str] = None
22
+ forward_status_html: str = ''
23
+
24
+
25
+ def _find_zone_for_dns_name(dns_name: str, zones_by_name: dict) -> Optional['Zone']:
26
+ """In-memory zone lookup."""
27
+ parts = dns_name.lower().rstrip('.').split('.')
28
+ for i in range(len(parts)):
29
+ candidate = '.'.join(parts[i:])
30
+ for suffix in (candidate + '.', candidate):
31
+ if suffix in zones_by_name:
32
+ return zones_by_name[suffix]
33
+ return None
34
+
35
+
36
+ def get_managed_dns_names(user) -> List[ManagedDNSNameRow]:
37
+ """
38
+ Return all primary DNS names (from IPAddress.dns_name), with or without a zone.
39
+ Extra DNS names are shown in the separate Extra DNS Names view.
40
+ """
41
+ from django.urls import reverse
42
+
43
+ from dns import rcode
44
+
45
+ from ipam.models import IPAddress
46
+ from netbox_ddns.models import ACTION_CHOICES, DNSStatus, Zone, get_rcode_display
47
+
48
+ rows: List[ManagedDNSNameRow] = []
49
+ no_zone_status = '<span class="text-muted">No zone configured</span>'
50
+
51
+ zones = list(Zone.objects.all().restrict(user, 'view'))
52
+ zones_by_name = {z.name: z for z in zones}
53
+ for z in zones:
54
+ n = z.name.rstrip('.')
55
+ if n != z.name:
56
+ zones_by_name[n] = z
57
+
58
+ ip_addresses = (
59
+ IPAddress.objects.filter(dns_name__isnull=False)
60
+ .exclude(dns_name='')
61
+ .restrict(user, 'view')
62
+ .prefetch_related('dnsstatus')
63
+ .order_by('dns_name')
64
+ )
65
+
66
+ for ip_address in ip_addresses:
67
+ zone = _find_zone_for_dns_name(ip_address.dns_name, zones_by_name)
68
+ if zone is None:
69
+ forward_status = no_zone_status
70
+ else:
71
+ try:
72
+ status = ip_address.dnsstatus
73
+ if status.forward_action is not None:
74
+ output = next(
75
+ label for value, label in ACTION_CHOICES if value == status.forward_action
76
+ )
77
+ output += ': '
78
+ output += get_rcode_display(status.forward_rcode) or ''
79
+ colour = 'green' if status.forward_rcode == rcode.NOERROR else 'red'
80
+ forward_status = f'<span style="color:{colour}">{output}</span>'
81
+ else:
82
+ forward_status = '<span class="text-muted">Not created</span>'
83
+ except DNSStatus.DoesNotExist:
84
+ forward_status = '<span class="text-muted">Not created</span>'
85
+
86
+ rows.append(ManagedDNSNameRow(
87
+ dns_name=ip_address.dns_name,
88
+ ip_address=ip_address,
89
+ zone=zone,
90
+ obj=ip_address,
91
+ view_url=ip_address.get_absolute_url(),
92
+ edit_url=reverse('ipam:ipaddress_edit', args=[ip_address.pk]),
93
+ delete_url=None,
94
+ forward_status_html=forward_status,
95
+ ))
96
+
97
+ return rows
98
+
4
99
 
5
100
  def normalize_fqdn(dns_name: str) -> str:
6
101
  if not dns_name:
netbox_ddns/validators.py CHANGED
@@ -21,3 +21,9 @@ def validate_base64(value):
21
21
  base64.b64decode(value, validate=True)
22
22
  except binascii.Error:
23
23
  raise ValidationError('Invalid base64 string')
24
+
25
+ class DNSNameValidator(RegexValidator):
26
+ regex = r"^([0-9A-Za-z_-]+|\*)(\.[0-9A-Za-z_-]+)*\.?$"
27
+ message = _(
28
+ "Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names"
29
+ )
netbox_ddns/views.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from django.contrib import messages
2
2
  from django.contrib.auth.mixins import PermissionRequiredMixin
3
- from django.shortcuts import get_object_or_404, redirect
3
+ from django.shortcuts import get_object_or_404, redirect, render
4
4
  from django.utils.translation import gettext as _
5
5
 
6
6
  from ipam.models import IPAddress
@@ -9,16 +9,34 @@ from netbox_ddns.background_tasks import dns_create
9
9
  from netbox_ddns.filtersets import ServerFilterSet, ZoneFilterSet, ReverseZoneFilterSet, ExtraDNSNameFilterSet
10
10
  from netbox_ddns.forms import ServerForm, ZoneForm, ReverseZoneForm, ExtraDNSNameIPAddressForm, ExtraDNSNameForm
11
11
  from netbox_ddns.models import DNSStatus, ExtraDNSName, Server, Zone, ReverseZone
12
- from netbox_ddns.tables import ServerTable, ZoneTable, ReverseZoneTable, ExtraDNSNameTable
13
- from netbox_ddns.utils import normalize_fqdn
12
+ from netbox_ddns.tables import ManagedDNSNameTable, ServerTable, ZoneTable, ReverseZoneTable, ExtraDNSNameTable
13
+ from netbox_ddns.utils import get_managed_dns_names, normalize_fqdn
14
14
 
15
15
  from netbox.views.generic import ObjectDeleteView, ObjectEditView, ObjectView, ObjectListView, BulkDeleteView
16
16
 
17
+ from django_tables2 import RequestConfig
17
18
  from django.views.generic import View
18
19
 
19
20
  from utilities.views import register_model_view, GetRelatedModelsMixin
20
21
 
21
22
 
23
+ # Managed DNS Names (primary DNS names from IP addresses)
24
+ class ManagedDNSNameListView(PermissionRequiredMixin, View):
25
+ """List of all primary DNS names (from IPAddress.dns_name). Extra DNS names have a separate view."""
26
+ permission_required = 'netbox_ddns.view_zone'
27
+
28
+ def get(self, request):
29
+ rows = get_managed_dns_names(request.user)
30
+ table = ManagedDNSNameTable(rows)
31
+ RequestConfig(request).configure(table)
32
+
33
+ return render(
34
+ request,
35
+ 'netbox_ddns/managed_dns_names.html',
36
+ {'table': table},
37
+ )
38
+
39
+
22
40
  # ReverseZone
23
41
  @register_model_view(ReverseZone)
24
42
  class ReverseZoneView(ObjectView):
@@ -1,22 +1,24 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: netbox-ddns
3
- Version: 1.5.0
3
+ Version: 1.7.0
4
4
  Summary: Dynamic DNS Connector for NetBox
5
5
  Home-page: https://github.com/sjm-steffann/netbox-ddns
6
6
  Author: Sander Steffann
7
7
  Author-email: sander@steffann.nl
8
8
  License: Apache 2.0
9
- Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Framework :: Django
11
11
  Classifier: Framework :: Django :: 3.0
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.6
15
- Requires-Python: >=3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.11
16
17
  Description-Content-Type: text/markdown
17
18
  License-File: LICENSE.txt
18
19
  Requires-Dist: setuptools
19
20
  Requires-Dist: dnspython
21
+ Dynamic: license-file
20
22
 
21
23
  # Dynamic DNS Connector for NetBox
22
24
 
@@ -1,20 +1,20 @@
1
- netbox_ddns/__init__.py,sha256=UtIar40H5eO-n802D0-MRAAFeqQouOTjB0aaaZvA9qU,652
1
+ netbox_ddns/__init__.py,sha256=friu8evzGLj_1VYrptsexpNvH2VhLl7J0vWnxpXos_c,652
2
2
  netbox_ddns/admin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- netbox_ddns/background_tasks.py,sha256=oCw6V3aas1Q1C8ftsn1TisrJ0JU51sdDt_iTlm-ZNmk,7070
3
+ netbox_ddns/background_tasks.py,sha256=u_3H_ktST9F05iRV3hVwF6Q7JDPlDlAc9tcV6YnEnjw,7824
4
4
  netbox_ddns/filtersets.py,sha256=nhHgkef7IqUA9ljyFn5rTuFYl4wKHzMkPfbLMCeHMtk,828
5
- netbox_ddns/forms.py,sha256=ERHgLdmEyJfHLp1R4PxOEmmWNKNeZUSImTChjuftxW8,1520
6
- netbox_ddns/models.py,sha256=C5XcotUzYHLFbdffr1mdM3p6XFT_WvjOr70g-vFoReg,13926
7
- netbox_ddns/navigation.py,sha256=PiDri64dpW2MCiFZBst_YDEhKfeYwG58BvxIfzhQDFc,1899
5
+ netbox_ddns/forms.py,sha256=ZcDxRWelufSc4kbUALI3ZrjhhXIWcefBtDgyU_632H4,1544
6
+ netbox_ddns/models.py,sha256=PTDR0okKBmcn1QgiyVi9cWGuM6REMxJXA5cQEEhRJz8,14400
7
+ netbox_ddns/navigation.py,sha256=NT0agBkUisanyGYzbAkH5CiuMBIOGL58Q6C-eZ5R7Xc,2047
8
8
  netbox_ddns/search.py,sha256=G07DBvjOzKNjbtm9d6_n___GKXQyJ2aCfSDjPRdRzGs,212
9
9
  netbox_ddns/signals.py,sha256=yHkgZImnyTqRwPXAr2W9bd4rJof7YblkylJnDn1_FDc,4185
10
- netbox_ddns/tables.py,sha256=yKZq_yo8RtqDGrTxQdQ1UVTDAWCcJvB_S36o74NLPSQ,1376
11
- netbox_ddns/template_content.py,sha256=eHFkkH5X5JcKLA1d7Qj_ooG1zD84ebI43mKqGzhafgs,2122
12
- netbox_ddns/urls.py,sha256=gBHvHlfWwQfQPBkl47v73PDDuIhgjGs4hUkCkCJSQbA,1543
13
- netbox_ddns/utils.py,sha256=qAomseFWGL_VvL0sPF3Zmb6taMl3P9eEASqbMunmoyA,901
14
- netbox_ddns/validators.py,sha256=K8EzI6D-Mm_H3Es6LzzXx5ntBDsr8tY79_DHZmbamWM,851
15
- netbox_ddns/views.py,sha256=X01Hc6chcrPyVWYwWsx8nrXMfsx10_OU3GVB3XtNbDE,10275
10
+ netbox_ddns/tables.py,sha256=4RTQI_H74awe8kN38iiT2-Vp7IZPBN6jfOeP1Jz37iU,2781
11
+ netbox_ddns/template_content.py,sha256=AN0voBq7LFwZ-v7UZcoIvt-8UlbEF4Lq1KQ4gfnka3o,2443
12
+ netbox_ddns/urls.py,sha256=VDoT1h0WwWrv4KpoVqUQUuZy-E8TmuYCuvll-dpbwPI,1681
13
+ netbox_ddns/utils.py,sha256=SJ00wzuMKGdeawVQOyXZoLkKX5iRuPBjj8-wYNnTCPA,4135
14
+ netbox_ddns/validators.py,sha256=bT9QA905uSWZzsGyhRDFWAlJceNd4cf7RWNdSc7FwSo,1084
15
+ netbox_ddns/views.py,sha256=h-QE9JZVr630Jme5rSzhDVQQIE-SdtGL9GBvjuhs0Y8,10949
16
16
  netbox_ddns/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- netbox_ddns/api/serializers.py,sha256=8rQUNLIIoUOz2M7AHKxdedDIS7QAMUSdrnMcTWuwG78,1098
17
+ netbox_ddns/api/serializers.py,sha256=UOPZPh80rq4o06uK3mhB7dR0hUFO4YoGbmB5hvbrkKE,1110
18
18
  netbox_ddns/api/urls.py,sha256=eoDxcu9tvR31xtTu0QebvTPqsCpckJfTqFj6Pm6-EyY,204
19
19
  netbox_ddns/api/views.py,sha256=9P5vqubPTd0MYOrApnmDYSzjdEo9kmq0BcjVqUFtIA8,362
20
20
  netbox_ddns/migrations/0001_initial.py,sha256=pYwGDj9C3mPUuc8lfiXqTOYEhZ-MpGhLywGRulHcZQA,2461
@@ -30,18 +30,21 @@ netbox_ddns/migrations/0010_extradnsname_created_extradnsname_custom_field_data_
30
30
  netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py,sha256=D0resBMdiy7JEznlq1Fcw018CPFZFIn7lEBp5sC6frU,1133
31
31
  netbox_ddns/migrations/0012_zone_created_zone_custom_field_data_and_more.py,sha256=5Rx21TsOxbb-bFRWceRTzBWVuw2b9p70w9yha7SfizE,1113
32
32
  netbox_ddns/migrations/0013_reversezone_created_reversezone_custom_field_data_and_more.py,sha256=BTo5lpO9NZMwHOCiOhINMfp4aFeIQXkXLQ5HKTARIk0,1137
33
+ netbox_ddns/migrations/0014_alter_extradnsname_name.py,sha256=oXN7jblZBZKGJFXpCEblUQeYwewgyE00FdeH4uUUR4E,521
34
+ netbox_ddns/migrations/0015_server_protocol.py,sha256=dbzGOeGSIqHl5yX7hEtlamuKjktgCSL79KUXLsxBE9w,409
33
35
  netbox_ddns/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
36
  netbox_ddns/templates/netbox_ddns/extradnsname.html,sha256=noeIxirmaaxBFCCbhCloIfctGQGY7JRi92z19by7e8s,2630
37
+ netbox_ddns/templates/netbox_ddns/managed_dns_names.html,sha256=sCIrlNFel-2sKwI0dYQkHBHfZv5OcZGEmyo850Ve_YA,491
35
38
  netbox_ddns/templates/netbox_ddns/reversezone.html,sha256=Ez5su5iqpbwZbRP8LUS0qcojWrnukyTtWBSLVGjI8MQ,1787
36
- netbox_ddns/templates/netbox_ddns/server.html,sha256=C1MdMk-d3PRjdgEhUngqGVv5MDiYjayrdFOnJTsozxg,2449
39
+ netbox_ddns/templates/netbox_ddns/server.html,sha256=72gyeS7gUe3E4a_R5PZySClAymD7sh0Luv-BNZ6oYqY,2664
37
40
  netbox_ddns/templates/netbox_ddns/update_reverse_zone.html,sha256=oF01zNC5sDJVhoWzoj7jQxZqaBE9xvZRsJ-JmI5zonI,429
38
41
  netbox_ddns/templates/netbox_ddns/update_zone.html,sha256=76o3CI7CbaJh6VDtAq2SgeW-kLGmY8SjYlkTfLDE8hw,422
39
42
  netbox_ddns/templates/netbox_ddns/zone.html,sha256=j1xHfagwx7hEuOWT5h4YIwGkKD9KM3guYtzZsdYhi8o,1912
40
43
  netbox_ddns/templates/netbox_ddns/ipaddress/dns_extra.html,sha256=8lk_HTcDiXugbHMq2hsOdjRB3gv-HX9XnrvZJVHnpoA,724
41
44
  netbox_ddns/templates/netbox_ddns/ipaddress/dns_info.html,sha256=x4cpUovY3ZxmFoJeTLeqNUIw4ezoBkNfsYaRA6abUh4,1714
42
45
  netbox_ddns/templates/netbox_ddns/ipaddress/dns_refresh_button.html,sha256=BBFwn2AP9PHfaU1ieYoB6X2eX84pPrUYYILbwYiKApc,430
43
- netbox_ddns-1.5.0.dist-info/LICENSE.txt,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
44
- netbox_ddns-1.5.0.dist-info/METADATA,sha256=z9VdpNI1Xw62cffAK0eGaWyavsKQw5oikYpD-XoCVUA,2266
45
- netbox_ddns-1.5.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
46
- netbox_ddns-1.5.0.dist-info/top_level.txt,sha256=nSPx7dwqPj2hyzG2H0ohzHt0kkyzA-6M3zYRL6qAm-s,12
47
- netbox_ddns-1.5.0.dist-info/RECORD,,
46
+ netbox_ddns-1.7.0.dist-info/licenses/LICENSE.txt,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
47
+ netbox_ddns-1.7.0.dist-info/METADATA,sha256=bHDDTy-Re-McIvHph68im8mDsgjcgmotJ6wdDiVUytc,2348
48
+ netbox_ddns-1.7.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
49
+ netbox_ddns-1.7.0.dist-info/top_level.txt,sha256=nSPx7dwqPj2hyzG2H0ohzHt0kkyzA-6M3zYRL6qAm-s,12
50
+ netbox_ddns-1.7.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5