diff --git a/netbox/extras/management/commands/runscript.py b/netbox/extras/management/commands/runscript.py index 609374378..0346549c8 100644 --- a/netbox/extras/management/commands/runscript.py +++ b/netbox/extras/management/commands/runscript.py @@ -10,7 +10,6 @@ from django.db import transaction from core.choices import JobStatusChoices from core.models import Job -from extras.api.serializers import ScriptOutputSerializer from extras.context_managers import event_tracking from extras.scripts import get_module_and_script from extras.signals import clear_events @@ -34,6 +33,30 @@ class Command(BaseCommand): parser.add_argument('script', help="Script to run") def handle(self, *args, **options): + def _output_results(job): + # Report on success/failure + if job.status == JobStatusChoices.STATUS_FAILED: + status = self.style.ERROR('FAILED') + elif job == JobStatusChoices.STATUS_ERRORED: + status = self.style.ERROR('ERRORED') + else: + status = self.style.SUCCESS('SUCCESS') + + for test_name, attrs in job.data.logs.items(): + self.stdout.write( + "\t{}: {} success, {} info, {} warning, {} failure".format( + test_name, attrs['success'], attrs['info'], attrs['warning'], attrs['failure'] + ) + ) + + def _set_job_data(job, script): + logs = script._logs + job.data = { + 'logs': logs, + 'output': script._output, + } + return job + def _run_script(): """ Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with @@ -48,7 +71,7 @@ class Command(BaseCommand): except AbortTransaction: script.log_info("Database changes have been reverted automatically.") clear_events.send(request) - job.data = ScriptOutputSerializer(script).data + job = _set_job_data(job, script) job.terminate() except Exception as e: stacktrace = traceback.format_exc() @@ -58,7 +81,7 @@ class Command(BaseCommand): script.log_info("Database changes have been reverted due to error.") logger.error(f"Exception raised during script execution: {e}") clear_events.send(request) - job.data = ScriptOutputSerializer(script).data + job = _set_job_data(job, script) job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=repr(e)) logger.info(f"Script completed in {job.duration}") diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index f5a3816ca..8644277a8 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -12,6 +12,7 @@ from django.core.validators import RegexValidator from django.db import transaction from django.utils import timezone from django.utils.functional import classproperty +from django.utils.translation import gettext as _ from core.choices import JobStatusChoices from core.models import Job @@ -25,6 +26,8 @@ from utilities.forms import add_blank_choice from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField from .context_managers import event_tracking from .forms import ScriptForm +from .utils import is_report + __all__ = ( 'BaseScript', @@ -612,8 +615,6 @@ def run_script(data, job, request=None, commit=True, **kwargs): Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with the event_tracking context manager (which is bypassed if commit == False). """ - from .reports import Report # here to prevent circular import - try: try: with transaction.atomic(): @@ -622,7 +623,7 @@ def run_script(data, job, request=None, commit=True, **kwargs): raise AbortTransaction() except AbortTransaction: msg = _("Database changes have been reverted automatically.") - if issubclass(script, Report): + if is_report(script): # script and legacy reports have different log function signatures script.log_info(message=msg) else: @@ -639,7 +640,7 @@ def run_script(data, job, request=None, commit=True, **kwargs): except Exception as e: if type(e) is AbortScript: msg = _("Script aborted with error: ") + str(e) - if issubclass(script, Report): + if is_report(script): script.log_failure(message=msg) else: script.log_failure(msg) @@ -648,13 +649,13 @@ def run_script(data, job, request=None, commit=True, **kwargs): else: stacktrace = traceback.format_exc() msg = _("An exception occurred: : ") + f"`{type(e).__name__}: {e}`\n```\n{stacktrace}\n```" - if issubclass(script, Report): + if is_report(script): script.log_failure(message=msg) else: script.log_failure(msg) logger.error(f"Exception raised during script execution: {e}") msg = _("Database changes have been reverted due to error.") - if issubclass(script, Report): + if is_report(script): script.log_info(message=msg) else: script.log_info(msg) diff --git a/netbox/extras/utils.py b/netbox/extras/utils.py index db06b07f8..fd95b8f9b 100644 --- a/netbox/extras/utils.py +++ b/netbox/extras/utils.py @@ -47,6 +47,18 @@ def register_features(model, features): registry['models'][app_label].add(model_name) +def is_report(obj): + """ + Returns True if the given object is a Report. + """ + from .reports import Report + try: + return issubclass(obj, Report) and obj != Report + except TypeError: + print("TypeError") + return False + + def is_script(obj): """ Returns True if the object is a Script.