Enable resolving scripts/reports from module class

This commit is contained in:
jeremystretch 2023-03-24 10:11:15 -04:00
parent bfccd6820e
commit f5830c1cd8
5 changed files with 58 additions and 21 deletions

View File

@ -1,5 +1,7 @@
import inspect
import json import json
import uuid import uuid
from functools import cached_property
from pkgutil import ModuleInfo, get_importer from pkgutil import ModuleInfo, get_importer
from django.conf import settings from django.conf import settings
@ -31,6 +33,7 @@ from netbox.models.features import (
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, SyncedDataMixin, CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, SyncedDataMixin,
TagsMixin, WebhooksMixin, TagsMixin, WebhooksMixin,
) )
from ..temp import is_report, is_script
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.rqworker import get_queue_for_model from utilities.rqworker import get_queue_for_model
from utilities.utils import render_jinja2 from utilities.utils import render_jinja2
@ -827,6 +830,11 @@ class PythonModuleMixin:
ispkg=False ispkg=False
) )
def get_module(self):
importer, module_name, _ = self.get_module_info()
module = importer.find_module(module_name).load_module(module_name)
return module
class Script(JobResultsMixin, WebhooksMixin, models.Model): class Script(JobResultsMixin, WebhooksMixin, models.Model):
""" """
@ -862,6 +870,18 @@ class ScriptModule(JobResultsMixin, WebhooksMixin, PythonModuleMixin, ManagedFil
def name(self): def name(self):
return self.file_path return self.file_path
@cached_property
def scripts(self):
module = self.get_module()
scripts = {}
for name, cls in inspect.getmembers(module, is_script):
# For child objects in submodules use the full import path w/o the root module as the name
child_name = cls.full_name.split(".", maxsplit=1)[1]
scripts[child_name] = cls
return scripts
# #
# Reports # Reports
@ -896,3 +916,15 @@ class ReportModule(JobResultsMixin, WebhooksMixin, PythonModuleMixin, ManagedFil
def get_absolute_url(self): def get_absolute_url(self):
return reverse('extras:report_list') return reverse('extras:report_list')
@cached_property
def reports(self):
module = self.get_module()
reports = {}
for name, cls in inspect.getmembers(module, is_report):
# For child objects in submodules use the full import path w/o the root module as the name
child_name = cls().full_name.split(".", maxsplit=1)[1]
reports[child_name] = cls
return reports

View File

@ -7,18 +7,12 @@ from django_rq import job
from .choices import JobResultStatusChoices, LogLevelChoices from .choices import JobResultStatusChoices, LogLevelChoices
from .models import JobResult, ReportModule from .models import JobResult, ReportModule
from .temp import is_report
from .utils import get_modules from .utils import get_modules
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def is_report(obj):
"""
Returns True if the given object is a Report.
"""
return obj in Report.__subclasses__()
def get_reports(): def get_reports():
return get_modules(ReportModule.objects.all(), is_report, 'report_order') return get_modules(ReportModule.objects.all(), is_report, 'report_order')

View File

@ -22,6 +22,7 @@ from utilities.exceptions import AbortScript, AbortTransaction
from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from .context_managers import change_logging from .context_managers import change_logging
from .forms import ScriptForm from .forms import ScriptForm
from .temp import is_script
from .utils import get_modules from .utils import get_modules
__all__ = [ __all__ = [
@ -423,15 +424,6 @@ class Script(BaseScript):
# Functions # Functions
# #
def is_script(obj):
"""
Returns True if the object is a Script.
"""
try:
return issubclass(obj, Script) and obj != Script
except TypeError:
return False
def is_variable(obj): def is_variable(obj):
""" """

20
netbox/extras/temp.py Normal file
View File

@ -0,0 +1,20 @@
def is_script(obj):
"""
Returns True if the object is a Script.
"""
from .scripts import Script
try:
return issubclass(obj, Script) and obj != Script
except TypeError:
return False
def is_report(obj):
"""
Returns True if the given object is a Report.
"""
from .reports import Report
try:
return issubclass(obj, Report) and obj != Report
except TypeError:
return False

View File

@ -849,10 +849,8 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
return 'extras.view_report' return 'extras.view_report'
def get(self, request, module, name): def get(self, request, module, name):
module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path=f'{module}.py')
report = get_report(module, name) report = module.reports[name]()
if report is None:
raise Http404
report_content_type = ContentType.objects.get(app_label='extras', model='report') report_content_type = ContentType.objects.get(app_label='extras', model='report')
report.result = JobResult.objects.filter( report.result = JobResult.objects.filter(
@ -1001,7 +999,8 @@ class ScriptView(ContentTypePermissionRequiredMixin, GetScriptMixin, View):
return 'extras.view_script' return 'extras.view_script'
def get(self, request, module, name): def get(self, request, module, name):
script = self._get_script(name, module) module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path=f'{module}.py')
script = module.scripts[name]()
form = script.as_form(initial=normalize_querydict(request.GET)) form = script.as_form(initial=normalize_querydict(request.GET))
# Look for a pending JobResult (use the latest one by creation timestamp) # Look for a pending JobResult (use the latest one by creation timestamp)