mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-14 12:29:35 -06:00
Lots of cleanup
This commit is contained in:
parent
c05106f9b2
commit
59899d0d9a
@ -1202,7 +1202,7 @@ class RackReservationView(generic.ObjectView):
|
|||||||
layout = layout.Layout(
|
layout = layout.Layout(
|
||||||
layout.Row(
|
layout.Row(
|
||||||
layout.Column(
|
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(),
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@ -14,48 +15,102 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
class PanelAction:
|
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 = 'ui/action.html'
|
||||||
label = None
|
label = None
|
||||||
button_class = 'primary'
|
button_class = 'primary'
|
||||||
button_icon = None
|
button_icon = None
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
self.view_name = view_name
|
self.view_name = view_name
|
||||||
self.view_kwargs = view_kwargs
|
self.view_kwargs = view_kwargs or {}
|
||||||
self.url_params = url_params or {}
|
self.url_params = url_params or {}
|
||||||
self.permissions = permissions
|
self.permissions = permissions
|
||||||
if label is not None:
|
if label is not None:
|
||||||
self.label = label
|
self.label = label
|
||||||
|
|
||||||
def get_url(self, context):
|
def get_url(self, context):
|
||||||
url = reverse(self.view_name, kwargs=self.view_kwargs or {})
|
"""
|
||||||
|
Resolve the URL for the action from its view name and kwargs. Append any additional URL parameters.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
context: The template context
|
||||||
|
"""
|
||||||
|
url = reverse(self.view_name, kwargs=self.view_kwargs)
|
||||||
if self.url_params:
|
if self.url_params:
|
||||||
|
# If the param value is callable, call it with the context and save the result.
|
||||||
url_params = {
|
url_params = {
|
||||||
k: v(context) if callable(v) else v for k, v in self.url_params.items()
|
k: v(context) if callable(v) else v for k, v in self.url_params.items()
|
||||||
}
|
}
|
||||||
|
# Set the return URL if not already set and an object is available.
|
||||||
if 'return_url' not in url_params and 'object' in context:
|
if 'return_url' not in url_params and 'object' in context:
|
||||||
url_params['return_url'] = context['object'].get_absolute_url()
|
url_params['return_url'] = context['object'].get_absolute_url()
|
||||||
url = f'{url}?{urlencode(url_params)}'
|
url = f'{url}?{urlencode(url_params)}'
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def get_context(self, context):
|
def render(self, context):
|
||||||
return {
|
"""
|
||||||
|
Render the action as HTML.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
context: The template context
|
||||||
|
"""
|
||||||
|
# Enforce permissions
|
||||||
|
user = context['request'].user
|
||||||
|
if not user.has_perms(self.permissions):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return render_to_string(self.template_name, {
|
||||||
'url': self.get_url(context),
|
'url': self.get_url(context),
|
||||||
'label': self.label,
|
'label': self.label,
|
||||||
'button_class': self.button_class,
|
'button_class': self.button_class,
|
||||||
'button_icon': self.button_icon,
|
'button_icon': self.button_icon,
|
||||||
}
|
})
|
||||||
|
|
||||||
|
|
||||||
class AddObject(PanelAction):
|
class AddObject(PanelAction):
|
||||||
|
"""
|
||||||
|
An action to add a new object.
|
||||||
|
"""
|
||||||
label = _('Add')
|
label = _('Add')
|
||||||
button_icon = 'plus-thick'
|
button_icon = 'plus-thick'
|
||||||
|
|
||||||
def __init__(self, model, label=None, url_params=None):
|
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
|
||||||
|
"""
|
||||||
# Resolve the model class from its app.name label
|
# Resolve the model class from its app.name label
|
||||||
app_label, model_name = model.split('.')
|
try:
|
||||||
model = apps.get_model(app_label, model_name)
|
app_label, model_name = model.split('.')
|
||||||
|
model = apps.get_model(app_label, model_name)
|
||||||
|
except (ValueError, LookupError):
|
||||||
|
raise ValueError(f"Invalid model label: {model}")
|
||||||
view_name = get_viewname(model, 'add')
|
view_name = get_viewname(model, 'add')
|
||||||
|
|
||||||
super().__init__(view_name=view_name, label=label, url_params=url_params)
|
super().__init__(view_name=view_name, label=label, url_params=url_params)
|
||||||
|
|
||||||
# Require "add" permission on the model by default
|
# Require "add" permission on the model
|
||||||
self.permissions = [get_permission_for_model(model, 'add')]
|
self.permissions = [get_permission_for_model(model, 'add')]
|
||||||
|
|||||||
@ -24,24 +24,52 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
class Panel(ABC):
|
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
|
template_name = None
|
||||||
title = None
|
title = None
|
||||||
actions = []
|
actions = []
|
||||||
|
|
||||||
def __init__(self, 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: A list of PanelActions to include in the panel header
|
||||||
|
"""
|
||||||
if title is not None:
|
if title is not None:
|
||||||
self.title = title
|
self.title = title
|
||||||
if actions is not None:
|
if actions is not None:
|
||||||
self.actions = actions
|
self.actions = actions
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
|
"""
|
||||||
|
Return the context data to be used when rendering the panel.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
context: The template context
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
'request': context.get('request'),
|
'request': context.get('request'),
|
||||||
|
'object': context.get('object'),
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
'actions': [action.get_context(context) for action in self.actions],
|
'actions': self.actions,
|
||||||
}
|
}
|
||||||
|
|
||||||
def render(self, context):
|
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))
|
return render_to_string(self.template_name, self.get_context(context))
|
||||||
|
|
||||||
|
|
||||||
@ -73,21 +101,43 @@ class ObjectPanelMeta(ABCMeta):
|
|||||||
|
|
||||||
|
|
||||||
class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
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'
|
template_name = 'ui/panels/object.html'
|
||||||
|
accessor = None
|
||||||
|
|
||||||
def __init__(self, accessor=None, only=None, exclude=None, **kwargs):
|
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)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
if accessor is not None:
|
if accessor is not None:
|
||||||
self.accessor = accessor
|
self.accessor = accessor
|
||||||
|
|
||||||
# Set included/excluded attributes
|
# Set included/excluded attributes
|
||||||
if only is not None and exclude is not None:
|
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.only = only or []
|
||||||
self.exclude = exclude or []
|
self.exclude = exclude or []
|
||||||
|
|
||||||
def get_context(self, context):
|
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
|
# Determine which attributes to display in the panel based on only/exclude args
|
||||||
attr_names = set(self._attrs.keys())
|
attr_names = set(self._attrs.keys())
|
||||||
if self.only:
|
if self.only:
|
||||||
@ -99,7 +149,6 @@ class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
**super().get_context(context),
|
**super().get_context(context),
|
||||||
'object': obj,
|
|
||||||
'attrs': [
|
'attrs': [
|
||||||
{
|
{
|
||||||
'label': attr.label or title(name),
|
'label': attr.label or title(name),
|
||||||
@ -110,24 +159,42 @@ class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
|||||||
|
|
||||||
|
|
||||||
class OrganizationalObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta):
|
class OrganizationalObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta):
|
||||||
|
"""
|
||||||
|
An ObjectPanel with attributes common to OrganizationalModels.
|
||||||
|
"""
|
||||||
name = attrs.TextAttr('name', label=_('Name'))
|
name = attrs.TextAttr('name', label=_('Name'))
|
||||||
description = attrs.TextAttr('description', label=_('Description'))
|
description = attrs.TextAttr('description', label=_('Description'))
|
||||||
|
|
||||||
|
|
||||||
class NestedGroupObjectPanel(OrganizationalObjectPanel, metaclass=ObjectPanelMeta):
|
class NestedGroupObjectPanel(OrganizationalObjectPanel, metaclass=ObjectPanelMeta):
|
||||||
|
"""
|
||||||
|
An ObjectPanel with attributes common to NestedGroupObjects.
|
||||||
|
"""
|
||||||
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
|
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
|
||||||
|
|
||||||
|
|
||||||
class CommentsPanel(Panel):
|
class CommentsPanel(Panel):
|
||||||
|
"""
|
||||||
|
A panel which displays comments associated with an object.
|
||||||
|
"""
|
||||||
template_name = 'ui/panels/comments.html'
|
template_name = 'ui/panels/comments.html'
|
||||||
title = _('Comments')
|
title = _('Comments')
|
||||||
|
|
||||||
|
|
||||||
class RelatedObjectsPanel(Panel):
|
class RelatedObjectsPanel(Panel):
|
||||||
|
"""
|
||||||
|
A panel which displays the types and counts of related objects.
|
||||||
|
"""
|
||||||
template_name = 'ui/panels/related_objects.html'
|
template_name = 'ui/panels/related_objects.html'
|
||||||
title = _('Related Objects')
|
title = _('Related Objects')
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
|
"""
|
||||||
|
Return the context data to be used when rendering the panel.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
context: The template context
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
**super().get_context(context),
|
**super().get_context(context),
|
||||||
'related_models': context.get('related_models'),
|
'related_models': context.get('related_models'),
|
||||||
@ -135,20 +202,42 @@ class RelatedObjectsPanel(Panel):
|
|||||||
|
|
||||||
|
|
||||||
class ObjectsTablePanel(Panel):
|
class ObjectsTablePanel(Panel):
|
||||||
|
"""
|
||||||
|
A panel which displays a table of objects (rendered via HTMX).
|
||||||
|
"""
|
||||||
template_name = 'ui/panels/objects_table.html'
|
template_name = 'ui/panels/objects_table.html'
|
||||||
title = None
|
title = None
|
||||||
|
|
||||||
def __init__(self, model, filters=None, **kwargs):
|
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)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
# Resolve the model class from its app.name label
|
# Resolve the model class from its app.name label
|
||||||
app_label, model_name = model.split('.')
|
try:
|
||||||
self.model = apps.get_model(app_label, model_name)
|
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 {}
|
self.filters = filters or {}
|
||||||
|
|
||||||
|
# If no title is specified, derive one from the model name
|
||||||
if self.title is None:
|
if self.title is None:
|
||||||
self.title = title(self.model._meta.verbose_name_plural)
|
self.title = title(self.model._meta.verbose_name_plural)
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
|
"""
|
||||||
|
Return the context data to be used when rendering the panel.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
context: The template context
|
||||||
|
"""
|
||||||
url_params = {
|
url_params = {
|
||||||
k: v(context) if callable(v) else v for k, v in self.filters.items()
|
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):
|
class TemplatePanel(Panel):
|
||||||
|
"""
|
||||||
|
A panel which renders content using an HTML template.
|
||||||
|
"""
|
||||||
def __init__(self, template_name, **kwargs):
|
def __init__(self, template_name, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new TemplatePanel.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
template_name: The name of the template to render
|
||||||
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.template_name = template_name
|
self.template_name = template_name
|
||||||
|
|
||||||
@ -173,7 +270,12 @@ class TemplatePanel(Panel):
|
|||||||
|
|
||||||
|
|
||||||
class PluginContentPanel(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):
|
def __init__(self, method, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.method = method
|
self.method = method
|
||||||
|
|||||||
@ -129,7 +129,7 @@ Context:
|
|||||||
{% for column in row.columns %}
|
{% for column in row.columns %}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% for panel in column.panels %}
|
{% for panel in column.panels %}
|
||||||
{% render_panel panel %}
|
{% render panel %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
6
netbox/templates/ui/action.html
Normal file
6
netbox/templates/ui/action.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<a href="{{ url }}" class="btn btn-ghost-{{ button_class }} btn-sm">
|
||||||
|
{% if button_icon %}
|
||||||
|
<i class="mdi mdi-{{ button_icon }}" aria-hidden="true"></i>
|
||||||
|
{% endif %}
|
||||||
|
{{ label }}
|
||||||
|
</a>
|
||||||
@ -4,12 +4,7 @@
|
|||||||
{% if actions %}
|
{% if actions %}
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
{% for action in actions %}
|
{% for action in actions %}
|
||||||
<a href="{{ action.url }}" class="btn btn-ghost-{{ action.button_class|default:"primary" }} btn-sm">
|
{% render action %}
|
||||||
{% if action.button_icon %}
|
|
||||||
<i class="mdi mdi-{{ action.button_icon }}" aria-hidden="true"></i>
|
|
||||||
{% endif %}
|
|
||||||
{{ action.label }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -183,5 +183,5 @@ def static_with_params(path, **params):
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def render_panel(context, panel):
|
def render(context, component):
|
||||||
return mark_safe(panel.render(context))
|
return mark_safe(component.render(context))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user