Merge branch 'develop' into develop

This commit is contained in:
Nicholas Totsch 2017-10-31 14:36:22 -05:00 committed by GitHub
commit a62d4645ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 148 additions and 141 deletions

View File

@ -7,7 +7,7 @@ from django.db.models import Q
from dcim.models import Site from dcim.models import Site
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter from utilities.filters import NumericInFilter
from .models import Provider, Circuit, CircuitTermination, CircuitType from .models import Provider, Circuit, CircuitTermination, CircuitType
@ -78,12 +78,12 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Circuit type (slug)', label='Circuit type (slug)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',

View File

@ -221,7 +221,7 @@ class WritableRackReservationSerializer(ValidatedModelSerializer):
class Meta: class Meta:
model = RackReservation model = RackReservation
fields = ['id', 'rack', 'units', 'description'] fields = ['id', 'rack', 'units', 'user', 'description']
# #

View File

@ -9,7 +9,7 @@ from django.db.models import Q
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.filters import NullableCharFieldFilter, NullableModelMultipleChoiceFilter, NumericInFilter from utilities.filters import NullableCharFieldFilter, NumericInFilter
from virtualization.models import Cluster from virtualization.models import Cluster
from .models import ( from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
@ -21,11 +21,12 @@ from .models import (
class RegionFilter(django_filters.FilterSet): class RegionFilter(django_filters.FilterSet):
parent_id = NullableModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
label='Parent region (ID)', label='Parent region (ID)',
) )
parent = NullableModelMultipleChoiceFilter( parent = django_filters.ModelMultipleChoiceFilter(
name='parent__slug',
queryset=Region.objects.all(), queryset=Region.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Parent region (slug)', label='Parent region (slug)',
@ -42,20 +43,22 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
region_id = NullableModelMultipleChoiceFilter( region_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
label='Region (ID)', label='Region (ID)',
) )
region = NullableModelMultipleChoiceFilter( region = django_filters.ModelMultipleChoiceFilter(
name='region__slug',
queryset=Region.objects.all(), queryset=Region.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Region (slug)', label='Region (slug)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
@ -126,32 +129,32 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
name='group', name='group__slug',
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Group', label='Group',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )
role_id = NullableModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = NullableModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
name='role', name='role__slug',
queryset=RackRole.objects.all(), queryset=RackRole.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label='Role (slug)',
@ -193,13 +196,13 @@ class RackReservationFilter(django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
name='rack__group', name='rack__group',
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
name='rack__group', name='rack__group__slug',
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Group', label='Group',
@ -378,22 +381,22 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label='Role (slug)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )
platform_id = NullableModelMultipleChoiceFilter( platform_id = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
label='Platform (ID)', label='Platform (ID)',
) )
platform = NullableModelMultipleChoiceFilter( platform = django_filters.ModelMultipleChoiceFilter(
name='platform', name='platform__slug',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Platform (slug)', label='Platform (slug)',
@ -415,12 +418,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
queryset=RackGroup.objects.all(), queryset=RackGroup.objects.all(),
label='Rack group (ID)', label='Rack group (ID)',
) )
rack_id = NullableModelMultipleChoiceFilter( rack_id = django_filters.ModelMultipleChoiceFilter(
name='rack', name='rack',
queryset=Rack.objects.all(), queryset=Rack.objects.all(),
label='Rack (ID)', label='Rack (ID)',
) )
cluster_id = NullableModelMultipleChoiceFilter( cluster_id = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
label='VM cluster (ID)', label='VM cluster (ID)',
) )
@ -605,7 +608,7 @@ class DeviceBayFilter(DeviceComponentFilterSet):
class InventoryItemFilter(DeviceComponentFilterSet): class InventoryItemFilter(DeviceComponentFilterSet):
parent_id = NullableModelMultipleChoiceFilter( parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItem.objects.all(), queryset=InventoryItem.objects.all(),
label='Parent inventory item (ID)', label='Parent inventory item (ID)',
) )

View File

@ -4,6 +4,7 @@ from mptt.forms import TreeNodeChoiceField
import re import re
from django import forms from django import forms
from django.contrib.auth.models import User
from django.contrib.postgres.forms.array import SimpleArrayField from django.contrib.postgres.forms.array import SimpleArrayField
from django.db.models import Count, Q from django.db.models import Count, Q
@ -376,6 +377,7 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm): class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm):
units = SimpleArrayField(forms.IntegerField(), widget=ArrayFieldSelectMultiple(attrs={'size': 10})) units = SimpleArrayField(forms.IntegerField(), widget=ArrayFieldSelectMultiple(attrs={'size': 10}))
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
class Meta: class Meta:
model = RackReservation model = RackReservation
@ -416,6 +418,15 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form):
) )
class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=RackReservation.objects.all(), widget=forms.MultipleHiddenInput)
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'), required=False)
description = forms.CharField(max_length=100, required=False)
class Meta:
nullable_fields = []
# #
# Manufacturers # Manufacturers
# #

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-10-31 17:32
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0048_rack_serial'),
]
operations = [
migrations.AlterField(
model_name='rackreservation',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -296,6 +296,7 @@ class Rack(CreatedUpdatedModel, CustomFieldModel):
self.tenant.name if self.tenant else None, self.tenant.name if self.tenant else None,
self.role.name if self.role else None, self.role.name if self.role else None,
self.get_type_display() if self.type else None, self.get_type_display() if self.type else None,
self.serial,
self.width, self.width,
self.u_height, self.u_height,
self.desc_units, self.desc_units,
@ -418,7 +419,7 @@ class RackReservation(models.Model):
units = ArrayField(models.PositiveSmallIntegerField()) units = ArrayField(models.PositiveSmallIntegerField())
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='rackreservations', on_delete=models.PROTECT) tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='rackreservations', on_delete=models.PROTECT)
user = models.ForeignKey(User, editable=False, on_delete=models.PROTECT) user = models.ForeignKey(User, on_delete=models.PROTECT)
description = models.CharField(max_length=100) description = models.CharField(max_length=100)
class Meta: class Meta:

