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
« 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
4__all__ = (
5 'Column',
6 'Layout',
7 'Row',
8 'SimpleLayout',
9)
12#
13# Base classes
14#
16class Layout:
17 """
18 A collection of rows and columns comprising the layout of content within the user interface.
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
41 def __iter__(self):
42 return iter(self.rows)
44 def __repr__(self):
45 return f"Layout({len(self.rows)} rows)"
48class Row:
49 """
50 A collection of columns arranged horizontally.
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
61 def __iter__(self):
62 return iter(self.columns)
64 def __repr__(self):
65 return f"Row({len(self.columns)} columns)"
68class Column:
69 """
70 A collection of panels arranged vertically.
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
88 def __iter__(self):
89 return iter(self.panels)
91 def __repr__(self):
92 return f"Column({len(self.panels)} panels)"
95#
96# Common layouts
97#
99class SimpleLayout(Layout):
100 """
101 A layout with one row of two columns and a second row with one column.
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.
106 ```
107 +-------+-------+
108 | Col 1 | Col 2 |
109 +-------+-------+
110 | Col 3 |
111 +---------------+
112 ```
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)