Parcourir la source

Merge pull request #3889 from netbox-community/3520-graph-template-language

Fixes #3520: Add template_language to extras.Graph
Jeremy Stretch il y a 6 ans
Parent
commit
830a51d9f5

+ 5 - 0
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:

+ 2 - 0
docs/release-notes/version-2.7.md

@@ -227,6 +227,7 @@ PATCH) to maintain backward compatibility. This behavior will be discontinued be
 * [#2669](https://github.com/digitalocean/netbox/issues/2669) - Relax uniqueness constraint on device and VM names
 * [#2902](https://github.com/digitalocean/netbox/issues/2902) - Replace `supervisord` with `systemd`
 * [#3455](https://github.com/digitalocean/netbox/issues/3455) - Add tenant assignment to cluster
+* [#3520](https://github.com/digitalocean/netbox/issues/3520) - Add Jinja2 template support for Graphs
 * [#3564](https://github.com/digitalocean/netbox/issues/3564) - Add list views for device components
 * [#3538](https://github.com/digitalocean/netbox/issues/3538) - Introduce a REST API endpoint for executing custom
   scripts
@@ -256,6 +257,7 @@ PATCH) to maintain backward compatibility. This behavior will be discontinued be
 * dcim.PowerOutlet: Added field `type`
 * dcim.PowerOutletTemplate: Added field `type`
 * dcim.RackRole: Added field `description`
+* extras.Graph: Added field `template_language` (to indicate `django` or `jinja2`)
 * extras.Graph: The `type` field has been changed to a content type foreign key. Models are specified as
   `<app>.<model>`; e.g. `dcim.site`.
 * ipam.Role: Added field `description`

+ 2 - 2
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',
     ]
 
 

+ 1 - 1
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):

+ 1 - 1
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']),
     )
 

+ 1 - 1
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):

+ 13 - 0
netbox/extras/migrations/0033_graph_type_to_fk.py → 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),
+        ),
     ]

+ 1 - 1
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 = [

+ 24 - 4
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)
 
 
 #

+ 8 - 3
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()

+ 46 - 0
netbox/extras/tests/test_models.py

@@ -0,0 +1,46 @@
+from django.contrib.contenttypes.models import ContentType
+from django.test import TestCase
+
+from dcim.models import Site
+from extras.choices import ExportTemplateLanguageChoices
+from extras.models import Graph
+
+
+class GraphTest(TestCase):
+
+    def setUp(self):
+
+        self.site = Site(name='Site 1', slug='site-1')
+
+    def test_graph_render_django(self):
+
+        # Using the pluralize filter as a sanity check (it's only available in Django)
+        TEMPLATE_TEXT = "{{ obj.name|lower }} thing{{ 2|pluralize }}"
+        RENDERED_TEXT = "site 1 things"
+
+        graph = Graph(
+            type=ContentType.objects.get(app_label='dcim', model='site'),
+            name='Graph 1',
+            template_language=ExportTemplateLanguageChoices.LANGUAGE_DJANGO,
+            source=TEMPLATE_TEXT,
+            link=TEMPLATE_TEXT
+        )
+
+        self.assertEqual(graph.embed_url(self.site), RENDERED_TEXT)
+        self.assertEqual(graph.embed_link(self.site), RENDERED_TEXT)
+
+    def test_graph_render_jinja2(self):
+
+        TEMPLATE_TEXT = "{{ [obj.name, obj.slug]|join(',') }}"
+        RENDERED_TEXT = "Site 1,site-1"
+
+        graph = Graph(
+            type=ContentType.objects.get(app_label='dcim', model='site'),
+            name='Graph 1',
+            template_language=ExportTemplateLanguageChoices.LANGUAGE_JINJA2,
+            source=TEMPLATE_TEXT,
+            link=TEMPLATE_TEXT
+        )
+
+        self.assertEqual(graph.embed_url(self.site), RENDERED_TEXT)
+        self.assertEqual(graph.embed_link(self.site), RENDERED_TEXT)