diff --git a/netbox/netbox/ui/actions.py b/netbox/netbox/ui/actions.py new file mode 100644 index 000000000..0b21b9071 --- /dev/null +++ b/netbox/netbox/ui/actions.py @@ -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')] diff --git a/netbox/netbox/ui/panels.py b/netbox/netbox/ui/panels.py index a602eaa33..195dcfd3c 100644 --- a/netbox/netbox/ui/panels.py +++ b/netbox/netbox/ui/panels.py @@ -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.utils.translation import gettext_lazy as _ -from netbox.ui import attrs +from netbox.ui import actions, attrs from netbox.ui.attrs import Attr from utilities.querydict import dict_to_querydict from utilities.string import title @@ -24,14 +25,28 @@ __all__ = ( 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: self.title = title + if actions is not None: + self.actions = actions - @abstractmethod - def render(self, obj): - pass + def get_context(self, obj): + return {} + + 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): @@ -64,20 +79,16 @@ class ObjectPanelMeta(ABCMeta): class ObjectPanel(Panel, metaclass=ObjectPanelMeta): template_name = 'ui/panels/object.html' - def get_attributes(self, obj): - return [ + def get_context(self, obj): + attrs = [ { 'label': attr.label or title(name), 'value': attr.render(obj, {'name': name}), } for name, attr in self._attrs.items() ] - - def render(self, context): - obj = context.get('object') - return render_to_string(self.template_name, { - 'title': self.title, - 'attrs': self.get_attributes(obj), - }) + return { + 'attrs': attrs, + } class NestedGroupObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta): @@ -90,44 +101,27 @@ class CustomFieldsPanel(Panel): template_name = 'ui/panels/custom_fields.html' title = _('Custom Fields') - def render(self, context): - obj = context.get('object') - 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, - }) + def get_context(self, obj): + return { + 'custom_fields': obj.get_custom_fields_by_group(), + } class TagsPanel(Panel): template_name = 'ui/panels/tags.html' title = _('Tags') - def render(self, context): - return render_to_string(self.template_name, { - 'title': self.title, - 'object': context.get('object'), - }) - class CommentsPanel(Panel): template_name = 'ui/panels/comments.html' 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): template_name = 'ui/panels/related_objects.html' title = _('Related Objects') + # TODO: Handle related_models from context def render(self, context): return render_to_string(self.template_name, { 'title': self.title, @@ -139,35 +133,37 @@ class RelatedObjectsPanel(Panel): class ImageAttachmentsPanel(Panel): template_name = 'ui/panels/image_attachments.html' title = _('Image Attachments') - - def render(self, context): - return render_to_string(self.template_name, { - 'title': self.title, - 'request': context.get('request'), - 'object': context.get('object'), - }) + actions = [ + actions.AddObject( + 'extras.imageattachment', + url_params={ + 'object_type': lambda obj: ContentType.objects.get_for_model(obj).pk, + 'object_id': lambda obj: obj.pk, + 'return_url': lambda obj: obj.get_absolute_url(), + }, + label=_('Attach an image'), + ), + ] class EmbeddedTablePanel(Panel): template_name = 'ui/panels/embedded_table.html' title = None - def __init__(self, viewname, url_params=None, **kwargs): + def __init__(self, view_name, url_params=None, **kwargs): super().__init__(**kwargs) - self.viewname = viewname + self.view_name = view_name self.url_params = url_params or {} - def render(self, context): - obj = context.get('object') + def get_context(self, obj): url_params = { 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 - return render_to_string(self.template_name, { - 'title': self.title, - 'viewname': self.viewname, + return { + 'viewname': self.view_name, 'url_params': dict_to_querydict(url_params), - }) + } class PluginContentPanel(Panel): diff --git a/netbox/templates/ui/panels/_base.html b/netbox/templates/ui/panels/_base.html index e1a6b4196..47ae689b7 100644 --- a/netbox/templates/ui/panels/_base.html +++ b/netbox/templates/ui/panels/_base.html @@ -1,4 +1,18 @@