mixins.py 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. from jinja2.exceptions import TemplateError
  2. from rest_framework.decorators import action
  3. from rest_framework.renderers import JSONRenderer
  4. from rest_framework.response import Response
  5. from rest_framework.status import HTTP_400_BAD_REQUEST
  6. from netbox.api.renderers import TextRenderer
  7. from .nested_serializers import NestedConfigTemplateSerializer
  8. __all__ = (
  9. 'ConfigContextQuerySetMixin',
  10. 'ConfigTemplateRenderMixin',
  11. 'RenderConfigMixin',
  12. )
  13. class ConfigContextQuerySetMixin:
  14. """
  15. Used by views that work with config context models (device and virtual machine).
  16. Provides a get_queryset() method which deals with adding the config context
  17. data annotation or not.
  18. """
  19. def get_queryset(self):
  20. """
  21. Build the proper queryset based on the request context
  22. If the `brief` query param equates to True or the `exclude` query param
  23. includes `config_context` as a value, return the base queryset.
  24. Else, return the queryset annotated with config context data
  25. """
  26. queryset = super().get_queryset()
  27. request = self.get_serializer_context()['request']
  28. if self.brief or 'config_context' in request.query_params.get('exclude', []):
  29. return queryset
  30. return queryset.annotate_config_context_data()
  31. class ConfigTemplateRenderMixin:
  32. """
  33. Provides a method to return a rendered ConfigTemplate as REST API data.
  34. """
  35. def render_configtemplate(self, request, configtemplate, context):
  36. try:
  37. output = configtemplate.render(context=context)
  38. except TemplateError as e:
  39. return Response({
  40. 'detail': f"An error occurred while rendering the template (line {e.lineno}): {e}"
  41. }, status=500)
  42. # If the client has requested "text/plain", return the raw content.
  43. if request.accepted_renderer.format == 'txt':
  44. return Response(output)
  45. template_serializer = NestedConfigTemplateSerializer(configtemplate, context={'request': request})
  46. return Response({
  47. 'configtemplate': template_serializer.data,
  48. 'content': output
  49. })
  50. class RenderConfigMixin(ConfigTemplateRenderMixin):
  51. """
  52. Provides a /render-config/ endpoint for REST API views whose model may have a ConfigTemplate assigned.
  53. """
  54. @action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer])
  55. def render_config(self, request, pk):
  56. """
  57. Resolve and render the preferred ConfigTemplate for this Device.
  58. """
  59. instance = self.get_object()
  60. object_type = instance._meta.model_name
  61. configtemplate = instance.get_config_template()
  62. if not configtemplate:
  63. return Response({
  64. 'error': f'No config template found for this {object_type}.'
  65. }, status=HTTP_400_BAD_REQUEST)
  66. # Compile context data
  67. context_data = instance.get_config_context()
  68. context_data.update(request.data)
  69. context_data.update({object_type: instance})
  70. return self.render_configtemplate(request, configtemplate, context_data)