web-dev-qa-db-fra.com

Comment créer plusieurs instances de modèle avec Django Rest Framework?

Je souhaite enregistrer et mettre à jour plusieurs instances à l'aide de Django Rest Framework avec un seul appel d'API. Par exemple, supposons que j'ai un modèle "Salle de classe" pouvant avoir plusieurs "Enseignants". Si je voulais créer plusieurs enseignants et mettre à jour tous leurs numéros de classe, comment pourrais-je le faire? Dois-je faire un appel API pour chaque enseignant?

Je sais qu'actuellement nous ne pouvons pas sauvegarder les modèles imbriqués, mais j'aimerais savoir si nous pouvons le sauvegarder au niveau de l'enseignant . Merci!

48
Chaz

Je sais que cela a été demandé il y a quelque temps maintenant, mais je l'ai trouvé en essayant de comprendre cela moi-même.

Il s'avère que si vous passez many=True lors de l'instanciation de la classe de sérialiseur pour un modèle, celui-ci peut alors accepter plusieurs objets.

Ceci est mentionné ici dans la documentation de Django Rest Framework

Pour mon cas, ma vue ressemblait à ceci:

class ThingViewSet(viewsets.ModelViewSet):
    """This view provides list, detail, create, retrieve, update
    and destroy actions for Things."""
    model = Thing
    serializer_class = ThingSerializer

Je ne voulais pas vraiment écrire une charge de boiler, juste pour avoir un contrôle direct sur l'instanciation du sérialiseur et passer many=True, donc dans ma classe de sérialiseur, je remplace le __init__ à la place:

class ThingSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        many = kwargs.pop('many', True)
        super(ThingSerializer, self).__init__(many=many, *args, **kwargs)

    class Meta:
        model = Thing
        fields = ('loads', 'of', 'fields', )

Enregistrement des données dans l'URL de la liste pour cette vue au format:

[
    {'loads':'foo','of':'bar','fields':'buzz'},
    {'loads':'fizz','of':'bazz','fields':'errrrm'}
]

Créé deux ressources avec ces détails. Qui était Nice.

58
Tom Manterfield

Je suis arrivé à la même conclusion que Daniel Albarral, mais voici une solution plus succincte:

class CreateListModelMixin(object):

    def get_serializer(self, *args, **kwargs):
        """ if an array is passed, set serializer to many """
        if isinstance(kwargs.get('data', {}), list):
            kwargs['many'] = True
        return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
31
Roger Collins

Je n'arrivais pas à comprendre comment obtenir la requête.DATA pour convertir un dictionnaire en tableau - ce qui limitait ma capacité à la solution de Tom Manterfield. Voici ma solution:

class ThingSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        many = kwargs.pop('many', True)
        super(ThingSerializer, self).__init__(many=many, *args, **kwargs)

    class Meta:
        model = Thing
        fields = ('loads', 'of', 'fields', )

class ThingViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet ):
    queryset = myModels\
        .Thing\
        .objects\
        .all()
    serializer_class = ThingSerializer

    def create(self, request, *args, **kwargs):
        self.user = request.user
        listOfThings = request.DATA['things']

        serializer = self.get_serializer(data=listOfThings, files=request.FILES, many=True)
        if serializer.is_valid():
            serializer.save()
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED,
                            headers=headers)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Et puis je lance l'équivalent de ceci sur le client:

var things = {    
    "things":[
        {'loads':'foo','of':'bar','fields':'buzz'},
        {'loads':'fizz','of':'bazz','fields':'errrrm'}]
}
thingClientResource.post(things)
11
akaphenom

Voici une autre solution: vous n'avez pas besoin de remplacer votre méthode de sérialiseur __init__. Remplacez simplement la méthode 'create' de votre vue (ModelViewSet). Remarquez many=isinstance(request.data,list). Ici many=True lorsque vous envoyez un tableau d'objets à créer et False lorsque vous n'en envoyez qu'un. De cette façon, vous pouvez enregistrer un élément et une liste!

from rest_framework import status, viewsets
from rest_framework.response import Response

class ThingViewSet(viewsets.ModelViewSet):

