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:
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.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)
|
||||
|
||||
|
||||
#
|
||||
|
@@ -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()
|
||||
|
@@ -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')),
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user