prompt: 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()
  
  ```
  
golden_diff: 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']:
  
verification_info: {"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"}]}