Просмотр исходного кода

Add layouts for DeviceType & ModuleTypeProfile

Jeremy Stretch 3 месяцев назад
Родитель
Сommit
1de41b4964

+ 21 - 0
netbox/dcim/ui/panels.py

@@ -112,3 +112,24 @@ class DeviceManagementPanel(panels.ObjectPanel):
         label=_('Out-of-band IP'),
         label=_('Out-of-band IP'),
         template_name='dcim/device/attrs/ipaddress.html',
         template_name='dcim/device/attrs/ipaddress.html',
     )
     )
+
+
+class DeviceTypePanel(panels.ObjectPanel):
+    manufacturer = attrs.ObjectAttr('manufacturer', label=_('Manufacturer'), linkify=True)
+    model = attrs.TextAttr('model', label=_('Model'))
+    part_number = attrs.TextAttr('part_number', label=_('Part number'))
+    default_platform = attrs.ObjectAttr('default_platform', label=_('Default platform'), linkify=True)
+    description = attrs.TextAttr('description', label=_('Description'))
+    u_height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
+    exclude_from_utilization = attrs.BooleanAttr('exclude_from_utilization', label=_('Exclude from utilization'))
+    full_depth = attrs.BooleanAttr('is_full_depth', label=_('Full depth'))
+    weight = attrs.NumericAttr('weight', unit_accessor='get_weight_unit_display', label=_('Weight'))
+    subdevice_role = attrs.ChoiceAttr('subdevice_role', label=_('Parent/child'))
+    airflow = attrs.ChoiceAttr('airflow', label=_('Airflow'))
+    front_image = attrs.ImageAttr('front_image', label=_('Front image'))
+    rear_image = attrs.ImageAttr('rear_image', label=_('Rear image'))
+
+
+class ModuleTypeProfilePanel(panels.ObjectPanel):
+    name = attrs.TextAttr('name', label=_('Name'))
+    description = attrs.TextAttr('description', label=_('Description'))

+ 41 - 1
netbox/dcim/views.py

@@ -21,7 +21,7 @@ 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, OrganizationalObjectPanel, RelatedObjectsPanel,
+    CommentsPanel, JSONPanel, NestedGroupObjectPanel, ObjectsTablePanel, OrganizationalObjectPanel, RelatedObjectsPanel,
     TemplatePanel,
     TemplatePanel,
 )
 )
 from netbox.views import generic
 from netbox.views import generic
@@ -1308,6 +1308,18 @@ class DeviceTypeListView(generic.ObjectListView):
 @register_model_view(DeviceType)
 @register_model_view(DeviceType)
 class DeviceTypeView(GetRelatedModelsMixin, generic.ObjectView):
 class DeviceTypeView(GetRelatedModelsMixin, generic.ObjectView):
     queryset = DeviceType.objects.all()
     queryset = DeviceType.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.DeviceTypePanel(),
