Add panels for common inclusion templates

This commit is contained in:
Jeremy Stretch
2025-10-31 14:38:33 -04:00
parent 3fd4664a76
commit 77613b37b2
9 changed files with 178 additions and 8 deletions

View File

@@ -18,6 +18,7 @@ from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
from netbox.object_actions import *
from netbox.ui import layout
from netbox.ui.panels import CommentsPanel, CustomFieldsPanel, ImageAttachmentsPanel, RelatedObjectsPanel, TagsPanel
from netbox.views import generic
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator, get_paginate_count
@@ -468,14 +469,20 @@ class SiteView(GetRelatedModelsMixin, generic.ObjectView):
layout = layout.Layout(
layout.Row(
layout.Column(
panels.SitePanel(_('Site'))
panels.SitePanel(_('Site')),
CustomFieldsPanel(),
TagsPanel(),
CommentsPanel(),
),
layout.Column(
RelatedObjectsPanel(),
ImageAttachmentsPanel(),
),
)
)
def get_extra_context(self, request, instance):
return {
# 'site_panel': panels.SitePanel(instance, _('Site')),
'related_models': self.get_related_models(
request,
instance,

View File

@@ -8,14 +8,23 @@ from netbox.ui.attrs import Attr
from utilities.string import title
__all__ = (
'CommentsPanel',
'CustomFieldsPanel',
'ImageAttachmentsPanel',
'NestedGroupObjectPanel',
'ObjectPanel',
'RelatedObjectsPanel',
'Panel',
'TagsPanel',
)
class Panel(ABC):
def __init__(self, title=None):
if title is not None:
self.title = title
@abstractmethod
def render(self, obj):
pass
@@ -51,9 +60,6 @@ class ObjectPanelMeta(ABCMeta):
class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
template_name = 'ui/panels/object.html'
def __init__(self, title=None):
self.title = title
def get_attributes(self, obj):
return [
{
@@ -74,3 +80,65 @@ class NestedGroupObjectPanel(ObjectPanel, metaclass=ObjectPanelMeta):
name = attrs.TextAttr('name', label=_('Name'))
description = attrs.TextAttr('description', label=_('Description'))
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
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,
})
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')
def render(self, context):
return render_to_string(self.template_name, {
'title': self.title,
'object': context.get('object'),
'related_models': context.get('related_models'),
})
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'),
})

View File

@@ -0,0 +1,4 @@
<div class="card">
<h2 class="card-header">{{ title }}</h2>
{% block panel_content %}{% endblock %}
</div>

View File

@@ -0,0 +1,12 @@
{% extends "ui/panels/_base.html" %}
{% load i18n %}
{% block panel_content %}
<div class="card-body">
{% if comments %}
{{ comments|markdown }}
{% else %}
<span class="text-muted">{% trans "None" %}</span>
{% endif %}
</div>
{% endblock panel_content %}

View File

@@ -0,0 +1,31 @@
{% extends "ui/panels/_base.html" %}
{% load i18n %}
{% block panel_content %}
<table class="table table-hover attr-table">
{% for group_name, fields in custom_fields.items %}
{% if group_name %}
<tr>
<th scope="row" colspan="2" class="fw-bold">{{ group_name }}</th>
</tr>
{% endif %}
{% for field, value in fields.items %}
<tr>
<th scope="row"{% if group_name %} class="ps-3"{% endif %}>{{ field }}
{% if field.description %}
<i
class="mdi mdi-information text-primary"
data-bs-toggle="tooltip"
data-bs-placement="right"
title="{{ field.description|escape }}"
></i>
{% endif %}
</th>
<td>
{% customfield_value field value %}
</td>
</tr>
{% endfor %}
{% endfor %}
</table>
{% endblock panel_content %}

View File

@@ -0,0 +1,7 @@
{% 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

@@ -1,5 +1,6 @@
<div class="card">
<h2 class="card-header">{{ title }}</h2>
{% extends "ui/panels/_base.html" %}
{% block panel_content %}
<table class="table table-hover attr-table">
{% for attr in attrs %}
<tr>
@@ -10,4 +11,4 @@
</tr>
{% endfor %}
</table>
</div>
{% endblock panel_content %}

View File

@@ -0,0 +1,25 @@
{% extends "ui/panels/_base.html" %}
{% load helpers %}
{% load i18n %}
{% block panel_content %}
<ul class="list-group list-group-flush" role="presentation">
{% for related_object_count in related_models %}
{% action_url related_object_count.queryset.model 'list' as list_url %}
{% if list_url %}
<a href="{{ list_url }}?{{ related_object_count.filter_param }}={{ object.pk }}" class="list-group-item list-group-item-action d-flex justify-content-between">
{{ related_object_count.name }}
{% with count=related_object_count.queryset.count %}
{% if count %}
<span class="badge text-bg-primary rounded-pill">{{ count }}</span>
{% else %}
<span class="badge text-bg-light rounded-pill">&mdash;</span>
{% endif %}
{% endwith %}
</a>
{% endif %}
{% empty %}
<span class="list-group-item text-muted">{% trans "None" %}</span>
{% endfor %}
</ul>
{% endblock panel_content %}

View File

@@ -0,0 +1,15 @@
{% extends "ui/panels/_base.html" %}
{% load helpers %}
{% load i18n %}
{% block panel_content %}
<div class="card-body">
{% with url=object|validated_viewname:"list" %}
{% for tag in object.tags.all %}
{% tag tag url %}
{% empty %}
<span class="text-muted">{% trans "No tags assigned" %}</span>
{% endfor %}
{% endwith %}
</div>
{% endblock panel_content %}