mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Merge branch 'develop' into develop-2.6
This commit is contained in:
commit
4f9b666eee
18
CHANGELOG.md
18
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
|
||||||
|
@ -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 %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
@ -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'
|
||||||
|
@ -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,
|
required=False
|
||||||
widget=StaticSelect2()
|
|
||||||
)
|
)
|
||||||
color = forms.CharField(
|
color = forms.CharField(
|
||||||
max_length=6,
|
max_length=6,
|
||||||
|
@ -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 %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
try:
|
||||||
serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
|
serializer = get_serializer_for_model(obj.changed_object, prefix='Nested')
|
||||||
if serializer is None:
|
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
|
||||||
|
@ -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']),
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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,
|
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, CommentField,
|
||||||
FilterChoiceField, FilterTreeNodeMultipleChoiceField, LaxURLField, JSONField, SlugField, 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
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py
Normal file
27
netbox/extras/migrations/0018_exporttemplate_add_jinja2.py
Normal file
@ -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),
|
||||||
|
]
|
@ -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,16 +381,36 @@ 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(self, queryset):
|
||||||
|
"""
|
||||||
|
Render the contents of the template.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
output = output.replace('\r\n', '\n')
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
def render_to_response(self, queryset):
|
def render_to_response(self, queryset):
|
||||||
"""
|
"""
|
||||||
Render the template to an HTTP response, delivered as a named file attachment
|
Render the template to an HTTP response, delivered as a named file attachment
|
||||||
"""
|
"""
|
||||||
template = Template(self.template_code)
|
output = self.render(queryset)
|
||||||
mime_type = 'text/plain' if not self.mime_type else self.mime_type
|
mime_type = 'text/plain' if not self.mime_type else self.mime_type
|
||||||
output = template.render(Context({'queryset': queryset}))
|
|
||||||
|
|
||||||
# Replace CRLF-style line terminators
|
|
||||||
output = output.replace('\r\n', '\n')
|
|
||||||
|
|
||||||
# Build the response
|
# Build the response
|
||||||
response = HttpResponse(output, content_type=mime_type)
|
response = HttpResponse(output, content_type=mime_type)
|
||||||
|
@ -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 %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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 %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -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 %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -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 %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user