Browse Source

Merge branch 'develop' into develop-2.6

Jeremy Stretch 6 years ago
parent
commit
4f9b666eee

+ 18 - 0
CHANGELOG.md

@@ -30,6 +30,24 @@ to now use "Extras | Tag."
 
 
 ---
 ---
 
 
+v2.5.10 (2019-04-08)
+
+## Enhancements
+
+* [#3052](https://github.com/digitalocean/netbox/issues/3052) - Add Jinja2 support for export templates
+
+## Bug Fixes
+
+* [#2937](https://github.com/digitalocean/netbox/issues/2937) - Redirect to list view after editing an object from list view
+* [#3036](https://github.com/digitalocean/netbox/issues/3036) - DCIM interfaces API endpoint should not include VM interfaces
+* [#3039](https://github.com/digitalocean/netbox/issues/3039) - Fix exception when retrieving change object for a component template via API
+* [#3041](https://github.com/digitalocean/netbox/issues/3041) - Fix form widget for bulk cable label update
+* [#3044](https://github.com/digitalocean/netbox/issues/3044) - Ignore site/rack fields when connecting a new cable via device search
+* [#3046](https://github.com/digitalocean/netbox/issues/3046) - Fix exception at reports API endpoint
+* [#3047](https://github.com/digitalocean/netbox/issues/3047) - Fix exception when writing mac address for an interface via API
+
+---
+
 v2.5.9 (2019-04-01)
 v2.5.9 (2019-04-01)
 
 
 ## Enhancements
 ## Enhancements

+ 1 - 1
netbox/circuits/tables.py

@@ -11,7 +11,7 @@ CIRCUITTYPE_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.circuit.change_circuittype %}
 {% if perms.circuit.change_circuittype %}
-    <a href="{% url 'circuits:circuittype_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'circuits:circuittype_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 

+ 3 - 1
netbox/dcim/api/views.py

@@ -426,7 +426,9 @@ class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
 
 
 
 
 class InterfaceViewSet(CableTraceMixin, ModelViewSet):
 class InterfaceViewSet(CableTraceMixin, ModelViewSet):
-    queryset = Interface.objects.select_related(
+    queryset = Interface.objects.filter(
+        device__isnull=False
+    ).select_related(
         'device', '_connected_interface', '_connected_circuittermination', 'cable'
         'device', '_connected_interface', '_connected_circuittermination', 'cable'
     ).prefetch_related(
     ).prefetch_related(
         'ip_addresses', 'tags'
         'ip_addresses', 'tags'

+ 1 - 1
netbox/dcim/fields.py

@@ -31,7 +31,7 @@ class MACAddressField(models.Field):
         try:
         try:
             return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
             return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
         except AddrFormatError as e:
         except AddrFormatError as e:
-            raise ValidationError(e)
+            raise ValidationError("Invalid MAC address format: {}".format(value))
 
 
     def db_type(self, connection):
     def db_type(self, connection):
         return 'macaddr'
         return 'macaddr'

+ 2 - 2
netbox/dcim/forms.py

@@ -2754,12 +2754,12 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
     status = forms.ChoiceField(
     status = forms.ChoiceField(
         choices=add_blank_choice(CONNECTION_STATUS_CHOICES),
         choices=add_blank_choice(CONNECTION_STATUS_CHOICES),
         required=False,
         required=False,
+        widget=StaticSelect2(),
         initial=''
         initial=''
     )
     )
     label = forms.CharField(
     label = forms.CharField(
         max_length=100,
         max_length=100,
-        required=False,
-        widget=StaticSelect2()
+        required=False
     )
     )
     color = forms.CharField(
     color = forms.CharField(
         max_length=6,
         max_length=6,

+ 8 - 8
netbox/dcim/tables.py

@@ -44,7 +44,7 @@ REGION_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.dcim.change_region %}
 {% if perms.dcim.change_region %}
-    <a href="{% url 'dcim:region_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'dcim:region_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -56,7 +56,7 @@ RACKGROUP_ACTIONS = """
     <i class="fa fa-eye"></i>
     <i class="fa fa-eye"></i>
 </a>
 </a>
 {% if perms.dcim.change_rackgroup %}
 {% if perms.dcim.change_rackgroup %}
-    <a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}" class="btn btn-xs btn-warning" title="Edit">
+    <a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning" title="Edit">
         <i class="glyphicon glyphicon-pencil"></i>
         <i class="glyphicon glyphicon-pencil"></i>
     </a>
     </a>
 {% endif %}
 {% endif %}
@@ -67,7 +67,7 @@ RACKROLE_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.dcim.change_rackrole %}
 {% if perms.dcim.change_rackrole %}
-    <a href="{% url 'dcim:rackrole_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'dcim:rackrole_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -88,7 +88,7 @@ RACKRESERVATION_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.dcim.change_rackreservation %}
 {% if perms.dcim.change_rackreservation %}
-    <a href="{% url 'dcim:rackreservation_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'dcim:rackreservation_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -97,7 +97,7 @@ MANUFACTURER_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.dcim.change_manufacturer %}
 {% if perms.dcim.change_manufacturer %}
-    <a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -106,7 +106,7 @@ DEVICEROLE_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.dcim.change_devicerole %}
 {% if perms.dcim.change_devicerole %}
-    <a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'dcim:devicerole_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -131,7 +131,7 @@ PLATFORM_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.dcim.change_platform %}
 {% if perms.dcim.change_platform %}
-    <a href="{% url 'dcim:platform_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'dcim:platform_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -168,7 +168,7 @@ VIRTUALCHASSIS_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.dcim.change_virtualchassis %}
 {% if perms.dcim.change_virtualchassis %}
-    <a href="{% url 'dcim:virtualchassis_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'dcim:virtualchassis_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 

+ 18 - 5
netbox/extras/api/serializers.py

@@ -17,7 +17,8 @@ from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantG
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from users.api.nested_serializers import NestedUserSerializer
 from users.api.nested_serializers import NestedUserSerializer
 from utilities.api import (
 from utilities.api import (
-    ChoiceField, ContentTypeField, get_serializer_for_model, SerializedPKRelatedField, ValidatedModelSerializer,
+    ChoiceField, ContentTypeField, get_serializer_for_model, SerializerNotFound, SerializedPKRelatedField,
+    ValidatedModelSerializer,
 )
 )
 from .nested_serializers import *
 from .nested_serializers import *
 
 
@@ -55,10 +56,17 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
 #
 #
 
 
 class ExportTemplateSerializer(ValidatedModelSerializer):
 class ExportTemplateSerializer(ValidatedModelSerializer):
+    template_language = ChoiceField(
+        choices=TEMPLATE_LANGUAGE_CHOICES,
+        default=TEMPLATE_LANGUAGE_JINJA2
+    )
 
 
     class Meta:
     class Meta:
         model = ExportTemplate
         model = ExportTemplate
-        fields = ['id', 'content_type', 'name', 'description', 'template_code', 'mime_type', 'file_extension']
+        fields = [
+            'id', 'content_type', 'name', 'description', 'template_language', 'template_code', 'mime_type',
+            'file_extension',
+        ]
 
 
 
 
 #
 #
@@ -238,9 +246,14 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
         """
         """
         if obj.changed_object is None:
         if obj.changed_object is None:
             return None
             return None
-        serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
-        if serializer is None:
+
+        try:
+            serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
+        except SerializerNotFound:
             return obj.object_repr
             return obj.object_repr
-        context = {'request': self.context['request']}
+        context = {
+            'request': self.context['request']
+        }
         data = serializer(obj.changed_object, context=context).data
         data = serializer(obj.changed_object, context=context).data
+
         return data
         return data

+ 1 - 0
netbox/extras/api/views.py

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

+ 8 - 0
netbox/extras/constants.py

@@ -56,6 +56,14 @@ EXPORTTEMPLATE_MODELS = [
     'cluster', 'virtualmachine',                                                    # Virtualization
     'cluster', 'virtualmachine',                                                    # Virtualization
 ]
 ]
 
 
+# ExportTemplate language choices
+TEMPLATE_LANGUAGE_DJANGO = 10
+TEMPLATE_LANGUAGE_JINJA2 = 20
+TEMPLATE_LANGUAGE_CHOICES = (
+    (TEMPLATE_LANGUAGE_DJANGO, 'Django'),
+    (TEMPLATE_LANGUAGE_JINJA2, 'Jinja2'),
+)
+
 # Topology map types
 # Topology map types
 TOPOLOGYMAP_TYPE_NETWORK = 1
 TOPOLOGYMAP_TYPE_NETWORK = 1
 TOPOLOGYMAP_TYPE_CONSOLE = 2
 TOPOLOGYMAP_TYPE_CONSOLE = 2

+ 1 - 1
netbox/extras/filters.py

@@ -81,7 +81,7 @@ class ExportTemplateFilter(django_filters.FilterSet):
 
 
     class Meta:
     class Meta:
         model = ExportTemplate
         model = ExportTemplate
-        fields = ['content_type', 'name']
+        fields = ['content_type', 'name', 'template_language']
 
 
 
 
 class TagFilter(django_filters.FilterSet):
 class TagFilter(django_filters.FilterSet):

+ 2 - 3
netbox/extras/forms.py

@@ -4,14 +4,13 @@ from django import forms
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
-from mptt.forms import TreeNodeMultipleChoiceField
 from taggit.forms import TagField
 from taggit.forms import TagField
 
 
 from dcim.models import DeviceRole, Platform, Region, Site
 from dcim.models import DeviceRole, Platform, Region, Site
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
-    add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ContentTypeSelect,
-    FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField, CommentField
+    add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, CommentField,
+    ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField,
 )
 )
 from .constants import (
 from .constants import (
     CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,
     CF_FILTER_DISABLED, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL,

+ 27 - 0
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py

@@ -0,0 +1,27 @@
+# Generated by Django 2.1.7 on 2019-04-08 14:49
+
+from django.db import migrations, models
+
+
+def set_template_language(apps, schema_editor):
+    """
+    Set the language for all existing ExportTemplates to Django (Jinja2 is the default for new ExportTemplates).
+    """
+    ExportTemplate = apps.get_model('extras', 'ExportTemplate')
+    ExportTemplate.objects.update(template_language=10)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('extras', '0017_exporttemplate_mime_type_length'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='exporttemplate',
+            name='template_language',
+            field=models.PositiveSmallIntegerField(default=20),
+        ),
+        migrations.RunPython(set_template_language),
+    ]

+ 31 - 6
netbox/extras/models.py

@@ -1,7 +1,6 @@
 from collections import OrderedDict
 from collections import OrderedDict
 from datetime import date
 from datetime import date
 
 
-import graphviz
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
@@ -12,6 +11,8 @@ from django.db.models import F, Q
 from django.http import HttpResponse
 from django.http import HttpResponse
 from django.template import Template, Context
 from django.template import Template, Context
 from django.urls import reverse
 from django.urls import reverse
+import graphviz
+from jinja2 import Environment
 from taggit.models import TagBase, GenericTaggedItemBase
 from taggit.models import TagBase, GenericTaggedItemBase
 
 
 from dcim.constants import CONNECTION_STATUS_CONNECTED
 from dcim.constants import CONNECTION_STATUS_CONNECTED
@@ -357,6 +358,10 @@ class ExportTemplate(models.Model):
         max_length=200,
         max_length=200,
         blank=True
         blank=True
     )
     )
+    template_language = models.PositiveSmallIntegerField(
+        choices=TEMPLATE_LANGUAGE_CHOICES,
+        default=TEMPLATE_LANGUAGE_JINJA2
+    )
     template_code = models.TextField()
     template_code = models.TextField()
     mime_type = models.CharField(
     mime_type = models.CharField(
         max_length=50,
         max_length=50,
@@ -376,17 +381,37 @@ class ExportTemplate(models.Model):
     def __str__(self):
     def __str__(self):
         return '{}: {}'.format(self.content_type, self.name)
         return '{}: {}'.format(self.content_type, self.name)
 
 
-    def render_to_response(self, queryset):
+    def render(self, queryset):
         """
         """
-        Render the template to an HTTP response, delivered as a named file attachment
+        Render the contents of the template.
         """
         """
-        template = Template(self.template_code)
-        mime_type = 'text/plain' if not self.mime_type else self.mime_type
-        output = template.render(Context({'queryset': queryset}))
+        context = {
+            'queryset': queryset
+        }
+
+        if self.template_language == TEMPLATE_LANGUAGE_DJANGO:
+            template = Template(self.template_code)
+            output = template.render(Context(context))
+
+        elif self.template_language == TEMPLATE_LANGUAGE_JINJA2:
+            template = Environment().from_string(source=self.template_code)
+            output = template.render(**context)
+
+        else:
+            return None
 
 
         # Replace CRLF-style line terminators
         # Replace CRLF-style line terminators
         output = output.replace('\r\n', '\n')
         output = output.replace('\r\n', '\n')
 
 
+        return output
+
+    def render_to_response(self, queryset):
+        """
+        Render the template to an HTTP response, delivered as a named file attachment
+        """
+        output = self.render(queryset)
+        mime_type = 'text/plain' if not self.mime_type else self.mime_type
+
         # Build the response
         # Build the response
         response = HttpResponse(output, content_type=mime_type)
         response = HttpResponse(output, content_type=mime_type)
         filename = 'netbox_{}{}'.format(
         filename = 'netbox_{}{}'.format(

+ 3 - 3
netbox/ipam/tables.py

@@ -30,7 +30,7 @@ RIR_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.ipam.change_rir %}
 {% if perms.ipam.change_rir %}
-    <a href="{% url 'ipam:rir_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'ipam:rir_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -52,7 +52,7 @@ ROLE_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.ipam.change_role %}
 {% if perms.ipam.change_role %}
-    <a href="{% url 'ipam:role_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'ipam:role_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -152,7 +152,7 @@ VLANGROUP_ACTIONS = """
     {% endif %}
     {% endif %}
 {% endwith %}
 {% endwith %}
 {% if perms.ipam.change_vlangroup %}
 {% if perms.ipam.change_vlangroup %}
-    <a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 

+ 4 - 4
netbox/netbox/api.py

@@ -147,18 +147,18 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
 # Miscellaneous
 # Miscellaneous
 #
 #
 
 
-def get_view_name(view_cls, suffix=None):
+def get_view_name(view, suffix=None):
     """
     """
     Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name`.
     Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name`.
     """
     """
-    if hasattr(view_cls, 'queryset'):
+    if hasattr(view, 'queryset'):
         # Determine the model name from the queryset.
         # Determine the model name from the queryset.
-        name = view_cls.queryset.model._meta.verbose_name
+        name = view.queryset.model._meta.verbose_name
         name = ' '.join([w[0].upper() + w[1:] for w in name.split()])  # Capitalize each word
         name = ' '.join([w[0].upper() + w[1:] for w in name.split()])  # Capitalize each word
 
 
     else:
     else:
         # Replicate DRF's built-in behavior.
         # Replicate DRF's built-in behavior.
-        name = view_cls.__name__
+        name = view.__class__.__name__
         name = formatting.remove_trailing_string(name, 'View')
         name = formatting.remove_trailing_string(name, 'View')
         name = formatting.remove_trailing_string(name, 'ViewSet')
         name = formatting.remove_trailing_string(name, 'ViewSet')
         name = formatting.camelcase_to_spaces(name)
         name = formatting.camelcase_to_spaces(name)

+ 3 - 2
netbox/project-static/js/forms.js

@@ -156,11 +156,12 @@ $(document).ready(function() {
                 filter_for_elements.each(function(index, filter_for_element) {
                 filter_for_elements.each(function(index, filter_for_element) {
                     var param_name = $(filter_for_element).attr(attr_name);
                     var param_name = $(filter_for_element).attr(attr_name);
                     var is_nullable = $(filter_for_element).attr("nullable");
                     var is_nullable = $(filter_for_element).attr("nullable");
+                    var is_visible = $(filter_for_element).is(":visible");
                     var value = $(filter_for_element).val();
                     var value = $(filter_for_element).val();
 
 
-                    if (param_name && value) {
+                    if (param_name && is_visible && value) {
                         parameters[param_name] = value;
                         parameters[param_name] = value;
-                    } else if (param_name && is_nullable) {
+                    } else if (param_name && is_visible && is_nullable) {
                         parameters[param_name] = "null";
                         parameters[param_name] = "null";
                     }
                     }
                 });
                 });

+ 1 - 1
netbox/secrets/tables.py

@@ -8,7 +8,7 @@ SECRETROLE_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.secrets.change_secretrole %}
 {% if perms.secrets.change_secretrole %}
-    <a href="{% url 'secrets:secretrole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'secrets:secretrole_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 

+ 1 - 1
netbox/tenancy/tables.py

@@ -8,7 +8,7 @@ TENANTGROUP_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.tenancy.change_tenantgroup %}
 {% if perms.tenancy.change_tenantgroup %}
-    <a href="{% url 'tenancy:tenantgroup_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'tenancy:tenantgroup_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 

+ 2 - 2
netbox/virtualization/tables.py

@@ -11,7 +11,7 @@ CLUSTERTYPE_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.virtualization.change_clustertype %}
 {% if perms.virtualization.change_clustertype %}
-    <a href="{% url 'virtualization:clustertype_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'virtualization:clustertype_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 
@@ -20,7 +20,7 @@ CLUSTERGROUP_ACTIONS = """
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
 </a>
 </a>
 {% if perms.virtualization.change_clustergroup %}
 {% if perms.virtualization.change_clustergroup %}
-    <a href="{% url 'virtualization:clustergroup_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
+    <a href="{% url 'virtualization:clustergroup_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
 {% endif %}
 {% endif %}
 """
 """
 
 

+ 1 - 1
netbox/virtualization/tests/test_api.py

@@ -645,7 +645,7 @@ class InterfaceTest(APITestCase):
 
 
     def test_delete_interface(self):
     def test_delete_interface(self):
 
 
-        url = reverse('dcim-api:interface-detail', kwargs={'pk': self.interface1.pk})
+        url = reverse('virtualization-api:interface-detail', kwargs={'pk': self.interface1.pk})
         response = self.client.delete(url, **self.header)
         response = self.client.delete(url, **self.header)
 
 
         self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
         self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)

+ 1 - 0
requirements.txt

@@ -10,6 +10,7 @@ django-timezone-field==3.0
 djangorestframework==3.9.0
 djangorestframework==3.9.0
 drf-yasg[validation]==1.14.0
 drf-yasg[validation]==1.14.0
 graphviz==0.10.1
 graphviz==0.10.1
+Jinja2==2.10
 Markdown==2.6.11
 Markdown==2.6.11
 netaddr==0.7.19
 netaddr==0.7.19
 Pillow==5.3.0
 Pillow==5.3.0