diff --git a/netbox/extras/models/scripts.py b/netbox/extras/models/scripts.py index 9118ab2ca..98d79c53c 100644 --- a/netbox/extras/models/scripts.py +++ b/netbox/extras/models/scripts.py @@ -96,6 +96,7 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile): Proxy model for script module files. """ objects = ScriptModuleManager() + error = None event_rules = GenericRelation( to='extras.EventRule', @@ -126,6 +127,7 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile): try: module = self.get_module() except Exception as e: + self.error = e logger.debug(f"Failed to load script: {self.python_name} error: {e}") module = None diff --git a/netbox/extras/views.py b/netbox/extras/views.py index ff147cc31..3a82539fb 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1052,12 +1052,27 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View): }) -class ScriptView(generic.ObjectView): +class BaseScriptView(generic.ObjectView): queryset = Script.objects.all() + def _get_script_class(self, script): + """ + Return an instance of the Script's Python class + """ + if script_class := script.python_class: + return script_class() + + +class ScriptView(BaseScriptView): + def get(self, request, **kwargs): script = self.get_object(**kwargs) - script_class = script.python_class() + script_class = self._get_script_class(script) + if not script_class: + return render(request, 'extras/script.html', { + 'script': script, + }) + form = script_class.as_form(initial=normalize_querydict(request.GET)) return render(request, 'extras/script.html', { @@ -1069,11 +1084,16 @@ class ScriptView(generic.ObjectView): def post(self, request, **kwargs): script = self.get_object(**kwargs) - script_class = script.python_class() if not request.user.has_perm('extras.run_script', obj=script): return HttpResponseForbidden() + script_class = self._get_script_class(script) + if not script_class: + return render(request, 'extras/script.html', { + 'script': script, + }) + form = script_class.as_form(request.POST, request.FILES) # Allow execution only if RQ worker process is running @@ -1103,21 +1123,22 @@ class ScriptView(generic.ObjectView): }) -class ScriptSourceView(generic.ObjectView): +class ScriptSourceView(BaseScriptView): queryset = Script.objects.all() def get(self, request, **kwargs): script = self.get_object(**kwargs) + script_class = self._get_script_class(script) return render(request, 'extras/script/source.html', { 'script': script, - 'script_class': script.python_class(), + 'script_class': script_class, 'job_count': script.jobs.count(), 'tab': 'source', }) -class ScriptJobsView(generic.ObjectView): +class ScriptJobsView(BaseScriptView): queryset = Script.objects.all() def get(self, request, **kwargs): diff --git a/netbox/templates/extras/script.html b/netbox/templates/extras/script.html index 8a4f86579..fe616dc8a 100644 --- a/netbox/templates/extras/script.html +++ b/netbox/templates/extras/script.html @@ -14,38 +14,43 @@ {% trans "You do not have permission to run scripts" %}. {% endif %} -