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

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 

6 

7from dcim.models import Cable, CableBundle 

8from netbox.tables import PrimaryModelTable, columns 

9from tenancy.tables import TenancyColumnsMixin 

10 

11from .template_code import CABLE_LENGTH 

12 

13__all__ = ( 

14 'CableBundleTable', 

15 'CableTable', 

16) 

17 

18 

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) 

29 

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) 

42 

43 return terminations 

44 

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 '&mdash;') 

52 

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)]) 

57 

58 

59# 

60# Cables 

61# 

62 

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 ) 

140 

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 ) 

151 

152 

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 ) 

164 

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 )