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
« 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)
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"""
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 )
40class ImportProfileTable(NetBoxTable):
41 """Table for listing ImportProfile objects."""
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"))
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 )
83class ColumnMappingTable(tables.Table):
84 """Table for displaying ColumnMapping objects inline on the profile detail page."""
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")
90 class Meta:
91 model = ColumnMapping
92 fields = ("source_column", "target_field", "actions")
95class ClassRoleMappingTable(tables.Table):
96 """Table for displaying ClassRoleMapping objects inline on the profile detail page."""
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")
105 class Meta:
106 model = ClassRoleMapping
107 fields = ("source_class", "creates_rack", "rack_type", "role_slug", "ignore", "actions")
110class CableClassMappingTable(tables.Table):
111 """Display CableClass target decisions inline on the profile detail page."""
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")
120 class Meta:
121 model = CableClassMapping
122 fields = ("cable_class", "cable_type", "cable_profile", "actions")
125class DeviceTypeMappingTable(tables.Table):
126 """Table for displaying DeviceTypeMapping objects inline on the profile detail page."""
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")
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 )
145class ImportExecutionTable(NetBoxTable):
146 """Table for listing Import Execution records in the history view."""
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 )
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 )
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)"
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)
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)
215class ColumnTransformRuleTable(tables.Table):
216 """Table for displaying ColumnTransformRule objects inline on the profile detail page."""
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")
224 class Meta:
225 model = ColumnTransformRule
226 fields = ("source_column", "pattern", "group_1_target", "group_2_target", "actions")
229class InferenceBackendTable(NetBoxTable):
230 """List AI backends with the decisions that select one."""
232 backend_key = tables.Column(linkify=True)
233 display_name = tables.Column()
234 enabled = columns.BooleanColumn()
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")