#527: Initial work to allow nullifying fields during bulk edit

This commit is contained in:
Jeremy Stretch 2016-09-30 16:17:41 -04:00
parent 8ed174e7af
commit 36066068d4
12 changed files with 106 additions and 122 deletions

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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 = {}

View File

@ -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 () {

View File

@ -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')

View File

@ -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">

View File

@ -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 %}

View File

@ -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):

View File

@ -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):

View File

@ -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,
} }

View File

@ -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