Coverage for ipam/models/asns.py: 64%
71 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from django.core.exceptions import ValidationError
2from django.db import models
3from django.utils.translation import gettext_lazy as _
5from ipam.fields import ASNField
6from ipam.querysets import ASNRangeQuerySet
7from netbox.models import OrganizationalModel, PrimaryModel
8from netbox.models.features import ContactsMixin
10__all__ = (
11 'ASN',
12 'ASNRange',
13)
16class ASNRange(OrganizationalModel):
17 name = models.CharField(
18 verbose_name=_('name'),
19 max_length=100,
20 unique=True,
21 db_collation="natural_sort"
22 )
23 slug = models.SlugField(
24 verbose_name=_('slug'),
25 max_length=100,
26 unique=True
27 )
28 rir = models.ForeignKey(
29 to='ipam.RIR',
30 on_delete=models.PROTECT,
31 related_name='asn_ranges',
32 verbose_name=_('RIR')
33 )
34 start = ASNField(
35 verbose_name=_('start'),
36 )
37 end = ASNField(
38 verbose_name=_('end'),
39 )
40 tenant = models.ForeignKey(
41 to='tenancy.Tenant',
42 on_delete=models.PROTECT,
43 related_name='asn_ranges',
44 blank=True,
45 null=True
46 )
48 objects = ASNRangeQuerySet.as_manager()
50 class Meta:
51 ordering = ('name',)
52 verbose_name = _('ASN range')
53 verbose_name_plural = _('ASN ranges')
55 def __str__(self):
56 return f'{self.name} ({self.range_as_string()})'
58 def clean(self):
59 super().clean()
61 if self.end <= self.start:
62 raise ValidationError(
63 _("Starting ASN ({start}) must be lower than ending ASN ({end}).").format(
64 start=self.start, end=self.end
65 )
66 )
68 @property
69 def range(self):
70 """
71 Return a range of integers representing the ASN range.
72 """
73 return range(self.start, self.end + 1)
75 @property
76 def start_asdot(self):
77 """
78 Return ASDOT notation for AS numbers greater than 16 bits.
79 """
80 return ASNField.to_asdot(self.start)
82 @property
83 def end_asdot(self):
84 """
85 Return ASDOT notation for AS numbers greater than 16 bits.
86 """
87 return ASNField.to_asdot(self.end)
89 def range_as_string(self):
90 """
91 Return a string representation of the ASN range.
92 """
93 return f'{self.start}-{self.end}'
95 def range_as_string_with_asdot(self):
96 """
97 Return a string representation of the ASN range, including ASDOT notation.
98 """
99 if self.end >= 65536:
100 return f'{self.range_as_string()} ({self.start_asdot}-{self.end_asdot})'
101 return self.range_as_string()
103 def get_child_asns(self):
104 """
105 Return all child ASNs (ASNs within the range).
106 """
107 return ASN.objects.filter(
108 asn__gte=self.start,
109 asn__lte=self.end
110 )
112 def get_available_asns(self):
113 """
114 Return all available ASNs within this range.
115 """
116 range = set(self.range)
117 existing_asns = set(self.get_child_asns().values_list('asn', flat=True))
118 available_asns = sorted(range - existing_asns)
120 return available_asns
123class ASN(ContactsMixin, PrimaryModel):
124 """
125 An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
126 one or more ASNs assigned to it.
127 """
128 rir = models.ForeignKey(
129 to='ipam.RIR',
130 on_delete=models.PROTECT,
131 related_name='asns',
132 verbose_name=_('RIR'),
133 help_text=_("Regional Internet Registry responsible for this AS number space")
134 )
135 asn = ASNField(
136 unique=True,
137 verbose_name=_('ASN'),
138 help_text=_('16- or 32-bit autonomous system number')
139 )
140 role = models.ForeignKey(
141 to='ipam.Role',
142 on_delete=models.SET_NULL,
143 related_name='asns',
144 blank=True,
145 null=True,
146 verbose_name=_('role'),
147 help_text=_("The primary function of this ASN")
148 )
149 tenant = models.ForeignKey(
150 to='tenancy.Tenant',
151 on_delete=models.PROTECT,
152 related_name='asns',
153 blank=True,
154 null=True
155 )
157 prerequisite_models = (
158 'ipam.RIR',
159 )
161 class Meta:
162 ordering = ['asn']
163 verbose_name = _('ASN')
164 verbose_name_plural = _('ASNs')
166 def __str__(self):
167 return f'AS{self.asn_with_asdot}'
169 @property
170 def asn_asdot(self):
171 """
172 Return ASDOT notation for AS numbers greater than 16 bits.
173 """
174 return ASNField.to_asdot(self.asn)
176 @property
177 def asn_with_asdot(self):
178 """
179 Return both plain and ASDOT notation, where applicable.
180 """
181 if self.asn >= 65536:
182 return f'{self.asn} ({self.asn_asdot})'
183 return str(self.asn)
185 @property
186 def prefixed_name(self):
187 """
188 Return the ASN with ASDOT notation prefixed with "AS".
189 """
190 return f'AS{self.asn_with_asdot}'