mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
#11559: Add device config API endpoint & cleanup
This commit is contained in:
@ -12,7 +12,7 @@ click ConfigTemplate "../../models/extras/configtemplate/"
|
|||||||
|
|
||||||
## Configuration Templates
|
## Configuration Templates
|
||||||
|
|
||||||
Configuration templates are written in the [Jinja2 templating language](https://jinja.palletsprojects.com/), and may be automatically populated from remote data sources. Context data is applied to a template during rendering to output a complete configuration file. Below is an example template.
|
Configuration templates are written in the [Jinja2 templating language](https://jinja.palletsprojects.com/), and may be automatically populated from remote data sources. Context data is applied to a template during rendering to output a complete configuration file. Below is an example Jinja2 template which renders a simple network switch configuration file.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{% extends 'base.j2' %}
|
{% extends 'base.j2' %}
|
||||||
@ -36,3 +36,44 @@ Configuration templates are written in the [Jinja2 templating language](https://
|
|||||||
```
|
```
|
||||||
|
|
||||||
When rendered for a specific NetBox device, the template's `device` variable will be populated with the device instance, and `ntp_servers` will be pulled from the device's available context data. The resulting output will be a valid configuration segment that can be applied directly to a compatible network device.
|
When rendered for a specific NetBox device, the template's `device` variable will be populated with the device instance, and `ntp_servers` will be pulled from the device's available context data. The resulting output will be a valid configuration segment that can be applied directly to a compatible network device.
|
||||||
|
|
||||||
|
## Rendering Templates
|
||||||
|
|
||||||
|
### Device Configurations
|
||||||
|
|
||||||
|
NetBox provides a REST API endpoint specifically for rendering the default configuration template for a specific device. This is accomplished by sending a POST request to the device's unique URL, optionally including additional context data.
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: Token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json; indent=4" \
|
||||||
|
http://netbox:8000/api/dcim/devices/123/render-config/ \
|
||||||
|
--data '{
|
||||||
|
"extra_data": "abc123"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
This request will trigger resolution of the device's preferred config template in the following order:
|
||||||
|
|
||||||
|
* The config template assigned to the individual device
|
||||||
|
* The config template assigned to the device's role
|
||||||
|
* The config template assigned to the device's platform
|
||||||
|
|
||||||
|
If no config template has been assigned to any of these three objects, the request will fail.
|
||||||
|
|
||||||
|
### General Purpose Use
|
||||||
|
|
||||||
|
NetBox config templates can also be rendered without being tied to any specific device, using a separate general purpose REST API endpoint. Any data included with a POST request to this endpoint will be passed as context data for the template.
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: Token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json; indent=4" \
|
||||||
|
http://netbox:8000/api/extras/config-templates/123/render/ \
|
||||||
|
--data '{
|
||||||
|
"foo": "abc",
|
||||||
|
"bar": 123
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Configuration Templates
|
# Configuration Templates
|
||||||
|
|
||||||
Configuration templates can be used to render [devices](../dcim/device.md) configurations from [context data](../../features/context-data.md). Templates are written in the [Jinja2 language](https://jinja.palletsprojects.com/) and can be associated with devices roles, platforms, and/or individual devices.
|
Configuration templates can be used to render [device](../dcim/device.md) configurations from [context data](../../features/context-data.md). Templates are written in the [Jinja2 language](https://jinja.palletsprojects.com/) and can be associated with devices roles, platforms, and/or individual devices.
|
||||||
|
|
||||||
Context data is made available to [devices](../dcim/device.md) and/or [virtual machines](../virtualization/virtualmachine.md) based on their relationships to other objects in NetBox. For example, context data can be associated only with devices assigned to a particular site, or only to virtual machines in a certain cluster.
|
Context data is made available to [devices](../dcim/device.md) and/or [virtual machines](../virtualization/virtualmachine.md) based on their relationships to other objects in NetBox. For example, context data can be associated only with devices assigned to a particular site, or only to virtual machines in a certain cluster.
|
||||||
|
|
||||||
|
@ -604,6 +604,7 @@ class InventoryItemTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class DeviceRoleSerializer(NetBoxModelSerializer):
|
class DeviceRoleSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
|
||||||
|
config_template = NestedConfigTemplateSerializer(required=False, allow_null=True, default=None)
|
||||||
device_count = serializers.IntegerField(read_only=True)
|
device_count = serializers.IntegerField(read_only=True)
|
||||||
virtualmachine_count = serializers.IntegerField(read_only=True)
|
virtualmachine_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
@ -618,6 +619,7 @@ class DeviceRoleSerializer(NetBoxModelSerializer):
|
|||||||
class PlatformSerializer(NetBoxModelSerializer):
|
class PlatformSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
|
||||||
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
|
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
|
||||||
|
config_template = NestedConfigTemplateSerializer(required=False, allow_null=True, default=None)
|
||||||
device_count = serializers.IntegerField(read_only=True)
|
device_count = serializers.IntegerField(read_only=True)
|
||||||
virtualmachine_count = serializers.IntegerField(read_only=True)
|
virtualmachine_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import socket
|
from django.http import Http404, HttpResponse
|
||||||
|
|
||||||
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
from drf_yasg.openapi import Parameter
|
from drf_yasg.openapi import Parameter
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.status import HTTP_400_BAD_REQUEST
|
||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
from rest_framework.viewsets import ViewSet
|
from rest_framework.viewsets import ViewSet
|
||||||
|
|
||||||
@ -15,14 +15,14 @@ from dcim import filtersets
|
|||||||
from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
|
from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from dcim.svg import CableTraceSVG
|
from dcim.svg import CableTraceSVG
|
||||||
from extras.api.views import ConfigContextQuerySetMixin
|
from extras.api.nested_serializers import NestedConfigTemplateSerializer
|
||||||
|
from extras.api.mixins import ConfigContextQuerySetMixin, ConfigTemplateRenderMixin
|
||||||
from ipam.models import Prefix, VLAN
|
from ipam.models import Prefix, VLAN
|
||||||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||||
from netbox.api.exceptions import ServiceUnavailable
|
|
||||||
from netbox.api.metadata import ContentTypeMetadata
|
from netbox.api.metadata import ContentTypeMetadata
|
||||||
from netbox.api.pagination import StripCountAnnotationsPaginator
|
from netbox.api.pagination import StripCountAnnotationsPaginator
|
||||||
|
from netbox.api.renderers import TextRenderer
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet
|
||||||
from netbox.config import get_config
|
|
||||||
from netbox.constants import NESTED_SERIALIZER_PREFIX
|
from netbox.constants import NESTED_SERIALIZER_PREFIX
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
@ -391,10 +391,10 @@ class PlatformViewSet(NetBoxModelViewSet):
|
|||||||
# Devices/modules
|
# Devices/modules
|
||||||
#
|
#
|
||||||
|
|
||||||
class DeviceViewSet(ConfigContextQuerySetMixin, NetBoxModelViewSet):
|
class DeviceViewSet(ConfigContextQuerySetMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
|
||||||
queryset = Device.objects.prefetch_related(
|
queryset = Device.objects.prefetch_related(
|
||||||
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
|
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
|
||||||
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',
|
||||||
)
|
)
|
||||||
filterset_class = filtersets.DeviceFilterSet
|
filterset_class = filtersets.DeviceFilterSet
|
||||||
pagination_class = StripCountAnnotationsPaginator
|
pagination_class = StripCountAnnotationsPaginator
|
||||||
@ -419,6 +419,19 @@ class DeviceViewSet(ConfigContextQuerySetMixin, NetBoxModelViewSet):
|
|||||||
|
|
||||||
return serializers.DeviceWithConfigContextSerializer
|
return serializers.DeviceWithConfigContextSerializer
|
||||||
|
|
||||||
|
@action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer])
|
||||||
|
def render_config(self, request, pk):
|
||||||
|
"""
|
||||||
|
Resolve and render the preferred ConfigTemplate for this Device.
|
||||||
|
"""
|
||||||
|
device = self.get_object()
|
||||||
|
configtemplate = device.get_config_template()
|
||||||
|
if not configtemplate:
|
||||||
|
return Response({'error': 'No config template found for this device.'}, status=HTTP_400_BAD_REQUEST)
|
||||||
|
context = {**request.data, 'device': device}
|
||||||
|
|
||||||
|
return self.render_configtemplate(request, configtemplate, context)
|
||||||
|
|
||||||
|
|
||||||
class VirtualDeviceContextViewSet(NetBoxModelViewSet):
|
class VirtualDeviceContextViewSet(NetBoxModelViewSet):
|
||||||
queryset = VirtualDeviceContext.objects.prefetch_related(
|
queryset = VirtualDeviceContext.objects.prefetch_related(
|
||||||
|
@ -1987,6 +1987,17 @@ class DeviceInventoryView(DeviceComponentsView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'configcontext', path='config-context')
|
||||||
|
class DeviceConfigContextView(ObjectConfigContextView):
|
||||||
|
queryset = Device.objects.annotate_config_context_data()
|
||||||
|
base_template = 'dcim/device/base.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Config Context'),
|
||||||
|
permission='extras.view_configcontext',
|
||||||
|
weight=2000
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Device, 'render-config')
|
@register_model_view(Device, 'render-config')
|
||||||
class DeviceRenderConfigView(generic.ObjectView):
|
class DeviceRenderConfigView(generic.ObjectView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
@ -1994,7 +2005,7 @@ class DeviceRenderConfigView(generic.ObjectView):
|
|||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Render Config'),
|
label=_('Render Config'),
|
||||||
permission='extras.view_configtemplate',
|
permission='extras.view_configtemplate',
|
||||||
weight=2000
|
weight=2100
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
@ -2020,17 +2031,6 @@ class DeviceRenderConfigView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Device, 'configcontext', path='config-context')
|
|
||||||
class DeviceConfigContextView(ObjectConfigContextView):
|
|
||||||
queryset = Device.objects.annotate_config_context_data()
|
|
||||||
base_template = 'dcim/device/base.html'
|
|
||||||
tab = ViewTab(
|
|
||||||
label=_('Config Context'),
|
|
||||||
permission='extras.view_configcontext',
|
|
||||||
weight=2100
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkImportView(generic.BulkImportView):
|
class DeviceBulkImportView(generic.BulkImportView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
model_form = forms.DeviceImportForm
|
model_form = forms.DeviceImportForm
|
||||||
|
46
netbox/extras/api/mixins.py
Normal file
46
netbox/extras/api/mixins.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from .nested_serializers import NestedConfigTemplateSerializer
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'ConfigContextQuerySetMixin',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextQuerySetMixin:
|
||||||
|
"""
|
||||||
|
Used by views that work with config context models (device and virtual machine).
|
||||||
|
Provides a get_queryset() method which deals with adding the config context
|
||||||
|
data annotation or not.
|
||||||
|
"""
|
||||||
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
|
Build the proper queryset based on the request context
|
||||||
|
|
||||||
|
If the `brief` query param equates to True or the `exclude` query param
|
||||||
|
includes `config_context` as a value, return the base queryset.
|
||||||
|
|
||||||
|
Else, return the queryset annotated with config context data
|
||||||
|
"""
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
request = self.get_serializer_context()['request']
|
||||||
|
if self.brief or 'config_context' in request.query_params.get('exclude', []):
|
||||||
|
return queryset
|
||||||
|
return queryset.annotate_config_context_data()
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigTemplateRenderMixin:
|
||||||
|
|
||||||
|
def render_configtemplate(self, request, configtemplate, context):
|
||||||
|
output = configtemplate.render(context=context)
|
||||||
|
|
||||||
|
# If the client has requested "text/plain", return the raw content.
|
||||||
|
if request.accepted_renderer.format == 'txt':
|
||||||
|
return Response(output)
|
||||||
|
|
||||||
|
template_serializer = NestedConfigTemplateSerializer(configtemplate, context={'request': request})
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'configtemplate': template_serializer.data,
|
||||||
|
'content': output
|
||||||
|
})
|
@ -26,6 +26,7 @@ from netbox.api.viewsets import NetBoxModelViewSet
|
|||||||
from utilities.exceptions import RQWorkerNotRunningException
|
from utilities.exceptions import RQWorkerNotRunningException
|
||||||
from utilities.utils import copy_safe_request, count_related
|
from utilities.utils import copy_safe_request, count_related
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
from .mixins import ConfigTemplateRenderMixin
|
||||||
from .nested_serializers import NestedConfigTemplateSerializer
|
from .nested_serializers import NestedConfigTemplateSerializer
|
||||||
|
|
||||||
|
|
||||||
@ -37,28 +38,6 @@ class ExtrasRootView(APIRootView):
|
|||||||
return 'Extras'
|
return 'Extras'
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextQuerySetMixin:
|
|
||||||
"""
|
|
||||||
Used by views that work with config context models (device and virtual machine).
|
|
||||||
Provides a get_queryset() method which deals with adding the config context
|
|
||||||
data annotation or not.
|
|
||||||
"""
|
|
||||||
def get_queryset(self):
|
|
||||||
"""
|
|
||||||
Build the proper queryset based on the request context
|
|
||||||
|
|
||||||
If the `brief` query param equates to True or the `exclude` query param
|
|
||||||
includes `config_context` as a value, return the base queryset.
|
|
||||||
|
|
||||||
Else, return the queryset annotated with config context data
|
|
||||||
"""
|
|
||||||
queryset = super().get_queryset()
|
|
||||||
request = self.get_serializer_context()['request']
|
|
||||||
if self.brief or 'config_context' in request.query_params.get('exclude', []):
|
|
||||||
return queryset
|
|
||||||
return queryset.annotate_config_context_data()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Webhooks
|
# Webhooks
|
||||||
#
|
#
|
||||||
@ -165,7 +144,7 @@ class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
|
|||||||
# Config templates
|
# Config templates
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConfigTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
|
class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
|
||||||
queryset = ConfigTemplate.objects.prefetch_related('data_source', 'data_file')
|
queryset = ConfigTemplate.objects.prefetch_related('data_source', 'data_file')
|
||||||
serializer_class = serializers.ConfigTemplateSerializer
|
serializer_class = serializers.ConfigTemplateSerializer
|
||||||
filterset_class = filtersets.ConfigTemplateFilterSet
|
filterset_class = filtersets.ConfigTemplateFilterSet
|
||||||
@ -177,17 +156,9 @@ class ConfigTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
|
|||||||
return the raw rendered content, rather than serialized JSON.
|
return the raw rendered content, rather than serialized JSON.
|
||||||
"""
|
"""
|
||||||
configtemplate = self.get_object()
|
configtemplate = self.get_object()
|
||||||
output = configtemplate.render(context=request.data)
|
context = request.data
|
||||||
|
|
||||||
# If the client has requested "text/plain", return the raw content.
|
return self.render_configtemplate(request, configtemplate, context)
|
||||||
if request.accepted_renderer.format == 'txt':
|
|
||||||
return Response(output)
|
|
||||||
|
|
||||||
template_serializer = NestedConfigTemplateSerializer(configtemplate, context={'request': request})
|
|
||||||
return Response({
|
|
||||||
'configtemplate': template_serializer.data,
|
|
||||||
'content': output
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -306,12 +306,26 @@ class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Config Template', ('name', 'description', 'environment_params', 'tags')),
|
('Config Template', ('name', 'description', 'environment_params', 'tags')),
|
||||||
('Content', ('data_source', 'data_file', 'template_code',)),
|
('Content', ('template_code',)),
|
||||||
|
('Data Source', ('data_source', 'data_file')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConfigTemplate
|
model = ConfigTemplate
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
widgets = {
|
||||||
|
'environment_params': forms.Textarea(attrs={'rows': 5})
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable content field when a DataFile has been set
|
||||||
|
if self.instance.data_file:
|
||||||
|
self.fields['template_code'].widget.attrs['readonly'] = True
|
||||||
|
self.fields['template_code'].help_text = _(
|
||||||
|
'Template content is populated from the remote source selected below.'
|
||||||
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
@ -22,7 +22,7 @@ class Migration(migrations.Migration):
|
|||||||
('name', models.CharField(max_length=100)),
|
('name', models.CharField(max_length=100)),
|
||||||
('description', models.CharField(blank=True, max_length=200)),
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
('template_code', models.TextField()),
|
('template_code', models.TextField()),
|
||||||
('environment_params', models.JSONField(blank=True, null=True)),
|
('environment_params', models.JSONField(blank=True, default=dict, null=True)),
|
||||||
('data_file', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='core.datafile')),
|
('data_file', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='core.datafile')),
|
||||||
('data_source', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='core.datasource')),
|
('data_source', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='core.datasource')),
|
||||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
@ -209,7 +209,12 @@ class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLog
|
|||||||
)
|
)
|
||||||
environment_params = models.JSONField(
|
environment_params = models.JSONField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True,
|
||||||
|
default=dict,
|
||||||
|
help_text=_(
|
||||||
|
'Any <a href="https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.Environment">additional parameters</a>'
|
||||||
|
' to pass when constructing the Jinja2 environment.'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -235,11 +240,7 @@ class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLog
|
|||||||
|
|
||||||
# Initialize the Jinja2 environment and instantiate the Template
|
# Initialize the Jinja2 environment and instantiate the Template
|
||||||
environment = self._get_environment()
|
environment = self._get_environment()
|
||||||
if self.data_file:
|
template = environment.from_string(self.template_code)
|
||||||
template = environment.get_template(self.data_file.path)
|
|
||||||
else:
|
|
||||||
template = environment.from_string(self.template_code)
|
|
||||||
|
|
||||||
output = template.render(**context)
|
output = template.render(**context)
|
||||||
|
|
||||||
# Replace CRLF-style line terminators
|
# Replace CRLF-style line terminators
|
||||||
@ -259,7 +260,8 @@ class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLog
|
|||||||
loader = BaseLoader()
|
loader = BaseLoader()
|
||||||
|
|
||||||
# Initialize the environment
|
# Initialize the environment
|
||||||
environment = SandboxedEnvironment(loader=loader)
|
env_params = self.environment_params or {}
|
||||||
|
environment = SandboxedEnvironment(loader=loader, **env_params)
|
||||||
environment.filters.update(get_config().JINJA2_FILTERS)
|
environment.filters.update(get_config().JINJA2_FILTERS)
|
||||||
|
|
||||||
return environment
|
return environment
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.api.views import ConfigContextQuerySetMixin
|
from extras.api.mixins import ConfigContextQuerySetMixin
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from virtualization import filtersets
|
from virtualization import filtersets
|
||||||
|
Reference in New Issue
Block a user