"""This view snippet provides both list and item create functionality."""

    #I took the liberty to change the model to queryset
    queryset = Thing.objects.all()
    serializer_class = ThingSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data, many=isinstance(request.data,list))
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
10
waqmax

Vous pouvez simplement écraser la méthode get_serializer dans votre APIView et passer many=True dans get_serializer de la vue de base comme suit:

class SomeAPIView(CreateAPIView):
    queryset = SomeModel.objects.all()
    serializer_class = SomeSerializer

    def get_serializer(self, instance=None, data=None, many=False, partial=False):
        return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
5
TehQuila

Je pense que le meilleur moyen de respecter l'architecture proposée du framework sera de créer un mix comme celui-ci:

class CreateListModelMixin(object):

    def create(self, request, *args, **kwargs):
        """
            Create a list of model instances if a list is provides or a
            single model instance otherwise.
        """
        data = request.data
        if isinstance(data, list):
            serializer = self.get_serializer(data=request.data, many=True)
        else:
            serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED,
                    headers=headers)

Ensuite, vous pouvez remplacer le CreateModelMixin de ModelViewSet comme ceci:

class <MyModel>ViewSet(CreateListModelMixin, viewsets.ModelViewSet):
    ...
    ...

Maintenant, dans le client, vous pouvez travailler comme ceci:

var things = [    
    {'loads':'foo','of':'bar','fields':'buzz'},
    {'loads':'fizz','of':'bazz','fields':'errrrm'}
]
thingClientResource.post(things)

ou

var thing = {
    'loads':'foo','of':'bar','fields':'buzz'
}

thingClientResource.post(thing)

MODIFIER:

Comme le suggère Roger Collins dans sa réponse est plus habile pour écraser la méthode get_serializer que la méthode 'create'.

4
Daniel Albarral

La Vues génériques page dans Django REST La documentation de Framework indique que la vue ListCreateAPIView generic est "utilisée pour les noeuds finaux en lecture-écriture représentant une collection d'instances de modèle".

C'est là que je commencerais à chercher (et je vais le faire, car nous aurons bientôt besoin de cette fonctionnalité dans notre projet).

Notez également que les exemples de la page Vues génériques utilisent ListCreateAPIView.

3
akaihola

Je suis venu avec un exemple simple dans post

Serializers.py

from rest_framework import serializers
from movie.models import Movie

class MovieSerializer(serializers.ModelSerializer):

    class Meta:
        model = Movie
        fields = [
            'popularity',
            'director',
            'genre',
            'imdb_score',
            'name',
        ]  

Views.py

from rest_framework.response import Response
from rest_framework import generics
from .serializers import MovieSerializer
from movie.models import Movie
from rest_framework import status
from rest_framework.permissions import IsAuthenticated

class MovieList(generics.ListCreateAPIView):
    queryset = Movie.objects.all().order_by('-id')[:10]
    serializer_class = MovieSerializer
    permission_classes = (IsAuthenticated,)

    def list(self, request):
        queryset = self.get_queryset()
        serializer = MovieSerializer(queryset, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        data = request.data
        if isinstance(data, list):  # <- is the main logic
            serializer = self.get_serializer(data=request.data, many=True)
        else:
            serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Ces lignes représentent la logique réelle de Multiple Instance -

data = request.data
if isinstance(data, list):  # <- is the main logic
      serializer = self.get_serializer(data=request.data, many=True)
else:
      serializer = self.get_serializer(data=request.data)

Si vous êtes confus avec plusieurs = True, voyez ceci

Lorsque nous envoyons des données, elles seront à l'intérieur de list, un peu comme ceci -

[
    {
        "popularity": 84.0,
        "director": "Stanley Kubrick",
        "genre": [
            1,
            6,
            10
        ],
        "imdb_score": 8.4,
        "name": "2001 : A Space Odyssey"
    },
    {
        "popularity": 84.0,
        "director": "Stanley Kubrick",
        "genre": [
            1,
            6,
            10
        ],
        "imdb_score": 8.4,
        "name": "2001 : A Space Odyssey"
    }
]
1
Huzaif Sayyed

La méthode la plus simple que j'ai rencontrée:

    def post(self, request, *args, **kwargs):
        serializer = ThatSerializer(data=request.data, many=isinstance(request.data, list))
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
0
popen