def _build_queryset( self, serializer=None, filters=None, queryset=None, requirements=None, extra_filters=None, disable_prefetches=False, ): """Build a queryset that pulls in all data required by this request. Handles nested prefetching of related data and deferring fields at the queryset level. Arguments: serializer: An optional serializer to use a base for the queryset. If no serializer is passed, the `get_serializer` method will be used to initialize the base serializer for the viewset. filters: An optional TreeMap of nested filters. queryset: An optional base queryset. requirements: An optional TreeMap of nested requirements. """ is_root_level = False if not serializer: serializer = self.view.get_serializer() is_root_level = True queryset = self._get_queryset(queryset=queryset, serializer=serializer) model = getattr(serializer.Meta, 'model', None) if not model: return queryset prefetches = {} # build a nested Prefetch queryset # based on request parameters and serializer fields fields = serializer.fields if requirements is None: requirements = TreeMap() self._get_implicit_requirements(fields, requirements) # Implicit requirements (i.e. via `requires`) can potentially # include fields that haven't been explicitly included. # Such fields would not be in `fields`, so they need to be added. implicitly_included = set(requirements.keys()) - set(fields.keys()) if implicitly_included: all_fields = serializer.get_all_fields() fields.update({ field: all_fields[field] for field in implicitly_included if field in all_fields }) if filters is None: filters = self._get_requested_filters() # build nested Prefetch queryset self._build_requested_prefetches(prefetches, requirements, model, fields, filters) # build remaining prefetches out of internal requirements # that are not already covered by request requirements self._build_implicit_prefetches(model, prefetches, requirements) # use requirements at this level to limit fields selected # only do this for GET requests where we are not requesting the # entire fieldset if ('*' not in requirements and not self.view.is_update() and not self.view.is_delete()): id_fields = getattr(serializer, 'get_id_fields', lambda: [])() # only include local model fields only = [ field for field in set(id_fields + list(requirements.keys())) if is_model_field(model, field) and not is_field_remote(model, field) ] queryset = queryset.only(*only) # add request filters query = self._filters_to_query(includes=filters.get('_include'), excludes=filters.get('_exclude'), serializer=serializer) # add additional filters specified by calling view if extra_filters: query = extra_filters if not query else extra_filters & query if query: # Convert internal django ValidationError to # APIException-based one in order to resolve validation error # from 500 status code to 400. try: queryset = queryset.filter(query) except InternalValidationError as e: raise ValidationError( dict(e) if hasattr(e, 'error_dict') else list(e)) except Exception as e: # Some other Django error in parsing the filter. # Very likely a bad query, so throw a ValidationError. err_msg = getattr(e, 'message', '') raise ValidationError(err_msg) # A serializer can have this optional function # to dynamically apply additional filters on # any queries that will use that serializer # You could use this to have (for example) different # serializers for different subsets of a model or to # implement permissions which work even in sideloads if hasattr(serializer, 'filter_queryset'): queryset = self._serializer_filter(serializer=serializer, queryset=queryset) # add prefetches and remove duplicates if necessary prefetch = prefetches.values() if prefetch and not disable_prefetches: queryset = queryset.prefetch_related(*prefetch) elif isinstance(queryset, Manager): queryset = queryset.all() if has_joins(queryset) or not is_root_level: queryset = queryset.distinct() if self.DEBUG: queryset._using_prefetches = prefetches return queryset
def _build_queryset(self, serializer=None, filters=None, queryset=None, requirements=None): """Build a queryset that pulls in all data required by this request. Handles nested prefetching of related data and deferring fields at the queryset level. Arguments: serializer: An optional serializer to use a base for the queryset. If no serializer is passed, the `get_serializer` method will be used to initialize the base serializer for the viewset. filters: An optional TreeMap of nested filters. queryset: An optional base queryset. requirements: An optional TreeMap of nested requirements. """ is_root_level = False if serializer: if queryset is None: queryset = serializer.Meta.model.objects else: serializer = self.view.get_serializer() is_root_level = True model = serializer.get_model() if not model: return queryset meta = Meta(model) prefetches = {} # build a nested Prefetch queryset # based on request parameters and serializer fields fields = serializer.fields if requirements is None: requirements = TreeMap() self._get_implicit_requirements(fields, requirements) if filters is None: filters = self._get_requested_filters() # build nested Prefetch queryset self._build_requested_prefetches(prefetches, requirements, model, fields, filters, is_root_level) # build remaining prefetches out of internal requirements # that are not already covered by request requirements self._build_implicit_prefetches(model, prefetches, requirements) # use requirements at this level to limit fields selected # only do this for GET requests where we are not requesting the # entire fieldset is_gui = self.view.get_format() == 'admin' if ('*' not in requirements and not self.view.is_update() and not self.view.is_delete() and not is_gui): id_fields = getattr(serializer, 'get_id_fields', lambda: [])() # only include local model fields only = [ field for field in set(id_fields + list(requirements.keys())) if meta.is_field(field) and not meta.is_field_remote(field) ] queryset = queryset.only(*only) # add request filters query = self._filters_to_query(filters) if query: # Convert internal django ValidationError to # APIException-based one in order to resolve validation error # from 500 status code to 400. try: queryset = queryset.filter(query) except InternalValidationError as e: raise ValidationError( dict(e) if hasattr(e, 'error_dict') else list(e)) except Exception as e: # Some other Django error in parsing the filter. # Very likely a bad query, so throw a ValidationError. err_msg = getattr(e, 'message', '') raise ValidationError(err_msg) # A serializer can have this optional function # to dynamically apply additional filters on # any queries that will use that serializer # You could use this to have (for example) different # serializers for different subsets of a model or to # implement permissions which work even in sideloads if hasattr(serializer, 'filter_queryset'): queryset = serializer.filter_queryset(queryset) # add prefetches and remove duplicates if necessary prefetch = prefetches.values() queryset = queryset.prefetch_related(*prefetch) if has_joins(queryset) or not is_root_level: queryset = queryset.distinct() if self.DEBUG: queryset._using_prefetches = prefetches return queryset
def _filter_queryset(self, serializer=None, filters=None, queryset=None, requirements=None): """Recursive queryset builder. Handles nested prefetching of related data and deferring fields at the queryset level. Arguments: serializer: An optional serializer to use a base for the queryset. If no serializer is passed, the `get_serializer` method will be used to initialize the base serializer for the viewset. filters: An optional TreeMap of nested filters. queryset: An optional base queryset. requirements: An optional TreeMap of nested requirements. """ is_root_level = False if serializer: if queryset is None: queryset = serializer.Meta.model.objects else: serializer = self.view.get_serializer() is_root_level = True model = getattr(serializer.Meta, 'model', None) if not model: return queryset prefetches = {} fields = serializer.fields if requirements is None: requirements = TreeMap() self._extract_requirements(fields, requirements) if filters is None: filters = self._extract_filters() # build nested Prefetch queryset self._add_request_prefetches(prefetches, requirements, model, fields, filters) # add any remaining requirements as prefetches self._add_internal_prefetches(prefetches, requirements) # use requirements at this level to limit fields selected # only do this for GET requests where we are not requesting the # entire fieldset if ('*' not in requirements and not self.view.is_update() and not self.view.is_delete()): id_fields = getattr(serializer, 'get_id_fields', lambda: [])() # only include local model fields only = [ field for field in set(id_fields + list(requirements.keys())) if is_model_field(model, field) and not is_field_remote(model, field) ] queryset = queryset.only(*only) # add filters query = self._filters_to_query(includes=filters.get('_include'), excludes=filters.get('_exclude'), serializer=serializer) if query: # Convert internal django ValidationError to APIException-based one # in order to resolve validation errors from 500 status code to # 400. try: queryset = queryset.filter(query) except InternalValidationError as e: raise ValidationError( dict(e) if hasattr(e, 'error_dict') else list(e)) except Exception as e: # Some other Django error in parsing the filter. Very likely # a bad query, so throw a ValidationError. err_msg = getattr(e, 'message', '') raise ValidationError(err_msg) # A serializer can have this optional function # to dynamically apply additional filters on # any queries that will use that serializer # You could use this to have (for example) different # serializers for different subsets of a model or to # implement permissions which work even in sideloads if hasattr(serializer, 'filter_queryset'): queryset = serializer.filter_queryset(queryset) prefetch = prefetches.values() queryset = queryset.prefetch_related(*prefetch) if has_joins(queryset) or not is_root_level: queryset = queryset.distinct() return queryset
def _filter_queryset( self, serializer=None, filters=None, queryset=None, requirements=None ): """Recursive queryset builder. Handles nested prefetching of related data and deferring fields at the queryset level. Arguments: serializer: An optional serializer to use a base for the queryset. If no serializer is passed, the `get_serializer` method will be used to initialize the base serializer for the viewset. filters: An optional TreeMap of nested filters. queryset: An optional base queryset. requirements: An optional TreeMap of nested requirements. """ is_root_level = False if serializer: if queryset is None: queryset = serializer.Meta.model.objects else: serializer = self.view.get_serializer() is_root_level = True model = getattr(serializer.Meta, 'model', None) if not model: return queryset prefetches = {} fields = serializer.fields if requirements is None: requirements = TreeMap() self._extract_requirements( fields, requirements ) if filters is None: filters = self._extract_filters() # build nested Prefetch queryset self._add_request_prefetches( prefetches, requirements, model, fields, filters ) # add any remaining requirements as prefetches self._add_internal_prefetches( prefetches, requirements ) # use requirements at this level to limit fields selected # only do this for GET requests where we are not requesting the # entire fieldset if '*' not in requirements and not self.view.is_update(): id_fields = getattr(serializer, 'get_id_fields', lambda: [])() # only include local model fields only = [ field for field in set(id_fields + list(requirements.keys())) if is_model_field(model, field) and not is_field_remote(model, field) ] queryset = queryset.only(*only) # add filters query = self._filters_to_query( includes=filters.get('_include'), excludes=filters.get('_exclude'), serializer=serializer ) if query: # Convert internal django ValidationError to APIException-based one # in order to resolve validation errors from 500 status code to # 400. try: queryset = queryset.filter(query) except InternalValidationError as e: raise ValidationError( dict(e) if hasattr(e, 'error_dict') else list(e) ) # A serializer can have this optional function # to dynamically apply additional filters on # any queries that will use that serializer # You could use this to have (for example) different # serializers for different subsets of a model or to # implement permissions which work even in sideloads if hasattr(serializer, 'filter_queryset'): queryset = serializer.filter_queryset(queryset) prefetch = prefetches.values() queryset = queryset.prefetch_related(*prefetch) if has_joins(queryset) or not is_root_level: queryset = queryset.distinct() return queryset