From 9399652dd01d3e5677282d5b6f7a06dd2b019925 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 10 Jan 2020 11:28:50 -0500 Subject: [PATCH] Add template_language field to Graph --- docs/additional-features/graphs.md | 5 ++++ netbox/extras/admin.py | 4 +-- netbox/extras/api/serializers.py | 2 +- netbox/extras/api/views.py | 2 +- netbox/extras/filters.py | 2 +- ...y => 0033_graph_type_template_language.py} | 13 +++++++++ .../migrations/0034_configcontext_tags.py | 2 +- netbox/extras/models.py | 28 ++++++++++++++++--- netbox/extras/tests/test_filters.py | 11 ++++++-- 9 files changed, 56 insertions(+), 13 deletions(-) rename netbox/extras/migrations/{0033_graph_type_to_fk.py => 0033_graph_type_template_language.py} (73%) diff --git a/docs/additional-features/graphs.md b/docs/additional-features/graphs.md index b20a6b424..264b7f1b7 100644 --- a/docs/additional-features/graphs.md +++ b/docs/additional-features/graphs.md @@ -8,6 +8,11 @@ NetBox does not have the ability to generate graphs natively, but this feature a * **Source URL:** The source of the image to be embedded. The associated object will be available as a template variable named `obj`. * **Link URL (optional):** A URL to which the graph will be linked. The associated object will be available as a template variable named `obj`. +Graph names and links can be rendered using the Django or Jinja2 template languages. + +!!! warning + Support for the Django templating language will be removed in NetBox v2.8. Jinja2 is recommended. + ## Examples You only need to define one graph object for each graph you want to include when viewing an object. For example, if you want to include a graph of traffic through an interface over the past five minutes, your graph source might looks like this: diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 61bedd189..2a39c207e 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -131,10 +131,10 @@ class CustomLinkAdmin(admin.ModelAdmin): @admin.register(Graph, site=admin_site) class GraphAdmin(admin.ModelAdmin): list_display = [ - 'name', 'type', 'weight', 'source', + 'name', 'type', 'weight', 'template_language', 'source', ] list_filter = [ - 'type', + 'type', 'template_language', ] diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index f167dee5c..7dd745513 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -34,7 +34,7 @@ class GraphSerializer(ValidatedModelSerializer): class Meta: model = Graph - fields = ['id', 'type', 'weight', 'name', 'source', 'link'] + fields = ['id', 'type', 'weight', 'name', 'template_language', 'source', 'link'] class RenderedGraphSerializer(serializers.ModelSerializer): diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 1a90b317d..aa1917f39 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -26,7 +26,7 @@ from . import serializers class ExtrasFieldChoicesViewSet(FieldChoicesViewSet): fields = ( (ExportTemplate, ['template_language']), - (Graph, ['type']), + (Graph, ['type', 'template_language']), (ObjectChange, ['action']), ) diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py index 792f8cee8..8a0d32b33 100644 --- a/netbox/extras/filters.py +++ b/netbox/extras/filters.py @@ -92,7 +92,7 @@ class GraphFilterSet(django_filters.FilterSet): class Meta: model = Graph - fields = ['type', 'name'] + fields = ['type', 'name', 'template_language'] class ExportTemplateFilterSet(django_filters.FilterSet): diff --git a/netbox/extras/migrations/0033_graph_type_to_fk.py b/netbox/extras/migrations/0033_graph_type_template_language.py similarity index 73% rename from netbox/extras/migrations/0033_graph_type_to_fk.py rename to netbox/extras/migrations/0033_graph_type_template_language.py index 6a0ee720a..f5e4034cc 100644 --- a/netbox/extras/migrations/0033_graph_type_to_fk.py +++ b/netbox/extras/migrations/0033_graph_type_template_language.py @@ -43,4 +43,17 @@ class Migration(migrations.Migration): to='contenttypes.ContentType' ), ), + + # Add the template_language field with an initial default of Django to preserve current behavior. Then, + # alter the field to set the default for any *new* Graphs to Jinja2. + migrations.AddField( + model_name='graph', + name='template_language', + field=models.CharField(default='django', max_length=50), + ), + migrations.AlterField( + model_name='graph', + name='template_language', + field=models.CharField(default='jinja2', max_length=50), + ), ] diff --git a/netbox/extras/migrations/0034_configcontext_tags.py b/netbox/extras/migrations/0034_configcontext_tags.py index 363572535..e5076f43c 100644 --- a/netbox/extras/migrations/0034_configcontext_tags.py +++ b/netbox/extras/migrations/0034_configcontext_tags.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('extras', '0033_graph_type_to_fk'), + ('extras', '0033_graph_type_template_language'), ] operations = [ diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 6e1693dd4..78ef0129a 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -421,6 +421,11 @@ class Graph(models.Model): max_length=100, verbose_name='Name' ) + template_language = models.CharField( + max_length=50, + choices=ExportTemplateLanguageChoices, + default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2 + ) source = models.CharField( max_length=500, verbose_name='Source URL' @@ -437,14 +442,29 @@ class Graph(models.Model): return self.name def embed_url(self, obj): - template = Template(self.source) - return template.render(Context({'obj': obj})) + context = {'obj': obj} + + # TODO: Remove in v2.8 + if self.template_language == ExportTemplateLanguageChoices.LANGUAGE_DJANGO: + template = Template(self.source) + return template.render(Context(context)) + + elif self.template_language == ExportTemplateLanguageChoices.LANGUAGE_JINJA2: + return render_jinja2(self.source, context) def embed_link(self, obj): if self.link is None: return '' - template = Template(self.link) - return template.render(Context({'obj': obj})) + + context = {'obj': obj} + + # TODO: Remove in v2.8 + if self.template_language == ExportTemplateLanguageChoices.LANGUAGE_DJANGO: + template = Template(self.link) + return template.render(Context(context)) + + elif self.template_language == ExportTemplateLanguageChoices.LANGUAGE_JINJA2: + return render_jinja2(self.link, context) # diff --git a/netbox/extras/tests/test_filters.py b/netbox/extras/tests/test_filters.py index f1f5f0d88..b8637988d 100644 --- a/netbox/extras/tests/test_filters.py +++ b/netbox/extras/tests/test_filters.py @@ -18,9 +18,9 @@ class GraphTestCase(TestCase): content_types = ContentType.objects.filter(model__in=['site', 'device', 'interface']) graphs = ( - Graph(name='Graph 1', type=content_types[0], source='http://example.com/1'), - Graph(name='Graph 2', type=content_types[1], source='http://example.com/2'), - Graph(name='Graph 3', type=content_types[2], source='http://example.com/3'), + Graph(name='Graph 1', type=content_types[0], template_language=ExportTemplateLanguageChoices.LANGUAGE_DJANGO, source='http://example.com/1'), + Graph(name='Graph 2', type=content_types[1], template_language=ExportTemplateLanguageChoices.LANGUAGE_JINJA2, source='http://example.com/2'), + Graph(name='Graph 3', type=content_types[2], template_language=ExportTemplateLanguageChoices.LANGUAGE_JINJA2, source='http://example.com/3'), ) Graph.objects.bulk_create(graphs) @@ -32,6 +32,11 @@ class GraphTestCase(TestCase): params = {'type': ContentType.objects.get(model='site').pk} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + # TODO: Remove in v2.8 + def test_template_language(self): + params = {'template_language': ExportTemplateLanguageChoices.LANGUAGE_JINJA2} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class ExportTemplateTestCase(TestCase): queryset = ExportTemplate.objects.all()