@@ -490,6 +490,69 @@ def get(self, request, *args, **kwargs):
490490 return response
491491
492492
493+ class ExportJSONMixin :
494+ """
495+ Add the ability to export the current filtered QuerySet of a `FilterView`
496+ into JSON format.
497+ """
498+
499+ export_json_query_param = "export_json"
500+
501+ def get_export_json_queryset (self ):
502+ return self .filterset .qs
503+
504+ def get_export_json_filename (self ):
505+ return f"{ self .project .name } _{ self .model ._meta .model_name } .json"
506+
507+ def get_filtered_files (self , queryset ):
508+ from scanpipe .api .serializers import CodebaseResourceSerializer
509+ yield from self .encode_queryset (queryset , CodebaseResourceSerializer )
510+
511+ def export_json_file_response (self ):
512+ queryset = self .get_export_json_queryset ()
513+
514+ output_file = io .BytesIO ()
515+
516+ for chunk in self .get_filtered_files (queryset ):
517+ output_file .write (chunk .encode ("utf-8" ))
518+ output_file .seek (0 )
519+
520+ return FileResponse (
521+ output_file ,
522+ as_attachment = True ,
523+ filename = self .get_export_json_filename (),
524+ content_type = "application/json" ,
525+ )
526+
527+ def encode_queryset (self , queryset , serializer_class ):
528+ for obj in queryset .iterator (chunk_size = 2000 ):
529+
530+ serialized_obj = serializer_class (obj )
531+ data = serialized_obj .data
532+
533+ for field in output .JSON_EXCLUDE_FIELDS :
534+ data .pop (field , None )
535+
536+ yield json .dumps (data , indent = 2 )
537+
538+ def get_context_data (self , ** kwargs ):
539+ context = super ().get_context_data (** kwargs )
540+
541+ query_dict = self .request .GET .copy ()
542+ query_dict [self .export_json_query_param ] = True
543+ context ["export_json_url_query" ] = query_dict .urlencode ()
544+
545+ return context
546+
547+ def get (self , request , * args , ** kwargs ):
548+ response = super ().get (request , * args , ** kwargs )
549+
550+ if request .GET .get (self .export_json_query_param ):
551+ return self .export_json_file_response ()
552+
553+ return response
554+
555+
493556class FormAjaxMixin :
494557 def is_xhr (self ):
495558 return self .request .META .get ("HTTP_X_REQUESTED_WITH" ) == "XMLHttpRequest"
@@ -1511,6 +1574,7 @@ class CodebaseResourceListView(
15111574 ProjectRelatedViewMixin ,
15121575 TableColumnsMixin ,
15131576 ExportXLSXMixin ,
1577+ ExportJSONMixin ,
15141578 PaginatedFilterView ,
15151579):
15161580 model = CodebaseResource
@@ -1584,6 +1648,7 @@ class DiscoveredPackageListView(
15841648 ProjectRelatedViewMixin ,
15851649 TableColumnsMixin ,
15861650 ExportXLSXMixin ,
1651+ ExportJSONMixin ,
15871652 PaginatedFilterView ,
15881653):
15891654 model = DiscoveredPackage
@@ -1640,6 +1705,7 @@ class DiscoveredDependencyListView(
16401705 ProjectRelatedViewMixin ,
16411706 TableColumnsMixin ,
16421707 ExportXLSXMixin ,
1708+ ExportJSONMixin ,
16431709 PaginatedFilterView ,
16441710):
16451711 model = DiscoveredDependency
@@ -1709,6 +1775,7 @@ class ProjectMessageListView(
17091775 ProjectRelatedViewMixin ,
17101776 TableColumnsMixin ,
17111777 ExportXLSXMixin ,
1778+ ExportJSONMixin ,
17121779 FilterView ,
17131780):
17141781 model = ProjectMessage
@@ -1733,6 +1800,7 @@ class CodebaseRelationListView(
17331800 PrefetchRelatedViewMixin ,
17341801 TableColumnsMixin ,
17351802 ExportXLSXMixin ,
1803+ ExportJSONMixin ,
17361804 PaginatedFilterView ,
17371805):
17381806 model = CodebaseRelation
0 commit comments