From 1a25f5a7f255ad332a6e4bfa43cb97294565c215 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Jan 2020 15:12:10 -0500 Subject: [PATCH] Fixes #4030: Fix exception when bulk editing interfaces (revised) --- docs/release-notes/version-2.7.md | 1 + netbox/dcim/models/device_components.py | 2 +- netbox/utilities/views.py | 28 ++++++++++++++++++------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index 196c041c5..ba33e062c 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -6,6 +6,7 @@ ## Bug Fixes +* [#4030](https://github.com/netbox-community/netbox/issues/4030) - Fix exception when bulk editing interfaces (revised) * [#4043](https://github.com/netbox-community/netbox/issues/4043) - Fix toggling of required fields in custom scripts * [#4049](https://github.com/netbox-community/netbox/issues/4049) - Restore missing `tags` field in IPAM service serializer * [#4052](https://github.com/netbox-community/netbox/issues/4052) - Fix error when bulk importing interfaces to virtual machines diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 68bab8037..e37569f79 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -676,7 +676,7 @@ class Interface(CableTermination, ComponentModel): self.untagged_vlan = None # Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.) - if self.pk and self.mode is not InterfaceModeChoices.MODE_TAGGED: + if self.pk and self.mode != InterfaceModeChoices.MODE_TAGGED: self.tagged_vlans.clear() return super().save(*args, **kwargs) diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index d900a8545..88e5005bc 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -6,7 +6,7 @@ from django.contrib import messages from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import transaction, IntegrityError -from django.db.models import Count, ProtectedError +from django.db.models import Count, ManyToManyField, ProtectedError from django.db.models.query import QuerySet from django.forms import CharField, Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea from django.http import HttpResponse, HttpResponseServerError @@ -650,7 +650,9 @@ class BulkEditView(GetReturnURLMixin, View): if form.is_valid(): 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 + ['pk', 'add_tags', 'remove_tags'] + ] nullified_fields = request.POST.getlist('_nullify') try: @@ -662,14 +664,24 @@ class BulkEditView(GetReturnURLMixin, View): # Update standard fields. If a field is listed in _nullify, delete its value. for name in standard_fields: - if name in form.nullable_fields and name in nullified_fields and isinstance(form.cleaned_data[name], QuerySet): - getattr(obj, name).set([]) - elif name in form.nullable_fields and name in nullified_fields: - setattr(obj, name, '' if isinstance(form.fields[name], CharField) else None) - elif isinstance(form.cleaned_data[name], QuerySet) and form.cleaned_data[name]: + + model_field = model._meta.get_field(name) + + # Handle nullification + if name in form.nullable_fields and name in nullified_fields: + if isinstance(model_field, ManyToManyField): + getattr(obj, name).set([]) + else: + setattr(obj, name, None if model_field.null else '') + + # ManyToManyFields + elif isinstance(model_field, ManyToManyField): getattr(obj, name).set(form.cleaned_data[name]) - elif form.cleaned_data[name] not in (None, '') and not isinstance(form.cleaned_data[name], QuerySet): + + # Normal fields + elif form.cleaned_data[name] not in (None, ''): setattr(obj, name, form.cleaned_data[name]) + obj.full_clean() obj.save()