1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

added plugin template content injection to primary model detail views

This commit is contained in:
John Anderson
2020-03-15 23:45:18 -04:00
parent 683c5a22db
commit 8364694fb4
24 changed files with 313 additions and 14 deletions

View File

@ -0,0 +1,87 @@
import collections
import inspect
from django.core.exceptions import ImproperlyConfigured
from django.template.loader import get_template
from extras.utils import registry
from .signals import register_detail_page_content_classes
class PluginTemplateContent:
"""
This class is used to register plugin content to be injected into core NetBox templates.
It contains methods that are overriden by plugin authors to return template content.
The `model` attribute on the class defines the which model detail page this class renders
content for. It should be set as a string in the form '<app_label>.<model_name>'.
"""
model = None
def __init__(self, obj):
self.obj = obj
def render(self, template, extra_context=None):
"""
Convenience menthod for rendering the provided template name. The detail page object is automatically
passed into the template context as `obj` but an additional context dictionary may be passed as `extra_context`.
"""
context = {'obj': self.obj}
if isinstance(extra_context, dict):
context.update(extra_context)
return get_template(template).render(context)
def left_page(self):
"""
Content that will be rendered on the left of the detail page view. Content should be returned as an
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
"""
raise NotImplementedError
def right_page(self):
"""
Content that will be rendered on the right of the detail page view. Content should be returned as an
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
"""
raise NotImplementedError
def full_width_page(self):
"""
Content that will be rendered within the full width of the detail page view. Content should be returned as an
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
"""
raise NotImplementedError
def buttons(self):
"""
Buttons that will be rendered and added to the existing list of buttons on the detail page view. Content
should be returned as an HTML string. Note that content does not need to be marked as safe because this is
automatically handled.
"""
raise NotImplementedError
def register_content_classes():
registry.plugin_template_content_classes = collections.defaultdict(list)
responses = register_detail_page_content_classes.send('registration_event')
for receiver, response in responses:
if not isinstance(response, list):
response = [response]
for template_class in response:
if not inspect.isclass(template_class):
raise TypeError('Plugin content class {} was passes as an instance!'.format(template_class))
if not issubclass(template_class, PluginTemplateContent):
raise TypeError('{} is not a subclass of extras.plugins.PluginTemplateContent!'.format(template_class))
if template_class.model is None:
raise TypeError('Plugin content class {} does not define a valid model!'.format(template_class))
registry.plugin_template_content_classes[template_class.model].append(template_class)
def get_content_classes(model):
if not hasattr(registry, 'plugin_template_content_classes'):
register_content_classes()
return registry.plugin_template_content_classes.get(model, [])

View File

@ -27,8 +27,8 @@ class PluginSignal(django.dispatch.Signal):
"""
This signal collects templates which render buttons for object detail pages
This signal collects templates which render content for object detail pages
"""
register_detail_page_buttons = PluginSignal(
register_detail_page_content_classes = PluginSignal(
providing_args=[]
)

View File

@ -2,26 +2,59 @@ from django import template as template_
from django.template.loader import get_template
from django.utils.safestring import mark_safe
from extras.plugins.signals import register_detail_page_buttons
from extras.plugins import get_content_classes
register = template_.Library()
def _get_registered_content(obj, method):
"""
Given an object and a PluginTemplateContent method name, return all the registered content for the
object's model.
"""
html = ''
plugin_template_classes = get_content_classes(obj._meta.label_lower)
for plugin_template_class in plugin_template_classes:
plugin_template_renderer = plugin_template_class(obj)
try:
content = getattr(plugin_template_renderer, method)()
except NotImplementedError:
# This content renderer class does not define content for this method
continue
html += content
return mark_safe(html)
@register.simple_tag()
def plugin_buttons(obj):
"""
Fire signal to collect all buttons registered by plugins
"""
html = ''
responses = register_detail_page_buttons.send(obj)
for receiver, response in responses:
if not isinstance(response, list):
response = [response]
for template in response:
if isinstance(template, str):
template_text = get_template(template).render({'obj': obj})
html += template_text
return _get_registered_content(obj, 'buttons')
return mark_safe(html)
@register.simple_tag()
def plugin_left_page(obj):
"""
Fire signal to collect all left page content registered by plugins
"""
return _get_registered_content(obj, 'left_page')
@register.simple_tag()
def plugin_right_page(obj):
"""
Fire signal to collect all right page content registered by plugins
"""
return _get_registered_content(obj, 'right_page')
@register.simple_tag()
def plugin_full_width_page(obj):
"""
Fire signal to collect all full width page content registered by plugins
"""
return _get_registered_content(obj, 'full_width_page')

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block title %}{{ circuit }}{% endblock %}
@ -28,6 +29,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons circuit %}
{% if perms.circuits.add_circuit %}
{% clone_button circuit %}
{% endif %}
@ -125,10 +127,17 @@
{% endif %}
</div>
</div>
{% plugin_left_page circuit %}
</div>
<div class="col-md-6">
{% include 'circuits/inc/circuit_termination.html' with termination=termination_a side='A' %}
{% include 'circuits/inc/circuit_termination.html' with termination=termination_z side='Z' %}
{% plugin_right_page circuit %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page circuit %}
</div>
</div>
{% endblock %}

