def bind(self, *args, **kwargs): """Bind to the parent serializer.""" if self.bound: # Prevent double-binding return super(DynamicRelationField, self).bind(*args, **kwargs) self.bound = True parent_model = getattr(self.parent.Meta, 'model', None) remote = is_field_remote(parent_model, self.source) try: model_field = get_model_field(parent_model, self.source) except: # model field may not be available for m2o fields with no # related_name model_field = None # Infer `required` and `allow_null` if 'required' not in self.kwargs and ( remote or (model_field and (model_field.has_default() or model_field.null))): self.required = False if 'allow_null' not in self.kwargs and getattr(model_field, 'null', False): self.allow_null = True self.model_field = model_field
def _add_request_prefetches(self, prefetches, requirements, model, fields, filters): """Add external (requested) prefetches to a prefetch dictionary.""" for name, field in six.iteritems(fields): original_field = field if isinstance(field, DynamicRelationField): field = field.serializer if isinstance(field, serializers.ListSerializer): field = field.child if not isinstance(field, serializers.ModelSerializer): continue source = field.source or name if '.' in source: raise ValidationError('nested relationship values ' 'are not supported') if source in prefetches: # ignore duplicated sources continue is_remote = is_field_remote(model, source) is_id_only = getattr(field, 'id_only', lambda: False)() if is_id_only and not is_remote: continue prefetch_queryset = self._build_prefetch_queryset( name, original_field, field, filters, requirements) # Note: There can only be one prefetch per source, even # though there can be multiple fields pointing to # the same source. This could break in some cases, # but is mostly an issue on writes when we use all # fields by default. prefetches[source] = Prefetch(source, queryset=prefetch_queryset)
def bind(self, *args, **kwargs): """Bind to the parent serializer.""" if self.bound: # Prevent double-binding return super(DynamicField, self).bind(*args, **kwargs) self.bound = True source = self.source or self.field_name if source == '*': if self.getter == '*': self.getter = 'get_%s' % self.field_name if self.setter == '*': self.setter = 'set_%s' % self.field_name return parent_model = self.parent_model if parent_model: remote = is_field_remote(parent_model, source) model_field = self.model_field generic = isinstance(model_field, GenericForeignKey) if not generic: # Infer `required` and `allow_null` if 'required' not in self.kwargs and ( remote or (model_field and (model_field.has_default() or model_field.null))): self.required = False if 'allow_null' not in self.kwargs and getattr( model_field, 'null', False): self.allow_null = True
def bind(self, *args, **kwargs): """Bind to the parent serializer.""" if self.bound: # Prevent double-binding return super(DynamicRelationField, self).bind(*args, **kwargs) self.bound = True parent_model = getattr(self.parent.Meta, 'model', None) remote = is_field_remote(parent_model, self.source) try: model_field = parent_model._meta.get_field_by_name(self.source)[0] except: # model field may not be available for m2o fields with no # related_name model_field = None # Infer `required` and `allow_null` if 'required' not in self.kwargs and ( remote or ( model_field and ( model_field.has_default() or model_field.null ) ) ): self.required = False if 'allow_null' not in self.kwargs and getattr( model_field, 'null', False ): self.allow_null = True self.model_field = model_field
def _build_requested_prefetches(self, prefetches, requirements, model, fields, filters): """Build a prefetch dictionary based on request requirements.""" for name, field in six.iteritems(fields): original_field = field if isinstance(field, DynamicRelationField): field = field.serializer if isinstance(field, serializers.ListSerializer): field = field.child if not isinstance(field, serializers.ModelSerializer): continue source = field.source or name if '.' in source: raise ValidationError('nested relationship values ' 'are not supported') if source in prefetches: # ignore duplicated sources continue is_remote = is_field_remote(model, source) is_id_only = getattr(field, 'id_only', lambda: False)() if is_id_only and not is_remote: continue related_queryset = getattr(original_field, 'queryset', None) if callable(related_queryset): related_queryset = related_queryset(field) source = field.source or name # Popping the source here (during explicit prefetch construction) # guarantees that implicitly required prefetches that follow will # not conflict. required = requirements.pop(source, None) prefetch_queryset = self._build_queryset(serializer=field, filters=filters.get( name, {}), queryset=related_queryset, requirements=required) # Note: There can only be one prefetch per source, even # though there can be multiple fields pointing to # the same source. This could break in some cases, # but is mostly an issue on writes when we use all # fields by default. prefetches[source] = self._create_prefetch(source, prefetch_queryset) return prefetches
def _add_request_prefetches( self, prefetches, requirements, model, fields, filters ): """Add external (requested) prefetches to a prefetch dictionary.""" for name, field in six.iteritems(fields): original_field = field if isinstance(field, DynamicRelationField): field = field.serializer if isinstance(field, serializers.ListSerializer): field = field.child if not isinstance(field, serializers.ModelSerializer): continue source = field.source or name if '.' in source: raise ValidationError( 'nested relationship values ' 'are not supported' ) if source in prefetches: # ignore duplicated sources continue is_remote = is_field_remote(model, source) is_id_only = getattr(field, 'id_only', lambda: False)() if is_id_only and not is_remote: continue prefetch_queryset = self._build_prefetch_queryset( name, original_field, field, filters, requirements ) # Note: There can only be one prefetch per source, even # though there can be multiple fields pointing to # the same source. This could break in some cases, # but is mostly an issue on writes when we use all # fields by default. prefetches[source] = Prefetch( source, queryset=prefetch_queryset )
def bind(self, *args, **kwargs): """Bind to the parent serializer.""" if self.bound: # Prevent double-binding return super(DynamicField, self).bind(*args, **kwargs) self.bound = True source = self.source or self.field_name if source == '*': if hasattr(self, 'method_name'): if self.getter is None: self.getter = self.method_name else: self.method_name = self.getter if self.getter: self.getter = memoize(getattr(self.parent, self.getter), 'pk') return if self.getter: self.getter = memoize(getattr(self.parent, self.getter), 'pk') self.source_attrs = source.split('.') parent_model = self.parent_model if parent_model: remote = is_field_remote(parent_model, source) model_field = self.model_field generic = isinstance(model_field, GenericForeignKey) if not generic: # Infer `required` and `allow_null` if 'required' not in self.kwargs and ( remote or (model_field and (model_field.has_default() or model_field.null))): self.required = False if 'allow_null' not in self.kwargs and getattr( model_field, 'null', False): self.allow_null = True if 'allow_blank' not in self.kwargs and self.allow_null: self.allow_blank = True help_text = getattr(model_field, 'help_text', None) if 'help_text' not in self.kwargs and help_text: self.help_text = help_text
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 _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