Replace EmbeddedTablePanel with ObjectsTablePanel

This commit is contained in:
Jeremy Stretch 2025-11-03 10:30:13 -05:00
parent 37bea1e98e
commit c392988212
5 changed files with 58 additions and 41 deletions

View File

@ -18,9 +18,9 @@ from extras.views import ObjectConfigContextView, ObjectRenderConfigView
from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
from netbox.object_actions import * from netbox.object_actions import *
from netbox.ui import layout from netbox.ui import actions, layout
from netbox.ui.panels import ( from netbox.ui.panels import (
CommentsPanel, CustomFieldsPanel, EmbeddedTablePanel, ImageAttachmentsPanel, PluginContentPanel, CommentsPanel, CustomFieldsPanel, ImageAttachmentsPanel, ObjectsTablePanel, PluginContentPanel,
RelatedObjectsPanel, TagsPanel, RelatedObjectsPanel, TagsPanel,
) )
from netbox.views import generic from netbox.views import generic
@ -485,19 +485,24 @@ class SiteView(GetRelatedModelsMixin, generic.ObjectView):
), ),
layout.Row( layout.Row(
layout.Column( layout.Column(
EmbeddedTablePanel( ObjectsTablePanel(
'dcim:location_list', model='dcim.Location',
url_params={'site_id': lambda x: x.pk}, filters={'site_id': lambda obj: obj.pk},
title=_('Locations') actions=[
actions.AddObject('dcim.Location', url_params={'site': lambda obj: obj.pk}),
],
), ),
EmbeddedTablePanel( ObjectsTablePanel(
'dcim:device_list', model='dcim.Device',
url_params={ title=_('Non-Racked Devices'),
'site_id': lambda x: x.pk, filters={
'site_id': lambda obj: obj.pk,
'rack_id': settings.FILTERS_NULL_CHOICE_VALUE, 'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE, 'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
}, },
title=_('Non-Racked Devices') actions=[
actions.AddObject('dcim.Device', url_params={'site': lambda obj: obj.pk}),
],
), ),
PluginContentPanel('full_width_page'), PluginContentPanel('full_width_page'),
), ),

View File

@ -32,6 +32,8 @@ class PanelAction:
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()
} }
if 'return_url' not in url_params:
url_params['return_url'] = obj.get_absolute_url()
url = f'{url}?{urlencode(url_params)}' url = f'{url}?{urlencode(url_params)}'
return url return url
@ -49,8 +51,11 @@ class AddObject(PanelAction):
button_icon = 'plus-thick' button_icon = 'plus-thick'
def __init__(self, model, label=None, url_params=None): def __init__(self, model, label=None, url_params=None):
# Resolve the model class from its app.name label
app_label, model_name = model.split('.') app_label, model_name = model.split('.')
model = apps.get_model(app_label, model_name) model = apps.get_model(app_label, model_name)
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
self.permissions = [get_permission_for_model(model, 'add')] self.permissions = [get_permission_for_model(model, 'add')]

View File

@ -1,5 +1,6 @@
from abc import ABC, ABCMeta from abc import ABC, ABCMeta
from django.apps import apps
from django.contrib.contenttypes.models import ContentType 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 _
@ -9,14 +10,15 @@ 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
from utilities.templatetags.plugins import _get_registered_content from utilities.templatetags.plugins import _get_registered_content
from utilities.views import get_viewname
__all__ = ( __all__ = (
'CommentsPanel', 'CommentsPanel',
'CustomFieldsPanel', 'CustomFieldsPanel',
'EmbeddedTablePanel',
'ImageAttachmentsPanel', 'ImageAttachmentsPanel',
'NestedGroupObjectPanel', 'NestedGroupObjectPanel',
'ObjectPanel', 'ObjectPanel',
'ObjectsTablePanel',
'RelatedObjectsPanel', 'RelatedObjectsPanel',
'Panel', 'Panel',
'PluginContentPanel', 'PluginContentPanel',
@ -130,9 +132,33 @@ class RelatedObjectsPanel(Panel):
}) })
class ImageAttachmentsPanel(Panel): class ObjectsTablePanel(Panel):
template_name = 'ui/panels/image_attachments.html' template_name = 'ui/panels/objects_table.html'
title = _('Image Attachments') title = None
def __init__(self, model, filters=None, **kwargs):
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)
self.filters = filters or {}
if self.title is None:
self.title = title(self.model._meta.verbose_name_plural)
def get_context(self, obj):
url_params = {
k: v(obj) if callable(v) else v for k, v in self.filters.items()
}
if 'return_url' not in url_params:
url_params['return_url'] = obj.get_absolute_url()
return {
'viewname': get_viewname(self.model, 'list'),
'url_params': dict_to_querydict(url_params),
}
class ImageAttachmentsPanel(ObjectsTablePanel):
actions = [ actions = [
actions.AddObject( actions.AddObject(
'extras.imageattachment', 'extras.imageattachment',
@ -145,25 +171,8 @@ class ImageAttachmentsPanel(Panel):
), ),
] ]
def __init__(self, **kwargs):
class EmbeddedTablePanel(Panel): super().__init__('extras.imageattachment', **kwargs)
template_name = 'ui/panels/embedded_table.html'
title = None
def __init__(self, view_name, url_params=None, **kwargs):
super().__init__(**kwargs)
self.view_name = view_name
self.url_params = url_params or {}
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 {
'viewname': self.view_name,
'url_params': dict_to_querydict(url_params),
}
class PluginContentPanel(Panel): class PluginContentPanel(Panel):

View File

@ -1,7 +0,0 @@
{% extends "ui/panels/_base.html" %}
{% load i18n %}
{# TODO: Add "attach an image" button in panel header #}
{% block panel_content %}
{% htmx_table 'extras:imageattachment_list' object_type_id=object|content_type_id object_id=object.pk %}
{% endblock panel_content %}

View File

@ -0,0 +1,5 @@
{% extends "ui/panels/_base.html" %}
{% block panel_content %}
{% include 'builtins/htmx_table.html' %}
{% endblock panel_content %}