@@ -498,6 +498,69 @@ def get(self, request, *args, **kwargs):
498498 return response
499499
500500
501+ class ExportJSONMixin :
502+ """
503+ Add the ability to export the current filtered QuerySet of a `FilterView`
504+ into JSON format.
505+ """
506+
507+ export_json_query_param = "export_json"
508+
509+ def get_export_json_queryset (self ):
510+ return self .filterset .qs
511+
512+ def get_export_json_filename (self ):
513+ return f"{ self .project .name } _{ self .model ._meta .model_name } .json"
514+
515+ def get_filtered_files (self , queryset ):
516+ from scanpipe .api .serializers import CodebaseResourceSerializer
517+
518+ yield from self .encode_queryset (queryset , CodebaseResourceSerializer )
519+
520+ def export_json_file_response (self ):
521+ queryset = self .get_export_json_queryset ()
522+
523+ output_file = io .BytesIO ()
524+
525+ for chunk in self .get_filtered_files (queryset ):
526+ output_file .write (chunk .encode ("utf-8" ))
527+ output_file .seek (0 )
528+
529+ return FileResponse (
530+ output_file ,
531+ as_attachment = True ,
532+ filename = self .get_export_json_filename (),
533+ content_type = "application/json" ,
534+ )
535+
536+ def encode_queryset (self , queryset , serializer_class ):
537+ for obj in queryset .iterator (chunk_size = 2000 ):
538+ serialized_obj = serializer_class (obj )
539+ data = serialized_obj .data
540+
541+ for field in output .JSON_EXCLUDE_FIELDS :
542+ data .pop (field , None )
543+
544+ yield json .dumps (data , indent = 2 )
545+
546+ def get_context_data (self , ** kwargs ):
547+ context = super ().get_context_data (** kwargs )
548+
549+ query_dict = self .request .GET .copy ()
550+ query_dict [self .export_json_query_param ] = True
551+ context ["export_json_url_query" ] = query_dict .urlencode ()
552+
553+ return context
554+
555+ def get (self , request , * args , ** kwargs ):
556+ response = super ().get (request , * args , ** kwargs )
557+
558+ if request .GET .get (self .export_json_query_param ):
559+ return self .export_json_file_response ()
560+
561+ return response
562+
563+
501564class FormAjaxMixin :
502565 def is_xhr (self ):
503566 return self .request .META .get ("HTTP_X_REQUESTED_WITH" ) == "XMLHttpRequest"
@@ -1542,6 +1605,7 @@ class CodebaseResourceListView(
15421605 ProjectRelatedViewMixin ,
15431606 TableColumnsMixin ,
15441607 ExportXLSXMixin ,
1608+ ExportJSONMixin ,
15451609 PaginatedFilterView ,
15461610):
15471611 model = CodebaseResource
@@ -1615,6 +1679,7 @@ class DiscoveredPackageListView(
16151679 ProjectRelatedViewMixin ,
16161680 TableColumnsMixin ,
16171681 ExportXLSXMixin ,
1682+ ExportJSONMixin ,
16181683 PaginatedFilterView ,
16191684):
16201685 model = DiscoveredPackage
@@ -1671,6 +1736,7 @@ class DiscoveredDependencyListView(
16711736 ProjectRelatedViewMixin ,
16721737 TableColumnsMixin ,
16731738 ExportXLSXMixin ,
1739+ ExportJSONMixin ,
16741740 PaginatedFilterView ,
16751741):
16761742 model = DiscoveredDependency
@@ -1740,6 +1806,7 @@ class ProjectMessageListView(
17401806 ProjectRelatedViewMixin ,
17411807 TableColumnsMixin ,
17421808 ExportXLSXMixin ,
1809+ ExportJSONMixin ,
17431810 FilterView ,
17441811):
17451812 model = ProjectMessage
@@ -1764,6 +1831,7 @@ class CodebaseRelationListView(
17641831 PrefetchRelatedViewMixin ,
17651832 TableColumnsMixin ,
17661833 ExportXLSXMixin ,
1834+ ExportJSONMixin ,
17671835 PaginatedFilterView ,
17681836):
17691837 model = CodebaseRelation
0 commit comments