mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-11 02:49:35 -06:00
Add plugin dev docs for UI components
This commit is contained in:
parent
a024012abd
commit
917280d1d3
148
docs/plugins/development/ui-components.md
Normal file
148
docs/plugins/development/ui-components.md
Normal file
@ -0,0 +1,148 @@
|
||||
# UI Components
|
||||
|
||||
!!! note "New in NetBox v4.5"
|
||||
All UI components described here were introduced in NetBox v4.5. Be sure to set the minimum NetBox version to 4.5.0 for your plugin before incorporating any of these resources.
|
||||
|
||||
!!! danger "Beta Feature"
|
||||
UI components are considered a beta feature, and are still under active development. Please be aware that the API for resources on this page is subject to change in future releases.
|
||||
|
||||
To simply the process of designing your plugin's user interface, and to encourage a consistent look and feel throughout the entire application, NetBox provides a set of components that enable programmatic UI design. These make it possible to declare complex page layouts with little or no custom HTML.
|
||||
|
||||
## Page Layout
|
||||
|
||||
A layout defines the general arrangement of content on a page into rows and columns. The layout is defined under the [view](./views.md) and declares a set of rows, each of which may have one or more columns. Below is an example layout.
|
||||
|
||||
```
|
||||
+-------+-------+-------+
|
||||
| Col 1 | Col 2 | Col 3 |
|
||||
+-------+-------+-------+
|
||||
| Col 4 |
|
||||
+-----------+-----------+
|
||||
| Col 5 | Col 6 |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
The above layout can be achieved with the following declaration under a view:
|
||||
|
||||
```python
|
||||
from netbox.ui import layout
|
||||
from netbox.views import generic
|
||||
|
||||
class MyView(generic.ObjectView):
|
||||
layout = layout.Layout(
|
||||
layout.Row(
|
||||
layout.Column(),
|
||||
layout.Column(),
|
||||
layout.Column(),
|
||||
),
|
||||
layout.Row(
|
||||
layout.Column(),
|
||||
),
|
||||
layout.Row(
|
||||
layout.Column(),
|
||||
layout.Column(),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
!!! note
|
||||
Currently, layouts are supported only for subclasses of [`generic.ObjectView`](./views.md#netbox.views.generic.ObjectView).
|
||||
|
||||
::: netbox.ui.layout.Layout
|
||||
|
||||
::: netbox.ui.layout.SimpleLayout
|
||||
|
||||
::: netbox.ui.layout.Row
|
||||
|
||||
::: netbox.ui.layout.Column
|
||||
|
||||
## Panels
|
||||
|
||||
Within each column, related blocks of content are arranged into panels. Each panel has a title and may have a set of associated actions, but the content within is otherwise arbitrary.
|
||||
|
||||
Plugins can define their own panels by inheriting from the base class `netbox.ui.panels.Panel`. Override the `get_context()` method to pass additional context to your custom panel template. An example is provided below.
|
||||
|
||||
```python
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from netbox.ui.panels import Panel
|
||||
|
||||
class RecentChangesPanel(Panel):
|
||||
template_name = 'my_plugin/panels/recent_changes.html'
|
||||
title = _('Recent Changes')
|
||||
|
||||
def get_context(self, context):
|
||||
return {
|
||||
**super().get_context(context),
|
||||
'changes': get_changes()[:10],
|
||||
}
|
||||
```
|
||||
|
||||
NetBox also includes a set of panels suite for specific uses, such as display object details or embedding a table of related objects. These are listed below.
|
||||
|
||||
::: netbox.ui.panels.Panel
|
||||
|
||||
::: netbox.ui.panels.ObjectPanel
|
||||
|
||||
::: netbox.ui.panels.ObjectAttributesPanel
|
||||
|
||||
#### Object Attributes
|
||||
|
||||
The following classes are available to represent object attributes within an ObjectAttributesPanel. Additionally, plugins can subclass `netbox.ui.attrs.ObjectAttribute` to create custom classes.
|
||||
|
||||
| Class | Description |
|
||||
|--------------------------------------|--------------------------------------------------|
|
||||
| `netbox.ui.attrs.AddressAttr` | A physical or mailing address. |
|
||||
| `netbox.ui.attrs.BooleanAttr` | A boolean value |
|
||||
| `netbox.ui.attrs.ColorAttr` | A color expressed in RGB |
|
||||
| `netbox.ui.attrs.ChoiceAttr` | A selection from a set of choices |
|
||||
| `netbox.ui.attrs.GPSCoordinatesAttr` | GPS coordinates (latitude and longitude) |
|
||||
| `netbox.ui.attrs.ImageAttr` | An attached image (displays the image) |
|
||||
| `netbox.ui.attrs.NestedObjectAttr` | A related nested object |
|
||||
| `netbox.ui.attrs.NumericAttr` | An integer or float value |
|
||||
| `netbox.ui.attrs.RelatedObjectAttr` | A related object |
|
||||
| `netbox.ui.attrs.TemplatedAttr` | Renders an attribute using a custom template |
|
||||
| `netbox.ui.attrs.TextAttr` | A string (text) value |
|
||||
| `netbox.ui.attrs.TimezoneAttr` | A timezone with annotated offset |
|
||||
| `netbox.ui.attrs.UtilizationAttr` | A numeric value expressed as a utilization graph |
|
||||
|
||||
::: netbox.ui.panels.OrganizationalObjectPanel
|
||||
|
||||
::: netbox.ui.panels.NestedGroupObjectPanel
|
||||
|
||||
::: netbox.ui.panels.CommentsPanel
|
||||
|
||||
::: netbox.ui.panels.JSONPanel
|
||||
|
||||
::: netbox.ui.panels.RelatedObjectsPanel
|
||||
|
||||
::: netbox.ui.panels.ObjectsTablePanel
|
||||
|
||||
::: netbox.ui.panels.TemplatePanel
|
||||
|
||||
::: netbox.ui.panels.PluginContentPanel
|
||||
|
||||
## Panel Actions
|
||||
|
||||
Each panel may have actions associated with it. These render as links or buttons within the panel header, opposite the panel's title. For example, a common use case is to include an "Add" action on a panel which displays a list of objects. Below is an example of this.
|
||||
|
||||
```python
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from netbox.ui import actions, panels
|
||||
|
||||
panels.ObjectsTablePanel(
|
||||
model='dcim.Region',
|
||||
title=_('Child Regions'),
|
||||
filters={'parent_id': lambda ctx: ctx['object'].pk},
|
||||
actions=[
|
||||
actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
|
||||
],
|
||||
),
|
||||
```
|
||||
|
||||
::: netbox.ui.actions.PanelAction
|
||||
|
||||
::: netbox.ui.actions.LinkAction
|
||||
|
||||
::: netbox.ui.actions.AddObject
|
||||
|
||||
::: netbox.ui.actions.CopyContent
|
||||
@ -143,6 +143,7 @@ nav:
|
||||
- Getting Started: 'plugins/development/index.md'
|
||||
- Models: 'plugins/development/models.md'
|
||||
- Views: 'plugins/development/views.md'
|
||||
- UI Components: 'plugins/development/ui-components.md'
|
||||
- Navigation: 'plugins/development/navigation.md'
|
||||
- Templates: 'plugins/development/templates.md'
|
||||
- Tables: 'plugins/development/tables.md'
|
||||
|
||||
@ -11,6 +11,7 @@ from utilities.views import get_viewname
|
||||
__all__ = (
|
||||
'AddObject',
|
||||
'CopyContent',
|
||||
'LinkAction',
|
||||
'PanelAction',
|
||||
)
|
||||
|
||||
@ -20,34 +21,28 @@ class PanelAction:
|
||||
A link (typically a button) within a panel to perform some associated action, such as adding an object.
|
||||
|
||||
Attributes:
|
||||
template_name: The name of the template to render
|
||||
label: The default human-friendly button text
|
||||
button_class: Bootstrap CSS class for the button
|
||||
button_icon: Name of the button's MDI icon
|
||||
template_name (str): The name of the template to render
|
||||
|
||||
Parameters:
|
||||
label (str): The human-friendly button text
|
||||
permissions (list): An iterable of permissions required to display the action
|
||||
button_class (str): Bootstrap CSS class for the button
|
||||
button_icon (str): Name of the button's MDI icon
|
||||
"""
|
||||
template_name = None
|
||||
label = None
|
||||
button_class = 'primary'
|
||||
button_icon = None
|
||||
|
||||
def __init__(self, label=None, permissions=None):
|
||||
"""
|
||||
Initialize a new PanelAction.
|
||||
|
||||
Parameters:
|
||||
label: The human-friendly button text
|
||||
permissions: A list of permissions required to display the action
|
||||
"""
|
||||
if label is not None:
|
||||
self.label = label
|
||||
def __init__(self, label, permissions=None, button_class='primary', button_icon=None):
|
||||
self.label = label
|
||||
self.permissions = permissions
|
||||
self.button_class = button_class
|
||||
self.button_icon = button_icon
|
||||
|
||||
def get_context(self, context):
|
||||
"""
|
||||
Return the template context used to render the action element.
|
||||
|
||||
Parameters:
|
||||
context: The template context
|
||||
context (dict): The template context
|
||||
"""
|
||||
return {
|
||||
'label': self.label,
|
||||
@ -60,7 +55,7 @@ class PanelAction:
|
||||
Render the action as HTML.
|
||||
|
||||
Parameters:
|
||||
context: The template context
|
||||
context (dict): The template context
|
||||
"""
|
||||
# Enforce permissions
|
||||
user = context['request'].user
|
||||
@ -74,26 +69,16 @@ class LinkAction(PanelAction):
|
||||
"""
|
||||
A hyperlink (typically a button) within a panel to perform some associated action, such as adding an object.
|
||||
|
||||
Attributes:
|
||||
label: The default human-friendly button text
|
||||
button_class: Bootstrap CSS class for the button
|
||||
button_icon: Name of the button's MDI icon
|
||||
Parameters:
|
||||
view_name (str): Name of the view to which the action will link
|
||||
view_kwargs (dict): Additional keyword arguments to pass to `reverse()` when resolving the URL
|
||||
url_params (dict): A dictionary of arbitrary URL parameters to append to the action's URL. If the value of a key
|
||||
is a callable, it will be passed the current template context.
|
||||
"""
|
||||
template_name = 'ui/actions/link.html'
|
||||
|
||||
def __init__(self, view_name, view_kwargs=None, url_params=None, **kwargs):
|
||||
"""
|
||||
Initialize a new PanelAction.
|
||||
|
||||
Parameters:
|
||||
view_name: Name of the view to which the action will link
|
||||
view_kwargs: Additional keyword arguments to pass to the view when resolving its URL
|
||||
url_params: A dictionary of arbitrary URL parameters to append to the action's URL
|
||||
permissions: A list of permissions required to display the action
|
||||
label: The human-friendly button text
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.view_name = view_name
|
||||
self.view_kwargs = view_kwargs or {}
|
||||
self.url_params = url_params or {}
|
||||
@ -103,7 +88,7 @@ class LinkAction(PanelAction):
|
||||
Resolve the URL for the action from its view name and kwargs. Append any additional URL parameters.
|
||||
|
||||
Parameters:
|
||||
context: The template context
|
||||
context (dict): The template context
|
||||
"""
|
||||
url = reverse(self.view_name, kwargs=self.view_kwargs)
|
||||
if self.url_params:
|
||||
@ -127,19 +112,12 @@ class LinkAction(PanelAction):
|
||||
class AddObject(LinkAction):
|
||||
"""
|
||||
An action to add a new object.
|
||||
|
||||
Parameters:
|
||||
model (str): The dotted label of the model to be added (e.g. "dcim.site")
|
||||
url_params (dict): A dictionary of arbitrary URL parameters to append to the resolved URL
|
||||
"""
|
||||
label = _('Add')
|
||||
button_icon = 'plus-thick'
|
||||
|
||||
def __init__(self, model, url_params=None, label=None):
|
||||
"""
|
||||
Initialize a new AddObject action.
|
||||
|
||||
Parameters:
|
||||
model: The dotted label of the model to be added (e.g. "dcim.site")
|
||||
url_params: A dictionary of arbitrary URL parameters to append to the resolved URL
|
||||
label: The human-friendly button text
|
||||
"""
|
||||
def __init__(self, model, url_params=None, **kwargs):
|
||||
# Resolve the model class from its app.name label
|
||||
try:
|
||||
app_label, model_name = model.split('.')
|
||||
@ -148,37 +126,29 @@ class AddObject(LinkAction):
|
||||
raise ValueError(f"Invalid model label: {model}")
|
||||
view_name = get_viewname(model, 'add')
|
||||
|
||||
super().__init__(view_name=view_name, url_params=url_params, label=label)
|
||||
kwargs.setdefault('label', _('Add'))
|
||||
kwargs.setdefault('button_icon', 'plus-thick')
|
||||
kwargs.setdefault('permissions', [get_permission_for_model(model, 'add')])
|
||||
|
||||
# Require "add" permission on the model
|
||||
self.permissions = [get_permission_for_model(model, 'add')]
|
||||
super().__init__(view_name=view_name, url_params=url_params, **kwargs)
|
||||
|
||||
|
||||
class CopyContent(PanelAction):
|
||||
"""
|
||||
An action to copy the contents of a panel to the clipboard.
|
||||
|
||||
Parameters:
|
||||
target_id (str): The ID of the target element containing the content to be copied
|
||||
"""
|
||||
template_name = 'ui/actions/copy_content.html'
|
||||
label = _('Copy')
|
||||
button_icon = 'content-copy'
|
||||
|
||||
def __init__(self, target_id, **kwargs):
|
||||
"""
|
||||
Instantiate a new CopyContent action.
|
||||
|
||||
Parameters:
|
||||
target_id: The ID of the target element containing the content to be copied
|
||||
"""
|
||||
kwargs.setdefault('label', _('Copy'))
|
||||
kwargs.setdefault('button_icon', 'content-copy')
|
||||
super().__init__(**kwargs)
|
||||
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,
|
||||
|
||||
@ -34,22 +34,18 @@ class ObjectAttribute:
|
||||
Base class for representing an attribute of an object.
|
||||
|
||||
Attributes:
|
||||
template_name: The name of the template to render
|
||||
label: Human-friendly label for the rendered attribute
|
||||
placeholder: HTML to render for empty/null values
|
||||
template_name (str): The name of the template to render
|
||||
placeholder (str): HTML to render for empty/null values
|
||||
|
||||
Parameters:
|
||||
accessor (str): The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
label (str): Human-friendly label for the rendered attribute
|
||||
"""
|
||||
template_name = None
|
||||
label = None
|
||||
placeholder = mark_safe(PLACEHOLDER_HTML)
|
||||
|
||||
def __init__(self, accessor, label=None):
|
||||
"""
|
||||
Instantiate a new ObjectAttribute.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
label: Human-friendly label for the rendered attribute
|
||||
"""
|
||||
self.accessor = accessor
|
||||
if label is not None:
|
||||
self.label = label
|
||||
@ -59,7 +55,7 @@ class ObjectAttribute:
|
||||
Return the value of the attribute.
|
||||
|
||||
Parameters:
|
||||
obj: The object for which the attribute is being rendered
|
||||
obj (object): The object for which the attribute is being rendered
|
||||
"""
|
||||
return resolve_attr_path(obj, self.accessor)
|
||||
|
||||
@ -68,8 +64,8 @@ class ObjectAttribute:
|
||||
Return any additional template context used to render the attribute value.
|
||||
|
||||
Parameters:
|
||||
obj: The object for which the attribute is being rendered
|
||||
context: The root template context
|
||||
obj (object): The object for which the attribute is being rendered
|
||||
context (dict): The root template context
|
||||
"""
|
||||
return {}
|
||||
|
||||
@ -90,21 +86,15 @@ class ObjectAttribute:
|
||||
class TextAttr(ObjectAttribute):
|
||||
"""
|
||||
A text attribute.
|
||||
|
||||
Parameters:
|
||||
style (str): CSS class to apply to the rendered attribute
|
||||
format_string (str): If specified, the value will be formatted using this string when rendering
|
||||
copy_button (bool): Set to True to include a copy-to-clipboard button
|
||||
"""
|
||||
template_name = 'ui/attrs/text.html'
|
||||
|
||||
def __init__(self, *args, style=None, format_string=None, copy_button=False, **kwargs):
|
||||
"""
|
||||
Instantiate a new TextAttr.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
label: Human-friendly label for the rendered attribute
|
||||
template_name: The name of the template to render
|
||||
style: CSS class to apply to the rendered attribute
|
||||
format_string: If specified, the value will be formatted using this string when rendering
|
||||
copy_button: Set to True to include a copy-to-clipboard button
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.style = style
|
||||
self.format_string = format_string
|
||||
@ -127,20 +117,14 @@ class TextAttr(ObjectAttribute):
|
||||
class NumericAttr(ObjectAttribute):
|
||||
"""
|
||||
An integer or float attribute.
|
||||
|
||||
Parameters:
|
||||
unit_accessor (str): Accessor for the unit of measurement to display alongside the value (if any)
|
||||
copy_button (bool): Set to True to include a copy-to-clipboard button
|
||||
"""
|
||||
template_name = 'ui/attrs/numeric.html'
|
||||
|
||||
def __init__(self, *args, unit_accessor=None, copy_button=False, **kwargs):
|
||||
"""
|
||||
Instantiate a new NumericAttr.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
unit_accessor: Accessor for the unit of measurement to display alongside the value (if any)
|
||||
copy_button: Set to True to include a copy-to-clipboard button
|
||||
label: Human-friendly label for the rendered attribute
|
||||
template_name: The name of the template to render
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.unit_accessor = unit_accessor
|
||||
self.copy_button = copy_button
|
||||
@ -181,19 +165,13 @@ class ChoiceAttr(ObjectAttribute):
|
||||
class BooleanAttr(ObjectAttribute):
|
||||
"""
|
||||
A boolean attribute.
|
||||
|
||||
Parameters:
|
||||
display_false (bool): If False, a placeholder will be rendered instead of the "False" indication
|
||||
"""
|
||||
template_name = 'ui/attrs/boolean.html'
|
||||
|
||||
def __init__(self, *args, display_false=True, **kwargs):
|
||||
"""
|
||||
Instantiate a new BooleanAttr.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
display_false: If False, a placeholder will be rendered instead of the "False" indication
|
||||
label: Human-friendly label for the rendered attribute
|
||||
template_name: The name of the template to render
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.display_false = display_false
|
||||
|
||||
@ -222,21 +200,15 @@ class ImageAttr(ObjectAttribute):
|
||||
class RelatedObjectAttr(ObjectAttribute):
|
||||
"""
|
||||
An attribute representing a related object.
|
||||
|
||||
Parameters:
|
||||
linkify (bool): If True, the rendered value will be hyperlinked to the related object's detail view
|
||||
grouped_by (str): A second-order object to annotate alongside the related object; for example, an attribute
|
||||
representing the dcim.Site model might specify grouped_by="region"
|
||||
"""
|
||||
template_name = 'ui/attrs/object.html'
|
||||
|
||||
def __init__(self, *args, linkify=None, grouped_by=None, **kwargs):
|
||||
"""
|
||||
Instantiate a new RelatedObjectAttr.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
linkify: If True, the rendered value will be hyperlinked to the related object's detail view
|
||||
grouped_by: A second-order object to annotate alongside the related object; for example, an attribute
|
||||
representing the dcim.Site model might specify grouped_by="region"
|
||||
label: Human-friendly label for the rendered attribute
|
||||
template_name: The name of the template to render
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.linkify = linkify
|
||||
self.grouped_by = grouped_by
|
||||
@ -254,20 +226,14 @@ class NestedObjectAttr(ObjectAttribute):
|
||||
"""
|
||||
An attribute representing a related nested object. Similar to `RelatedObjectAttr`, but includes the ancestors of the
|
||||
related object in the rendered output.
|
||||
|
||||
Parameters:
|
||||
linkify (bool): If True, the rendered value will be hyperlinked to the related object's detail view
|
||||
max_depth (int): Maximum number of ancestors to display (default: all)
|
||||
"""
|
||||
template_name = 'ui/attrs/nested_object.html'
|
||||
|
||||
def __init__(self, *args, linkify=None, max_depth=None, **kwargs):
|
||||
"""
|
||||
Instantiate a new NestedObjectAttr. Shows a related object as well as its ancestors.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
linkify: If True, the rendered value will be hyperlinked to the related object's detail view
|
||||
max_depth: Maximum number of ancestors to display (default: all)
|
||||
label: Human-friendly label for the rendered attribute
|
||||
template_name: The name of the template to render
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.linkify = linkify
|
||||
self.max_depth = max_depth
|
||||
@ -286,19 +252,13 @@ class NestedObjectAttr(ObjectAttribute):
|
||||
class AddressAttr(ObjectAttribute):
|
||||
"""
|
||||
A physical or mailing address.
|
||||
|
||||
Parameters:
|
||||
map_url (bool): If true, the address will render as a hyperlink using settings.MAPS_URL
|
||||
"""
|
||||
template_name = 'ui/attrs/address.html'
|
||||
|
||||
def __init__(self, *args, map_url=True, **kwargs):
|
||||
"""
|
||||
Instantiate a new AddressAttr.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
map_url: If true, the address will render as a hyperlink using settings.MAPS_URL
|
||||
label: Human-friendly label for the rendered attribute
|
||||
template_name: The name of the template to render
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
if map_url is True:
|
||||
self.map_url = get_config().MAPS_URL
|
||||
@ -316,21 +276,16 @@ class AddressAttr(ObjectAttribute):
|
||||
class GPSCoordinatesAttr(ObjectAttribute):
|
||||
"""
|
||||
A GPS coordinates pair comprising latitude and longitude values.
|
||||
|
||||
Parameters:
|
||||
latitude_attr (float): The name of the field containing the latitude value
|
||||
longitude_attr (float): The name of the field containing the longitude value
|
||||
map_url (bool): If true, the address will render as a hyperlink using settings.MAPS_URL
|
||||
"""
|
||||
template_name = 'ui/attrs/gps_coordinates.html'
|
||||
label = _('GPS coordinates')
|
||||
|
||||
def __init__(self, latitude_attr='latitude', longitude_attr='longitude', map_url=True, **kwargs):
|
||||
"""
|
||||
Instantiate a new GPSCoordinatesAttr.
|
||||
|
||||
Parameters:
|
||||
latitude_attr: The name of the field containing the latitude value
|
||||
longitude_attr: The name of the field containing the longitude value
|
||||
map_url: If true, the address will render as a hyperlink using settings.MAPS_URL
|
||||
label: Human-friendly label for the rendered attribute
|
||||
template_name: The name of the template to render
|
||||
"""
|
||||
super().__init__(accessor=None, **kwargs)
|
||||
self.latitude_attr = latitude_attr
|
||||
self.longitude_attr = longitude_attr
|
||||
@ -365,18 +320,12 @@ class TimezoneAttr(ObjectAttribute):
|
||||
class TemplatedAttr(ObjectAttribute):
|
||||
"""
|
||||
Renders an attribute using a custom template.
|
||||
|
||||
Parameters:
|
||||
template_name (str): The name of the template to render
|
||||
context (dict): Additional context to pass to the template when rendering
|
||||
"""
|
||||
def __init__(self, *args, template_name, context=None, **kwargs):
|
||||
"""
|
||||
Instantiate a new TemplatedAttr.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||
template_name: The name of the template to render
|
||||
context: Additional context to pass to the template when rendering
|
||||
label: Human-friendly label for the rendered attribute
|
||||
template_name: The name of the template to render
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.template_name = template_name
|
||||
self.context = context or {}
|
||||
|
||||
@ -15,6 +15,9 @@ __all__ = (
|
||||
class Layout:
|
||||
"""
|
||||
A collection of rows and columns comprising the layout of content within the user interface.
|
||||
|
||||
Parameters:
|
||||
*rows: One or more Row instances
|
||||
"""
|
||||
def __init__(self, *rows):
|
||||
for i, row in enumerate(rows):
|
||||
@ -26,6 +29,9 @@ class Layout:
|
||||
class Row:
|
||||
"""
|
||||
A collection of columns arranged horizontally.
|
||||
|
||||
Parameters:
|
||||
*columns: One or more Column instances
|
||||
"""
|
||||
def __init__(self, *columns):
|
||||
for i, column in enumerate(columns):
|
||||
@ -37,6 +43,9 @@ class Row:
|
||||
class Column:
|
||||
"""
|
||||
A collection of panels arranged vertically.
|
||||
|
||||
Parameters:
|
||||
*panels: One or more Panel instances
|
||||
"""
|
||||
def __init__(self, *panels):
|
||||
for i, panel in enumerate(panels):
|
||||
@ -51,13 +60,23 @@ class Column:
|
||||
|
||||
class SimpleLayout(Layout):
|
||||
"""
|
||||
A layout with one row of two columns and a second row with one column. Includes registered plugin content.
|
||||
A layout with one row of two columns and a second row with one column.
|
||||
|
||||
+------+------+
|
||||
| col1 | col2 |
|
||||
+------+------+
|
||||
| col3 |
|
||||
+-------------+
|
||||
Plugin content registered for `left_page`, `right_page`, or `full_width_path` is included automatically. Most object
|
||||
views in NetBox utilize this layout.
|
||||
|
||||
```
|
||||
+-------+-------+
|
||||
| Col 1 | Col 2 |
|
||||
+-------+-------+
|
||||
| Col 3 |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
Parameters:
|
||||
left_panels: Panel instances to be rendered in the top lefthand column
|
||||
right_panels: Panel instances to be rendered in the top righthand column
|
||||
bottom_panels: Panel instances to be rendered in the bottom row
|
||||
"""
|
||||
def __init__(self, left_panels=None, right_panels=None, bottom_panels=None):
|
||||
left_panels = left_panels or []
|
||||
|
||||
@ -36,19 +36,19 @@ class Panel:
|
||||
Panels are arranged within rows and columns, (generally) render as discrete "cards" within the user interface. Each
|
||||
panel has a title and may have one or more actions associated with it, which will be rendered as hyperlinks in the
|
||||
top right corner of the card.
|
||||
|
||||
Attributes:
|
||||
template_name (str): The name of the template used to render the 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 = None
|
||||
title = None
|
||||
actions = None
|
||||
|
||||
def __init__(self, title=None, actions=None):
|
||||
"""
|
||||
Instantiate a new Panel.
|
||||
|
||||
Parameters:
|
||||
title: The human-friendly title of the panel
|
||||
actions: An iterable of PanelActions to include in the panel header
|
||||
"""
|
||||
if title is not None:
|
||||
self.title = title
|
||||
self.actions = actions or self.actions or []
|
||||
@ -58,7 +58,7 @@ class Panel:
|
||||
Return the context data to be used when rendering the panel.
|
||||
|
||||
Parameters:
|
||||
context: The template context
|
||||
context (dict): The template context
|
||||
"""
|
||||
return {
|
||||
'request': context.get('request'),
|
||||
@ -72,7 +72,7 @@ class Panel:
|
||||
Render the panel as HTML.
|
||||
|
||||
Parameters:
|
||||
context: The template context
|
||||
context (dict): The template context
|
||||
"""
|
||||
return render_to_string(self.template_name, self.get_context(context))
|
||||
|
||||
@ -84,16 +84,13 @@ class Panel:
|
||||
class ObjectPanel(Panel):
|
||||
"""
|
||||
Base class for object-specific panels.
|
||||
|
||||
Parameters:
|
||||
accessor (str): The dotted path in context data to the object being rendered (default: "object")
|
||||
"""
|
||||
accessor = 'object'
|
||||
|
||||
def __init__(self, accessor=None, **kwargs):
|
||||
"""
|
||||
Instantiate a new ObjectPanel.
|
||||
|
||||
Parameters:
|
||||
accessor: The dotted path in context data to the object being rendered (default: "object")
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if accessor is not None:
|
||||
@ -141,17 +138,16 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
|
||||
|
||||
Attributes are added to the panel by declaring ObjectAttribute instances in the class body (similar to fields on
|
||||
a Django form). Attributes are displayed in the order they are declared.
|
||||
|
||||
Note that the `only` and `exclude` parameters are mutually exclusive.
|
||||
|
||||
Parameters:
|
||||
only (list): If specified, only attributes in this list will be displayed
|
||||
exclude (list): If specified, attributes in this list will be excluded from display
|
||||
"""
|
||||
template_name = 'ui/panels/object_attributes.html'
|
||||
|
||||
def __init__(self, only=None, exclude=None, **kwargs):
|
||||
"""
|
||||
Instantiate a new ObjectPanel.
|
||||
|
||||
Parameters:
|
||||
only: If specified, only attributes in this list will be displayed
|
||||
exclude: If specified, attributes in this list will be excluded from display
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Set included/excluded attributes
|
||||
@ -192,7 +188,7 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
|
||||
|
||||
class OrganizationalObjectPanel(ObjectAttributesPanel, metaclass=ObjectAttributesPanelMeta):
|
||||
"""
|
||||
An ObjectPanel with attributes common to OrganizationalModels. Includes name and description.
|
||||
An ObjectPanel with attributes common to OrganizationalModels. Includes `name` and `description` attributes.
|
||||
"""
|
||||
name = attrs.TextAttr('name', label=_('Name'))
|
||||
description = attrs.TextAttr('description', label=_('Description'))
|
||||
@ -200,25 +196,24 @@ class OrganizationalObjectPanel(ObjectAttributesPanel, metaclass=ObjectAttribute
|
||||
|
||||
class NestedGroupObjectPanel(ObjectAttributesPanel, metaclass=ObjectAttributesPanelMeta):
|
||||
"""
|
||||
An ObjectPanel with attributes common to NestedGroupObjects. Includes the parent object.
|
||||
An ObjectPanel with attributes common to NestedGroupObjects. Includes the `parent` attribute.
|
||||
"""
|
||||
name = attrs.TextAttr('name', label=_('Name'))
|
||||
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
|
||||
description = attrs.TextAttr('description', label=_('Description'))
|
||||
|
||||
|
||||
class CommentsPanel(ObjectPanel):
|
||||
"""
|
||||
A panel which displays comments associated with an object.
|
||||
|
||||
Parameters:
|
||||
field_name (str): The name of the comment field on the object (default: "comments")
|
||||
"""
|
||||
template_name = 'ui/panels/comments.html'
|
||||
title = _('Comments')
|
||||
|
||||
def __init__(self, field_name='comments', **kwargs):
|
||||
"""
|
||||
Instantiate a new CommentsPanel.
|
||||
|
||||
Parameters:
|
||||
field_name: The name of the comment field on the object (default: "comments")
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.field_name = field_name
|
||||
|
||||
@ -232,17 +227,14 @@ class CommentsPanel(ObjectPanel):
|
||||
class JSONPanel(ObjectPanel):
|
||||
"""
|
||||
A panel which renders formatted JSON data from an object's JSONField.
|
||||
|
||||
Parameters:
|
||||
field_name (str): The name of the JSON field on the object
|
||||
copy_button (bool): Set to True (default) to include a copy-to-clipboard button
|
||||
"""
|
||||
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
|
||||
|
||||
@ -278,18 +270,16 @@ class RelatedObjectsPanel(Panel):
|
||||
class ObjectsTablePanel(Panel):
|
||||
"""
|
||||
A panel which displays a table of objects (rendered via HTMX).
|
||||
|
||||
Parameters:
|
||||
model (str): The dotted label of the model to be added (e.g. "dcim.site")
|
||||
filters (dict): A dictionary of arbitrary URL parameters to append to the table's URL. If the value of a key is
|
||||
a callable, it will be passed the current template context.
|
||||
"""
|
||||
template_name = 'ui/panels/objects_table.html'
|
||||
title = None
|
||||
|
||||
def __init__(self, model, filters=None, **kwargs):
|
||||
"""
|
||||
Instantiate a new ObjectsTablePanel.
|
||||
|
||||
Parameters:
|
||||
model: The dotted label of the model to be added (e.g. "dcim.site")
|
||||
filters: A dictionary of arbitrary URL parameters to append to the table's URL
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Resolve the model class from its app.name label
|
||||
@ -321,14 +311,11 @@ class ObjectsTablePanel(Panel):
|
||||
class TemplatePanel(Panel):
|
||||
"""
|
||||
A panel which renders custom content using an HTML template.
|
||||
|
||||
Parameters:
|
||||
template_name (str): The name of the template to render
|
||||
"""
|
||||
def __init__(self, template_name, **kwargs):
|
||||
"""
|
||||
Instantiate a new TemplatePanel.
|
||||
|
||||
Parameters:
|
||||
template_name: The name of the template to render
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.template_name = template_name
|
||||
|
||||
@ -342,7 +329,7 @@ class PluginContentPanel(Panel):
|
||||
A panel which displays embedded plugin content.
|
||||
|
||||
Parameters:
|
||||
method: The name of the plugin method to render (e.g. "left_page")
|
||||
method (str): The name of the plugin method to render (e.g. "left_page")
|
||||
"""
|
||||
def __init__(self, method, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@ -44,6 +44,7 @@ class ObjectView(ActionsMixin, BaseObjectView):
|
||||
Note: If `template_name` is not specified, it will be determined automatically based on the queryset model.
|
||||
|
||||
Attributes:
|
||||
layout: An instance of `netbox.ui.layout.Layout` which defines the page layout (overrides HTML template)
|
||||
tab: A ViewTab instance for the view
|
||||
actions: An iterable of ObjectAction subclasses (see ActionsMixin)
|
||||
"""
|
||||
@ -59,9 +60,6 @@ class ObjectView(ActionsMixin, BaseObjectView):
|
||||
Return self.template_name if defined. Otherwise, dynamically resolve the template name using the queryset
|
||||
model's `app_label` and `model_name`.
|
||||
"""
|
||||
# TODO: Temporarily allow layout to override template_name
|
||||
if self.layout is not None:
|
||||
return 'generic/object.html'
|
||||
if self.template_name is not None:
|
||||
return self.template_name
|
||||
model_opts = self.queryset.model._meta
|
||||
|
||||
Loading…
Reference in New Issue
Block a user