2121# Visit https://github.com/aboutcode-org/scancode.io for support and download.
2222
2323import json
24+ import logging
2425
2526from django .apps import apps
2627from django .core .exceptions import ObjectDoesNotExist
5859from scanpipe .pipes .compliance import get_project_compliance_alerts
5960from scanpipe .views import project_results_json_response
6061
62+ logger = logging .getLogger (__name__ )
6163scanpipe_app = apps .get_app_config ("scanpipe" )
6264
6365
66+ class ErrorResponse (Response ):
67+ def __init__ (self , message , status_code = status .HTTP_400_BAD_REQUEST , ** kwargs ):
68+ # If message is already a dict, use it as-is
69+ if isinstance (message , dict ):
70+ data = message
71+ else :
72+ # Otherwise, wrap string in {"status": message}
73+ data = {"status" : message }
74+
75+ super ().__init__ (data = data , status = status_code , ** kwargs )
76+
77+
6478class ProjectFilterSet (django_filters .rest_framework .FilterSet ):
6579 name = django_filters .CharFilter ()
6680 name__contains = django_filters .CharFilter (
@@ -176,8 +190,7 @@ def results_download(self, request, *args, **kwargs):
176190 elif format == "all_outputs" :
177191 output_file = output .to_all_outputs (project )
178192 else :
179- message = {"status" : f"Format { format } not supported." }
180- return Response (message , status = status .HTTP_400_BAD_REQUEST )
193+ return ErrorResponse (f"Format { format } not supported." )
181194
182195 filename = output .safe_filename (f"scancodeio_{ project .name } _{ output_file .name } " )
183196 return FileResponse (
@@ -196,8 +209,7 @@ def summary(self, request, *args, **kwargs):
196209 summary_file = project .get_latest_output (filename = "summary" )
197210
198211 if not summary_file :
199- message = {"error" : "Summary file not available" }
200- return Response (message , status = status .HTTP_400_BAD_REQUEST )
212+ return ErrorResponse ({"error" : "Summary file not available" })
201213
202214 summary_json = json .loads (summary_file .read_text ())
203215 return Response (summary_json )
@@ -224,14 +236,14 @@ def report(self, request, *args, **kwargs):
224236 ),
225237 "choices" : ", " .join (model_choices ),
226238 }
227- return Response (message , status = status . HTTP_400_BAD_REQUEST )
239+ return ErrorResponse (message )
228240
229241 if model not in model_choices :
230242 message = {
231243 "error" : f"{ model } is not on of the valid choices" ,
232244 "choices" : ", " .join (model_choices ),
233245 }
234- return Response (message , status = status . HTTP_400_BAD_REQUEST )
246+ return ErrorResponse (message )
235247
236248 output_file = output .get_xlsx_report (
237249 project_qs = project_qs ,
@@ -254,8 +266,7 @@ def get_filtered_response(
254266 """
255267 filterset = filterset_class (data = request .GET , queryset = queryset )
256268 if not filterset .is_valid ():
257- message = {"errors" : filterset .errors }
258- return Response (message , status = status .HTTP_400_BAD_REQUEST )
269+ return ErrorResponse ({"errors" : filterset .errors })
259270
260271 queryset = filterset .qs
261272 paginated_qs = self .paginate_queryset (queryset )
@@ -313,14 +324,12 @@ def file_content(self, request, *args, **kwargs):
313324 try :
314325 codebase_resource = codebase_resources .get (path = path )
315326 except ObjectDoesNotExist :
316- message = {"status" : "Resource not found. Use ?path=<resource_path>" }
317- return Response (message , status = status .HTTP_400_BAD_REQUEST )
327+ return ErrorResponse ("Resource not found. Use ?path=<resource_path>" )
318328
319329 try :
320330 file_content = codebase_resource .file_content
321331 except OSError :
322- message = {"status" : "File not available" }
323- return Response (message , status = status .HTTP_400_BAD_REQUEST )
332+ return ErrorResponse ("File not available" )
324333
325334 return Response ({"file_content" : file_content })
326335
@@ -339,32 +348,29 @@ def add_pipeline(self, request, *args, **kwargs):
339348 {"status" : "Pipeline added." }, status = status .HTTP_201_CREATED
340349 )
341350
342- message = {"status" : f"{ pipeline } is not a valid pipeline." }
343- return Response (message , status = status .HTTP_400_BAD_REQUEST )
351+ return ErrorResponse (f"{ pipeline } is not a valid pipeline." )
344352
345353 message = {
346354 "status" : "Pipeline required." ,
347355 "pipelines" : list (scanpipe_app .pipelines .keys ()),
348356 }
349- return Response (message , status = status . HTTP_400_BAD_REQUEST )
357+ return ErrorResponse (message )
350358
351359 @action (detail = True , methods = ["get" , "post" ])
352360 def add_input (self , request , * args , ** kwargs ):
353361 project = self .get_object ()
354362
355363 if not project .can_change_inputs :
356- message = {
357- "status" : "Cannot add inputs once a pipeline has started to execute."
358- }
359- return Response (message , status = status .HTTP_400_BAD_REQUEST )
364+ return ErrorResponse (
365+ "Cannot add inputs once a pipeline has started to execute."
366+ )
360367
361368 upload_file = request .data .get ("upload_file" )
362369 upload_file_tag = request .data .get ("upload_file_tag" , "" )
363370 input_urls = request .data .get ("input_urls" , [])
364371
365372 if not (upload_file or input_urls ):
366- message = {"status" : "upload_file or input_urls required." }
367- return Response (message , status = status .HTTP_400_BAD_REQUEST )
373+ return ErrorResponse ("upload_file or input_urls required." )
368374
369375 if upload_file :
370376 project .add_upload (upload_file , tag = upload_file_tag )
@@ -396,13 +402,13 @@ def add_webhook(self, request, *args, **kwargs):
396402 )
397403
398404 # Return validation errors
399- return Response (serializer .errors , status = status . HTTP_400_BAD_REQUEST )
405+ return ErrorResponse (serializer .errors )
400406
401407 def destroy (self , request , * args , ** kwargs ):
402408 try :
403409 return super ().destroy (request , * args , ** kwargs )
404- except RunInProgressError as error :
405- return Response ({ "status" : str ( error )}, status = status . HTTP_400_BAD_REQUEST )
410+ except RunInProgressError :
411+ return ErrorResponse ( "Cannot delete project while a run is in progress." )
406412
407413 @action (detail = True , methods = ["get" , "post" ])
408414 def archive (self , request , * args , ** kwargs ):
@@ -423,10 +429,10 @@ def archive(self, request, *args, **kwargs):
423429 remove_codebase = request .data .get ("remove_codebase" ),
424430 remove_output = request .data .get ("remove_output" ),
425431 )
426- except RunInProgressError as error :
427- return Response ( error , status = status . HTTP_400_BAD_REQUEST )
428- else :
429- return Response ({"status" : f"The project { project } has been archived." })
432+ except RunInProgressError :
433+ return ErrorResponse ( "Cannot archive project while a run is in progress." )
434+
435+ return Response ({"status" : f"The project { project } has been archived." })
430436
431437 @action (detail = True , methods = ["get" , "post" ])
432438 def reset (self , request , * args , ** kwargs ):
@@ -437,13 +443,15 @@ def reset(self, request, *args, **kwargs):
437443 return Response ({"status" : message })
438444
439445 try :
440- project .reset (keep_input = True )
441- except RunInProgressError as error :
442- return Response (error , status = status .HTTP_400_BAD_REQUEST )
443- else :
444- message = (
445- f"All data, except inputs, for the { project } project have been removed."
446+ project .reset (
447+ keep_input = request .data .get ("keep_input" , True ),
448+ restore_pipelines = request .data .get ("restore_pipelines" , False ),
449+ execute_now = request .data .get ("execute_now" , False ),
446450 )
451+ except RunInProgressError :
452+ return ErrorResponse ("Cannot reset project while a run is in progress." )
453+ else :
454+ message = f"The { project } project has been reset."
447455 return Response ({"status" : message })
448456
449457 @action (detail = True , methods = ["get" ])
@@ -455,8 +463,7 @@ def outputs(self, request, *args, **kwargs):
455463 if file_path .exists ():
456464 return FileResponse (file_path .open ("rb" ))
457465
458- message = {"status" : f"Output file { filename } not found" }
459- return Response (message , status = status .HTTP_400_BAD_REQUEST )
466+ return ErrorResponse (f"Output file { filename } not found" )
460467
461468 action_url = self .reverse_action (self .outputs .url_name , args = [project .pk ])
462469 output_data = [
@@ -531,14 +538,11 @@ class RunViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
531538 def start_pipeline (self , request , * args , ** kwargs ):
532539 run = self .get_object ()
533540 if run .task_end_date :
534- message = {"status" : "Pipeline already executed." }
535- return Response (message , status = status .HTTP_400_BAD_REQUEST )
541+ return ErrorResponse ("Pipeline already executed." )
536542 elif run .task_start_date :
537- message = {"status" : "Pipeline already started." }
538- return Response (message , status = status .HTTP_400_BAD_REQUEST )
543+ return ErrorResponse ("Pipeline already started." )
539544 elif run .task_id :
540- message = {"status" : "Pipeline already queued." }
541- return Response (message , status = status .HTTP_400_BAD_REQUEST )
545+ return ErrorResponse ("Pipeline already queued." )
542546
543547 transaction .on_commit (run .start )
544548
@@ -549,8 +553,7 @@ def stop_pipeline(self, request, *args, **kwargs):
549553 run = self .get_object ()
550554
551555 if run .status != run .Status .RUNNING :
552- message = {"status" : "Pipeline is not running." }
553- return Response (message , status = status .HTTP_400_BAD_REQUEST )
556+ return ErrorResponse ("Pipeline is not running." )
554557
555558 run .stop_task ()
556559 return Response ({"status" : f"Pipeline { run .pipeline_name } stopped." })
@@ -560,8 +563,7 @@ def delete_pipeline(self, request, *args, **kwargs):
560563 run = self .get_object ()
561564
562565 if run .status not in [run .Status .NOT_STARTED , run .Status .QUEUED ]:
563- message = {"status" : "Only non started or queued pipelines can be deleted." }
564- return Response (message , status = status .HTTP_400_BAD_REQUEST )
566+ return ErrorResponse ("Only non started or queued pipelines can be deleted." )
565567
566568 run .delete_task ()
567569 return Response ({"status" : f"Pipeline { run .pipeline_name } deleted." })
0 commit comments