2016-07-11 9 views
5

Betrachten Sie diesen Fall, wo ich ein Book und Author Modell habe.Ändern Sie ein Feld in einem Django REST Framework ModelSerializer basierend auf dem Anforderungstyp?

serializers.py

class AuthorSerializer(serializers.ModelSerializer): 

    class Meta: 
     model = models.Author 
     fields = ('id', 'name') 

class BookSerializer(serializers.ModelSerializer): 
    author = AuthorSerializer(read_only=True) 

    class Meta: 
     model = models.Book 
     fields = ('id', 'title', 'author') 

viewsets.py

class BookViewSet(viewsets.ModelViewSet): 
    queryset = Book.objects.all() 
    serializer_class = BookSerializer 

Dies funktioniert gut, wenn ich eine GET Anfrage für ein Buch schicken. Ich bekomme eine Ausgabe mit einem verschachtelten Serializer, der die Buchdetails und die verschachtelten Autorendetails enthält, was ich will.

Allerdings, wenn ich möchte ein Buch erstellen/aktualisieren, ich habe ein gerade ihre ID statt POST/PUT/PATCH mit den verschachtelten Details des Autors zu senden. Ich möchte in der Lage sein, ein Buchobjekt zu erstellen/aktualisieren, indem ich eine Autoren-ID und nicht das gesamte Autorenobjekt spezifiziere.

Also, etwas, wo mein Serializer für eine GET Anfrage wie folgt aussieht

class BookSerializer(serializers.ModelSerializer): 
    author = AuthorSerializer(read_only=True) 

    class Meta: 
     model = models.Book 
     fields = ('id', 'title', 'author') 

und meine Serializer sieht aus wie dies für eine POST, PUT, PATCH Anfrage

class BookSerializer(serializers.ModelSerializer): 
    author = PrimaryKeyRelatedField(queryset=Author.objects.all()) 

    class Meta: 
     model = models.Book 
     fields = ('id', 'title', 'author') 

ich auch nicht wollen um zwei völlig separate Serialisierer für jede Art von Anfrage zu erstellen. Ich möchte nur das author Feld in der BookSerializer ändern.

Schließlich gibt es eine bessere Möglichkeit, diese ganze Sache zu tun?

+0

Schauen Sie auf http://www.django-rest-framework.org/api-guide/routers/ - fügen Sie Dekorateure hinzu, die Ihren Bedürfnissen entsprechen. – dmitryro

+0

@ dmitryro Ich verstehe es nicht. Könnten Sie bitte weiter erklären? Wie würde das Hinzufügen von Dekoratoren Felder für Serializer ändern? –

+0

Sie müssen einen benutzerdefinierten Router erstellen, der verschiedene Anforderungsmethoden verarbeitet - POST, GET, PUT und Ihre Methoden auf der Grundlage der gewünschten Anforderungsmethode dekorieren - Dokumentation enthält einige Beispiele. Siehe auch http://stackoverflow.com/questions/28957912/overriding-django-rest-viewset-with-custom-post-method-and-model – dmitryro

Antwort

5

IMHO, mehrere Serialisierer werden nur mehr und mehr Verwirrung schaffen.

Eher würde ich unten Lösung bevorzugen:

  1. nicht Ihre Viewset Ändern Sie (lassen Sie es Standard)
  2. hinzufügen .validate() -Methode in Ihrem Serializer; zusammen mit anderen erforderlichen .create oder .update() etc. Hier wird echte Logik in validate() -Methode gehen. Wo basierend auf Anforderungstyp erstellen wir valided_data dict wie von unserem Serializer erforderlich.

Ich denke, das ist der sauberste Ansatz.

Siehe mein ähnliches Problem und Lösung bei DRF: Allow all fields in GET request but restrict POST to just one field

+0

Dies ist die eleganteste Lösung für dieses Problem! – Ajoy

0

Die Art und Weise, wie ich mit diesem Problem fertig wurde, hatte einen anderen Serialisierer, wenn es ein verwandtes Feld ist.

class HumanSerializer(PersonSerializer): 

    class Meta: 
     model = Human 
     fields = PersonSerializer.Meta.fields + (
      'firstname', 
      'middlename', 
      'lastname', 
      'sex', 
      'date_of_birth', 
      'balance' 
     ) 
     read_only_fields = ('name',) 


class HumanRelatedSerializer(HumanSerializer): 
    def to_internal_value(self, data): 
     return self.Meta.model.objects.get(id=data['id']) 


class PhoneNumberSerializer(serializers.ModelSerializer): 
    contact = HumanRelatedSerializer() 

    class Meta: 
     model = PhoneNumber 
     fields = (
      'id', 
      'contact', 
      'phone', 
      'extension' 
     ) 

Sie könnten etwas tun, aber für die RelatedSerializer tun:

def to_internal_value(self, data): 
    return self.Meta.model.objects.get(id=data) 

Wenn also die Serialisierung, können Sie das zugehörige Objekt serialisiert werden, und wenn-Serialisierung de, müssen Sie nur die ID Holen Sie sich das verwandte Objekt.

+0

Hat dies getan. Funktioniert nicht. Mache ich etwas falsch? 'Klasse AuthorRelatedSerializer (AuthorSerializer): def to_internal_value (self, Daten): return self.Meta.model.objects.get (id = data [ 'id']) Klasse BookSerializer (serializers.ModelSerializer): author = AuthorRelatedSerializer() ' –

+0

Mein Beispiel erwartet {id: 6, ...}, wenn Sie nur eine Ganzzahl übergeben, sollte es' def to_internal_value (self, data) sein: return self.Meta.model. objects.get (id = data) ' – miyamoto

2

Sie suchen nach der get_serializer_class Methode auf der ViewSet. Auf diese Weise können Sie den Anforderungstyp für den zu verwendenden Serializer ändern.

from rest_framework import viewsets 

class MyModelViewSet(viewsets.ModelViewSet): 

    model = MyModel 
    queryset = MyModel.objects.all() 

    def get_serializer_class(self): 
     if self.action in ('create', 'update', 'partial_update'): 
      return MySerializerWithPrimaryKeysForCreatingOrUpdating 
     else: 
      return MySerializerWithNestedData 
+0

Dies ist ein möglicher Weg, der funktionieren könnte, aber ich möchte nicht zwei völlig getrennte Serialisierer deklarieren, da dies überflüssig wäre. Ich habe mich gefragt, ob es einen besseren Weg gibt, dies in einem Serialisierer zu tun. –

2

Es gibt eine Funktion von DRF ist, wo Sie dynamisch die Felder auf dem Serializer http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields

Mein Anwendungsfall ändern können: Verwendung Slug Feld auf GET so können wir sehen, Nice rep einer Relation, aber bei POST/PUT wechseln Sie zurück zum klassischen Primärschlüssel-Update. Passen Sie Ihre Serializer etwas wie folgt aus:

class FooSerializer(serializers.ModelSerializer): 
    bar = serializers.SlugRelatedField(slug_field='baz', queryset=models.Bar.objects.all()) 

    class Meta: 
     model = models.Foo 
     fields = '__all__' 

    def __init__(self, *args, **kwargs): 
     super(FooSerializer, self).__init__(*args, **kwargs) 

     try: 
      if self.context['request'].method in ['POST', 'PUT']: 
       self.fields['bar'] = serializers.PrimaryKeyRelatedField(queryset=models.Bar.objects.all()) 
     except KeyError: 
      pass 

Der KeyError manchmal auf Code Initialisierung ohne eine Anfrage ausgelöst wird, möglicherweise Unit-Tests.

Genießen Sie und verwenden Sie verantwortlich.