From 04244e188fdd47bca45ff4d58909213e902a4c82 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 5 Mar 2026 08:43:46 -0500 Subject: [PATCH] #20923: Migrate DCIM view templates (#21372) * Permit passing template_name to Panel instance * Define UI layout for ModuleType view * Define UI layout for DeviceRole view * Define UI layout for Platform view * Define UI layout for Module view * Misc cleanup * Linkify module bay --- netbox/dcim/ui/panels.py | 31 +++++++ netbox/dcim/views.py | 74 +++++++++++++++ netbox/netbox/ui/panels.py | 10 +- netbox/templates/dcim/devicerole.html | 64 ------------- netbox/templates/dcim/module.html | 72 --------------- netbox/templates/dcim/moduletype.html | 92 +------------------ netbox/templates/dcim/panels/module_type.html | 27 ++++++ .../dcim/panels/module_type_attributes.html | 29 ++++++ netbox/templates/dcim/platform.html | 58 ------------ 9 files changed, 168 insertions(+), 289 deletions(-) create mode 100644 netbox/templates/dcim/panels/module_type.html create mode 100644 netbox/templates/dcim/panels/module_type_attributes.html diff --git a/netbox/dcim/ui/panels.py b/netbox/dcim/ui/panels.py index 67f7d0f32..fa7ad848a 100644 --- a/netbox/dcim/ui/panels.py +++ b/netbox/dcim/ui/panels.py @@ -137,6 +137,12 @@ class DeviceDimensionsPanel(panels.ObjectAttributesPanel): total_weight = attrs.TemplatedAttr('total_weight', template_name='dcim/device/attrs/total_weight.html') +class DeviceRolePanel(panels.NestedGroupObjectPanel): + color = attrs.ColorAttr('color') + vm_role = attrs.BooleanAttr('vm_role', label=_('VM role')) + config_template = attrs.RelatedObjectAttr('config_template', linkify=True) + + class DeviceTypePanel(panels.ObjectAttributesPanel): manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True) model = attrs.TextAttr('model') @@ -153,11 +159,36 @@ class DeviceTypePanel(panels.ObjectAttributesPanel): rear_image = attrs.ImageAttr('rear_image') +class ModulePanel(panels.ObjectAttributesPanel): + device = attrs.RelatedObjectAttr('device', linkify=True) + device_type = attrs.RelatedObjectAttr('device.device_type', linkify=True, grouped_by='manufacturer') + module_bay = attrs.NestedObjectAttr('module_bay', linkify=True) + status = attrs.ChoiceAttr('status') + description = attrs.TextAttr('description') + serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True) + asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True) + + class ModuleTypeProfilePanel(panels.ObjectAttributesPanel): name = attrs.TextAttr('name') description = attrs.TextAttr('description') +class ModuleTypePanel(panels.ObjectAttributesPanel): + profile = attrs.RelatedObjectAttr('profile', linkify=True) + manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True) + model = attrs.TextAttr('model', label=_('Model name')) + part_number = attrs.TextAttr('part_number') + description = attrs.TextAttr('description') + airflow = attrs.ChoiceAttr('airflow') + weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display') + + +class PlatformPanel(panels.NestedGroupObjectPanel): + manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True) + config_template = attrs.RelatedObjectAttr('config_template', linkify=True) + + class VirtualChassisMembersPanel(panels.ObjectPanel): """ A panel which lists all members of a virtual chassis. diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 617df8d2c..a2b0b6f31 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -25,6 +25,7 @@ from netbox.ui.panels import ( NestedGroupObjectPanel, ObjectsTablePanel, OrganizationalObjectPanel, + Panel, RelatedObjectsPanel, TemplatePanel, ) @@ -1667,6 +1668,22 @@ class ModuleTypeListView(generic.ObjectListView): @register_model_view(ModuleType) class ModuleTypeView(GetRelatedModelsMixin, generic.ObjectView): queryset = ModuleType.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.ModuleTypePanel(), + TagsPanel(), + CommentsPanel(), + ], + right_panels=[ + Panel( + title=_('Attributes'), + template_name='dcim/panels/module_type_attributes.html', + ), + RelatedObjectsPanel(), + CustomFieldsPanel(), + ImageAttachmentsPanel(), + ], + ) def get_extra_context(self, request, instance): return { @@ -2306,6 +2323,27 @@ class DeviceRoleListView(generic.ObjectListView): @register_model_view(DeviceRole) class DeviceRoleView(GetRelatedModelsMixin, generic.ObjectView): queryset = DeviceRole.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.DeviceRolePanel(), + TagsPanel(), + ], + right_panels=[ + RelatedObjectsPanel(), + CustomFieldsPanel(), + CommentsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + model='dcim.DeviceRole', + title=_('Child Device Roles'), + filters={'parent_id': lambda ctx: ctx['object'].pk}, + actions=[ + actions.AddObject('dcim.DeviceRole', url_params={'parent': lambda ctx: ctx['object'].pk}), + ], + ), + ] + ) def get_extra_context(self, request, instance): return { @@ -2385,6 +2423,27 @@ class PlatformListView(generic.ObjectListView): @register_model_view(Platform) class PlatformView(GetRelatedModelsMixin, generic.ObjectView): queryset = Platform.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.PlatformPanel(), + TagsPanel(), + ], + right_panels=[ + RelatedObjectsPanel(), + CustomFieldsPanel(), + CommentsPanel(), + ], + bottom_panels=[ + ObjectsTablePanel( + model='dcim.Platform', + title=_('Child Platforms'), + filters={'parent_id': lambda ctx: ctx['object'].pk}, + actions=[ + actions.AddObject('dcim.Platform', url_params={'parent': lambda ctx: ctx['object'].pk}), + ], + ), + ] + ) def get_extra_context(self, request, instance): return { @@ -2778,6 +2837,21 @@ class ModuleListView(generic.ObjectListView): @register_model_view(Module) class ModuleView(GetRelatedModelsMixin, generic.ObjectView): queryset = Module.objects.all() + layout = layout.SimpleLayout( + left_panels=[ + panels.ModulePanel(), + TagsPanel(), + CommentsPanel(), + ], + right_panels=[ + Panel( + title=_('Module Type'), + template_name='dcim/panels/module_type.html', + ), + RelatedObjectsPanel(), + CustomFieldsPanel(), + ], + ) def get_extra_context(self, request, instance): return { diff --git a/netbox/netbox/ui/panels.py b/netbox/netbox/ui/panels.py index 55e36b704..9f4be94db 100644 --- a/netbox/netbox/ui/panels.py +++ b/netbox/netbox/ui/panels.py @@ -44,15 +44,18 @@ class Panel: Parameters: title (str): The human-friendly title of the panel actions (list): An iterable of PanelActions to include in the panel header + template_name (str): Overrides the default template name, if defined """ template_name = None title = None actions = None - def __init__(self, title=None, actions=None): + def __init__(self, title=None, actions=None, template_name=None): if title is not None: self.title = title self.actions = actions or self.actions or [] + if template_name is not None: + self.template_name = template_name def get_context(self, context): """ @@ -317,9 +320,8 @@ class TemplatePanel(Panel): Parameters: template_name (str): The name of the template to render """ - def __init__(self, template_name, **kwargs): - super().__init__(**kwargs) - self.template_name = template_name + def __init__(self, template_name): + super().__init__(template_name=template_name) def render(self, context): # Pass the entire context to the template diff --git a/netbox/templates/dcim/devicerole.html b/netbox/templates/dcim/devicerole.html index 5cce95d23..72fdbd3ed 100644 --- a/netbox/templates/dcim/devicerole.html +++ b/netbox/templates/dcim/devicerole.html @@ -15,67 +15,3 @@ {% endif %} {% endblock extra_controls %} - -{% block content %} -
-
-
-