View File

@ -3,6 +3,7 @@
{% load static %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block title %}{{ provider }}{% endblock %}
@ -28,6 +29,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons provider %}
{% if show_graphs %}
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ provider.name }}" data-url="{% url 'circuits-api:provider-graphs' pk=provider.pk %}" title="Show graphs">
<i class="fa fa-signal" aria-hidden="true"></i> Graphs
@ -116,6 +118,7 @@
{% endif %}
</div>
</div>
{% plugin_left_page provider %}
</div>
<div class="col-md-8">
<div class="panel panel-default">
@ -132,9 +135,15 @@
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %}
{% plugin_right_page provider %}
</div>
</div>
{% include 'inc/modal.html' with name='graphs' title='Graphs' %}
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page provider %}
</div>
</div>
{% endblock %}
{% block javascript %}

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -13,6 +14,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons cable %}
{% if perms.dcim.change_cable %}
{% edit_button cable %}
{% endif %}
@ -79,6 +81,7 @@
</tr>
</table>
</div>
{% plugin_left_page cable %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
@ -93,6 +96,12 @@
</div>
{% include 'dcim/inc/cable_termination.html' with termination=cable.termination_b %}
</div>
{% plugin_right_page cable %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page cable %}
</div>
</div>
{% endblock %}

View File

@ -333,6 +333,7 @@
{% endif %}
</div>
</div>
{% plugin_left_page device %}
</div>
<div class="col-md-6">
{% if console_ports or power_ports %}
@ -499,6 +500,12 @@
<div class="panel-body text-muted">None found</div>
{% endif %}
</div>
{% plugin_right_page device %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page device %}
</div>
</div>
<div class="row">

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block title %}{{ devicetype.manufacturer }} {{ devicetype.model }}{% endblock %}
@ -16,6 +17,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons devicetype %}
{% if perms.dcim.change_devicetype %}
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -139,6 +141,7 @@
</tr>
</table>
</div>
{% plugin_left_page devicetype %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' with obj=devicetype %}
@ -155,6 +158,7 @@
{% endif %}
</div>
</div>
{% plugin_right_page devicetype %}
</div>
</div>
{% if devicetype.consoleport_templates.exists or devicetype.powerport_templates.exists %}
@ -167,6 +171,11 @@
</div>
</div>
{% endif %}
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page devicetype %}
</div>
</div>
{% if devicetype.is_parent_device or devicebay_table.rows %}
<div class="row">
<div class="col-md-12">

View File

@ -3,6 +3,7 @@
{% load static %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -31,6 +32,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons powerfeed %}
{% if perms.dcim.add_powerfeed %}
{% clone_button powerfeed %}
{% endif %}
@ -123,6 +125,7 @@
</div>
{% include 'inc/custom_fields_panel.html' with obj=powerfeed %}
{% include 'extras/inc/tags_panel.html' with tags=powerfeed.tags.all url='dcim:powerfeed_list' %}
{% plugin_left_page powerfeed %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
@ -164,6 +167,12 @@
{% endif %}
</div>
</div>
{% plugin_right_page powerfeed %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page powerfeed %}
</div>
</div>
{% endblock %}

View File

@ -3,6 +3,7 @@
{% load custom_links %}
{% load helpers %}
{% load static %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -30,6 +31,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons powerpanel %}
{% if perms.dcim.change_powerpanel %}
{% edit_button powerpanel %}
{% endif %}
@ -80,9 +82,16 @@
</tr>
</table>
</div>
{% plugin_left_page powerpanel %}
</div>
<div class="col-md-9">
{% include 'panel_table.html' with table=powerfeed_table heading='Connected Feeds' %}
{% plugin_right_page powerpanel %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page powerpanel %}
</div>
</div>
{% endblock %}

View File

@ -3,6 +3,7 @@
{% load custom_links %}
{% load helpers %}
{% load static %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -27,6 +28,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons rack %}
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}"{% else %}disabled="disabled"{% endif %} class="btn btn-primary">
<span class="fa fa-chevron-left" aria-hidden="true"></span> Previous Rack
</a>
@ -312,6 +314,7 @@
</div>
{% endif %}
</div>
{% plugin_left_page rack %}
</div>
<div class="col-md-6">
<div class="row" style="margin-bottom: 20px">
@ -369,6 +372,12 @@
</div>
{% endif %}
</div>
{% plugin_right_page rack %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page rack %}
</div>
</div>
{% endblock %}

