def _run_script(): """ Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with the change_logging context manager (which is bypassed if commit == False). """ try: with transaction.atomic(): script.output = script.run(data=data, commit=commit) job_result.set_status( JobResultStatusChoices.STATUS_COMPLETED) if not commit: raise AbortTransaction() except AbortTransaction: script.log_info( "Database changes have been reverted automatically.") except Exception as e: stacktrace = traceback.format_exc() script.log_failure( f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```" ) script.log_info( "Database changes have been reverted due to error.") logger.error(f"Exception raised during script execution: {e}") job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) finally: job_result.data = ScriptOutputSerializer(script).data job_result.save() logger.info(f"Script completed in {job_result.duration}")
def run_script(script, data, request, commit=True): """ 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 start_time = None end_time = None script_name = script.__class__.__name__ logger = logging.getLogger( f"netbox.scripts.{script.module()}.{script_name}") logger.info(f"Running script (commit={commit})") # Add files to form data files = request.FILES for field_name, fileobj in files.items(): data[field_name] = fileobj # Add the current request as a property of the script script.request = request # Determine whether the script accepts a 'commit' argument (this was introduced in v2.7.8) kwargs = {'data': data} if 'commit' in inspect.signature(script.run).parameters: kwargs['commit'] = commit try: with transaction.atomic(): start_time = time.time() output = script.run(**kwargs) end_time = time.time() if not commit: raise AbortTransaction() except AbortTransaction: pass except Exception as e: stacktrace = traceback.format_exc() script.log_failure( "An exception occurred: `{}: {}`\n```\n{}\n```".format( type(e).__name__, e, stacktrace)) logger.error(f"Exception raised during script execution: {e}") commit = False finally: if not commit: # Delete all pending changelog entries purge_changelog.send(Script) script.log_info( "Database changes have been reverted automatically.") # Calculate execution time if end_time is not None: execution_time = end_time - start_time logger.info(f"Script completed in {execution_time:.4f} seconds") else: execution_time = None return output, execution_time
def run_script(script, data, request, commit=True): """ 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 start_time = None end_time = None # Add files to form data files = request.FILES for field_name, fileobj in files.items(): data[field_name] = fileobj # Add the current request as a property of the script script.request = request try: with transaction.atomic(): start_time = time.time() output = script.run(data) end_time = time.time() if not commit: raise AbortTransaction() except AbortTransaction: pass except Exception as e: stacktrace = traceback.format_exc() script.log_failure( "An exception occurred: `{}: {}`\n```\n{}\n```".format( type(e).__name__, e, stacktrace)) commit = False finally: if not commit: # Delete all pending changelog entries purge_changelog.send(Script) script.log_info( "Database changes have been reverted automatically.") # Calculate execution time if end_time is not None: execution_time = end_time - start_time else: execution_time = None return output, execution_time
def post(self, request): logger = logging.getLogger('netbox.views.ObjectImportView') form = ImportForm(request.POST) if form.is_valid(): logger.debug("Import form validation was successful") # Initialize model form data = form.cleaned_data['data'] model_form = self.model_form(data) # Assign default values for any fields which were not specified. We have to do this manually because passing # 'initial=' to the form on initialization merely sets default values for the widgets. Since widgets are not # used for YAML/JSON import, we first bind the imported data normally, then update the form's data with the # applicable field defaults as needed prior to form validation. for field_name, field in model_form.fields.items(): if field_name not in data and hasattr(field, 'initial'): model_form.data[field_name] = field.initial if model_form.is_valid(): try: with transaction.atomic(): # Save the primary object obj = model_form.save() logger.debug(f"Created {obj} (PK: {obj.pk})") # Iterate through the related object forms (if any), validating and saving each instance. for field_name, related_object_form in self.related_object_forms.items( ): logger.debug( "Processing form for related objects: {related_object_form}" ) for i, rel_obj_data in enumerate( data.get(field_name, list())): f = related_object_form(obj, rel_obj_data) for subfield_name, field in f.fields.items(): if subfield_name not in rel_obj_data and hasattr( field, 'initial'): f.data[subfield_name] = field.initial if f.is_valid(): f.save() else: # Replicate errors on the related object form to the primary form for display for subfield_name, errors in f.errors.items( ): for err in errors: err_msg = "{}[{}] {}: {}".format( field_name, i, subfield_name, err) model_form.add_error(None, err_msg) raise AbortTransaction() except AbortTransaction: pass if not model_form.errors: logger.info(f"Import object {obj} (PK: {obj.pk})") messages.success( request, mark_safe('Imported object: <a href="{}">{}</a>'.format( obj.get_absolute_url(), obj))) if '_addanother' in request.POST: return redirect(request.get_full_path()) return_url = form.cleaned_data.get('return_url') if return_url is not None and is_safe_url( url=return_url, allowed_hosts=request.get_host()): return redirect(return_url) else: return redirect(self.get_return_url(request, obj)) else: logger.debug("Model form validation failed") # Replicate model form errors for display for field, errors in model_form.errors.items(): for err in errors: if field == '__all__': form.add_error(None, err) else: form.add_error(None, "{}: {}".format(field, err)) else: logger.debug("Import form validation failed") return render( request, self.template_name, { 'form': form, 'obj_type': self.model._meta.verbose_name, 'return_url': self.get_return_url(request), })
def run_script(data, request, commit=True, *args, **kwargs): """ 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. """ job_result = kwargs.pop('job_result') module, script_name = job_result.name.split('.', 1) script = get_script(module, script_name)() job_result.status = JobResultStatusChoices.STATUS_RUNNING job_result.save() logger = logging.getLogger(f"netbox.scripts.{module}.{script_name}") logger.info(f"Running script (commit={commit})") # Add files to form data files = request.FILES for field_name, fileobj in files.items(): data[field_name] = fileobj # Add the current request as a property of the script script.request = request # TODO: Drop backward-compatibility for absent 'commit' argument in v2.10 # Determine whether the script accepts a 'commit' argument (this was introduced in v2.7.8) kwargs = {'data': data} if 'commit' in inspect.signature(script.run).parameters: kwargs['commit'] = commit else: warnings.warn( f"The run() method of script {script} should support a 'commit' argument. This will be required beginning " f"with NetBox v2.10.") with change_logging(request): try: with transaction.atomic(): script.output = script.run(**kwargs) job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED) if not commit: raise AbortTransaction() except AbortTransaction: script.log_info( "Database changes have been reverted automatically.") except Exception as e: stacktrace = traceback.format_exc() script.log_failure( f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```" ) script.log_info( "Database changes have been reverted due to error.") logger.error(f"Exception raised during script execution: {e}") job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) finally: job_result.data = ScriptOutputSerializer(script).data job_result.save() logger.info(f"Script completed in {job_result.duration}") # Delete any previous terminal state results JobResult.objects.filter( obj_type=job_result.obj_type, name=job_result.name, status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES).exclude( pk=job_result.pk).delete()