Repository: netbox-community/netbox

Issue:
Script job count is incorrect
### Deployment Type

Self-hosted

### NetBox Version

v3.6.7

### Python Version

3.10

### Steps to Reproduce

1. Create a script file:

```python
from extras.scripts import Script


class TestScript(Script):
    name = 'Test'
    description = ''

    def run(self, data, commit=True):
        self.log_success('Finished Script')


class TestScriptNew(Script):
    name = 'Test New'
    description = ''

    def run(self, data, commit=True):
        self.log_success('Finished Script 2')
```
2. Upload to NetBox
3. Run `Test`
5. View `Test New`

### Expected Behavior

Job tab badge has "0"

### Observed Behavior

Job tab badge has >0


Discussion:
This is caused by the badge querying `module.jobs` without filtering for the script name.

Resolving patch:
diff --git a/netbox/extras/models/mixins.py b/netbox/extras/models/mixins.py
--- a/netbox/extras/models/mixins.py
+++ b/netbox/extras/models/mixins.py
@@ -8,6 +8,16 @@
 
 class PythonModuleMixin:
 
+    def get_jobs(self, name):
+        """
+        Returns a list of Jobs associated with this specific script or report module
+        :param name: The class name of the script or report
+        :return: List of Jobs associated with this
+        """
+        return self.jobs.filter(
+            name=name
+        )
+
     @property
     def path(self):
         return os.path.splitext(self.file_path)[0]
diff --git a/netbox/extras/views.py b/netbox/extras/views.py
--- a/netbox/extras/views.py
+++ b/netbox/extras/views.py
@@ -1057,16 +1057,14 @@ def get_required_permission(self):
     def get(self, request, module, name):
         module = get_report_module(module, request)
         report = module.reports[name]()
+        jobs = module.get_jobs(report.class_name)
 
-        object_type = ContentType.objects.get(app_label='extras', model='reportmodule')
-        report.result = Job.objects.filter(
-            object_type=object_type,
-            object_id=module.pk,
-            name=report.name,
+        report.result = jobs.filter(
             status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
         ).first()
 
         return render(request, 'extras/report.html', {
+            'job_count': jobs.count(),
             'module': module,
             'report': report,
             'form': ReportForm(scheduling_enabled=report.scheduling_enabled),
@@ -1078,6 +1076,7 @@ def post(self, request, module, name):
 
         module = get_report_module(module, request)
         report = module.reports[name]()
+        jobs = module.get_jobs(report.class_name)
         form = ReportForm(request.POST, scheduling_enabled=report.scheduling_enabled)
 
         if form.is_valid():
@@ -1086,6 +1085,7 @@ def post(self, request, module, name):
             if not get_workers_for_queue('default'):
                 messages.error(request, "Unable to run report: RQ worker process not running.")
                 return render(request, 'extras/report.html', {
+                    'job_count': jobs.count(),
                     'report': report,
                 })
 
@@ -1103,6 +1103,7 @@ def post(self, request, module, name):
             return redirect('extras:report_result', job_pk=job.pk)
 
         return render(request, 'extras/report.html', {
+            'job_count': jobs.count(),
             'module': module,
             'report': report,
             'form': form,
@@ -1117,8 +1118,10 @@ def get_required_permission(self):
     def get(self, request, module, name):
         module = get_report_module(module, request)
         report = module.reports[name]()
+        jobs = module.get_jobs(report.class_name)
 
         return render(request, 'extras/report/source.html', {
+            'job_count': jobs.count(),
             'module': module,
             'report': report,
             'tab': 'source',
@@ -1133,13 +1136,7 @@ def get_required_permission(self):
     def get(self, request, module, name):
         module = get_report_module(module, request)
         report = module.reports[name]()
-
-        object_type = ContentType.objects.get(app_label='extras', model='reportmodule')
-        jobs = Job.objects.filter(
-            object_type=object_type,
-            object_id=module.pk,
-            name=report.class_name
-        )
+        jobs = module.get_jobs(report.class_name)
 
         jobs_table = JobTable(
             data=jobs,
@@ -1149,6 +1146,7 @@ def get(self, request, module, name):
         jobs_table.configure(request)
 
         return render(request, 'extras/report/jobs.html', {
+            'job_count': jobs.count(),
             'module': module,
             'report': report,
             'table': jobs_table,
@@ -1232,19 +1230,16 @@ def get_required_permission(self):
     def get(self, request, module, name):
         module = get_script_module(module, request)
         script = module.scripts[name]()
+        jobs = module.get_jobs(script.class_name)
         form = script.as_form(initial=normalize_querydict(request.GET))
 
         # Look for a pending Job (use the latest one by creation timestamp)
-        object_type = ContentType.objects.get(app_label='extras', model='scriptmodule')
-        script.result = Job.objects.filter(
-            object_type=object_type,
-            object_id=module.pk,
-            name=script.name,
-        ).exclude(
+        script.result = module.get_jobs(script.class_name).exclude(
             status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
         ).first()
 
         return render(request, 'extras/script.html', {
+            'job_count': jobs.count(),
             'module': module,
             'script': script,
             'form': form,
@@ -1256,6 +1251,7 @@ def post(self, request, module, name):
 
         module = get_script_module(module, request)
         script = module.scripts[name]()
+        jobs = module.get_jobs(script.class_name)
         form = script.as_form(request.POST, request.FILES)
 
         # Allow execution only if RQ worker process is running
@@ -1279,6 +1275,7 @@ def post(self, request, module, name):
             return redirect('extras:script_result', job_pk=job.pk)
 
         return render(request, 'extras/script.html', {
+            'job_count': jobs.count(),
             'module': module,
             'script': script,
             'form': form,
@@ -1293,8 +1290,10 @@ def get_required_permission(self):
     def get(self, request, module, name):
         module = get_script_module(module, request)
         script = module.scripts[name]()
+        jobs = module.get_jobs(script.class_name)
 
         return render(request, 'extras/script/source.html', {
+            'job_count': jobs.count(),
             'module': module,
             'script': script,
             'tab': 'source',
@@ -1309,13 +1308,7 @@ def get_required_permission(self):
     def get(self, request, module, name):
         module = get_script_module(module, request)
         script = module.scripts[name]()
-
-        object_type = ContentType.objects.get(app_label='extras', model='scriptmodule')
-        jobs = Job.objects.filter(
-            object_type=object_type,
-            object_id=module.pk,
-            name=script.class_name
-        )
+        jobs = module.get_jobs(script.class_name)
 
         jobs_table = JobTable(
             data=jobs,
@@ -1325,6 +1318,7 @@ def get(self, request, module, name):
         jobs_table.configure(request)
 
         return render(request, 'extras/script/jobs.html', {
+            'job_count': jobs.count(),
             'module': module,
             'script': script,
             'table': jobs_table,


Test patch: