Coverage for ipam/tables/ip.py: 96%
122 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
1import django_tables2 as tables
2from django.utils.safestring import mark_safe
3from django.utils.translation import gettext_lazy as _
4from django_tables2.utils import Accessor
6from ipam.models import *
7from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
8from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin, TenantColumn
10from .template_code import *
12__all__ = (
13 'AggregateTable',
14 'AnnotatedIPAddressTable',
15 'AssignedIPAddressesTable',
16 'IPAddressAssignTable',
17 'IPAddressTable',
18 'IPRangeTable',
19 'PrefixTable',
20 'RIRTable',
21 'RoleTable',
22)
24AVAILABLE_LABEL = mark_safe('<span class="badge text-bg-success">Available</span>')
27#
28# RIRs
29#
31class RIRTable(OrganizationalModelTable):
32 name = tables.Column(
33 verbose_name=_('Name'),
34 linkify=True
35 )
36 is_private = columns.BooleanColumn(
37 verbose_name=_('Private'),
38 false_mark=None
39 )
40 aggregate_count = columns.LinkedCountColumn(
41 viewname='ipam:aggregate_list',
42 url_params={'rir_id': 'pk'},
43 verbose_name=_('Aggregates')
44 )
45 tags = columns.TagColumn(
46 url_name='ipam:rir_list'
47 )
49 class Meta(OrganizationalModelTable.Meta):
50 model = RIR
51 fields = (
52 'pk', 'id', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'comments', 'tags', 'created',
53 'last_updated', 'actions',
54 )
55 default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description')
58#
59# Aggregates
60#
62class AggregateTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
63 prefix = tables.Column(
64 linkify=True,
65 verbose_name=_('Aggregate'),
66 attrs={
67 # Allow the aggregate to be copied to the clipboard
68 'a': {'id': lambda record: f"aggregate_{record.pk}"}
69 }
70 )
71 date_added = tables.DateColumn(
72 format="Y-m-d",
73 verbose_name=_('Added')
74 )
75 child_count = tables.Column(
76 verbose_name=_('Prefixes')
77 )
78 utilization = columns.UtilizationColumn(
79 verbose_name=_('Utilization'),
80 accessor='get_utilization',
81 orderable=False
82 )
83 tags = columns.TagColumn(
84 url_name='ipam:aggregate_list'
85 )
86 actions = columns.ActionsColumn(
87 extra_buttons=AGGREGATE_COPY_BUTTON
88 )
90 class Meta(PrimaryModelTable.Meta):
91 model = Aggregate
92 fields = (
93 'pk', 'id', 'prefix', 'rir', 'tenant', 'tenant_group', 'child_count', 'utilization', 'date_added',
94 'description', 'contacts', 'comments', 'tags', 'created', 'last_updated',
95 )
96 default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
99#
100# Roles
101#
103class RoleTable(OrganizationalModelTable):
104 name = tables.Column(
105 verbose_name=_('Name'),
106 linkify=True
107 )
108 prefix_count = columns.LinkedCountColumn(
109 viewname='ipam:prefix_list',
110 url_params={'role_id': 'pk'},
111 verbose_name=_('Prefixes')
112 )
113 iprange_count = columns.LinkedCountColumn(
114 viewname='ipam:iprange_list',
115 url_params={'role_id': 'pk'},
116 verbose_name=_('IP Ranges')
117 )
118 vlan_count = columns.LinkedCountColumn(
119 viewname='ipam:vlan_list',
120 url_params={'role_id': 'pk'},
121 verbose_name=_('VLANs')
122 )
123 asn_count = columns.LinkedCountColumn(
124 viewname='ipam:asn_list',
125 url_params={'role_id': 'pk'},
126 verbose_name=_('ASNs')
127 )
128 tags = columns.TagColumn(
129 url_name='ipam:role_list'
130 )
132 class Meta(OrganizationalModelTable.Meta):
133 model = Role
134 fields = (
135 'pk', 'id', 'name', 'slug', 'prefix_count', 'iprange_count', 'vlan_count', 'asn_count', 'description',
136 'weight', 'comments', 'tags', 'created', 'last_updated', 'actions',
137 )
138 default_columns = ('pk', 'name', 'prefix_count', 'iprange_count', 'vlan_count', 'asn_count', 'description')
141#
142# Prefixes
143#
145class PrefixUtilizationColumn(columns.UtilizationColumn):
146 """
147 Extend UtilizationColumn to allow disabling the warning & danger thresholds for prefixes
148 marked as fully utilized.
149 """
150 template_code = """
151 {% load helpers %}
152 {% if record.pk and record.mark_utilized %}
153 {% utilization_graph value warning_threshold=0 danger_threshold=0 %}
154 {% elif record.pk %}
155 {% utilization_graph value %}
156 {% endif %}
157 """
160class PrefixTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
161 prefix = columns.TemplateColumn(
162 verbose_name=_('Prefix'),
163 template_code=PREFIX_LINK_WITH_DEPTH,
164 export_raw=True,
165 attrs={'td': {'class': 'text-nowrap'}}
166 )
167 prefix_flat = columns.TemplateColumn(
168 accessor=Accessor('prefix'),
169 template_code=PREFIX_LINK,
170 export_raw=True,
171 verbose_name=_('Prefix (Flat)')
172 )
173 depth = tables.Column(
174 accessor=Accessor('_depth'),
175 verbose_name=_('Depth')
176 )
177 children = columns.LinkedCountColumn(
178 accessor=Accessor('_children'),
179 viewname='ipam:prefix_list',
180 url_params={
181 'vrf_id': 'vrf_id',
182 'within': 'prefix',
183 },
184 verbose_name=_('Children')
185 )
186 status = columns.ChoiceFieldColumn(
187 verbose_name=_('Status'),
188 default=AVAILABLE_LABEL
189 )
190 vrf = tables.TemplateColumn(
191 template_code=VRF_LINK,
192 verbose_name=_('VRF')
193 )
194 scope_type = columns.ContentTypeColumn(
195 verbose_name=_('Scope Type'),
196 )
197 scope = tables.Column(
198 verbose_name=_('Scope'),
199 linkify=True,
200 orderable=False
201 )
202 vlan_group = tables.Column(
203 accessor='vlan__group',
204 linkify=True,
205 verbose_name=_('VLAN Group')
206 )
207 vlan = tables.Column(
208 order_by=('vlan__vid', 'vlan__pk'),
209 linkify=True,
210 verbose_name=_('VLAN')
211 )
212 role = tables.Column(
213 verbose_name=_('Role'),
214 linkify=True
215 )
216 is_pool = columns.BooleanColumn(
217 verbose_name=_('Pool'),
218 false_mark=None
219 )
220 mark_utilized = columns.BooleanColumn(
221 verbose_name=_('Marked Utilized'),
222 false_mark=None
223 )
224 utilization = PrefixUtilizationColumn(
225 verbose_name=_('Utilization'),
226 accessor='get_utilization',
227 orderable=False
228 )
229 tags = columns.TagColumn(
230 url_name='ipam:prefix_list'
231 )
232 actions = columns.ActionsColumn(
233 extra_buttons=PREFIX_COPY_BUTTON
234 )
236 class Meta(PrimaryModelTable.Meta):
237 model = Prefix
238 fields = (
239 'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'tenant_group',
240 'scope', 'scope_type', 'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'contacts',
241 'comments', 'tags', 'created', 'last_updated',
242 )
243 default_columns = (
244 'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'scope', 'vlan', 'role',
245 'description',
246 )
247 row_attrs = {
248 'class': lambda record: 'success' if not record.pk else '',
249 }
252#
253# IP ranges
254#
256class IPRangeTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
257 start_address = tables.Column(
258 verbose_name=_('Start address'),
259 linkify=True
260 )
261 vrf = tables.TemplateColumn(
262 template_code=VRF_LINK,
263 verbose_name=_('VRF')
264 )
265 status = columns.ChoiceFieldColumn(
266 verbose_name=_('Status'),
267 default=AVAILABLE_LABEL
268 )
269 role = tables.Column(
270 verbose_name=_('Role'),
271 linkify=True
272 )
273 mark_populated = columns.BooleanColumn(
274 verbose_name=_('Marked Populated'),
275 false_mark=None
276 )
277 mark_utilized = columns.BooleanColumn(
278 verbose_name=_('Marked Utilized'),
279 false_mark=None
280 )
281 utilization = columns.UtilizationColumn(
282 verbose_name=_('Utilization'),
283 accessor='utilization',
284 orderable=False
285 )
286 tags = columns.TagColumn(
287 url_name='ipam:iprange_list'
288 )
290 class Meta(PrimaryModelTable.Meta):
291 model = IPRange
292 fields = (
293 'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group',
294 'mark_populated', 'mark_utilized', 'utilization', 'description', 'contacts', 'comments', 'tags',
295 'created', 'last_updated',
296 )
297 default_columns = (
298 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
299 )
300 row_attrs = {
301 'class': lambda record: 'success' if not record.pk else '',
302 }
305#
306# IP Addresses
307#
309class IPAddressTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
310 address = tables.TemplateColumn(
311 template_code=IPADDRESS_LINK,
312 verbose_name=_('IP Address')
313 )
314 vrf = tables.TemplateColumn(
315 template_code=VRF_LINK,
316 verbose_name=_('VRF')
317 )
318 status = columns.ChoiceFieldColumn(
319 verbose_name=_('Status'),
320 default=AVAILABLE_LABEL
321 )
322 role = columns.ChoiceFieldColumn(
323 verbose_name=_('Role'),
324 )
325 assigned_object = tables.Column(
326 linkify=True,
327 orderable=False,
328 verbose_name=_('Interface')
329 )
330 assigned_object_parent = tables.Column(
331 accessor='assigned_object__parent_object',
332 linkify=True,
333 orderable=False,
334 verbose_name=_('Parent')
335 )
336 nat_inside = tables.Column(
337 linkify=True,
338 orderable=False,
339 verbose_name=_('NAT (Inside)')
340 )
341 nat_outside = columns.ManyToManyColumn(
342 linkify_item=True,
343 orderable=False,
344 verbose_name=_('NAT (Outside)')
345 )
346 assigned = columns.BooleanColumn(
347 accessor='assigned_object_id',
348 linkify=lambda record: record.assigned_object.get_absolute_url(),
349 verbose_name=_('Assigned'),
350 false_mark=None
351 )
352 tags = columns.TagColumn(
353 url_name='ipam:ipaddress_list'
354 )
355 actions = columns.ActionsColumn(
356 extra_buttons=IPADDRESS_COPY_BUTTON
357 )
359 class Meta(PrimaryModelTable.Meta):
360 model = IPAddress
361 fields = (
362 'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'nat_outside',
363 'assigned', 'dns_name', 'description', 'comments', 'contacts', 'tags', 'created', 'last_updated',
364 )
365 default_columns = (
366 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
367 )
368 row_attrs = {
369 'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
370 }
373class AnnotatedIPAddressTable(IPAddressTable):
374 address = tables.TemplateColumn(
375 template_code=IPADDRESS_LINK,
376 verbose_name=_('IP Address')
377 )
379 def render_pk(self, value, record, bound_column):
380 if type(record) is not self._meta.model:
381 return ''
382 return bound_column.column.render(value, bound_column, record)
384 class Meta(IPAddressTable.Meta):
385 pass
388class IPAddressAssignTable(NetBoxTable):
389 address = tables.TemplateColumn(
390 template_code=IPADDRESS_ASSIGN_LINK,
391 verbose_name=_('IP Address')
392 )
393 status = columns.ChoiceFieldColumn(
394 verbose_name=_('Status'),
395 )
396 assigned_object = tables.Column(
397 verbose_name=_('Assigned Object'),
398 orderable=False
399 )
401 class Meta(NetBoxTable.Meta):
402 model = IPAddress
403 fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'description')
404 exclude = ('id', )
405 orderable = False
408class AssignedIPAddressesTable(NetBoxTable):
409 """
410 List IP addresses assigned to an object.
411 """
412 address = tables.Column(
413 linkify=True,
414 verbose_name=_('IP Address')
415 )
416 vrf = tables.TemplateColumn(
417 template_code=VRF_LINK,
418 verbose_name=_('VRF')
419 )
420 status = columns.ChoiceFieldColumn(
421 verbose_name=_('Status'),
422 )
423 tenant = TenantColumn(
424 verbose_name=_('Tenant'),
425 )
427 class Meta(NetBoxTable.Meta):
428 model = IPAddress
429 fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
430 exclude = ('id', )