{% trans "Device Role" %}

- - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Parent" %}{{ object.parent|linkify|placeholder }}
{% trans "Color" %} -   -
{% trans "VM Role" %}{% checkmark object.vm_role %}
{% trans "Config Template" %}{{ object.config_template|linkify|placeholder }}
-
- {% include 'inc/panels/tags.html' %} - {% plugin_left_page object %} -
-
- {% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_right_page object %} -
-
-
-
-
-

- {% trans "Child Device Roles" %} - {% if perms.dcim.add_devicerole %} - - {% endif %} -

- {% htmx_table 'dcim:devicerole_list' parent_id=object.pk %} -
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/module.html b/netbox/templates/dcim/module.html index f9aecb3f0..a39eccf4d 100644 --- a/netbox/templates/dcim/module.html +++ b/netbox/templates/dcim/module.html @@ -46,75 +46,3 @@ {% endif %} {% endblock %} - -{% block content %} -
-
-
-

{% trans "Module" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Device" %}{{ object.device|linkify }}
{% trans "Device Type" %}{{ object.device.device_type|linkify }}
{% trans "Module Bay" %}{% nested_tree object.module_bay %}
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Serial Number" %}{{ object.serial|placeholder }}
{% trans "Asset Tag" %}{{ object.asset_tag|placeholder }}
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_left_page object %} -
-
-
-

{% trans "Module Type" %}

