Add add/delete views for reports & scripts

This commit is contained in:
jeremystretch 2023-03-23 17:01:12 -04:00
parent 1daac5f781
commit b2bba7fc40
11 changed files with 126 additions and 37 deletions

View File

@ -3,12 +3,14 @@ import copy
from django import forms from django import forms
from core.models import * from core.models import *
from extras.forms.mixins import SyncedDataMixin
from netbox.forms import NetBoxModelForm from netbox.forms import NetBoxModelForm
from netbox.registry import registry from netbox.registry import registry
from utilities.forms import BootstrapMixin, CommentField, get_field_value from utilities.forms import CommentField, get_field_value
__all__ = ( __all__ = (
'DataSourceForm', 'DataSourceForm',
'ManagedFileForm',
) )
@ -73,3 +75,26 @@ class DataSourceForm(NetBoxModelForm):
self.instance.parameters = parameters self.instance.parameters = parameters
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
class ManagedFileForm(SyncedDataMixin, NetBoxModelForm):
upload_file = forms.FileField(
required=False
)
fieldsets = (
('File Upload', ('upload_file',)),
('Data Source', ('data_source', 'data_file')),
)
class Meta:
model = ManagedFile
fields = ('data_source', 'data_file')
def clean(self):
super().clean()
if self.cleaned_data.get('upload_file') and self.cleaned_data.get('data_file'):
raise forms.ValidationError("Cannot upload a file and sync from an existing file")
return self.cleaned_data

View File

@ -315,3 +315,14 @@ class DataFile(models.Model):
self.data = f.read() self.data = f.read()
return is_modified return is_modified
def write_to_disk(self, path, overwrite=False):
"""
Write the object's data to disk at the specified path
"""
# Check whether file already exists
if os.path.isfile(path) and not overwrite:
raise FileExistsError()
with open(path, 'wb+') as new_file:
new_file.write(self.data)

View File

@ -71,3 +71,14 @@ class ManagedFile(SyncedDataMixin, models.Model):
'scripts': settings.SCRIPTS_ROOT, 'scripts': settings.SCRIPTS_ROOT,
'reports': settings.REPORTS_ROOT, 'reports': settings.REPORTS_ROOT,
}[self.file_root] }[self.file_root]
def sync_data(self):
if self.data_file:
self.file_path = self.data_path
self.data_file.write_to_disk(self.full_path, overwrite=True)
def delete(self, *args, **kwargs):
# Delete file from disk
os.remove(self.full_path)
return super().delete(*args, **kwargs)

View File

@ -21,9 +21,6 @@ urlpatterns = (
# Managed files # Managed files
path('files/', views.ManagedFileListView.as_view(), name='managedfile_list'), path('files/', views.ManagedFileListView.as_view(), name='managedfile_list'),
# path('files/add/', views.ManagedFileEditView.as_view(), name='managedfile_add'),
# path('files/edit/', views.ManagedFileBulkEditView.as_view(), name='managedfile_bulk_edit'),
# path('files/delete/', views.ManagedFileBulkDeleteView.as_view(), name='managedfile_bulk_delete'),
path('files/<int:pk>/', include(get_model_urls('core', 'managedfile'))), path('files/<int:pk>/', include(get_model_urls('core', 'managedfile'))),
) )

View File

@ -128,35 +128,9 @@ class DataFileBulkDeleteView(generic.BulkDeleteView):
class ManagedFileListView(generic.ObjectListView): class ManagedFileListView(generic.ObjectListView):
queryset = ManagedFile.objects.all() queryset = ManagedFile.objects.all()
# filterset = filtersets.ManagedFileFilterSet
# filterset_form = forms.ManagedFileFilterForm
table = tables.ManagedFileTable table = tables.ManagedFileTable
@register_model_view(ManagedFile) @register_model_view(ManagedFile)
class ManagedFileView(generic.ObjectView): class ManagedFileView(generic.ObjectView):
queryset = ManagedFile.objects.all() queryset = ManagedFile.objects.all()
# @register_model_view(ManagedFile, 'edit')
# class ManagedFileEditView(generic.ObjectEditView):
# queryset = ManagedFile.objects.all()
# form = forms.ManagedFileForm
@register_model_view(ManagedFile, 'delete')
class ManagedFileDeleteView(generic.ObjectDeleteView):
queryset = ManagedFile.objects.all()
# class ManagedFileBulkEditView(generic.BulkEditView):
# queryset = ManagedFile.objects.all()
# # filterset = filtersets.ManagedFileFilterSet
# table = tables.ManagedFileTable
# form = forms.ManagedFileBulkEditForm
# class ManagedFileBulkDeleteView(generic.BulkDeleteView):
# queryset = ManagedFile.objects.all()
# # filterset = filtersets.ManagedFileFilterSet
# table = tables.ManagedFileTable

