def update(self, instance, validated_data): # support nested writes if possible meta = Meta(instance) to_save = [instance] # Simply set each attribute on the instance, and then save it. # Note that unlike `.create()` we don't need to treat many-to-many # relationships as being a special case. During updates we already # have an instance pk for the relationships to be associated with. try: with transaction.atomic(): for attr, value in validated_data.items(): try: field = meta.get_field(attr) if field.related_model: if isinstance(value, dict): # nested dictionary on a has-one # relationship, we should take the current # related value and apply updates to it to_save.extend( nested_update(instance, attr, value)) else: # normal relationship update setattr(instance, attr, value) else: setattr(instance, attr, value) except AttributeError: setattr(instance, attr, value) for s in to_save: s.save() except Exception as e: raise exceptions.ValidationError(e) return instance
def _build_implicit_prefetches(self, model, prefetches, requirements): """Build a prefetch dictionary based on internal requirements.""" meta = Meta(model) for source, remainder in six.iteritems(requirements): if not remainder or isinstance(remainder, six.string_types): # no further requirements to prefetch continue related_field = meta.get_field(source) related_model = get_related_model(related_field) queryset = self._build_implicit_queryset( related_model, remainder) if related_model else None prefetches[source] = Prefetch(source, queryset=queryset) return prefetches
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)