From 6930e441ae7a0be01f1d7367e048c6393d461ebd Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 6 Feb 2024 09:32:19 -0800 Subject: [PATCH] 14438 script job mapping --- .../migrations/0107_alter_script_options.py | 11 +++- netbox/extras/models/scripts.py | 15 +++++- netbox/extras/urls.py | 6 +-- netbox/extras/views.py | 51 ++++++++++--------- netbox/templates/extras/script.html | 2 +- netbox/templates/extras/script/base.html | 6 +-- netbox/templates/extras/script/source.html | 4 +- netbox/templates/extras/script_list.html | 2 +- 8 files changed, 61 insertions(+), 36 deletions(-) diff --git a/netbox/extras/migrations/0107_alter_script_options.py b/netbox/extras/migrations/0107_alter_script_options.py index b49b9ba5e..25988d7e4 100644 --- a/netbox/extras/migrations/0107_alter_script_options.py +++ b/netbox/extras/migrations/0107_alter_script_options.py @@ -8,14 +8,19 @@ def update_scripts(apps, schema_editor): from extras.models import ScriptModule ScriptModuleNew = apps.get_model('extras', 'ScriptModule') Script = apps.get_model('extras', 'Script') + ContentType = apps.get_model('contenttypes', 'ContentType') + ct = ContentType.objects.get(app_label='extras', model='script') for module in ScriptModule.objects.all(): for script in module.get_module_scripts.keys(): - Script.objects.create( + obj = Script.objects.create( name=script, module=ScriptModuleNew.objects.get(file_root=module.file_root, file_path=module.file_path), ) + # update all jobs associated with this module/name to point to the new script obj + module.jobs.filter(name=name).update(object_type=ct, object_id=obj.id) + class Migration(migrations.Migration): @@ -35,6 +40,10 @@ class Migration(migrations.Migration): 'ordering': ('name', 'pk'), }, ), + migrations.AddConstraint( + model_name='script', + constraint=models.UniqueConstraint(fields=('name', 'module'), name='extras_script_unique_name_module'), + ), migrations.RunPython( code=update_scripts, reverse_code=migrations.RunPython.noop diff --git a/netbox/extras/models/scripts.py b/netbox/extras/models/scripts.py index fb0be49c6..f39ef2e44 100644 --- a/netbox/extras/models/scripts.py +++ b/netbox/extras/models/scripts.py @@ -21,7 +21,7 @@ __all__ = ( logger = logging.getLogger('netbox.data_backends') -class Script(EventRulesMixin, models.Model): +class Script(EventRulesMixin, JobsMixin, models.Model): name = models.CharField( verbose_name=_('name'), max_length=79, @@ -37,11 +37,24 @@ class Script(EventRulesMixin, models.Model): class Meta: ordering = ('name', 'pk') + constraints = ( + models.UniqueConstraint( + fields=('name', 'module'), + name='%(app_label)s_%(class)s_unique_name_module' + ), + ) + verbose_name = _('script') + verbose_name_plural = _('scripts') @cached_property def python_class(self): return self.module.get_module_scripts.get(self.name) + def get_jobs(self): + return self.module.jobs.filter( + name=self.name + ) + class ScriptModuleManager(models.Manager.from_queryset(RestrictedQuerySet)): diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index 0a1786f1f..919c54c6f 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -130,9 +130,9 @@ urlpatterns = [ path('scripts/add/', views.ScriptModuleCreateView.as_view(), name='scriptmodule_add'), path('scripts/results//', views.ScriptResultView.as_view(), name='script_result'), path('scripts//', include(get_model_urls('extras', 'scriptmodule'))), - path('scripts///', views.ScriptView.as_view(), name='script'), - path('scripts///source/', views.ScriptSourceView.as_view(), name='script_source'), - path('scripts///jobs/', views.ScriptJobsView.as_view(), name='script_jobs'), + path('scripts/class//', views.ScriptView.as_view(), name='script'), + path('scripts/class//source/', views.ScriptSourceView.as_view(), name='script_source'), + path('scripts/class//jobs/', views.ScriptJobsView.as_view(), name='script_jobs'), # Markdown path('render/markdown/', views.RenderMarkdownView.as_view(), name="render_markdown"), diff --git a/netbox/extras/views.py b/netbox/extras/views.py index ad00d7412..c81c03a5e 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1226,27 +1226,28 @@ class ScriptView(ContentTypePermissionRequiredMixin, View): def get_required_permission(self): return 'extras.view_script' - 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)) + def get(self, request, pk): + script = Script.objects.get(pk=pk) + script_class = script.python_class() + jobs = script.get_jobs() + form = script_class.as_form(initial=normalize_querydict(request.GET)) return render(request, 'extras/script.html', { 'job_count': jobs.count(), - 'module': module, + 'module': script.module, 'script': script, + 'script_class': script_class, 'form': form, }) - def post(self, request, module, name): + def post(self, request, pk): if not request.user.has_perm('extras.run_script'): return HttpResponseForbidden() - 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) + script = Script.objects.get(pk=pk) + script_class = script.python_class() + jobs = script.get_jobs() + form = script_class.as_form(request.POST, request.FILES) # Allow execution only if RQ worker process is running if not get_workers_for_queue('default'): @@ -1255,8 +1256,8 @@ class ScriptView(ContentTypePermissionRequiredMixin, View): elif form.is_valid(): job = Job.enqueue( run_script, - instance=module, - name=script.class_name, + instance=script.module, + name=script_class.class_name, user=request.user, schedule_at=form.cleaned_data.pop('_schedule_at'), interval=form.cleaned_data.pop('_interval'), @@ -1270,8 +1271,9 @@ class ScriptView(ContentTypePermissionRequiredMixin, View): return render(request, 'extras/script.html', { 'job_count': jobs.count(), - 'module': module, + 'module': script.module, 'script': script, + 'script_class': script_class, 'form': form, }) @@ -1281,15 +1283,16 @@ class ScriptSourceView(ContentTypePermissionRequiredMixin, View): def get_required_permission(self): return 'extras.view_script' - def get(self, request, module, name): - module = get_script_module(module, request) - script = module.scripts[name]() - jobs = module.get_jobs(script.class_name) + def get(self, request, pk): + script = Script.objects.get(pk=pk) + script_class = script.python_class() + jobs = script.get_jobs() return render(request, 'extras/script/source.html', { 'job_count': jobs.count(), - 'module': module, + 'module': script.module, 'script': script, + 'script_class': script_class, 'tab': 'source', }) @@ -1299,10 +1302,10 @@ class ScriptJobsView(ContentTypePermissionRequiredMixin, View): def get_required_permission(self): return 'extras.view_script' - def get(self, request, module, name): - module = get_script_module(module, request) - script = module.scripts[name]() - jobs = module.get_jobs(script.class_name) + def get(self, request, pk): + script = Script.objects.get(pk=pk) + script_class = script.python_class() + jobs = script.get_jobs() jobs_table = JobTable( data=jobs, @@ -1313,7 +1316,7 @@ class ScriptJobsView(ContentTypePermissionRequiredMixin, View): return render(request, 'extras/script/jobs.html', { 'job_count': jobs.count(), - 'module': module, + 'module': script.module, 'script': script, 'table': jobs_table, 'tab': 'jobs', diff --git a/netbox/templates/extras/script.html b/netbox/templates/extras/script.html index dbed132be..074d06fe5 100644 --- a/netbox/templates/extras/script.html +++ b/netbox/templates/extras/script.html @@ -17,7 +17,7 @@ {% csrf_token %}
{# Render grouped fields according to declared fieldsets #} - {% for group, fields in script.get_fieldsets %} + {% for group, fields in script_class.get_fieldsets %} {% if fields %}
diff --git a/netbox/templates/extras/script/base.html b/netbox/templates/extras/script/base.html index 194f097e7..429db590e 100644 --- a/netbox/templates/extras/script/base.html +++ b/netbox/templates/extras/script/base.html @@ -26,13 +26,13 @@ {% block tabs %}