mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-15 12:59:35 -06:00
Introduce panel actions
This commit is contained in:
parent
da68503a19
commit
37bea1e98e
56
netbox/netbox/ui/actions.py
Normal file
56
netbox/netbox/ui/actions.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from utilities.permissions import get_permission_for_model
|
||||||
|
from utilities.views import get_viewname
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'AddObject',
|
||||||
|
'PanelAction',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PanelAction:
|
||||||
|
label = None
|
||||||
|
button_class = 'primary'
|
||||||
|
button_icon = None
|
||||||
|
|
||||||
|
def __init__(self, view_name, view_kwargs=None, url_params=None, permissions=None, label=None):
|
||||||
|
self.view_name = view_name
|
||||||
|
self.view_kwargs = view_kwargs
|
||||||
|
self.url_params = url_params or {}
|
||||||
|
self.permissions = permissions
|
||||||
|
if label is not None:
|
||||||
|
self.label = label
|
||||||
|
|
||||||
|
def get_url(self, obj):
|
||||||
|
url = reverse(self.view_name, kwargs=self.view_kwargs or {})
|
||||||
|
if self.url_params:
|
||||||
|
url_params = {
|
||||||
|
k: v(obj) if callable(v) else v for k, v in self.url_params.items()
|
||||||
|
}
|
||||||
|
url = f'{url}?{urlencode(url_params)}'
|
||||||
|
return url
|
||||||
|
|
||||||
|
def get_context(self, obj):
|
||||||
|
return {
|
||||||
|
'url': self.get_url(obj),
|
||||||
|
'label': self.label,
|
||||||
|
'button_class': self.button_class,
|
||||||
|
'button_icon': self.button_icon,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AddObject(PanelAction):
|
||||||
|
label = _('Add')
|
||||||
|
button_icon = 'plus-thick'
|
||||||
|
|
||||||
|
def __init__(self, model, label=None, url_params=None):
|
||||||
|
app_label, model_name = model.split('.')
|
||||||
|
model = apps.get_model(app_label, model_name)
|
||||||
|
view_name = get_viewname(model, 'add')
|
||||||
|
super().__init__(view_name=view_name, label=label, url_params=url_params)
|
||||||
|
self.permissions = [get_permission_for_model(model, 'add')]
|
||||||
@ -1,9 +1,10 @@
|
|||||||
from abc import ABC, ABCMeta, abstractmethod
|
from abc import ABC, ABCMeta
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.template.loader import render_to_string
|
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 actions, attrs
|
||||||
from netbox.ui.attrs import Attr
|
from netbox.ui.attrs import Attr
|
||||||
from utilities.querydict import dict_to_querydict
|
from utilities.querydict import dict_to_querydict
|
||||||
from utilities.string import title
|
from utilities.string import title
|
||||||
@ -24,14 +25,28 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
class Panel(ABC):
|
class Panel(ABC):
|
||||||
|
template_name = None
|
||||||
|
title = None
|
||||||
|
actions = []
|
||||||
|
|
||||||
def __init__(self, title=None):
|
def __init__(self, title=None, actions=None):
|
||||||
if title is not None:
|
if title is not None:
|
||||||
self.title = title
|
self.title = title
|
||||||
|
if actions is not None:
|
||||||
|
self.actions = actions
|
||||||
|
|
||||||
@abstractmethod
|
def get_context(self, obj):
|
||||||
def render(self, obj):
|
return {}
|
||||||
pass
|
|
||||||
|
def render(self, context):
|
||||||
|
obj = context.get('object')
|
||||||
|
return render_to_string(self.template_name, {
|
||||||
|
'request': context.get('request'),
|
||||||
|
'object': obj,
|
||||||
|
'title': self.title,
|
||||||
|
'actions': [action.get_context(obj) for action in self.actions],
|
||||||
|
**self.get_context(obj),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class ObjectPanelMeta(ABCMeta):
|
class ObjectPanelMeta(ABCMeta):
|
||||||
@ -64,20 +79,16 @@ class ObjectPanelMeta(ABCMeta):
|
|||||||
class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
||||||
template_name = 'ui/panels/object.html'
|
template_name = 'ui/panels/object.html'
|
||||||
|
|
||||||
def get_attributes(self, obj):
|
def get_context(self, obj):
|
||||||
return [
|
attrs = [
|
||||||
{
|
{
|
||||||
'label': attr.label or title(name),
|
'label': attr.label or title(name),
|
||||||
'value': attr.render(obj, {'name': name}),
|
'value': attr.render(obj, {'name': name}),
|
||||||
} for name, attr in self._attrs.items()
|
} for name, attr in self._attrs.items()
|
||||||
]
|
]
|
||||||
|
return {
|
||||||
def render(self, context):
|
'attrs': attrs,
|
||||||
obj = context.get('object')
|
}
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
'title': self.title,
|
|
||||||
'attrs': self.get_attributes(obj),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class NestedGroupObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta):
|
class NestedGroupObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta):
|
||||||
@ -90,44 +101,27 @@ class CustomFieldsPanel(Panel):
|
|||||||
template_name = 'ui/panels/custom_fields.html'
|
template_name = 'ui/panels/custom_fields.html'
|
||||||
title = _('Custom Fields')
|
title = _('Custom Fields')
|
||||||
|
|
||||||
def render(self, context):
|
def get_context(self, obj):
|
||||||
obj = context.get('object')
|
return {
|
||||||
custom_fields = obj.get_custom_fields_by_group()
|
'custom_fields': obj.get_custom_fields_by_group(),
|
||||||
if not custom_fields:
|
}
|
||||||
return ''
|
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
'title': self.title,
|
|
||||||
'custom_fields': custom_fields,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class TagsPanel(Panel):
|
class TagsPanel(Panel):
|
||||||
template_name = 'ui/panels/tags.html'
|
template_name = 'ui/panels/tags.html'
|
||||||
title = _('Tags')
|
title = _('Tags')
|
||||||
|
|
||||||
def render(self, context):
|
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
'title': self.title,
|
|
||||||
'object': context.get('object'),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class CommentsPanel(Panel):
|
class CommentsPanel(Panel):
|
||||||
template_name = 'ui/panels/comments.html'
|
template_name = 'ui/panels/comments.html'
|
||||||
title = _('Comments')
|
title = _('Comments')
|
||||||
|
|
||||||
def render(self, context):
|
|
||||||
obj = context.get('object')
|
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
'title': self.title,
|
|
||||||
'comments': obj.comments,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class RelatedObjectsPanel(Panel):
|
class RelatedObjectsPanel(Panel):
|
||||||
template_name = 'ui/panels/related_objects.html'
|
template_name = 'ui/panels/related_objects.html'
|
||||||
title = _('Related Objects')
|
title = _('Related Objects')
|
||||||
|
|
||||||
|
# TODO: Handle related_models from context
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
return render_to_string(self.template_name, {
|
return render_to_string(self.template_name, {
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
@ -139,35 +133,37 @@ class RelatedObjectsPanel(Panel):
|
|||||||
class ImageAttachmentsPanel(Panel):
|
class ImageAttachmentsPanel(Panel):
|
||||||
template_name = 'ui/panels/image_attachments.html'
|
template_name = 'ui/panels/image_attachments.html'
|
||||||
title = _('Image Attachments')
|
title = _('Image Attachments')
|
||||||
|
actions = [
|
||||||
def render(self, context):
|
actions.AddObject(
|
||||||
return render_to_string(self.template_name, {
|
'extras.imageattachment',
|
||||||
'title': self.title,
|
url_params={
|
||||||
'request': context.get('request'),
|
'object_type': lambda obj: ContentType.objects.get_for_model(obj).pk,
|
||||||
'object': context.get('object'),
|
'object_id': lambda obj: obj.pk,
|
||||||
})
|
'return_url': lambda obj: obj.get_absolute_url(),
|
||||||
|
},
|
||||||
|
label=_('Attach an image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EmbeddedTablePanel(Panel):
|
class EmbeddedTablePanel(Panel):
|
||||||
template_name = 'ui/panels/embedded_table.html'
|
template_name = 'ui/panels/embedded_table.html'
|
||||||
title = None
|
title = None
|
||||||
|
|
||||||
def __init__(self, viewname, url_params=None, **kwargs):
|
def __init__(self, view_name, url_params=None, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.viewname = viewname
|
self.view_name = view_name
|
||||||
self.url_params = url_params or {}
|
self.url_params = url_params or {}
|
||||||
|
|
||||||
def render(self, context):
|
def get_context(self, obj):
|
||||||
obj = context.get('object')
|
|
||||||
url_params = {
|
url_params = {
|
||||||
k: v(obj) if callable(v) else v for k, v in self.url_params.items()
|
k: v(obj) if callable(v) else v for k, v in self.url_params.items()
|
||||||
}
|
}
|
||||||
# url_params['return_url'] = return_url or context['request'].path
|
# url_params['return_url'] = return_url or context['request'].path
|
||||||
return render_to_string(self.template_name, {
|
return {
|
||||||
'title': self.title,
|
'viewname': self.view_name,
|
||||||
'viewname': self.viewname,
|
|
||||||
'url_params': dict_to_querydict(url_params),
|
'url_params': dict_to_querydict(url_params),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class PluginContentPanel(Panel):
|
class PluginContentPanel(Panel):
|
||||||
|
|||||||
@ -1,4 +1,18 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{{ title }}</h2>
|
<h2 class="card-header">
|
||||||
|
{{ title }}
|
||||||
|
{% if actions %}
|
||||||
|
<div class="card-actions">
|
||||||
|
{% for action in actions %}
|
||||||
|
<a href="{{ action.url }}" class="btn btn-ghost-{{ action.button_class|default:"primary" }} btn-sm">
|
||||||
|
{% if action.button_icon %}
|
||||||
|
<i class="mdi mdi-{{ action.button_icon }}" aria-hidden="true"></i>
|
||||||
|
{% endif %}
|
||||||
|
{{ action.label }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
{% block panel_content %}{% endblock %}
|
{% block panel_content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
{% block panel_content %}
|
{% block panel_content %}
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if comments %}
|
{% if object.comments %}
|
||||||
{{ comments|markdown }}
|
{{ object.comments|markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">{% trans "None" %}</span>
|
<span class="text-muted">{% trans "None" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user