We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Unable to run scripts from CLI in v4.0
### Deployment Type

Self-hosted

### NetBox Version

v4.0.0

### Python Version

3.11

### Steps to Reproduce

1. Create a script
2. Run it with `python manage.py runscript 'module.ScriptName' inside the NetBox instance


### Expected Behavior

Script should run.

### Observed Behavior

Script fails with:
> AttributeError: 'Script' object has no attribute 'full_name'

Running the same script from GUI works fine, have tried multiple scripts, and haven't been able to run any via CLI in v4. 

Seems to be this line that fails: https://github.com/netbox-community/netbox/blob/develop/netbox/extras/management/commands/runscript.py#L104

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `netbox/extras/management/commands/runscript.py`
Content:
```
1 import json
2 import logging
3 import sys
4 import traceback
5 import uuid
6 
7 from django.contrib.auth import get_user_model
8 from django.core.management.base import BaseCommand, CommandError
9 from django.db import transaction
10 
11 from core.choices import JobStatusChoices
12 from core.models import Job
13 from extras.context_managers import event_tracking
14 from extras.scripts import get_module_and_script
15 from extras.signals import clear_events
16 from utilities.exceptions import AbortTransaction
17 from utilities.request import NetBoxFakeRequest
18 
19 
20 class Command(BaseCommand):
21     help = "Run a script in NetBox"
22 
23     def add_arguments(self, parser):
24         parser.add_argument(
25             '--loglevel',
26             help="Logging Level (default: info)",
27             dest='loglevel',
28             default='info',
29             choices=['debug', 'info', 'warning', 'error', 'critical'])
30         parser.add_argument('--commit', help="Commit this script to database", action='store_true')
31         parser.add_argument('--user', help="User script is running as")
32         parser.add_argument('--data', help="Data as a string encapsulated JSON blob")
33         parser.add_argument('script', help="Script to run")
34 
35     def handle(self, *args, **options):
36 
37         def _run_script():
38             """
39             Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with
40             the event_tracking context manager (which is bypassed if commit == False).
41             """
42             try:
43                 try:
44                     with transaction.atomic():
45                         script.output = script.run(data=data, commit=commit)
46                         if not commit:
47                             raise AbortTransaction()
48                 except AbortTransaction:
49                     script.log_info("Database changes have been reverted automatically.")
50                     clear_events.send(request)
51                 job.data = script.get_job_data()
52                 job.terminate()
53             except Exception as e:
54                 stacktrace = traceback.format_exc()
55                 script.log_failure(
56                     f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```"
57                 )
58                 script.log_info("Database changes have been reverted due to error.")
59                 logger.error(f"Exception raised during script execution: {e}")
60                 clear_events.send(request)
61                 job.data = script.get_job_data()
62                 job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=repr(e))
63 
64             # Print any test method results
65             for test_name, attrs in job.data['tests'].items():
66                 self.stdout.write(
67                     "\t{}: {} success, {} info, {} warning, {} failure".format(
68                         test_name, attrs['success'], attrs['info'], attrs['warning'], attrs['failure']
69                     )
70                 )
71 
72             logger.info(f"Script completed in {job.duration}")
73 
74         User = get_user_model()
75 
76         # Params
77         script = options['script']
78         loglevel = options['loglevel']
79         commit = options['commit']
80 
81         try:
82             data = json.loads(options['data'])
83         except TypeError:
84             data = {}
85 
86         module_name, script_name = script.split('.', 1)
87         module, script = get_module_and_script(module_name, script_name)
88 
89         # Take user from command line if provided and exists, other
90         if options['user']:
91             try:
92                 user = User.objects.get(username=options['user'])
93             except User.DoesNotExist:
94                 user = User.objects.filter(is_superuser=True).order_by('pk')[0]
95         else:
96             user = User.objects.filter(is_superuser=True).order_by('pk')[0]
97 
98         # Setup logging to Stdout
99         formatter = logging.Formatter(f'[%(asctime)s][%(levelname)s] - %(message)s')
100         stdouthandler = logging.StreamHandler(sys.stdout)
101         stdouthandler.setLevel(logging.DEBUG)
102         stdouthandler.setFormatter(formatter)
103 
104         logger = logging.getLogger(f"netbox.scripts.{script.full_name}")
105         logger.addHandler(stdouthandler)
106 
107         try:
108             logger.setLevel({
109                 'critical': logging.CRITICAL,
110                 'debug': logging.DEBUG,
111                 'error': logging.ERROR,
112                 'fatal': logging.FATAL,
113                 'info': logging.INFO,
114                 'warning': logging.WARNING,
115             }[loglevel])
116         except KeyError:
117             raise CommandError(f"Invalid log level: {loglevel}")
118 
119         # Initialize the script form
120         script = script()
121         form = script.as_form(data, None)
122 
123         # Create the job
124         job = Job.objects.create(
125             object=module,
126             name=script.class_name,
127             user=User.objects.filter(is_superuser=True).order_by('pk')[0],
128             job_id=uuid.uuid4()
129         )
130 
131         request = NetBoxFakeRequest({
132             'META': {},
133             'POST': data,
134             'GET': {},
135             'FILES': {},
136             'user': user,
137             'path': '',
138             'id': job.job_id
139         })
140 
141         if form.is_valid():
142             job.status = JobStatusChoices.STATUS_RUNNING
143             job.save()
144 
145             logger.info(f"Running script (commit={commit})")
146             script.request = request
147 
148             # Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process
149             # change logging, webhooks, etc.
150             with event_tracking(request):
151                 _run_script()
152         else:
153             logger.error('Data is not valid:')
154             for field, errors in form.errors.get_json_data().items():
155                 for error in errors:
156                     logger.error(f'\t{field}: {error.get("message")}')
157             job.status = JobStatusChoices.STATUS_ERRORED
158             job.save()
159
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/netbox/extras/management/commands/runscript.py b/netbox/extras/management/commands/runscript.py
--- a/netbox/extras/management/commands/runscript.py
+++ b/netbox/extras/management/commands/runscript.py
@@ -85,6 +85,7 @@
 
         module_name, script_name = script.split('.', 1)
         module, script = get_module_and_script(module_name, script_name)
+        script = script.python_class
 
         # Take user from command line if provided and exists, other
         if options['user']:

{"golden_diff": "diff --git a/netbox/extras/management/commands/runscript.py b/netbox/extras/management/commands/runscript.py\n--- a/netbox/extras/management/commands/runscript.py\n+++ b/netbox/extras/management/commands/runscript.py\n@@ -85,6 +85,7 @@\n \n         module_name, script_name = script.split('.', 1)\n         module, script = get_module_and_script(module_name, script_name)\n+        script = script.python_class\n \n         # Take user from command line if provided and exists, other\n         if options['user']:\n", "issue": "Unable to run scripts from CLI in v4.0\n### Deployment Type\n\nSelf-hosted\n\n### NetBox Version\n\nv4.0.0\n\n### Python Version\n\n3.11\n\n### Steps to Reproduce\n\n1. Create a script\r\n2. Run it with `python manage.py runscript 'module.ScriptName' inside the NetBox instance\r\n\n\n### Expected Behavior\n\nScript should run.\n\n### Observed Behavior\n\nScript fails with:\r\n> AttributeError: 'Script' object has no attribute 'full_name'\r\n\r\nRunning the same script from GUI works fine, have tried multiple scripts, and haven't been able to run any via CLI in v4. \r\n\r\nSeems to be this line that fails: https://github.com/netbox-community/netbox/blob/develop/netbox/extras/management/commands/runscript.py#L104\n", "before_files": [{"content": "import json\nimport logging\nimport sys\nimport traceback\nimport uuid\n\nfrom django.contrib.auth import get_user_model\nfrom django.core.management.base import BaseCommand, CommandError\nfrom django.db import transaction\n\nfrom core.choices import JobStatusChoices\nfrom core.models import Job\nfrom extras.context_managers import event_tracking\nfrom extras.scripts import get_module_and_script\nfrom extras.signals import clear_events\nfrom utilities.exceptions import AbortTransaction\nfrom utilities.request import NetBoxFakeRequest\n\n\nclass Command(BaseCommand):\n    help = \"Run a script in NetBox\"\n\n    def add_arguments(self, parser):\n        parser.add_argument(\n            '--loglevel',\n            help=\"Logging Level (default: info)\",\n            dest='loglevel',\n            default='info',\n            choices=['debug', 'info', 'warning', 'error', 'critical'])\n        parser.add_argument('--commit', help=\"Commit this script to database\", action='store_true')\n        parser.add_argument('--user', help=\"User script is running as\")\n        parser.add_argument('--data', help=\"Data as a string encapsulated JSON blob\")\n        parser.add_argument('script', help=\"Script to run\")\n\n    def handle(self, *args, **options):\n\n        def _run_script():\n            \"\"\"\n            Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with\n            the event_tracking context manager (which is bypassed if commit == False).\n            \"\"\"\n            try:\n                try:\n                    with transaction.atomic():\n                        script.output = script.run(data=data, commit=commit)\n                        if not commit:\n                            raise AbortTransaction()\n                except AbortTransaction:\n                    script.log_info(\"Database changes have been reverted automatically.\")\n                    clear_events.send(request)\n                job.data = script.get_job_data()\n                job.terminate()\n            except Exception as e:\n                stacktrace = traceback.format_exc()\n                script.log_failure(\n                    f\"An exception occurred: `{type(e).__name__}: {e}`\\n```\\n{stacktrace}\\n```\"\n                )\n                script.log_info(\"Database changes have been reverted due to error.\")\n                logger.error(f\"Exception raised during script execution: {e}\")\n                clear_events.send(request)\n                job.data = script.get_job_data()\n                job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=repr(e))\n\n            # Print any test method results\n            for test_name, attrs in job.data['tests'].items():\n                self.stdout.write(\n                    \"\\t{}: {} success, {} info, {} warning, {} failure\".format(\n                        test_name, attrs['success'], attrs['info'], attrs['warning'], attrs['failure']\n                    )\n                )\n\n            logger.info(f\"Script completed in {job.duration}\")\n\n        User = get_user_model()\n\n        # Params\n        script = options['script']\n        loglevel = options['loglevel']\n        commit = options['commit']\n\n        try:\n            data = json.loads(options['data'])\n        except TypeError:\n            data = {}\n\n        module_name, script_name = script.split('.', 1)\n        module, script = get_module_and_script(module_name, script_name)\n\n        # Take user from command line if provided and exists, other\n        if options['user']:\n            try:\n                user = User.objects.get(username=options['user'])\n            except User.DoesNotExist:\n                user = User.objects.filter(is_superuser=True).order_by('pk')[0]\n        else:\n            user = User.objects.filter(is_superuser=True).order_by('pk')[0]\n\n        # Setup logging to Stdout\n        formatter = logging.Formatter(f'[%(asctime)s][%(levelname)s] - %(message)s')\n        stdouthandler = logging.StreamHandler(sys.stdout)\n        stdouthandler.setLevel(logging.DEBUG)\n        stdouthandler.setFormatter(formatter)\n\n        logger = logging.getLogger(f\"netbox.scripts.{script.full_name}\")\n        logger.addHandler(stdouthandler)\n\n        try:\n            logger.setLevel({\n                'critical': logging.CRITICAL,\n                'debug': logging.DEBUG,\n                'error': logging.ERROR,\n                'fatal': logging.FATAL,\n                'info': logging.INFO,\n                'warning': logging.WARNING,\n            }[loglevel])\n        except KeyError:\n            raise CommandError(f\"Invalid log level: {loglevel}\")\n\n        # Initialize the script form\n        script = script()\n        form = script.as_form(data, None)\n\n        # Create the job\n        job = Job.objects.create(\n            object=module,\n            name=script.class_name,\n            user=User.objects.filter(is_superuser=True).order_by('pk')[0],\n            job_id=uuid.uuid4()\n        )\n\n        request = NetBoxFakeRequest({\n            'META': {},\n            'POST': data,\n            'GET': {},\n            'FILES': {},\n            'user': user,\n            'path': '',\n            'id': job.job_id\n        })\n\n        if form.is_valid():\n            job.status = JobStatusChoices.STATUS_RUNNING\n            job.save()\n\n            logger.info(f\"Running script (commit={commit})\")\n            script.request = request\n\n            # Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process\n            # change logging, webhooks, etc.\n            with event_tracking(request):\n                _run_script()\n        else:\n            logger.error('Data is not valid:')\n            for field, errors in form.errors.get_json_data().items():\n                for error in errors:\n                    logger.error(f'\\t{field}: {error.get(\"message\")}')\n            job.status = JobStatusChoices.STATUS_ERRORED\n            job.save()\n", "path": "netbox/extras/management/commands/runscript.py"}], "after_files": [{"content": "import json\nimport logging\nimport sys\nimport traceback\nimport uuid\n\nfrom django.contrib.auth import get_user_model\nfrom django.core.management.base import BaseCommand, CommandError\nfrom django.db import transaction\n\nfrom core.choices import JobStatusChoices\nfrom core.models import Job\nfrom extras.context_managers import event_tracking\nfrom extras.scripts import get_module_and_script\nfrom extras.signals import clear_events\nfrom utilities.exceptions import AbortTransaction\nfrom utilities.request import NetBoxFakeRequest\n\n\nclass Command(BaseCommand):\n    help = \"Run a script in NetBox\"\n\n    def add_arguments(self, parser):\n        parser.add_argument(\n            '--loglevel',\n            help=\"Logging Level (default: info)\",\n            dest='loglevel',\n            default='info',\n            choices=['debug', 'info', 'warning', 'error', 'critical'])\n        parser.add_argument('--commit', help=\"Commit this script to database\", action='store_true')\n        parser.add_argument('--user', help=\"User script is running as\")\n        parser.add_argument('--data', help=\"Data as a string encapsulated JSON blob\")\n        parser.add_argument('script', help=\"Script to run\")\n\n    def handle(self, *args, **options):\n\n        def _run_script():\n            \"\"\"\n            Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with\n            the event_tracking context manager (which is bypassed if commit == False).\n            \"\"\"\n            try:\n                try:\n                    with transaction.atomic():\n                        script.output = script.run(data=data, commit=commit)\n                        if not commit:\n                            raise AbortTransaction()\n                except AbortTransaction:\n                    script.log_info(\"Database changes have been reverted automatically.\")\n                    clear_events.send(request)\n                job.data = script.get_job_data()\n                job.terminate()\n            except Exception as e:\n                stacktrace = traceback.format_exc()\n                script.log_failure(\n                    f\"An exception occurred: `{type(e).__name__}: {e}`\\n```\\n{stacktrace}\\n```\"\n                )\n                script.log_info(\"Database changes have been reverted due to error.\")\n                logger.error(f\"Exception raised during script execution: {e}\")\n                clear_events.send(request)\n                job.data = script.get_job_data()\n                job.terminate(status=JobStatusChoices.STATUS_ERRORED, error=repr(e))\n\n            # Print any test method results\n            for test_name, attrs in job.data['tests'].items():\n                self.stdout.write(\n                    \"\\t{}: {} success, {} info, {} warning, {} failure\".format(\n                        test_name, attrs['success'], attrs['info'], attrs['warning'], attrs['failure']\n                    )\n                )\n\n            logger.info(f\"Script completed in {job.duration}\")\n\n        User = get_user_model()\n\n        # Params\n        script = options['script']\n        loglevel = options['loglevel']\n        commit = options['commit']\n\n        try:\n            data = json.loads(options['data'])\n        except TypeError:\n            data = {}\n\n        module_name, script_name = script.split('.', 1)\n        module, script = get_module_and_script(module_name, script_name)\n        script = script.python_class\n\n        # Take user from command line if provided and exists, other\n        if options['user']:\n            try:\n                user = User.objects.get(username=options['user'])\n            except User.DoesNotExist:\n                user = User.objects.filter(is_superuser=True).order_by('pk')[0]\n        else:\n            user = User.objects.filter(is_superuser=True).order_by('pk')[0]\n\n        # Setup logging to Stdout\n        formatter = logging.Formatter(f'[%(asctime)s][%(levelname)s] - %(message)s')\n        stdouthandler = logging.StreamHandler(sys.stdout)\n        stdouthandler.setLevel(logging.DEBUG)\n        stdouthandler.setFormatter(formatter)\n\n        logger = logging.getLogger(f\"netbox.scripts.{script.full_name}\")\n        logger.addHandler(stdouthandler)\n\n        try:\n            logger.setLevel({\n                'critical': logging.CRITICAL,\n                'debug': logging.DEBUG,\n                'error': logging.ERROR,\n                'fatal': logging.FATAL,\n                'info': logging.INFO,\n                'warning': logging.WARNING,\n            }[loglevel])\n        except KeyError:\n            raise CommandError(f\"Invalid log level: {loglevel}\")\n\n        # Initialize the script form\n        script = script()\n        form = script.as_form(data, None)\n\n        # Create the job\n        job = Job.objects.create(\n            object=module,\n            name=script.class_name,\n            user=User.objects.filter(is_superuser=True).order_by('pk')[0],\n            job_id=uuid.uuid4()\n        )\n\n        request = NetBoxFakeRequest({\n            'META': {},\n            'POST': data,\n            'GET': {},\n            'FILES': {},\n            'user': user,\n            'path': '',\n            'id': job.job_id\n        })\n\n        if form.is_valid():\n            job.status = JobStatusChoices.STATUS_RUNNING\n            job.save()\n\n            logger.info(f\"Running script (commit={commit})\")\n            script.request = request\n\n            # Execute the script. If commit is True, wrap it with the event_tracking context manager to ensure we process\n            # change logging, webhooks, etc.\n            with event_tracking(request):\n                _run_script()\n        else:\n            logger.error('Data is not valid:')\n            for field, errors in form.errors.get_json_data().items():\n                for error in errors:\n                    logger.error(f'\\t{field}: {error.get(\"message\")}')\n            job.status = JobStatusChoices.STATUS_ERRORED\n            job.save()\n", "path": "netbox/extras/management/commands/runscript.py"}]}