1
0
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:
jeremystretch
2023-03-21 17:00:06 -04:00
parent d6afc125e5
commit 00088cba6d
11 changed files with 154 additions and 65 deletions

View 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
})

View File

@@ -26,6 +26,7 @@ from netbox.api.viewsets import NetBoxModelViewSet
from utilities.exceptions import RQWorkerNotRunningException
from utilities.utils import copy_safe_request, count_related
from . import serializers
from .mixins import ConfigTemplateRenderMixin
from .nested_serializers import NestedConfigTemplateSerializer
@@ -37,28 +38,6 @@ class ExtrasRootView(APIRootView):
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
#
@@ -165,7 +144,7 @@ class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
# Config templates
#
class ConfigTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
queryset = ConfigTemplate.objects.prefetch_related('data_source', 'data_file')
serializer_class = serializers.ConfigTemplateSerializer
filterset_class = filtersets.ConfigTemplateFilterSet
@@ -177,17 +156,9 @@ class ConfigTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
return the raw rendered content, rather than serialized JSON.
"""
configtemplate = self.get_object()
output = configtemplate.render(context=request.data)
context = request.data
# 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
})
return self.render_configtemplate(request, configtemplate, context)
#

View File

@@ -306,12 +306,26 @@ class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
fieldsets = (
('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:
model = ConfigTemplate
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):
super().clean()

View File

@@ -22,7 +22,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=100)),
('description', models.CharField(blank=True, max_length=200)),
('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_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')),

View File

@@ -209,7 +209,12 @@ class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLog
)
environment_params = models.JSONField(
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:
@@ -235,11 +240,7 @@ class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLog
# Initialize the Jinja2 environment and instantiate the Template
environment = self._get_environment()
if self.data_file:
template = environment.get_template(self.data_file.path)
else:
template = environment.from_string(self.template_code)
template = environment.from_string(self.template_code)
output = template.render(**context)
# Replace CRLF-style line terminators
@@ -259,7 +260,8 @@ class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLog
loader = BaseLoader()
# 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)
return environment