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:
@ -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:
|
||||
|
@ -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`
|
||||
|
@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -26,7 +26,7 @@ from . import serializers
|
||||
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(ExportTemplate, ['template_language']),
|
||||
(Graph, ['type']),
|
||||
(Graph, ['type', 'template_language']),
|
||||
(ObjectChange, ['action']),
|
||||
)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
@ -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 = [
|
||||
|
@ -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)
|
||||
|
||||
|
||||
#
|
||||
|
@ -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
netbox/extras/tests/test_models.py
Normal file
46
netbox/extras/tests/test_models.py
Normal 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)
|
Reference in New Issue
Block a user