mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-22 05:12:22 -06:00
Closes #4349: Drop support for embedded graphs
This commit is contained in:
@@ -2,7 +2,7 @@ from django import forms
|
||||
from django.contrib import admin
|
||||
|
||||
from utilities.forms import LaxURLField
|
||||
from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, JobResult, Webhook
|
||||
from .models import CustomField, CustomFieldChoice, CustomLink, ExportTemplate, JobResult, Webhook
|
||||
|
||||
|
||||
def order_content_types(field):
|
||||
@@ -150,45 +150,6 @@ class CustomLinkAdmin(admin.ModelAdmin):
|
||||
form = CustomLinkForm
|
||||
|
||||
|
||||
#
|
||||
# Graphs
|
||||
#
|
||||
|
||||
class GraphForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Graph
|
||||
exclude = ()
|
||||
help_texts = {
|
||||
'template_language': "<a href=\"https://jinja.palletsprojects.com\">Jinja2</a> is strongly recommended for "
|
||||
"new graphs."
|
||||
}
|
||||
widgets = {
|
||||
'source': forms.Textarea,
|
||||
'link': forms.Textarea,
|
||||
}
|
||||
|
||||
|
||||
@admin.register(Graph)
|
||||
class GraphAdmin(admin.ModelAdmin):
|
||||
fieldsets = (
|
||||
('Graph', {
|
||||
'fields': ('type', 'name', 'weight')
|
||||
}),
|
||||
('Templates', {
|
||||
'fields': ('template_language', 'source', 'link'),
|
||||
'classes': ('monospace',)
|
||||
})
|
||||
)
|
||||
form = GraphForm
|
||||
list_display = [
|
||||
'name', 'type', 'weight', 'template_language', 'source',
|
||||
]
|
||||
list_filter = [
|
||||
'type', 'template_language',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Export templates
|
||||
#
|
||||
|
||||
@@ -7,7 +7,6 @@ from utilities.api import ChoiceField, WritableNestedSerializer
|
||||
__all__ = [
|
||||
'NestedConfigContextSerializer',
|
||||
'NestedExportTemplateSerializer',
|
||||
'NestedGraphSerializer',
|
||||
'NestedImageAttachmentSerializer',
|
||||
'NestedJobResultSerializer',
|
||||
'NestedTagSerializer',
|
||||
@@ -30,14 +29,6 @@ class NestedExportTemplateSerializer(WritableNestedSerializer):
|
||||
fields = ['id', 'url', 'name']
|
||||
|
||||
|
||||
class NestedGraphSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:graph-detail')
|
||||
|
||||
class Meta:
|
||||
model = models.Graph
|
||||
fields = ['id', 'url', 'name']
|
||||
|
||||
|
||||
class NestedImageAttachmentSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:imageattachment-detail')
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from dcim.api.nested_serializers import (
|
||||
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
|
||||
from extras.choices import *
|
||||
from extras.models import (
|
||||
ConfigContext, ExportTemplate, Graph, ImageAttachment, ObjectChange, JobResult, Tag,
|
||||
ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag,
|
||||
)
|
||||
from extras.utils import FeatureQuery
|
||||
from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
|
||||
@@ -25,43 +25,6 @@ from virtualization.models import Cluster, ClusterGroup
|
||||
from .nested_serializers import *
|
||||
|
||||
|
||||
#
|
||||
# Graphs
|
||||
#
|
||||
|
||||
class GraphSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:graph-detail')
|
||||
type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(FeatureQuery('graphs').get_query()),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Graph
|
||||
fields = ['id', 'url', 'type', 'weight', 'name', 'template_language', 'source', 'link']
|
||||
|
||||
|
||||
class RenderedGraphSerializer(serializers.ModelSerializer):
|
||||
embed_url = serializers.SerializerMethodField(
|
||||
read_only=True
|
||||
)
|
||||
embed_link = serializers.SerializerMethodField(
|
||||
read_only=True
|
||||
)
|
||||
type = ContentTypeField(
|
||||
read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Graph
|
||||
fields = ['id', 'type', 'weight', 'name', 'embed_url', 'embed_link']
|
||||
|
||||
def get_embed_url(self, obj):
|
||||
return obj.embed_url(self.context['graphed_object'])
|
||||
|
||||
def get_embed_link(self, obj):
|
||||
return obj.embed_link(self.context['graphed_object'])
|
||||
|
||||
|
||||
#
|
||||
# Export templates
|
||||
#
|
||||
|
||||
@@ -8,9 +8,6 @@ router.APIRootView = views.ExtrasRootView
|
||||
# Custom field choices
|
||||
router.register('_custom_field_choices', views.CustomFieldChoicesViewSet, basename='custom-field-choice')
|
||||
|
||||
# Graphs
|
||||
router.register('graphs', views.GraphViewSet)
|
||||
|
||||
# Export templates
|
||||
router.register('export-templates', views.ExportTemplateViewSet)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from rq import Worker
|
||||
from extras import filters
|
||||
from extras.choices import JobResultStatusChoices
|
||||
from extras.models import (
|
||||
ConfigContext, CustomFieldChoice, ExportTemplate, Graph, ImageAttachment, ObjectChange, JobResult, Tag,
|
||||
ConfigContext, CustomFieldChoice, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag,
|
||||
)
|
||||
from extras.reports import get_report, get_reports, run_report
|
||||
from extras.scripts import get_script, get_scripts, run_script
|
||||
@@ -98,17 +98,6 @@ class CustomFieldModelViewSet(ModelViewSet):
|
||||
return super().get_queryset().prefetch_related('custom_field_values__field')
|
||||
|
||||
|
||||
#
|
||||
# Graphs
|
||||
#
|
||||
|
||||
class GraphViewSet(ModelViewSet):
|
||||
metadata_class = ContentTypeMetadata
|
||||
queryset = Graph.objects.all()
|
||||
serializer_class = serializers.GraphSerializer
|
||||
filterset_class = filters.GraphFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Export templates
|
||||
#
|
||||
|
||||
@@ -79,21 +79,6 @@ class ObjectChangeActionChoices(ChoiceSet):
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# ExportTemplates
|
||||
#
|
||||
|
||||
class TemplateLanguageChoices(ChoiceSet):
|
||||
|
||||
LANGUAGE_JINJA2 = 'jinja2'
|
||||
LANGUAGE_DJANGO = 'django'
|
||||
|
||||
CHOICES = (
|
||||
(LANGUAGE_JINJA2, 'Jinja2'),
|
||||
(LANGUAGE_DJANGO, 'Django (Legacy)'),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Log Levels for Reports and Scripts
|
||||
#
|
||||
|
||||
@@ -6,7 +6,6 @@ EXTRAS_FEATURES = [
|
||||
'custom_fields',
|
||||
'custom_links',
|
||||
'export_templates',
|
||||
'graphs',
|
||||
'job_results',
|
||||
'webhooks'
|
||||
]
|
||||
|
||||
@@ -7,7 +7,7 @@ from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.filters import BaseFilterSet
|
||||
from virtualization.models import Cluster, ClusterGroup
|
||||
from .choices import *
|
||||
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, JobResult, Tag
|
||||
from .models import ConfigContext, CustomField, ExportTemplate, ObjectChange, JobResult, Tag
|
||||
|
||||
|
||||
__all__ = (
|
||||
@@ -16,7 +16,6 @@ __all__ = (
|
||||
'CustomFieldFilter',
|
||||
'CustomFieldFilterSet',
|
||||
'ExportTemplateFilterSet',
|
||||
'GraphFilterSet',
|
||||
'LocalConfigContextFilterSet',
|
||||
'ObjectChangeFilterSet',
|
||||
'TagFilterSet',
|
||||
@@ -90,13 +89,6 @@ class CustomFieldFilterSet(django_filters.FilterSet):
|
||||
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
||||
|
||||
|
||||
class GraphFilterSet(BaseFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Graph
|
||||
fields = ['id', 'type', 'name', 'template_language']
|
||||
|
||||
|
||||
class ExportTemplateFilterSet(BaseFilterSet):
|
||||
|
||||
class Meta:
|
||||
|
||||
16
netbox/extras/migrations/0049_remove_graph.py
Normal file
16
netbox/extras/migrations/0049_remove_graph.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 3.1 on 2020-08-21 15:47
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0048_exporttemplate_remove_template_language'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Graph',
|
||||
),
|
||||
]
|
||||
@@ -1,7 +1,7 @@
|
||||
from .change_logging import ChangeLoggedModel, ObjectChange
|
||||
from .customfields import CustomField, CustomFieldChoice, CustomFieldModel, CustomFieldValue
|
||||
from .models import (
|
||||
ConfigContext, ConfigContextModel, CustomLink, ExportTemplate, Graph, ImageAttachment, JobResult, Report, Script,
|
||||
ConfigContext, ConfigContextModel, CustomLink, ExportTemplate, ImageAttachment, JobResult, Report, Script,
|
||||
Webhook,
|
||||
)
|
||||
from .tags import Tag, TaggedItem
|
||||
@@ -16,7 +16,6 @@ __all__ = (
|
||||
'CustomFieldValue',
|
||||
'CustomLink',
|
||||
'ExportTemplate',
|
||||
'Graph',
|
||||
'ImageAttachment',
|
||||
'JobResult',
|
||||
'ObjectChange',
|
||||
|
||||
@@ -203,69 +203,6 @@ class CustomLink(models.Model):
|
||||
return self.name
|
||||
|
||||
|
||||
#
|
||||
# Graphs
|
||||
#
|
||||
|
||||
class Graph(models.Model):
|
||||
type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to=FeatureQuery('graphs')
|
||||
)
|
||||
weight = models.PositiveSmallIntegerField(
|
||||
default=1000
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
verbose_name='Name'
|
||||
)
|
||||
template_language = models.CharField(
|
||||
max_length=50,
|
||||
choices=TemplateLanguageChoices,
|
||||
default=TemplateLanguageChoices.LANGUAGE_JINJA2
|
||||
)
|
||||
source = models.CharField(
|
||||
max_length=500,
|
||||
verbose_name='Source URL'
|
||||
)
|
||||
link = models.URLField(
|
||||
blank=True,
|
||||
verbose_name='Link URL'
|
||||
)
|
||||
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('type', 'weight', 'name', 'pk') # (type, weight, name) may be non-unique
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def embed_url(self, obj):
|
||||
context = {'obj': obj}
|
||||
|
||||
if self.template_language == TemplateLanguageChoices.LANGUAGE_DJANGO:
|
||||
template = Template(self.source)
|
||||
return template.render(Context(context))
|
||||
|
||||
elif self.template_language == TemplateLanguageChoices.LANGUAGE_JINJA2:
|
||||
return render_jinja2(self.source, context)
|
||||
|
||||
def embed_link(self, obj):
|
||||
if self.link is None:
|
||||
return ''
|
||||
|
||||
context = {'obj': obj}
|
||||
|
||||
if self.template_language == TemplateLanguageChoices.LANGUAGE_DJANGO:
|
||||
template = Template(self.link)
|
||||
return template.render(Context(context))
|
||||
|
||||
elif self.template_language == TemplateLanguageChoices.LANGUAGE_JINJA2:
|
||||
return render_jinja2(self.link, context)
|
||||
|
||||
|
||||
#
|
||||
# Export templates
|
||||
#
|
||||
|
||||
@@ -10,7 +10,7 @@ from rq import Worker
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, RackGroup, RackRole, Site
|
||||
from extras.api.views import ReportViewSet, ScriptViewSet
|
||||
from extras.models import ConfigContext, ExportTemplate, Graph, ImageAttachment, Tag
|
||||
from extras.models import ConfigContext, ExportTemplate, ImageAttachment, Tag
|
||||
from extras.reports import Report
|
||||
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
|
||||
from utilities.testing import APITestCase, APIViewTestCases
|
||||
@@ -29,39 +29,6 @@ class AppTest(APITestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class GraphTest(APIViewTestCases.APIViewTestCase):
|
||||
model = Graph
|
||||
brief_fields = ['id', 'name', 'url']
|
||||
create_data = [
|
||||
{
|
||||
'type': 'dcim.site',
|
||||
'name': 'Graph 4',
|
||||
'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=4',
|
||||
},
|
||||
{
|
||||
'type': 'dcim.site',
|
||||
'name': 'Graph 5',
|
||||
'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=5',
|
||||
},
|
||||
{
|
||||
'type': 'dcim.site',
|
||||
'name': 'Graph 6',
|
||||
'source': 'http://example.com/graphs.py?site={{ obj.name }}&foo=6',
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
ct = ContentType.objects.get_for_model(Site)
|
||||
|
||||
graphs = (
|
||||
Graph(type=ct, name='Graph 1', source='http://example.com/graphs.py?site={{ obj.name }}&foo=1'),
|
||||
Graph(type=ct, name='Graph 2', source='http://example.com/graphs.py?site={{ obj.name }}&foo=2'),
|
||||
Graph(type=ct, name='Graph 3', source='http://example.com/graphs.py?site={{ obj.name }}&foo=3'),
|
||||
)
|
||||
Graph.objects.bulk_create(graphs)
|
||||
|
||||
|
||||
class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ExportTemplate
|
||||
brief_fields = ['id', 'name', 'url']
|
||||
|
||||
@@ -2,49 +2,12 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from extras.choices import *
|
||||
from extras.filters import *
|
||||
from extras.utils import FeatureQuery
|
||||
from extras.models import ConfigContext, ExportTemplate, Graph, Tag
|
||||
from extras.models import ConfigContext, ExportTemplate, Tag
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
|
||||
|
||||
class GraphTestCase(TestCase):
|
||||
queryset = Graph.objects.all()
|
||||
filterset = GraphFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
# Get the first three available types
|
||||
content_types = ContentType.objects.filter(FeatureQuery('graphs').get_query())[:3]
|
||||
|
||||
graphs = (
|
||||
Graph(name='Graph 1', type=content_types[0], template_language=TemplateLanguageChoices.LANGUAGE_DJANGO, source='http://example.com/1'),
|
||||
Graph(name='Graph 2', type=content_types[1], template_language=TemplateLanguageChoices.LANGUAGE_JINJA2, source='http://example.com/2'),
|
||||
Graph(name='Graph 3', type=content_types[2], template_language=TemplateLanguageChoices.LANGUAGE_JINJA2, source='http://example.com/3'),
|
||||
)
|
||||
Graph.objects.bulk_create(graphs)
|
||||
|
||||
def test_id(self):
|
||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Graph 1', 'Graph 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_type(self):
|
||||
content_type = ContentType.objects.filter(FeatureQuery('graphs').get_query()).first()
|
||||
params = {'type': content_type.pk}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_template_language(self):
|
||||
params = {'template_language': TemplateLanguageChoices.LANGUAGE_JINJA2}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ExportTemplateTestCase(TestCase):
|
||||
queryset = ExportTemplate.objects.all()
|
||||
filterset = ExportTemplateFilterSet
|
||||
|
||||
@@ -1,49 +1,6 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from dcim.models import Site
|
||||
from extras.choices import TemplateLanguageChoices
|
||||
from extras.models import Graph, Tag
|
||||
|
||||
|
||||
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=TemplateLanguageChoices.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=TemplateLanguageChoices.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)
|
||||
from extras.models import Tag
|
||||
|
||||
|
||||
class TagTest(TestCase):
|
||||
|
||||
Reference in New Issue
Block a user