diff --git a/netbox_dns_bridge/filtersets.py b/netbox_dns_bridge/filtersets.py
index 87de3a6..fb77cf8 100644
--- a/netbox_dns_bridge/filtersets.py
+++ b/netbox_dns_bridge/filtersets.py
@@ -3,32 +3,8 @@ from django.db.models import Q
import netbox_dns.models
from netbox.filtersets import NetBoxModelFilterSet
-from netbox_dns.choices import ZoneStatusChoices
-from .models import CatalogZone
-
-
-class ZoneFilterSet(NetBoxModelFilterSet):
- view = django_filters.ModelMultipleChoiceFilter(
- field_name="view",
- queryset=netbox_dns.models.View.objects.all(),
- )
- status = django_filters.MultipleChoiceFilter(
- choices=ZoneStatusChoices,
- null_value=None,
- )
-
- class Meta:
- model = netbox_dns.models.Zone
- fields = ("id", "name", "view", "status", "catz_identifier")
-
- def search(self, queryset, name, value):
- if not value.strip():
- return queryset
- return queryset.filter(
- Q(name__icontains=value)
- | Q(catz_identifier__name__icontains=value)
- )
+from .models import CatalogZone, CatalogZoneMember, SeenTransferClient
class CatalogZoneFilterSet(NetBoxModelFilterSet):
@@ -45,3 +21,39 @@ class CatalogZoneFilterSet(NetBoxModelFilterSet):
if not value.strip():
return queryset
return queryset.filter(Q(view__name__icontains=value))
+
+
+class CatalogZoneMemberFilterSet(NetBoxModelFilterSet):
+ catalog_zone = django_filters.ModelMultipleChoiceFilter(
+ field_name="catalog_zone",
+ queryset=CatalogZone.objects.all(),
+ )
+
+ class Meta:
+ model = CatalogZoneMember
+ fields = ("id", "catalog_zone", "zone")
+
+ def search(self, queryset, name, value):
+ if not value.strip():
+ return queryset
+ return queryset.filter(
+ Q(name__icontains=value) | Q(zone__name__icontains=value)
+ )
+
+
+class SeenTransferClientFilterSet(NetBoxModelFilterSet):
+ view = django_filters.ModelMultipleChoiceFilter(
+ field_name="view",
+ queryset=netbox_dns.models.View.objects.all(),
+ )
+
+ class Meta:
+ model = SeenTransferClient
+ fields = ("id", "view", "source_ip")
+
+ def search(self, queryset, name, value):
+ if not value.strip():
+ return queryset
+ return queryset.filter(
+ Q(source_ip__icontains=value) | Q(view__name__icontains=value)
+ )
diff --git a/netbox_dns_bridge/forms.py b/netbox_dns_bridge/forms.py
index dd7d784..7225ea3 100644
--- a/netbox_dns_bridge/forms.py
+++ b/netbox_dns_bridge/forms.py
@@ -3,10 +3,9 @@ from django.utils.translation import gettext_lazy as _
import netbox_dns.models
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelForm
-from netbox_dns.choices import ZoneStatusChoices
from utilities.forms.fields import DynamicModelMultipleChoiceField
-from .models import CatalogZone
+from .models import CatalogZone, CatalogZoneMember, SeenTransferClient
class CatalogZoneForm(NetBoxModelForm):
@@ -35,20 +34,6 @@ class CatalogZoneForm(NetBoxModelForm):
)
-class CatalogZoneMemberFilterForm(NetBoxModelFilterSetForm):
- model = netbox_dns.models.Zone
-
- q = forms.CharField(required=False, label=_("Search"))
- view = DynamicModelMultipleChoiceField(
- queryset=netbox_dns.models.View.objects.all(),
- required=False,
- )
- status = forms.MultipleChoiceField(
- choices=ZoneStatusChoices,
- required=False,
- )
-
-
class CatalogZoneFilterForm(NetBoxModelFilterSetForm):
model = CatalogZone
@@ -57,3 +42,23 @@ class CatalogZoneFilterForm(NetBoxModelFilterSetForm):
queryset=netbox_dns.models.View.objects.all(),
required=False,
)
+
+
+class CatalogZoneMemberFilterForm(NetBoxModelFilterSetForm):
+ model = CatalogZoneMember
+
+ q = forms.CharField(required=False, label=_("Search"))
+ catalog_zone = forms.ModelMultipleChoiceField(
+ queryset=CatalogZone.objects.all(),
+ required=False,
+ )
+
+
+class SeenTransferClientFilterForm(NetBoxModelFilterSetForm):
+ model = SeenTransferClient
+
+ q = forms.CharField(required=False, label=_("Search"))
+ view = forms.ModelMultipleChoiceField(
+ queryset=netbox_dns.models.View.objects.all(),
+ required=False,
+ )
diff --git a/netbox_dns_bridge/jobs/notify.py b/netbox_dns_bridge/jobs/notify.py
index 128805b..1598647 100644
--- a/netbox_dns_bridge/jobs/notify.py
+++ b/netbox_dns_bridge/jobs/notify.py
@@ -12,7 +12,7 @@ from django.db import close_old_connections, OperationalError
from netbox.jobs import JobRunner
from netbox_dns.models import View, Zone, Record
from netbox_dns_bridge.utils import get_logger
-from netbox_dns_bridge.models import SeenTransferClients, CatalogZone
+from netbox_dns_bridge.models import SeenTransferClient, CatalogZone
LOGGER = get_logger(__name__)
SETTINGS = settings.PLUGINS_CONFIG.get("netbox_dns_bridge", {})
@@ -111,7 +111,7 @@ class SendClientNotify(JobRunner):
seen_cutoff = timezone.now() - timezone.timedelta(hours=cutoff_hours)
clients = list(
- SeenTransferClients.objects.filter(
+ SeenTransferClient.objects.filter(
view=view, last_seen__gte=seen_cutoff
).values_list("source_ip", flat=True)
)
diff --git a/netbox_dns_bridge/management/commands/dns_transfer_endpoint/catalog_zone_manager.py b/netbox_dns_bridge/management/commands/dns_transfer_endpoint/catalog_zone_manager.py
index 919a753..a565de3 100644
--- a/netbox_dns_bridge/management/commands/dns_transfer_endpoint/catalog_zone_manager.py
+++ b/netbox_dns_bridge/management/commands/dns_transfer_endpoint/catalog_zone_manager.py
@@ -4,7 +4,7 @@ import dns.rdatatype
import dns.rdataclass
from netbox_dns_bridge.utils import get_logger
from netbox_dns.models import Zone as NBZone, View as NBView
-from netbox_dns_bridge.models import CatalogZone, CatalogZoneMemberIdentifier
+from netbox_dns_bridge.models import CatalogZone, CatalogZoneMember
from netbox_dns_bridge.jobs import notify
from uuid import uuid4
from base64 import b32encode
@@ -63,7 +63,7 @@ def create_zone(name, view_name) -> dns.zone.Zone:
# get zones from netbox
nb_zones = NBZone.objects.filter(
view__name=view_name, active=True
- ).select_related("catz_identifier")
+ ).select_related("catalog_zone_member")
ptr_base = dns.name.from_text("zones", origin)
@@ -73,21 +73,21 @@ def create_zone(name, view_name) -> dns.zone.Zone:
# Create PTR record
try:
- catz_identifier = nb_zone.catz_identifier
- except NBZone.catz_identifier.RelatedObjectDoesNotExist:
+ member = nb_zone.catalog_zone_member
+ except NBZone.catalog_zone_member.RelatedObjectDoesNotExist:
catalog_zone = _get_or_create_catalog_zone(nb_zone.view)
- catz_identifier = CatalogZoneMemberIdentifier.objects.create(
+ member = CatalogZoneMember.objects.create(
zone=nb_zone,
- name=_generate_member_identifier(),
+ name=_generate_member_name(),
catalog_zone=catalog_zone,
)
- p_name = catz_identifier.name
+ p_name = member.name
ptr_name = dns.name.from_text(p_name, ptr_base)
if not ptr_name.is_subdomain(origin):
raise ValueError(
- f"Catalog zone member identifier {ptr_name.to_text()} not a subdomain"
+ f"Catalog zone member {ptr_name.to_text()} not a subdomain"
)
rdata = dns.rdata.from_text(
dns.rdataclass.IN, dns.rdatatype.PTR, qname.to_text()
@@ -132,7 +132,7 @@ def create_zone(name, view_name) -> dns.zone.Zone:
return None
-def _generate_member_identifier() -> str:
+def _generate_member_name() -> str:
return b32encode(uuid4().bytes)[0:26].lower().decode("UTF-8")
@@ -143,18 +143,18 @@ def _get_or_create_catalog_zone(view: NBView) -> CatalogZone:
return catalog_zone
-def update_member_identifier(zone: NBZone) -> None:
+def update_member(zone: NBZone) -> None:
try:
catalog_zone = _get_or_create_catalog_zone(zone.view)
- CatalogZoneMemberIdentifier.objects.update_or_create(
+ CatalogZoneMember.objects.update_or_create(
zone=zone,
defaults={
- "name": _generate_member_identifier(),
+ "name": _generate_member_name(),
"catalog_zone": catalog_zone,
},
)
except OperationalError as e:
- LOGGER.error(f"ERROR: Failed to update Catalog Zone member identifier: {e}")
+ LOGGER.error(f"ERROR: Failed to update Catalog Zone member: {e}")
close_old_connections()
diff --git a/netbox_dns_bridge/management/commands/dns_transfer_endpoint/request_handler.py b/netbox_dns_bridge/management/commands/dns_transfer_endpoint/request_handler.py
index d38b0f1..0f725b3 100644
--- a/netbox_dns_bridge/management/commands/dns_transfer_endpoint/request_handler.py
+++ b/netbox_dns_bridge/management/commands/dns_transfer_endpoint/request_handler.py
@@ -15,7 +15,7 @@ from django.db import close_old_connections, OperationalError
from django.conf import settings
from netbox_dns.models import Zone, View
from . import catalog_zone_manager as catzm
-from netbox_dns_bridge.models import SeenTransferClients
+from netbox_dns_bridge.models import SeenTransferClient
from netbox_dns_bridge.utils import build_dns_zone, get_logger
LOGGER = get_logger(__name__)
@@ -77,7 +77,7 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
def _track_seen_client(self, source_ip: str, view: View) -> None:
if SETTINGS.get("notify_clients", False):
try:
- SeenTransferClients.objects.update_or_create(
+ SeenTransferClient.objects.update_or_create(
source_ip=source_ip,
view=view,
defaults={"last_seen": timezone.now()},
diff --git a/netbox_dns_bridge/migrations/0003_catalogzone_and_per_zone_identifier.py b/netbox_dns_bridge/migrations/0003_catalogzone_and_per_zone_identifier.py
deleted file mode 100644
index a8d8ae3..0000000
--- a/netbox_dns_bridge/migrations/0003_catalogzone_and_per_zone_identifier.py
+++ /dev/null
@@ -1,146 +0,0 @@
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-def copy_serials_and_link_identifiers(apps, schema_editor):
- """Seed one CatalogZone per view from the old global serial and link identifiers."""
- IntegerKeyValueSetting = apps.get_model(
- "netbox_dns_bridge", "IntegerKeyValueSetting"
- )
- CatalogZone = apps.get_model("netbox_dns_bridge", "CatalogZone")
- CatalogZoneMemberIdentifier = apps.get_model(
- "netbox_dns_bridge", "CatalogZoneMemberIdentifier"
- )
-
- # Preserve the current global serial; default to 1 when never initialized.
- seed_serial = 1
- kv = IntegerKeyValueSetting.objects.filter(key="catalog-zone-soa-serial").first()
- if kv is not None:
- seed_serial = kv.value
-
- # Create a CatalogZone for every view that currently has at least one
- # member identifier, seeding each with the old global serial.
- view_ids = set(
- CatalogZoneMemberIdentifier.objects.exclude(
- zone__view__isnull=True
- ).values_list("zone__view_id", flat=True)
- )
-
- view_id_to_catalog_zone = {}
- for view_id in view_ids:
- cz, _ = CatalogZone.objects.get_or_create(
- view_id=view_id, defaults={"soa_serial": seed_serial}
- )
- view_id_to_catalog_zone[view_id] = cz
-
- # Link each identifier to the CatalogZone matching its zone's view.
- for ident in CatalogZoneMemberIdentifier.objects.select_related("zone__view"):
- cz = view_id_to_catalog_zone.get(ident.zone.view_id)
- if cz is None:
- # Defensive: identifier existed without a resolvable view. Create
- # the CatalogZone on demand so the NOT NULL constraint can be set.
- cz, _ = CatalogZone.objects.get_or_create(
- view_id=ident.zone.view_id, defaults={"soa_serial": seed_serial}
- )
- view_id_to_catalog_zone[ident.zone.view_id] = cz
- ident.catalog_zone = cz
- ident.save(update_fields=["catalog_zone"])
-
-
-def reverse_migration(apps, schema_editor):
- """Best-effort reverse: restore the KV row and null the FK."""
- IntegerKeyValueSetting = apps.get_model(
- "netbox_dns_bridge", "IntegerKeyValueSetting"
- )
- CatalogZone = apps.get_model("netbox_dns_bridge", "CatalogZone")
- CatalogZoneMemberIdentifier = apps.get_model(
- "netbox_dns_bridge", "CatalogZoneMemberIdentifier"
- )
-
- # Pick a representative serial (first catalog zone) to write back.
- first = CatalogZone.objects.order_by("pk").first()
- serial = first.soa_serial if first is not None else 1
- IntegerKeyValueSetting.objects.update_or_create(
- key="catalog-zone-soa-serial", defaults={"value": serial}
- )
-
- CatalogZoneMemberIdentifier.objects.update(catalog_zone=None)
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- (
- "netbox_dns",
- "0030_dnsseckeytemplate_comments_dnsseckeytemplate_owner_and_more",
- ),
- ("netbox_dns_bridge", "0002_alter_integerkeyvaluesetting_options_and_more"),
- ]
-
- operations = [
- migrations.CreateModel(
- name="CatalogZone",
- fields=[
- (
- "id",
- models.BigAutoField(
- auto_created=True, primary_key=True, serialize=False
- ),
- ),
- ("soa_serial", models.IntegerField(default=1)),
- ("soa_refresh", models.IntegerField(default=60)),
- ("soa_retry", models.IntegerField(default=10)),
- ("soa_expire", models.IntegerField(default=1209600)),
- ("soa_minimum", models.IntegerField(default=0)),
- (
- "view",
- models.OneToOneField(
- on_delete=django.db.models.deletion.CASCADE,
- related_name="catalog_zone",
- to="netbox_dns.view",
- ),
- ),
- ],
- options={
- "ordering": ("view__name",),
- },
- ),
- migrations.AddField(
- model_name="catalogzonememberidentifier",
- name="catalog_zone",
- field=models.ForeignKey(
- null=True,
- on_delete=django.db.models.deletion.CASCADE,
- related_name="member_identifiers",
- to="netbox_dns_bridge.catalogzone",
- ),
- ),
- migrations.AlterField(
- model_name="catalogzonememberidentifier",
- name="name",
- field=models.CharField(max_length=26),
- ),
- migrations.RunPython(
- copy_serials_and_link_identifiers,
- reverse_code=reverse_migration,
- ),
- migrations.AlterField(
- model_name="catalogzonememberidentifier",
- name="catalog_zone",
- field=models.ForeignKey(
- on_delete=django.db.models.deletion.CASCADE,
- related_name="member_identifiers",
- to="netbox_dns_bridge.catalogzone",
- ),
- ),
- migrations.AddConstraint(
- model_name="catalogzonememberidentifier",
- constraint=models.UniqueConstraint(
- fields=("name", "catalog_zone"),
- name="unique_name_per_catalog_zone",
- ),
- ),
- migrations.DeleteModel(
- name="IntegerKeyValueSetting",
- ),
- ]
diff --git a/netbox_dns_bridge/migrations/0003_full_netbox_models.py b/netbox_dns_bridge/migrations/0003_full_netbox_models.py
new file mode 100644
index 0000000..62376fa
--- /dev/null
+++ b/netbox_dns_bridge/migrations/0003_full_netbox_models.py
@@ -0,0 +1,233 @@
+import taggit.managers
+import utilities.json
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+def copy_serials_and_link_members(apps, schema_editor):
+ """Seed one CatalogZone per view from the old global serial and link members."""
+ IntegerKeyValueSetting = apps.get_model(
+ "netbox_dns_bridge", "IntegerKeyValueSetting"
+ )
+ CatalogZone = apps.get_model("netbox_dns_bridge", "CatalogZone")
+ CatalogZoneMember = apps.get_model("netbox_dns_bridge", "CatalogZoneMember")
+
+ # Preserve the current global serial; default to 1 when never initialized.
+ seed_serial = 1
+ kv = IntegerKeyValueSetting.objects.filter(key="catalog-zone-soa-serial").first()
+ if kv is not None:
+ seed_serial = kv.value
+
+ # Create a CatalogZone for every view that currently has at least one
+ # member, seeding each with the old global serial.
+ view_ids = set(
+ CatalogZoneMember.objects.exclude(
+ zone__view__isnull=True
+ ).values_list("zone__view_id", flat=True)
+ )
+
+ view_id_to_catalog_zone = {}
+ for view_id in view_ids:
+ cz, _ = CatalogZone.objects.get_or_create(
+ view_id=view_id, defaults={"soa_serial": seed_serial}
+ )
+ view_id_to_catalog_zone[view_id] = cz
+
+ # Link each member to the CatalogZone matching its zone's view.
+ for member in CatalogZoneMember.objects.select_related("zone__view"):
+ cz = view_id_to_catalog_zone.get(member.zone.view_id)
+ if cz is None:
+ # Defensive: member existed without a resolvable view. Create
+ # the CatalogZone on demand so the NOT NULL constraint can be set.
+ cz, _ = CatalogZone.objects.get_or_create(
+ view_id=member.zone.view_id, defaults={"soa_serial": seed_serial}
+ )
+ view_id_to_catalog_zone[member.zone.view_id] = cz
+ member.catalog_zone = cz
+ member.save(update_fields=["catalog_zone"])
+
+
+def reverse_migration(apps, schema_editor):
+ """Best-effort reverse: restore the KV row and null the FK."""
+ IntegerKeyValueSetting = apps.get_model(
+ "netbox_dns_bridge", "IntegerKeyValueSetting"
+ )
+ CatalogZone = apps.get_model("netbox_dns_bridge", "CatalogZone")
+ CatalogZoneMember = apps.get_model("netbox_dns_bridge", "CatalogZoneMember")
+
+ # Pick a representative serial (first catalog zone) to write back.
+ first = CatalogZone.objects.order_by("pk").first()
+ serial = first.soa_serial if first is not None else 1
+ IntegerKeyValueSetting.objects.update_or_create(
+ key="catalog-zone-soa-serial", defaults={"value": serial}
+ )
+
+ CatalogZoneMember.objects.update(catalog_zone=None)
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("extras", "0140_imageattachment_image_size"),
+ (
+ "netbox_dns",
+ "0032_record_expiration_date",
+ ),
+ ("netbox_dns_bridge", "0002_alter_integerkeyvaluesetting_options_and_more"),
+ ]
+
+ operations = [
+ # --- CatalogZone (full NetBoxModel, born with all fields) ---
+ migrations.CreateModel(
+ name="CatalogZone",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True, primary_key=True, serialize=False
+ ),
+ ),
+ ("soa_serial", models.IntegerField(default=1)),
+ ("soa_refresh", models.IntegerField(default=60)),
+ ("soa_retry", models.IntegerField(default=10)),
+ ("soa_expire", models.IntegerField(default=1209600)),
+ ("soa_minimum", models.IntegerField(default=0)),
+ ("created", models.DateTimeField(auto_now_add=True, null=True)),
+ ("last_updated", models.DateTimeField(auto_now=True, null=True)),
+ (
+ "custom_field_data",
+ models.JSONField(
+ blank=True,
+ default=dict,
+ encoder=utilities.json.CustomFieldJSONEncoder,
+ ),
+ ),
+ (
+ "tags",
+ taggit.managers.TaggableManager(
+ through="extras.TaggedItem", to="extras.Tag"
+ ),
+ ),
+ (
+ "view",
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="catalog_zone",
+ to="netbox_dns.view",
+ ),
+ ),
+ ],
+ options={
+ "ordering": ("view__name",),
+ },
+ ),
+ # --- CatalogZoneMemberIdentifier → CatalogZoneMember (rename + full NetBoxModel) ---
+ migrations.RenameModel(
+ old_name="CatalogZoneMemberIdentifier",
+ new_name="CatalogZoneMember",
+ ),
+ migrations.AddField(
+ model_name="catalogzonemember",
+ name="created",
+ field=models.DateTimeField(auto_now_add=True, null=True),
+ ),
+ migrations.AddField(
+ model_name="catalogzonemember",
+ name="last_updated",
+ field=models.DateTimeField(auto_now=True, null=True),
+ ),
+ migrations.AddField(
+ model_name="catalogzonemember",
+ name="custom_field_data",
+ field=models.JSONField(
+ blank=True,
+ default=dict,
+ encoder=utilities.json.CustomFieldJSONEncoder,
+ ),
+ ),
+ migrations.AddField(
+ model_name="catalogzonemember",
+ name="tags",
+ field=taggit.managers.TaggableManager(
+ through="extras.TaggedItem", to="extras.Tag"
+ ),
+ ),
+ migrations.AlterField(
+ model_name="catalogzonemember",
+ name="name",
+ field=models.CharField(max_length=26),
+ ),
+ migrations.AlterField(
+ model_name="catalogzonemember",
+ name="zone",
+ field=models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="catalog_zone_member",
+ to="netbox_dns.zone",
+ ),
+ ),
+ migrations.AddField(
+ model_name="catalogzonemember",
+ name="catalog_zone",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="members",
+ to="netbox_dns_bridge.catalogzone",
+ ),
+ ),
+ migrations.RunPython(
+ copy_serials_and_link_members,
+ reverse_code=reverse_migration,
+ ),
+ migrations.AlterField(
+ model_name="catalogzonemember",
+ name="catalog_zone",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="members",
+ to="netbox_dns_bridge.catalogzone",
+ ),
+ ),
+ migrations.AddConstraint(
+ model_name="catalogzonemember",
+ constraint=models.UniqueConstraint(
+ fields=("name", "catalog_zone"),
+ name="unique_name_per_catalog_zone",
+ ),
+ ),
+ migrations.DeleteModel(
+ name="IntegerKeyValueSetting",
+ ),
+ # --- SeenTransferClients → SeenTransferClient (rename + full NetBoxModel) ---
+ migrations.RenameModel(
+ old_name="SeenTransferClients",
+ new_name="SeenTransferClient",
+ ),
+ migrations.AddField(
+ model_name="seentransferclient",
+ name="created",
+ field=models.DateTimeField(auto_now_add=True, null=True),
+ ),
+ migrations.AddField(
+ model_name="seentransferclient",
+ name="last_updated",
+ field=models.DateTimeField(auto_now=True, null=True),
+ ),
+ migrations.AddField(
+ model_name="seentransferclient",
+ name="custom_field_data",
+ field=models.JSONField(
+ blank=True,
+ default=dict,
+ encoder=utilities.json.CustomFieldJSONEncoder,
+ ),
+ ),
+ migrations.AddField(
+ model_name="seentransferclient",
+ name="tags",
+ field=taggit.managers.TaggableManager(
+ through="extras.TaggedItem", to="extras.Tag"
+ ),
+ ),
+ ]
diff --git a/netbox_dns_bridge/models.py b/netbox_dns_bridge/models.py
index 5eb479e..cd41b4e 100644
--- a/netbox_dns_bridge/models.py
+++ b/netbox_dns_bridge/models.py
@@ -1,13 +1,11 @@
from django.db import models
-from django.urls import reverse
# import netbox.models
import netbox_dns.models
+from netbox.models import NetBoxModel
-class CatalogZone(models.Model):
- _netbox_private = True
-
+class CatalogZone(NetBoxModel):
view = models.OneToOneField(
to=netbox_dns.models.View,
on_delete=models.CASCADE,
@@ -21,17 +19,14 @@ class CatalogZone(models.Model):
class Meta:
ordering = ("view__name",)
+ verbose_name = "Catalog Zone"
+ verbose_name_plural = "Catalog Zones"
def __str__(self):
return f"{self.view.name}: {self.soa_serial}"
- def get_absolute_url(self):
- return reverse("plugins:netbox_dns_bridge:catalogzone_edit", args=[self.pk])
-
-
-class CatalogZoneMemberIdentifier(models.Model):
- _netbox_private = True
+class CatalogZoneMember(NetBoxModel):
name = models.CharField(
max_length=26,
)
@@ -39,17 +34,19 @@ class CatalogZoneMemberIdentifier(models.Model):
zone = models.OneToOneField(
to=netbox_dns.models.Zone,
on_delete=models.CASCADE,
- related_name="catz_identifier",
+ related_name="catalog_zone_member",
)
catalog_zone = models.ForeignKey(
to=CatalogZone,
on_delete=models.CASCADE,
- related_name="member_identifiers",
+ related_name="members",
)
class Meta:
ordering = ("name",)
+ verbose_name = "Catalog Zone Member"
+ verbose_name_plural = "Catalog Zone Members"
constraints = [
models.UniqueConstraint(
fields=["name", "catalog_zone"],
@@ -61,9 +58,7 @@ class CatalogZoneMemberIdentifier(models.Model):
return self.name
-class SeenTransferClients(models.Model):
- _netbox_private = True
-
+class SeenTransferClient(NetBoxModel):
source_ip = models.GenericIPAddressField()
last_seen = models.DateTimeField()
view = models.ForeignKey(
@@ -77,6 +72,8 @@ class SeenTransferClients(models.Model):
class Meta:
ordering = ("source_ip",)
+ verbose_name = "Seen Transfer Client"
+ verbose_name_plural = "Seen Transfer Clients"
constraints = [
models.UniqueConstraint(
fields=["source_ip", "view"],
diff --git a/netbox_dns_bridge/navigation.py b/netbox_dns_bridge/navigation.py
index f8b616c..80a0671 100644
--- a/netbox_dns_bridge/navigation.py
+++ b/netbox_dns_bridge/navigation.py
@@ -2,32 +2,33 @@ from django.utils.translation import gettext_lazy as _
from netbox.plugins import PluginMenu, PluginMenuItem
catalog_zones_menu_item = PluginMenuItem(
- link="plugins:netbox_dns_bridge:catalog_zones",
+ link="plugins:netbox_dns_bridge:catalogzone_list",
link_text=_("Catalog Zones"),
- permissions=[],
- staff_only=True,
+ permissions=["netbox_dns_bridge.view_catalogzone"],
)
catalog_zone_members_menu_item = PluginMenuItem(
- link="plugins:netbox_dns_bridge:catalog_zone_members",
+ link="plugins:netbox_dns_bridge:catalogzonemember_list",
link_text=_("Catalog Zone Members"),
- permissions=[],
- staff_only=True,
+ permissions=["netbox_dns_bridge.view_catalogzonemember"],
)
-notify_menu_item = PluginMenuItem(
- link="plugins:netbox_dns_bridge:notify",
- link_text=_("Notify"),
- permissions=[],
- staff_only=True,
+seen_transfer_clients_menu_item = PluginMenuItem(
+ link="plugins:netbox_dns_bridge:seentransferclient_list",
+ link_text=_("Clients"),
+ permissions=["netbox_dns_bridge.view_seentransferclient"],
)
menu = PluginMenu(
label=_("DNS Bridge"),
groups=(
(
- _("Information"),
- (catalog_zones_menu_item, catalog_zone_members_menu_item, notify_menu_item),
+ _("Catalog Zone"),
+ (catalog_zones_menu_item, catalog_zone_members_menu_item),
+ ),
+ (
+ _("Notify"),
+ (seen_transfer_clients_menu_item,),
),
),
icon_class="mdi mdi-bridge",
diff --git a/netbox_dns_bridge/signals/netbox_dns_zones.py b/netbox_dns_bridge/signals/netbox_dns_zones.py
index f335254..b2ff8d9 100644
--- a/netbox_dns_bridge/signals/netbox_dns_zones.py
+++ b/netbox_dns_bridge/signals/netbox_dns_zones.py
@@ -77,15 +77,15 @@ def zone_post_save(sender, instance, created, **kwargs):
transaction.on_commit(_on_commit)
if created:
- # If zone was created, create catz member identifier record for this zone.
- catzm.update_member_identifier(zone)
+ # If zone was created, create the catalog zone member record for this zone.
+ catzm.update_member(zone)
else:
- # Re-create the catz member identifier when the zone name or its view
- # changed. A view move leaves the identifier pointing at the wrong
+ # Re-create the catalog zone member when the zone name or its view
+ # changed. A view move leaves the member pointing at the wrong
# catalog zone, so it must be refreshed.
old_name = getattr(zone, "_old_name", None)
if (old_name and zone.name != old_name) or view_changed:
- catzm.update_member_identifier(zone)
+ catzm.update_member(zone)
@receiver(post_delete, sender=Zone)
diff --git a/netbox_dns_bridge/tables.py b/netbox_dns_bridge/tables.py
index 61bbb8d..8ee2c36 100644
--- a/netbox_dns_bridge/tables.py
+++ b/netbox_dns_bridge/tables.py
@@ -1,29 +1,30 @@
import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
-import netbox_dns.models
from netbox.tables import NetBoxTable
+from netbox.tables.columns import ActionsColumn
-from .models import CatalogZone
+from .models import CatalogZone, CatalogZoneMember, SeenTransferClient
class CatalogZoneMemberTable(NetBoxTable):
name = tables.Column(linkify=True)
+ zone = tables.Column(
+ accessor="zone__name",
+ verbose_name=_("Zone"),
+ linkify=True,
+ )
view = tables.Column(
- accessor="view__name",
+ accessor="catalog_zone__view__name",
verbose_name=_("View"),
)
- status = tables.Column(verbose_name=_("Status"))
- catz_identifier = tables.Column(
- accessor="catz_identifier__name",
- verbose_name=_("Catalog Zone Identifier"),
- default="—",
- )
+
+ actions = ActionsColumn(actions=())
class Meta(NetBoxTable.Meta):
- model = netbox_dns.models.Zone
- fields = ("name", "view", "status", "catz_identifier")
- default_columns = ("name", "view", "status", "catz_identifier")
+ model = CatalogZoneMember
+ fields = ("name", "zone", "view")
+ default_columns = ("name", "zone", "view")
class CatalogZoneTable(NetBoxTable):
@@ -38,6 +39,8 @@ class CatalogZoneTable(NetBoxTable):
soa_expire = tables.Column(verbose_name=_("Expire"))
soa_minimum = tables.Column(verbose_name=_("Minimum"))
+ actions = ActionsColumn(actions=("edit", "changelog"))
+
class Meta(NetBoxTable.Meta):
model = CatalogZone
fields = (
@@ -56,3 +59,20 @@ class CatalogZoneTable(NetBoxTable):
"soa_expire",
"soa_minimum",
)
+
+
+class SeenTransferClientTable(NetBoxTable):
+ source_ip = tables.Column(linkify=True)
+ view = tables.Column(
+ accessor="view__name",
+ verbose_name=_("View"),
+ linkify=True,
+ )
+ last_seen = tables.DateTimeColumn(verbose_name=_("Last Seen"))
+
+ actions = ActionsColumn(actions=())
+
+ class Meta(NetBoxTable.Meta):
+ model = SeenTransferClient
+ fields = ("source_ip", "view", "last_seen")
+ default_columns = ("source_ip", "view", "last_seen")
diff --git a/netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_members.html b/netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_members.html
deleted file mode 100644
index 1352a41..0000000
--- a/netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_members.html
+++ /dev/null
@@ -1,4 +0,0 @@
-{% extends 'generic/object_list.html' %}
-{% load i18n %}
-
-{% block title %}{% trans "Catalog Zone Members" %}{% endblock %}
diff --git a/netbox_dns_bridge/templates/netbox_dns_bridge/catalogzone.html b/netbox_dns_bridge/templates/netbox_dns_bridge/catalogzone.html
new file mode 100644
index 0000000..dc11ad4
--- /dev/null
+++ b/netbox_dns_bridge/templates/netbox_dns_bridge/catalogzone.html
@@ -0,0 +1,50 @@
+{% extends 'generic/object.html' %}
+{% load plugins %}
+{% load i18n %}
+
+{% block content %}
+
+
+
+
+
+
+ | {% trans "View" %} |
+ {{ object.view|linkify }} |
+
+
+ | {% trans "SOA Serial" %} |
+ {{ object.soa_serial }} |
+
+
+ | {% trans "Refresh" %} |
+ {{ object.soa_refresh }} |
+
+
+ | {% trans "Retry" %} |
+ {{ object.soa_retry }} |
+
+
+ | {% trans "Expire" %} |
+ {{ object.soa_expire }} |
+
+
+ | {% trans "Minimum" %} |
+ {{ object.soa_minimum }} |
+
+
+
+ {% include 'inc/panels/custom_fields.html' %}
+ {% plugin_left_page object %}
+
+
+ {% include 'inc/panels/tags.html' %}
+ {% plugin_right_page object %}
+
+
+
+
+ {% plugin_full_width_page object %}
+
+
+{% endblock %}
diff --git a/netbox_dns_bridge/templates/netbox_dns_bridge/catalogzonemember.html b/netbox_dns_bridge/templates/netbox_dns_bridge/catalogzonemember.html
new file mode 100644
index 0000000..096d280
--- /dev/null
+++ b/netbox_dns_bridge/templates/netbox_dns_bridge/catalogzonemember.html
@@ -0,0 +1,42 @@
+{% extends 'generic/object.html' %}
+{% load plugins %}
+{% load i18n %}
+
+{% block content %}
+
+
+
+
+
+
+ | {% trans "Name" %} |
+ {{ object.name }} |
+
+
+ | {% trans "Zone" %} |
+ {{ object.zone|linkify }} |
+
+
+ | {% trans "Catalog Zone" %} |
+ {{ object.catalog_zone|linkify }} |
+
+
+ | {% trans "View" %} |
+ {{ object.catalog_zone.view|linkify }} |
+
+
+
+ {% include 'inc/panels/custom_fields.html' %}
+ {% plugin_left_page object %}
+
+
+ {% include 'inc/panels/tags.html' %}
+ {% plugin_right_page object %}
+
+
+
+
+ {% plugin_full_width_page object %}
+
+
+{% endblock %}
diff --git a/netbox_dns_bridge/templates/netbox_dns_bridge/notify_overview.html b/netbox_dns_bridge/templates/netbox_dns_bridge/notify_overview.html
deleted file mode 100644
index 78c6a44..0000000
--- a/netbox_dns_bridge/templates/netbox_dns_bridge/notify_overview.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{% extends 'base/layout.html' %}
-
-{% block title %}DNS Bridge Notify{% endblock %}
-
-{% block content %}
-
-
-
-
-
-
-
-
-
- | Source IP |
- View |
- Last Seen |
-
-
-
- {% for c in seen_clients %}
-
- | {{ c.source_ip }} |
- {{ c.view.name }} |
- {{ c.last_seen|date:"Y-m-d H:i:s" }} |
-
- {% empty %}
- | No seen transfer clients. |
- {% endfor %}
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/netbox_dns_bridge/templates/netbox_dns_bridge/seentransferclient.html b/netbox_dns_bridge/templates/netbox_dns_bridge/seentransferclient.html
new file mode 100644
index 0000000..ffb34d2
--- /dev/null
+++ b/netbox_dns_bridge/templates/netbox_dns_bridge/seentransferclient.html
@@ -0,0 +1,38 @@
+{% extends 'generic/object.html' %}
+{% load plugins %}
+{% load i18n %}
+
+{% block content %}
+
+
+
+
+
+
+ | {% trans "Source IP" %} |
+ {{ object.source_ip }} |
+
+
+ | {% trans "View" %} |
+ {{ object.view|linkify }} |
+
+
+ | {% trans "Last Seen" %} |
+ {{ object.last_seen|date:"Y-m-d H:i:s" }} |
+
+
+
+ {% include 'inc/panels/custom_fields.html' %}
+ {% plugin_left_page object %}
+
+
+ {% include 'inc/panels/tags.html' %}
+ {% plugin_right_page object %}
+
+
+
+
+ {% plugin_full_width_page object %}
+
+
+{% endblock %}
diff --git a/netbox_dns_bridge/urls.py b/netbox_dns_bridge/urls.py
index 18d97e2..441a70a 100644
--- a/netbox_dns_bridge/urls.py
+++ b/netbox_dns_bridge/urls.py
@@ -1,23 +1,47 @@
-from django.urls import path
+from django.urls import include, path
-from .views import (
+from utilities.urls import get_model_urls
+
+# Import views so register_model_view decorators run.
+from .views import ( # noqa: F401
CatalogZoneEditView,
CatalogZoneListView,
- CatalogZoneMembersView,
- NotifyOverviewView,
+ CatalogZoneMemberListView,
+ CatalogZoneMemberView,
+ CatalogZoneView,
+ SeenTransferClientListView,
+ SeenTransferClientView,
)
urlpatterns = [
- path("catalog-zones/", CatalogZoneListView.as_view(), name="catalog_zones"),
path(
- "catalog-zones//edit/",
- CatalogZoneEditView.as_view(),
- name="catalogzone_edit",
+ "catalog-zones/",
+ include(
+ get_model_urls("netbox_dns_bridge", "catalogzone", detail=False)
+ ),
+ ),
+ path(
+ "catalog-zones//",
+ include(get_model_urls("netbox_dns_bridge", "catalogzone")),
),
path(
"catalog-zone-members/",
- CatalogZoneMembersView.as_view(),
- name="catalog_zone_members",
+ include(
+ get_model_urls("netbox_dns_bridge", "catalogzonemember", detail=False)
+ ),
+ ),
+ path(
+ "catalog-zone-members//",
+ include(get_model_urls("netbox_dns_bridge", "catalogzonemember")),
+ ),
+ path(
+ "seen-transfer-clients/",
+ include(
+ get_model_urls("netbox_dns_bridge", "seentransferclient", detail=False)
+ ),
+ ),
+ path(
+ "seen-transfer-clients//",
+ include(get_model_urls("netbox_dns_bridge", "seentransferclient")),
),
- path("notify/", NotifyOverviewView.as_view(), name="notify"),
]
diff --git a/netbox_dns_bridge/views.py b/netbox_dns_bridge/views.py
index 1b310f8..1463b9d 100644
--- a/netbox_dns_bridge/views.py
+++ b/netbox_dns_bridge/views.py
@@ -1,67 +1,76 @@
-from django.contrib.auth.decorators import user_passes_test
-from django.shortcuts import render
-from django.utils.decorators import method_decorator
-from django.views import View
+from netbox.object_actions import EditObject
+from netbox.views import generic
+from utilities.views import register_model_view
-import netbox_dns.models
-from netbox.views.generic import ObjectEditView, ObjectListView
-
-from .filtersets import CatalogZoneFilterSet, ZoneFilterSet
+from .filtersets import (
+ CatalogZoneFilterSet,
+ CatalogZoneMemberFilterSet,
+ SeenTransferClientFilterSet,
+)
from .forms import (
CatalogZoneFilterForm,
CatalogZoneForm,
CatalogZoneMemberFilterForm,
+ SeenTransferClientFilterForm,
+)
+from .models import CatalogZone, CatalogZoneMember, SeenTransferClient
+from .tables import (
+ CatalogZoneMemberTable,
+ CatalogZoneTable,
+ SeenTransferClientTable,
)
-from .models import CatalogZone, SeenTransferClients
-from .tables import CatalogZoneMemberTable, CatalogZoneTable
-@method_decorator(
- user_passes_test(lambda u: u.is_superuser), name="dispatch"
-)
-class CatalogZoneListView(ObjectListView):
+@register_model_view(CatalogZone, "list", path="", detail=False)
+class CatalogZoneListView(generic.ObjectListView):
queryset = CatalogZone.objects.select_related("view").order_by("view__name")
table = CatalogZoneTable
filterset = CatalogZoneFilterSet
filterset_form = CatalogZoneFilterForm
- template_name = "netbox_dns_bridge/catalog_zone_list.html"
- actions = ("edit", "export")
-
-
-@method_decorator(
- user_passes_test(lambda u: u.is_superuser), name="dispatch"
-)
-class CatalogZoneEditView(ObjectEditView):
- queryset = CatalogZone.objects.select_related("view")
- form = CatalogZoneForm
- template_name = "netbox_dns_bridge/catalog_zone_edit.html"
-
-
-@method_decorator(
- user_passes_test(lambda u: u.is_superuser), name="dispatch"
-)
-class CatalogZoneMembersView(ObjectListView):
- queryset = netbox_dns.models.Zone.objects.select_related(
- "view", "catz_identifier"
- ).order_by("view__name", "name")
- table = CatalogZoneMemberTable
- filterset = ZoneFilterSet
- filterset_form = CatalogZoneMemberFilterForm
- template_name = "netbox_dns_bridge/catalog_zone_members.html"
actions = ()
-@method_decorator(
- user_passes_test(lambda u: u.is_superuser), name="dispatch"
-)
-class NotifyOverviewView(View):
- template_name = "netbox_dns_bridge/notify_overview.html"
+@register_model_view(CatalogZone)
+class CatalogZoneView(generic.ObjectView):
+ queryset = CatalogZone.objects.select_related("view")
+ actions = (EditObject,)
- def get(self, request):
- seen_clients = (
- SeenTransferClients.objects.select_related("view")
- .all()
- .order_by("source_ip")
- )
- context = {"seen_clients": seen_clients}
- return render(request, self.template_name, context)
+
+@register_model_view(CatalogZone, "edit")
+class CatalogZoneEditView(generic.ObjectEditView):
+ queryset = CatalogZone.objects.select_related("view")
+ form = CatalogZoneForm
+
+
+@register_model_view(CatalogZoneMember, "list", path="", detail=False)
+class CatalogZoneMemberListView(generic.ObjectListView):
+ queryset = CatalogZoneMember.objects.select_related(
+ "zone", "catalog_zone__view"
+ ).order_by("catalog_zone__view__name", "name")
+ table = CatalogZoneMemberTable
+ filterset = CatalogZoneMemberFilterSet
+ filterset_form = CatalogZoneMemberFilterForm
+ actions = ()
+
+
+@register_model_view(CatalogZoneMember)
+class CatalogZoneMemberView(generic.ObjectView):
+ queryset = CatalogZoneMember.objects.select_related(
+ "zone", "catalog_zone__view"
+ )
+ actions = ()
+
+
+@register_model_view(SeenTransferClient, "list", path="", detail=False)
+class SeenTransferClientListView(generic.ObjectListView):
+ queryset = SeenTransferClient.objects.select_related("view").order_by("source_ip")
+ table = SeenTransferClientTable
+ filterset = SeenTransferClientFilterSet
+ filterset_form = SeenTransferClientFilterForm
+ actions = ()
+
+
+@register_model_view(SeenTransferClient)
+class SeenTransferClientView(generic.ObjectView):
+ queryset = SeenTransferClient.objects.select_related("view")
+ actions = ()