+            TagsPanel(),
+        ],
+        right_panels=[
+            RelatedObjectsPanel(),
+            CustomFieldsPanel(),
+            CommentsPanel(),
+            ImageAttachmentsPanel(),
+        ],
+    )
 
 
     def get_extra_context(self, request, instance):
     def get_extra_context(self, request, instance):
         return {
         return {
@@ -1559,6 +1571,34 @@ class ModuleTypeProfileListView(generic.ObjectListView):
 @register_model_view(ModuleTypeProfile)
 @register_model_view(ModuleTypeProfile)
 class ModuleTypeProfileView(GetRelatedModelsMixin, generic.ObjectView):
 class ModuleTypeProfileView(GetRelatedModelsMixin, generic.ObjectView):
     queryset = ModuleTypeProfile.objects.all()
     queryset = ModuleTypeProfile.objects.all()
+    layout = layout.SimpleLayout(
+        left_panels=[
+            panels.ModuleTypeProfilePanel(),
+            TagsPanel(),
+            CommentsPanel(),
+        ],
+        right_panels=[
+            JSONPanel(field_name='schema', title=_('Schema')),
+            CustomFieldsPanel(),
+        ],
+        bottom_panels=[
+            ObjectsTablePanel(
+                model='dcim.ModuleType',
+                title=_('Module Types'),
+                filters={
+                    'profile_id': lambda ctx: ctx['object'].pk,
+                },
+                actions=[
+                    actions.AddObject(
+                        'dcim.ModuleType',
+                        url_params={
+                            'profile': lambda ctx: ctx['object'].pk,
+                        }
+                    ),
+                ],
+            ),
+        ]
+    )
 
 
 
 
 @register_model_view(ModuleTypeProfile, 'add', detail=False)
 @register_model_view(ModuleTypeProfile, 'add', detail=False)

+ 29 - 1
netbox/netbox/ui/actions.py

@@ -24,11 +24,12 @@ class PanelAction:
         button_class: Bootstrap CSS class for the button
         button_class: Bootstrap CSS class for the button
         button_icon: Name of the button's MDI icon
         button_icon: Name of the button's MDI icon
     """
     """
-    template_name = 'ui/action.html'
+    template_name = 'ui/actions/link.html'
     label = None
     label = None
     button_class = 'primary'
     button_class = 'primary'
     button_icon = None
     button_icon = None
 
 
+    # TODO: Refactor URL parameters to AddObject
     def __init__(self, view_name, view_kwargs=None, url_params=None, permissions=None, label=None):
     def __init__(self, view_name, view_kwargs=None, url_params=None, permissions=None, label=None):
         """
         """
         Initialize a new PanelAction.
         Initialize a new PanelAction.
@@ -114,3 +115,30 @@ class AddObject(PanelAction):
 
 
         # Require "add" permission on the model
         # Require "add" permission on the model
         self.permissions = [get_permission_for_model(model, 'add')]
         self.permissions = [get_permission_for_model(model, 'add')]
+
+
+class CopyContent:
+    """
+    An action to copy the contents of a panel to the clipboard.
+    """
+    template_name = 'ui/actions/copy_content.html'
+    label = _('Copy')
+    button_class = 'primary'
+    button_icon = 'content-copy'
+
+    def __init__(self, target_id):
+        self.target_id = target_id
+
+    def render(self, context):
+        """
+        Render the action as HTML.
+
+        Parameters:
+            context: The template context
+        """
+        return render_to_string(self.template_name, {
+            'target_id': self.target_id,
+            'label': self.label,
+            'button_class': self.button_class,
+            'button_icon': self.button_icon,
+        })

+ 14 - 0
netbox/netbox/ui/attrs.py

@@ -135,6 +135,20 @@ class ColorAttr(Attr):
         })
         })
 
 
 
 
+class ImageAttr(Attr):
+    template_name = 'ui/attrs/image.html'
+
+    def render(self, obj, context=None):
+        context = context or {}
+        value = self._resolve_attr(obj, self.accessor)
+        if value in (None, ''):
+            return self.placeholder
+        return render_to_string(self.template_name, {
+            **context,
+            'value': value,
+        })
+
+
 class ObjectAttr(Attr):
 class ObjectAttr(Attr):
     template_name = 'ui/attrs/object.html'
     template_name = 'ui/attrs/object.html'
 
 

+ 40 - 3
netbox/netbox/ui/panels.py

@@ -5,6 +5,7 @@ from django.template.loader import render_to_string
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
 from netbox.ui import attrs
 from netbox.ui import attrs
+from netbox.ui.actions import CopyContent
 from utilities.querydict import dict_to_querydict
 from utilities.querydict import dict_to_querydict
 from utilities.string import title
 from utilities.string import title
 from utilities.templatetags.plugins import _get_registered_content
 from utilities.templatetags.plugins import _get_registered_content
