Repository: netbox-community/netbox

Issue:
CLI run of reports hangs forever, and log shows "no attribute username" error
### Deployment Type

Self-hosted

### NetBox Version

v3.7.1

### Python Version

3.11

### Steps to Reproduce

1. Add any report to Netbox. This one is "CruftCustomFields.py", but I can reproduce with any report.
2. Run via web UI.
3. Run via CLI with `/opt/netbox/venv/bin/python3 /opt/netbox/netbox/manage.py runreport --verbosity 3 CruftCustomFields.py`

### Expected Behavior

If running it via the web UI works, then running it via the CLI would also work.

Also, if running via CLI fails, exit with error instead of hanging.

Alternately, if report requires a username, some way for `manage.py runreport` to specify the username to run it as.

### Observed Behavior

Report works fine via web UI.

Via CLI, output is 
```
[18:32:11] Running CruftCustomFields.CruftCustomFields..
```
and just sits there until interrupted.

netbox.log shows
```
2024-01-22 10:32:11,620 INFO default: extras.reports.run_report(job=<Job: da94b31a-7d7d-4258-8965-9047af161546>) (da94b31a-7d7d-4258-8965-9047af161546)
2024-01-22 10:32:11,653 ERROR [Job da94b31a-7d7d-4258-8965-9047af161546]: exception raised while executing (extras.reports.run_report)
Traceback (most recent call last):
  File "/opt/netbox/venv/lib/python3.11/site-packages/rq/worker.py", line 1428, in perform_job
    rv = job.perform()
         ^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.11/site-packages/rq/job.py", line 1278, in perform
    self._result = self._execute()
                   ^^^^^^^^^^^^^^^
  File "/opt/netbox/venv/lib/python3.11/site-packages/rq/job.py", line 1315, in _execute
    result = self.func(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/extras/reports.py", line 36, in run_report
    job.start()
  File "/opt/netbox/netbox/core/models/jobs.py", line 176, in start
    job_start.send(self)
  File "/opt/netbox/venv/lib/python3.11/site-packages/django/dispatch/dispatcher.py", line 176, in send
    return [
           ^
  File "/opt/netbox/venv/lib/python3.11/site-packages/django/dispatch/dispatcher.py", line 177, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/netbox/netbox/extras/signals.py", line 254, in process_job_start_event_rules
    process_event_rules(event_rules, sender.object_type.model, EVENT_JOB_START, sender.data, sender.user.username)
                                                                                             ^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'username'
```


Discussion:

Resolving patch:
diff --git a/netbox/extras/events.py b/netbox/extras/events.py
--- a/netbox/extras/events.py
+++ b/netbox/extras/events.py
@@ -71,10 +71,10 @@ def enqueue_object(queue, instance, user, request_id, action):
     })
 
 
-def process_event_rules(event_rules, model_name, event, data, username, snapshots=None, request_id=None):
-    try:
+def process_event_rules(event_rules, model_name, event, data, username=None, snapshots=None, request_id=None):
+    if username:
         user = get_user_model().objects.get(username=username)
-    except ObjectDoesNotExist:
+    else:
         user = None
 
     for event_rule in event_rules:
diff --git a/netbox/extras/signals.py b/netbox/extras/signals.py
--- a/netbox/extras/signals.py
+++ b/netbox/extras/signals.py
@@ -251,7 +251,8 @@ def process_job_start_event_rules(sender, **kwargs):
     Process event rules for jobs starting.
     """
     event_rules = EventRule.objects.filter(type_job_start=True, enabled=True, content_types=sender.object_type)
-    process_event_rules(event_rules, sender.object_type.model, EVENT_JOB_START, sender.data, sender.user.username)
+    username = sender.user.username if sender.user else None
+    process_event_rules(event_rules, sender.object_type.model, EVENT_JOB_START, sender.data, username)
 
 
 @receiver(job_end)
@@ -260,4 +261,5 @@ def process_job_end_event_rules(sender, **kwargs):
     Process event rules for jobs terminating.
     """
     event_rules = EventRule.objects.filter(type_job_end=True, enabled=True, content_types=sender.object_type)
-    process_event_rules(event_rules, sender.object_type.model, EVENT_JOB_END, sender.data, sender.user.username)
+    username = sender.user.username if sender.user else None
+    process_event_rules(event_rules, sender.object_type.model, EVENT_JOB_END, sender.data, username)


Test patch: