Move from hard-coded CatalogZone SOA values (refresh, retry, expire and minimum)
to DB driven to permit user modification: modified: netbox_dns_bridge/models.py Add 4 cols for values. modified: netbox_dns_bridge/migrations/0003_catalogzone_and_per_zone_identifier.py Adjust migration. This expects that the last migration the user has is 0002 (<=v1.5.8) modified: netbox_dns_bridge/jobs/notify.py modified: netbox_dns_bridge/management/commands/dns_transfer_endpoint/catalog_zone_manager.py Update to use new db values for SOA creation. Change UI to permit editing for SOA values and disable member identifier editing. modified: netbox_dns_bridge/forms.py modified: netbox_dns_bridge/navigation.py new file: netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_edit.html new file: netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_list.html new file: netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_members.html deleted: netbox_dns_bridge/templates/netbox_dns_bridge/catz_overview.html modified: netbox_dns_bridge/tables.py modified: netbox_dns_bridge/urls.py modified: netbox_dns_bridge/views.py modified: netbox_dns_bridge/filtersets.py AI created/modified: Left the UI update to the LLM. modified: netbox_dns_bridge/tests/test_catalog_zone_manager.py modified: netbox_dns_bridge/tests/test_models.py modified: netbox_dns_bridge/tests/test_notify.py modified: netbox_dns_bridge/tests/test_views.py AI created/modified: Updating tests
This commit is contained in:
1 parent
73d04b956d
commit
14805ed32b
18 files changed
+349
-77
No files matched your search
@@ -5,6 +5,8 @@ import netbox_dns.models
|
|||||||
from netbox.filtersets import NetBoxModelFilterSet
|
from netbox.filtersets import NetBoxModelFilterSet
|
||||||
from netbox_dns.choices import ZoneStatusChoices
|
from netbox_dns.choices import ZoneStatusChoices
|
||||||
|
|
||||||
|
from .models import CatalogZone
|
||||||
|
|
||||||
|
|
||||||
class ZoneFilterSet(NetBoxModelFilterSet):
|
class ZoneFilterSet(NetBoxModelFilterSet):
|
||||||
view = django_filters.ModelMultipleChoiceFilter(
|
view = django_filters.ModelMultipleChoiceFilter(
|
||||||
@@ -27,3 +29,19 @@ class ZoneFilterSet(NetBoxModelFilterSet):
|
|||||||
Q(name__icontains=value)
|
Q(name__icontains=value)
|
||||||
| Q(catz_identifier__name__icontains=value)
|
| Q(catz_identifier__name__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CatalogZoneFilterSet(NetBoxModelFilterSet):
|
||||||
|
view = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name="view",
|
||||||
|
queryset=netbox_dns.models.View.objects.all(),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CatalogZone
|
||||||
|
fields = ("id", "view")
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(Q(view__name__icontains=value))
|
||||||
@@ -2,12 +2,40 @@ from django import forms
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import netbox_dns.models
|
import netbox_dns.models
|
||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelForm
|
||||||
from netbox_dns.choices import ZoneStatusChoices
|
from netbox_dns.choices import ZoneStatusChoices
|
||||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||||
|
|
||||||
|
from .models import CatalogZone
|
||||||
|
|
||||||
class CatalogZoneFilterForm(NetBoxModelFilterSetForm):
|
|
||||||
|
class CatalogZoneForm(NetBoxModelForm):
|
||||||
|
soa_serial = forms.IntegerField(label=_("SOA Serial"))
|
||||||
|
soa_refresh = forms.IntegerField(label=_("Refresh"))
|
||||||
|
soa_retry = forms.IntegerField(label=_("Retry"))
|
||||||
|
soa_expire = forms.IntegerField(label=_("Expire"))
|
||||||
|
soa_minimum = forms.IntegerField(label=_("Minimum"))
|
||||||
|
|
||||||
|
view = forms.ModelChoiceField(
|
||||||
|
queryset=netbox_dns.models.View.objects.all(),
|
||||||
|
disabled=True,
|
||||||
|
required=False,
|
||||||
|
label=_("View"),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CatalogZone
|
||||||
|
fields = (
|
||||||
|
"view",
|
||||||
|
"soa_serial",
|
||||||
|
"soa_refresh",
|
||||||
|
"soa_retry",
|
||||||
|
"soa_expire",
|
||||||
|
"soa_minimum",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CatalogZoneMemberFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = netbox_dns.models.Zone
|
model = netbox_dns.models.Zone
|
||||||
|
|
||||||
q = forms.CharField(required=False, label=_("Search"))
|
q = forms.CharField(required=False, label=_("Search"))
|
||||||
@@ -19,3 +47,13 @@ class CatalogZoneFilterForm(NetBoxModelFilterSetForm):
|
|||||||
choices=ZoneStatusChoices,
|
choices=ZoneStatusChoices,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CatalogZoneFilterForm(NetBoxModelFilterSetForm):
|
||||||
|
model = CatalogZone
|
||||||
|
|
||||||
|
q = forms.CharField(required=False, label=_("Search"))
|
||||||
|
view = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=netbox_dns.models.View.objects.all(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
@@ -51,6 +51,10 @@ def _send_notify(
|
|||||||
notify_over_tcp,
|
notify_over_tcp,
|
||||||
port,
|
port,
|
||||||
log_prefix="NOTIFY",
|
log_prefix="NOTIFY",
|
||||||
|
soa_refresh=60,
|
||||||
|
soa_retry=10,
|
||||||
|
soa_expire=1209600,
|
||||||
|
soa_minimum=0,
|
||||||
):
|
):
|
||||||
"""Build a DNS NOTIFY message for *zone_name* and send it to every target IP."""
|
"""Build a DNS NOTIFY message for *zone_name* and send it to every target IP."""
|
||||||
fqdn = dns.name.from_text(zone_name, dns.name.root).to_text()
|
fqdn = dns.name.from_text(zone_name, dns.name.root).to_text()
|
||||||
@@ -61,7 +65,7 @@ def _send_notify(
|
|||||||
soa_rdata = dns.rdata.from_text(
|
soa_rdata = dns.rdata.from_text(
|
||||||
dns.rdataclass.IN,
|
dns.rdataclass.IN,
|
||||||
dns.rdatatype.SOA,
|
dns.rdatatype.SOA,
|
||||||
f"invalid. invalid. {soa_serial} 60 10 1209600 0",
|
f"invalid. invalid. {soa_serial} {soa_refresh} {soa_retry} {soa_expire} {soa_minimum}",
|
||||||
)
|
)
|
||||||
msg.answer.append(dns.rrset.from_rdata(fqdn, 0, soa_rdata))
|
msg.answer.append(dns.rrset.from_rdata(fqdn, 0, soa_rdata))
|
||||||
msg.use_tsig(keyring={tsig_key.name: tsig_key}, keyname=tsig_key.name)
|
msg.use_tsig(keyring={tsig_key.name: tsig_key}, keyname=tsig_key.name)
|
||||||
@@ -95,6 +99,10 @@ class SendClientNotify(JobRunner):
|
|||||||
view_name = kwargs["view_name"]
|
view_name = kwargs["view_name"]
|
||||||
zone_name = kwargs["zone_name"]
|
zone_name = kwargs["zone_name"]
|
||||||
soa_serial = kwargs["soa_serial"]
|
soa_serial = kwargs["soa_serial"]
|
||||||
|
soa_refresh = kwargs.get("soa_refresh", 60)
|
||||||
|
soa_retry = kwargs.get("soa_retry", 10)
|
||||||
|
soa_expire = kwargs.get("soa_expire", 1209600)
|
||||||
|
soa_minimum = kwargs.get("soa_minimum", 0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
view = View.objects.get(name=view_name)
|
view = View.objects.get(name=view_name)
|
||||||
@@ -111,6 +119,8 @@ class SendClientNotify(JobRunner):
|
|||||||
_send_notify(
|
_send_notify(
|
||||||
zone_name, soa_serial, clients, tsig_key,
|
zone_name, soa_serial, clients, tsig_key,
|
||||||
view_name, notify_over_tcp, port, log_prefix="NOTIFY",
|
view_name, notify_over_tcp, port, log_prefix="NOTIFY",
|
||||||
|
soa_refresh=soa_refresh, soa_retry=soa_retry,
|
||||||
|
soa_expire=soa_expire, soa_minimum=soa_minimum,
|
||||||
)
|
)
|
||||||
|
|
||||||
except View.DoesNotExist as e:
|
except View.DoesNotExist as e:
|
||||||
@@ -278,17 +288,31 @@ def schedule_catalog_zone_notify(view: View):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
serial = view.catalog_zone.soa_serial
|
catalog_zone = view.catalog_zone
|
||||||
except CatalogZone.DoesNotExist:
|
except CatalogZone.DoesNotExist:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
serial = catalog_zone.soa_serial
|
||||||
|
soa_refresh = catalog_zone.soa_refresh
|
||||||
|
soa_retry = catalog_zone.soa_retry
|
||||||
|
soa_expire = catalog_zone.soa_expire
|
||||||
|
soa_minimum = catalog_zone.soa_minimum
|
||||||
|
|
||||||
SendClientNotify.enqueue(
|
SendClientNotify.enqueue(
|
||||||
zone_name="catz",
|
zone_name="catz",
|
||||||
soa_serial=serial,
|
soa_serial=serial,
|
||||||
view_name=view.name,
|
view_name=view.name,
|
||||||
|
soa_refresh=soa_refresh,
|
||||||
|
soa_retry=soa_retry,
|
||||||
|
soa_expire=soa_expire,
|
||||||
|
soa_minimum=soa_minimum,
|
||||||
)
|
)
|
||||||
SendClientNotify.enqueue(
|
SendClientNotify.enqueue(
|
||||||
zone_name=f"{view.name}.catz",
|
zone_name=f"{view.name}.catz",
|
||||||
soa_serial=serial,
|
soa_serial=serial,
|
||||||
view_name=view.name,
|
view_name=view.name,
|
||||||
|
soa_refresh=soa_refresh,
|
||||||
|
soa_retry=soa_retry,
|
||||||
|
soa_expire=soa_expire,
|
||||||
|
soa_minimum=soa_minimum,
|
||||||
)
|
)
|
||||||
@@ -174,7 +174,9 @@ def _create_soa_rdataset(view: NBView) -> dns.rdataset:
|
|||||||
rdata = dns.rdata.from_text(
|
rdata = dns.rdata.from_text(
|
||||||
rclass,
|
rclass,
|
||||||
rtype,
|
rtype,
|
||||||
f"invalid. invalid. {serial} 60 10 1209600 0",
|
f"invalid. invalid. {serial} {serial_obj.soa_refresh} "
|
||||||
|
f"{serial_obj.soa_retry} {serial_obj.soa_expire} "
|
||||||
|
f"{serial_obj.soa_minimum}",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create Rdataset and add the RDATA to it
|
# Create Rdataset and add the RDATA to it
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
("soa_serial", models.IntegerField(default=1)),
|
("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",
|
"view",
|
||||||
models.OneToOneField(
|
models.OneToOneField(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
# import netbox.models
|
# import netbox.models
|
||||||
import netbox_dns.models
|
import netbox_dns.models
|
||||||
@@ -13,6 +14,10 @@ class CatalogZone(models.Model):
|
|||||||
related_name="catalog_zone",
|
related_name="catalog_zone",
|
||||||
)
|
)
|
||||||
soa_serial = models.IntegerField(default=1)
|
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)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("view__name",)
|
ordering = ("view__name",)
|
||||||
@@ -20,6 +25,9 @@ class CatalogZone(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.view.name}: {self.soa_serial}"
|
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):
|
class CatalogZoneMemberIdentifier(models.Model):
|
||||||
_netbox_private = True
|
_netbox_private = True
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from netbox.plugins import PluginMenu, PluginMenuItem
|
from netbox.plugins import PluginMenu, PluginMenuItem
|
||||||
|
|
||||||
information_menu_item = PluginMenuItem(
|
catalog_zones_menu_item = PluginMenuItem(
|
||||||
link="plugins:netbox_dns_bridge:catz",
|
link="plugins:netbox_dns_bridge:catalog_zones",
|
||||||
link_text=_("Catalog Zones"),
|
link_text=_("Catalog Zones"),
|
||||||
permissions=[],
|
permissions=[],
|
||||||
staff_only=True,
|
staff_only=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
catalog_zone_members_menu_item = PluginMenuItem(
|
||||||
|
link="plugins:netbox_dns_bridge:catalog_zone_members",
|
||||||
|
link_text=_("Catalog Zone Members"),
|
||||||
|
permissions=[],
|
||||||
|
staff_only=True,
|
||||||
|
)
|
||||||
|
|
||||||
notify_menu_item = PluginMenuItem(
|
notify_menu_item = PluginMenuItem(
|
||||||
link="plugins:netbox_dns_bridge:notify",
|
link="plugins:netbox_dns_bridge:notify",
|
||||||
link_text=_("Notify"),
|
link_text=_("Notify"),
|
||||||
@@ -18,7 +25,10 @@ notify_menu_item = PluginMenuItem(
|
|||||||
menu = PluginMenu(
|
menu = PluginMenu(
|
||||||
label=_("DNS Bridge"),
|
label=_("DNS Bridge"),
|
||||||
groups=(
|
groups=(
|
||||||
(_("Information"), (information_menu_item, notify_menu_item)),
|
(
|
||||||
|
_("Information"),
|
||||||
|
(catalog_zones_menu_item, catalog_zone_members_menu_item, notify_menu_item),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
icon_class="mdi mdi-bridge",
|
icon_class="mdi mdi-bridge",
|
||||||
)
|
)
|
||||||
@@ -4,8 +4,10 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
import netbox_dns.models
|
import netbox_dns.models
|
||||||
from netbox.tables import NetBoxTable
|
from netbox.tables import NetBoxTable
|
||||||
|
|
||||||
|
from .models import CatalogZone
|
||||||
|
|
||||||
class CatalogZoneTable(NetBoxTable):
|
|
||||||
|
class CatalogZoneMemberTable(NetBoxTable):
|
||||||
name = tables.Column(linkify=True)
|
name = tables.Column(linkify=True)
|
||||||
view = tables.Column(
|
view = tables.Column(
|
||||||
accessor="view__name",
|
accessor="view__name",
|
||||||
@@ -22,3 +24,35 @@ class CatalogZoneTable(NetBoxTable):
|
|||||||
model = netbox_dns.models.Zone
|
model = netbox_dns.models.Zone
|
||||||
fields = ("name", "view", "status", "catz_identifier")
|
fields = ("name", "view", "status", "catz_identifier")
|
||||||
default_columns = ("name", "view", "status", "catz_identifier")
|
default_columns = ("name", "view", "status", "catz_identifier")
|
||||||
|
|
||||||
|
|
||||||
|
class CatalogZoneTable(NetBoxTable):
|
||||||
|
view = tables.Column(
|
||||||
|
accessor="view__name",
|
||||||
|
verbose_name=_("View"),
|
||||||
|
linkify=True,
|
||||||
|
)
|
||||||
|
soa_serial = tables.Column(verbose_name=_("SOA Serial"))
|
||||||
|
soa_refresh = tables.Column(verbose_name=_("Refresh"))
|
||||||
|
soa_retry = tables.Column(verbose_name=_("Retry"))
|
||||||
|
soa_expire = tables.Column(verbose_name=_("Expire"))
|
||||||
|
soa_minimum = tables.Column(verbose_name=_("Minimum"))
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = CatalogZone
|
||||||
|
fields = (
|
||||||
|
"view",
|
||||||
|
"soa_serial",
|
||||||
|
"soa_refresh",
|
||||||
|
"soa_retry",
|
||||||
|
"soa_expire",
|
||||||
|
"soa_minimum",
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
"view",
|
||||||
|
"soa_serial",
|
||||||
|
"soa_refresh",
|
||||||
|
"soa_retry",
|
||||||
|
"soa_expire",
|
||||||
|
"soa_minimum",
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{% extends 'generic/object_edit.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Edit Catalog Zone" %}{% endblock %}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{% extends 'generic/object_list.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Catalog Zones" %}{% endblock %}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{% extends 'generic/object_list.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Catalog Zone Members" %}{% endblock %}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
{% extends 'generic/object_list.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Catalog Zone Info" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">{% trans "Catalog Zone SOA Serial Numbers" %}</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
{% if catalog_zones %}
|
|
||||||
<table class="table table-sm">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "View" %}</th>
|
|
||||||
<th>{% trans "SOA Serial" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for cz in catalog_zones %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ cz.view.name }}</td>
|
|
||||||
<td>{{ cz.soa_serial }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">{% trans "No catalog zones initialized." %}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
@@ -174,6 +174,23 @@ class TestCreateZone:
|
|||||||
soa = zone.get_rdataset(origin, dns.rdatatype.SOA)
|
soa = zone.get_rdataset(origin, dns.rdatatype.SOA)
|
||||||
assert soa[0].serial == catz_serial
|
assert soa[0].serial == catz_serial
|
||||||
|
|
||||||
|
def test_soa_parameters_from_model(self, make_zone, make_view):
|
||||||
|
view = make_view(name="cz-soa-params-view")
|
||||||
|
make_zone(name="soa-params.test", view=view)
|
||||||
|
CatalogZone.objects.filter(view=view).update(
|
||||||
|
soa_refresh=300,
|
||||||
|
soa_retry=100,
|
||||||
|
soa_expire=86400,
|
||||||
|
soa_minimum=3600,
|
||||||
|
)
|
||||||
|
zone = catzm.create_zone("catz", "cz-soa-params-view")
|
||||||
|
origin = dns.name.from_text("catz", dns.name.root)
|
||||||
|
soa = zone.get_rdataset(origin, dns.rdatatype.SOA)
|
||||||
|
assert soa[0].refresh == 300
|
||||||
|
assert soa[0].retry == 100
|
||||||
|
assert soa[0].expire == 86400
|
||||||
|
assert soa[0].minimum == 3600
|
||||||
|
|
||||||
def test_creates_identifier_on_the_fly(self, make_zone, make_view):
|
def test_creates_identifier_on_the_fly(self, make_zone, make_view):
|
||||||
view = make_view(name="cz-ident-view")
|
view = make_view(name="cz-ident-view")
|
||||||
zone_obj = make_zone(name="no-ident.test", view=view)
|
zone_obj = make_zone(name="no-ident.test", view=view)
|
||||||
|
|||||||
@@ -20,6 +20,28 @@ class TestCatalogZone:
|
|||||||
cz = CatalogZone.objects.create(view=view)
|
cz = CatalogZone.objects.create(view=view)
|
||||||
assert cz.soa_serial == 1
|
assert cz.soa_serial == 1
|
||||||
|
|
||||||
|
def test_default_soa_parameters(self, make_view):
|
||||||
|
view = make_view(name="cz-soa-default-view")
|
||||||
|
cz = CatalogZone.objects.create(view=view)
|
||||||
|
assert cz.soa_refresh == 60
|
||||||
|
assert cz.soa_retry == 10
|
||||||
|
assert cz.soa_expire == 1209600
|
||||||
|
assert cz.soa_minimum == 0
|
||||||
|
|
||||||
|
def test_custom_soa_parameters(self, make_view):
|
||||||
|
view = make_view(name="cz-soa-custom-view")
|
||||||
|
cz = CatalogZone.objects.create(
|
||||||
|
view=view,
|
||||||
|
soa_refresh=300,
|
||||||
|
soa_retry=100,
|
||||||
|
soa_expire=86400,
|
||||||
|
soa_minimum=3600,
|
||||||
|
)
|
||||||
|
assert cz.soa_refresh == 300
|
||||||
|
assert cz.soa_retry == 100
|
||||||
|
assert cz.soa_expire == 86400
|
||||||
|
assert cz.soa_minimum == 3600
|
||||||
|
|
||||||
def test_one_to_one_relationship(self, make_view):
|
def test_one_to_one_relationship(self, make_view):
|
||||||
view = make_view(name="cz-oto-view")
|
view = make_view(name="cz-oto-view")
|
||||||
cz = CatalogZone.objects.create(view=view, soa_serial=7)
|
cz = CatalogZone.objects.create(view=view, soa_serial=7)
|
||||||
|
|||||||
@@ -229,12 +229,22 @@ class TestScheduleCatalogZoneNotify:
|
|||||||
nh.schedule_catalog_zone_notify(view)
|
nh.schedule_catalog_zone_notify(view)
|
||||||
assert mock_enq.call_count == 2
|
assert mock_enq.call_count == 2
|
||||||
mock_enq.assert_any_call(
|
mock_enq.assert_any_call(
|
||||||
zone_name="catz", soa_serial=42, view_name="catz-notify-view"
|
zone_name="catz",
|
||||||
|
soa_serial=42,
|
||||||
|
view_name="catz-notify-view",
|
||||||
|
soa_refresh=60,
|
||||||
|
soa_retry=10,
|
||||||
|
soa_expire=1209600,
|
||||||
|
soa_minimum=0,
|
||||||
)
|
)
|
||||||
mock_enq.assert_any_call(
|
mock_enq.assert_any_call(
|
||||||
zone_name="catz-notify-view.catz",
|
zone_name="catz-notify-view.catz",
|
||||||
soa_serial=42,
|
soa_serial=42,
|
||||||
view_name="catz-notify-view",
|
view_name="catz-notify-view",
|
||||||
|
soa_refresh=60,
|
||||||
|
soa_retry=10,
|
||||||
|
soa_expire=1209600,
|
||||||
|
soa_minimum=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_does_not_enqueue_when_notify_clients_disabled(self, make_view, monkeypatch):
|
def test_does_not_enqueue_when_notify_clients_disabled(self, make_view, monkeypatch):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Tests for views: CatzOverviewView, NotifyOverviewView — auth gate and context."""
|
"""Tests for views: CatalogZoneMembersView, CatalogZoneListView, CatalogZoneEditView, NotifyOverviewView — auth gate and context."""
|
||||||
import pytest
|
import pytest
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
@@ -28,36 +28,113 @@ def regular_user(db):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestCatzOverviewView:
|
class TestCatalogZoneMembersView:
|
||||||
def test_unauthenticated_redirects_to_login(self, client):
|
def test_unauthenticated_redirects_to_login(self, client):
|
||||||
resp = client.get("/plugins/dns-bridge/catz/")
|
resp = client.get("/plugins/dns-bridge/catalog-zone-members/")
|
||||||
assert resp.status_code in (302, 403)
|
assert resp.status_code in (302, 403)
|
||||||
|
|
||||||
def test_regular_user_forbidden(self, client, regular_user):
|
def test_regular_user_forbidden(self, client, regular_user):
|
||||||
client.login(username="regular", password="testpass")
|
client.login(username="regular", password="testpass")
|
||||||
resp = client.get("/plugins/dns-bridge/catz/")
|
resp = client.get("/plugins/dns-bridge/catalog-zone-members/")
|
||||||
assert resp.status_code in (302, 403)
|
assert resp.status_code in (302, 403)
|
||||||
|
|
||||||
def test_superuser_can_access(self, client, superuser):
|
def test_superuser_can_access(self, client, superuser):
|
||||||
client.login(username="admin", password="testpass")
|
client.login(username="admin", password="testpass")
|
||||||
resp = client.get("/plugins/dns-bridge/catz/")
|
resp = client.get("/plugins/dns-bridge/catalog-zone-members/")
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
def test_context_contains_catalog_zones(self, client, superuser, catz_serial, make_zone):
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestCatalogZoneListView:
|
||||||
|
def test_unauthenticated_redirects_to_login(self, client):
|
||||||
|
resp = client.get("/plugins/dns-bridge/catalog-zones/")
|
||||||
|
assert resp.status_code in (302, 403)
|
||||||
|
|
||||||
|
def test_regular_user_forbidden(self, client, regular_user):
|
||||||
|
client.login(username="regular", password="testpass")
|
||||||
|
resp = client.get("/plugins/dns-bridge/catalog-zones/")
|
||||||
|
assert resp.status_code in (302, 403)
|
||||||
|
|
||||||
|
def test_superuser_can_access(self, client, superuser):
|
||||||
|
client.login(username="admin", password="testpass")
|
||||||
|
resp = client.get("/plugins/dns-bridge/catalog-zones/")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
def test_list_contains_catalog_zones(self, client, superuser, catz_serial, make_zone):
|
||||||
zone = make_zone(name="ctx.test")
|
zone = make_zone(name="ctx.test")
|
||||||
client.login(username="admin", password="testpass")
|
client.login(username="admin", password="testpass")
|
||||||
resp = client.get("/plugins/dns-bridge/catz/")
|
resp = client.get("/plugins/dns-bridge/catalog-zones/")
|
||||||
assert "catalog_zones" in resp.context
|
cz_list = list(resp.context["table"].data)
|
||||||
cz_list = list(resp.context["catalog_zones"])
|
|
||||||
assert len(cz_list) >= 1
|
assert len(cz_list) >= 1
|
||||||
cz = next(c for c in cz_list if c.view_id == zone.view_id)
|
cz = next(c for c in cz_list if c.view_id == zone.view_id)
|
||||||
assert cz.soa_serial == catz_serial
|
assert cz.soa_serial == catz_serial
|
||||||
|
|
||||||
def test_context_no_catalog_zones_when_empty(self, client, superuser):
|
def test_list_empty_when_no_catalog_zones(self, client, superuser):
|
||||||
client.login(username="admin", password="testpass")
|
client.login(username="admin", password="testpass")
|
||||||
resp = client.get("/plugins/dns-bridge/catz/")
|
resp = client.get("/plugins/dns-bridge/catalog-zones/")
|
||||||
assert "catalog_zones" in resp.context
|
cz_list = list(resp.context["table"].data)
|
||||||
assert list(resp.context["catalog_zones"]) == []
|
assert cz_list == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestCatalogZoneEditView:
|
||||||
|
def test_unauthenticated_redirects_to_login(self, client, make_view):
|
||||||
|
view = make_view(name="edit-cz-view")
|
||||||
|
from netbox_dns_bridge.models import CatalogZone
|
||||||
|
cz = CatalogZone.objects.create(view=view)
|
||||||
|
resp = client.get(f"/plugins/dns-bridge/catalog-zones/{cz.pk}/edit/")
|
||||||
|
assert resp.status_code in (302, 403)
|
||||||
|
|
||||||
|
def test_regular_user_forbidden(self, client, regular_user, make_view):
|
||||||
|
view = make_view(name="edit-cz-view-forbidden")
|
||||||
|
from netbox_dns_bridge.models import CatalogZone
|
||||||
|
cz = CatalogZone.objects.create(view=view)
|
||||||
|
client.login(username="regular", password="testpass")
|
||||||
|
resp = client.get(f"/plugins/dns-bridge/catalog-zones/{cz.pk}/edit/")
|
||||||
|
assert resp.status_code in (302, 403)
|
||||||
|
|
||||||
|
def test_superuser_can_get_edit_form(self, client, superuser, make_view):
|
||||||
|
view = make_view(name="edit-cz-view-get")
|
||||||
|
from netbox_dns_bridge.models import CatalogZone
|
||||||
|
cz = CatalogZone.objects.create(view=view)
|
||||||
|
client.login(username="admin", password="testpass")
|
||||||
|
resp = client.get(f"/plugins/dns-bridge/catalog-zones/{cz.pk}/edit/")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
def test_superuser_can_update_soa_fields(self, client, superuser, make_view):
|
||||||
|
view = make_view(name="edit-cz-view-post")
|
||||||
|
from netbox_dns_bridge.models import CatalogZone
|
||||||
|
cz = CatalogZone.objects.create(view=view)
|
||||||
|
client.login(username="admin", password="testpass")
|
||||||
|
resp = client.post(
|
||||||
|
f"/plugins/dns-bridge/catalog-zones/{cz.pk}/edit/",
|
||||||
|
{
|
||||||
|
"soa_serial": 100,
|
||||||
|
"soa_refresh": 300,
|
||||||
|
"soa_retry": 100,
|
||||||
|
"soa_expire": 86400,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
cz.refresh_from_db()
|
||||||
|
assert cz.soa_serial == 100
|
||||||
|
assert cz.soa_refresh == 300
|
||||||
|
assert cz.soa_retry == 100
|
||||||
|
assert cz.soa_expire == 86400
|
||||||
|
assert cz.soa_minimum == 3600
|
||||||
|
|
||||||
|
def test_no_create_route(self, client, superuser):
|
||||||
|
client.login(username="admin", password="testpass")
|
||||||
|
resp = client.get("/plugins/dns-bridge/catalog-zones/add/")
|
||||||
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
def test_no_delete_route(self, client, superuser, make_view):
|
||||||
|
view = make_view(name="edit-cz-view-delete")
|
||||||
|
from netbox_dns_bridge.models import CatalogZone
|
||||||
|
cz = CatalogZone.objects.create(view=view)
|
||||||
|
client.login(username="admin", password="testpass")
|
||||||
|
resp = client.get(f"/plugins/dns-bridge/catalog-zones/{cz.pk}/delete/")
|
||||||
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import CatzOverviewView, NotifyOverviewView
|
from .views import (
|
||||||
|
CatalogZoneEditView,
|
||||||
|
CatalogZoneListView,
|
||||||
|
CatalogZoneMembersView,
|
||||||
|
NotifyOverviewView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("catz/", CatzOverviewView.as_view(), name="catz"),
|
path("catalog-zones/", CatalogZoneListView.as_view(), name="catalog_zones"),
|
||||||
|
path(
|
||||||
|
"catalog-zones/<int:pk>/edit/",
|
||||||
|
CatalogZoneEditView.as_view(),
|
||||||
|
name="catalogzone_edit",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"catalog-zone-members/",
|
||||||
|
CatalogZoneMembersView.as_view(),
|
||||||
|
name="catalog_zone_members",
|
||||||
|
),
|
||||||
path("notify/", NotifyOverviewView.as_view(), name="notify"),
|
path("notify/", NotifyOverviewView.as_view(), name="notify"),
|
||||||
]
|
]
|
||||||
+33
-15
@@ -4,34 +4,52 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
import netbox_dns.models
|
import netbox_dns.models
|
||||||
from netbox.views.generic import ObjectListView
|
from netbox.views.generic import ObjectEditView, ObjectListView
|
||||||
|
|
||||||
from .filtersets import ZoneFilterSet
|
from .filtersets import CatalogZoneFilterSet, ZoneFilterSet
|
||||||
from .forms import CatalogZoneFilterForm
|
from .forms import (
|
||||||
|
CatalogZoneFilterForm,
|
||||||
|
CatalogZoneForm,
|
||||||
|
CatalogZoneMemberFilterForm,
|
||||||
|
)
|
||||||
from .models import CatalogZone, SeenTransferClients
|
from .models import CatalogZone, SeenTransferClients
|
||||||
from .tables import CatalogZoneTable
|
from .tables import CatalogZoneMemberTable, CatalogZoneTable
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(
|
@method_decorator(
|
||||||
user_passes_test(lambda u: u.is_superuser), name="dispatch"
|
user_passes_test(lambda u: u.is_superuser), name="dispatch"
|
||||||
)
|
)
|
||||||
class CatzOverviewView(ObjectListView):
|
class CatalogZoneListView(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(
|
queryset = netbox_dns.models.Zone.objects.select_related(
|
||||||
"view", "catz_identifier"
|
"view", "catz_identifier"
|
||||||
).order_by("view__name", "name")
|
).order_by("view__name", "name")
|
||||||
table = CatalogZoneTable
|
table = CatalogZoneMemberTable
|
||||||
filterset = ZoneFilterSet
|
filterset = ZoneFilterSet
|
||||||
filterset_form = CatalogZoneFilterForm
|
filterset_form = CatalogZoneMemberFilterForm
|
||||||
template_name = "netbox_dns_bridge/catz_overview.html"
|
template_name = "netbox_dns_bridge/catalog_zone_members.html"
|
||||||
actions = ()
|
actions = ()
|
||||||
|
|
||||||
def get_extra_context(self, request):
|
|
||||||
context = super().get_extra_context(request)
|
|
||||||
context["catalog_zones"] = CatalogZone.objects.select_related(
|
|
||||||
"view"
|
|
||||||
).order_by("view__name")
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(
|
@method_decorator(
|
||||||
user_passes_test(lambda u: u.is_superuser), name="dispatch"
|
user_passes_test(lambda u: u.is_superuser), name="dispatch"
|
||||||
|
|||||||
Reference in new issue
Block a user