django rest framework create nested objects “Models” by POST

后端 未结 4 881
暖寄归人
暖寄归人 2020-12-23 12:29

I\'m trying POST a new a Nested Object, the problem is just create the \"top\" object (Playlist), but don\'t create the \"ChannelItem\"...

My Models:



        
相关标签:
4条回答
  • 2020-12-23 12:35

    after a long effort I made a first version that funcinasse ... I believe that with some improvement could be included within the ModelSerializer

    class ChannelItemSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = ChannelItem
            fields = ('id', 'content_id', 'content_version')
    
        def field_from_native(self, data, files, field_name, into):
            try:
                if self._use_files:
                    _files = files[field_name]
                else:
                    _data = data[field_name]
            except KeyError:
                if getattr(self, 'default', None):
                    _data = self.default
                else:
                    if getattr(self, 'required', None):
                        raise ValidationError(self.error_messages['required'])
                    return
    
            if type(_data) is list:
                into[field_name] = [] 
                for item in _data:
                    into[field_name].append(self._custom_from_native(item))
            else:
                into[field_name] = self._custom_from_native(_data)
    
    
        def _custom_from_native(self, data):
            self._errors = {}
            if data is not None:
                attrs = self.restore_fields(data, None)
                attrs = self.perform_validation(attrs)
            else:
                self._errors['non_field_errors'] = ['No input provided']
    
            if not self._errors:
                return self.restore_object(attrs, instance=getattr(self, 'object', None))
    
    
    
    
    class PlaylistSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = Playlist
            fields = ('id', 'provider', 'channel_id', 'channel_version', 'start', 'url', 'channel_items')
            depth = 1
    
        channel_items = ChannelItemSerializer()
    
        def restore_object(self, attrs, instance=None):
            self.foreign_data = {}
    
            for (obj, model) in self.opts.model._meta.get_all_related_objects_with_model():
                field_name = obj.field.related_query_name()
                if field_name in attrs:
                    self.foreign_data[field_name] = attrs.pop(field_name)
    
    
            return super(PlaylistSerializer, self).restore_object(attrs, instance)
    
        def save(self, save_m2m=True):
            super(PlaylistSerializer, self).save(save_m2m)
    
            if getattr(self, 'foreign_data', None):
                for accessor_name, object_list in self.foreign_data.items():
                    setattr(self.object, accessor_name, object_list)
                self.foreign_data = {}
    
            return self.object
    
    0 讨论(0)
  • 2020-12-23 12:40

    Nested representations do not currently support read-write, and should instead be read-only.

    You should probably look into using a flat representation instead, using pk or hyperlinked relations.

    If you need the nested representation, you may want to consider having two separate endpoints - a flat writable endpoint, and a nested read-only endpoint.

    0 讨论(0)
  • 2020-12-23 12:41

    For me, I have a hybrid workaround that I'm OK with. Namely, create a view that has:

    • the ManyToMany field in its un-nested serializer form
    • alias the nested ManyToMany field into a variable with _objs as the suffix and specify it as as read only
    • when you PUT back to the server reconcile the two aliased fields and store the result in the un-nested serializer field

    e.g.

    class MSerializer(serializers.HyperlinkedModelSerializer):
        foo_objs = TempSensorSerializer(source='foos', many=True, allow_add_remove=True,required=False,read_only=True)
        class Meta:
            model = M
            fields = ('url', 'foos', 'foo_objs')
    

    I don't love this solution, but it beats trying to separately query and collate the nested fields after retrieving the initial container.

    0 讨论(0)
  • 2020-12-23 12:47

    If someone needs a quick-and-dirty solution for that, I came up with this one I'll be temporary using in a project:

    class NestedManyToManyField(serializers.WritableField):
        def to_native(self, value):
            serializer = self.Meta.serializer(value.all(), many=True, context=self.context)
            return serializer.data
        def from_native(self, data):
            serializer = self.Meta.serializer(data=data, many=True, context=self.context)
            serializer.is_valid()
            serializer.save()
            return serializer.object
        class Meta:
            serializer = None
    

    Then create your own subclass of NestedManyToManyField:

    class TopicNestedSerializer(NestedManyToManyField):
        class Meta:
            serializer = MyOriginalSerializer
    

    An example of MyOriginalSerializer:

    class MyOriginalSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.MyModel
            fields = ('id', 'title',)
    

    This works fine for me so far. But be aware there are clean fixes coming:

    • https://github.com/tomchristie/django-rest-framework/issues/960
    • https://github.com/tomchristie/django-rest-framework/pull/817
    0 讨论(0)
提交回复
热议问题