mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Add changelog message support for bulk edit & bulk delete
This commit is contained in:
parent
1b11895c90
commit
0703fe7852
@ -100,7 +100,7 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
|
|||||||
return customfield.to_form_field(for_csv_import=True)
|
return customfield.to_form_field(for_csv_import=True)
|
||||||
|
|
||||||
|
|
||||||
class NetBoxModelBulkEditForm(CustomFieldsMixin, BulkEditForm):
|
class NetBoxModelBulkEditForm(ChangeLoggingMixin, CustomFieldsMixin, BulkEditForm):
|
||||||
"""
|
"""
|
||||||
Base form for modifying multiple NetBox objects (of the same type) in bulk via the UI. Adds support for custom
|
Base form for modifying multiple NetBox objects (of the same type) in bulk via the UI. Adds support for custom
|
||||||
fields and adding/removing tags.
|
fields and adding/removing tags.
|
||||||
|
@ -20,13 +20,6 @@ class ChangeLoggingMixin(forms.Form):
|
|||||||
max_length=200
|
max_length=200
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
|
|
||||||
# Attach the changelog message (if any) to the instance
|
|
||||||
self.instance._changelog_message = self.cleaned_data.pop('changelog_message', None)
|
|
||||||
|
|
||||||
return self.cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldsMixin:
|
class CustomFieldsMixin:
|
||||||
"""
|
"""
|
||||||
|
@ -21,6 +21,7 @@ from core.models import ObjectType
|
|||||||
from core.signals import clear_events
|
from core.signals import clear_events
|
||||||
from extras.choices import CustomFieldUIEditableChoices
|
from extras.choices import CustomFieldUIEditableChoices
|
||||||
from extras.models import CustomField, ExportTemplate
|
from extras.models import CustomField, ExportTemplate
|
||||||
|
from netbox.forms.mixins import ChangeLoggingMixin
|
||||||
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
|
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
|
||||||
from utilities.error_handlers import handle_protectederror
|
from utilities.error_handlers import handle_protectederror
|
||||||
from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation
|
from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation
|
||||||
@ -622,6 +623,9 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
if hasattr(obj, 'snapshot'):
|
if hasattr(obj, 'snapshot'):
|
||||||
obj.snapshot()
|
obj.snapshot()
|
||||||
|
|
||||||
|
# Attach the changelog message (if any) to the object
|
||||||
|
obj._changelog_message = form.cleaned_data.get('changelog_message')
|
||||||
|
|
||||||
# Update standard fields. If a field is listed in _nullify, delete its value.
|
# Update standard fields. If a field is listed in _nullify, delete its value.
|
||||||
for name, model_field in model_fields.items():
|
for name, model_field in model_fields.items():
|
||||||
# Handle nullification
|
# Handle nullification
|
||||||
@ -892,7 +896,7 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
"""
|
"""
|
||||||
Provide a standard bulk delete form if none has been specified for the view
|
Provide a standard bulk delete form if none has been specified for the view
|
||||||
"""
|
"""
|
||||||
class BulkDeleteForm(BackgroundJobMixin, ConfirmationForm):
|
class BulkDeleteForm(BackgroundJobMixin, ChangeLoggingMixin, ConfirmationForm):
|
||||||
pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput)
|
pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput)
|
||||||
|
|
||||||
return BulkDeleteForm
|
return BulkDeleteForm
|
||||||
@ -939,9 +943,15 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
try:
|
try:
|
||||||
with transaction.atomic(using=router.db_for_write(model)):
|
with transaction.atomic(using=router.db_for_write(model)):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
|
|
||||||
# Take a snapshot of change-logged models
|
# Take a snapshot of change-logged models
|
||||||
if hasattr(obj, 'snapshot'):
|
if hasattr(obj, 'snapshot'):
|
||||||
obj.snapshot()
|
obj.snapshot()
|
||||||
|
|
||||||
|
# Attach the changelog message (if any) to the object
|
||||||
|
obj._changelog_message = form.cleaned_data.get('changelog_message')
|
||||||
|
|
||||||
|
# Delete the object
|
||||||
obj.delete()
|
obj.delete()
|
||||||
|
|
||||||
except (ProtectedError, RestrictedError) as e:
|
except (ProtectedError, RestrictedError) as e:
|
||||||
|
@ -288,6 +288,9 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
logger.debug("Form validation was successful")
|
logger.debug("Form validation was successful")
|
||||||
|
|
||||||
|
# Record changelog message (if any)
|
||||||
|
obj._changelog_message = form.cleaned_data.pop('changelog_message', '')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with transaction.atomic(using=router.db_for_write(model)):
|
with transaction.atomic(using=router.db_for_write(model)):
|
||||||
object_created = form.instance.pk is None
|
object_created = form.instance.pk is None
|
||||||
@ -463,22 +466,23 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
|
|||||||
obj = self.get_object(**kwargs)
|
obj = self.get_object(**kwargs)
|
||||||
form = DeleteForm(request.POST)
|
form = DeleteForm(request.POST)
|
||||||
|
|
||||||
# Take a snapshot of change-logged models
|
|
||||||
if hasattr(obj, 'snapshot'):
|
|
||||||
obj.snapshot()
|
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
logger.debug("Form validation was successful")
|
logger.debug("Form validation was successful")
|
||||||
|
|
||||||
|
# Take a snapshot of change-logged models
|
||||||
|
if hasattr(obj, 'snapshot'):
|
||||||
|
obj.snapshot()
|
||||||
|
|
||||||
|
# Record changelog message (if any)
|
||||||
obj._changelog_message = form.cleaned_data.pop('changelog_message', '')
|
obj._changelog_message = form.cleaned_data.pop('changelog_message', '')
|
||||||
|
|
||||||
|
# Delete the object
|
||||||
try:
|
try:
|
||||||
obj.delete()
|
obj.delete()
|
||||||
|
|
||||||
except (ProtectedError, RestrictedError) as e:
|
except (ProtectedError, RestrictedError) as e:
|
||||||
logger.info(f"Caught {type(e)} while attempting to delete objects")
|
logger.info(f"Caught {type(e)} while attempting to delete objects")
|
||||||
handle_protectederror([obj], request, e)
|
handle_protectederror([obj], request, e)
|
||||||
return redirect(obj.get_absolute_url())
|
return redirect(obj.get_absolute_url())
|
||||||
|
|
||||||
except AbortRequest as e:
|
except AbortRequest as e:
|
||||||
logger.debug(e.message)
|
logger.debug(e.message)
|
||||||
messages.error(request, mark_safe(e.message))
|
messages.error(request, mark_safe(e.message))
|
||||||
|
@ -67,6 +67,7 @@ Context:
|
|||||||
|
|
||||||
{# Meta fields #}
|
{# Meta fields #}
|
||||||
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3">
|
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3">
|
||||||
|
{% render_field form.changelog_message %}
|
||||||
{% render_field form.background_job %}
|
{% render_field form.background_job %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ Context:
|
|||||||
|
|
||||||
{# Meta fields #}
|
{# Meta fields #}
|
||||||
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3">
|
<div class="bg-primary-subtle border border-primary rounded-1 pt-3 mb-3">
|
||||||
|
{% render_field form.changelog_message %}
|
||||||
{% render_field form.background_job %}
|
{% render_field form.background_job %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user