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