mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
Fixes 18208: Consolidate rendering configuration templates (#18604)
This commit is contained in:
parent
e1d1aab4bd
commit
3e1cc0d7f3
@ -4,17 +4,15 @@ from django.core.paginator import EmptyPage, PageNotAnInteger
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
|
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, modelformset_factory
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from jinja2.exceptions import TemplateError
|
|
||||||
|
|
||||||
from circuits.models import Circuit, CircuitTermination
|
from circuits.models import Circuit, CircuitTermination
|
||||||
from extras.views import ObjectConfigContextView
|
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
||||||
from ipam.models import ASN, IPAddress, Prefix, VLANGroup
|
from ipam.models import ASN, IPAddress, Prefix, VLANGroup
|
||||||
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||||
@ -2253,54 +2251,14 @@ class DeviceConfigContextView(ObjectConfigContextView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(Device, 'render-config')
|
@register_model_view(Device, 'render-config')
|
||||||
class DeviceRenderConfigView(generic.ObjectView):
|
class DeviceRenderConfigView(ObjectRenderConfigView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
template_name = 'dcim/device/render_config.html'
|
base_template = 'dcim/device/base.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Render Config'),
|
label=_('Render Config'),
|
||||||
weight=2100
|
weight=2100,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
|
||||||
instance = self.get_object(**kwargs)
|
|
||||||
context = self.get_extra_context(request, instance)
|
|
||||||
|
|
||||||
# If a direct export has been requested, return the rendered template content as a
|
|
||||||
# downloadable file.
|
|
||||||
if request.GET.get('export'):
|
|
||||||
content = context['rendered_config'] or context['error_message']
|
|
||||||
response = HttpResponse(content, content_type='text')
|
|
||||||
filename = f"{instance.name or 'config'}.txt"
|
|
||||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
|
||||||
return response
|
|
||||||
|
|
||||||
return render(request, self.get_template_name(), {
|
|
||||||
'object': instance,
|
|
||||||
'tab': self.tab,
|
|
||||||
**context,
|
|
||||||
})
|
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
|
||||||
# Compile context data
|
|
||||||
context_data = instance.get_config_context()
|
|
||||||
context_data.update({'device': instance})
|
|
||||||
|
|
||||||
# Render the config template
|
|
||||||
rendered_config = None
|
|
||||||
error_message = None
|
|
||||||
if config_template := instance.get_config_template():
|
|
||||||
try:
|
|
||||||
rendered_config = config_template.render(context=context_data)
|
|
||||||
except TemplateError as e:
|
|
||||||
error_message = _("An error occurred while rendering the template: {error}").format(error=e)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'config_template': config_template,
|
|
||||||
'context_data': context_data,
|
|
||||||
'rendered_config': rendered_config,
|
|
||||||
'error_message': error_message,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Device, 'virtual-machines')
|
@register_model_view(Device, 'virtual-machines')
|
||||||
class DeviceVirtualMachinesView(generic.ObjectChildrenView):
|
class DeviceVirtualMachinesView(generic.ObjectChildrenView):
|
||||||
|
@ -10,6 +10,7 @@ from django.utils import timezone
|
|||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from jinja2.exceptions import TemplateError
|
||||||
|
|
||||||
from core.choices import ManagedFileRootPathChoices
|
from core.choices import ManagedFileRootPathChoices
|
||||||
from core.forms import ManagedFileForm
|
from core.forms import ManagedFileForm
|
||||||
@ -885,6 +886,61 @@ class ConfigTemplateBulkSyncDataView(generic.BulkSyncDataView):
|
|||||||
queryset = ConfigTemplate.objects.all()
|
queryset = ConfigTemplate.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectRenderConfigView(generic.ObjectView):
|
||||||
|
base_template = None
|
||||||
|
template_name = 'extras/object_render_config.html'
|
||||||
|
|
||||||
|
def get(self, request, **kwargs):
|
||||||
|
instance = self.get_object(**kwargs)
|
||||||
|
context = self.get_extra_context(request, instance)
|
||||||
|
|
||||||
|
# If a direct export has been requested, return the rendered template content as a
|
||||||
|
# downloadable file.
|
||||||
|
if request.GET.get('export'):
|
||||||
|
content = context['rendered_config'] or context['error_message']
|
||||||
|
response = HttpResponse(content, content_type='text')
|
||||||
|
filename = f"{instance.name or 'config'}.txt"
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||||
|
return response
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
self.get_template_name(),
|
||||||
|
{
|
||||||
|
'object': instance,
|
||||||
|
'tab': self.tab,
|
||||||
|
**context,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context_data(self, request, instance):
|
||||||
|
return {
|
||||||
|
f'{instance._meta.model_name}': instance,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
# Compile context data
|
||||||
|
context_data = instance.get_config_context()
|
||||||
|
context_data.update(self.get_extra_context_data(request, instance))
|
||||||
|
|
||||||
|
# Render the config template
|
||||||
|
rendered_config = None
|
||||||
|
error_message = None
|
||||||
|
if config_template := instance.get_config_template():
|
||||||
|
try:
|
||||||
|
rendered_config = config_template.render(context=context_data)
|
||||||
|
except TemplateError as e:
|
||||||
|
error_message = _("An error occurred while rendering the template: {error}").format(error=e)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'base_template': self.base_template,
|
||||||
|
'config_template': config_template,
|
||||||
|
'context_data': context_data,
|
||||||
|
'rendered_config': rendered_config,
|
||||||
|
'error_message': error_message,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Image attachments
|
# Image attachments
|
||||||
#
|
#
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{% extends 'dcim/device/base.html' %}
|
{% extends base_template %}
|
||||||
|
{% load helpers %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
@ -67,7 +68,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
{% trans "No configuration template has been assigned for this device." %}
|
{% trans "No configuration template has been assigned." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
@ -1,75 +0,0 @@
|
|||||||
{% extends 'virtualization/virtualmachine/base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{{ object }} - {% trans "Config" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-5">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header">{% trans "Config Template" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Config Template" %}</th>
|
|
||||||
<td>{{ config_template|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Data Source" %}</th>
|
|
||||||
<td>{{ config_template.data_file.source|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Data File" %}</th>
|
|
||||||
<td>{{ config_template.data_file|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-7">
|
|
||||||
<div class="card">
|
|
||||||
<div class="accordion accordion-flush" id="renderConfig">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="renderConfigHeading">
|
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsedRenderConfig" aria-expanded="false" aria-controls="collapsedRenderConfig">
|
|
||||||
{% trans "Context Data" %}
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="collapsedRenderConfig" class="accordion-collapse collapse" aria-labelledby="renderConfigHeading" data-bs-parent="#renderConfig">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<pre class="card-body">{{ context_data|pprint }}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
{% if config_template %}
|
|
||||||
{% if rendered_config %}
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="card-header d-flex justify-content-between">
|
|
||||||
{% trans "Rendered Config" %}
|
|
||||||
<a href="?export=True" class="btn btn-primary lh-1" role="button">
|
|
||||||
<i class="mdi mdi-download" aria-hidden="true"></i> {% trans "Download" %}
|
|
||||||
</a>
|
|
||||||
</h2>
|
|
||||||
<pre class="card-body">{{ rendered_config }}</pre>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<h4 class="alert-title mb-1">{% trans "Error rendering template" %}</h4>
|
|
||||||
{% trans error_message %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<div class="alert alert-info">
|
|
||||||
{% trans "No configuration template has been assigned for this virtual machine." %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,17 +1,15 @@
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Prefetch, Sum
|
from django.db.models import Prefetch, Sum
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from jinja2.exceptions import TemplateError
|
|
||||||
|
|
||||||
from dcim.filtersets import DeviceFilterSet
|
from dcim.filtersets import DeviceFilterSet
|
||||||
from dcim.forms import DeviceFilterForm
|
from dcim.forms import DeviceFilterForm
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from dcim.tables import DeviceTable
|
from dcim.tables import DeviceTable
|
||||||
from extras.views import ObjectConfigContextView
|
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||||
@ -427,54 +425,14 @@ class VirtualMachineConfigContextView(ObjectConfigContextView):
|
|||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualMachine, 'render-config')
|
@register_model_view(VirtualMachine, 'render-config')
|
||||||
class VirtualMachineRenderConfigView(generic.ObjectView):
|
class VirtualMachineRenderConfigView(ObjectRenderConfigView):
|
||||||
queryset = VirtualMachine.objects.all()
|
queryset = VirtualMachine.objects.all()
|
||||||
template_name = 'virtualization/virtualmachine/render_config.html'
|
base_template = 'virtualization/virtualmachine/base.html'
|
||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Render Config'),
|
label=_('Render Config'),
|
||||||
weight=2100
|
weight=2100,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
|
||||||
instance = self.get_object(**kwargs)
|
|
||||||
context = self.get_extra_context(request, instance)
|
|
||||||
|
|
||||||
# If a direct export has been requested, return the rendered template content as a
|
|
||||||
# downloadable file.
|
|
||||||
if request.GET.get('export'):
|
|
||||||
content = context['rendered_config'] or context['error_message']
|
|
||||||
response = HttpResponse(content, content_type='text')
|
|
||||||
filename = f"{instance.name or 'config'}.txt"
|
|
||||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
|
||||||
return response
|
|
||||||
|
|
||||||
return render(request, self.get_template_name(), {
|
|
||||||
'object': instance,
|
|
||||||
'tab': self.tab,
|
|
||||||
**context,
|
|
||||||
})
|
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
|
||||||
# Compile context data
|
|
||||||
context_data = instance.get_config_context()
|
|
||||||
context_data.update({'virtualmachine': instance})
|
|
||||||
|
|
||||||
# Render the config template
|
|
||||||
rendered_config = None
|
|
||||||
error_message = None
|
|
||||||
if config_template := instance.get_config_template():
|
|
||||||
try:
|
|
||||||
rendered_config = config_template.render(context=context_data)
|
|
||||||
except TemplateError as e:
|
|
||||||
error_message = _("An error occurred while rendering the template: {error}").format(error=e)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'config_template': config_template,
|
|
||||||
'context_data': context_data,
|
|
||||||
'rendered_config': rendered_config,
|
|
||||||
'error_message': error_message,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(VirtualMachine, 'add', detail=False)
|
@register_model_view(VirtualMachine, 'add', detail=False)
|
||||||
@register_model_view(VirtualMachine, 'edit')
|
@register_model_view(VirtualMachine, 'edit')
|
||||||
|
Loading…
Reference in New Issue
Block a user