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

46 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-02 15:19 +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 channel_count = tables.Column(verbose_name="Channels") 

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

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

101 actions = columns.ActionsColumn( 

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

103 extra_buttons=( 

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

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

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

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

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

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

110 ), 

111 ) 

112 

113 class Meta: 

114 model = InterfaceNameRule 

115 fields = ( 

116 "pk", 

117 "id", 

118 "enabled", 

119 "specificity_score", 

120 "module_type", 

121 "module_type_pattern", 

122 "module_type_is_regex", 

123 "parent_module_type", 

124 "device_type", 

125 "platform", 

126 "applies_to_device_interfaces", 

127 "name_template", 

128 "channel_count", 

129 "channel_start", 

130 "description", 

131 "actions", 

132 ) 

133 default_columns = ( 

134 "pk", 

135 "id", 

136 "enabled", 

137 "specificity_score", 

138 "module_type", 

139 "module_type_pattern", 

140 "module_type_is_regex", 

141 "parent_module_type", 

142 "device_type", 

143 "platform", 

144 "applies_to_device_interfaces", 

145 "name_template", 

146 "channel_count", 

147 "description", 

148 "actions", 

149 ) 

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

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