Coverage for netbox_data_import/tables.py: 100%

94 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-09 20:50 +0000

1# SPDX-License-Identifier: Apache-2.0 

2# Copyright (C) 2025 Marcin Zieba <marcinpsk@gmail.com> 

3import django_tables2 as tables 

4from netbox.tables import NetBoxTable, columns 

5from .models import ( 

6 CableClassMapping, 

7 ImportProfile, 

8 InferenceBackend, 

9 ColumnMapping, 

10 ClassRoleMapping, 

11 DeviceTypeMapping, 

12 ColumnTransformRule, 

13 ImportExecution, 

14) 

15 

16 

17_MAPPING_ACTIONS_TEMPLATE = """ 

18<a href="{% url edit_url record.pk %}" class="btn btn-sm btn-warning" aria-label="Edit {{ record }}"> 

19 <i class="mdi mdi-pencil"></i> 

20</a> 

21<a href="{% url delete_url record.pk %}" class="btn btn-sm btn-danger" aria-label="Delete {{ record }}"> 

22 <i class="mdi mdi-trash-can-outline"></i> 

23</a> 

24""" 

25 

26 

27def _mapping_actions_column(route_prefix: str) -> tables.TemplateColumn: 

28 """Return the edit and delete actions every inline mapping table shares.""" 

29 return tables.TemplateColumn( 

30 template_code=_MAPPING_ACTIONS_TEMPLATE, 

31 extra_context={ 

32 "edit_url": f"plugins:netbox_data_import:{route_prefix}_edit", 

33 "delete_url": f"plugins:netbox_data_import:{route_prefix}_delete", 

34 }, 

35 verbose_name="", 

36 orderable=False, 

37 ) 

38 

39 

40class ImportProfileTable(NetBoxTable): 

41 """Table for listing ImportProfile objects.""" 

42 

43 name = tables.Column(linkify=True) 

44 source_adapter = tables.Column() 

45 column_mappings = tables.Column( 

46 accessor="column_mappings.count", 

47 verbose_name="Columns", 

48 orderable=False, 

49 ) 

50 class_role_mappings = tables.Column( 

51 accessor="class_role_mappings.count", 

52 verbose_name="Class Mappings", 

53 orderable=False, 

54 ) 

55 device_type_mappings = tables.Column( 

56 accessor="device_type_mappings.count", 

57 verbose_name="DT Mappings", 

58 orderable=False, 

59 ) 

60 actions = columns.ActionsColumn(actions=("edit", "delete")) 

61 

62 class Meta(NetBoxTable.Meta): 

63 model = ImportProfile 

64 fields = ( 

65 "pk", 

66 "name", 

67 "source_adapter", 

68 "column_mappings", 

69 "class_role_mappings", 

70 "device_type_mappings", 

71 "actions", 

72 ) 

73 default_columns = ( 

74 "name", 

75 "source_adapter", 

76 "column_mappings", 

77 "class_role_mappings", 

78 "device_type_mappings", 

79 "actions", 

80 ) 

81 

82 

83class ColumnMappingTable(tables.Table): 

84 """Table for displaying ColumnMapping objects inline on the profile detail page.""" 

85 

86 source_column = tables.Column() 

87 target_field = tables.Column(accessor="get_target_field_display", order_by="target_field") 

88 actions = _mapping_actions_column("columnmapping") 

89 

90 class Meta: 

91 model = ColumnMapping 

92 fields = ("source_column", "target_field", "actions") 

93 

94 

95class ClassRoleMappingTable(tables.Table): 

96 """Table for displaying ClassRoleMapping objects inline on the profile detail page.""" 

97 

98 source_class = tables.Column() 

99 creates_rack = tables.BooleanColumn() 

100 rack_type = tables.Column(verbose_name="Rack Type", accessor="rack_type", default="—") 

101 role_slug = tables.Column() 

102 ignore = tables.BooleanColumn() 

103 actions = _mapping_actions_column("classrolemapping") 

104 

105 class Meta: 

106 model = ClassRoleMapping 

107 fields = ("source_class", "creates_rack", "rack_type", "role_slug", "ignore", "actions") 

108 

109 

110class CableClassMappingTable(tables.Table): 

111 """Display CableClass target decisions inline on the profile detail page.""" 

112 

113 cable_class = tables.Column(verbose_name="CableClass") 

114 cable_type = tables.Column(accessor="cable_type_display", order_by="cable_type", verbose_name="Cable Type") 