@@ -12,6 +13,7 @@ from utilities.views import get_viewname
 
 
 __all__ = (
 __all__ = (
     'CommentsPanel',
     'CommentsPanel',
+    'JSONPanel',
     'NestedGroupObjectPanel',
     'NestedGroupObjectPanel',
     'ObjectPanel',
     'ObjectPanel',
     'ObjectsTablePanel',
     'ObjectsTablePanel',
@@ -34,7 +36,7 @@ class Panel(ABC):
     """
     """
     template_name = None
     template_name = None
     title = None
     title = None
-    actions = []
+    actions = None
 
 
     def __init__(self, title=None, actions=None):
     def __init__(self, title=None, actions=None):
         """
         """
@@ -46,8 +48,7 @@ class Panel(ABC):
         """
         """
         if title is not None:
         if title is not None:
             self.title = title
             self.title = title
-        if actions is not None:
-            self.actions = actions
+        self.actions = actions or []
 
 
     def get_context(self, context):
     def get_context(self, context):
         """
         """
@@ -251,6 +252,42 @@ class ObjectsTablePanel(Panel):
         }
         }
 
 
 
 
+class JSONPanel(Panel):
+    """
+    A panel which renders formatted JSON data.
+    """
+    template_name = 'ui/panels/json.html'
+
+    def __init__(self, field_name, copy_button=True, **kwargs):
+        """
+        Instantiate a new JSONPanel.
+
+        Parameters:
+            field_name: The name of the JSON field on the object
+            copy_button: Set to True (default) to include a copy-to-clipboard button
+        """
+        super().__init__(**kwargs)
+        self.field_name = field_name
+
+        if copy_button:
+            self.actions.append(
+                CopyContent(f'panel_{field_name}'),
+            )
+
+    def get_context(self, context):
+        """
+        Return the context data to be used when rendering the panel.
+
+        Parameters:
+            context: The template context
+        """
+        return {
+            **super().get_context(context),
+            'data': getattr(context['object'], self.field_name),
+            'field_name': self.field_name,
+        }
+
+
 class TemplatePanel(Panel):
 class TemplatePanel(Panel):
     """
     """
     A panel which renders content using an HTML template.
     A panel which renders content using an HTML template.

+ 7 - 0
netbox/templates/ui/actions/copy_content.html

@@ -0,0 +1,7 @@
+{% load i18n %}
+<a class="btn btn-ghost-{{ button_class }} btn-sm copy-content" data-clipboard-target="#{{ target_id }}" title="{% trans "Copy to clipboard" %}">
+  {% if button_icon %}
+    <i class="mdi mdi-{{ button_icon }}" aria-hidden="true"></i>
+  {% endif %}
+  {{ label }}
+</a>

+ 1 - 1
netbox/templates/ui/action.html → netbox/templates/ui/actions/link.html

@@ -1,4 +1,4 @@
-<a href="{{ url }}" class="btn btn-ghost-{{ button_class }} btn-sm">
+<a {% if url %}href="{{ url }}" {% endif %}class="btn btn-ghost-{{ button_class }} btn-sm">
   {% if button_icon %}
   {% if button_icon %}
     <i class="mdi mdi-{{ button_icon }}" aria-hidden="true"></i>
     <i class="mdi mdi-{{ button_icon }}" aria-hidden="true"></i>
   {% endif %}
   {% endif %}

+ 1 - 1
netbox/templates/ui/attrs/boolean.html

@@ -1 +1 @@
-{% checkmark object.desc_units %}
+{% checkmark value %}

+ 3 - 0
netbox/templates/ui/attrs/image.html

@@ -0,0 +1,3 @@
+<a href="{{ value.url }}">
+  <img src="{{ value.url }}" alt="{{ value.name }}" class="img-fluid" />
+</a>

+ 5 - 0
netbox/templates/ui/panels/json.html

@@ -0,0 +1,5 @@
+{% extends "ui/panels/_base.html" %}
+
+{% block panel_content %}
+  <pre id="panel_{{ field_name }}">{{ data|json }}</pre>
+{% endblock panel_content %}