- - - - - - - - - - {% for k, v in object.module_type.attributes.items %} - - - - - {% endfor %} -
{% trans "Manufacturer" %}{{ object.module_type.manufacturer|linkify }}
{% trans "Model" %}{{ object.module_type|linkify }}
{{ k }}{{ v|placeholder }}
-
- {% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
-{% endblock %} diff --git a/netbox/templates/dcim/moduletype.html b/netbox/templates/dcim/moduletype.html index 691ff1636..d782c1c27 100644 --- a/netbox/templates/dcim/moduletype.html +++ b/netbox/templates/dcim/moduletype.html @@ -1,7 +1,4 @@ {% extends 'generic/object.html' %} -{% load buttons %} -{% load helpers %} -{% load plugins %} {% load i18n %} {% block title %}{{ object.manufacturer }} {{ object.model }}{% endblock %} @@ -14,92 +11,5 @@ {% endblock %} {% block extra_controls %} - {% include 'dcim/inc/moduletype_buttons.html' %} -{% endblock %} - -{% block content %} -
-
-
-

{% trans "Module Type" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Profile" %}{{ object.profile|linkify|placeholder }}
{% trans "Manufacturer" %}{{ object.manufacturer|linkify }}
{% trans "Model Name" %}{{ object.model }}
{% trans "Part Number" %}{{ object.part_number|placeholder }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Airflow" %}{{ object.get_airflow_display|placeholder }}
{% trans "Weight" %} - {% if object.weight %} - {{ object.weight|floatformat }} {{ object.get_weight_unit_display }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
-
- {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_left_page object %} -
-
-
-

{% trans "Attributes" %}

- {% if not object.profile %} -
- {% trans "No profile assigned" %} -
- {% elif object.attributes %} - - {% for k, v in object.attributes.items %} - - - - - {% endfor %} -
{{ k }} - {% if v is True or v is False %} - {% checkmark v %} - {% else %} - {{ v|placeholder }} - {% endif %} -
- {% else %} -
- {% trans "None" %} -
- {% endif %} -
- {% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/image_attachments.html' %} - {% plugin_right_page object %} -
-
-
-
- {% plugin_full_width_page object %} -
-
+ {% include 'dcim/inc/moduletype_buttons.html' %} {% endblock %} diff --git a/netbox/templates/dcim/panels/module_type.html b/netbox/templates/dcim/panels/module_type.html new file mode 100644 index 000000000..7fb90470b --- /dev/null +++ b/netbox/templates/dcim/panels/module_type.html @@ -0,0 +1,27 @@ +{% extends "ui/panels/_base.html" %} +{% load helpers i18n %} + +{% block panel_content %} + + + + + + + + + + {% for k, v in object.module_type.attributes.items %} + + + + + {% endfor %} +
{% trans "Manufacturer" %}{{ object.module_type.manufacturer|linkify }}
{% trans "Model" %}{{ object.module_type|linkify }}
{{ k }} + {% if v is True or v is False %} + {% checkmark v %} + {% else %} + {{ v|placeholder }} + {% endif %} +
+{% endblock panel_content %} diff --git a/netbox/templates/dcim/panels/module_type_attributes.html b/netbox/templates/dcim/panels/module_type_attributes.html new file mode 100644 index 000000000..85907686d --- /dev/null +++ b/netbox/templates/dcim/panels/module_type_attributes.html @@ -0,0 +1,29 @@ +{% extends "ui/panels/_base.html" %} +{% load helpers i18n %} + +{% block panel_content %} + {% if not object.profile %} +
+ {% trans "No profile assigned" %} +
+ {% elif object.attributes %} + + {% for k, v in object.attributes.items %} + + + + + {% endfor %} +
{{ k }} + {% if v is True or v is False %} + {% checkmark v %} + {% else %} + {{ v|placeholder }} + {% endif %} +
+ {% else %} +
+ {% trans "None" %} +
+ {% endif %} +{% endblock panel_content %} diff --git a/netbox/templates/dcim/platform.html b/netbox/templates/dcim/platform.html index 4becc042b..26179d4a2 100644 --- a/netbox/templates/dcim/platform.html +++ b/netbox/templates/dcim/platform.html @@ -18,61 +18,3 @@ {% endif %} {% endblock extra_controls %} - -{% block content %} -
-
-
-

{% trans "Platform" %}

- - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Parent" %}{{ object.parent|linkify|placeholder }}
{% trans "Manufacturer" %}{{ object.manufacturer|linkify|placeholder }}
{% trans "Config Template" %}{{ object.config_template|linkify|placeholder }}
-
- {% include 'inc/panels/tags.html' %} - {% plugin_left_page object %} -
-
- {% include 'inc/panels/related_objects.html' %} - {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/comments.html' %} - {% plugin_right_page object %} -
-
-
-
-
-

- {% trans "Child Platforms" %} - {% if perms.dcim.add_platform %} - - {% endif %} -

- {% htmx_table 'dcim:platform_list' parent_id=object.pk %} -
- {% plugin_full_width_page object %} -
-
-{% endblock %}