From a128b7de5530df219ebc26e14859c660d58a3039 Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 30 Mar 2026 15:41:31 -0700 Subject: [PATCH] Run source/file conflict checks before super().validate() / full_clean() super().validate() calls full_clean() on the model instance, which raises a unique-constraint error for (file_root, file_path) when file_path is empty (e.g. data_source-only requests). Move the conflict guards above the super() call so they produce clear, actionable error messages before full_clean() has a chance to surface confusing database-level errors. Co-Authored-By: Claude Sonnet 4.6 --- netbox/extras/api/serializers_/scripts.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/netbox/extras/api/serializers_/scripts.py b/netbox/extras/api/serializers_/scripts.py index d13a9aa08..8229b1025 100644 --- a/netbox/extras/api/serializers_/scripts.py +++ b/netbox/extras/api/serializers_/scripts.py @@ -37,15 +37,11 @@ class ScriptModuleSerializer(ValidatedModelSerializer): def validate(self, data): upload_file = data.pop('upload_file', None) - # ScriptModule.save() sets file_root; inject it here so full_clean() succeeds - data['file_root'] = ManagedFileRootPathChoices.SCRIPTS - data = super().validate(data) - data.pop('file_root', None) - if upload_file is not None: - data['upload_file'] = upload_file # For multipart requests, nested serializer fields (data_source, data_file) are # silently dropped by DRF's HTML parser, so also check initial_data for raw values. + # These checks must run before super().validate() calls full_clean(), which would + # otherwise surface confusing unique-constraint errors for empty file_path values. has_data_file = data.get('data_file') or self.initial_data.get('data_file') has_data_source = data.get('data_source') or self.initial_data.get('data_source') @@ -66,6 +62,13 @@ class ScriptModuleSerializer(ValidatedModelSerializer): _("Must upload a file or select a data file to sync") ) + # ScriptModule.save() sets file_root; inject it here so full_clean() succeeds + data['file_root'] = ManagedFileRootPathChoices.SCRIPTS + data = super().validate(data) + data.pop('file_root', None) + if upload_file is not None: + data['upload_file'] = upload_file + return data def _save_upload(self, upload_file, validated_data):