mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-17 04:58:16 -06:00
14438 check valid script for views
This commit is contained in:
parent
41c792a3e5
commit
172d1b00cc
@ -35,12 +35,17 @@ class Migration(migrations.Migration):
|
||||
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')),
|
||||
('module', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='scripts', to='extras.scriptmodule')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name', 'pk'),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='script',
|
||||
name='is_valid',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='script',
|
||||
constraint=models.UniqueConstraint(fields=('name', 'module'), name='extras_script_unique_name_module'),
|
||||
|
@ -29,9 +29,12 @@ class Script(EventRulesMixin, JobsMixin, models.Model):
|
||||
)
|
||||
module = models.ForeignKey(
|
||||
to='extras.ScriptModule',
|
||||
on_delete=models.PROTECT,
|
||||
on_delete=models.RESTRICT,
|
||||
related_name='scripts'
|
||||
)
|
||||
is_valid = models.BooleanField(
|
||||
default=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@ -51,6 +54,14 @@ class Script(EventRulesMixin, JobsMixin, models.Model):
|
||||
def python_class(self):
|
||||
return self.module.get_module_scripts.get(self.name)
|
||||
|
||||
def delete_if_no_jobs(self):
|
||||
if self.jobs.all():
|
||||
self.is_valid = False
|
||||
self.save()
|
||||
else:
|
||||
self.delete()
|
||||
self.id = None
|
||||
|
||||
|
||||
class ScriptModuleManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
||||
|
||||
@ -100,6 +111,35 @@ class ScriptModule(PythonModuleMixin, JobsMixin, ManagedFile):
|
||||
|
||||
return scripts
|
||||
|
||||
def sync_classes(self):
|
||||
db_classes = {}
|
||||
for obj in self.scripts.filter(module=self):
|
||||
db_classes[obj.name] = obj
|
||||
|
||||
db_classes_set = {k for k in db_classes.keys()}
|
||||
|
||||
module_scripts = self.get_module_scripts
|
||||
|
||||
module_classes_set = {k for k in module_scripts.keys()}
|
||||
|
||||
# remove any existing db classes if they are no longer in the file
|
||||
removed = db_classes_set - module_classes_set
|
||||
for name in removed:
|
||||
db_classes[name].delete_if_no_jobs()
|
||||
|
||||
added = module_classes_set - db_classes_set
|
||||
for name in added:
|
||||
Script.objects.create(
|
||||
module=self,
|
||||
name=name,
|
||||
is_valid=True,
|
||||
)
|
||||
|
||||
def sync_data(self):
|
||||
super().sync_data()
|
||||
self.sync_classes()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.file_root = ManagedFileRootPathChoices.SCRIPTS
|
||||
return super().save(*args, **kwargs)
|
||||
super().save(*args, **kwargs)
|
||||
self.sync_classes()
|
||||
|
@ -920,7 +920,7 @@ class DashboardWidgetAddView(LoginRequiredMixin, View):
|
||||
widget = widget_class(**data)
|
||||
request.user.dashboard.add_widget(widget)
|
||||
request.user.dashboard.save()
|
||||
messages.success(request, f'Added widget {widget.id}')
|
||||
messages.success(request, _('Added widget: ') + str(widget.id))
|
||||
|
||||
return HttpResponse(headers={
|
||||
'HX-Redirect': reverse('home'),
|
||||
@ -961,7 +961,7 @@ class DashboardWidgetConfigView(LoginRequiredMixin, View):
|
||||
data['config'] = config_form.cleaned_data
|
||||
request.user.dashboard.config[str(id)].update(data)
|
||||
request.user.dashboard.save()
|
||||
messages.success(request, f'Updated widget {widget.id}')
|
||||
messages.success(request, _('Updated widget: ') + str(widget.id))
|
||||
|
||||
return HttpResponse(headers={
|
||||
'HX-Redirect': reverse('home'),
|
||||
@ -997,9 +997,9 @@ class DashboardWidgetDeleteView(LoginRequiredMixin, View):
|
||||
if form.is_valid():
|
||||
request.user.dashboard.delete_widget(id)
|
||||
request.user.dashboard.save()
|
||||
messages.success(request, f'Deleted widget {id}')
|
||||
messages.success(request, _('Deleted widget: ') + str(id))
|
||||
else:
|
||||
messages.error(request, f'Error deleting widget: {form.errors[0]}')
|
||||
messages.error(request, _('Error deleting widget: ') + str(form.errors[0]))
|
||||
|
||||
return redirect(reverse('home'))
|
||||
|
||||
@ -1042,19 +1042,45 @@ def get_script_module(module, request):
|
||||
return get_object_or_404(ScriptModule.objects.restrict(request.user), file_path__regex=f"^{module}\\.")
|
||||
|
||||
|
||||
class ScriptView(ContentTypePermissionRequiredMixin, View):
|
||||
class BaseScriptView(ContentTypePermissionRequiredMixin, View):
|
||||
script = None
|
||||
script_class = None
|
||||
jobs = None
|
||||
|
||||
def get_required_permission(self):
|
||||
return 'extras.view_script'
|
||||
|
||||
def get_script(self, request, pk):
|
||||
self.script = Script.objects.get(pk=pk)
|
||||
if self.script.python_class:
|
||||
self.script_class = script.python_class()
|
||||
else:
|
||||
self.script.delete_if_no_jobs()
|
||||
messages.error(request, _("Script class has been deleted."))
|
||||
if not self.script.id:
|
||||
return redirect('extras:script_list')
|
||||
else:
|
||||
return redirect('extras:script_jobs', pk=self.script.id)
|
||||
|
||||
self.jobs = self.script.jobs.all()
|
||||
return None
|
||||
|
||||
|
||||
class ScriptView(BaseScriptView):
|
||||
|
||||
def get_required_permission(self):
|
||||
return 'extras.view_script'
|
||||
|
||||
def get(self, request, pk):
|
||||
script = Script.objects.get(pk=pk)
|
||||
script_class = script.python_class()
|
||||
jobs = script.jobs.all()
|
||||
form = script_class.as_form(initial=normalize_querydict(request.GET))
|
||||
if ret := self.get_script(request, pk):
|
||||
return ret
|
||||
|
||||
form = None
|
||||
if self.script_class:
|
||||
form = script_class.as_form(initial=normalize_querydict(request.GET))
|
||||
|
||||
return render(request, 'extras/script.html', {
|
||||
'job_count': jobs.count(),
|
||||
'job_count': self.jobs.count(),
|
||||
'module': script.module,
|
||||
'script': script,
|
||||
'script_class': script_class,
|
||||
@ -1065,85 +1091,93 @@ class ScriptView(ContentTypePermissionRequiredMixin, View):
|
||||
if not request.user.has_perm('extras.run_script'):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
script = Script.objects.get(pk=pk)
|
||||
script_class = script.python_class()
|
||||
jobs = script.jobs.all()
|
||||
form = script_class.as_form(request.POST, request.FILES)
|
||||
if ret := self.get_script(request, pk):
|
||||
return ret
|
||||
|
||||
form = None
|
||||
if self.script_class:
|
||||
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'):
|
||||
messages.error(request, "Unable to run script: RQ worker process not running.")
|
||||
messages.error(request, _("Unable to run script: RQ worker process not running."))
|
||||
elif form.is_valid():
|
||||
job = Job.enqueue(
|
||||
run_script,
|
||||
instance=script,
|
||||
name=script_class.class_name,
|
||||
instance=self.script,
|
||||
name=self.script_class.class_name,
|
||||
user=request.user,
|
||||
schedule_at=form.cleaned_data.pop('_schedule_at'),
|
||||
interval=form.cleaned_data.pop('_interval'),
|
||||
data=form.cleaned_data,
|
||||
request=copy_safe_request(request),
|
||||
job_timeout=script.python_class.job_timeout,
|
||||
job_timeout=self.script.python_class.job_timeout,
|
||||
commit=form.cleaned_data.pop('_commit')
|
||||
)
|
||||
|
||||
return redirect('extras:script_result', job_pk=job.pk)
|
||||
|
||||
return render(request, 'extras/script.html', {
|
||||
'job_count': jobs.count(),
|
||||
'module': script.module,
|
||||
'script': script,
|
||||
'script_class': script_class,
|
||||
'job_count': self.jobs.count(),
|
||||
'module': self.script.module,
|
||||
'script': self.script,
|
||||
'script_class': self.script_class,
|
||||
'form': form,
|
||||
})
|
||||
|
||||
|
||||
class ScriptSourceView(ContentTypePermissionRequiredMixin, View):
|
||||
class ScriptSourceView(BaseScriptView):
|
||||
|
||||
def get_required_permission(self):
|
||||
return 'extras.view_script'
|
||||
|
||||
def get(self, request, pk):
|
||||
script = Script.objects.get(pk=pk)
|
||||
script_class = script.python_class()
|
||||
jobs = script.jobs.all()
|
||||
if ret := self.get_script(request, pk):
|
||||
return ret
|
||||
|
||||
return render(request, 'extras/script/source.html', {
|
||||
'job_count': jobs.count(),
|
||||
'module': script.module,
|
||||
'script': script,
|
||||
'script_class': script_class,
|
||||
'job_count': self.jobs.count(),
|
||||
'module': self.script.module,
|
||||
'script': self.script,
|
||||
'script_class': self.script_class,
|
||||
'tab': 'source',
|
||||
})
|
||||
|
||||
|
||||
class ScriptJobsView(ContentTypePermissionRequiredMixin, View):
|
||||
class ScriptJobsView(BaseScriptView):
|
||||
|
||||
def get_required_permission(self):
|
||||
return 'extras.view_script'
|
||||
|
||||
def get(self, request, pk):
|
||||
script = Script.objects.get(pk=pk)
|
||||
script_class = script.python_class()
|
||||
jobs = script.jobs.all()
|
||||
self.script = Script.objects.get(pk=pk)
|
||||
if self.script.python_class:
|
||||
self.script_class = script.python_class()
|
||||
else:
|
||||
self.script.delete_if_no_jobs()
|
||||
if not self.script.id:
|
||||
messages.error(request, _("Script class has been deleted."))
|
||||
return redirect('extras:script_list')
|
||||
|
||||
self.jobs = self.script.jobs.all()
|
||||
|
||||
jobs_table = JobTable(
|
||||
data=jobs,
|
||||
data=self.jobs,
|
||||
orderable=False,
|
||||
user=request.user
|
||||
)
|
||||
jobs_table.configure(request)
|
||||
|
||||
return render(request, 'extras/script/jobs.html', {
|
||||
'job_count': jobs.count(),
|
||||
'module': script.module,
|
||||
'script': script,
|
||||
'job_count': self.jobs.count(),
|
||||
'module': self.script.module,
|
||||
'script': self.script,
|
||||
'table': jobs_table,
|
||||
'tab': 'jobs',
|
||||
})
|
||||
|
||||
|
||||
class ScriptResultView(ContentTypePermissionRequiredMixin, View):
|
||||
class ScriptResultView(BaseScriptView):
|
||||
|
||||
def get_required_permission(self):
|
||||
return 'extras.view_script'
|
||||
|
Loading…
Reference in New Issue
Block a user