netbox-plugin-dns 0.21.4__py3-none-any.whl → 1.4.7__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_dns/__init__.py +106 -41
- netbox_dns/api/field_serializers.py +25 -0
- netbox_dns/api/nested_serializers.py +95 -52
- netbox_dns/api/serializers.py +14 -296
- netbox_dns/api/serializers_/__init__.py +0 -0
- netbox_dns/api/serializers_/dnssec_key_template.py +69 -0
- netbox_dns/api/serializers_/dnssec_policy.py +165 -0
- netbox_dns/api/serializers_/nameserver.py +56 -0
- netbox_dns/api/serializers_/prefix.py +18 -0
- netbox_dns/api/serializers_/record.py +105 -0
- netbox_dns/api/serializers_/record_template.py +71 -0
- netbox_dns/api/serializers_/registrar.py +45 -0
- netbox_dns/api/serializers_/registration_contact.py +50 -0
- netbox_dns/api/serializers_/view.py +81 -0
- netbox_dns/api/serializers_/zone.py +247 -0
- netbox_dns/api/serializers_/zone_template.py +157 -0
- netbox_dns/api/urls.py +13 -2
- netbox_dns/api/views.py +96 -58
- netbox_dns/choices/__init__.py +4 -0
- netbox_dns/choices/dnssec_key_template.py +67 -0
- netbox_dns/choices/dnssec_policy.py +40 -0
- netbox_dns/choices/record.py +104 -0
- netbox_dns/choices/utilities.py +4 -0
- netbox_dns/choices/zone.py +119 -0
- netbox_dns/fields/__init__.py +4 -0
- netbox_dns/fields/address.py +22 -16
- netbox_dns/fields/choice_array.py +33 -0
- netbox_dns/fields/ipam.py +15 -0
- netbox_dns/fields/network.py +42 -18
- netbox_dns/fields/rfc2317.py +97 -0
- netbox_dns/fields/timeperiod.py +33 -0
- netbox_dns/filters.py +7 -0
- netbox_dns/filtersets/__init__.py +12 -0
- netbox_dns/filtersets/dnssec_key_template.py +57 -0
- netbox_dns/filtersets/dnssec_policy.py +101 -0
- netbox_dns/filtersets/nameserver.py +46 -0
- netbox_dns/filtersets/record.py +135 -0
- netbox_dns/filtersets/record_template.py +59 -0
- netbox_dns/{filters → filtersets}/registrar.py +8 -1
- netbox_dns/{filters/contact.py → filtersets/registration_contact.py} +9 -3
- netbox_dns/filtersets/view.py +45 -0
- netbox_dns/filtersets/zone.py +254 -0
- netbox_dns/filtersets/zone_template.py +165 -0
- netbox_dns/forms/__init__.py +5 -1
- netbox_dns/forms/dnssec_key_template.py +250 -0
- netbox_dns/forms/dnssec_policy.py +654 -0
- netbox_dns/forms/nameserver.py +121 -27
- netbox_dns/forms/record.py +215 -104
- netbox_dns/forms/record_template.py +285 -0
- netbox_dns/forms/registrar.py +108 -31
- netbox_dns/forms/registration_contact.py +282 -0
- netbox_dns/forms/view.py +331 -20
- netbox_dns/forms/zone.py +769 -373
- netbox_dns/forms/zone_template.py +463 -0
- netbox_dns/graphql/__init__.py +25 -22
- netbox_dns/graphql/enums.py +41 -0
- netbox_dns/graphql/filter_lookups.py +13 -0
- netbox_dns/graphql/filters/__init__.py +12 -0
- netbox_dns/graphql/filters/dnssec_key_template.py +63 -0
- netbox_dns/graphql/filters/dnssec_policy.py +124 -0
- netbox_dns/graphql/filters/nameserver.py +32 -0
- netbox_dns/graphql/filters/record.py +89 -0
- netbox_dns/graphql/filters/record_template.py +55 -0
- netbox_dns/graphql/filters/registrar.py +30 -0
- netbox_dns/graphql/filters/registration_contact.py +27 -0
- netbox_dns/graphql/filters/view.py +28 -0
- netbox_dns/graphql/filters/zone.py +147 -0
- netbox_dns/graphql/filters/zone_template.py +97 -0
- netbox_dns/graphql/schema.py +89 -7
- netbox_dns/graphql/types.py +355 -0
- netbox_dns/locale/de/LC_MESSAGES/django.mo +0 -0
- netbox_dns/locale/en/LC_MESSAGES/django.mo +0 -0
- netbox_dns/locale/fr/LC_MESSAGES/django.mo +0 -0
- netbox_dns/management/commands/cleanup_database.py +175 -156
- netbox_dns/management/commands/cleanup_rrset_ttl.py +64 -0
- netbox_dns/management/commands/rebuild_dnssync.py +23 -0
- netbox_dns/management/commands/setup_dnssync.py +140 -0
- netbox_dns/migrations/0001_squashed_netbox_dns_0_15.py +0 -27
- netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py +557 -0
- netbox_dns/migrations/{0013_add_nameserver_zone_record_description.py → 0002_contact_description_registrar_description.py} +4 -9
- netbox_dns/migrations/0003_default_view.py +15 -0
- netbox_dns/migrations/0004_create_and_assign_default_view.py +26 -0
- netbox_dns/migrations/0005_alter_zone_view_not_null.py +18 -0
- netbox_dns/migrations/0006_templating.py +172 -0
- netbox_dns/migrations/0007_alter_ordering_options.py +25 -0
- netbox_dns/migrations/0008_view_prefixes.py +18 -0
- netbox_dns/migrations/0009_rename_contact_registrationcontact.py +36 -0
- netbox_dns/migrations/0010_view_ip_address_filter.py +18 -0
- netbox_dns/migrations/0011_rename_related_fields.py +63 -0
- netbox_dns/migrations/0012_natural_ordering.py +88 -0
- netbox_dns/migrations/0013_zonetemplate_soa_mname_zonetemplate_soa_rname.py +30 -0
- netbox_dns/migrations/0014_alter_unique_constraints_lowercase.py +42 -0
- netbox_dns/migrations/0015_dnssec.py +168 -0
- netbox_dns/migrations/{0015_add_record_status.py → 0016_dnssec_policy_status.py} +5 -4
- netbox_dns/migrations/0017_dnssec_policy_zone_zone_template.py +41 -0
- netbox_dns/migrations/0018_zone_domain_status_zone_expiration_date.py +23 -0
- netbox_dns/migrations/0019_dnssecpolicy_parental_agents.py +25 -0
- netbox_dns/migrations/0020_netbox_3_4.py +1 -1
- netbox_dns/migrations/0020_remove_dnssecpolicy_parental_agents_and_more.py +29 -0
- netbox_dns/migrations/0021_alter_record_ptr_record.py +25 -0
- netbox_dns/migrations/0021_record_ip_address.py +1 -1
- netbox_dns/migrations/0022_alter_record_ipam_ip_address.py +26 -0
- netbox_dns/migrations/0023_disable_ptr_false.py +27 -0
- netbox_dns/migrations/0024_zonetemplate_parental_agents.py +25 -0
- netbox_dns/migrations/0025_remove_zone_inline_signing_and_more.py +22 -0
- netbox_dns/migrations/0026_alter_dnssecpolicy_nsec3_opt_out.py +18 -0
- netbox_dns/migrations/0026_domain_registration.py +1 -1
- netbox_dns/migrations/0027_zone_comments.py +18 -0
- netbox_dns/migrations/0028_alter_zone_default_ttl_alter_zone_soa_minimum_and_more.py +54 -0
- netbox_dns/migrations/0028_rfc2317_fields.py +44 -0
- netbox_dns/migrations/0029_alter_registrationcontact_street.py +18 -0
- netbox_dns/migrations/0029_record_fqdn.py +30 -0
- netbox_dns/mixins/__init__.py +1 -0
- netbox_dns/mixins/object_modification.py +57 -0
- netbox_dns/models/__init__.py +5 -1
- netbox_dns/models/dnssec_key_template.py +114 -0
- netbox_dns/models/dnssec_policy.py +203 -0
- netbox_dns/models/nameserver.py +61 -30
- netbox_dns/models/record.py +781 -234
- netbox_dns/models/record_template.py +198 -0
- netbox_dns/models/registrar.py +34 -15
- netbox_dns/models/{contact.py → registration_contact.py} +72 -43
- netbox_dns/models/view.py +129 -9
- netbox_dns/models/zone.py +806 -242
- netbox_dns/models/zone_template.py +209 -0
- netbox_dns/navigation.py +176 -76
- netbox_dns/signals/__init__.py +0 -0
- netbox_dns/signals/dnssec.py +32 -0
- netbox_dns/signals/ipam_dnssync.py +216 -0
- netbox_dns/tables/__init__.py +5 -1
- netbox_dns/tables/dnssec_key_template.py +49 -0
- netbox_dns/tables/dnssec_policy.py +140 -0
- netbox_dns/tables/ipam_dnssync.py +12 -0
- netbox_dns/tables/nameserver.py +14 -17
- netbox_dns/tables/record.py +117 -59
- netbox_dns/tables/record_template.py +91 -0
- netbox_dns/tables/registrar.py +20 -10
- netbox_dns/tables/{contact.py → registration_contact.py} +22 -11
- netbox_dns/tables/view.py +47 -3
- netbox_dns/tables/zone.py +62 -31
- netbox_dns/tables/zone_template.py +78 -0
- netbox_dns/template_content.py +124 -38
- netbox_dns/templates/netbox_dns/dnsseckeytemplate.html +70 -0
- netbox_dns/templates/netbox_dns/dnssecpolicy.html +163 -0
- netbox_dns/templates/netbox_dns/nameserver.html +31 -28
- netbox_dns/templates/netbox_dns/record/managed.html +2 -1
- netbox_dns/templates/netbox_dns/record/related.html +17 -6
- netbox_dns/templates/netbox_dns/record.html +140 -93
- netbox_dns/templates/netbox_dns/recordtemplate.html +96 -0
- netbox_dns/templates/netbox_dns/registrar.html +41 -34
- netbox_dns/templates/netbox_dns/registrationcontact.html +76 -0
- netbox_dns/templates/netbox_dns/view/button.html +10 -0
- netbox_dns/templates/netbox_dns/view/prefix.html +44 -0
- netbox_dns/templates/netbox_dns/view/related.html +33 -0
- netbox_dns/templates/netbox_dns/view.html +62 -18
- netbox_dns/templates/netbox_dns/zone/base.html +6 -3
- netbox_dns/templates/netbox_dns/zone/child.html +6 -5
- netbox_dns/templates/netbox_dns/zone/child_zone.html +18 -0
- netbox_dns/templates/netbox_dns/zone/delegation_record.html +18 -0
- netbox_dns/templates/netbox_dns/zone/managed_record.html +1 -1
- netbox_dns/templates/netbox_dns/zone/record.html +6 -5
- netbox_dns/templates/netbox_dns/zone/registration.html +43 -24
- netbox_dns/templates/netbox_dns/zone/rfc2317_child_zone.html +18 -0
- netbox_dns/templates/netbox_dns/zone.html +178 -119
- netbox_dns/templates/netbox_dns/zonetemplate/child.html +46 -0
- netbox_dns/templates/netbox_dns/zonetemplate.html +124 -0
- netbox_dns/templatetags/netbox_dns.py +10 -0
- netbox_dns/urls.py +50 -210
- netbox_dns/utilities/__init__.py +3 -0
- netbox_dns/{utilities.py → utilities/conversions.py} +55 -7
- netbox_dns/utilities/dns.py +11 -0
- netbox_dns/utilities/ipam_dnssync.py +370 -0
- netbox_dns/validators/__init__.py +4 -0
- netbox_dns/validators/dns_name.py +116 -0
- netbox_dns/validators/dns_value.py +147 -0
- netbox_dns/validators/dnssec.py +148 -0
- netbox_dns/validators/rfc2317.py +28 -0
- netbox_dns/views/__init__.py +5 -1
- netbox_dns/views/dnssec_key_template.py +78 -0
- netbox_dns/views/dnssec_policy.py +146 -0
- netbox_dns/views/nameserver.py +34 -15
- netbox_dns/views/record.py +156 -15
- netbox_dns/views/record_template.py +93 -0
- netbox_dns/views/registrar.py +32 -13
- netbox_dns/views/registration_contact.py +101 -0
- netbox_dns/views/view.py +58 -14
- netbox_dns/views/zone.py +130 -33
- netbox_dns/views/zone_template.py +82 -0
- netbox_plugin_dns-1.4.7.dist-info/METADATA +132 -0
- netbox_plugin_dns-1.4.7.dist-info/RECORD +201 -0
- {netbox_plugin_dns-0.21.4.dist-info → netbox_plugin_dns-1.4.7.dist-info}/WHEEL +2 -1
- {netbox_plugin_dns-0.21.4.dist-info → netbox_plugin_dns-1.4.7.dist-info/licenses}/LICENSE +2 -1
- netbox_plugin_dns-1.4.7.dist-info/top_level.txt +1 -0
- netbox_dns/filters/__init__.py +0 -6
- netbox_dns/filters/nameserver.py +0 -18
- netbox_dns/filters/record.py +0 -53
- netbox_dns/filters/view.py +0 -18
- netbox_dns/filters/zone.py +0 -112
- netbox_dns/forms/contact.py +0 -211
- netbox_dns/graphql/contact.py +0 -19
- netbox_dns/graphql/nameserver.py +0 -19
- netbox_dns/graphql/record.py +0 -19
- netbox_dns/graphql/registrar.py +0 -19
- netbox_dns/graphql/view.py +0 -19
- netbox_dns/graphql/zone.py +0 -19
- netbox_dns/management/commands/setup_coupling.py +0 -75
- netbox_dns/management/commands/update_soa.py +0 -22
- netbox_dns/middleware.py +0 -226
- netbox_dns/migrations/0001_initial.py +0 -115
- netbox_dns/migrations/0002_zone_default_ttl.py +0 -18
- netbox_dns/migrations/0003_soa_managed_records.py +0 -112
- netbox_dns/migrations/0004_create_ptr_for_a_aaaa_records.py +0 -80
- netbox_dns/migrations/0005_update_ns_records.py +0 -41
- netbox_dns/migrations/0006_zone_soa_serial_auto.py +0 -29
- netbox_dns/migrations/0007_alter_zone_soa_serial_auto.py +0 -17
- netbox_dns/migrations/0008_zone_status_names.py +0 -21
- netbox_dns/migrations/0009_netbox32.py +0 -71
- netbox_dns/migrations/0010_update_soa_records.py +0 -58
- netbox_dns/migrations/0011_add_view_model.py +0 -70
- netbox_dns/migrations/0012_adjust_zone_and_record.py +0 -17
- netbox_dns/migrations/0014_add_view_description.py +0 -16
- netbox_dns/migrations/0016_cleanup_ptr_records.py +0 -38
- netbox_dns/migrations/0017_alter_record_ttl.py +0 -17
- netbox_dns/migrations/0018_zone_arpa_network.py +0 -51
- netbox_dns/migrations/0019_update_ns_ttl.py +0 -19
- netbox_dns/templates/netbox_dns/contact.html +0 -71
- netbox_dns/templates/netbox_dns/related_dns_objects.html +0 -21
- netbox_dns/templatetags/view_helpers.py +0 -15
- netbox_dns/validators.py +0 -57
- netbox_dns/views/contact.py +0 -83
- netbox_plugin_dns-0.21.4.dist-info/METADATA +0 -101
- netbox_plugin_dns-0.21.4.dist-info/RECORD +0 -110
netbox_dns/models/record.py
CHANGED
|
@@ -1,45 +1,95 @@
|
|
|
1
1
|
import ipaddress
|
|
2
|
+
import netaddr
|
|
2
3
|
|
|
3
4
|
import dns
|
|
4
|
-
from dns import rdata, rdatatype, rdataclass
|
|
5
5
|
from dns import name as dns_name
|
|
6
|
+
from dns import rdata
|
|
6
7
|
|
|
7
8
|
from django.core.exceptions import ValidationError
|
|
8
|
-
from django.db import
|
|
9
|
-
from django.db.models import Q, ExpressionWrapper, BooleanField
|
|
10
|
-
from django.
|
|
11
|
-
from django.
|
|
9
|
+
from django.db import models
|
|
10
|
+
from django.db.models import Q, ExpressionWrapper, BooleanField, Min
|
|
11
|
+
from django.conf import settings
|
|
12
|
+
from django.utils.translation import gettext_lazy as _
|
|
13
|
+
from django.core.validators import MaxValueValidator
|
|
12
14
|
|
|
13
15
|
from netbox.models import NetBoxModel
|
|
16
|
+
from netbox.models.features import ContactsMixin
|
|
14
17
|
from netbox.search import SearchIndex, register_search
|
|
18
|
+
from netbox.plugins.utils import get_plugin_config
|
|
15
19
|
from utilities.querysets import RestrictedQuerySet
|
|
16
|
-
from utilities.choices import ChoiceSet
|
|
17
|
-
|
|
18
|
-
try:
|
|
19
|
-
# NetBox 3.5.0 - 3.5.7, 3.5.9+
|
|
20
|
-
from extras.plugins import get_plugin_config
|
|
21
|
-
except ImportError:
|
|
22
|
-
# NetBox 3.5.8
|
|
23
|
-
from extras.plugins.utils import get_plugin_config
|
|
24
20
|
|
|
25
21
|
from netbox_dns.fields import AddressField
|
|
26
|
-
from netbox_dns.utilities import
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
from netbox_dns.utilities import arpa_to_prefix, name_to_unicode, check_filter
|
|
23
|
+
from netbox_dns.validators import validate_generic_name, validate_record_value
|
|
24
|
+
from netbox_dns.mixins import ObjectModificationMixin
|
|
25
|
+
from netbox_dns.choices import (
|
|
26
|
+
RecordTypeChoices,
|
|
27
|
+
RecordStatusChoices,
|
|
28
|
+
RecordClassChoices,
|
|
29
29
|
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
|
|
31
|
+
__all__ = (
|
|
32
|
+
"Record",
|
|
33
|
+
"RecordIndex",
|
|
33
34
|
)
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
ZONE_ACTIVE_STATUS_LIST = get_plugin_config("netbox_dns", "zone_active_status")
|
|
37
|
+
RECORD_ACTIVE_STATUS_LIST = get_plugin_config("netbox_dns", "record_active_status")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def min_ttl(*ttl_list):
|
|
41
|
+
return min((ttl for ttl in ttl_list if ttl is not None), default=None)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def record_data_from_ip_address(ip_address, zone):
|
|
45
|
+
cf_data = ip_address.custom_field_data
|
|
46
|
+
|
|
47
|
+
if cf_data.get("ipaddress_dns_disabled"):
|
|
48
|
+
# +
|
|
49
|
+
# DNS record creation disabled for this address
|
|
50
|
+
# -
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
if zone.view.ip_address_filter is not None and not check_filter(
|
|
54
|
+
ip_address, zone.view.ip_address_filter
|
|
55
|
+
):
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
data = {
|
|
59
|
+
"name": (
|
|
60
|
+
dns_name.from_text(ip_address.dns_name)
|
|
61
|
+
.relativize(dns_name.from_text(zone.name))
|
|
62
|
+
.to_text()
|
|
63
|
+
),
|
|
64
|
+
"type": (
|
|
65
|
+
RecordTypeChoices.A
|
|
66
|
+
if ip_address.address.version == 4
|
|
67
|
+
else RecordTypeChoices.AAAA
|
|
68
|
+
),
|
|
69
|
+
"value": str(ip_address.address.ip),
|
|
70
|
+
"status": (
|
|
71
|
+
RecordStatusChoices.STATUS_ACTIVE
|
|
72
|
+
if ip_address.status
|
|
73
|
+
in settings.PLUGINS_CONFIG["netbox_dns"].get(
|
|
74
|
+
"dnssync_ipaddress_active_status", []
|
|
75
|
+
)
|
|
76
|
+
else RecordStatusChoices.STATUS_INACTIVE
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if "ipaddress_dns_record_ttl" in cf_data:
|
|
81
|
+
data["ttl"] = cf_data.get("ipaddress_dns_record_ttl")
|
|
82
|
+
|
|
83
|
+
if (disable_ptr := cf_data.get("ipaddress_dns_record_disable_ptr")) is not None:
|
|
84
|
+
data["disable_ptr"] = disable_ptr
|
|
85
|
+
|
|
86
|
+
return data
|
|
39
87
|
|
|
40
88
|
|
|
41
89
|
class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
42
|
-
"""
|
|
90
|
+
"""
|
|
91
|
+
Custom manager for records providing the activity status annotation
|
|
92
|
+
"""
|
|
43
93
|
|
|
44
94
|
def get_queryset(self):
|
|
45
95
|
return (
|
|
@@ -48,14 +98,8 @@ class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
|
48
98
|
.annotate(
|
|
49
99
|
active=ExpressionWrapper(
|
|
50
100
|
Q(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Q(address_record__isnull=True)
|
|
54
|
-
| Q(
|
|
55
|
-
address_record__zone__status__in=zone.Zone.ACTIVE_STATUS_LIST
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
|
-
& Q(status__in=Record.ACTIVE_STATUS_LIST)
|
|
101
|
+
zone__status__in=ZONE_ACTIVE_STATUS_LIST,
|
|
102
|
+
status__in=RECORD_ACTIVE_STATUS_LIST,
|
|
59
103
|
),
|
|
60
104
|
output_field=BooleanField(),
|
|
61
105
|
)
|
|
@@ -63,100 +107,124 @@ class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
|
63
107
|
)
|
|
64
108
|
|
|
65
109
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
SINGLETONS = [
|
|
80
|
-
rdtype.name for rdtype in rdatatype.RdataType if rdatatype.is_singleton(rdtype)
|
|
81
|
-
]
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@initialize_choice_names
|
|
85
|
-
class RecordClassChoices(ChoiceSet):
|
|
86
|
-
CHOICES = [
|
|
87
|
-
(rdclass.name, rdclass.name)
|
|
88
|
-
for rdclass in sorted(rdataclass.RdataClass)
|
|
89
|
-
if not rdataclass.is_metaclass(rdclass)
|
|
90
|
-
]
|
|
110
|
+
class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
111
|
+
class Meta:
|
|
112
|
+
verbose_name = _("Record")
|
|
113
|
+
verbose_name_plural = _("Records")
|
|
114
|
+
|
|
115
|
+
ordering = (
|
|
116
|
+
"fqdn",
|
|
117
|
+
"zone",
|
|
118
|
+
"name",
|
|
119
|
+
"type",
|
|
120
|
+
"value",
|
|
121
|
+
"status",
|
|
122
|
+
)
|
|
91
123
|
|
|
124
|
+
objects = RecordManager()
|
|
125
|
+
raw_objects = RestrictedQuerySet.as_manager()
|
|
92
126
|
|
|
93
|
-
|
|
94
|
-
|
|
127
|
+
clone_fields = (
|
|
128
|
+
"zone",
|
|
129
|
+
"type",
|
|
130
|
+
"name",
|
|
131
|
+
"value",
|
|
132
|
+
"status",
|
|
133
|
+
"ttl",
|
|
134
|
+
"disable_ptr",
|
|
135
|
+
"description",
|
|
136
|
+
"tenant",
|
|
137
|
+
)
|
|
95
138
|
|
|
96
|
-
|
|
97
|
-
|
|
139
|
+
def __init__(self, *args, **kwargs):
|
|
140
|
+
super().__init__(*args, **kwargs)
|
|
98
141
|
|
|
99
|
-
|
|
100
|
-
(STATUS_ACTIVE, "Active", "blue"),
|
|
101
|
-
(STATUS_INACTIVE, "Inactive", "red"),
|
|
102
|
-
]
|
|
142
|
+
self._cleanup_ptr_record = None
|
|
103
143
|
|
|
144
|
+
def __str__(self):
|
|
145
|
+
try:
|
|
146
|
+
fqdn = dns_name.from_text(
|
|
147
|
+
self.name, origin=dns_name.from_text(self.zone.name)
|
|
148
|
+
).relativize(dns_name.root)
|
|
149
|
+
name = fqdn.to_unicode()
|
|
150
|
+
except dns_name.IDNAException:
|
|
151
|
+
name = fqdn.to_text()
|
|
152
|
+
except dns_name.LabelTooLong:
|
|
153
|
+
name = f"{self.name[:59]}..."
|
|
104
154
|
|
|
105
|
-
|
|
106
|
-
ACTIVE_STATUS_LIST = (RecordStatusChoices.STATUS_ACTIVE,)
|
|
155
|
+
return f"{name} [{self.type}]"
|
|
107
156
|
|
|
108
157
|
unique_ptr_qs = Q(
|
|
109
158
|
Q(disable_ptr=False),
|
|
110
159
|
Q(Q(type=RecordTypeChoices.A) | Q(type=RecordTypeChoices.AAAA)),
|
|
111
160
|
)
|
|
112
161
|
|
|
162
|
+
name = models.CharField(
|
|
163
|
+
verbose_name=_("Name"),
|
|
164
|
+
max_length=255,
|
|
165
|
+
db_collation="natural_sort",
|
|
166
|
+
)
|
|
113
167
|
zone = models.ForeignKey(
|
|
114
|
-
"Zone",
|
|
168
|
+
verbose_name=_("Zone"),
|
|
169
|
+
to="Zone",
|
|
115
170
|
on_delete=models.CASCADE,
|
|
171
|
+
related_name="records",
|
|
172
|
+
)
|
|
173
|
+
fqdn = models.CharField(
|
|
174
|
+
verbose_name=_("FQDN"),
|
|
175
|
+
max_length=255,
|
|
176
|
+
null=True,
|
|
177
|
+
blank=True,
|
|
178
|
+
default=None,
|
|
179
|
+
db_collation="natural_sort",
|
|
116
180
|
)
|
|
117
181
|
type = models.CharField(
|
|
182
|
+
verbose_name=_("Type"),
|
|
118
183
|
choices=RecordTypeChoices,
|
|
119
184
|
max_length=10,
|
|
120
185
|
)
|
|
121
|
-
name = models.CharField(
|
|
122
|
-
max_length=255,
|
|
123
|
-
)
|
|
124
186
|
value = models.CharField(
|
|
187
|
+
verbose_name=_("Value"),
|
|
125
188
|
max_length=65535,
|
|
126
189
|
)
|
|
127
190
|
status = models.CharField(
|
|
191
|
+
verbose_name=_("Status"),
|
|
128
192
|
max_length=50,
|
|
129
193
|
choices=RecordStatusChoices,
|
|
130
194
|
default=RecordStatusChoices.STATUS_ACTIVE,
|
|
131
195
|
blank=False,
|
|
132
196
|
)
|
|
133
197
|
ttl = models.PositiveIntegerField(
|
|
134
|
-
verbose_name="TTL",
|
|
198
|
+
verbose_name=_("TTL"),
|
|
135
199
|
null=True,
|
|
136
200
|
blank=True,
|
|
201
|
+
validators=[MaxValueValidator(2147483647)],
|
|
137
202
|
)
|
|
138
203
|
managed = models.BooleanField(
|
|
204
|
+
verbose_name=_("Managed"),
|
|
139
205
|
null=False,
|
|
140
206
|
default=False,
|
|
141
207
|
)
|
|
142
|
-
ptr_record = models.
|
|
143
|
-
"
|
|
208
|
+
ptr_record = models.ForeignKey(
|
|
209
|
+
verbose_name=_("PTR Record"),
|
|
210
|
+
to="self",
|
|
144
211
|
on_delete=models.SET_NULL,
|
|
145
|
-
related_name="
|
|
146
|
-
verbose_name="PTR record",
|
|
212
|
+
related_name="address_records",
|
|
147
213
|
null=True,
|
|
148
214
|
blank=True,
|
|
149
215
|
)
|
|
150
216
|
disable_ptr = models.BooleanField(
|
|
151
|
-
verbose_name="Disable PTR",
|
|
152
|
-
help_text="Disable PTR record creation",
|
|
217
|
+
verbose_name=_("Disable PTR"),
|
|
218
|
+
help_text=_("Disable PTR record creation"),
|
|
153
219
|
default=False,
|
|
154
220
|
)
|
|
155
221
|
description = models.CharField(
|
|
222
|
+
verbose_name=_("Description"),
|
|
156
223
|
max_length=200,
|
|
157
224
|
blank=True,
|
|
158
225
|
)
|
|
159
226
|
tenant = models.ForeignKey(
|
|
227
|
+
verbose_name=_("Tenant"),
|
|
160
228
|
to="tenancy.Tenant",
|
|
161
229
|
on_delete=models.PROTECT,
|
|
162
230
|
related_name="netbox_dns_records",
|
|
@@ -164,51 +232,35 @@ class Record(NetBoxModel):
|
|
|
164
232
|
null=True,
|
|
165
233
|
)
|
|
166
234
|
ip_address = AddressField(
|
|
167
|
-
verbose_name="Related IP Address",
|
|
168
|
-
help_text="IP address related to an address (A/AAAA) or PTR record",
|
|
235
|
+
verbose_name=_("Related IP Address"),
|
|
236
|
+
help_text=_("IP address related to an address (A/AAAA) or PTR record"),
|
|
169
237
|
blank=True,
|
|
170
238
|
null=True,
|
|
171
239
|
)
|
|
172
240
|
ipam_ip_address = models.ForeignKey(
|
|
173
|
-
verbose_name="IPAM IP Address",
|
|
241
|
+
verbose_name=_("IPAM IP Address"),
|
|
174
242
|
to="ipam.IPAddress",
|
|
175
|
-
on_delete=models.
|
|
243
|
+
on_delete=models.SET_NULL,
|
|
176
244
|
related_name="netbox_dns_records",
|
|
177
245
|
blank=True,
|
|
178
246
|
null=True,
|
|
179
247
|
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
"
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"status",
|
|
189
|
-
"ttl",
|
|
190
|
-
"disable_ptr",
|
|
191
|
-
"description",
|
|
192
|
-
]
|
|
193
|
-
|
|
194
|
-
class Meta:
|
|
195
|
-
ordering = ("zone", "name", "type", "value", "status")
|
|
248
|
+
rfc2317_cname_record = models.ForeignKey(
|
|
249
|
+
verbose_name=_("RFC2317 CNAME Record"),
|
|
250
|
+
to="self",
|
|
251
|
+
on_delete=models.SET_NULL,
|
|
252
|
+
related_name="rfc2317_ptr_records",
|
|
253
|
+
null=True,
|
|
254
|
+
blank=True,
|
|
255
|
+
)
|
|
196
256
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
dns_name.from_text(
|
|
201
|
-
self.name, origin=dns_name.from_text(self.zone.name, origin=None)
|
|
202
|
-
)
|
|
203
|
-
.relativize(dns_name.root)
|
|
204
|
-
.to_unicode()
|
|
205
|
-
)
|
|
206
|
-
except dns_name.IDNAException:
|
|
207
|
-
name = self.name
|
|
208
|
-
except dns_name.LabelTooLong as exc:
|
|
209
|
-
name = f"{self.name[:59]}..."
|
|
257
|
+
@property
|
|
258
|
+
def cleanup_ptr_record(self):
|
|
259
|
+
return self._cleanup_ptr_record
|
|
210
260
|
|
|
211
|
-
|
|
261
|
+
@cleanup_ptr_record.setter
|
|
262
|
+
def cleanup_ptr_record(self, ptr_record):
|
|
263
|
+
self._cleanup_ptr_record = ptr_record
|
|
212
264
|
|
|
213
265
|
@property
|
|
214
266
|
def display_name(self):
|
|
@@ -217,15 +269,15 @@ class Record(NetBoxModel):
|
|
|
217
269
|
def get_status_color(self):
|
|
218
270
|
return RecordStatusChoices.colors.get(self.status)
|
|
219
271
|
|
|
220
|
-
def get_absolute_url(self):
|
|
221
|
-
return reverse("plugins:netbox_dns:record", kwargs={"pk": self.id})
|
|
222
|
-
|
|
223
272
|
@property
|
|
224
|
-
def
|
|
225
|
-
|
|
226
|
-
|
|
273
|
+
def value_fqdn(self):
|
|
274
|
+
if self.type not in (RecordTypeChoices.CNAME, RecordTypeChoices.NS):
|
|
275
|
+
return None
|
|
227
276
|
|
|
228
|
-
|
|
277
|
+
_zone = dns_name.from_text(self.zone.name)
|
|
278
|
+
value_fqdn = dns_name.from_text(self.value, origin=_zone)
|
|
279
|
+
|
|
280
|
+
return value_fqdn.to_text()
|
|
229
281
|
|
|
230
282
|
@property
|
|
231
283
|
def address_from_name(self):
|
|
@@ -235,11 +287,19 @@ class Record(NetBoxModel):
|
|
|
235
287
|
|
|
236
288
|
return None
|
|
237
289
|
|
|
290
|
+
@property
|
|
291
|
+
def address_from_rfc2317_name(self):
|
|
292
|
+
prefix = self.zone.rfc2317_prefix
|
|
293
|
+
if prefix is not None:
|
|
294
|
+
return ".".join(str(prefix.ip).split(".")[0:3] + [self.name])
|
|
295
|
+
|
|
296
|
+
return None
|
|
297
|
+
|
|
238
298
|
@property
|
|
239
299
|
def is_active(self):
|
|
240
300
|
return (
|
|
241
|
-
self.status in
|
|
242
|
-
and self.zone.status in
|
|
301
|
+
self.status in RECORD_ACTIVE_STATUS_LIST
|
|
302
|
+
and self.zone.status in ZONE_ACTIVE_STATUS_LIST
|
|
243
303
|
)
|
|
244
304
|
|
|
245
305
|
@property
|
|
@@ -251,105 +311,357 @@ class Record(NetBoxModel):
|
|
|
251
311
|
return self.type == RecordTypeChoices.PTR
|
|
252
312
|
|
|
253
313
|
@property
|
|
254
|
-
def
|
|
255
|
-
|
|
256
|
-
self.zone.view_filter, arpa_network__net_contains=self.value
|
|
257
|
-
).order_by(Length("name").desc())
|
|
314
|
+
def rfc2317_ptr_name(self):
|
|
315
|
+
return self.value.split(".")[-1]
|
|
258
316
|
|
|
259
|
-
|
|
260
|
-
|
|
317
|
+
@property
|
|
318
|
+
def rfc2317_ptr_cname_name(self):
|
|
319
|
+
assert self.type == RecordTypeChoices.A
|
|
320
|
+
if (
|
|
321
|
+
self.ptr_record is not None
|
|
322
|
+
and self.ptr_record.zone.rfc2317_parent_zone is not None
|
|
323
|
+
):
|
|
324
|
+
return dns_name.from_text(
|
|
325
|
+
ipaddress.ip_address(self.value).reverse_pointer
|
|
326
|
+
).relativize(
|
|
327
|
+
dns_name.from_text(self.ptr_record.zone.rfc2317_parent_zone.name)
|
|
328
|
+
)
|
|
261
329
|
|
|
262
330
|
return None
|
|
263
331
|
|
|
264
|
-
|
|
332
|
+
@property
|
|
333
|
+
def ptr_zone(self):
|
|
334
|
+
if self.type == RecordTypeChoices.A:
|
|
335
|
+
ptr_zone = (
|
|
336
|
+
self.zone.view.zones.filter(
|
|
337
|
+
rfc2317_prefix__net_contains=self.value,
|
|
338
|
+
)
|
|
339
|
+
.order_by("rfc2317_prefix__net_mask_length")
|
|
340
|
+
.last()
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if ptr_zone is not None:
|
|
344
|
+
return ptr_zone
|
|
345
|
+
|
|
346
|
+
ptr_zone = (
|
|
347
|
+
self.zone.view.zones.filter(arpa_network__net_contains=self.value)
|
|
348
|
+
.order_by("arpa_network__net_mask_length")
|
|
349
|
+
.last()
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return ptr_zone
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def is_delegation_record(self):
|
|
356
|
+
return self in self.zone.delegation_records
|
|
357
|
+
|
|
358
|
+
def refresh_ptr_record(
|
|
359
|
+
self, ptr_record=None, update_rfc2317_cname=True, save_zone_serial=True
|
|
360
|
+
):
|
|
361
|
+
if ptr_record is None:
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
if not ptr_record.address_records.exists():
|
|
365
|
+
if ptr_record.rfc2317_cname_record is not None:
|
|
366
|
+
ptr_record.remove_from_rfc2317_cname_record()
|
|
367
|
+
|
|
368
|
+
ptr_record.delete(save_zone_serial=save_zone_serial)
|
|
369
|
+
|
|
370
|
+
elif update_rfc2317_cname:
|
|
371
|
+
ptr_record.update_rfc2317_cname_record(save_zone_serial=save_zone_serial)
|
|
372
|
+
|
|
373
|
+
def update_ptr_record(self, update_rfc2317_cname=True, save_zone_serial=True):
|
|
265
374
|
ptr_zone = self.ptr_zone
|
|
266
375
|
|
|
376
|
+
# +
|
|
377
|
+
# Check whether a PTR record is optioned for and return if that is not the
|
|
378
|
+
# case.
|
|
379
|
+
# -
|
|
267
380
|
if (
|
|
268
381
|
ptr_zone is None
|
|
269
382
|
or self.disable_ptr
|
|
270
383
|
or not self.is_active
|
|
271
|
-
or self.name
|
|
384
|
+
or self.name.startswith("*")
|
|
272
385
|
):
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
self.ptr_record.delete()
|
|
276
|
-
self.ptr_record = None
|
|
386
|
+
self.cleanup_ptr_record = self.ptr_record
|
|
387
|
+
self.ptr_record = None
|
|
277
388
|
return
|
|
278
389
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
390
|
+
# +
|
|
391
|
+
# Determine the ptr_name and ptr_value related to the ptr_zone. RFC2317
|
|
392
|
+
# PTR names and zones need to be handled differently.
|
|
393
|
+
# -
|
|
394
|
+
if ptr_zone.is_rfc2317_zone:
|
|
395
|
+
ptr_name = self.rfc2317_ptr_name
|
|
396
|
+
else:
|
|
397
|
+
ptr_name = (
|
|
398
|
+
dns_name.from_text(ipaddress.ip_address(self.value).reverse_pointer)
|
|
399
|
+
.relativize(dns_name.from_text(ptr_zone.name))
|
|
400
|
+
.to_text()
|
|
401
|
+
)
|
|
282
402
|
ptr_value = self.fqdn
|
|
283
|
-
ptr_record = self.ptr_record
|
|
284
403
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
zone_id=ptr_zone.pk,
|
|
305
|
-
type=RecordTypeChoices.PTR,
|
|
404
|
+
# +
|
|
405
|
+
# If there is an existing and matching PTR record there is nothing to be done.
|
|
406
|
+
# -
|
|
407
|
+
if (ptr_record := self.ptr_record) is not None:
|
|
408
|
+
if (
|
|
409
|
+
ptr_record.zone == ptr_zone
|
|
410
|
+
and ptr_record.name == ptr_name
|
|
411
|
+
and ptr_record.value == ptr_value
|
|
412
|
+
and ptr_record.ttl == self.ttl
|
|
413
|
+
):
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
# +
|
|
417
|
+
# If the existing PTR record no longer matches the address record,
|
|
418
|
+
# check whether there is an existing PTR record that does. In that
|
|
419
|
+
# case, mark the old PTR record for cleanup and use the existing one.
|
|
420
|
+
# -
|
|
421
|
+
try:
|
|
422
|
+
existing_ptr_record = Record.objects.get(
|
|
306
423
|
name=ptr_name,
|
|
307
|
-
|
|
424
|
+
zone=ptr_zone,
|
|
425
|
+
type=RecordTypeChoices.PTR,
|
|
308
426
|
value=ptr_value,
|
|
309
|
-
managed=True,
|
|
310
427
|
)
|
|
311
428
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
429
|
+
self.cleanup_ptr_record = self.ptr_record
|
|
430
|
+
self.ptr_record = existing_ptr_record
|
|
431
|
+
ptr_record = self.ptr_record
|
|
432
|
+
|
|
433
|
+
except Record.DoesNotExist:
|
|
434
|
+
pass
|
|
435
|
+
|
|
436
|
+
# +
|
|
437
|
+
# If there is an RFC2317 CNAME for the PTR record and it is either
|
|
438
|
+
# not required or needs to be changed, remove it.
|
|
439
|
+
# -
|
|
440
|
+
if (
|
|
441
|
+
ptr_record.zone.pk != ptr_zone.pk or not ptr_record.zone.is_rfc2317_zone
|
|
442
|
+
) and ptr_record.rfc2317_cname_record is not None:
|
|
443
|
+
ptr_record.rfc2317_cname_record.delete(
|
|
444
|
+
save_zone_serial=save_zone_serial
|
|
445
|
+
)
|
|
446
|
+
ptr_record.rfc2317_cname_record = None
|
|
447
|
+
|
|
448
|
+
# +
|
|
449
|
+
# If the PTR record is used exclusively by the address record it can be
|
|
450
|
+
# modified to match the new name, zone, value and TTL.
|
|
451
|
+
# -
|
|
452
|
+
if ptr_record.address_records.count() == 1:
|
|
453
|
+
ptr_record.snapshot()
|
|
454
|
+
ptr_record.zone = ptr_zone
|
|
455
|
+
ptr_record.name = ptr_name
|
|
456
|
+
ptr_record.value = ptr_value
|
|
457
|
+
ptr_record.ttl = self.ttl
|
|
458
|
+
ptr_record.managed = True
|
|
459
|
+
ptr_record.save(
|
|
460
|
+
update_rfc2317_cname=update_rfc2317_cname,
|
|
461
|
+
save_zone_serial=save_zone_serial,
|
|
462
|
+
)
|
|
463
|
+
return
|
|
315
464
|
|
|
316
|
-
|
|
465
|
+
# +
|
|
466
|
+
# Either there was no PTR record or the existing PTR record could not be re-used,
|
|
467
|
+
# so we need to either find a matching PTR record or create a new one.
|
|
468
|
+
# -
|
|
317
469
|
try:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
470
|
+
ptr_record = Record.objects.get(
|
|
471
|
+
name=ptr_name,
|
|
472
|
+
zone=ptr_zone,
|
|
473
|
+
type=RecordTypeChoices.PTR,
|
|
474
|
+
value=ptr_value,
|
|
475
|
+
)
|
|
321
476
|
|
|
322
|
-
|
|
323
|
-
|
|
477
|
+
# +
|
|
478
|
+
# If no existing PTR record could be found in the database, create a new
|
|
479
|
+
# one from scratch.
|
|
480
|
+
# -
|
|
481
|
+
except Record.DoesNotExist:
|
|
482
|
+
ptr_record = Record(
|
|
483
|
+
zone_id=ptr_zone.pk,
|
|
484
|
+
type=RecordTypeChoices.PTR,
|
|
485
|
+
name=ptr_name,
|
|
486
|
+
ttl=self.ttl,
|
|
487
|
+
value=ptr_value,
|
|
488
|
+
managed=True,
|
|
489
|
+
)
|
|
490
|
+
ptr_record.save(
|
|
491
|
+
update_rfc2317_cname=update_rfc2317_cname,
|
|
492
|
+
save_zone_serial=save_zone_serial,
|
|
493
|
+
)
|
|
324
494
|
|
|
325
|
-
|
|
495
|
+
self.ptr_record = ptr_record
|
|
326
496
|
|
|
327
|
-
|
|
497
|
+
def remove_from_rfc2317_cname_record(self, save_zone_serial=True):
|
|
498
|
+
if self.rfc2317_cname_record.pk:
|
|
499
|
+
rfc2317_ptr_records = self.rfc2317_cname_record.rfc2317_ptr_records.exclude(
|
|
500
|
+
pk=self.pk
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
if rfc2317_ptr_records:
|
|
504
|
+
self.rfc2317_cname_record.ttl = rfc2317_ptr_records.aggregate(
|
|
505
|
+
Min("ttl")
|
|
506
|
+
).get("ttl__min")
|
|
507
|
+
self.rfc2317_cname_record.save(
|
|
508
|
+
update_fields=["ttl"], save_zone_serial=save_zone_serial
|
|
509
|
+
)
|
|
510
|
+
else:
|
|
511
|
+
self.rfc2317_cname_record.delete()
|
|
512
|
+
|
|
513
|
+
def update_rfc2317_cname_record(self, save_zone_serial=True):
|
|
514
|
+
if self.zone.rfc2317_parent_managed:
|
|
515
|
+
cname_name = (
|
|
516
|
+
dns_name.from_text(
|
|
517
|
+
ipaddress.ip_address(self.ip_address).reverse_pointer
|
|
518
|
+
)
|
|
519
|
+
.relativize(dns_name.from_text(self.zone.rfc2317_parent_zone.name))
|
|
520
|
+
.to_text()
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
if self.rfc2317_cname_record is not None:
|
|
524
|
+
if self.rfc2317_cname_record.name == cname_name:
|
|
525
|
+
self.rfc2317_cname_record.zone = self.zone.rfc2317_parent_zone
|
|
526
|
+
self.rfc2317_cname_record.value = self.fqdn
|
|
527
|
+
self.rfc2317_cname_record.ttl = min_ttl(
|
|
528
|
+
self.rfc2317_cname_record.rfc2317_ptr_records.exclude(
|
|
529
|
+
pk=self.pk
|
|
530
|
+
)
|
|
531
|
+
.aggregate(Min("ttl"))
|
|
532
|
+
.get("ttl__min"),
|
|
533
|
+
self.ttl,
|
|
534
|
+
)
|
|
535
|
+
self.rfc2317_cname_record.save(save_zone_serial=save_zone_serial)
|
|
536
|
+
|
|
537
|
+
return
|
|
538
|
+
|
|
539
|
+
self.remove_from_rfc2317_cname_record(save_zone_serial=save_zone_serial)
|
|
540
|
+
|
|
541
|
+
rfc2317_cname_record = self.zone.rfc2317_parent_zone.records.filter(
|
|
542
|
+
name=cname_name,
|
|
543
|
+
type=RecordTypeChoices.CNAME,
|
|
544
|
+
managed=True,
|
|
545
|
+
value=self.fqdn,
|
|
546
|
+
).first()
|
|
547
|
+
|
|
548
|
+
if rfc2317_cname_record is not None:
|
|
549
|
+
rfc2317_cname_record.ttl = min_ttl(
|
|
550
|
+
rfc2317_cname_record.rfc2317_ptr_records.exclude(pk=self.pk)
|
|
551
|
+
.aggregate(Min("ttl"))
|
|
552
|
+
.get("ttl__min"),
|
|
553
|
+
self.ttl,
|
|
554
|
+
)
|
|
555
|
+
rfc2317_cname_record.save(
|
|
556
|
+
update_fields=["ttl"], save_zone_serial=save_zone_serial
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
else:
|
|
560
|
+
rfc2317_cname_record = Record(
|
|
561
|
+
name=cname_name,
|
|
562
|
+
type=RecordTypeChoices.CNAME,
|
|
563
|
+
zone=self.zone.rfc2317_parent_zone,
|
|
564
|
+
managed=True,
|
|
565
|
+
value=self.fqdn,
|
|
566
|
+
ttl=self.ttl,
|
|
567
|
+
)
|
|
568
|
+
rfc2317_cname_record.save(save_zone_serial=save_zone_serial)
|
|
569
|
+
|
|
570
|
+
self.rfc2317_cname_record = rfc2317_cname_record
|
|
571
|
+
|
|
572
|
+
else:
|
|
573
|
+
if self.rfc2317_cname_record is not None:
|
|
574
|
+
self.rfc2317_cname_record.delete(save_zone_serial=save_zone_serial)
|
|
575
|
+
self.rfc2317_cname_record = None
|
|
576
|
+
|
|
577
|
+
def update_from_ip_address(self, ip_address, zone=None):
|
|
578
|
+
"""
|
|
579
|
+
Update an address record according to data from an IPAddress object.
|
|
580
|
+
|
|
581
|
+
Returns a tuple of two booleans: (update, delete).
|
|
582
|
+
|
|
583
|
+
update: The record was updated and needs to be cleaned and/or saved
|
|
584
|
+
delete: The record is no longer needed and needs to be deleted
|
|
585
|
+
"""
|
|
586
|
+
if zone is None:
|
|
587
|
+
zone = self.zone
|
|
588
|
+
|
|
589
|
+
data = record_data_from_ip_address(ip_address, zone)
|
|
590
|
+
|
|
591
|
+
if data is None:
|
|
592
|
+
return False, True
|
|
593
|
+
|
|
594
|
+
if all((getattr(self, attr) == data[attr] for attr in data.keys())):
|
|
595
|
+
return False, False
|
|
596
|
+
|
|
597
|
+
for attr, value in data.items():
|
|
598
|
+
setattr(self, attr, value)
|
|
599
|
+
|
|
600
|
+
return True, False
|
|
601
|
+
|
|
602
|
+
@classmethod
|
|
603
|
+
def create_from_ip_address(cls, ip_address, zone):
|
|
604
|
+
data = record_data_from_ip_address(ip_address, zone)
|
|
605
|
+
|
|
606
|
+
if data is None:
|
|
607
|
+
return
|
|
608
|
+
|
|
609
|
+
return Record(
|
|
610
|
+
zone=zone,
|
|
611
|
+
managed=True,
|
|
612
|
+
ipam_ip_address=ip_address,
|
|
613
|
+
**data,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
def update_fqdn(self, zone=None):
|
|
617
|
+
if zone is None:
|
|
618
|
+
zone = self.zone
|
|
619
|
+
|
|
620
|
+
_zone = dns_name.from_text(zone.name, origin=dns_name.root)
|
|
621
|
+
name = dns_name.from_text(self.name, origin=None)
|
|
622
|
+
fqdn = dns_name.from_text(self.name, origin=_zone)
|
|
623
|
+
|
|
624
|
+
if not fqdn.is_subdomain(_zone):
|
|
328
625
|
raise ValidationError(
|
|
329
626
|
{
|
|
330
|
-
"name":
|
|
627
|
+
"name": _("{name} is not a name in {zone}").format(
|
|
628
|
+
name=self.name, zone=zone.name
|
|
629
|
+
),
|
|
331
630
|
}
|
|
332
631
|
)
|
|
333
632
|
|
|
334
|
-
|
|
633
|
+
_zone.to_unicode()
|
|
634
|
+
name.to_unicode()
|
|
635
|
+
|
|
636
|
+
self.name = name.relativize(_zone).to_text()
|
|
637
|
+
self.fqdn = fqdn.to_text()
|
|
638
|
+
|
|
639
|
+
def validate_name(self, new_zone=None):
|
|
640
|
+
if new_zone is None:
|
|
641
|
+
new_zone = self.zone
|
|
642
|
+
|
|
643
|
+
try:
|
|
644
|
+
self.update_fqdn(zone=new_zone)
|
|
645
|
+
|
|
646
|
+
except dns.exception.DNSException as exc:
|
|
335
647
|
raise ValidationError(
|
|
336
648
|
{
|
|
337
|
-
"name":
|
|
649
|
+
"name": str(exc),
|
|
338
650
|
}
|
|
339
651
|
)
|
|
340
652
|
|
|
341
653
|
if self.type not in get_plugin_config(
|
|
342
|
-
"netbox_dns", "tolerate_non_rfc1035_types", default=
|
|
654
|
+
"netbox_dns", "tolerate_non_rfc1035_types", default=[]
|
|
343
655
|
):
|
|
344
656
|
try:
|
|
345
|
-
|
|
657
|
+
validate_generic_name(
|
|
346
658
|
self.name,
|
|
347
659
|
(
|
|
348
660
|
self.type
|
|
349
661
|
in get_plugin_config(
|
|
350
662
|
"netbox_dns",
|
|
351
663
|
"tolerate_leading_underscore_types",
|
|
352
|
-
default=
|
|
664
|
+
default=[],
|
|
353
665
|
)
|
|
354
666
|
),
|
|
355
667
|
)
|
|
@@ -358,74 +670,265 @@ class Record(NetBoxModel):
|
|
|
358
670
|
{
|
|
359
671
|
"name": exc,
|
|
360
672
|
}
|
|
361
|
-
)
|
|
673
|
+
)
|
|
362
674
|
|
|
363
675
|
def validate_value(self):
|
|
364
|
-
if self.type in (RecordTypeChoices.PTR):
|
|
365
|
-
try:
|
|
366
|
-
validate_fqdn(self.value)
|
|
367
|
-
except ValidationError as exc:
|
|
368
|
-
raise ValidationError(
|
|
369
|
-
{
|
|
370
|
-
"value": exc,
|
|
371
|
-
}
|
|
372
|
-
) from None
|
|
373
|
-
|
|
374
676
|
try:
|
|
375
|
-
|
|
376
|
-
except
|
|
377
|
-
raise ValidationError(
|
|
378
|
-
{
|
|
379
|
-
"value": f"Record value {self.value} is not a valid value for a {self.type} record: {exc}."
|
|
380
|
-
}
|
|
381
|
-
) from None
|
|
677
|
+
validate_record_value(self)
|
|
678
|
+
except ValidationError as exc:
|
|
679
|
+
raise ValidationError({"value": exc})
|
|
382
680
|
|
|
383
|
-
def
|
|
681
|
+
def check_unique_record(self, new_zone=None):
|
|
384
682
|
if not get_plugin_config("netbox_dns", "enforce_unique_records", False):
|
|
385
683
|
return
|
|
386
684
|
|
|
387
685
|
if not self.is_active:
|
|
388
686
|
return
|
|
389
687
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
688
|
+
if new_zone is None:
|
|
689
|
+
new_zone = self.zone
|
|
690
|
+
|
|
691
|
+
records = new_zone.records.filter(
|
|
692
|
+
name__iexact=self.name,
|
|
393
693
|
type=self.type,
|
|
394
694
|
value=self.value,
|
|
395
|
-
status__in=
|
|
695
|
+
status__in=RECORD_ACTIVE_STATUS_LIST,
|
|
396
696
|
)
|
|
397
|
-
|
|
697
|
+
|
|
698
|
+
if not self._state.adding:
|
|
699
|
+
records = records.exclude(pk=self.pk)
|
|
700
|
+
|
|
701
|
+
if records.exists():
|
|
702
|
+
if self.ipam_ip_address is not None:
|
|
703
|
+
if not records.filter(
|
|
704
|
+
ipam_ip_address__isnull=True
|
|
705
|
+
).exists() or get_plugin_config(
|
|
706
|
+
"netbox_dns", "dnssync_conflict_deactivate", False
|
|
707
|
+
):
|
|
708
|
+
return
|
|
709
|
+
|
|
398
710
|
raise ValidationError(
|
|
399
711
|
{
|
|
400
|
-
"value":
|
|
712
|
+
"value": _(
|
|
713
|
+
"There is already an active {type} record for name {name} in zone {zone} with value {value}."
|
|
714
|
+
).format(
|
|
715
|
+
type=self.type, name=self.name, zone=self.zone, value=self.value
|
|
716
|
+
)
|
|
401
717
|
}
|
|
402
|
-
)
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
@property
|
|
721
|
+
def absolute_value(self):
|
|
722
|
+
if self.type in RecordTypeChoices.CUSTOM_TYPES:
|
|
723
|
+
return self.value
|
|
724
|
+
|
|
725
|
+
zone = dns_name.from_text(self.zone.name)
|
|
726
|
+
rr = rdata.from_text(RecordClassChoices.IN, self.type, self.value)
|
|
727
|
+
|
|
728
|
+
match self.type:
|
|
729
|
+
case (
|
|
730
|
+
RecordTypeChoices.CNAME
|
|
731
|
+
| RecordTypeChoices.DNAME
|
|
732
|
+
| RecordTypeChoices.NS
|
|
733
|
+
| RecordTypeChoices.HTTPS
|
|
734
|
+
| RecordTypeChoices.SRV
|
|
735
|
+
| RecordTypeChoices.SVCB
|
|
736
|
+
):
|
|
737
|
+
return rr.replace(target=rr.target.derelativize(zone)).to_text()
|
|
738
|
+
|
|
739
|
+
case RecordTypeChoices.MX | RecordTypeChoices.RT | RecordTypeChoices.KX:
|
|
740
|
+
return rr.replace(exchange=rr.exchange.derelativize(zone)).to_text()
|
|
741
|
+
|
|
742
|
+
case RecordTypeChoices.RP:
|
|
743
|
+
return rr.replace(
|
|
744
|
+
mbox=rr.mbox.derelativize(zone), txt=rr.txt.derelativize(zone)
|
|
745
|
+
).to_text()
|
|
746
|
+
|
|
747
|
+
case RecordTypeChoices.NAPTR:
|
|
748
|
+
return rr.replace(
|
|
749
|
+
replacement=rr.replacement.derelativize(zone)
|
|
750
|
+
).to_text()
|
|
751
|
+
|
|
752
|
+
case RecordTypeChoices.PX:
|
|
753
|
+
return rr.replace(
|
|
754
|
+
map822=rr.map822.derelativize(zone),
|
|
755
|
+
mapx400=rr.mapx400.derelativize(zone),
|
|
756
|
+
).to_text()
|
|
757
|
+
|
|
758
|
+
return self.value
|
|
759
|
+
|
|
760
|
+
def handle_conflicting_address_records(self):
|
|
761
|
+
if self.ipam_ip_address is None or not self.is_active:
|
|
762
|
+
return
|
|
763
|
+
|
|
764
|
+
if not get_plugin_config("netbox_dns", "dnssync_conflict_deactivate", False):
|
|
765
|
+
return
|
|
766
|
+
|
|
767
|
+
records = self.zone.records.filter(
|
|
768
|
+
name=self.name,
|
|
769
|
+
type=self.type,
|
|
770
|
+
value=self.value,
|
|
771
|
+
status__in=RECORD_ACTIVE_STATUS_LIST,
|
|
772
|
+
ipam_ip_address__isnull=True,
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
for record in records:
|
|
776
|
+
record.status = RecordStatusChoices.STATUS_INACTIVE
|
|
777
|
+
record.save(update_fields=["status"])
|
|
778
|
+
|
|
779
|
+
def check_unique_rrset_ttl(self):
|
|
780
|
+
if not self._state.adding:
|
|
781
|
+
return
|
|
782
|
+
|
|
783
|
+
if not get_plugin_config("netbox_dns", "enforce_unique_rrset_ttl", False):
|
|
784
|
+
return
|
|
785
|
+
|
|
786
|
+
if self.ipam_ip_address is not None and get_plugin_config(
|
|
787
|
+
"netbox_dns", "dnssync_conflict_deactivate", False
|
|
788
|
+
):
|
|
789
|
+
return
|
|
790
|
+
|
|
791
|
+
if self.type == RecordTypeChoices.PTR and self.managed:
|
|
792
|
+
return
|
|
793
|
+
|
|
794
|
+
records = (
|
|
795
|
+
self.zone.records.filter(
|
|
796
|
+
name=self.name,
|
|
797
|
+
type=self.type,
|
|
798
|
+
)
|
|
799
|
+
.exclude(ttl=self.ttl)
|
|
800
|
+
.exclude(type=RecordTypeChoices.PTR, managed=True)
|
|
801
|
+
.exclude(status=RecordStatusChoices.STATUS_INACTIVE)
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
if self.ipam_ip_address is not None:
|
|
805
|
+
records = records.exclude(ipam_ip_address__isnull=False)
|
|
806
|
+
|
|
807
|
+
if not records.exists():
|
|
808
|
+
return
|
|
809
|
+
|
|
810
|
+
conflicting_ttls = {record.ttl for record in records}
|
|
811
|
+
if len(conflicting_ttls) == 1 and self.ttl is None:
|
|
812
|
+
self.ttl = conflicting_ttls.pop()
|
|
813
|
+
return
|
|
814
|
+
|
|
815
|
+
raise ValidationError(
|
|
816
|
+
{
|
|
817
|
+
"ttl": _(
|
|
818
|
+
"There is at least one active {type} record for name {name} in zone {zone} and TTL is different ({ttls})."
|
|
819
|
+
).format(
|
|
820
|
+
type=self.type,
|
|
821
|
+
name=self.name,
|
|
822
|
+
zone=self.zone,
|
|
823
|
+
ttls=", ".join(str(ttl) for ttl in conflicting_ttls),
|
|
824
|
+
)
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
def update_rrset_ttl(self, ttl=None):
|
|
829
|
+
if self._state.adding:
|
|
830
|
+
return
|
|
831
|
+
|
|
832
|
+
if not get_plugin_config("netbox_dns", "enforce_unique_rrset_ttl", False):
|
|
833
|
+
return
|
|
834
|
+
|
|
835
|
+
if self.type == RecordTypeChoices.PTR and self.managed:
|
|
836
|
+
return
|
|
837
|
+
|
|
838
|
+
if ttl is None:
|
|
839
|
+
ttl = self.ttl
|
|
403
840
|
|
|
404
|
-
|
|
841
|
+
records = (
|
|
842
|
+
self.zone.records.filter(
|
|
843
|
+
name=self.name,
|
|
844
|
+
type=self.type,
|
|
845
|
+
)
|
|
846
|
+
.exclude(pk=self.pk)
|
|
847
|
+
.exclude(ttl=ttl)
|
|
848
|
+
.exclude(type=RecordTypeChoices.PTR, managed=True)
|
|
849
|
+
.exclude(status=RecordStatusChoices.STATUS_INACTIVE)
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
for record in records:
|
|
853
|
+
record.ttl = ttl
|
|
854
|
+
record.save(update_fields=["ttl"], update_rrset_ttl=False)
|
|
855
|
+
|
|
856
|
+
def clean_fields(self, exclude=None):
|
|
405
857
|
self.type = self.type.upper()
|
|
406
|
-
|
|
858
|
+
if get_plugin_config("netbox_dns", "convert_names_to_lowercase", False):
|
|
859
|
+
self.name = self.name.lower()
|
|
407
860
|
|
|
408
|
-
|
|
409
|
-
|
|
861
|
+
super().clean_fields(exclude=exclude)
|
|
862
|
+
|
|
863
|
+
def clean(self, *args, new_zone=None, **kwargs):
|
|
864
|
+
self.validate_name(new_zone=new_zone)
|
|
410
865
|
self.validate_value()
|
|
411
|
-
self.
|
|
866
|
+
self.check_unique_record(new_zone=new_zone)
|
|
867
|
+
if self._state.adding:
|
|
868
|
+
self.check_unique_rrset_ttl()
|
|
869
|
+
|
|
870
|
+
if not self.is_address_record:
|
|
871
|
+
self.disable_ptr = False
|
|
412
872
|
|
|
413
873
|
if not self.is_active:
|
|
414
874
|
return
|
|
415
875
|
|
|
416
|
-
records = (
|
|
417
|
-
|
|
418
|
-
.exclude(pk=self.pk)
|
|
419
|
-
.exclude(active=False)
|
|
876
|
+
records = self.zone.records.filter(name=self.name, active=True).exclude(
|
|
877
|
+
pk=self.pk
|
|
420
878
|
)
|
|
421
879
|
|
|
880
|
+
if self.type == RecordTypeChoices.A and not self.disable_ptr:
|
|
881
|
+
ptr_zone = self.ptr_zone
|
|
882
|
+
|
|
883
|
+
if (
|
|
884
|
+
ptr_zone is not None
|
|
885
|
+
and ptr_zone.is_rfc2317_zone
|
|
886
|
+
and ptr_zone.rfc2317_parent_managed
|
|
887
|
+
):
|
|
888
|
+
ptr_cname_zone = ptr_zone.rfc2317_parent_zone
|
|
889
|
+
ptr_cname_name = self.rfc2317_ptr_cname_name
|
|
890
|
+
ptr_fqdn = dns_name.from_text(
|
|
891
|
+
self.rfc2317_ptr_name, origin=dns_name.from_text(ptr_zone.name)
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
if (
|
|
895
|
+
ptr_cname_zone.records.filter(
|
|
896
|
+
name=ptr_cname_name,
|
|
897
|
+
active=True,
|
|
898
|
+
)
|
|
899
|
+
.exclude(
|
|
900
|
+
type=RecordTypeChoices.CNAME,
|
|
901
|
+
value=ptr_fqdn,
|
|
902
|
+
)
|
|
903
|
+
.exclude(type=RecordTypeChoices.NSEC)
|
|
904
|
+
.exists()
|
|
905
|
+
):
|
|
906
|
+
raise ValidationError(
|
|
907
|
+
{
|
|
908
|
+
"value": _(
|
|
909
|
+
"There is already an active record for name {name} in zone {zone}, RFC2317 CNAME is not allowed."
|
|
910
|
+
).format(name=ptr_cname_name, zone=ptr_cname_zone)
|
|
911
|
+
}
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
if self.type == RecordTypeChoices.SOA and self.name != "@":
|
|
915
|
+
raise ValidationError(
|
|
916
|
+
{
|
|
917
|
+
"name": _(
|
|
918
|
+
"SOA records are only allowed with name @ and are created automatically by NetBox DNS"
|
|
919
|
+
)
|
|
920
|
+
}
|
|
921
|
+
)
|
|
922
|
+
|
|
422
923
|
if self.type == RecordTypeChoices.CNAME:
|
|
423
924
|
if records.exclude(type=RecordTypeChoices.NSEC).exists():
|
|
424
925
|
raise ValidationError(
|
|
425
926
|
{
|
|
426
|
-
"type":
|
|
927
|
+
"type": _(
|
|
928
|
+
"There is already an active record for name {name} in zone {zone}, CNAME is not allowed."
|
|
929
|
+
).format(name=self.name, zone=self.zone)
|
|
427
930
|
}
|
|
428
|
-
)
|
|
931
|
+
)
|
|
429
932
|
|
|
430
933
|
elif (
|
|
431
934
|
records.filter(type=RecordTypeChoices.CNAME).exists()
|
|
@@ -433,57 +936,101 @@ class Record(NetBoxModel):
|
|
|
433
936
|
):
|
|
434
937
|
raise ValidationError(
|
|
435
938
|
{
|
|
436
|
-
"type":
|
|
939
|
+
"type": _(
|
|
940
|
+
"There is already an active CNAME record for name {name} in zone {zone}, no other record allowed."
|
|
941
|
+
).format(name=self.name, zone=self.zone)
|
|
437
942
|
}
|
|
438
|
-
)
|
|
943
|
+
)
|
|
439
944
|
|
|
440
945
|
elif self.type in RecordTypeChoices.SINGLETONS:
|
|
441
946
|
if records.filter(type=self.type).exists():
|
|
442
947
|
raise ValidationError(
|
|
443
948
|
{
|
|
444
|
-
"type":
|
|
949
|
+
"type": _(
|
|
950
|
+
"There is already an active {type} record for name {name} in zone {zone}, more than one are not allowed."
|
|
951
|
+
).format(type=self.type, name=self.name, zone=self.zone)
|
|
445
952
|
}
|
|
446
|
-
)
|
|
953
|
+
)
|
|
447
954
|
|
|
448
|
-
|
|
955
|
+
super().clean(*args, **kwargs)
|
|
956
|
+
|
|
957
|
+
def save(
|
|
958
|
+
self,
|
|
959
|
+
*args,
|
|
960
|
+
update_rfc2317_cname=True,
|
|
961
|
+
save_zone_serial=True,
|
|
962
|
+
update_rrset_ttl=True,
|
|
963
|
+
**kwargs,
|
|
964
|
+
):
|
|
449
965
|
self.full_clean()
|
|
450
966
|
|
|
967
|
+
if not self._state.adding and update_rrset_ttl:
|
|
968
|
+
self.update_rrset_ttl()
|
|
969
|
+
|
|
451
970
|
if self.is_ptr_record:
|
|
452
|
-
|
|
971
|
+
if self.zone.is_rfc2317_zone:
|
|
972
|
+
self.ip_address = self.address_from_rfc2317_name
|
|
973
|
+
if update_rfc2317_cname:
|
|
974
|
+
self.update_rfc2317_cname_record(save_zone_serial=save_zone_serial)
|
|
975
|
+
else:
|
|
976
|
+
self.ip_address = self.address_from_name
|
|
977
|
+
|
|
453
978
|
elif self.is_address_record:
|
|
454
|
-
self.ip_address = self.value
|
|
979
|
+
self.ip_address = netaddr.IPAddress(self.value)
|
|
455
980
|
else:
|
|
456
981
|
self.ip_address = None
|
|
457
982
|
|
|
458
983
|
if self.is_address_record:
|
|
459
|
-
self.
|
|
984
|
+
self.handle_conflicting_address_records()
|
|
985
|
+
self.update_ptr_record(
|
|
986
|
+
update_rfc2317_cname=update_rfc2317_cname,
|
|
987
|
+
save_zone_serial=save_zone_serial,
|
|
988
|
+
)
|
|
460
989
|
elif self.ptr_record is not None:
|
|
461
|
-
self.ptr_record
|
|
990
|
+
self.cleanup_ptr_record = self.ptr_record
|
|
462
991
|
self.ptr_record = None
|
|
463
992
|
|
|
464
|
-
|
|
993
|
+
changed_fields = self.changed_fields
|
|
994
|
+
if changed_fields is None or changed_fields:
|
|
995
|
+
super().save(*args, **kwargs)
|
|
465
996
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
997
|
+
self.refresh_ptr_record(
|
|
998
|
+
self.cleanup_ptr_record,
|
|
999
|
+
update_rfc2317_cname=update_rfc2317_cname,
|
|
1000
|
+
save_zone_serial=save_zone_serial,
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
if self.type != RecordTypeChoices.SOA and self.zone.soa_serial_auto:
|
|
1004
|
+
self.zone.update_serial(save_zone_serial=save_zone_serial)
|
|
469
1005
|
|
|
470
|
-
def delete(self, *args, **kwargs):
|
|
471
|
-
if self.
|
|
472
|
-
self.
|
|
1006
|
+
def delete(self, *args, save_zone_serial=True, **kwargs):
|
|
1007
|
+
if self.rfc2317_cname_record:
|
|
1008
|
+
self.remove_from_rfc2317_cname_record(save_zone_serial=save_zone_serial)
|
|
1009
|
+
|
|
1010
|
+
ptr_record = self.ptr_record
|
|
473
1011
|
|
|
474
1012
|
super().delete(*args, **kwargs)
|
|
475
1013
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
1014
|
+
self.refresh_ptr_record(
|
|
1015
|
+
ptr_record,
|
|
1016
|
+
update_rfc2317_cname=True,
|
|
1017
|
+
save_zone_serial=save_zone_serial,
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
_zone = self.zone
|
|
1021
|
+
if _zone.soa_serial_auto:
|
|
1022
|
+
_zone.update_serial(save_zone_serial=save_zone_serial)
|
|
479
1023
|
|
|
480
1024
|
|
|
481
1025
|
@register_search
|
|
482
1026
|
class RecordIndex(SearchIndex):
|
|
483
1027
|
model = Record
|
|
1028
|
+
|
|
484
1029
|
fields = (
|
|
485
|
-
("
|
|
1030
|
+
("fqdn", 100),
|
|
1031
|
+
("name", 120),
|
|
486
1032
|
("value", 150),
|
|
487
1033
|
("zone", 200),
|
|
488
1034
|
("type", 200),
|
|
1035
|
+
("description", 500),
|
|
489
1036
|
)
|