115 cable_profile = tables.Column( 

116 accessor="cable_profile_display", order_by="cable_profile", verbose_name="Cable Profile" 

117 ) 

118 actions = _mapping_actions_column("cableclassmapping") 

119 

120 class Meta: 

121 model = CableClassMapping 

122 fields = ("cable_class", "cable_type", "cable_profile", "actions") 

123 

124 

125class DeviceTypeMappingTable(tables.Table): 

126 """Table for displaying DeviceTypeMapping objects inline on the profile detail page.""" 

127 

128 source_make = tables.Column() 

129 source_model = tables.Column() 

130 netbox_manufacturer_slug = tables.Column() 

131 netbox_device_type_slug = tables.Column() 

132 actions = _mapping_actions_column("devicetypemapping") 

133 

134 class Meta: 

135 model = DeviceTypeMapping 

136 fields = ( 

137 "source_make", 

138 "source_model", 

139 "netbox_manufacturer_slug", 

140 "netbox_device_type_slug", 

141 "actions", 

142 ) 

143 

144 

145class ImportExecutionTable(NetBoxTable): 

146 """Table for listing Import Execution records in the history view.""" 

147 

148 created = tables.DateTimeColumn(format="Y-m-d H:i") 

149 profile = tables.Column(linkify=lambda record: record.profile.get_absolute_url() if record.profile else None) 

150 input_filename = tables.Column(verbose_name="File") 

151 site_name = tables.Column(verbose_name="Site") 

152 racks_created = tables.Column( 

153 accessor="result_counts", 

154 verbose_name="Racks", 

155 orderable=False, 

156 ) 

157 devices_created = tables.Column( 

158 accessor="result_counts", 

159 verbose_name="Devices", 

160 orderable=False, 

161 ) 

162 errors = tables.TemplateColumn( 

163 template_code=""" 

164 {% with c=record.result_counts %} 

165 {% if c.errors %}<span class="badge bg-danger">{{ c.errors }}</span>{% else %}—{% endif %} 

166 {% endwith %} 

167 """, 

168 verbose_name="Errors", 

169 orderable=False, 

170 ) 

171 

172 class Meta(NetBoxTable.Meta): 

173 model = ImportExecution 

174 fields = ( 

175 "pk", 

176 "created", 

177 "profile", 

178 "input_filename", 

179 "site_name", 

180 "racks_created", 

181 "devices_created", 

182 "errors", 

183 ) 

184 default_columns = ( 

185 "created", 

186 "profile", 

187 "input_filename", 

188 "site_name", 

189 "racks_created", 

190 "devices_created", 

191 "errors", 

192 ) 

193 

194 def render_profile(self, record): 

195 """Return the profile name, or a placeholder for deleted profiles.""" 

196 if record.profile: 

197 return record.profile.name 

198 return "(deleted)" 

199 

200 def render_racks_created(self, value): 

201 """Extract racks_created count from the JSON result_counts dict.""" 

202 value = value or {} 

203 if isinstance(value.get("created"), dict): 

204 return value["created"].get("rack", 0) 

205 return value.get("racks_created", 0) 

206 

207 def render_devices_created(self, value): 

208 """Extract devices_created count from the JSON result_counts dict.""" 

209 value = value or {} 

210 if isinstance(value.get("created"), dict): 

211 return value["created"].get("device", 0) 

212 return value.get("devices_created", 0) 

213 

214 

215class ColumnTransformRuleTable(tables.Table): 

216 """Table for displaying ColumnTransformRule objects inline on the profile detail page.""" 

217 

218 source_column = tables.Column() 

219 pattern = tables.Column() 

220 group_1_target = tables.Column() 

221 group_2_target = tables.Column() 

222 actions = _mapping_actions_column("columntransformrule") 

223 

224 class Meta: 

225 model = ColumnTransformRule 

226 fields = ("source_column", "pattern", "group_1_target", "group_2_target", "actions") 

227 

228 

229class InferenceBackendTable(NetBoxTable): 

230 """List AI backends with the decisions that select one.""" 

231 

232 backend_key = tables.Column(linkify=True) 

233 display_name = tables.Column() 

234 enabled = columns.BooleanColumn() 

235 

236 class Meta(NetBoxTable.Meta): 

237 model = InferenceBackend 

238 fields = ("pk", "backend_key", "display_name", "adapter_type", "api_root", "model", "enabled") 

239 default_columns = ("backend_key", "display_name", "adapter_type", "model", "enabled")