This commit is contained in:
Arthur
2026-04-01 12:43:50 -07:00
parent 77bebe7eed
commit edf9b1c1d0
3 changed files with 7 additions and 23 deletions

View File

@@ -1,6 +1,10 @@
import logging
from django.core.files.storage import storages from django.core.files.storage import storages
from django.db import IntegrityError from django.db import IntegrityError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers from rest_framework import serializers
@@ -58,7 +62,7 @@ class ScriptModuleSerializer(ValidatedModelSerializer):
try: try:
storage.delete(file_path) storage.delete(file_path)
except Exception: except Exception:
pass logger.warning(f"Failed to delete orphaned script file '{file_path}' from storage.")
class ScriptSerializer(ValidatedModelSerializer): class ScriptSerializer(ValidatedModelSerializer):

View File

@@ -21,6 +21,7 @@ from netbox.api.features import SyncedDataMixin
from netbox.api.metadata import ContentTypeMetadata from netbox.api.metadata import ContentTypeMetadata
from netbox.api.renderers import TextRenderer from netbox.api.renderers import TextRenderer
from netbox.api.viewsets import BaseViewSet, NetBoxModelViewSet from netbox.api.viewsets import BaseViewSet, NetBoxModelViewSet
from netbox.api.viewsets.mixins import ObjectValidationMixin
from utilities.exceptions import RQWorkerNotRunningException from utilities.exceptions import RQWorkerNotRunningException
from utilities.request import copy_safe_request from utilities.request import copy_safe_request
@@ -264,7 +265,7 @@ class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxMo
# Scripts # Scripts
# #
class ScriptModuleViewSet(CreateModelMixin, BaseViewSet): class ScriptModuleViewSet(ObjectValidationMixin, CreateModelMixin, BaseViewSet):
queryset = ScriptModule.objects.all() queryset = ScriptModule.objects.all()
serializer_class = serializers.ScriptModuleSerializer serializer_class = serializers.ScriptModuleSerializer

View File

@@ -1433,27 +1433,6 @@ class ScriptModuleTest(APITestCase):
mock_storage.save.assert_called_once() mock_storage.save.assert_called_once()
self.assertTrue(ScriptModule.objects.filter(file_path='test_upload.py').exists()) self.assertTrue(ScriptModule.objects.filter(file_path='test_upload.py').exists())
def test_upload_script_module_duplicate_fails(self):
self.add_permissions('extras.add_scriptmodule', 'core.add_managedfile')
script_content = b"from extras.scripts import Script\nclass TestScript(Script):\n pass\n"
mock_storage = MagicMock()
mock_storage.save.return_value = 'test_upload.py'
with patch('extras.api.serializers_.scripts.storages') as mock_storages:
mock_storages.create_storage.return_value = mock_storage
mock_storages.backends = {'scripts': {}}
# First upload succeeds
upload_file = SimpleUploadedFile('test_upload.py', script_content, content_type='text/plain')
self.client.post(self.url, {'file': upload_file}, format='multipart', **self.header)
# Second upload with same name should fail
upload_file = SimpleUploadedFile('test_upload.py', script_content, content_type='text/plain')
response = self.client.post(
self.url,
{'file': upload_file},
format='multipart',
**self.header,
)
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
def test_upload_script_module_without_file_fails(self): def test_upload_script_module_without_file_fails(self):
self.add_permissions('extras.add_scriptmodule', 'core.add_managedfile') self.add_permissions('extras.add_scriptmodule', 'core.add_managedfile')
response = self.client.post(self.url, {}, format='json', **self.header) response = self.client.post(self.url, {}, format='json', **self.header)