mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Added bulk editing capability for custom fields
This commit is contained in:
@ -66,15 +66,6 @@ class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'circuits/provider_bulk_edit.html'
|
template_name = 'circuits/provider_bulk_edit.html'
|
||||||
default_redirect_url = 'circuits:provider_list'
|
default_redirect_url = 'circuits:provider_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
for field in ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'circuits.delete_provider'
|
permission_required = 'circuits.delete_provider'
|
||||||
@ -159,19 +150,6 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'circuits/circuit_bulk_edit.html'
|
template_name = 'circuits/circuit_bulk_edit.html'
|
||||||
default_redirect_url = 'circuits:circuit_list'
|
default_redirect_url = 'circuits:circuit_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
if form.cleaned_data['tenant'] == 0:
|
|
||||||
fields_to_update['tenant'] = None
|
|
||||||
elif form.cleaned_data['tenant']:
|
|
||||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
|
||||||
for field in ['type', 'provider', 'port_speed', 'commit_rate', 'comments']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'circuits.delete_circuit'
|
permission_required = 'circuits.delete_circuit'
|
||||||
|
@ -3,7 +3,7 @@ import re
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
|
|
||||||
from extras.forms import CustomFieldForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from tenancy.forms import bulkedit_tenant_choices
|
from tenancy.forms import bulkedit_tenant_choices
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
@ -112,7 +112,7 @@ class SiteImportForm(BulkImportForm, BootstrapMixin):
|
|||||||
csv = CSVDataField(csv_form=SiteFromCSVForm)
|
csv = CSVDataField(csv_form=SiteFromCSVForm)
|
||||||
|
|
||||||
|
|
||||||
class SiteBulkEditForm(forms.Form, BootstrapMixin):
|
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.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||||
|
|
||||||
|
@ -122,16 +122,6 @@ class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'dcim/site_bulk_edit.html'
|
template_name = 'dcim/site_bulk_edit.html'
|
||||||
default_redirect_url = 'dcim:site_list'
|
default_redirect_url = 'dcim:site_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
if form.cleaned_data['tenant'] == 0:
|
|
||||||
fields_to_update['tenant'] = None
|
|
||||||
elif form.cleaned_data['tenant']:
|
|
||||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rack groups
|
# Rack groups
|
||||||
@ -248,20 +238,6 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'dcim/rack_bulk_edit.html'
|
template_name = 'dcim/rack_bulk_edit.html'
|
||||||
default_redirect_url = 'dcim:rack_list'
|
default_redirect_url = 'dcim:rack_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
for field in ['group', 'tenant', 'role']:
|
|
||||||
if form.cleaned_data[field] == 0:
|
|
||||||
fields_to_update[field] = None
|
|
||||||
elif form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
for field in ['site', 'type', 'width', 'u_height', 'comments']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_rack'
|
permission_required = 'dcim.delete_rack'
|
||||||
@ -372,15 +348,6 @@ class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'dcim/devicetype_bulk_edit.html'
|
template_name = 'dcim/devicetype_bulk_edit.html'
|
||||||
default_redirect_url = 'dcim:devicetype_list'
|
default_redirect_url = 'dcim:devicetype_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
for field in ['manufacturer', 'u_height']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_devicetype'
|
permission_required = 'dcim.delete_devicetype'
|
||||||
@ -682,23 +649,6 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'dcim/device_bulk_edit.html'
|
template_name = 'dcim/device_bulk_edit.html'
|
||||||
default_redirect_url = 'dcim:device_list'
|
default_redirect_url = 'dcim:device_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
for field in ['tenant', 'platform']:
|
|
||||||
if form.cleaned_data[field] == 0:
|
|
||||||
fields_to_update[field] = None
|
|
||||||
elif form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
if form.cleaned_data['status']:
|
|
||||||
status = form.cleaned_data['status']
|
|
||||||
fields_to_update['status'] = True if status == 'True' else False
|
|
||||||
for field in ['tenant', 'device_type', 'device_role', 'serial']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'dcim.delete_device'
|
permission_required = 'dcim.delete_device'
|
||||||
|
@ -4,78 +4,90 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from .models import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CustomField, CustomFieldValue
|
from .models import CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_INTEGER, CF_TYPE_SELECT, CustomField, CustomFieldValue
|
||||||
|
|
||||||
|
|
||||||
|
def get_custom_fields_for_model(content_type, bulk_editing=False):
|
||||||
|
"""Retrieve all CustomFields applicable to the given ContentType"""
|
||||||
|
field_dict = {}
|
||||||
|
custom_fields = CustomField.objects.filter(obj_type=content_type)
|
||||||
|
|
||||||
|
for cf in custom_fields:
|
||||||
|
field_name = 'cf_{}'.format(str(cf.name))
|
||||||
|
|
||||||
|
# Integer
|
||||||
|
if cf.type == CF_TYPE_INTEGER:
|
||||||
|
field = forms.IntegerField(required=cf.required, initial=cf.default)
|
||||||
|
|
||||||
|
# Boolean
|
||||||
|
elif cf.type == CF_TYPE_BOOLEAN:
|
||||||
|
choices = (
|
||||||
|
(None, '---------'),
|
||||||
|
(True, 'True'),
|
||||||
|
(False, 'False'),
|
||||||
|
)
|
||||||
|
field = forms.NullBooleanField(required=cf.required, widget=forms.Select(choices=choices))
|
||||||
|
|
||||||
|
# Date
|
||||||
|
elif cf.type == CF_TYPE_DATE:
|
||||||
|
field = forms.DateField(required=cf.required, initial=cf.default)
|
||||||
|
|
||||||
|
# Select
|
||||||
|
elif cf.type == CF_TYPE_SELECT:
|
||||||
|
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
|
||||||
|
if not cf.required:
|
||||||
|
choices = [(0, 'None')] + choices
|
||||||
|
if bulk_editing:
|
||||||
|
choices = [(None, '---------')] + choices
|
||||||
|
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required)
|
||||||
|
else:
|
||||||
|
field = forms.ModelChoiceField(queryset=cf.choices.all(), required=cf.required)
|
||||||
|
|
||||||
|
# Text
|
||||||
|
else:
|
||||||
|
field = forms.CharField(max_length=100, required=cf.required, initial=cf.default)
|
||||||
|
|
||||||
|
field.model = cf
|
||||||
|
field.label = cf.label if cf.label else cf.name.capitalize()
|
||||||
|
field.help_text = cf.description
|
||||||
|
|
||||||
|
field_dict[field_name] = field
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldForm(forms.ModelForm):
|
class CustomFieldForm(forms.ModelForm):
|
||||||
custom_fields = []
|
custom_fields = []
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
self.obj_type = ContentType.objects.get_for_model(self._meta.model)
|
||||||
|
|
||||||
super(CustomFieldForm, self).__init__(*args, **kwargs)
|
super(CustomFieldForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
obj_type = ContentType.objects.get_for_model(self._meta.model)
|
# Add all applicable CustomFields to the form
|
||||||
|
for name, field in get_custom_fields_for_model(self.obj_type).items():
|
||||||
# Find all CustomFields for this model
|
self.fields[name] = field
|
||||||
custom_fields = CustomField.objects.filter(obj_type=obj_type)
|
self.custom_fields.append(name)
|
||||||
for cf in custom_fields:
|
|
||||||
|
|
||||||
field_name = 'cf_{}'.format(str(cf.name))
|
|
||||||
|
|
||||||
# Integer
|
|
||||||
if cf.type == CF_TYPE_INTEGER:
|
|
||||||
field = forms.IntegerField(required=cf.required, initial=cf.default)
|
|
||||||
|
|
||||||
# Boolean
|
|
||||||
elif cf.type == CF_TYPE_BOOLEAN:
|
|
||||||
if cf.required:
|
|
||||||
field = forms.BooleanField(required=False, initial=bool(cf.default))
|
|
||||||
else:
|
|
||||||
field = forms.NullBooleanField(required=False, initial=bool(cf.default))
|
|
||||||
|
|
||||||
# Date
|
|
||||||
elif cf.type == CF_TYPE_DATE:
|
|
||||||
field = forms.DateField(required=cf.required, initial=cf.default)
|
|
||||||
|
|
||||||
# Select
|
|
||||||
elif cf.type == CF_TYPE_SELECT:
|
|
||||||
field = forms.ModelChoiceField(queryset=cf.choices.all(), required=cf.required)
|
|
||||||
|
|
||||||
# Text
|
|
||||||
else:
|
|
||||||
field = forms.CharField(max_length=100, required=cf.required, initial=cf.default)
|
|
||||||
|
|
||||||
field.model = cf
|
|
||||||
field.label = cf.label if cf.label else cf.name.capitalize()
|
|
||||||
field.help_text = cf.description
|
|
||||||
self.fields[field_name] = field
|
|
||||||
self.custom_fields.append(field_name)
|
|
||||||
|
|
||||||
# If editing an existing object, initialize values for all custom fields
|
# If editing an existing object, initialize values for all custom fields
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
existing_values = CustomFieldValue.objects.filter(obj_type=obj_type, obj_id=self.instance.pk)\
|
existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\
|
||||||
.select_related('field')
|
.select_related('field')
|
||||||
for cfv in existing_values:
|
for cfv in existing_values:
|
||||||
self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.value
|
self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.value
|
||||||
|
|
||||||
def _save_custom_fields(self):
|
def _save_custom_fields(self):
|
||||||
|
|
||||||
if self.instance.pk:
|
for field_name in self.custom_fields:
|
||||||
obj_type = ContentType.objects.get_for_model(self.instance)
|
try:
|
||||||
|
cfv = CustomFieldValue.objects.get(field=self.fields[field_name].model, obj_type=self.obj_type,
|
||||||
for field_name in self.custom_fields:
|
obj_id=self.instance.pk)
|
||||||
|
except CustomFieldValue.DoesNotExist:
|
||||||
try:
|
cfv = CustomFieldValue(
|
||||||
cfv = CustomFieldValue.objects.get(field=self.fields[field_name].model, obj_type=obj_type,
|
field=self.fields[field_name].model,
|
||||||
obj_id=self.instance.pk)
|
obj_type=self.obj_type,
|
||||||
except CustomFieldValue.DoesNotExist:
|
obj_id=self.instance.pk
|
||||||
cfv = CustomFieldValue(
|
)
|
||||||
field=self.fields[field_name].model,
|
cfv.value = self.cleaned_data[field_name]
|
||||||
obj_type=obj_type,
|
cfv.save()
|
||||||
obj_id=self.instance.pk
|
|
||||||
)
|
|
||||||
if cfv.pk and self.cleaned_data[field_name] is None:
|
|
||||||
cfv.delete()
|
|
||||||
elif self.cleaned_data[field_name] is not None:
|
|
||||||
cfv.value = self.cleaned_data[field_name]
|
|
||||||
cfv.save()
|
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
obj = super(CustomFieldForm, self).save(commit)
|
obj = super(CustomFieldForm, self).save(commit)
|
||||||
@ -87,3 +99,19 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
self.save_custom_fields = self._save_custom_fields
|
self.save_custom_fields = self._save_custom_fields
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFieldBulkEditForm(forms.Form):
|
||||||
|
custom_fields = []
|
||||||
|
|
||||||
|
def __init__(self, model, *args, **kwargs):
|
||||||
|
|
||||||
|
self.obj_type = ContentType.objects.get_for_model(model)
|
||||||
|
|
||||||
|
super(CustomFieldBulkEditForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Add all applicable CustomFields to the form
|
||||||
|
for name, field in get_custom_fields_for_model(self.obj_type, bulk_editing=True).items():
|
||||||
|
field.required = False
|
||||||
|
self.fields[name] = field
|
||||||
|
self.custom_fields.append(name)
|
||||||
|
@ -131,14 +131,22 @@ class CustomFieldValue(models.Model):
|
|||||||
if self.field.type == CF_TYPE_INTEGER:
|
if self.field.type == CF_TYPE_INTEGER:
|
||||||
self.val_int = value
|
self.val_int = value
|
||||||
elif self.field.type == CF_TYPE_BOOLEAN:
|
elif self.field.type == CF_TYPE_BOOLEAN:
|
||||||
self.val_int = bool(value) if value else None
|
self.val_int = int(bool(value)) if value is not None else None
|
||||||
elif self.field.type == CF_TYPE_DATE:
|
elif self.field.type == CF_TYPE_DATE:
|
||||||
self.val_date = value
|
self.val_date = value
|
||||||
elif self.field.type == CF_TYPE_SELECT:
|
elif self.field.type == CF_TYPE_SELECT:
|
||||||
self.val_int = value.id
|
# Could be ModelChoiceField or TypedChoiceField
|
||||||
|
self.val_int = value.id if hasattr(value, 'id') else value
|
||||||
else:
|
else:
|
||||||
self.val_char = value
|
self.val_char = value
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if (self.field.type == CF_TYPE_TEXT and self.value == '') or self.value is None:
|
||||||
|
if self.pk:
|
||||||
|
self.delete()
|
||||||
|
else:
|
||||||
|
super(CustomFieldValue, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldChoice(models.Model):
|
class CustomFieldChoice(models.Model):
|
||||||
field = models.ForeignKey('CustomField', related_name='choices', limit_choices_to={'type': CF_TYPE_SELECT},
|
field = models.ForeignKey('CustomField', related_name='choices', limit_choices_to={'type': CF_TYPE_SELECT},
|
||||||
|
@ -136,19 +136,6 @@ class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'ipam/vrf_bulk_edit.html'
|
template_name = 'ipam/vrf_bulk_edit.html'
|
||||||
default_redirect_url = 'ipam:vrf_list'
|
default_redirect_url = 'ipam:vrf_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
if form.cleaned_data['tenant'] == 0:
|
|
||||||
fields_to_update['tenant'] = None
|
|
||||||
elif form.cleaned_data['tenant']:
|
|
||||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
|
||||||
for field in ['description']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_vrf'
|
permission_required = 'ipam.delete_vrf'
|
||||||
@ -261,15 +248,6 @@ class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'ipam/aggregate_bulk_edit.html'
|
template_name = 'ipam/aggregate_bulk_edit.html'
|
||||||
default_redirect_url = 'ipam:aggregate_list'
|
default_redirect_url = 'ipam:aggregate_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
for field in ['rir', 'date_added', 'description']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_aggregate'
|
permission_required = 'ipam.delete_aggregate'
|
||||||
@ -401,20 +379,6 @@ class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'ipam/prefix_bulk_edit.html'
|
template_name = 'ipam/prefix_bulk_edit.html'
|
||||||
default_redirect_url = 'ipam:prefix_list'
|
default_redirect_url = 'ipam:prefix_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
for field in ['vrf', 'tenant']:
|
|
||||||
if form.cleaned_data[field] == 0:
|
|
||||||
fields_to_update[field] = None
|
|
||||||
elif form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
for field in ['site', 'status', 'role', 'description']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_prefix'
|
permission_required = 'ipam.delete_prefix'
|
||||||
@ -527,20 +491,6 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'ipam/ipaddress_bulk_edit.html'
|
template_name = 'ipam/ipaddress_bulk_edit.html'
|
||||||
default_redirect_url = 'ipam:ipaddress_list'
|
default_redirect_url = 'ipam:ipaddress_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
for field in ['vrf', 'tenant']:
|
|
||||||
if form.cleaned_data[field] == 0:
|
|
||||||
fields_to_update[field] = None
|
|
||||||
elif form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
for field in ['description']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_ipaddress'
|
permission_required = 'ipam.delete_ipaddress'
|
||||||
@ -629,19 +579,6 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'ipam/vlan_bulk_edit.html'
|
template_name = 'ipam/vlan_bulk_edit.html'
|
||||||
default_redirect_url = 'ipam:vlan_list'
|
default_redirect_url = 'ipam:vlan_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
if form.cleaned_data['tenant'] == 0:
|
|
||||||
fields_to_update['tenant'] = None
|
|
||||||
elif form.cleaned_data['tenant']:
|
|
||||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
|
||||||
for field in ['site', 'group', 'status', 'role', 'description']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'ipam.delete_vlan'
|
permission_required = 'ipam.delete_vlan'
|
||||||
|
@ -205,15 +205,6 @@ class SecretBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'secrets/secret_bulk_edit.html'
|
template_name = 'secrets/secret_bulk_edit.html'
|
||||||
default_redirect_url = 'secrets:secret_list'
|
default_redirect_url = 'secrets:secret_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
for field in ['role', 'name']:
|
|
||||||
if form.cleaned_data[field]:
|
|
||||||
fields_to_update[field] = form.cleaned_data[field]
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'secrets.delete_secret'
|
permission_required = 'secrets.delete_secret'
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
{% block select_objects_table %}
|
{% block select_objects_table %}
|
||||||
{% for site in selected_objects %}
|
{% for site in selected_objects %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'dcim:site' slug=site.slug %}">{{ site.slug }}</a></td>
|
<td><a href="{% url 'dcim:site' slug=site.slug %}">{{ site }}</a></td>
|
||||||
<td>{{ site.tenant }}</td>
|
<td>{{ site.tenant }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ field }}</td>
|
<td>{{ field }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if value %}
|
{% if value != None %}
|
||||||
{{ value }}
|
{{ value }}
|
||||||
{% elif field.required %}
|
{% elif field.required %}
|
||||||
<span class="text-warning">Not defined</span>
|
<span class="text-warning">Not defined</span>
|
||||||
|
@ -107,16 +107,6 @@ class TenantBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
template_name = 'tenancy/tenant_bulk_edit.html'
|
template_name = 'tenancy/tenant_bulk_edit.html'
|
||||||
default_redirect_url = 'tenancy:tenant_list'
|
default_redirect_url = 'tenancy:tenant_list'
|
||||||
|
|
||||||
def update_objects(self, pk_list, form):
|
|
||||||
|
|
||||||
fields_to_update = {}
|
|
||||||
if form.cleaned_data['group'] == 0:
|
|
||||||
fields_to_update['group'] = None
|
|
||||||
elif form.cleaned_data['group']:
|
|
||||||
fields_to_update['group'] = form.cleaned_data['group']
|
|
||||||
|
|
||||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
|
||||||
|
|
||||||
|
|
||||||
class TenantBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
class TenantBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||||
permission_required = 'tenancy.delete_tenant'
|
permission_required = 'tenancy.delete_tenant'
|
||||||
|
@ -7,7 +7,7 @@ from django.core.exceptions import ImproperlyConfigured
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import transaction, IntegrityError
|
from django.db import transaction, IntegrityError
|
||||||
from django.db.models import ProtectedError
|
from django.db.models import ProtectedError
|
||||||
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput
|
from django.forms import ModelMultipleChoiceField, MultipleHiddenInput, TypedChoiceField
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.template import TemplateSyntaxError
|
from django.template import TemplateSyntaxError
|
||||||
@ -15,8 +15,8 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.utils.http import is_safe_url
|
from django.utils.http import is_safe_url
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
from extras.forms import CustomFieldForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm
|
||||||
from extras.models import ExportTemplate, UserAction
|
from extras.models import CustomField, CustomFieldValue, ExportTemplate, UserAction
|
||||||
|
|
||||||
from .error_handlers import handle_protectederror
|
from .error_handlers import handle_protectederror
|
||||||
from .forms import ConfirmationForm
|
from .forms import ConfirmationForm
|
||||||
@ -282,9 +282,22 @@ class BulkEditView(View):
|
|||||||
pk_list = request.POST.getlist('pk')
|
pk_list = request.POST.getlist('pk')
|
||||||
|
|
||||||
if '_apply' in request.POST:
|
if '_apply' in request.POST:
|
||||||
form = self.form(request.POST)
|
if hasattr(self.form, 'custom_fields'):
|
||||||
|
form = self.form(self.cls, request.POST)
|
||||||
|
else:
|
||||||
|
form = self.form(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
updated_count = self.update_objects(pk_list, form)
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
# Update objects
|
||||||
|
updated_count = self.update_objects(pk_list, form, standard_fields)
|
||||||
|
|
||||||
|
# Update custom fields for objects
|
||||||
|
if custom_fields:
|
||||||
|
self.update_custom_fields(pk_list, form, custom_fields)
|
||||||
|
|
||||||
if updated_count:
|
if updated_count:
|
||||||
msg = u'Updated {} {}'.format(updated_count, self.cls._meta.verbose_name_plural)
|
msg = u'Updated {} {}'.format(updated_count, self.cls._meta.verbose_name_plural)
|
||||||
messages.success(self.request, msg)
|
messages.success(self.request, msg)
|
||||||
@ -292,7 +305,10 @@ class BulkEditView(View):
|
|||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = self.form(initial={'pk': pk_list})
|
if hasattr(self.form, 'custom_fields'):
|
||||||
|
form = self.form(self.cls, initial={'pk': pk_list})
|
||||||
|
else:
|
||||||
|
form = self.form(initial={'pk': pk_list})
|
||||||
|
|
||||||
selected_objects = self.cls.objects.filter(pk__in=pk_list)
|
selected_objects = self.cls.objects.filter(pk__in=pk_list)
|
||||||
if not selected_objects:
|
if not selected_objects:
|
||||||
@ -305,11 +321,29 @@ class BulkEditView(View):
|
|||||||
'cancel_url': redirect_url,
|
'cancel_url': redirect_url,
|
||||||
})
|
})
|
||||||
|
|
||||||
def update_objects(self, obj_list, form):
|
def update_objects(self, pk_list, form, fields):
|
||||||
"""
|
fields_to_update = {}
|
||||||
This method provides the update logic (must be overridden by subclasses).
|
|
||||||
"""
|
for name in fields:
|
||||||
raise NotImplementedError()
|
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):
|
||||||
|
obj_type = ContentType.objects.get_for_model(self.cls)
|
||||||
|
|
||||||
|
for name in fields:
|
||||||
|
if form.cleaned_data[name] not in [None, u'']:
|
||||||
|
for pk in pk_list:
|
||||||
|
try:
|
||||||
|
cfv = CustomFieldValue.objects.get(field=form.fields[name].model, obj_type=obj_type, obj_id=pk)
|
||||||
|
except CustomFieldValue.DoesNotExist:
|
||||||
|
cfv = CustomFieldValue(field=form.fields[name].model, obj_type=obj_type, obj_id=pk)
|
||||||
|
cfv.value = form.cleaned_data[name]
|
||||||
|
cfv.save()
|
||||||
|
|
||||||
|
|
||||||
class BulkDeleteView(View):
|
class BulkDeleteView(View):
|
||||||
|
Reference in New Issue
Block a user