View File

@ -17,6 +17,10 @@ WEBHOOK_EVENT_TYPES = {
EVENT_JOB_END: 'job_ended', EVENT_JOB_END: 'job_ended',
} }
# Managed files
REPORTS_ROOT_NAME = 'reports'
SCRIPTS_ROOT_NAME = 'scripts'
# Dashboard # Dashboard
DEFAULT_DASHBOARD = [ DEFAULT_DASHBOARD = [
{ {

View File

@ -836,7 +836,7 @@ class Script(JobResultsMixin, WebhooksMixin, models.Model):
managed = False managed = False
class ScriptModuleManager(models.Manager): class ScriptModuleManager(models.Manager.from_queryset(RestrictedQuerySet)):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(file_root='scripts') return super().get_queryset().filter(file_root='scripts')
@ -851,6 +851,10 @@ class ScriptModule(JobResultsMixin, WebhooksMixin, PythonModuleMixin, ManagedFil
class Meta: class Meta:
proxy = True proxy = True
def save(self, *args, **kwargs):
self.file_root = SCRIPTS_ROOT_NAME
return super().save(*args, **kwargs)
# #
# Reports # Reports
@ -864,7 +868,7 @@ class Report(JobResultsMixin, WebhooksMixin, models.Model):
managed = False managed = False
class ReportModuleManager(models.Manager): class ReportModuleManager(models.Manager.from_queryset(RestrictedQuerySet)):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(file_root='reports') return super().get_queryset().filter(file_root='reports')
@ -878,3 +882,7 @@ class ReportModule(JobResultsMixin, WebhooksMixin, PythonModuleMixin, ManagedFil
class Meta: class Meta:
proxy = True proxy = True
def save(self, *args, **kwargs):
self.file_root = REPORTS_ROOT_NAME
return super().save(*args, **kwargs)

View File

@ -96,17 +96,21 @@ urlpatterns = [
path('reports/', views.ReportListView.as_view(), name='report_list'), path('reports/', views.ReportListView.as_view(), name='report_list'),
path('reports/results/<int:job_result_pk>/', views.ReportResultView.as_view(), name='report_result'), path('reports/results/<int:job_result_pk>/', views.ReportResultView.as_view(), name='report_result'),
re_path(r'^reports/(?P<module>.([^.]+)).(?P<name>.(.+))/', views.ReportView.as_view(), name='report'), re_path(r'^reports/(?P<module>.([^.]+)).(?P<name>.(.+))/', views.ReportView.as_view(), name='report'),
path('reports/add/', views.ScriptModuleCreateView.as_view(), name='reportmodule_add'),
path('reports/<int:pk>/', include(get_model_urls('extras', 'reportmodule'))),
# Scripts
path('scripts/', views.ScriptListView.as_view(), name='script_list'),
path('scripts/results/<int:job_result_pk>/', views.ScriptResultView.as_view(), name='script_result'),
re_path(r'^scripts/(?P<module>.([^.]+)).(?P<name>.(.+))/', views.ScriptView.as_view(), name='script'),
path('scripts/add/', views.ScriptModuleCreateView.as_view(), name='scriptmodule_add'),
path('scripts/<int:pk>/', include(get_model_urls('extras', 'scriptmodule'))),
# Job results # Job results
path('job-results/', views.JobResultListView.as_view(), name='jobresult_list'), path('job-results/', views.JobResultListView.as_view(), name='jobresult_list'),
path('job-results/delete/', views.JobResultBulkDeleteView.as_view(), name='jobresult_bulk_delete'), path('job-results/delete/', views.JobResultBulkDeleteView.as_view(), name='jobresult_bulk_delete'),
path('job-results/<int:pk>/delete/', views.JobResultDeleteView.as_view(), name='jobresult_delete'), path('job-results/<int:pk>/delete/', views.JobResultDeleteView.as_view(), name='jobresult_delete'),
# Scripts
path('scripts/', views.ScriptListView.as_view(), name='script_list'),
path('scripts/results/<int:job_result_pk>/', views.ScriptResultView.as_view(), name='script_result'),
re_path(r'^scripts/(?P<module>.([^.]+)).(?P<name>.(.+))/', views.ScriptView.as_view(), name='script'),
# Markdown # Markdown
path('render/markdown/', views.RenderMarkdownView.as_view(), name="render_markdown") path('render/markdown/', views.RenderMarkdownView.as_view(), name="render_markdown")
] ]

View File

@ -7,6 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.views.generic import View from django.views.generic import View
from core.forms import ManagedFileForm
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
from extras.dashboard.utils import get_widget_class from extras.dashboard.utils import get_widget_class
from netbox.views import generic from netbox.views import generic
@ -18,6 +19,7 @@ from utilities.utils import copy_safe_request, count_related, get_viewname, norm
from utilities.views import ContentTypePermissionRequiredMixin, register_model_view from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
from . import filtersets, forms, tables from . import filtersets, forms, tables
from .choices import JobResultStatusChoices from .choices import JobResultStatusChoices
from .constants import SCRIPTS_ROOT_NAME, REPORTS_ROOT_NAME
from .forms.reports import ReportForm from .forms.reports import ReportForm
from .models import * from .models import *
from .reports import get_report, get_reports, run_report from .reports import get_report, get_reports, run_report
@ -790,6 +792,21 @@ class DashboardWidgetDeleteView(LoginRequiredMixin, View):
# Reports # Reports
# #
@register_model_view(ReportModule, 'edit')
class ReportModuleCreateView(generic.ObjectEditView):
queryset = ReportModule.objects.all()
form = ManagedFileForm
def alter_object(self, obj, *args, **kwargs):
obj.file_root = REPORTS_ROOT_NAME
return obj
@register_model_view(ReportModule, 'delete')
class ReportModuleDeleteView(generic.ObjectDeleteView):
queryset = ReportModule.objects.all()
class ReportListView(ContentTypePermissionRequiredMixin, View): class ReportListView(ContentTypePermissionRequiredMixin, View):
""" """
Retrieve all of the available reports from disk and the recorded JobResult (if any) for each. Retrieve all of the available reports from disk and the recorded JobResult (if any) for each.
@ -819,6 +836,7 @@ class ReportListView(ContentTypePermissionRequiredMixin, View):
ret.append((module, module_reports)) ret.append((module, module_reports))
return render(request, 'extras/report_list.html', { return render(request, 'extras/report_list.html', {
'model': ReportModule,
'reports': ret, 'reports': ret,
}) })
@ -924,6 +942,21 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
# Scripts # Scripts
# #
@register_model_view(ScriptModule, 'edit')
class ScriptModuleCreateView(generic.ObjectEditView):
queryset = ScriptModule.objects.all()
form = ManagedFileForm
def alter_object(self, obj, *args, **kwargs):
obj.file_root = SCRIPTS_ROOT_NAME
return obj
@register_model_view(ScriptModule, 'delete')
class ScriptModuleDeleteView(generic.ObjectDeleteView):
queryset = ScriptModule.objects.all()
class GetScriptMixin: class GetScriptMixin:
def _get_script(self, name, module=None): def _get_script(self, name, module=None):
if module is None: if module is None:
@ -957,6 +990,7 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
script.result = results.get(script.full_name) script.result = results.get(script.full_name)
return render(request, 'extras/script_list.html', { return render(request, 'extras/script_list.html', {
'model': ScriptModule,
'scripts': scripts, 'scripts': scripts,
}) })

View File

@ -1,5 +1,7 @@
{% extends 'base/layout.html' %} {% extends 'base/layout.html' %}
{% load buttons %}
{% load helpers %} {% load helpers %}
{% load perms %}
{% block title %}Reports{% endblock %} {% block title %}Reports{% endblock %}
@ -11,6 +13,15 @@
</ul> </ul>
{% endblock tabs %} {% endblock tabs %}
{% block controls %}
<div class="controls">
<div class="control-group">
{% block extra_controls %}{% endblock %}
{% add_button model %}
</div>
</div>
{% endblock controls %}
{% block content-wrapper %} {% block content-wrapper %}
<div class="tab-content"> <div class="tab-content">
{% if reports %} {% if reports %}

View File

@ -1,8 +1,18 @@
{% extends 'base/layout.html' %} {% extends 'base/layout.html' %}
{% load buttons %}
{% load helpers %} {% load helpers %}
{% block title %}Scripts{% endblock %} {% block title %}Scripts{% endblock %}
{% block controls %}
<div class="controls">
<div class="control-group">
{% block extra_controls %}{% endblock %}
{% add_button model %}
</div>
</div>
{% endblock controls %}
{% block tabs %} {% block tabs %}
<ul class="nav nav-tabs px-3"> <ul class="nav nav-tabs px-3">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">