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:
@ -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
|
||||||
|
@ -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',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -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']),
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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,
|
||||||
|
27
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py
Normal file
27
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py
Normal 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),
|
||||||
|
]
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user