예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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)