diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index c207c3d8d..d9541169b 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -2,6 +2,7 @@ import logging import re from copy import deepcopy +from django import forms from django.contrib import messages from django.contrib.contenttypes.fields import GenericForeignKey, GenericRel from django.contrib.contenttypes.models import ContentType @@ -513,12 +514,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): count=len(form.cleaned_data['data']), object_type=model._meta.verbose_name_plural, ) - if job := process_request_as_job(self.__class__, request, name=job_name): - msg = _('Created background job {job.pk}: {job.name}').format( - url=job.get_absolute_url(), - job=job - ) - messages.info(request, mark_safe(msg)) + if process_request_as_job(self.__class__, request, name=job_name): return redirect(redirect_url) try: @@ -712,6 +708,16 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): if '_apply' in request.POST: if form.is_valid(): logger.debug("Form validation was successful") + + # If indicated, defer this request to a background job & redirect the user + if form.cleaned_data['background_job']: + job_name = _('Bulk edit {count} {object_type}').format( + count=len(form.cleaned_data['pk']), + object_type=model._meta.verbose_name_plural, + ) + if process_request_as_job(self.__class__, request, name=job_name): + return redirect(self.get_return_url(request)) + try: with transaction.atomic(using=router.db_for_write(model)): updated_objects = self._update_objects(form, request) @@ -721,6 +727,16 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): if object_count != len(updated_objects): raise PermissionsViolation + # If this request was executed via a background job, return the raw data for logging + if is_background_request(request): + return AsyncJobData( + log=[ + _('Updated {object}').format(object=str(obj)) + for obj in updated_objects + ], + errors=form.errors + ) + if updated_objects: msg = f'Updated {len(updated_objects)} {model._meta.verbose_name_plural}' logger.info(msg) @@ -878,6 +894,11 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): """ class BulkDeleteForm(ConfirmationForm): pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput) + background_job = forms.BooleanField( + label=_('Background job'), + help_text=_("Process as a job to edit objects in the background"), + required=False, + ) return BulkDeleteForm @@ -908,6 +929,15 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): if form.is_valid(): logger.debug("Form validation was successful") + # If indicated, defer this request to a background job & redirect the user + if form.cleaned_data['background_job']: + job_name = _('Bulk delete {count} {object_type}').format( + count=len(form.cleaned_data['pk']), + object_type=model._meta.verbose_name_plural, + ) + if process_request_as_job(self.__class__, request, name=job_name): + return redirect(self.get_return_url(request)) + # Delete objects queryset = self.queryset.filter(pk__in=pk_list) deleted_count = queryset.count() @@ -929,6 +959,16 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView): messages.error(request, mark_safe(e.message)) return redirect(self.get_return_url(request)) + # If this request was executed via a background job, return the raw data for logging + if is_background_request(request): + return AsyncJobData( + log=[ + _('Deleted {object}').format(object=str(obj)) + for obj in queryset + ], + errors=form.errors + ) + msg = _("Deleted {count} {object_type}").format( count=deleted_count, object_type=model._meta.verbose_name_plural diff --git a/netbox/templates/generic/bulk_delete.html b/netbox/templates/generic/bulk_delete.html index 4e3eecd8e..c14695995 100644 --- a/netbox/templates/generic/bulk_delete.html +++ b/netbox/templates/generic/bulk_delete.html @@ -1,4 +1,5 @@ {% extends 'generic/_base.html' %} +{% load form_helpers %} {% load helpers %} {% load render_table from django_tables2 %} {% load i18n %} @@ -61,6 +62,7 @@ Context: {% for field in form.hidden_fields %} {{ field }} {% endfor %} + {% render_field form.background_job %}