diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 7e57bb723..29710971e 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2299,6 +2299,11 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form): label='Name' ) + def clean_tags(self): + # Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we + # must first convert the list of tags to a string. + return ','.join(self.cleaned_data.get('tags')) + # # Console ports @@ -2355,9 +2360,7 @@ class ConsolePortBulkCreateForm( form_from_model(ConsolePort, ['type', 'description', 'tags']), DeviceBulkAddComponentForm ): - tags = TagField( - required=False - ) + pass class ConsolePortBulkEditForm( @@ -2447,9 +2450,7 @@ class ConsoleServerPortBulkCreateForm( form_from_model(ConsoleServerPort, ['type', 'description', 'tags']), DeviceBulkAddComponentForm ): - tags = TagField( - required=False - ) + pass class ConsoleServerPortBulkEditForm( @@ -2563,9 +2564,7 @@ class PowerPortBulkCreateForm( form_from_model(PowerPort, ['type', 'maximum_draw', 'allocated_draw', 'description', 'tags']), DeviceBulkAddComponentForm ): - tags = TagField( - required=False - ) + pass class PowerPortBulkEditForm( @@ -2685,9 +2684,7 @@ class PowerOutletBulkCreateForm( form_from_model(PowerOutlet, ['type', 'feed_leg', 'description', 'tags']), DeviceBulkAddComponentForm ): - tags = TagField( - required=False - ) + pass class PowerOutletBulkEditForm( @@ -2968,9 +2965,7 @@ class InterfaceBulkCreateForm( form_from_model(Interface, ['type', 'enabled', 'mtu', 'mgmt_only', 'description', 'tags']), DeviceBulkAddComponentForm ): - tags = TagField( - required=False - ) + pass class InterfaceBulkEditForm( @@ -3241,9 +3236,7 @@ class FrontPortCreateForm(BootstrapMixin, forms.Form): # form_from_model(FrontPort, ['type', 'description', 'tags']), # DeviceBulkAddComponentForm # ): -# tags = TagField( -# required=False -# ) +# pass class FrontPortBulkEditForm( @@ -3381,9 +3374,7 @@ class RearPortBulkCreateForm( form_from_model(RearPort, ['type', 'positions', 'description', 'tags']), DeviceBulkAddComponentForm ): - tags = TagField( - required=False - ) + pass class RearPortBulkEditForm( diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 0d5153740..b671eec9c 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -972,25 +972,32 @@ class BulkComponentCreateView(GetReturnURLMixin, View): new_components = [] data = deepcopy(form.cleaned_data) - for obj in data['pk']: + try: + with transaction.atomic(): - names = data['name_pattern'] - for name in names: - component_data = { - self.parent_field: obj.pk, - 'name': name, - } - component_data.update(data) - component_form = self.model_form(component_data) - if component_form.is_valid(): - new_components.append(component_form.save(commit=False)) - else: - for field, errors in component_form.errors.as_data().items(): - for e in errors: - form.add_error(field, '{} {}: {}'.format(obj, name, ', '.join(e))) + for obj in data['pk']: + + names = data['name_pattern'] + for name in names: + component_data = { + self.parent_field: obj.pk, + 'name': name, + } + component_data.update(data) + component_form = self.model_form(component_data) + if component_form.is_valid(): + instance = component_form.save() + logger.debug(f"Created {instance} on {instance.parent}") + new_components.append(instance) + else: + for field, errors in component_form.errors.as_data().items(): + for e in errors: + form.add_error(field, '{} {}: {}'.format(obj, name, ', '.join(e))) + + except IntegrityError: + pass if not form.errors: - self.model.objects.bulk_create(new_components) msg = "Added {} {} to {} {}.".format( len(new_components), model_name, diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index a8232cbb5..9ba5ff032 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -828,6 +828,11 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form): label='Name' ) + def clean_tags(self): + # Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we + # must first convert the list of tags to a string. + return ','.join(self.cleaned_data.get('tags')) + class InterfaceBulkCreateForm( form_from_model(Interface, ['enabled', 'mtu', 'description', 'tags']), @@ -838,6 +843,3 @@ class InterfaceBulkCreateForm( initial=VMInterfaceTypeChoices.TYPE_VIRTUAL, widget=forms.HiddenInput() ) - tags = TagField( - required=False - )