|
|
@@ -24,24 +24,52 @@ __all__ = (
|
|
|
|
|
|
|
|
|
class Panel(ABC):
|
|
|
+ """
|
|
|
+ A block of content rendered within an HTML template.
|
|
|
+
|
|
|
+ Attributes:
|
|
|
+ template_name: The name of the template to render
|
|
|
+ title: The human-friendly title of the panel
|
|
|
+ actions: A list of PanelActions to include in the panel header
|
|
|
+ """
|
|
|
template_name = None
|
|
|
title = None
|
|
|
actions = []
|
|
|
|
|
|
def __init__(self, title=None, actions=None):
|
|
|
+ """
|
|
|
+ Instantiate a new Panel.
|
|
|
+
|
|
|
+ Parameters:
|
|
|
+ title: The human-friendly title of the panel
|
|
|
+ actions: A list of PanelActions to include in the panel header
|
|
|
+ """
|
|
|
if title is not None:
|
|
|
self.title = title
|
|
|
if actions is not None:
|
|
|
self.actions = actions
|
|
|
|
|
|
def get_context(self, context):
|
|
|
+ """
|
|
|
+ Return the context data to be used when rendering the panel.
|
|
|
+
|
|
|
+ Parameters:
|
|
|
+ context: The template context
|
|
|
+ """
|
|
|
return {
|
|
|
'request': context.get('request'),
|
|
|
+ 'object': context.get('object'),
|
|
|
'title': self.title,
|
|
|
- 'actions': [action.get_context(context) for action in self.actions],
|
|
|
+ 'actions': self.actions,
|
|
|
}
|
|
|
|
|
|
def render(self, context):
|
|
|
+ """
|
|
|
+ Render the panel as HTML.
|
|
|
+
|
|
|
+ Parameters:
|
|
|
+ context: The template context
|
|
|
+ """
|
|
|
return render_to_string(self.template_name, self.get_context(context))
|
|
|
|
|
|
|
|
|
@@ -73,21 +101,43 @@ class ObjectPanelMeta(ABCMeta):
|
|
|
|
|
|
|
|
|
class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
|
|
- accessor = None
|
|
|
+ """
|
|
|
+ A panel which displays selected attributes of an object.
|
|
|
+
|
|
|
+ Attributes:
|
|
|
+ template_name: The name of the template to render
|
|
|
+ accessor: The name of the attribute on the object
|
|
|
+ """
|
|
|
template_name = 'ui/panels/object.html'
|
|
|
+ accessor = None
|
|
|
|
|
|
def __init__(self, accessor=None, only=None, exclude=None, **kwargs):
|
|
|
+ """
|
|
|
+ Instantiate a new ObjectPanel.
|
|
|
+
|
|
|
+ Parameters:
|
|
|
+ accessor: The name of the attribute on the object
|
|
|
+ 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)
|
|
|
+
|
|
|
if accessor is not None:
|
|
|
self.accessor = accessor
|
|
|
|
|
|
# Set included/excluded attributes
|
|
|
if only is not None and exclude is not None:
|
|
|
- raise ValueError("attrs and exclude cannot both be specified.")
|
|
|
+ raise ValueError("only and exclude cannot both be specified.")
|
|
|
self.only = only or []
|
|
|
self.exclude = exclude or []
|
|
|
|
|
|
def get_context(self, context):
|
|
|
+ """
|
|
|
+ Return the context data to be used when rendering the panel.
|
|
|
+
|
|
|
+ Parameters:
|
|
|
+ context: The template context
|
|
|
+ """
|
|
|
# Determine which attributes to display in the panel based on only/exclude args
|
|
|
attr_names = set(self._attrs.keys())
|
|
|
if self.only:
|
|
|
@@ -99,7 +149,6 @@ class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
|
|
|
|
|
return {
|
|
|
**super().get_context(context),
|
|
|
- 'object': obj,
|
|
|
'attrs': [
|
|
|
{
|
|
|
'label': attr.label or title(name),
|
|
|
@@ -110,24 +159,42 @@ class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
|
|
|
|
|
|
|
|
class OrganizationalObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta):
|
|
|
+ """
|
|
|
+ An ObjectPanel with attributes common to OrganizationalModels.
|
|
|
+ """
|
|
|
name = attrs.TextAttr('name', label=_('Name'))
|
|
|
description = attrs.TextAttr('description', label=_('Description'))
|
|
|
|
|
|
|
|
|
class NestedGroupObjectPanel(OrganizationalObjectPanel, metaclass=ObjectPanelMeta):
|
|
|
+ """
|
|
|
+ An ObjectPanel with attributes common to NestedGroupObjects.
|
|
|
+ """
|
|
|
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
|
|
|
|
|
|
|
|
|
class CommentsPanel(Panel):
|
|
|
+ """
|
|
|
+ A panel which displays comments associated with an object.
|
|
|
+ """
|
|
|
template_name = 'ui/panels/comments.html'
|
|
|
title = _('Comments')
|
|
|
|
|
|
|
|
|
class RelatedObjectsPanel(Panel):
|
|
|
+ """
|
|
|
+ A panel which displays the types and counts of related objects.
|
|
|
+ """
|
|
|
template_name = 'ui/panels/related_objects.html'
|
|
|
title = _('Related Objects')
|
|
|
|
|
|
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),
|
|
|
'related_models': context.get('related_models'),
|
|
|
@@ -135,20 +202,42 @@ class RelatedObjectsPanel(Panel):
|
|
|
|
|
|
|
|
|
class ObjectsTablePanel(Panel):
|
|
|
+ """
|
|
|
+ A panel which displays a table of objects (rendered via HTMX).
|
|
|
+ """
|
|
|
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
|
|
|
- app_label, model_name = model.split('.')
|
|
|
- self.model = apps.get_model(app_label, model_name)
|
|
|
+ try:
|
|
|
+ app_label, model_name = model.split('.')
|
|
|
+ self.model = apps.get_model(app_label, model_name)
|
|
|
+ except (ValueError, LookupError):
|
|
|
+ raise ValueError(f"Invalid model label: {model}")
|
|
|
+
|
|
|
self.filters = filters or {}
|
|
|
+
|
|
|
+ # If no title is specified, derive one from the model name
|
|
|
if self.title is None:
|
|
|
self.title = title(self.model._meta.verbose_name_plural)
|
|
|
|
|
|
def get_context(self, context):
|
|
|
+ """
|
|
|
+ Return the context data to be used when rendering the panel.
|
|
|
+
|
|
|
+ Parameters:
|
|
|
+ context: The template context
|
|
|
+ """
|
|
|
url_params = {
|
|
|
k: v(context) if callable(v) else v for k, v in self.filters.items()
|
|
|
}
|
|
|
@@ -162,8 +251,16 @@ class ObjectsTablePanel(Panel):
|
|
|
|
|
|
|
|
|
class TemplatePanel(Panel):
|
|
|
-
|
|
|
+ """
|
|
|
+ A panel which renders content using an HTML template.
|
|
|
+ """
|
|
|
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
|
|
|
|
|
|
@@ -173,7 +270,12 @@ class TemplatePanel(Panel):
|
|
|
|
|
|
|
|
|
class PluginContentPanel(Panel):
|
|
|
+ """
|
|
|
+ A panel which displays embedded plugin content.
|
|
|
|
|
|
+ Parameters:
|
|
|
+ method: The name of the plugin method to render (e.g. left_page)
|
|
|
+ """
|
|
|
def __init__(self, method, **kwargs):
|
|
|
super().__init__(**kwargs)
|
|
|
self.method = method
|