Skip to content

Commit

Permalink
Merge pull request #3889 from netbox-community/3520-graph-template-la…
Browse files Browse the repository at this point in the history
…nguage

Fixes #3520: Add template_language to extras.Graph
  • Loading branch information
jeremystretch committed Jan 10, 2020
2 parents 03b2259 + 3c247ac commit 830a51d
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 13 deletions.
5 changes: 5 additions & 0 deletions docs/additional-features/graphs.md
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes/version-2.7.md
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
4 changes: 2 additions & 2 deletions netbox/extras/admin.py
Expand Up @@ -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',
]


Expand Down
2 changes: 1 addition & 1 deletion netbox/extras/api/serializers.py
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion netbox/extras/api/views.py
Expand Up @@ -26,7 +26,7 @@
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
fields = (
(ExportTemplate, ['template_language']),
(Graph, ['type']),
(Graph, ['type', 'template_language']),
(ObjectChange, ['action']),
)

Expand Down
2 changes: 1 addition & 1 deletion netbox/extras/filters.py
Expand Up @@ -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):
Expand Down
Expand Up @@ -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),
),
]
2 changes: 1 addition & 1 deletion netbox/extras/migrations/0034_configcontext_tags.py
Expand Up @@ -6,7 +6,7 @@
class Migration(migrations.Migration):

dependencies = [
('extras', '0033_graph_type_to_fk'),
('extras', '0033_graph_type_template_language'),
]

operations = [
Expand Down
28 changes: 24 additions & 4 deletions netbox/extras/models.py
Expand Up @@ -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'
Expand All @@ -437,14 +442,29 @@ def __str__(self):
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)


#
Expand Down
11 changes: 8 additions & 3 deletions netbox/extras/tests/test_filters.py
Expand Up @@ -18,9 +18,9 @@ def setUpTestData(cls):
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)

Expand All @@ -32,6 +32,11 @@ def test_type(self):
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()
Expand Down
46 changes: 46 additions & 0 deletions 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)

0 comments on commit 830a51d

Please sign in to comment.