Coverage for /home/runner/work/netbox-InterfaceNameRules-plugin/netbox-InterfaceNameRules-plugin/netbox-InterfaceNameRules-plugin/netbox_interface_name_rules/tables.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-22 21:16 +0000

1# SPDX-License-Identifier: Apache-2.0 

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

3import django_tables2 as tables 

4from django.utils.html import format_html 

5from netbox.tables import NetBoxTable, columns 

6 

7from .models import InterfaceNameRule 

8 

9 

10class SpecificityColumn(tables.Column): 

11 """Renders the rule's specificity_score as a badge with its label.""" 

12 

13 def __init__(self, *args, **kwargs): 

14 kwargs.setdefault("verbose_name", "Priority") 

15 kwargs.setdefault("orderable", False) 

16 kwargs.setdefault("attrs", {"th": {"title": "Higher score = higher priority in rule lookup"}}) 

17 super().__init__(*args, **kwargs) 

18 

19 def render(self, value, record): 

20 """Render the specificity score as a coloured badge.""" 

21 label = record.specificity_label 

22 if record.applies_to_device_interfaces: 

23 css = "text-bg-warning" # device-interface rule 

24 elif not record.module_type_is_regex: 

25 css = "text-bg-success" # exact FK — always highest priority 

26 elif record.device_type_id or record.parent_module_type_id: 

27 css = "text-bg-primary" # device- or parent-scoped regex 

28 elif record.platform_id: 

29 css = "text-bg-info" # platform-scoped regex 

30 else: 

31 css = "text-bg-secondary" # unscoped (global) regex 

32 return format_html( 

33 '<span class="badge {}" title="{}" style="font-family:monospace">{}</span>', 

34 css, 

35 label, 

36 value, 

37 ) 

38 

39 def value(self, value, record=None, **kwargs): 

40 """Return the raw numeric specificity score for CSV export.""" 

41 return value 

42 

43 

44class InterfaceNameRuleTable(NetBoxTable): 

45 """Table for displaying InterfaceNameRule objects.""" 

46 

47 pk = columns.ToggleColumn() 

48 enabled = tables.TemplateColumn( 

49 template_code=""" 

50{% if record.enabled %} 

51<button type="button" class="btn btn-sm btn-success toggle-btn" 

52 data-toggle-url="{% url 'plugins:netbox_interface_name_rules:interfacenamerule_toggle' record.pk %}" 

53 data-pk="{{ record.pk }}" 

54 title="Enabled — click to disable" 

55 aria-label="Toggle rule enabled"> 

56 <i class="mdi mdi-check"></i> 

57</button> 

58{% else %} 

59<button type="button" class="btn btn-sm btn-secondary toggle-btn" 

60 data-toggle-url="{% url 'plugins:netbox_interface_name_rules:interfacenamerule_toggle' record.pk %}" 

61 data-pk="{{ record.pk }}" 

62 title="Disabled — click to enable" 

63 aria-label="Toggle rule enabled"> 

64 <i class="mdi mdi-minus"></i> 

65</button> 

66{% endif %} 

67""", 

68 verbose_name="Enabled", 

69 orderable=True, 

70 attrs={"td": {"class": "text-center"}, "th": {"class": "text-center"}}, 

71 ) 

72 specificity_score = SpecificityColumn() 

73 module_type = tables.Column(verbose_name="Module Type", linkify=True) 

74 module_type_pattern = tables.Column(verbose_name="Pattern") 

75 module_type_is_regex = tables.TemplateColumn( 

76 template_code=""" 

77{% if record.applies_to_device_interfaces %} 

78<span class="badge text-bg-info" title="Pattern filters interface names (device-level rule — not a module type selector)"> 

79 <i class="mdi mdi-filter-outline"></i> 

80</span> 

81{% elif record.module_type_is_regex %} 

82<span class="badge text-bg-success" title="Module type is matched by regex pattern"> 

83 <i class="mdi mdi-check"></i> 

84</span> 

85{% else %} 

86<span class="text-muted" title="Exact module type match (FK)">—</span> 

87{% endif %} 

88""", 

89 verbose_name="Regex", 

90 orderable=True, 

91 attrs={"td": {"class": "text-center"}, "th": {"class": "text-center"}}, 

92 ) 

93 parent_module_type = tables.Column(verbose_name="Parent Module Type", linkify=True) 

94 device_type = tables.Column(verbose_name="Device Type", linkify=True) 

95 platform = tables.Column(verbose_name="Platform", linkify=True) 

96 applies_to_device_interfaces = columns.BooleanColumn(verbose_name="Device Ifaces") 

97 name_template = tables.Column(verbose_name="Name Template") 

98 parent_name_template = tables.Column(verbose_name="Parent Name Template") 

99 breakout_mode = columns.ChoiceFieldColumn(verbose_name="Breakout Mode") 

100 channel_count = tables.Column(verbose_name="Channels") 

101 channel_start = tables.Column(verbose_name="Channel Start") 

102 description = tables.Column(verbose_name="Description", linkify=False) 

103 actions = columns.ActionsColumn( 

104 actions=("edit", "delete"), 

105 extra_buttons=( 

106 "<a href=\"{% url 'plugins:netbox_interface_name_rules:interfacenamerule_test' %}?rule_id={{ record.pk }}\"" 

107 ' class="btn btn-sm btn-outline-secondary" title="Test in Build Rule" aria-label="Test in Build Rule">' 

108 '<i class="mdi mdi-flask-outline"></i></a>' 

109 "<a href=\"{% url 'plugins:netbox_interface_name_rules:interfacenamerule_duplicate' record.pk %}\"" 

110 ' class="btn btn-sm btn-success" title="Duplicate" aria-label="Duplicate">' 

111 '<i class="mdi mdi-content-copy"></i></a>' 

112 ), 

113 ) 

114 

115 class Meta: 

116 model = InterfaceNameRule 

117 fields = ( 

118 "pk", 

119 "id", 

120 "enabled", 

121 "specificity_score", 

122 "module_type", 

123 "module_type_pattern", 

124 "module_type_is_regex", 

125 "parent_module_type", 

126 "device_type", 

127 "platform", 

128 "applies_to_device_interfaces", 

129 "name_template", 

130 "parent_name_template", 

131 "breakout_mode", 

132 "channel_count", 

133 "channel_start", 

134 "description", 

135 "actions", 

136 ) 

137 default_columns = ( 

138 "pk", 

139 "id", 

140 "enabled", 

141 "specificity_score", 

142 "module_type", 

143 "module_type_pattern", 

144 "module_type_is_regex", 

145 "parent_module_type", 

146 "device_type", 

147 "platform", 

148 "applies_to_device_interfaces", 

149 "name_template", 

150 "breakout_mode", 

151 "channel_count", 

152 "description", 

153 "actions", 

154 ) 

155 row_attrs = {"class": lambda record: "" if record.enabled else "text-muted opacity-50"} 

156 attrs = {"class": "table table-hover table-headings table-striped"}