From b1671531865a409f154d156173c048f7ea35628b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 17 Apr 2023 13:12:14 -0400 Subject: [PATCH] Enable scheduling_enabled parameter for reports --- docs/customization/reports.md | 4 ++++ docs/release-notes/version-3.5.md | 1 + netbox/extras/api/serializers.py | 10 ++++++++++ netbox/extras/api/views.py | 8 ++++++-- netbox/extras/forms/reports.py | 29 +++++++++++++++++------------ netbox/extras/forms/scripts.py | 4 ++-- netbox/extras/reports.py | 1 + netbox/extras/views.py | 4 ++-- 8 files changed, 43 insertions(+), 18 deletions(-) diff --git a/docs/customization/reports.md b/docs/customization/reports.md index fab12bf7d..b68e17bf4 100644 --- a/docs/customization/reports.md +++ b/docs/customization/reports.md @@ -91,6 +91,10 @@ As you can see, reports are completely customizable. Validation logic can be as A human-friendly description of what your report does. +### `scheduling_enabled` + +By default, a report can be scheduled for execution at a later time. Setting `scheduling_enabled` to False disables this ability: Only immediate execution will be possible. (This also disables the ability to set a recurring execution interval.) + ### `job_timeout` Set the maximum allowed runtime for the report. If not set, `RQ_DEFAULT_TIMEOUT` will be used. diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index af12d4748..c3b8462e8 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -57,6 +57,7 @@ Two new webhook trigger events have been introduced: `job_start` and `job_end`. * [#10729](https://github.com/netbox-community/netbox/issues/10729) - Add date & time custom field type * [#11029](https://github.com/netbox-community/netbox/issues/11029) - Enable change logging for cable terminations * [#11254](https://github.com/netbox-community/netbox/issues/11254) - Introduce the `X-Request-ID` HTTP header to annotate the unique ID of each request for change logging +* [#11255](https://github.com/netbox-community/netbox/issues/11255) - Introduce the `scheduling_enabled` settings for reports & scripts * [#11291](https://github.com/netbox-community/netbox/issues/11291) - Optimized GraphQL API request handling * [#11440](https://github.com/netbox-community/netbox/issues/11440) - Add an `enabled` field for device type interfaces * [#11494](https://github.com/netbox-community/netbox/issues/11494) - Enable filtering objects by create/update request IDs diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index be48aed9b..cbe4ed56d 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -443,6 +443,16 @@ class ReportInputSerializer(serializers.Serializer): schedule_at = serializers.DateTimeField(required=False, allow_null=True) interval = serializers.IntegerField(required=False, allow_null=True) + def validate_schedule_at(self, value): + if value and not self.context['report'].scheduling_enabled: + raise serializers.ValidationError("Scheduling is not enabled for this report.") + return value + + def validate_interval(self, value): + if value and not self.context['report'].scheduling_enabled: + raise serializers.ValidationError("Scheduling is not enabled for this report.") + return value + # # Scripts diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 645100169..f302024b0 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -244,8 +244,12 @@ class ReportViewSet(ViewSet): raise RQWorkerNotRunningException() # Retrieve and run the Report. This will create a new Job. - module, report = self._get_report(pk) - input_serializer = serializers.ReportInputSerializer(data=request.data) + module, report_cls = self._get_report(pk) + report = report_cls() + input_serializer = serializers.ReportInputSerializer( + data=request.data, + context={'report': report} + ) if input_serializer.is_valid(): report.result = Job.enqueue( diff --git a/netbox/extras/forms/reports.py b/netbox/extras/forms/reports.py index 6a0b99eec..f3a9927f8 100644 --- a/netbox/extras/forms/reports.py +++ b/netbox/extras/forms/reports.py @@ -25,20 +25,25 @@ class ReportForm(BootstrapMixin, forms.Form): help_text=_("Interval at which this report is re-run (in minutes)") ) - def clean(self): - scheduled_time = self.cleaned_data['schedule_at'] - if scheduled_time and scheduled_time < local_now(): - raise forms.ValidationError(_('Scheduled time must be in the future.')) - - # When interval is used without schedule at, raise an exception - if self.cleaned_data['interval'] and not scheduled_time: - self.cleaned_data['schedule_at'] = local_now() - - return self.cleaned_data - - def __init__(self, *args, **kwargs): + def __init__(self, *args, scheduling_enabled=True, **kwargs): super().__init__(*args, **kwargs) # Annotate the current system time for reference now = local_now().strftime('%Y-%m-%d %H:%M:%S') self.fields['schedule_at'].help_text += f' (current time: {now})' + + # Remove scheduling fields if scheduling is disabled + if not scheduling_enabled: + self.fields.pop('schedule_at') + self.fields.pop('interval') + + def clean(self): + scheduled_time = self.cleaned_data.get('schedule_at') + if scheduled_time and scheduled_time < local_now(): + raise forms.ValidationError(_('Scheduled time must be in the future.')) + + # When interval is used without schedule at, schedule for the current time + if self.cleaned_data.get('interval') and not scheduled_time: + self.cleaned_data['schedule_at'] = local_now() + + return self.cleaned_data diff --git a/netbox/extras/forms/scripts.py b/netbox/extras/forms/scripts.py index 5677db536..b19faba8f 100644 --- a/netbox/extras/forms/scripts.py +++ b/netbox/extras/forms/scripts.py @@ -44,12 +44,12 @@ class ScriptForm(BootstrapMixin, forms.Form): self.fields.pop('_interval') def clean(self): - scheduled_time = self.cleaned_data['_schedule_at'] + scheduled_time = self.cleaned_data.get('_schedule_at') if scheduled_time and scheduled_time < local_now(): raise forms.ValidationError(_('Scheduled time must be in the future.')) # When interval is used without schedule at, schedule for the current time - if self.cleaned_data['_interval'] and not scheduled_time: + if self.cleaned_data.get('_interval') and not scheduled_time: self.cleaned_data['_schedule_at'] = local_now() return self.cleaned_data diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index c7de9fd6a..8f3af2a09 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -83,6 +83,7 @@ class Report(object): } """ description = None + scheduling_enabled = True job_timeout = None def __init__(self): diff --git a/netbox/extras/views.py b/netbox/extras/views.py index a819d1287..286ec76cd 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -876,7 +876,7 @@ class ReportView(ContentTypePermissionRequiredMixin, View): return render(request, 'extras/report.html', { 'module': module, 'report': report, - 'form': ReportForm(), + 'form': ReportForm(scheduling_enabled=report.scheduling_enabled), }) def post(self, request, module, name): @@ -885,7 +885,7 @@ class ReportView(ContentTypePermissionRequiredMixin, View): module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path__startswith=module) report = module.reports[name]() - form = ReportForm(request.POST) + form = ReportForm(request.POST, scheduling_enabled=report.scheduling_enabled) if form.is_valid():