From aea5612c39813b8278da6af8270f137a796eb287 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 28 Apr 2017 12:32:27 -0400 Subject: [PATCH] Closes #1110: Expand bulk edit forms to include boolean fields (e.g. toggle is_pool for prefixes) --- netbox/dcim/forms.py | 15 ++++++++++++--- netbox/ipam/forms.py | 8 ++++++-- netbox/utilities/forms.py | 13 +++++++++++++ netbox/utilities/views.py | 4 ++-- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 776976e62..55e127ed4 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -11,9 +11,9 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi from ipam.models import IPAddress from tenancy.models import Tenant from utilities.forms import ( - APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkImportForm, CommentField, - CSVDataField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, - SmallTextarea, SlugField, FilterTreeNodeMultipleChoiceField, + APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, + BulkImportForm, CommentField, CSVDataField, ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, + Livesearch, SelectWithDisabled, SmallTextarea, SlugField, FilterTreeNodeMultipleChoiceField, ) from .formfields import MACAddressFormField @@ -272,6 +272,7 @@ class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): 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') u_height = forms.IntegerField(required=False, label='Height (U)') + desc_units = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Descending units') comments = CommentField(widget=SmallTextarea) class Meta: @@ -375,7 +376,13 @@ class DeviceTypeBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput) manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False) u_height = forms.IntegerField(min_value=1, required=False) + is_full_depth = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is full depth') interface_ordering = forms.ChoiceField(choices=add_blank_choice(IFACE_ORDERING_CHOICES), required=False) + is_console_server = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is full depth') + is_pdu = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is a PDU') + is_network_device = forms.NullBooleanField( + required=False, widget=BulkEditNullBooleanSelect, label='Is a network device' + ) class Meta: nullable_fields = [] @@ -484,6 +491,7 @@ class InterfaceTemplateCreateForm(DeviceComponentForm): class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=InterfaceTemplate.objects.all(), widget=forms.MultipleHiddenInput) form_factor = forms.ChoiceField(choices=add_blank_choice(IFACE_FF_CHOICES), required=False) + mgmt_only = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Management only') class Meta: nullable_fields = [] @@ -1413,6 +1421,7 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm): device = forms.ModelChoiceField(queryset=Device.objects.all(), widget=forms.HiddenInput) lag = forms.ModelChoiceField(queryset=Interface.objects.all(), required=False, label='Parent LAG') form_factor = forms.ChoiceField(choices=add_blank_choice(IFACE_FF_CHOICES), required=False) + mgmt_only = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Management only') description = forms.CharField(max_length=100, required=False) class Meta: diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 33f93d26b..d9c2e30c3 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -5,8 +5,8 @@ from dcim.models import Site, Rack, Device, Interface from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from tenancy.models import Tenant from utilities.forms import ( - APISelect, BootstrapMixin, BulkImportForm, CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch, - ReturnURLForm, SlugField, add_blank_choice, + APISelect, BootstrapMixin, BulkEditNullBooleanSelect, BulkImportForm, CSVDataField, ExpandableIPAddressField, + FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice, ) from .models import ( @@ -61,6 +61,9 @@ class VRFImportForm(BootstrapMixin, BulkImportForm): class VRFBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput) tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) + enforce_unique = forms.NullBooleanField( + required=False, widget=BulkEditNullBooleanSelect, label='Enforce unique space' + ) description = forms.CharField(max_length=100, required=False) class Meta: @@ -256,6 +259,7 @@ class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) status = forms.ChoiceField(choices=add_blank_choice(PREFIX_STATUS_CHOICES), required=False) role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False) + is_pool = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect, label='Is a pool') description = forms.CharField(max_length=100, required=False) class Meta: diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index f59aa6984..610780223 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -138,6 +138,19 @@ class ColorSelect(forms.Select): option_value, selected_html, option_value, force_text(option_label)) +class BulkEditNullBooleanSelect(forms.NullBooleanSelect): + + def __init__(self, *args, **kwargs): + super(BulkEditNullBooleanSelect, self).__init__(*args, **kwargs) + + # Override the built-in choice labels + self.choices = ( + ('1', '---------'), + ('2', 'Yes'), + ('3', 'No'), + ) + + class SelectWithDisabled(forms.Select): """ Modified the stock Select widget to accept choices using a dict() for a label. The dict for each option must include diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 1985df2ac..9ed35aaa0 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -423,7 +423,7 @@ class BulkEditView(View): filter: FilterSet to apply when deleting by QuerySet form: The form class used to edit objects in bulk template_name: The name of the template - default_return_url: Name of the URL to which the user is redirected after editing the objects (can be overriden by + default_return_url: Name of the URL to which the user is redirected after editing the objects (can be overridden by POSTing return_url) """ cls = None @@ -475,7 +475,7 @@ class BulkEditView(View): fields_to_update[field] = '' else: fields_to_update[field] = None - elif form.cleaned_data[field]: + elif form.cleaned_data[field] not in (None, ''): fields_to_update[field] = form.cleaned_data[field] updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)