View File

@ -45,6 +45,7 @@ urlpatterns = [
# Rack reservations # Rack reservations
url(r'^rack-reservations/$', views.RackReservationListView.as_view(), name='rackreservation_list'), url(r'^rack-reservations/$', views.RackReservationListView.as_view(), name='rackreservation_list'),
url(r'^rack-reservations/edit/$', views.RackReservationBulkEditView.as_view(), name='rackreservation_bulk_edit'),
url(r'^rack-reservations/delete/$', views.RackReservationBulkDeleteView.as_view(), name='rackreservation_bulk_delete'), url(r'^rack-reservations/delete/$', views.RackReservationBulkDeleteView.as_view(), name='rackreservation_bulk_delete'),
url(r'^rack-reservations/(?P<pk>\d+)/edit/$', views.RackReservationEditView.as_view(), name='rackreservation_edit'), url(r'^rack-reservations/(?P<pk>\d+)/edit/$', views.RackReservationEditView.as_view(), name='rackreservation_edit'),
url(r'^rack-reservations/(?P<pk>\d+)/delete/$', views.RackReservationDeleteView.as_view(), name='rackreservation_delete'), url(r'^rack-reservations/(?P<pk>\d+)/delete/$', views.RackReservationDeleteView.as_view(), name='rackreservation_delete'),

View File

@ -426,6 +426,16 @@ class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
return obj.rack.get_absolute_url() return obj.rack.get_absolute_url()
class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_rackreservation'
cls = RackReservation
queryset = RackReservation.objects.select_related('rack', 'user')
filter = filters.RackReservationFilter
table = tables.RackReservationTable
form = forms.RackReservationBulkEditForm
default_return_url = 'dcim:rackreservation_list'
class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_rackreservation' permission_required = 'dcim.delete_rackreservation'
cls = RackReservation cls = RackReservation

View File

@ -9,7 +9,7 @@ from django.db.models import Q
from dcim.models import Site, Device, Interface from dcim.models import Site, Device, Interface
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter from utilities.filters import NumericInFilter
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
from .models import ( from .models import (
Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Aggregate, IPAddress, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role,
@ -23,12 +23,12 @@ class VRFFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
@ -110,37 +110,37 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='filter_mask_length', method='filter_mask_length',
label='Mask length', label='Mask length',
) )
vrf_id = NullableModelMultipleChoiceFilter( vrf_id = django_filters.ModelMultipleChoiceFilter(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
label='VRF', label='VRF',
) )
vrf = NullableModelMultipleChoiceFilter( vrf = django_filters.ModelMultipleChoiceFilter(
name='vrf', name='vrf__rd',
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
to_field_name='rd', to_field_name='rd',
label='VRF (RD)', label='VRF (RD)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )
site_id = NullableModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label='Site (ID)',
) )
site = NullableModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
name='site', name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
vlan_id = NullableModelMultipleChoiceFilter( vlan_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
label='VLAN (ID)', label='VLAN (ID)',
) )
@ -148,12 +148,12 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
name='vlan__vid', name='vlan__vid',
label='VLAN number (1-4095)', label='VLAN number (1-4095)',
) )
role_id = NullableModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=Role.objects.all(), queryset=Role.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = NullableModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
name='role', name='role__slug',
queryset=Role.objects.all(), queryset=Role.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label='Role (slug)',
@ -207,22 +207,22 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='filter_mask_length', method='filter_mask_length',
label='Mask length', label='Mask length',
) )
vrf_id = NullableModelMultipleChoiceFilter( vrf_id = django_filters.ModelMultipleChoiceFilter(
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
label='VRF', label='VRF',
) )
vrf = NullableModelMultipleChoiceFilter( vrf = django_filters.ModelMultipleChoiceFilter(
name='vrf', name='vrf__rd',
queryset=VRF.objects.all(), queryset=VRF.objects.all(),
to_field_name='rd', to_field_name='rd',
label='VRF (RD)', label='VRF (RD)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
@ -290,12 +290,12 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
class VLANGroupFilter(django_filters.FilterSet): class VLANGroupFilter(django_filters.FilterSet):
site_id = NullableModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label='Site (ID)',
) )
site = NullableModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
name='site', name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
@ -312,42 +312,42 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
site_id = NullableModelMultipleChoiceFilter( site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(), queryset=Site.objects.all(),
label='Site (ID)', label='Site (ID)',
) )
site = NullableModelMultipleChoiceFilter( site = django_filters.ModelMultipleChoiceFilter(
name='site', name='site__slug',
queryset=Site.objects.all(), queryset=Site.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Site (slug)', label='Site (slug)',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
name='group', name='group__slug',
queryset=VLANGroup.objects.all(), queryset=VLANGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Group', label='Group',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant', name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )
role_id = NullableModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=Role.objects.all(), queryset=Role.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = NullableModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
name='role', name='role__slug',
queryset=Role.objects.all(), queryset=Role.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label='Role (slug)',

View File

@ -13,7 +13,7 @@ except ImportError:
) )
VERSION = '2.2.3-dev' VERSION = '2.2.5-dev'
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -206,6 +206,10 @@ LOGIN_URL = '/{}login/'.format(BASE_PATH)
# Secrets # Secrets
SECRETS_MIN_PUBKEY_SIZE = 2048 SECRETS_MIN_PUBKEY_SIZE = 2048
# Django filters
FILTERS_NULL_CHOICE_LABEL = 'None'
FILTERS_NULL_CHOICE_VALUE = '0' # Must be a string
# Django REST framework (API) # Django REST framework (API)
REST_FRAMEWORK_VERSION = VERSION[0:3] # Use major.minor as API version REST_FRAMEWORK_VERSION = VERSION[0:3] # Use major.minor as API version
REST_FRAMEWORK = { REST_FRAMEWORK = {

View File

@ -120,7 +120,7 @@
</td> </td>
<td> <td>
<strong>Network Device</strong><br /> <strong>Network Device</strong><br />
<small class="text-muted">This device {% if devicetype.is_network_device %}has{% else %}does not have{% endif %} non-management network interfaces</small> <small class="text-muted">This device {% if devicetype.is_network_device %}has{% else %}does not have{% endif %} network interfaces</small>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -5,7 +5,7 @@
<h1>{% block title %}Rack Reservations{% endblock %}</h1> <h1>{% block title %}Rack Reservations{% endblock %}</h1>
<div class="row"> <div class="row">
<div class="col-md-9"> <div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_delete_url='dcim:rackreservation_bulk_delete' %} {% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rackreservation_bulk_edit' bulk_delete_url='dcim:rackreservation_bulk_delete' %}
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
{% include 'inc/search_panel.html' %} {% include 'inc/search_panel.html' %}

View File

@ -5,7 +5,7 @@ import django_filters
from django.db.models import Q from django.db.models import Q
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter from utilities.filters import NumericInFilter
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -22,12 +22,12 @@ class TenantFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search', method='search',
label='Search', label='Search',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
label='Group (ID)', label='Group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
name='group', name='group__slug',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Group (slug)', label='Group (slug)',

View File

@ -4,7 +4,6 @@ import django_filters
import itertools import itertools
from django import forms from django import forms
from django.db.models import Q
from django.utils.encoding import force_text from django.utils.encoding import force_text
@ -66,51 +65,3 @@ class NullableModelMultipleChoiceField(forms.ModelMultipleChoiceField):
stripped_value = value stripped_value = value
super(NullableModelMultipleChoiceField, self).clean(stripped_value) super(NullableModelMultipleChoiceField, self).clean(stripped_value)
return value return value
class NullableModelMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
"""
This class extends ModelMultipleChoiceFilter to accept an additional value which implies "is null". The default
queryset filter argument is:
.filter(fieldname=value)
When filtering by the value representing "is null" ('0' by default) the argument is modified to:
.filter(fieldname__isnull=True)
"""
field_class = NullableModelMultipleChoiceField
def __init__(self, *args, **kwargs):
self.null_value = kwargs.get('null_value', 0)
super(NullableModelMultipleChoiceFilter, self).__init__(*args, **kwargs)
def filter(self, qs, value):
value = value or () # Make sure we have an iterable
if self.is_noop(qs, value):
return qs
# Even though not a noop, no point filtering if empty
if not value:
return qs
q = Q()
for v in set(value):
# Filtering by "is null"
if v == force_text(self.null_value):
arg = {'{}__isnull'.format(self.name): True}
# Filtering by a related field (e.g. slug)
elif self.field.to_field_name is not None:
arg = {'{}__{}'.format(self.name, self.field.to_field_name): v}
# Filtering by primary key (default)
else:
arg = {self.name: v}
if self.conjoined:
qs = self.get_method(qs)(**arg)
else:
q |= Q(**arg)
if self.distinct:
return self.get_method(qs)(q).distinct()
return self.get_method(qs)(q)

View File

@ -9,7 +9,7 @@ from django.db.models import Q
from dcim.models import DeviceRole, Interface, Platform, Site from dcim.models import DeviceRole, Interface, Platform, Site
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter from utilities.filters import NumericInFilter
from .constants import STATUS_CHOICES from .constants import STATUS_CHOICES
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@ -20,11 +20,12 @@ class ClusterFilter(CustomFieldFilterSet):
method='search', method='search',
label='Search', label='Search',
) )
group_id = NullableModelMultipleChoiceFilter( group_id = django_filters.ModelMultipleChoiceFilter(
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
label='Parent group (ID)', label='Parent group (ID)',
) )
group = NullableModelMultipleChoiceFilter( group = django_filters.ModelMultipleChoiceFilter(
name='group__slug',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Parent group (slug)', label='Parent group (slug)',
@ -72,13 +73,13 @@ class VirtualMachineFilter(CustomFieldFilterSet):
status = django_filters.MultipleChoiceFilter( status = django_filters.MultipleChoiceFilter(
choices=STATUS_CHOICES choices=STATUS_CHOICES
) )
cluster_group_id = NullableModelMultipleChoiceFilter( cluster_group_id = django_filters.ModelMultipleChoiceFilter(
name='cluster__group', name='cluster__group',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
label='Cluster group (ID)', label='Cluster group (ID)',
) )
cluster_group = NullableModelMultipleChoiceFilter( cluster_group = django_filters.ModelMultipleChoiceFilter(
name='cluster__group', name='cluster__group__slug',
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Cluster group (slug)', label='Cluster group (slug)',
@ -87,29 +88,32 @@ class VirtualMachineFilter(CustomFieldFilterSet):
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
label='Cluster (ID)', label='Cluster (ID)',
) )
role_id = NullableModelMultipleChoiceFilter( role_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
label='Role (ID)', label='Role (ID)',
) )
role = NullableModelMultipleChoiceFilter( role = django_filters.ModelMultipleChoiceFilter(
name='role__slug',
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Role (slug)', label='Role (slug)',
) )
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = NullableModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )
platform_id = NullableModelMultipleChoiceFilter( platform_id = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
label='Platform (ID)', label='Platform (ID)',
) )
platform = NullableModelMultipleChoiceFilter( platform = django_filters.ModelMultipleChoiceFilter(
name='platform__slug',
queryset=Platform.objects.all(), queryset=Platform.objects.all(),
to_field_name='slug', to_field_name='slug',
label='Platform (slug)', label='Platform (slug)',

View File

@ -1,7 +1,7 @@
Django>=1.11,<2.0 Django>=1.11,<2.0
django-cors-headers>=2.1 django-cors-headers>=2.1
django-debug-toolbar>=1.8 django-debug-toolbar>=1.8
django-filter>=1.0.4 django-filter>=1.1.0
django-mptt==0.8.7 django-mptt==0.8.7
django-rest-swagger>=2.1.0 django-rest-swagger>=2.1.0
django-tables2>=1.10.0 django-tables2>=1.10.0