mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Enable background jobs for bulk edit & bulk delete
This commit is contained in:
parent
27a7263f7c
commit
76e0ee837b
@ -2,6 +2,7 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from django import forms
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRel
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRel
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -513,12 +514,7 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
count=len(form.cleaned_data['data']),
|
count=len(form.cleaned_data['data']),
|
||||||
object_type=model._meta.verbose_name_plural,
|
object_type=model._meta.verbose_name_plural,
|
||||||
)
|
)
|
||||||
if job := process_request_as_job(self.__class__, request, name=job_name):
|
if process_request_as_job(self.__class__, request, name=job_name):
|
||||||
msg = _('Created background job {job.pk}: <a href="{url}">{job.name}</a>').format(
|
|
||||||
url=job.get_absolute_url(),
|
|
||||||
job=job
|
|
||||||
)
|
|
||||||
messages.info(request, mark_safe(msg))
|
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -712,6 +708,16 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
if '_apply' in request.POST:
|
if '_apply' in request.POST:
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
logger.debug("Form validation was successful")
|
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:
|
try:
|
||||||
with transaction.atomic(using=router.db_for_write(model)):
|
with transaction.atomic(using=router.db_for_write(model)):
|
||||||
updated_objects = self._update_objects(form, request)
|
updated_objects = self._update_objects(form, request)
|
||||||
@ -721,6 +727,16 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
if object_count != len(updated_objects):
|
if object_count != len(updated_objects):
|
||||||
raise PermissionsViolation
|
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:
|
if updated_objects:
|
||||||
msg = f'Updated {len(updated_objects)} {model._meta.verbose_name_plural}'
|
msg = f'Updated {len(updated_objects)} {model._meta.verbose_name_plural}'
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
@ -878,6 +894,11 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
"""
|
"""
|
||||||
class BulkDeleteForm(ConfirmationForm):
|
class BulkDeleteForm(ConfirmationForm):
|
||||||
pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput)
|
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
|
return BulkDeleteForm
|
||||||
|
|
||||||
@ -908,6 +929,15 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
logger.debug("Form validation was successful")
|
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
|
# Delete objects
|
||||||
queryset = self.queryset.filter(pk__in=pk_list)
|
queryset = self.queryset.filter(pk__in=pk_list)
|
||||||
deleted_count = queryset.count()
|
deleted_count = queryset.count()
|
||||||
@ -929,6 +959,16 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
messages.error(request, mark_safe(e.message))
|
messages.error(request, mark_safe(e.message))
|
||||||
return redirect(self.get_return_url(request))
|
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(
|
msg = _("Deleted {count} {object_type}").format(
|
||||||
count=deleted_count,
|
count=deleted_count,
|
||||||
object_type=model._meta.verbose_name_plural
|
object_type=model._meta.verbose_name_plural
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{% extends 'generic/_base.html' %}
|
{% extends 'generic/_base.html' %}
|
||||||
|
{% load form_helpers %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
@ -61,6 +62,7 @@ Context:
|
|||||||
{% for field in form.hidden_fields %}
|
{% for field in form.hidden_fields %}
|
||||||
{{ field }}
|
{{ field }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% render_field form.background_job %}
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
<a href="{{ return_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
|
<a href="{{ return_url }}" class="btn btn-outline-secondary">{% trans "Cancel" %}</a>
|
||||||
<button type="submit" name="_confirm" class="btn btn-danger">{% trans "Delete" %} {{ table.rows|length }} {{ model|meta:"verbose_name_plural" }}</button>
|
<button type="submit" name="_confirm" class="btn btn-danger">{% trans "Delete" %} {{ table.rows|length }} {{ model|meta:"verbose_name_plural" }}</button>
|
||||||
|
@ -89,6 +89,8 @@ Context:
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% render_field form.background_job %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{# Render all fields #}
|
{# Render all fields #}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.jobs import AsyncViewJob
|
from netbox.jobs import AsyncViewJob
|
||||||
from utilities.request import copy_safe_request
|
from utilities.request import copy_safe_request
|
||||||
|
|
||||||
@ -38,9 +42,19 @@ def process_request_as_job(view, request, name=None):
|
|||||||
request_copy._background = True
|
request_copy._background = True
|
||||||
|
|
||||||
# Enqueue a job to perform the work in the background
|
# Enqueue a job to perform the work in the background
|
||||||
return AsyncViewJob.enqueue(
|
job = AsyncViewJob.enqueue(
|
||||||
name=name,
|
name=name,
|
||||||
user=request.user,
|
user=request.user,
|
||||||
view_cls=view,
|
view_cls=view,
|
||||||
request=request_copy,
|
request=request_copy,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Record a message on the original request indicating deferral to a background job
|
||||||
|
msg = _('Created background job {id}: <a href="{url}">{name}</a>').format(
|
||||||
|
id=job.pk,
|
||||||
|
url=job.get_absolute_url(),
|
||||||
|
name=job.name
|
||||||
|
)
|
||||||
|
messages.info(request, mark_safe(msg))
|
||||||
|
|
||||||
|
return job
|
||||||
|
Loading…
Reference in New Issue
Block a user