mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 17:08:41 -06:00
#527: Initial work to allow nullifying fields during bulk edit
This commit is contained in:
parent
8ed174e7af
commit
36066068d4
@ -3,7 +3,6 @@ from django.db.models import Count
|
|||||||
|
|
||||||
from dcim.models import Site, Device, Interface, Rack, IFACE_FF_VIRTUAL
|
from dcim.models import Site, Device, Interface, Rack, IFACE_FF_VIRTUAL
|
||||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.forms import bulkedit_tenant_choices
|
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, FilterChoiceField, Livesearch, SmallTextarea,
|
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, FilterChoiceField, Livesearch, SmallTextarea,
|
||||||
@ -57,6 +56,9 @@ class ProviderBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
|
admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
|
||||||
|
|
||||||
|
|
||||||
class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Provider
|
model = Provider
|
||||||
@ -178,11 +180,14 @@ class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
|
type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
|
||||||
provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
|
provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
|
||||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
port_speed = forms.IntegerField(required=False, label='Port speed (Kbps)')
|
port_speed = forms.IntegerField(required=False, label='Port speed (Kbps)')
|
||||||
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
|
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['tenant', 'port_speed', 'commit_rate', 'comments']
|
||||||
|
|
||||||
|
|
||||||
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Circuit
|
model = Circuit
|
||||||
|
@ -5,11 +5,11 @@ from django.db.models import Count, Q
|
|||||||
|
|
||||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from tenancy.forms import bulkedit_tenant_choices
|
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, add_blank_choice, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
|
APISelect, add_blank_choice, BootstrapMixin, BulkEditForm, BulkImportForm, CommentField, CSVDataField,
|
||||||
FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea,
|
||||||
|
SlugField,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -42,39 +42,6 @@ def get_device_by_name_or_pk(name):
|
|||||||
return device
|
return device
|
||||||
|
|
||||||
|
|
||||||
def bulkedit_platform_choices():
|
|
||||||
choices = [
|
|
||||||
(None, '---------'),
|
|
||||||
(0, 'None'),
|
|
||||||
]
|
|
||||||
choices += [(p.pk, p.name) for p in Platform.objects.all()]
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
def bulkedit_rackgroup_choices():
|
|
||||||
"""
|
|
||||||
Include an option to remove the currently assigned group from a rack.
|
|
||||||
"""
|
|
||||||
choices = [
|
|
||||||
(None, '---------'),
|
|
||||||
(0, 'None'),
|
|
||||||
]
|
|
||||||
choices += [(r.pk, r) for r in RackGroup.objects.all()]
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
def bulkedit_rackrole_choices():
|
|
||||||
"""
|
|
||||||
Include an option to remove the currently assigned role from a rack.
|
|
||||||
"""
|
|
||||||
choices = [
|
|
||||||
(None, '---------'),
|
|
||||||
(0, 'None'),
|
|
||||||
]
|
|
||||||
choices += [(r.pk, r.name) for r in RackRole.objects.all()]
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Sites
|
# Sites
|
||||||
#
|
#
|
||||||
@ -114,7 +81,10 @@ class SiteImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
|
|
||||||
class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['tenant']
|
||||||
|
|
||||||
|
|
||||||
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
@ -234,14 +204,17 @@ class RackImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site')
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site')
|
||||||
group = forms.TypedChoiceField(choices=bulkedit_rackgroup_choices, coerce=int, required=False, label='Group')
|
group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False, label='Group')
|
||||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
role = forms.TypedChoiceField(choices=bulkedit_rackrole_choices, coerce=int, required=False, label='Role')
|
role = forms.ModelChoiceField(queryset=RackRole.objects.all(), required=False)
|
||||||
type = forms.ChoiceField(choices=add_blank_choice(RACK_TYPE_CHOICES), required=False, label='Type')
|
type = forms.ChoiceField(choices=add_blank_choice(RACK_TYPE_CHOICES), required=False, label='Type')
|
||||||
width = forms.ChoiceField(choices=add_blank_choice(RACK_WIDTH_CHOICES), required=False, label='Width')
|
width = forms.ChoiceField(choices=add_blank_choice(RACK_WIDTH_CHOICES), required=False, label='Width')
|
||||||
u_height = forms.IntegerField(required=False, label='Height (U)')
|
u_height = forms.IntegerField(required=False, label='Height (U)')
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['group', 'tenant', 'role', 'comments']
|
||||||
|
|
||||||
|
|
||||||
class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Rack
|
model = Rack
|
||||||
@ -279,7 +252,7 @@ class DeviceTypeForm(forms.ModelForm, BootstrapMixin):
|
|||||||
'is_pdu', 'is_network_device', 'subdevice_role']
|
'is_pdu', 'is_network_device', 'subdevice_role']
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
|
class DeviceTypeBulkEditForm(BulkEditForm, BootstrapMixin):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
|
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
|
||||||
u_height = forms.IntegerField(min_value=1, required=False)
|
u_height = forms.IntegerField(min_value=1, required=False)
|
||||||
@ -583,12 +556,14 @@ class DeviceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), required=False, label='Type')
|
device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), required=False, label='Type')
|
||||||
device_role = forms.ModelChoiceField(queryset=DeviceRole.objects.all(), required=False, label='Role')
|
device_role = forms.ModelChoiceField(queryset=DeviceRole.objects.all(), required=False, label='Role')
|
||||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
platform = forms.TypedChoiceField(choices=bulkedit_platform_choices, coerce=int, required=False,
|
platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)
|
||||||
label='Platform')
|
|
||||||
status = forms.ChoiceField(choices=FORM_STATUS_CHOICES, required=False, initial='', label='Status')
|
status = forms.ChoiceField(choices=FORM_STATUS_CHOICES, required=False, initial='', label='Status')
|
||||||
serial = forms.CharField(max_length=50, required=False, label='Serial Number')
|
serial = forms.CharField(max_length=50, required=False, label='Serial Number')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['tenant', 'platform']
|
||||||
|
|
||||||
|
|
||||||
class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Device
|
model = Device
|
||||||
|
@ -3,7 +3,7 @@ from collections import OrderedDict
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from utilities.forms import LaxURLField
|
from utilities.forms import BulkEditForm, LaxURLField
|
||||||
from .models import (
|
from .models import (
|
||||||
CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, CustomField, CustomFieldValue
|
CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CF_TYPE_URL, CustomField, CustomFieldValue
|
||||||
)
|
)
|
||||||
@ -49,8 +49,6 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
|||||||
# Select
|
# Select
|
||||||
elif cf.type == CF_TYPE_SELECT:
|
elif cf.type == CF_TYPE_SELECT:
|
||||||
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
|
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
|
||||||
if not cf.required:
|
|
||||||
choices = [(0, 'None')] + choices
|
|
||||||
if bulk_edit or filterable_only:
|
if bulk_edit or filterable_only:
|
||||||
choices = [(None, '---------')] + choices
|
choices = [(None, '---------')] + choices
|
||||||
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required)
|
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required)
|
||||||
@ -126,7 +124,7 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldBulkEditForm(forms.Form):
|
class CustomFieldBulkEditForm(BulkEditForm):
|
||||||
custom_fields = []
|
custom_fields = []
|
||||||
|
|
||||||
def __init__(self, model, *args, **kwargs):
|
def __init__(self, model, *args, **kwargs):
|
||||||
|
@ -3,7 +3,6 @@ from django.db.models import Count
|
|||||||
|
|
||||||
from dcim.models import Site, Device, Interface
|
from dcim.models import Site, Device, Interface
|
||||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.forms import bulkedit_tenant_choices
|
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, BootstrapMixin, CSVDataField, BulkImportForm, FilterChoiceField, Livesearch, SlugField,
|
APISelect, BootstrapMixin, CSVDataField, BulkImportForm, FilterChoiceField, Livesearch, SlugField,
|
||||||
@ -23,18 +22,6 @@ IP_FAMILY_CHOICES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def bulkedit_vrf_choices():
|
|
||||||
"""
|
|
||||||
Include an option to assign the object to the global table.
|
|
||||||
"""
|
|
||||||
choices = [
|
|
||||||
(None, '---------'),
|
|
||||||
(0, 'Global'),
|
|
||||||
]
|
|
||||||
choices += [(v.pk, v.name) for v in VRF.objects.all()]
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# VRFs
|
# VRFs
|
||||||
#
|
#
|
||||||
@ -67,9 +54,12 @@ class VRFImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
|
|
||||||
class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['tenant', 'description']
|
||||||
|
|
||||||
|
|
||||||
class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = VRF
|
model = VRF
|
||||||
@ -124,6 +114,9 @@ class AggregateBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
date_added = forms.DateField(required=False)
|
date_added = forms.DateField(required=False)
|
||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['date_added', 'description']
|
||||||
|
|
||||||
|
|
||||||
class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Aggregate
|
model = Aggregate
|
||||||
@ -253,12 +246,15 @@ class PrefixImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
||||||
vrf = forms.TypedChoiceField(choices=bulkedit_vrf_choices, coerce=int, required=False, label='VRF')
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
|
||||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
status = forms.ChoiceField(choices=FORM_PREFIX_STATUS_CHOICES, required=False)
|
status = forms.ChoiceField(choices=FORM_PREFIX_STATUS_CHOICES, required=False)
|
||||||
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['site', 'vrf', 'tenant', 'role', 'description']
|
||||||
|
|
||||||
|
|
||||||
def prefix_status_choices():
|
def prefix_status_choices():
|
||||||
status_counts = {}
|
status_counts = {}
|
||||||
@ -407,10 +403,13 @@ class IPAddressImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
|
|
||||||
class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class IPAddressBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
vrf = forms.TypedChoiceField(choices=bulkedit_vrf_choices, coerce=int, required=False, label='VRF')
|
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF')
|
||||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['vrf', 'tenant', 'description']
|
||||||
|
|
||||||
|
|
||||||
class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
@ -509,11 +508,14 @@ class VLANBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
||||||
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
|
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
|
||||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
status = forms.ChoiceField(choices=FORM_VLAN_STATUS_CHOICES, required=False)
|
status = forms.ChoiceField(choices=FORM_VLAN_STATUS_CHOICES, required=False)
|
||||||
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
||||||
description = forms.CharField(max_length=100, required=False)
|
description = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['group', 'tenant', 'role', 'description']
|
||||||
|
|
||||||
|
|
||||||
def vlan_status_choices():
|
def vlan_status_choices():
|
||||||
status_counts = {}
|
status_counts = {}
|
||||||
|
@ -37,6 +37,11 @@ $(document).ready(function() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bulk edit nullification
|
||||||
|
$('input:checkbox[name=_nullify]').click(function (event) {
|
||||||
|
$('#id_' + this.value).toggle('disabled');
|
||||||
|
});
|
||||||
|
|
||||||
// API select widget
|
// API select widget
|
||||||
$('select[filter-for]').change(function () {
|
$('select[filter-for]').change(function () {
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from django import forms
|
|||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from utilities.forms import BootstrapMixin, BulkImportForm, CSVDataField, FilterChoiceField, SlugField
|
from utilities.forms import BootstrapMixin, BulkEditForm, BulkImportForm, CSVDataField, FilterChoiceField, SlugField
|
||||||
|
|
||||||
from .models import Secret, SecretRole, UserKey
|
from .models import Secret, SecretRole, UserKey
|
||||||
|
|
||||||
@ -89,11 +89,14 @@ class SecretImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
csv = CSVDataField(csv_form=SecretFromCSVForm, widget=forms.Textarea(attrs={'class': 'requires-private-key'}))
|
csv = CSVDataField(csv_form=SecretFromCSVForm, widget=forms.Textarea(attrs={'class': 'requires-private-key'}))
|
||||||
|
|
||||||
|
|
||||||
class SecretBulkEditForm(forms.Form, BootstrapMixin):
|
class SecretBulkEditForm(BulkEditForm, BootstrapMixin):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Secret.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), required=False)
|
role = forms.ModelChoiceField(queryset=SecretRole.objects.all(), required=False)
|
||||||
name = forms.CharField(max_length=100, required=False)
|
name = forms.CharField(max_length=100, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
class SecretFilterForm(forms.Form, BootstrapMixin):
|
class SecretFilterForm(forms.Form, BootstrapMixin):
|
||||||
role = FilterChoiceField(queryset=SecretRole.objects.annotate(filter_count=Count('secrets')), to_field_name='slug')
|
role = FilterChoiceField(queryset=SecretRole.objects.annotate(filter_count=Count('secrets')), to_field_name='slug')
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
{% if request.POST.redirect_url %}
|
{% if request.POST.redirect_url %}
|
||||||
<input type="hidden" name="redirect_url" value="{{ request.POST.redirect_url }}" />
|
<input type="hidden" name="redirect_url" value="{{ request.POST.redirect_url }}" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% for field in form.hidden_fields %}
|
||||||
|
{{ field }}
|
||||||
|
{% endfor %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@ -29,7 +32,13 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>{% block form_title %}Attributes{% endblock %}</strong></div>
|
<div class="panel-heading"><strong>{% block form_title %}Attributes{% endblock %}</strong></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% render_form form %}
|
{% for field in form.visible_fields %}
|
||||||
|
{% if field.name in form.nullable_fields %}
|
||||||
|
{% render_field field bulk_nullable=True %}
|
||||||
|
{% else %}
|
||||||
|
{% render_field field %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group text-right">
|
<div class="form-group text-right">
|
||||||
|
@ -5,26 +5,26 @@
|
|||||||
<div class="col-md-9 col-md-offset-3">
|
<div class="col-md-9 col-md-offset-3">
|
||||||
<div class="checkbox{% if field.errors %} has-error{% endif %}">
|
<div class="checkbox{% if field.errors %} has-error{% endif %}">
|
||||||
<label for="{{ field.id_for_label }}">
|
<label for="{{ field.id_for_label }}">
|
||||||
{{ field }}
|
{{ field }} {{ field.label }}
|
||||||
{{ field.label }}
|
|
||||||
</label>
|
</label>
|
||||||
{% if field.help_text %}
|
{% if field.help_text %}
|
||||||
<span class="help-block">{{ field.help_text|safe }}</span>
|
<span class="help-block">{{ field.help_text|safe }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% if bulk_nullable %}
|
||||||
{% elif field|widget_type == 'radioselect' %}
|
<label class="checkbox-inline">
|
||||||
<div class="col-md-9 col-md-offset-3">
|
<input type="checkbox" name="_nullify" value="{{ field.name }}" /> Set null
|
||||||
<div class="radio{% if field.errors %} has-error{% endif %}">
|
|
||||||
<label for="{{ field.id_for_label }}">
|
|
||||||
{{ field }}
|
|
||||||
{{ field.label }}
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% elif field|widget_type == 'textarea' %}
|
{% elif field|widget_type == 'textarea' %}
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
{{ field }}
|
{{ field }}
|
||||||
|
{% if bulk_nullable %}
|
||||||
|
<label class="checkbox-inline">
|
||||||
|
<input type="checkbox" name="_nullify" value="{{ field.name }}" /> Set null
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
{% if field.help_text %}
|
{% if field.help_text %}
|
||||||
<span class="help-block">{{ field.help_text|safe }}</span>
|
<span class="help-block">{{ field.help_text|safe }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -40,6 +40,11 @@
|
|||||||
<label class="col-md-3 control-label{% if field.field.required %} required{% endif %}" for="{{ field.id_for_label }}">{{ field.label }}</label>
|
<label class="col-md-3 control-label{% if field.field.required %} required{% endif %}" for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{{ field }}
|
{{ field }}
|
||||||
|
{% if bulk_nullable %}
|
||||||
|
<label class="checkbox-inline">
|
||||||
|
<input type="checkbox" name="_nullify" value="{{ field.name }}" /> Set null
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
{% if field.help_text %}
|
{% if field.help_text %}
|
||||||
<span class="help-block">{{ field.help_text|safe }}</span>
|
<span class="help-block">{{ field.help_text|safe }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -7,30 +7,6 @@ from utilities.forms import BootstrapMixin, BulkImportForm, CommentField, CSVDat
|
|||||||
from .models import Tenant, TenantGroup
|
from .models import Tenant, TenantGroup
|
||||||
|
|
||||||
|
|
||||||
def bulkedit_tenantgroup_choices():
|
|
||||||
"""
|
|
||||||
Include an option to remove the currently assigned TenantGroup from a Tenant.
|
|
||||||
"""
|
|
||||||
choices = [
|
|
||||||
(None, '---------'),
|
|
||||||
(0, 'None'),
|
|
||||||
]
|
|
||||||
choices += [(g.pk, g.name) for g in TenantGroup.objects.all()]
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
def bulkedit_tenant_choices():
|
|
||||||
"""
|
|
||||||
Include an option to remove the currently assigned Tenant from an object.
|
|
||||||
"""
|
|
||||||
choices = [
|
|
||||||
(None, '---------'),
|
|
||||||
(0, 'None'),
|
|
||||||
]
|
|
||||||
choices += [(t.pk, t.name) for t in Tenant.objects.all()]
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tenant groups
|
# Tenant groups
|
||||||
#
|
#
|
||||||
@ -71,7 +47,10 @@ class TenantImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
|
|
||||||
class TenantBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class TenantBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Tenant.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Tenant.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
group = forms.TypedChoiceField(choices=bulkedit_tenantgroup_choices, coerce=int, required=False, label='Group')
|
group = forms.ModelChoiceField(queryset=TenantGroup.objects.all(), required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['group']
|
||||||
|
|
||||||
|
|
||||||
class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class TenantFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
|
@ -294,6 +294,13 @@ class ConfirmationForm(forms.Form, BootstrapMixin):
|
|||||||
confirm = forms.BooleanField(required=True)
|
confirm = forms.BooleanField(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BulkEditForm(forms.Form):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(BulkEditForm, self).__init__(*args, **kwargs)
|
||||||
|
self.nullable_fields = getattr(self.Meta, 'nullable_fields')
|
||||||
|
|
||||||
|
|
||||||
class BulkImportForm(forms.Form):
|
class BulkImportForm(forms.Form):
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
@ -5,12 +5,13 @@ register = template.Library()
|
|||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('utilities/render_field.html')
|
@register.inclusion_tag('utilities/render_field.html')
|
||||||
def render_field(field):
|
def render_field(field, bulk_nullable=False):
|
||||||
"""
|
"""
|
||||||
Render a single form field from template
|
Render a single form field from template
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
'field': field,
|
'field': field,
|
||||||
|
'bulk_nullable': bulk_nullable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -310,8 +310,15 @@ class BulkEditView(View):
|
|||||||
custom_fields = form.custom_fields if hasattr(form, 'custom_fields') else []
|
custom_fields = form.custom_fields if hasattr(form, 'custom_fields') else []
|
||||||
standard_fields = [field for field in form.fields if field not in custom_fields and field != 'pk']
|
standard_fields = [field for field in form.fields if field not in custom_fields and field != 'pk']
|
||||||
|
|
||||||
# Update objects
|
# Update standard fields. If a field is listed in _nullify, delete its value.
|
||||||
updated_count = self.update_objects(pk_list, form, standard_fields)
|
nullified_fields = request.POST.getlist('_nullify')
|
||||||
|
fields_to_update = {}
|
||||||
|
for field in standard_fields:
|
||||||
|
if field in form.nullable_fields and field in nullified_fields:
|
||||||
|
fields_to_update[field] = ''
|
||||||
|
elif form.cleaned_data[field]:
|
||||||
|
fields_to_update[field] = form.cleaned_data[field]
|
||||||
|
updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
||||||
|
|
||||||
# Update custom fields for objects
|
# Update custom fields for objects
|
||||||
if custom_fields:
|
if custom_fields:
|
||||||
@ -342,18 +349,6 @@ class BulkEditView(View):
|
|||||||
'cancel_url': redirect_url,
|
'cancel_url': redirect_url,
|
||||||
})
|
})
|
||||||
|
|
||||||
def update_objects(self, pk_list, form, fields):
|
|
||||||
fields_to_update = {}
|
|
||||||
|
|
||||||
for name in fields:
|
|
||||||
# Check for zero value (bulk editing)
|
|
||||||
if isinstance(form.fields[name], TypedChoiceField) and form.cleaned_data[name] == 0:
|
|
||||||
fields_to_update[name] = None
|
|
||||||
elif form.cleaned_data[name]:
|
|
||||||
fields_to_update[name] = form.cleaned_data[name]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
def update_custom_fields(self, pk_list, form, fields):
|
def update_custom_fields(self, pk_list, form, fields):
|
||||||
obj_type = ContentType.objects.get_for_model(self.cls)
|
obj_type = ContentType.objects.get_for_model(self.cls)
|
||||||
objs_updated = False
|
objs_updated = False
|
||||||
|
Loading…
Reference in New Issue
Block a user