Пример #1
0
    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
Пример #2
0
    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)
Пример #3
0
    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
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
    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
            )
Пример #7
0
    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
Пример #8
0
    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
Пример #9
0
    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
Пример #10
0
    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