From 1034ee7380647011304bfb53afbd9f27802f4a7e Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 5 Feb 2024 13:41:23 -0800 Subject: [PATCH 01/51] 14438 script model --- .../migrations/0107_alter_script_options.py | 35 +++++++++++++++++++ netbox/extras/models/scripts.py | 17 ++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 netbox/extras/migrations/0107_alter_script_options.py diff --git a/netbox/extras/migrations/0107_alter_script_options.py b/netbox/extras/migrations/0107_alter_script_options.py new file mode 100644 index 0000000000..6829761f3e --- /dev/null +++ b/netbox/extras/migrations/0107_alter_script_options.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.9 on 2024-02-05 21:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0106_bookmark_user_cascade_deletion'), + ] + + operations = [ + migrations.AddField( + model_name='script', + name='name', + field=models.CharField(default=None, max_length=79), + preserve_default=False, + ), + migrations.AddField( + model_name='script', + name='script_module', + field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='scripts', to='extras.scriptmodule'), + preserve_default=False, + ), + migrations.AlterField( + model_name='script', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False), + ), + migrations.AlterModelOptions( + name='script', + options={'ordering': ('name', 'pk')}, + ), + ] diff --git a/netbox/extras/models/scripts.py b/netbox/extras/models/scripts.py index 93275acdab..2f1454e2ad 100644 --- a/netbox/extras/models/scripts.py +++ b/netbox/extras/models/scripts.py @@ -22,11 +22,18 @@ class Script(EventRulesMixin, models.Model): - """ - Dummy model used to generate permissions for custom scripts. Does not exist in the database. - """ + name = models.CharField( + verbose_name=_('name'), + max_length=79, + ) + script_module = models.ForeignKey( + to='extras.ScriptModule', + on_delete=models.PROTECT, + related_name='scripts' + ) + class Meta: - managed = False + ordering = ('name', 'pk') class ScriptModuleManager(models.Manager.from_queryset(RestrictedQuerySet)): @@ -52,6 +59,7 @@ def get_absolute_url(self): def __str__(self): return self.python_name + ''' @cached_property def scripts(self): @@ -75,6 +83,7 @@ def _get_name(cls): scripts[_get_name(cls)] = cls return scripts + ''' def save(self, *args, **kwargs): self.file_root = ManagedFileRootPathChoices.SCRIPTS From 4cb3b63f79ea9814bc4627bcad4f8be683591cca Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 5 Feb 2024 14:16:24 -0800 Subject: [PATCH 02/51] 14438 script model --- .../migrations/0107_alter_script_options.py | 18 +++++++++++++++++- netbox/extras/models/scripts.py | 6 ++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/netbox/extras/migrations/0107_alter_script_options.py b/netbox/extras/migrations/0107_alter_script_options.py index 6829761f3e..47612d8f86 100644 --- a/netbox/extras/migrations/0107_alter_script_options.py +++ b/netbox/extras/migrations/0107_alter_script_options.py @@ -4,6 +4,18 @@ import django.db.models.deletion +def update_scripts(apps, schema_editor): + ScriptModule = apps.get_model('extras', 'ScriptModule') + Script = apps.get_model('extras', 'Script') + + for module in ScriptModule.objects.all(): + for script, cls in module.get_module_scripts: + Script.objects.create( + name=script, + script_module=module, + ) + + class Migration(migrations.Migration): dependencies = [ @@ -19,7 +31,7 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='script', - name='script_module', + name='module', field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='scripts', to='extras.scriptmodule'), preserve_default=False, ), @@ -32,4 +44,8 @@ class Migration(migrations.Migration): name='script', options={'ordering': ('name', 'pk')}, ), + 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 2f1454e2ad..baaa9b9731 100644 --- a/netbox/extras/models/scripts.py +++ b/netbox/extras/models/scripts.py @@ -26,7 +26,7 @@ class Script(EventRulesMixin, models.Model): verbose_name=_('name'), max_length=79, ) - script_module = models.ForeignKey( + module = models.ForeignKey( to='extras.ScriptModule', on_delete=models.PROTECT, related_name='scripts' @@ -59,9 +59,8 @@ def get_absolute_url(self): def __str__(self): return self.python_name - ''' @cached_property - def scripts(self): + def get_module_scripts(self): def _get_name(cls): # For child objects in submodules use the full import path w/o the root module as the name @@ -83,7 +82,6 @@ def _get_name(cls): scripts[_get_name(cls)] = cls return scripts - ''' def save(self, *args, **kwargs): self.file_root = ManagedFileRootPathChoices.SCRIPTS From fc890e36afab0354b9918829bd323cfeddf8a0ce Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 5 Feb 2024 15:15:01 -0800 Subject: [PATCH 03/51] 14438 script model --- .../migrations/0107_alter_script_options.py | 37 +++++++------------ netbox/extras/models/scripts.py | 7 ++++ netbox/templates/extras/script_list.html | 8 ++-- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/netbox/extras/migrations/0107_alter_script_options.py b/netbox/extras/migrations/0107_alter_script_options.py index 47612d8f86..b49b9ba5e7 100644 --- a/netbox/extras/migrations/0107_alter_script_options.py +++ b/netbox/extras/migrations/0107_alter_script_options.py @@ -5,14 +5,15 @@ def update_scripts(apps, schema_editor): - ScriptModule = apps.get_model('extras', 'ScriptModule') + from extras.models import ScriptModule + ScriptModuleNew = apps.get_model('extras', 'ScriptModule') Script = apps.get_model('extras', 'Script') for module in ScriptModule.objects.all(): - for script, cls in module.get_module_scripts: + for script in module.get_module_scripts.keys(): Script.objects.create( name=script, - script_module=module, + module=ScriptModuleNew.objects.get(file_root=module.file_root, file_path=module.file_path), ) @@ -23,26 +24,16 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='script', - name='name', - field=models.CharField(default=None, max_length=79), - preserve_default=False, - ), - migrations.AddField( - model_name='script', - name='module', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='scripts', to='extras.scriptmodule'), - preserve_default=False, - ), - migrations.AlterField( - model_name='script', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False), - ), - migrations.AlterModelOptions( - name='script', - options={'ordering': ('name', 'pk')}, + migrations.CreateModel( + name='Script', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=79)), + ('module', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='scripts', to='extras.scriptmodule')), + ], + options={ + 'ordering': ('name', 'pk'), + }, ), migrations.RunPython( code=update_scripts, diff --git a/netbox/extras/models/scripts.py b/netbox/extras/models/scripts.py index baaa9b9731..fb0be49c68 100644 --- a/netbox/extras/models/scripts.py +++ b/netbox/extras/models/scripts.py @@ -32,9 +32,16 @@ class Script(EventRulesMixin, models.Model): related_name='scripts' ) + def __str__(self): + return self.name + class Meta: ordering = ('name', 'pk') + @cached_property + def python_class(self): + return self.module.get_module_scripts.get(self.name) + class ScriptModuleManager(models.Manager.from_queryset(RestrictedQuerySet)): diff --git a/netbox/templates/extras/script_list.html b/netbox/templates/extras/script_list.html index bb91f75222..d5c8711e51 100644 --- a/netbox/templates/extras/script_list.html +++ b/netbox/templates/extras/script_list.html @@ -53,15 +53,15 @@
{% with jobs=module.get_latest_jobs %} - {% for script_name, script_class in module.scripts.items %} + {% for script in module.scripts.all %} - {{ script_class.name }} + {{ script.python_class.name }} - {{ script_class.Meta.description|markdown|placeholder }} + {{ script.python_class.Meta.description|markdown|placeholder }} - {% with last_result=jobs|get_key:script_class.class_name %} + {% with last_result=jobs|get_key:script.python_class.class_name %} {% if last_result %} {{ last_result.created|annotated_date }} From 6930e441ae7a0be01f1d7367e048c6393d461ebd Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 6 Feb 2024 09:32:19 -0800 Subject: [PATCH 04/51] 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 b49b9ba5e7..25988d7e40 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 fb0be49c68..f39ef2e441 100644 --- a/netbox/extras/models/scripts.py +++ b/netbox/extras/models/scripts.py @@ -21,7 +21,7 @@ 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 @@ def __str__(self): 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 0a1786f1f3..919c54c6fd 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -130,9 +130,9 @@ 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 ad00d74123..c81c03a5e4 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 @@ def post(self, request, module, name): 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 @@ def post(self, request, module, name): 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 @@ def get(self, request, module, name): 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 dbed132be8..074d06fe5d 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 194f097e7f..429db590e3 100644 --- a/netbox/templates/extras/script/base.html +++ b/netbox/templates/extras/script/base.html @@ -26,13 +26,13 @@ {% block tabs %}