mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-07 16:18:16 -06:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
dbcabe4223
@ -78,6 +78,8 @@ AUTH_LDAP_USER_ATTR_MAP = {
|
|||||||
```
|
```
|
||||||
|
|
||||||
# User Groups for Permissions
|
# User Groups for Permissions
|
||||||
|
!!! Info
|
||||||
|
When using Microsoft Active Directory, Support for nested Groups can be activated by using `GroupOfNamesType()` instead of `NestedGroupOfNamesType()` for AUTH_LDAP_GROUP_TYPE.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
|
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
|
||||||
|
@ -13,6 +13,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Count, Q, ObjectDoesNotExist
|
from django.db.models import Count, Q, ObjectDoesNotExist
|
||||||
|
from django.db.models.expressions import RawSQL
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
|
||||||
@ -642,15 +643,16 @@ class InterfaceQuerySet(models.QuerySet):
|
|||||||
To order interfaces naturally, the `name` field is split into six distinct components: leading text (type),
|
To order interfaces naturally, the `name` field is split into six distinct components: leading text (type),
|
||||||
slot, subslot, position, channel, and virtual circuit:
|
slot, subslot, position, channel, and virtual circuit:
|
||||||
|
|
||||||
{type}{slot}/{subslot}/{position}:{channel}.{vc}
|
{type}{slot}/{subslot}/{position}/{subposition}:{channel}.{vc}
|
||||||
|
|
||||||
Components absent from the interface name are ignored. For example, an interface named GigabitEthernet0/1 would
|
Components absent from the interface name are ignored. For example, an interface named GigabitEthernet1/2/3
|
||||||
be parsed as follows:
|
would be parsed as follows:
|
||||||
|
|
||||||
name = 'GigabitEthernet'
|
name = 'GigabitEthernet'
|
||||||
slot = None
|
slot = 1
|
||||||
subslot = 0
|
subslot = 2
|
||||||
position = 1
|
position = 3
|
||||||
|
subposition = 0
|
||||||
channel = None
|
channel = None
|
||||||
vc = 0
|
vc = 0
|
||||||
|
|
||||||
@ -659,17 +661,35 @@ class InterfaceQuerySet(models.QuerySet):
|
|||||||
"""
|
"""
|
||||||
sql_col = '{}.name'.format(self.model._meta.db_table)
|
sql_col = '{}.name'.format(self.model._meta.db_table)
|
||||||
ordering = {
|
ordering = {
|
||||||
IFACE_ORDERING_POSITION: ('_slot', '_subslot', '_position', '_channel', '_vc', '_type', 'name'),
|
IFACE_ORDERING_POSITION: (
|
||||||
IFACE_ORDERING_NAME: ('_type', '_slot', '_subslot', '_position', '_channel', '_vc', 'name'),
|
'_slot', '_subslot', '_position', '_subposition', '_channel', '_vc', '_type', '_id', 'name',
|
||||||
|
),
|
||||||
|
IFACE_ORDERING_NAME: (
|
||||||
|
'_type', '_slot', '_subslot', '_position', '_subposition', '_channel', '_vc', '_id', 'name',
|
||||||
|
),
|
||||||
}[method]
|
}[method]
|
||||||
return self.extra(select={
|
|
||||||
'_type': "SUBSTRING({} FROM '^([^0-9]+)')".format(sql_col),
|
TYPE_RE = r"SUBSTRING({} FROM '^([^0-9]+)')"
|
||||||
'_slot': "CAST(SUBSTRING({} FROM '([0-9]+)\/[0-9]+\/[0-9]+(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
|
ID_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)$') AS integer)"
|
||||||
'_subslot': "CAST(SUBSTRING({} FROM '([0-9]+)\/[0-9]+(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
|
SLOT_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)\/') AS integer)"
|
||||||
'_position': "CAST(SUBSTRING({} FROM '([0-9]+)(:[0-9]+)?(\.[0-9]+)?$') AS integer)".format(sql_col),
|
SUBSLOT_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/)([0-9]+)') AS integer)"
|
||||||
'_channel': "COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)".format(sql_col),
|
POSITION_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{2}}([0-9]+)') AS integer)"
|
||||||
'_vc': "COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)".format(sql_col),
|
SUBPOSITION_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(?:[0-9]+\/){{3}}([0-9]+)') AS integer)"
|
||||||
}).order_by(*ordering)
|
CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)"
|
||||||
|
VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)"
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'_type': RawSQL(TYPE_RE.format(sql_col), []),
|
||||||
|
'_id': RawSQL(ID_RE.format(sql_col), []),
|
||||||
|
'_slot': RawSQL(SLOT_RE.format(sql_col), []),
|
||||||
|
'_subslot': RawSQL(SUBSLOT_RE.format(sql_col), []),
|
||||||
|
'_position': RawSQL(POSITION_RE.format(sql_col), []),
|
||||||
|
'_subposition': RawSQL(SUBPOSITION_RE.format(sql_col), []),
|
||||||
|
'_channel': RawSQL(CHANNEL_RE.format(sql_col), []),
|
||||||
|
'_vc': RawSQL(VC_RE.format(sql_col), []),
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.annotate(**fields).order_by(*ordering)
|
||||||
|
|
||||||
def connectable(self):
|
def connectable(self):
|
||||||
"""
|
"""
|
||||||
|
@ -98,3 +98,112 @@ class RackTestCase(TestCase):
|
|||||||
face=None,
|
face=None,
|
||||||
)
|
)
|
||||||
self.assertTrue(pdu)
|
self.assertTrue(pdu)
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
self.site = Site.objects.create(
|
||||||
|
name='TestSite1',
|
||||||
|
slug='my-test-site'
|
||||||
|
)
|
||||||
|
self.rack = Rack.objects.create(
|
||||||
|
name='TestRack1',
|
||||||
|
facility_id='A101',
|
||||||
|
site=self.site,
|
||||||
|
u_height=42
|
||||||
|
)
|
||||||
|
self.manufacturer = Manufacturer.objects.create(
|
||||||
|
name='Acme',
|
||||||
|
slug='acme'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.device_type = DeviceType.objects.create(
|
||||||
|
manufacturer=self.manufacturer,
|
||||||
|
model='FrameForwarder 2048',
|
||||||
|
slug='ff2048'
|
||||||
|
)
|
||||||
|
self.role = DeviceRole.objects.create(
|
||||||
|
name='Switch',
|
||||||
|
slug='switch',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_interface_order_natural(self):
|
||||||
|
device1 = Device.objects.create(
|
||||||
|
name='TestSwitch1',
|
||||||
|
device_type=self.device_type,
|
||||||
|
device_role=self.role,
|
||||||
|
site=self.site,
|
||||||
|
rack=self.rack,
|
||||||
|
position=10,
|
||||||
|
face=RACK_FACE_REAR,
|
||||||
|
)
|
||||||
|
interface1 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/3/1'
|
||||||
|
)
|
||||||
|
interface2 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/5/1'
|
||||||
|
)
|
||||||
|
interface3 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/4'
|
||||||
|
)
|
||||||
|
interface4 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/3/2/4'
|
||||||
|
)
|
||||||
|
interface5 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Ethernet1/3/2/1'
|
||||||
|
)
|
||||||
|
interface6 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='Loopback1'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
list(Interface.objects.all().order_naturally()),
|
||||||
|
[interface1, interface5, interface4, interface3, interface2, interface6]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_interface_order_natural_subinterfaces(self):
|
||||||
|
device1 = Device.objects.create(
|
||||||
|
name='TestSwitch1',
|
||||||
|
device_type=self.device_type,
|
||||||
|
device_role=self.role,
|
||||||
|
site=self.site,
|
||||||
|
rack=self.rack,
|
||||||
|
position=10,
|
||||||
|
face=RACK_FACE_REAR,
|
||||||
|
)
|
||||||
|
interface1 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/3'
|
||||||
|
)
|
||||||
|
interface2 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/2.2'
|
||||||
|
)
|
||||||
|
interface3 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/0.120'
|
||||||
|
)
|
||||||
|
interface4 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/0'
|
||||||
|
)
|
||||||
|
interface5 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0/0/1.117'
|
||||||
|
)
|
||||||
|
interface6 = Interface.objects.create(
|
||||||
|
device=device1,
|
||||||
|
name='GigabitEthernet0'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
list(Interface.objects.all().order_naturally()),
|
||||||
|
[interface4, interface3, interface5, interface2, interface1, interface6]
|
||||||
|
)
|
||||||
|
@ -34,7 +34,7 @@ RIR_ACTIONS = """
|
|||||||
|
|
||||||
UTILIZATION_GRAPH = """
|
UTILIZATION_GRAPH = """
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% if record.pk %}{% utilization_graph value %}{% else %}—{% endif %}
|
{% if record.pk %}{% utilization_graph record.get_utilization %}{% else %}—{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ROLE_ACTIONS = """
|
ROLE_ACTIONS = """
|
||||||
@ -210,10 +210,10 @@ class AggregateTable(BaseTable):
|
|||||||
|
|
||||||
class AggregateDetailTable(AggregateTable):
|
class AggregateDetailTable(AggregateTable):
|
||||||
child_count = tables.Column(verbose_name='Prefixes')
|
child_count = tables.Column(verbose_name='Prefixes')
|
||||||
get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
|
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
|
||||||
|
|
||||||
class Meta(AggregateTable.Meta):
|
class Meta(AggregateTable.Meta):
|
||||||
fields = ('pk', 'prefix', 'rir', 'child_count', 'get_utilization', 'date_added', 'description')
|
fields = ('pk', 'prefix', 'rir', 'child_count', 'utilization', 'date_added', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -256,10 +256,10 @@ class PrefixTable(BaseTable):
|
|||||||
|
|
||||||
|
|
||||||
class PrefixDetailTable(PrefixTable):
|
class PrefixDetailTable(PrefixTable):
|
||||||
get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
|
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False)
|
||||||
|
|
||||||
class Meta(PrefixTable.Meta):
|
class Meta(PrefixTable.Meta):
|
||||||
fields = ('pk', 'prefix', 'status', 'vrf', 'get_utilization', 'tenant', 'site', 'vlan', 'role', 'description')
|
fields = ('pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -473,11 +473,11 @@ class PrefixView(View):
|
|||||||
child_prefixes = Prefix.objects.filter(
|
child_prefixes = Prefix.objects.filter(
|
||||||
vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
|
vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
|
||||||
).select_related(
|
).select_related(
|
||||||
'site', 'role'
|
'site', 'vlan', 'role',
|
||||||
).annotate_depth(limit=0)
|
).annotate_depth(limit=0)
|
||||||
if child_prefixes:
|
if child_prefixes:
|
||||||
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
||||||
child_prefix_table = tables.PrefixTable(child_prefixes)
|
child_prefix_table = tables.PrefixDetailTable(child_prefixes)
|
||||||
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
||||||
child_prefix_table.base_columns['pk'].visible = True
|
child_prefix_table.base_columns['pk'].visible = True
|
||||||
|
|
||||||
|
@ -234,6 +234,10 @@ REST_FRAMEWORK = {
|
|||||||
'DEFAULT_PERMISSION_CLASSES': (
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
'utilities.api.TokenPermissions',
|
'utilities.api.TokenPermissions',
|
||||||
),
|
),
|
||||||
|
'DEFAULT_RENDERER_CLASSES': (
|
||||||
|
'rest_framework.renderers.JSONRenderer',
|
||||||
|
'utilities.api.FormlessBrowsableAPIRenderer',
|
||||||
|
),
|
||||||
'DEFAULT_VERSION': REST_FRAMEWORK_VERSION,
|
'DEFAULT_VERSION': REST_FRAMEWORK_VERSION,
|
||||||
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
|
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
|
||||||
'PAGE_SIZE': PAGINATE_COUNT,
|
'PAGE_SIZE': PAGINATE_COUNT,
|
||||||
|
@ -329,13 +329,14 @@ li.occupied + li.available {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Devices */
|
/* Devices */
|
||||||
table.component-list tr.ipaddress td {
|
table.component-list td.subtable {
|
||||||
background-color: #eeffff;
|
padding: 0;
|
||||||
padding-bottom: 4px;
|
padding-left: 16px;
|
||||||
padding-top: 4px;
|
|
||||||
}
|
}
|
||||||
table.component-list tr.ipaddress:hover td {
|
table.component-list td.subtable td {
|
||||||
background-color: #e6f7f7;
|
border: none;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AJAX loader */
|
/* AJAX loader */
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<tr class="interface{% if not iface.enabled %} danger{% elif iface.connection and iface.connection.connection_status %} success{% elif iface.connection and not iface.connection.connection_status %} info{% endif %}">
|
<tr class="interface{% if not iface.enabled %} danger{% elif iface.connection and iface.connection.connection_status or iface.circuit_termination %} success{% elif iface.connection and not iface.connection.connection_status %} info{% elif iface.is_virtual %} warning{% endif %}">
|
||||||
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
<td class="pk">
|
<td class="pk">
|
||||||
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
<input name="pk" type="checkbox" value="{{ iface.pk }}" />
|
||||||
@ -113,41 +113,55 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for ip in iface.ip_addresses.all %}
|
{% with iface.ip_addresses.all as ipaddresses %}
|
||||||
<tr class="ipaddress">
|
{% if ipaddresses %}
|
||||||
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
<tr class="ipaddress">
|
||||||
<td></td>
|
{% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
|
||||||
{% endif %}
|
<td></td>
|
||||||
<td colspan="3">
|
<td colspan="6" class="subtable">
|
||||||
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
|
|
||||||
{% if ip.description %}
|
|
||||||
<i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i>
|
|
||||||
{% endif %}
|
|
||||||
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
|
|
||||||
<span class="label label-success">Primary</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
{% if ip.vrf %}
|
|
||||||
<a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}">{{ ip.vrf }}</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">Global</span>
|
<td colspan="7" class="subtable">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
<table class="table table-hover">
|
||||||
<td>
|
{% for ip in ipaddresses %}
|
||||||
<span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
|
<tr>
|
||||||
</td>
|
<td>
|
||||||
<td class="text-right">
|
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a>
|
||||||
{% if perms.ipam.change_ipaddress %}
|
{% if ip.description %}
|
||||||
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
|
<i class="fa fa-fw fa-comment-o" title="{{ ip.description }}"></i>
|
||||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
{% endif %}
|
||||||
</a>
|
</td>
|
||||||
{% endif %}
|
<td>
|
||||||
{% if perms.ipam.delete_ipaddress %}
|
{% if device.primary_ip4 == ip or device.primary_ip6 == ip %}
|
||||||
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
|
<span class="label label-success">Primary</span>
|
||||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
|
{% endif %}
|
||||||
</a>
|
</td>
|
||||||
{% endif %}
|
<td>
|
||||||
</td>
|
{% if ip.vrf %}
|
||||||
</tr>
|
<a href="{% url 'ipam:vrf' pk=ip.vrf.pk %}">{{ ip.vrf }}</a>
|
||||||
{% endfor %}
|
{% else %}
|
||||||
|
<span class="text-muted">Global table</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="label label-{{ ip.get_status_class }}">{{ ip.get_status_display }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{% if perms.ipam.change_ipaddress %}
|
||||||
|
<a href="{% url 'ipam:ipaddress_edit' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs">
|
||||||
|
<i class="glyphicon glyphicon-pencil" aria-hidden="true" title="Edit IP address"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.delete_ipaddress %}
|
||||||
|
<a href="{% url 'ipam:ipaddress_delete' pk=ip.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
|
||||||
|
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete IP address"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
@ -8,6 +8,7 @@ from rest_framework.compat import is_authenticated
|
|||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
from rest_framework.permissions import BasePermission, DjangoModelPermissions, SAFE_METHODS
|
from rest_framework.permissions import BasePermission, DjangoModelPermissions, SAFE_METHODS
|
||||||
|
from rest_framework.renderers import BrowsableAPIRenderer
|
||||||
from rest_framework.serializers import Field, ModelSerializer, ValidationError
|
from rest_framework.serializers import Field, ModelSerializer, ValidationError
|
||||||
from rest_framework.views import get_view_name as drf_get_view_name
|
from rest_framework.views import get_view_name as drf_get_view_name
|
||||||
|
|
||||||
@ -206,6 +207,18 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
|||||||
return self.default_limit
|
return self.default_limit
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Renderers
|
||||||
|
#
|
||||||
|
|
||||||
|
class FormlessBrowsableAPIRenderer(BrowsableAPIRenderer):
|
||||||
|
"""
|
||||||
|
Override the built-in BrowsableAPIRenderer to disable HTML forms.
|
||||||
|
"""
|
||||||
|
def show_form_for_method(self, *args, **kwargs):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
#
|
#
|
||||||
|
@ -478,7 +478,7 @@ class BulkEditView(View):
|
|||||||
template_name = 'utilities/obj_bulk_edit.html'
|
template_name = 'utilities/obj_bulk_edit.html'
|
||||||
default_return_url = 'home'
|
default_return_url = 'home'
|
||||||
|
|
||||||
def get(self):
|
def get(self, request):
|
||||||
return redirect(self.default_return_url)
|
return redirect(self.default_return_url)
|
||||||
|
|
||||||
def post(self, request, **kwargs):
|
def post(self, request, **kwargs):
|
||||||
@ -626,6 +626,9 @@ class BulkDeleteView(View):
|
|||||||
template_name = 'utilities/obj_bulk_delete.html'
|
template_name = 'utilities/obj_bulk_delete.html'
|
||||||
default_return_url = 'home'
|
default_return_url = 'home'
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
return redirect(self.default_return_url)
|
||||||
|
|
||||||
def post(self, request, **kwargs):
|
def post(self, request, **kwargs):
|
||||||
|
|
||||||
# Attempt to derive parent object if a parent class has been given
|
# Attempt to derive parent object if a parent class has been given
|
||||||
|
Loading…
Reference in New Issue
Block a user