def get_id_fields(self): """ Called to return a list of fields consisting of, at minimum, the PK field name. The output of this method is used to construct a Prefetch object with a .only() queryset when this field is not being sideloaded but we need to return a list of IDs. """ model = self.get_model() meta = Meta(model) out = [meta.get_pk_field().attname] # If this is being called, it means it # is a many-relation to its parent. # Django wants the FK to the parent, # but since accurately inferring the FK # pointing back to the parent is less than trivial, # we will just pull all ID fields. # TODO: We also might need to return all non-nullable fields, # or else it is possible Django will issue another request. for field in meta.get_fields(): if isinstance(field, models.ForeignKey): out.append(field.attname) return out
def resolve(self, query): """Resolves a query into model and serializer fields. Arguments: query: an API field path, in dot-nation e.g: "creator.location_name" Returns: (model_fields, api_fields) e.g: [ Blog._meta.fields.user, User._meta.fields.location, Location._meta.fields.name ], [ DynamicRelationField(source="user"), DynamicCharField(source="location.name") ] Raises: ValidationError if the query is invalid, e.g. references a method field or an undefined field ``` Note that the lists do not necessarily contain the same number of elements because API fields can reference nested model fields. """ # noqa if not isinstance(query, six.string_types): parts = query query = '.'.join(query) else: parts = query.split('.') model_fields = [] api_fields = [] serializer = self model = serializer.get_model() resource_name = serializer.get_name() meta = Meta(model) api_name = parts[0] other = parts[1:] try: api_field = serializer.get_field(api_name) except: api_field = None if other: if not (api_field and isinstance(api_field, DynamicRelationField)): raise ValidationError({ api_name: 'Could not resolve "%s": ' '"%s.%s" is not an API relation' % (query, resource_name, api_name) }) source = api_field.source or api_name related = api_field.serializer_class() other = '.'.join(other) model_fields, api_fields = related.resolve(other) try: model_field = meta.get_field(source) except AttributeError: raise ValidationError({ api_name: 'Could not resolve "%s": ' '"%s.%s" is not a model relation' % (query, meta.get_name(), source) }) model_fields.insert(0, model_field) api_fields.insert(0, api_field) else: if api_name == 'pk': # pk is an alias for the id field model_field = meta.get_pk_field() model_fields.append(model_field) if api_field: # the pk field may not exist # on the serializer api_fields.append(api_field) else: if not api_field: raise ValidationError({ api_name: 'Could not resolve "%s": ' '"%s.%s" is not an API field' % (query, resource_name, api_name) }) api_fields.append(api_field) if api_field.source == '*': # a method field was requested, model field is unknown return (model_fields, api_fields) source = api_field.source or api_name if '.' in source: fields = source.split('.') for field in fields[:-1]: related_model = None try: model_field = meta.get_field(field) related_model = model_field.related_model except: pass if not related_model: raise ValidationError({ api_name: 'Could not resolve "%s": ' '"%s.%s" is not a model relation' % (query, meta.get_name(), field) }) model = related_model meta = Meta(model) model_fields.append(model_field) field = fields[-1] try: model_field = meta.get_field(field) except: raise ValidationError({ api_name: 'Could not resolve: "%s", ' '"%s.%s" is not a model field' % (query, meta.get_name(), field) }) model_fields.append(model_field) else: try: model_field = meta.get_field(source) except: raise ValidationError({ api_name: 'Could not resolve "%s": ' '"%s.%s" is not a model field' % (query, meta.get_name(), source) }) model_fields.append(model_field) return (model_fields, api_fields)