diff --git a/netbox/core/choices.py b/netbox/core/choices.py index b392afc1e..2b3d90a42 100644 --- a/netbox/core/choices.py +++ b/netbox/core/choices.py @@ -33,3 +33,17 @@ class DataSourceStatusChoices(ChoiceSet): (COMPLETED, _('Completed'), 'green'), (FAILED, _('Failed'), 'red'), ) + + +# +# Managed files +# + +class ManagedFileRootPathChoices(ChoiceSet): + SCRIPTS = 'scripts' # settings.SCRIPTS_ROOT + REPORTS = 'reports' # settings.REPORTS_ROOT + + CHOICES = ( + (SCRIPTS, _('Scripts')), + (REPORTS, _('Reports')), + ) diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py index 10c9e63f3..99e2786ef 100644 --- a/netbox/core/forms/model_forms.py +++ b/netbox/core/forms/model_forms.py @@ -96,6 +96,8 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm): 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") + if not self.cleaned_data.get('upload_file') and not self.cleaned_data.get('data_file'): + raise forms.ValidationError("Must upload a file or select a data file to sync") return self.cleaned_data diff --git a/netbox/core/models/files.py b/netbox/core/models/files.py index a4bbdfeb6..a725ea0ac 100644 --- a/netbox/core/models/files.py +++ b/netbox/core/models/files.py @@ -6,6 +6,7 @@ from django.db import models from django.urls import reverse from django.utils.translation import gettext as _ +from ..choices import ManagedFileRootPathChoices from netbox.models.features import SyncedDataMixin from utilities.querysets import RestrictedQuerySet @@ -15,15 +16,11 @@ __all__ = ( logger = logging.getLogger('netbox.core.files') -ROOT_PATH_CHOICES = ( - ('scripts', 'Scripts Root'), - ('reports', 'Reports Root'), -) - class ManagedFile(SyncedDataMixin, models.Model): """ - Database representation for a file on disk. + Database representation for a file on disk. This class is typically wrapped by a proxy class (e.g. ScriptModule) + to provide additional functionality. """ created = models.DateTimeField( auto_now_add=True @@ -35,7 +32,7 @@ class ManagedFile(SyncedDataMixin, models.Model): ) file_root = models.CharField( max_length=1000, - choices=ROOT_PATH_CHOICES + choices=ManagedFileRootPathChoices ) file_path = models.FilePathField( editable=False, diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 0990ecd8c..6d9f78001 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -17,10 +17,6 @@ WEBHOOK_EVENT_TYPES = { EVENT_JOB_END: 'job_ended', } -# Managed files -REPORTS_ROOT_NAME = 'reports' -SCRIPTS_ROOT_NAME = 'scripts' - # Dashboard DEFAULT_DASHBOARD = [ { diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 685614b2f..f1e70190b 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -5,6 +5,7 @@ import uuid from functools import cached_property from pkgutil import ModuleInfo, get_importer +import django_rq from django.conf import settings from django.contrib import admin from django.contrib.auth.models import User @@ -20,12 +21,12 @@ from django.utils import timezone from django.utils.formats import date_format from django.utils.translation import gettext as _ from rest_framework.utils.encoders import JSONEncoder -import django_rq +from core.choices import ManagedFileRootPathChoices from core.models import ManagedFile from extras.choices import * -from extras.constants import * from extras.conditions import ConditionSet +from extras.constants import * from extras.utils import FeatureQuery, image_upload, is_report, is_script from netbox.config import get_config from netbox.constants import RQ_QUEUE_DEFAULT @@ -853,7 +854,7 @@ class Script(JobResultsMixin, WebhooksMixin, models.Model): class ScriptModuleManager(models.Manager.from_queryset(RestrictedQuerySet)): def get_queryset(self): - return super().get_queryset().filter(file_root='scripts') + return super().get_queryset().filter(file_root=ManagedFileRootPathChoices.SCRIPTS) class ScriptModule(PythonModuleMixin, ManagedFile): @@ -888,7 +889,7 @@ class ScriptModule(PythonModuleMixin, ManagedFile): return scripts def save(self, *args, **kwargs): - self.file_root = SCRIPTS_ROOT_NAME + self.file_root = ManagedFileRootPathChoices.SCRIPTS return super().save(*args, **kwargs) @@ -907,7 +908,7 @@ class Report(JobResultsMixin, WebhooksMixin, models.Model): class ReportModuleManager(models.Manager.from_queryset(RestrictedQuerySet)): def get_queryset(self): - return super().get_queryset().filter(file_root='reports') + return super().get_queryset().filter(file_root=ManagedFileRootPathChoices.REPORTS) class ReportModule(PythonModuleMixin, ManagedFile): @@ -942,5 +943,5 @@ class ReportModule(PythonModuleMixin, ManagedFile): return reports def save(self, *args, **kwargs): - self.file_root = REPORTS_ROOT_NAME + self.file_root = ManagedFileRootPathChoices.REPORTS return super().save(*args, **kwargs) diff --git a/netbox/extras/utils.py b/netbox/extras/utils.py index 818c6e720..23892e098 100644 --- a/netbox/extras/utils.py +++ b/netbox/extras/utils.py @@ -1,13 +1,9 @@ -import threading - from django.db.models import Q from django.utils.deconstruct import deconstructible from taggit.managers import _TaggableManager from netbox.registry import registry -lock = threading.Lock() - def is_taggable(obj): """ diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 23b46530b..2ab354bab 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -7,6 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.views.generic import View +from core.choices import ManagedFileRootPathChoices from core.forms import ManagedFileForm from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm from extras.dashboard.utils import get_widget_class @@ -19,7 +20,6 @@ from utilities.utils import copy_safe_request, count_related, get_viewname, norm from utilities.views import ContentTypePermissionRequiredMixin, register_model_view from . import filtersets, forms, tables from .choices import JobResultStatusChoices -from .constants import SCRIPTS_ROOT_NAME, REPORTS_ROOT_NAME from .forms.reports import ReportForm from .models import * from .reports import get_report, run_report @@ -798,7 +798,7 @@ class ReportModuleCreateView(generic.ObjectEditView): form = ManagedFileForm def alter_object(self, obj, *args, **kwargs): - obj.file_root = REPORTS_ROOT_NAME + obj.file_root = ManagedFileRootPathChoices.REPORTS return obj @@ -937,7 +937,7 @@ class ScriptModuleCreateView(generic.ObjectEditView): form = ManagedFileForm def alter_object(self, obj, *args, **kwargs): - obj.file_root = SCRIPTS_ROOT_NAME + obj.file_root = ManagedFileRootPathChoices.SCRIPTS return obj