diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index 15c91a880..32ffc1396 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -387,6 +387,12 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form): # class ScriptForm(BootstrapMixin, forms.Form): + _commit = forms.BooleanField( + required=False, + initial=True, + label="Commit changes", + help_text="Commit changes to the database (uncheck for a dry-run)" + ) def __init__(self, vars, *args, **kwargs): @@ -395,3 +401,6 @@ class ScriptForm(BootstrapMixin, forms.Form): # Dynamically populate fields for variables for name, var in vars.items(): self.fields[name] = var.as_field() + + # Move _commit to the end of the form + self.fields.move_to_end('_commit', True) diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index 7814163b7..49ea43abb 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -7,6 +7,7 @@ from django.conf import settings from django.core.validators import RegexValidator from django.db import transaction +from utilities.exceptions import AbortTransaction from .constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING from .forms import ScriptForm @@ -197,21 +198,32 @@ def is_variable(obj): return isinstance(obj, ScriptVariable) -def run_script(script, data=None): +def run_script(script, data, commit=True): """ - A wrapper for calling Script.run(). This performs error handling. It exists outside of the Script class to ensure - it cannot be overridden by a script author. + A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It + exists outside of the Script class to ensure it cannot be overridden by a script author. """ + output = None + try: with transaction.atomic(): - return script.run(data) + output = script.run(data) + if not commit: + raise AbortTransaction() + except AbortTransaction: + pass except Exception as e: script.log_failure( - "An exception occurred: {}".format(e) - ) - script.log_info( - "Database changes have been reverted automatically." + "An exception occurred. {}: {}".format(type(e).__name__, e) ) + commit = False + finally: + if not commit: + script.log_info( + "Database changes have been reverted automatically." + ) + + return output def get_scripts(): diff --git a/netbox/extras/views.py b/netbox/extras/views.py index b359077cf..6b87c27de 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -404,7 +404,8 @@ class ScriptView(PermissionRequiredMixin, View): output = None if form.is_valid(): - run_script(script) + commit = form.cleaned_data.pop('_commit') + run_script(script, form.cleaned_data, commit) return render(request, 'extras/script.html', { 'module': module, diff --git a/netbox/templates/extras/script.html b/netbox/templates/extras/script.html index 87f389c75..fcb2428f4 100644 --- a/netbox/templates/extras/script.html +++ b/netbox/templates/extras/script.html @@ -56,7 +56,7 @@ {% endif %}
{{ output }}
{{ script.filename }}
{{ script.source }}