View File

@ -3,6 +3,7 @@
{% load custom_links %}
{% load helpers %}
{% load static %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -27,6 +28,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons rackreservation %}
{% if perms.dcim.change_rackreservation %}
{% edit_button rackreservation %}
{% endif %}
@ -119,6 +121,7 @@
</tr>
</table>
</div>
{% plugin_left_page rackreservation %}
</div>
<div class="col-md-6">
{% with rack=rackreservation.rack %}
@ -137,6 +140,12 @@
</div>
</div>
{% endwith %}
{% plugin_right_page rackreservation %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page rackreservation %}
</div>
</div>
{% endblock %}

View File

@ -5,6 +5,7 @@
{% load plugins %}
{% load static %}
{% load tz %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -214,6 +215,7 @@
{% endif %}
</div>
</div>
{% plugin_left_page site %}
</div>
<div class="col-md-5">
<div class="panel panel-default">
@ -288,9 +290,15 @@
</div>
{% endif %}
</div>
{% plugin_right_page site %}
</div>
</div>
{% include 'inc/modal.html' with name='graphs' title='Graphs' %}
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page site %}
</div>
</div>
{% endblock %}
{% block javascript %}

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -26,6 +27,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons aggregate %}
{% if perms.ipam.add_aggregate %}
{% clone_button aggregate %}
{% endif %}
@ -88,10 +90,17 @@
</tr>
</table>
</div>
{% plugin_left_page aggregate %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' with obj=aggregate %}
{% include 'extras/inc/tags_panel.html' with tags=aggregate.tags.all url='ipam:aggregate_list' %}
{% plugin_right_page aggregate %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page aggregate %}
</div>
</div>
<div class="row">

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -28,6 +29,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons ipaddress %}
{% if perms.ipam.add_ipaddress %}
{% clone_button ipaddress %}
{% endif %}
@ -152,6 +154,7 @@
</div>
{% include 'inc/custom_fields_panel.html' with obj=ipaddress %}
{% include 'extras/inc/tags_panel.html' with tags=ipaddress.tags.all url='ipam:ipaddress_list' %}
{% plugin_left_page ipaddress %}
</div>
<div class="col-md-8">
{% include 'panel_table.html' with table=parent_prefixes_table heading='Parent Prefixes' %}
@ -159,6 +162,12 @@
{% include 'panel_table.html' with table=duplicate_ips_table heading='Duplicate IP Addresses' panel_class='danger' %}
{% endif %}
{% include 'utilities/obj_table.html' with table=related_ips_table table_template='panel_table.html' heading='Related IP Addresses' panel_class='default noprint' %}
{% plugin_right_page ipaddress %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page ipaddress %}
</div>
</div>
{% endblock %}

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -28,6 +29,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons prefix %}
{% if perms.ipam.add_prefix and active_tab == 'prefixes' and first_available_prefix %}
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ prefix.vrf.pk }}&site={{ prefix.site.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
@ -187,12 +189,19 @@
</div>
{% include 'inc/custom_fields_panel.html' with obj=prefix %}
{% include 'extras/inc/tags_panel.html' with tags=prefix.tags.all url='ipam:prefix_list' %}
{% plugin_left_page prefix %}
</div>
<div class="col-md-7">
{% if duplicate_prefix_table.rows %}
{% include 'panel_table.html' with table=duplicate_prefix_table heading='Duplicate Prefixes' panel_class='danger' %}
{% endif %}
{% include 'panel_table.html' with table=parent_prefix_table heading='Parent Prefixes' panel_class='default' %}
{% plugin_right_page prefix %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page prefix %}
</div>
</div>
{% endblock %}

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row noprint">
@ -26,6 +27,7 @@
</div>
</div>
<div class="pull-right">
{% plugin_buttons service %}
{% if perms.dcim.change_service %}
{% edit_button service %}
{% endif %}
@ -81,6 +83,15 @@
</div>
{% include 'inc/custom_fields_panel.html' with obj=service %}
{% include 'extras/inc/tags_panel.html' with tags=service.tags.all url='ipam:service_list' %}
</div>
{% plugin_left_page service %}
</div>
<div class="col-md-6">
{% plugin_right_page service %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page service %}
</div>
</div>
{% endblock %}

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -31,6 +32,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons vlan %}
{% if perms.ipam.add_vlan %}
{% clone_button vlan %}
{% endif %}
@ -139,6 +141,7 @@
</div>
{% include 'inc/custom_fields_panel.html' with obj=vlan %}
{% include 'extras/inc/tags_panel.html' with tags=vlan.tags.all url='ipam:vlan_list' %}
{% plugin_left_page vlan %}
</div>
<div class="col-md-8">
<div class="panel panel-default">
@ -155,6 +158,12 @@
</div>
{% endif %}
</div>
{% plugin_right_page vlan %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page vlan %}
</div>
</div>
{% endblock %}

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -25,6 +26,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons vrf %}
{% if perms.ipam.add_vrf %}
{% clone_button vrf %}
{% endif %}
@ -97,9 +99,16 @@
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=vrf.tags.all url='ipam:vrf_list' %}
{% plugin_left_page vrf %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' with obj=vrf %}
{% plugin_right_page vrf %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page vrf %}
</div>
</div>
{% endblock %}

View File

@ -4,6 +4,7 @@
{% load helpers %}
{% load secret_helpers %}
{% load static %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -16,6 +17,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons secret %}
{% if perms.secrets.change_secret %}
{% edit_button secret %}
{% endif %}
@ -65,6 +67,7 @@
</table>
</div>
{% include 'inc/custom_fields_panel.html' with obj=secret %}
{% plugin_left_page secret %}
</div>
<div class="col-md-6">
{% if secret|decryptable_by:request.user %}
@ -100,6 +103,12 @@
</div>
{% endif %}
{% include 'extras/inc/tags_panel.html' with tags=secret.tags.all url='secrets:secret_list' %}
{% plugin_right_page secret %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page secret %}
</div>
</div>

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -28,6 +29,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons tenant %}
{% if perms.tenancy.add_tenant %}
{% clone_button tenant %}
{% endif %}
@ -93,6 +95,7 @@
{% endif %}
</div>
</div>
{% plugin_left_page tenant %}
</div>
<div class="col-md-5">
<div class="panel panel-default">
@ -146,6 +149,12 @@
</div>
</div>
</div>
{% plugin_right_page tenant %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page tenant %}
</div>
</div>
{% endblock %}

View File

@ -2,6 +2,7 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint" xmlns="http://www.w3.org/1999/html">
@ -28,6 +29,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons cluster %}
{% if perms.virtualization.add_cluster %}
{% clone_button cluster %}
{% endif %}
@ -121,6 +123,7 @@
{% endif %}
</div>
</div>
{% plugin_left_page cluster %}
</div>
<div class="col-md-7">
<div class="panel panel-default">
@ -148,6 +151,12 @@
</form>
{% endif %}
</div>
{% plugin_right_page cluster %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page cluster %}
</div>
</div>
{% endblock %}

View File

@ -3,6 +3,7 @@
{% load custom_links %}
{% load static %}
{% load helpers %}
{% load plugins %}
{% block header %}
<div class="row noprint">
@ -28,6 +29,7 @@
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons virtualmachine %}
{% if perms.virtualization.add_virtualmachine %}
{% clone_button virtualmachine %}
{% endif %}
@ -158,6 +160,7 @@
{% endif %}
</div>
</div>
{% plugin_left_page virtualmachine %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
@ -235,6 +238,12 @@
</div>
{% endif %}
</div>
{% plugin_right_page virtualmachine %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page virtualmachine %}
</div>
</div>
<div class="row">