Browse Source

Fixes 18208: Consolidate rendering configuration templates (#18604)

Alexander Haase 1 year ago
parent
commit
3e1cc0d7f3

+ 4 - 46
netbox/dcim/views.py

@@ -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):

+ 56 - 0
netbox/extras/views.py

@@ -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
 #
 #

+ 3 - 2
netbox/templates/dcim/device/render_config.html → netbox/templates/extras/object_render_config.html

@@ -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>

+ 0 - 75
netbox/templates/virtualization/virtualmachine/render_config.html

@@ -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 %}

+ 4 - 46
netbox/virtualization/views.py

@@ -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')