from jinja2.exceptions import TemplateError from rest_framework.decorators import action from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.status import HTTP_400_BAD_REQUEST from netbox.api.renderers import TextRenderer from .nested_serializers import NestedConfigTemplateSerializer __all__ = ( 'ConfigContextQuerySetMixin', 'ConfigTemplateRenderMixin', 'RenderConfigMixin', ) 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: """ Provides a method to return a rendered ConfigTemplate as REST API data. """ def render_configtemplate(self, request, configtemplate, context): try: output = configtemplate.render(context=context) except TemplateError as e: return Response({ 'detail': f"An error occurred while rendering the template (line {e.lineno}): {e}" }, status=500) # 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 }) class RenderConfigMixin(ConfigTemplateRenderMixin): """ Provides a /render-config/ endpoint for REST API views whose model may have a ConfigTemplate assigned. """ @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. """ instance = self.get_object() object_type = instance._meta.model_name configtemplate = instance.get_config_template() if not configtemplate: return Response({ 'error': f'No config template found for this {object_type}.' }, status=HTTP_400_BAD_REQUEST) # Compile context data context_data = instance.get_config_context() context_data.update(request.data) context_data.update({object_type: instance}) return self.render_configtemplate(request, configtemplate, context_data)