Beispiel #1
0
    def resolve(self, serializer, query, view=None):
        """Resolve an ordering.

        Arguments:
            query: a string representing an API field
                e.g: "location.name"
            serializer: a serializer instance
                e.g. UserSerializer
            view: a view instance (optional)
                e.g. UserViewSet

        Returns:
            Double-underscore-separated list of strings,
            representing a model field.
                e.g. "location__real_name"

        Raises:
            ValidationError if the query cannot be rewritten
        """
        if not self._is_allowed_query(query, view):
            raise ValidationError('Invalid sort option: %s' % query)

        model_fields, _ = serializer.resolve(query, sort=True)
        resolved = '__'.join([Meta.get_query_name(f) for f in model_fields])
        return resolved
Beispiel #2
0
    def _build_requested_prefetches(self, prefetches, requirements, model,
                                    fields, filters, is_root_level):
        """Build a prefetch dictionary based on request requirements."""
        meta = Meta(model)
        for name, field in six.iteritems(fields):
            original_field = field
            if isinstance(field, dfields.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 == '*':
                # ignore custom getter/setter
                continue

            if source in prefetches:
                # ignore duplicated sources
                continue

            related_queryset = getattr(original_field, 'queryset', None)
            if callable(related_queryset):
                related_queryset = related_queryset(field)

            is_id_only = getattr(field, 'id_only', lambda: False)()
            is_remote = meta.is_field_remote(source)
            is_gui_root = self.view.get_format() == 'admin' and is_root_level
            if (related_queryset is None and is_id_only and not is_remote
                    and not is_gui_root):
                # full representation and remote fields
                # should all trigger prefetching
                continue

            # Popping the source here (during explicit prefetch construction)
            # guarantees that implicitly required prefetches that follow will
            # not conflict.
            required = requirements.pop(source, None)

            query_name = Meta.get_query_name(original_field.model_field)
            prefetch_queryset = self._build_queryset(serializer=field,
                                                     filters=filters.get(
                                                         query_name, {}),
                                                     queryset=related_queryset,
                                                     requirements=required)

            # 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)

        return prefetches
Beispiel #3
0
    def _get_requested_filters(self, **kwargs):
        """
        Convert 'filters' query params into a dict that can be passed
        to Q. Returns a dict with two fields, 'include' and 'exclude',
        which can be used like:

            result = self._get_requested_filters()
            q = Q(**result['_include'] & ~Q(**result['_exclude'])

        """

        filters_map = kwargs.get('filters_map')

        view = getattr(self, 'view', None)
        if view:
            serializer_class = view.get_serializer_class()
            serializer = serializer_class()
            if not filters_map:
                filters_map = view.get_request_feature(view.FILTER)
        else:
            serializer = None

        out = TreeMap()

        for key, value in six.iteritems(filters_map):

            # Inclusion or exclusion?
            if key[0] == '-':
                key = key[1:]
                category = '_exclude'
            else:
                category = '_include'

            # for relational filters, separate out relation path part
            if '|' in key:
                rel, key = key.split('|')
                rel = rel.split('.')
            else:
                rel = None

            terms = key.split('.')
            # Last part could be operator, e.g. "events.capacity.gte"
            if len(terms) > 1 and terms[-1] in self.VALID_FILTER_OPERATORS:
                operator = terms.pop()
            else:
                operator = None

            # All operators except 'range' and 'in' should have one value
            if operator == 'range':
                value = value[:2]
                if value[0] == '':
                    operator = 'lte'
                    value = value[1]
                elif value[1] == '':
                    operator = 'gte'
                    value = value[0]
            elif operator == 'in':
                # no-op: i.e. accept `value` as an arbitrarily long list
                pass
            elif operator in self.VALID_FILTER_OPERATORS:
                value = value[0]
                if (operator == 'isnull'
                        and isinstance(value, six.string_types)):
                    value = is_truthy(value)
                elif operator == 'eq':
                    operator = None

            if serializer:
                s = serializer

                if rel:
                    # get related serializer
                    model_fields, serializer_fields = serializer.resolve(rel)
                    s = serializer_fields[-1]
                    s = getattr(s, 'serializer', s)
                    rel = [Meta.get_query_name(f) for f in model_fields]

                # perform model-field resolution
                model_fields, serializer_fields = s.resolve(terms)
                field = serializer_fields[-1] if serializer_fields else None
                # if the field is a boolean,
                # coerce the value
                if field and isinstance(
                        field,
                    (serializers.BooleanField, serializers.NullBooleanField)):
                    value = is_truthy(value)
                key = '__'.join([Meta.get_query_name(f) for f in model_fields])

            else:
                key = '__'.join(terms)

            if operator:
                key += '__%s' % operator

            # insert into output tree
            path = rel if rel else []
            path += [category, key]
            out.insert(path, value)
        return out