Introduce SimpleLayout

This commit is contained in:
Jeremy Stretch 2025-11-04 17:14:24 -05:00
parent 59899d0d9a
commit d5cec3723e
3 changed files with 197 additions and 226 deletions

View File

@ -21,7 +21,8 @@ from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
from netbox.object_actions import * from netbox.object_actions import *
from netbox.ui import actions, layout from netbox.ui import actions, layout
from netbox.ui.panels import ( from netbox.ui.panels import (
CommentsPanel, NestedGroupObjectPanel, ObjectsTablePanel, PluginContentPanel, RelatedObjectsPanel, TemplatePanel, CommentsPanel, NestedGroupObjectPanel, ObjectsTablePanel, OrganizationalObjectPanel, RelatedObjectsPanel,
TemplatePanel,
) )
from netbox.views import generic from netbox.views import generic
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
@ -228,22 +229,17 @@ class RegionListView(generic.ObjectListView):
@register_model_view(Region) @register_model_view(Region)
class RegionView(GetRelatedModelsMixin, generic.ObjectView): class RegionView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Region.objects.all() queryset = Region.objects.all()
layout = layout.Layout( layout = layout.SimpleLayout(
layout.Row( left_panels=[
layout.Column(
NestedGroupObjectPanel(), NestedGroupObjectPanel(),
TagsPanel(), TagsPanel(),
CustomFieldsPanel(), CustomFieldsPanel(),
CommentsPanel(), CommentsPanel(),
PluginContentPanel('left_page'), ],
), right_panels=[
layout.Column(
RelatedObjectsPanel(), RelatedObjectsPanel(),
PluginContentPanel('right_page'), ],
), bottom_panels=[
),
layout.Row(
layout.Column(
ObjectsTablePanel( ObjectsTablePanel(
model='dcim.Region', model='dcim.Region',
title=_('Child Regions'), title=_('Child Regions'),
@ -252,9 +248,7 @@ class RegionView(GetRelatedModelsMixin, generic.ObjectView):
actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}), actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
], ],
), ),
PluginContentPanel('full_width_page'), ]
),
),
) )
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
@ -367,22 +361,17 @@ class SiteGroupListView(generic.ObjectListView):
@register_model_view(SiteGroup) @register_model_view(SiteGroup)
class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView): class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
queryset = SiteGroup.objects.all() queryset = SiteGroup.objects.all()
layout = layout.Layout( layout = layout.SimpleLayout(
layout.Row( left_panels=[
layout.Column(
NestedGroupObjectPanel(), NestedGroupObjectPanel(),
TagsPanel(), TagsPanel(),
CustomFieldsPanel(), CustomFieldsPanel(),
CommentsPanel(), CommentsPanel(),
PluginContentPanel('left_page'), ],
), right_panels=[
layout.Column(
RelatedObjectsPanel(), RelatedObjectsPanel(),
PluginContentPanel('right_page'), ],
), bottom_panels=[
),
layout.Row(
layout.Column(
ObjectsTablePanel( ObjectsTablePanel(
model='dcim.SiteGroup', model='dcim.SiteGroup',
title=_('Child Groups'), title=_('Child Groups'),
@ -391,9 +380,7 @@ class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}), actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
], ],
), ),
PluginContentPanel('full_width_page'), ]
),
),
) )
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
@ -524,23 +511,18 @@ class SiteListView(generic.ObjectListView):
@register_model_view(Site) @register_model_view(Site)
class SiteView(GetRelatedModelsMixin, generic.ObjectView): class SiteView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Site.objects.prefetch_related('tenant__group') queryset = Site.objects.prefetch_related('tenant__group')
layout = layout.Layout( layout = layout.SimpleLayout(
layout.Row( left_panels=[
layout.Column(
panels.SitePanel(), panels.SitePanel(),
CustomFieldsPanel(), CustomFieldsPanel(),
TagsPanel(), TagsPanel(),
CommentsPanel(), CommentsPanel(),
PluginContentPanel('left_page'), ],
), right_panels=[
layout.Column(
RelatedObjectsPanel(), RelatedObjectsPanel(),
ImageAttachmentsPanel(), ImageAttachmentsPanel(),
PluginContentPanel('right_page'), ],
), bottom_panels=[
),
layout.Row(
layout.Column(
ObjectsTablePanel( ObjectsTablePanel(
model='dcim.Location', model='dcim.Location',
filters={'site_id': lambda ctx: ctx['object'].pk}, filters={'site_id': lambda ctx: ctx['object'].pk},
@ -560,9 +542,7 @@ class SiteView(GetRelatedModelsMixin, generic.ObjectView):
actions.AddObject('dcim.Device', url_params={'site': lambda ctx: ctx['object'].pk}), actions.AddObject('dcim.Device', url_params={'site': lambda ctx: ctx['object'].pk}),
], ],
), ),
PluginContentPanel('full_width_page'), ]
),
),
) )
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
@ -664,23 +644,18 @@ class LocationListView(generic.ObjectListView):
@register_model_view(Location) @register_model_view(Location)
class LocationView(GetRelatedModelsMixin, generic.ObjectView): class LocationView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Location.objects.all() queryset = Location.objects.all()
layout = layout.Layout( layout = layout.SimpleLayout(
layout.Row( left_panels=[
layout.Column(
panels.LocationPanel(), panels.LocationPanel(),
TagsPanel(), TagsPanel(),
CustomFieldsPanel(), CustomFieldsPanel(),
CommentsPanel(), CommentsPanel(),
PluginContentPanel('left_page'), ],
), right_panels=[
layout.Column(
RelatedObjectsPanel(), RelatedObjectsPanel(),
ImageAttachmentsPanel(), ImageAttachmentsPanel(),
PluginContentPanel('right_page'), ],
), bottom_panels=[
),
layout.Row(
layout.Column(
ObjectsTablePanel( ObjectsTablePanel(
model='dcim.Location', model='dcim.Location',
title=_('Child Locations'), title=_('Child Locations'),
@ -713,9 +688,7 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView):
), ),
], ],
), ),
PluginContentPanel('full_width_page'), ]
),
),
) )
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
@ -817,24 +790,15 @@ class RackRoleListView(generic.ObjectListView):
@register_model_view(RackRole) @register_model_view(RackRole)
class RackRoleView(GetRelatedModelsMixin, generic.ObjectView): class RackRoleView(GetRelatedModelsMixin, generic.ObjectView):
queryset = RackRole.objects.all() queryset = RackRole.objects.all()
layout = layout.Layout( layout = layout.SimpleLayout(
layout.Row( left_panels=[
layout.Column(
panels.RackRolePanel(), panels.RackRolePanel(),
TagsPanel(), TagsPanel(),
PluginContentPanel('left_page'), ],
), right_panels=[
layout.Column(
RelatedObjectsPanel(), RelatedObjectsPanel(),
CustomFieldsPanel(), CustomFieldsPanel(),
PluginContentPanel('right_page'), ],
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
) )
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
@ -903,28 +867,19 @@ class RackTypeListView(generic.ObjectListView):
@register_model_view(RackType) @register_model_view(RackType)
class RackTypeView(GetRelatedModelsMixin, generic.ObjectView): class RackTypeView(GetRelatedModelsMixin, generic.ObjectView):
queryset = RackType.objects.all() queryset = RackType.objects.all()
layout = layout.Layout( layout = layout.SimpleLayout(
layout.Row( left_panels=[
layout.Column(
panels.RackTypePanel(), panels.RackTypePanel(),
panels.RackDimensionsPanel(title=_('Dimensions')), panels.RackDimensionsPanel(title=_('Dimensions')),
TagsPanel(), TagsPanel(),
CommentsPanel(), CommentsPanel(),
PluginContentPanel('left_page'), ],
), right_panels=[
layout.Column(
panels.RackNumberingPanel(title=_('Numbering')), panels.RackNumberingPanel(title=_('Numbering')),
panels.RackWeightPanel(title=_('Weight'), exclude=['total_weight']), panels.RackWeightPanel(title=_('Weight'), exclude=['total_weight']),
CustomFieldsPanel(), CustomFieldsPanel(),
RelatedObjectsPanel(), RelatedObjectsPanel(),
PluginContentPanel('right_page'), ],
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
) )
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
@ -1043,9 +998,8 @@ class RackElevationListView(generic.ObjectListView):
@register_model_view(Rack) @register_model_view(Rack)
class RackView(GetRelatedModelsMixin, generic.ObjectView): class RackView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role') queryset = Rack.objects.prefetch_related('site__region', 'tenant__group', 'location', 'role')
layout = layout.Layout( layout = layout.SimpleLayout(
layout.Row( left_panels=[
layout.Column(
panels.RackPanel(), panels.RackPanel(),
panels.RackDimensionsPanel(title=_('Dimensions')), panels.RackDimensionsPanel(title=_('Dimensions')),
panels.RackNumberingPanel(title=_('Numbering')), panels.RackNumberingPanel(title=_('Numbering')),
@ -1054,19 +1008,11 @@ class RackView(GetRelatedModelsMixin, generic.ObjectView):
TagsPanel(), TagsPanel(),
CommentsPanel(), CommentsPanel(),
ImageAttachmentsPanel(), ImageAttachmentsPanel(),
PluginContentPanel('left_page'), ],
), right_panels=[
layout.Column(
TemplatePanel('dcim/panels/rack_elevations.html'), TemplatePanel('dcim/panels/rack_elevations.html'),
RelatedObjectsPanel(), RelatedObjectsPanel(),
PluginContentPanel('right_page'), ],
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
) )
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
@ -1199,27 +1145,18 @@ class RackReservationListView(generic.ObjectListView):
@register_model_view(RackReservation) @register_model_view(RackReservation)
class RackReservationView(generic.ObjectView): class RackReservationView(generic.ObjectView):
queryset = RackReservation.objects.all() queryset = RackReservation.objects.all()
layout = layout.Layout( layout = layout.SimpleLayout(
layout.Row( left_panels=[
layout.Column( panels.RackPanel(accessor='rack', only=['region', 'site', 'location']),
panels.RackPanel(title=_('Rack'), accessor='rack', only=['region', 'site', 'location']),
CustomFieldsPanel(), CustomFieldsPanel(),
TagsPanel(), TagsPanel(),
CommentsPanel(), CommentsPanel(),
ImageAttachmentsPanel(), ImageAttachmentsPanel(),
PluginContentPanel('left_page'), ],
), right_panels=[
layout.Column(
TemplatePanel('dcim/panels/rack_elevations.html'), TemplatePanel('dcim/panels/rack_elevations.html'),
RelatedObjectsPanel(), RelatedObjectsPanel(),
PluginContentPanel('right_page'), ],
),
),
layout.Row(
layout.Column(
PluginContentPanel('full_width_page'),
),
),
) )
@ -1294,6 +1231,10 @@ class ManufacturerListView(generic.ObjectListView):
@register_model_view(Manufacturer) @register_model_view(Manufacturer)
class ManufacturerView(GetRelatedModelsMixin, generic.ObjectView): class ManufacturerView(GetRelatedModelsMixin, generic.ObjectView):
queryset = Manufacturer.objects.all() queryset = Manufacturer.objects.all()
layout = layout.SimpleLayout(
left_panels=[OrganizationalObjectPanel(), TagsPanel()],
right_panels=[RelatedObjectsPanel(), CustomFieldsPanel()],
)
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
return { return {

View File

@ -1,12 +1,17 @@
from netbox.ui.panels import Panel from netbox.ui.panels import Panel, PluginContentPanel
__all__ = ( __all__ = (
'Column', 'Column',
'Layout', 'Layout',
'Row', 'Row',
'SimpleLayout',
) )
#
# Base classes
#
class Layout: class Layout:
def __init__(self, *rows): def __init__(self, *rows):
@ -32,3 +37,27 @@ class Column:
if not isinstance(panel, Panel): if not isinstance(panel, Panel):
raise TypeError(f"Panel {i} must be an instance of a Panel, not {type(panel)}.") raise TypeError(f"Panel {i} must be an instance of a Panel, not {type(panel)}.")
self.panels = panels self.panels = panels
#
# Standard layouts
#
class SimpleLayout(Layout):
"""
A layout with one row of two columns and a second row with one column. Includes registered plugin content.
"""
def __init__(self, left_panels=None, right_panels=None, bottom_panels=None):
left_panels = left_panels or []
right_panels = right_panels or []
bottom_panels = bottom_panels or []
rows = [
Row(
Column(*left_panels, PluginContentPanel('left_page')),
Column(*right_panels, PluginContentPanel('right_page')),
),
Row(
Column(*bottom_panels, PluginContentPanel('full_width_page'))
)
]
super().__init__(*rows)

View File

@ -149,6 +149,7 @@ class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
return { return {
**super().get_context(context), **super().get_context(context),
'title': self.title or title(obj._meta.verbose_name),
'attrs': [ 'attrs': [
{ {
'label': attr.label or title(name), 'label': attr.label or title(name),