1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #3052: Add Jinja2 support for export templates

This commit is contained in:
Jeremy Stretch
2019-04-08 12:20:24 -04:00
parent 7f6d79362e
commit 6f8591f769
9 changed files with 82 additions and 10 deletions

View File

@ -1,5 +1,9 @@
v2.5.10 (FUTURE) v2.5.10 (FUTURE)
## Enhancements
* [#3052](https://github.com/digitalocean/netbox/issues/3052) - Add Jinja2 support for export templates
## Bug Fixes ## Bug Fixes
* [#3036](https://github.com/digitalocean/netbox/issues/3036) - DCIM interfaces API endpoint should not include VM interfaces * [#3036](https://github.com/digitalocean/netbox/issues/3036) - DCIM interfaces API endpoint should not include VM interfaces

View File

@ -55,10 +55,17 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
# #
class ExportTemplateSerializer(ValidatedModelSerializer): class ExportTemplateSerializer(ValidatedModelSerializer):
template_language = ChoiceField(
choices=TEMPLATE_LANGUAGE_CHOICES,
default=TEMPLATE_LANGUAGE_JINJA2
)
class Meta: class Meta:
model = ExportTemplate model = ExportTemplate
fields = ['id', 'content_type', 'name', 'description', 'template_code', 'mime_type', 'file_extension'] fields = [
'id', 'content_type', 'name', 'description', 'template_language', 'template_code', 'mime_type',
'file_extension',
]
# #

View File

@ -23,6 +23,7 @@ from . import serializers
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet): class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
fields = ( fields = (
(ExportTemplate, ['template_language']),
(Graph, ['type']), (Graph, ['type']),
(ObjectChange, ['action']), (ObjectChange, ['action']),
) )

View File

@ -56,6 +56,14 @@ EXPORTTEMPLATE_MODELS = [
'cluster', 'virtualmachine', # Virtualization 'cluster', 'virtualmachine', # Virtualization
] ]
# ExportTemplate language choices
TEMPLATE_LANGUAGE_DJANGO = 10
TEMPLATE_LANGUAGE_JINJA2 = 20
TEMPLATE_LANGUAGE_CHOICES = (
(TEMPLATE_LANGUAGE_DJANGO, 'Django'),
(TEMPLATE_LANGUAGE_JINJA2, 'Jinja2'),
)
# Topology map types # Topology map types
TOPOLOGYMAP_TYPE_NETWORK = 1 TOPOLOGYMAP_TYPE_NETWORK = 1
TOPOLOGYMAP_TYPE_CONSOLE = 2 TOPOLOGYMAP_TYPE_CONSOLE = 2

View File

@ -82,7 +82,7 @@ class ExportTemplateFilter(django_filters.FilterSet):
class Meta: class Meta:
model = ExportTemplate model = ExportTemplate
fields = ['content_type', 'name'] fields = ['content_type', 'name', 'template_language']
class TagFilter(django_filters.FilterSet): class TagFilter(django_filters.FilterSet):

View File

@ -4,7 +4,6 @@ from django import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from mptt.forms import TreeNodeMultipleChoiceField
from taggit.forms import TagField from taggit.forms import TagField
from taggit.models import Tag from taggit.models import Tag
@ -12,7 +11,7 @@ from dcim.models import DeviceRole, Platform, Region, Site
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.forms import ( from utilities.forms import (
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect, add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect,
FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField, FilterChoiceField, LaxURLField, JSONField, SlugField,
) )
from .constants import ( from .constants import (
CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,

View File

@ -0,0 +1,27 @@
# Generated by Django 2.1.7 on 2019-04-08 14:49
from django.db import migrations, models
def set_template_language(apps, schema_editor):
"""
Set the language for all existing ExportTemplates to Django (Jinja2 is the default for new ExportTemplates).
"""
ExportTemplate = apps.get_model('extras', 'ExportTemplate')
ExportTemplate.objects.update(template_language=10)
class Migration(migrations.Migration):
dependencies = [
('extras', '0017_exporttemplate_mime_type_length'),
]
operations = [
migrations.AddField(
model_name='exporttemplate',
name='template_language',
field=models.PositiveSmallIntegerField(default=20),
),
migrations.RunPython(set_template_language),
]

View File

@ -1,7 +1,6 @@
from collections import OrderedDict from collections import OrderedDict
from datetime import date from datetime import date
import graphviz
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -12,6 +11,8 @@ from django.db.models import F, Q
from django.http import HttpResponse from django.http import HttpResponse
from django.template import Template, Context from django.template import Template, Context
from django.urls import reverse from django.urls import reverse
import graphviz
from jinja2 import Environment
from dcim.constants import CONNECTION_STATUS_CONNECTED from dcim.constants import CONNECTION_STATUS_CONNECTED
from utilities.utils import deepmerge, foreground_color from utilities.utils import deepmerge, foreground_color
@ -355,6 +356,10 @@ class ExportTemplate(models.Model):
max_length=200, max_length=200,
blank=True blank=True
) )
template_language = models.PositiveSmallIntegerField(
choices=TEMPLATE_LANGUAGE_CHOICES,
default=TEMPLATE_LANGUAGE_JINJA2
)
template_code = models.TextField() template_code = models.TextField()
mime_type = models.CharField( mime_type = models.CharField(
max_length=50, max_length=50,
@ -374,16 +379,36 @@ class ExportTemplate(models.Model):
def __str__(self): def __str__(self):
return '{}: {}'.format(self.content_type, self.name) return '{}: {}'.format(self.content_type, self.name)
def render(self, queryset):
"""
Render the contents of the template.
"""
context = {
'queryset': queryset
}
if self.template_language == TEMPLATE_LANGUAGE_DJANGO:
template = Template(self.template_code)
output = template.render(Context(context))
elif self.template_language == TEMPLATE_LANGUAGE_JINJA2:
template = Environment().from_string(source=self.template_code)
output = template.render(**context)
else:
return None
# Replace CRLF-style line terminators
output = output.replace('\r\n', '\n')
return output
def render_to_response(self, queryset): def render_to_response(self, queryset):
""" """
Render the template to an HTTP response, delivered as a named file attachment Render the template to an HTTP response, delivered as a named file attachment
""" """
template = Template(self.template_code) output = self.render(queryset)
mime_type = 'text/plain' if not self.mime_type else self.mime_type mime_type = 'text/plain' if not self.mime_type else self.mime_type
output = template.render(Context({'queryset': queryset}))
# Replace CRLF-style line terminators
output = output.replace('\r\n', '\n')
# Build the response # Build the response
response = HttpResponse(output, content_type=mime_type) response = HttpResponse(output, content_type=mime_type)

View File

@ -10,6 +10,7 @@ django-timezone-field==3.0
djangorestframework==3.9.0 djangorestframework==3.9.0
drf-yasg[validation]==1.14.0 drf-yasg[validation]==1.14.0
graphviz==0.10.1 graphviz==0.10.1
Jinja2==2.10
Markdown==2.6.11 Markdown==2.6.11
netaddr==0.7.19 netaddr==0.7.19
Pillow==5.3.0 Pillow==5.3.0