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

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

Fixes #3520: Add template_language to extras.Graph
This commit is contained in:
Jeremy Stretch
2020-01-10 11:59:05 -05:00
committed by GitHub
11 changed files with 104 additions and 13 deletions

View File

@ -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:

View File

@ -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`

View File

@ -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',
]

View File

@ -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):

View File

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

View File

@ -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):

View File

@ -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),
),
]

View File

@ -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 = [

View File

@ -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)
#

View File

@ -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()

View File

@ -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)