11from django .utils .functional import lazy
22
3+ from drf_spectacular .extensions import OpenApiSerializerExtension
4+ from drf_spectacular .plumbing import force_instance
35from drf_spectacular .types import OpenApiTypes
46from drf_spectacular .utils import extend_schema_field
57from rest_framework import serializers
@@ -42,6 +44,8 @@ class Meta:
4244 "progress_percentage" ,
4345 "state" ,
4446 "human_readable_error" ,
47+ "created_on" ,
48+ "updated_on" ,
4549 )
4650 extra_kwargs = {
4751 "id" : {"read_only" : True },
@@ -63,9 +67,31 @@ class Meta:
6367 fields = ("user_id" , "type" )
6468
6569
70+ class JobTypeFiltersSerializer (serializers .Serializer ):
71+ """
72+ Base serializer for job type-specific filters. This serves as the base class
73+ for all job type filter serializers and uses 'type' as a discriminator field.
74+ """
75+
76+ type = serializers .ChoiceField (
77+ choices = lazy (job_type_registry .get_types , list )(),
78+ required = True ,
79+ help_text = "The type of job to filter for. Determines which additional filter fields are available." ,
80+ )
81+
82+
6683class ListJobQuerySerializer (serializers .Serializer ):
6784 states = serializers .CharField (required = False )
6885 job_ids = serializers .CharField (required = False )
86+ type = serializers .ChoiceField (
87+ choices = lazy (job_type_registry .get_types , list )(),
88+ required = False ,
89+ help_text = "The type of job to filter for. Determines which additional filter fields are available." ,
90+ )
91+ offset = serializers .IntegerField (required = False , min_value = 0 )
92+ limit = serializers .IntegerField (
93+ required = False , min_value = 1 , max_value = 100 , default = 20
94+ )
6995
7096 def validate_states (self , value ):
7197 if not value :
@@ -95,3 +121,95 @@ def validate_job_ids(self, value):
95121 f"Job id { job_id } is not a valid integer."
96122 )
97123 return validated_job_ids
124+
125+ def validate (self , attrs ):
126+ job_type_name = attrs .get ("type" )
127+
128+ # Collect type-specific filters in a separate dict
129+ type_filters = {}
130+
131+ if job_type_name :
132+ job_type = job_type_registry .get (job_type_name )
133+ filters_serializer_class = job_type .get_filters_serializer ()
134+
135+ if filters_serializer_class :
136+ filters_data = {}
137+
138+ # Add any type-specific fields from initial_data
139+ filters_serializer = filters_serializer_class ()
140+
141+ for field_name in filters_serializer .fields .keys ():
142+ if field_name in self .initial_data :
143+ filters_data [field_name ] = self .initial_data [field_name ]
144+
145+ # Validate using the type-specific serializer
146+ filters_serializer = filters_serializer_class (data = filters_data )
147+ if filters_serializer .is_valid ():
148+ for field_name , value in filters_serializer .validated_data .items ():
149+ # if the field starts with the job_type name to disambiguate
150+ # the query parameter, remove it
151+ field_key = field_name
152+ if field_name .startswith (f"{ job_type .type } _" ):
153+ field_key = field_name [len (job_type .type ) + 1 :]
154+ type_filters [field_key ] = value
155+ else :
156+ raise serializers .ValidationError (filters_serializer .errors )
157+
158+ # Add type_filters dict to attrs for easy access in the view
159+ attrs ["type_filters" ] = type_filters
160+ attrs ["job_type_name" ] = job_type_name
161+
162+ return attrs
163+
164+
165+ class ListJobQuerySerializerExtension (OpenApiSerializerExtension ):
166+ """
167+ Custom OpenAPI serializer extension that dynamically adds type-specific filter
168+ fields to the ListJobQuerySerializer based on the job registry. This creates a flat
169+ parameter list where type-specific fields appear when the corresponding type is
170+ selected, since it's not possible to use a discriminator in query parameters.
171+ """
172+
173+ target_class = "baserow.api.jobs.serializers.ListJobQuerySerializer"
174+
175+ def map_serializer (self , auto_schema , direction ):
176+ """
177+ Generate the schema by adding all type-specific fields from job filters
178+ serializers to the base ListJobQuerySerializer properties.
179+ """
180+
181+ schema = auto_schema ._map_serializer (
182+ self .target , direction , bypass_extensions = True
183+ )
184+
185+ properties = schema .get ("properties" , {})
186+ base_field_names = set (ListJobQuerySerializer ().fields .keys ())
187+
188+ # Collect all type-specific fields from job registry
189+ for job_type in job_type_registry .get_all ():
190+ filters_serializer_class = job_type .get_filters_serializer ()
191+ if (
192+ not filters_serializer_class
193+ or filters_serializer_class == JobTypeFiltersSerializer
194+ ):
195+ continue
196+
197+ serializer = force_instance (filters_serializer_class )
198+
199+ for field_name , field in serializer .fields .items ():
200+ # Skip base fields and the type field
201+ if field_name in base_field_names or field_name == "type" :
202+ continue
203+
204+ field_schema = auto_schema ._map_serializer_field (field , direction )
205+
206+ help_text = field_schema .get ("description" , "" )
207+ field_schema [
208+ "description"
209+ ] = f"**[Only for type='{ job_type .type } ']** { help_text } "
210+
211+ if field_name not in properties :
212+ properties [field_name ] = field_schema
213+
214+ schema ["properties" ] = properties
215+ return schema
0 commit comments