web-dev-qa-db-fra.com

le cadre de repos django crée des objets imbriqués "Modèles" en POST

J'essaie POST un nouvel objet imbriqué, le problème est simplement de créer l'objet "top" (Playlist), mais ne créez pas l'objet "ChannelItem" ...

Mes modèles:

class Playlist(models.Model):
    provider = models.IntegerField()
    channel_id = models.CharField(max_length=100)
    channel_version = models.CharField(blank=True, max_length=100)
    start = models.DateTimeField()
    url = models.CharField(max_length=500)


class ChannelItem(models.Model):
    playlist = models.ForeignKey(Playlist, editable=False, related_name='channelitems')
    content_id = models.CharField(max_length=100)
    content_version = models.CharField(blank=True, max_length=100)

Mon sérialiseur:

class ChannelItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = ChannelItem
        fields = ('content_id', 'content_version')
        exclude = ('id')
        depth = 1


class PlaylistSerializer(serializers.ModelSerializer):

    class Meta:
        model = Playlist
        fields = ('id', 'provider', 'channel_id', 'channel_version', 'start', 
                  'url', 'channelitems')
        depth = 2

channelitems = ChannelItemSerializer()

J'utilise le curl pour publier les données suivantes:

'{"provider":125,"channel_id":"xyz", "channel_version":"xsqt", 
"start":"2012-12-17T11:04:35","url":"http://192.168.1.83:8080/maaaaa",
"channelitems":[{"content_id":"0.flv", "content_version":"ss"},
{"content_id":"1.flv","content_version":"ss"}]}' http://localhost:8000/playlist_scheduler/playlists/

Je reçois le message:

HTTP/1.1 201 CREATED
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 17 Dec 2012 20:12:54 GMT
Server: 0.0.0.0

{"id": 25, "provider": 125, "channel_id": "xyz", "channel_version": "xsqt",
"start":"2012-12-17T11:04:35", "url": "http://localhost:8080/something",
"channelitems": []}
25
skopp

Les représentations imbriquées ne prennent actuellement pas en charge lecture/écriture , mais doivent plutôt être en lecture seule.

Vous devriez probablement utiliser plutôt une représentation plate, pk ou des liens hypertextes.

Si vous avez besoin de la représentation imbriquée, vous pouvez envisager d’avoir deux points de terminaison distincts: un point de terminaison inscriptible à plat et un point de terminaison imbriqué en lecture seule.

21
Tom Christie

Si quelqu'un a besoin d'une solution rapide pour cela, je propose celle-ci que je vais utiliser temporairement dans un projet:

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

Créez ensuite votre propre sous-classe de NestedManyToManyField:

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

Un exemple de MyOriginalSerializer:

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

Cela fonctionne bien pour moi jusqu'à présent. Mais soyez conscient qu'il y a des correctifs à venir:

6
Micha Mazaheri

après un long effort, j’ai réalisé une première version en funcinasse .... Je pense qu’une amélioration pourrait être incluse dans le 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
3
skopp

Pour moi, j'ai une solution de contournement hybride qui me convient. À savoir, créez une vue qui a:

  • le champ ManyToMany sous sa forme de sérialiseur non imbriqué
  • alias le champ ManyToMany imbriqué dans une variable avec le suffixe _objs et spécifiez-le comme étant en lecture seule
  • lorsque vous PUT sur le serveur, rapprochez les deux champs avec alias et stockez le résultat dans le champ du sérialiseur non imbriqué

par exemple.

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

Je n'aime pas cette solution, mais il est plus efficace d'essayer d'interroger et d'assembler séparément les champs imbriqués après la récupération du conteneur initial.

1
Ross Rogers