Coverage for dcim/tables/cables.py: 72%
59 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.html import escape
3from django.utils.safestring import mark_safe
4from django.utils.translation import gettext_lazy as _
5from django_tables2.utils import Accessor
7from dcim.models import Cable, CableBundle
8from netbox.tables import PrimaryModelTable, columns
9from tenancy.tables import TenancyColumnsMixin
11from .template_code import CABLE_LENGTH
13__all__ = (
14 'CableBundleTable',
15 'CableTable',
16)
19class CableTerminationsColumn(tables.Column):
20 """
21 Args:
22 cable_end: Which side of the cable to report on (A or B)
23 attr: The CableTermination attribute to return for each instance (returns the termination object by default)
24 """
25 def __init__(self, cable_end, attr='termination', *args, **kwargs):
26 self.cable_end = cable_end
27 self.attr = attr
28 super().__init__(accessor=Accessor('terminations'), *args, **kwargs)
30 def _get_terminations(self, manager, deduplicate=False):
31 # CableTerminations are ordered by connector, which defines the mapping between the two ends of
32 # a cable, so the order in which they are listed is significant: collecting them into a set would
33 # render (and export) them in an arbitrary order, which for a cable with a profile assigned no
34 # longer reflects how it is wired.
35 terminations = []
36 for cabletermination in manager.all():
37 if cabletermination.cable_end == self.cable_end:
38 if termination := getattr(cabletermination, self.attr, None):
39 if deduplicate and termination in terminations:
40 continue
41 terminations.append(termination)
43 return terminations
45 def render(self, value):
46 # Collapse any repeated parent objects (e.g. several terminations on the same device) for display
47 links = [
48 f'<a href="{term.get_absolute_url()}">{escape(term)}</a>'
49 for term in self._get_terminations(value, deduplicate=True)
50 ]
51 return mark_safe('<br />'.join(links) or '—')
53 def value(self, value):
54 # Exported values are never deduplicated: each termination must be accompanied by its parent
55 # object at the same position for the exported data to be re-importable.
56 return ','.join([str(t) for t in self._get_terminations(value)])
59#
60# Cables
61#
63class CableTable(TenancyColumnsMixin, PrimaryModelTable):
64 a_terminations = CableTerminationsColumn(
65 cable_end='A',
66 orderable=False,
67 verbose_name=_('Termination A')
68 )
69 b_terminations = CableTerminationsColumn(
70 cable_end='B',
71 orderable=False,
72 verbose_name=_('Termination B')
73 )
74 device_a = CableTerminationsColumn(
75 cable_end='A',
76 attr='_device',
77 orderable=False,
78 verbose_name=_('Device A')
79 )
80 device_b = CableTerminationsColumn(
81 cable_end='B',
82 attr='_device',
83 orderable=False,
84 verbose_name=_('Device B')
85 )
86 location_a = CableTerminationsColumn(
87 cable_end='A',
88 attr='_location',
89 orderable=False,
90 verbose_name=_('Location A')
91 )
92 location_b = CableTerminationsColumn(
93 cable_end='B',
94 attr='_location',
95 orderable=False,
96 verbose_name=_('Location B')
97 )
98 rack_a = CableTerminationsColumn(
99 cable_end='A',
100 attr='_rack',
101 orderable=False,
102 verbose_name=_('Rack A')
103 )
104 rack_b = CableTerminationsColumn(
105 cable_end='B',
106 attr='_rack',
107 orderable=False,
108 verbose_name=_('Rack B')
109 )
110 site_a = CableTerminationsColumn(
111 cable_end='A',
112 attr='_site',
113 orderable=False,
114 verbose_name=_('Site A')
115 )
116 site_b = CableTerminationsColumn(
117 cable_end='B',
118 attr='_site',
119 orderable=False,
120 verbose_name=_('Site B')
121 )
122 status = columns.ChoiceFieldColumn()
123 profile = columns.ChoiceFieldColumn()
124 length = columns.TemplateColumn(
125 template_code=CABLE_LENGTH,
126 order_by=('_abs_length')
127 )
128 color = columns.ColorColumn()
129 color_name = tables.Column(
130 verbose_name=_('Color Name'),
131 orderable=False
132 )
133 bundle = tables.Column(
134 verbose_name=_('Bundle'),
135 linkify=True,
136 )
137 tags = columns.TagColumn(
138 url_name='dcim:cable_list'
139 )
141 class Meta(PrimaryModelTable.Meta):
142 model = Cable
143 fields = (
144 'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'device_a', 'device_b', 'rack_a', 'rack_b',
145 'location_a', 'location_b', 'site_a', 'site_b', 'status', 'profile', 'type', 'tenant', 'tenant_group',
146 'color', 'color_name', 'bundle', 'length', 'description', 'comments', 'tags', 'created', 'last_updated',
147 )
148 default_columns = (
149 'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'status', 'type',
150 )
153class CableBundleTable(PrimaryModelTable):
154 name = tables.Column(
155 verbose_name=_('Name'),
156 linkify=True,
157 )
158 cable_count = tables.Column(
159 verbose_name=_('Cables'),
160 )
161 tags = columns.TagColumn(
162 url_name='dcim:cablebundle_list'
163 )
165 class Meta(PrimaryModelTable.Meta):
166 model = CableBundle
167 fields = (
168 'pk', 'id', 'name', 'cable_count', 'description', 'tags', 'created', 'last_updated',
169 )
170 default_columns = (
171 'pk', 'id', 'name', 'cable_count', 'description',
172 )