Coverage for netbox/ui/layout.py: 69%

52 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 18:35 +0000

1from netbox.ui.breadcrumbs import Breadcrumb 

2from netbox.ui.panels import Panel, PluginContentPanel 

3 

4__all__ = ( 

5 'Column', 

6 'Layout', 

7 'Row', 

8 'SimpleLayout', 

9) 

10 

11 

12# 

13# Base classes 

14# 

15 

16class Layout: 

17 """ 

18 A collection of rows and columns comprising the layout of content within the user interface. 

19 

20 Parameters: 

21 *rows: One or more Row instances 

22 breadcrumbs: An ordered iterable of `Breadcrumb` instances rendered at the top of the page, after the 

23 default breadcrumb linking to the object's list view. The trail defined on a model's base view is 

24 shared across all of that model's object views (e.g. its detail view and every peer/tabbed view). 

25 root_breadcrumb: Whether to prepend the default root breadcrumb (a link to the object's list view) to 

26 the trail. Set False for views whose list view is not an appropriate root (e.g. a user's personal 

27 token list), allowing the layout's own breadcrumbs to stand in its place. 

28 """ 

29 def __init__(self, *rows, breadcrumbs=None, root_breadcrumb=True): 

30 for i, row in enumerate(rows): 

31 if not isinstance(row, Row): 31 ↛ 32line 31 didn't jump to line 32 because the condition on line 31 was never true

32 raise TypeError(f"Row {i} must be a Row instance, not {type(row)}.") 

33 breadcrumbs = breadcrumbs or [] 

34 for i, breadcrumb in enumerate(breadcrumbs): 

35 if not isinstance(breadcrumb, Breadcrumb): 35 ↛ 36line 35 didn't jump to line 36 because the condition on line 35 was never true

36 raise TypeError(f"Breadcrumb {i} must be a Breadcrumb instance, not {type(breadcrumb)}.") 

37 self.rows = rows 

38 self.breadcrumbs = breadcrumbs 

39 self.root_breadcrumb = root_breadcrumb 

40 

41 def __iter__(self): 

42 return iter(self.rows) 

43 

44 def __repr__(self): 

45 return f"Layout({len(self.rows)} rows)" 

46 

47 

48class Row: 

49 """ 

50 A collection of columns arranged horizontally. 

51 

52 Parameters: 

53 *columns: One or more Column instances 

54 """ 

55 def __init__(self, *columns): 

56 for i, column in enumerate(columns): 

57 if not isinstance(column, Column): 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true

58 raise TypeError(f"Column {i} must be a Column instance, not {type(column)}.") 

59 self.columns = columns 

60 

61 def __iter__(self): 

62 return iter(self.columns) 

63 

64 def __repr__(self): 

65 return f"Row({len(self.columns)} columns)" 

66 

67 

68class Column: 

69 """ 

70 A collection of panels arranged vertically. 

71 

72 Parameters: 

73 *panels: One or more Panel instances 

74 width: Bootstrap grid column width (1-12). If unset, the column will expand to fill available space. 

75 """ 

76 def __init__(self, *panels, width=None): 

77 for i, panel in enumerate(panels): 

78 if not isinstance(panel, Panel): 78 ↛ 79line 78 didn't jump to line 79 because the condition on line 78 was never true

79 raise TypeError(f"Panel {i} must be an instance of a Panel, not {type(panel)}.") 

80 if width is not None: 80 ↛ 81line 80 didn't jump to line 81 because the condition on line 80 was never true

81 if type(width) is not int: 

82 raise ValueError(f"Column width must be an integer, not {type(width)}") 

83 if width not in range(1, 13): 

84 raise ValueError(f"Column width must be an integer between 1 and 12 (got {width}).") 

85 self.panels = panels 

86 self.width = width 

87 

88 def __iter__(self): 

89 return iter(self.panels) 

90 

91 def __repr__(self): 

92 return f"Column({len(self.panels)} panels)" 

93 

94 

95# 

96# Common layouts 

97# 

98 

99class SimpleLayout(Layout): 

100 """ 

101 A layout with one row of two columns and a second row with one column. 

102 

103 Plugin content registered for `left_page`, `right_page`, or `full_width_page` is included automatically. Most object 

104 views in NetBox utilize this layout. 

105 

106 ``` 

107 +-------+-------+ 

108 | Col 1 | Col 2 | 

109 +-------+-------+ 

110 | Col 3 | 

111 +---------------+ 

112 ``` 

113 

114 Parameters: 

115 left_panels: Panel instances to be rendered in the top lefthand column 

116 right_panels: Panel instances to be rendered in the top righthand column 

117 bottom_panels: Panel instances to be rendered in the bottom row 

118 breadcrumbs: Breadcrumb instances rendered at the top of the page (see Layout) 

119 root_breadcrumb: Whether to prepend the default root breadcrumb (see Layout) 

120 """ 

121 def __init__(self, left_panels=None, right_panels=None, bottom_panels=None, breadcrumbs=None, 

122 root_breadcrumb=True): 

123 left_panels = left_panels or [] 

124 right_panels = right_panels or [] 

125 bottom_panels = bottom_panels or [] 

126 rows = [ 

127 Row( 

128 Column(*left_panels, PluginContentPanel('left_page')), 

129 Column(*right_panels, PluginContentPanel('right_page')), 

130 ), 

131 Row( 

132 Column(*bottom_panels, PluginContentPanel('full_width_page')) 

133 ) 

134 ] 

135 super().__init__(*rows, breadcrumbs=breadcrumbs, root_breadcrumb=root_breadcrumb)