2015-04-29 7 views
33

Mit Django REST Framework ermöglicht ein Standard-ModelSerializer, dass ForeignKey-Modellbeziehungen zugewiesen oder geändert werden, indem eine ID als Integer POST wird.DRF: Einfache Fremdschlüsselzuweisung mit verschachtelten Serialisierern?

Was ist die einfachste Weg, um dieses Verhalten aus einem verschachtelten Serializer zu bekommen?

Hinweis, ich spreche nur über die Zuweisung vorhandener Datenbankobjekte, nicht verschachtelte Erstellung.

Ich habe gehackt in der Vergangenheit um diese weg mit zusätzlichen ‚id‘ Felder in der Serializer und mit benutzerdefinierten create und update Methoden, aber dies ist eine solche scheinbar einfachen und häufigen Problem für mich, dass ich neugierig bin ich zu wissen bester Weg.

class Child(models.Model): 
    name = CharField(max_length=20) 

class Parent(models.Model): 
    name = CharField(max_length=20) 
    phone_number = models.ForeignKey(PhoneNumber) 
    child = models.ForeignKey(Child) 

class ChildSerializer(ModelSerializer): 
    class Meta: 
     model = Child 

class ParentSerializer(ModelSerializer): 
    # phone_number relation is automatic and will accept ID integers 
    children = ChildSerializer() # this one will not 

    class Meta: 
     model = Parent 

Antwort

30

Die beste Lösung hier ist, zwei verschiedene Felder zu verwenden: eins zum Lesen und das andere zum Schreiben. Ohne etwas schwer Heben zu tun, ist es schwierig zu bekommen, was Sie suchen in einem einzigen Feld.

Das schreibgeschützte Feld wäre Ihr geschachtelter Serializer (ChildSerializer in diesem Fall) und es wird Ihnen ermöglichen, die gleiche verschachtelte Darstellung zu erhalten, die Sie erwarten. Die meisten Leute definieren dies als nur child, weil sie bereits ihr Front-End von diesem Punkt geschrieben haben und das Ändern würde Probleme verursachen.

Das schreibgeschützte Feld wäre ein PrimaryKeyRelatedField, das Sie normalerweise für die Zuweisung von Objekten basierend auf ihrem Primärschlüssel verwenden würden. Dies muss nicht schreibgeschützt sein, besonders wenn Sie versuchen, die Symmetrie zwischen dem, was empfangen wird und dem, was gesendet wird, zu suchen, aber es klingt so, als ob Ihnen das am besten passt. In diesem Feld sollte a source auf das Fremdschlüsselfeld (child in diesem Beispiel) gesetzt sein, damit es beim Erstellen und Aktualisieren ordnungsgemäß zugewiesen wird.


Dies wurde in der Diskussionsgruppe ein paar Mal erwähnt, und ich denke, das ist immer noch die beste Lösung. Danke an Sven Maurer for pointing it out.

+0

Kevin Dank. Ich hatte mit dem gleichen Problem zu kämpfen. Ich habe ChildSerializer zwei Felder hinzugefügt. ** parent = ParentSerializer (read_only = True) ** und ** parent_id = serializers.PrimaryKeyRelatedField (...., write_only = True, ....) ** Ich habe auch sowohl ** parent ** als auch ** parent_id hinzugefügt ** zu Feldern des ChildSerializers. Aber ich sehe kein ** child_id ** Feld in der Antwort. Was eigentlich gut und praktisch ist, aber ich frage mich, was ist der Grund dafür? Hast du irgendeine Idee? – hnroot

+0

gute Antwort. nur ein paar Beispielcode wie in Skinny's Antwort (wahrscheinlich unten) fehlt – molecular

1

Ich denke, der von Kevin skizzierte Ansatz wäre wahrscheinlich die beste Lösung, aber ich konnte es nie zum Laufen bringen. DRF hat Fehler geworfen, als ich sowohl einen geschachtelten Serializer als auch einen Primärschlüssel-Feldsatz hatte. Das Entfernen des einen oder anderen würde funktionieren, aber offenbar gab es mir nicht das Ergebnis, das ich brauchte. Das Beste, was ich tun konnte, ist die Schaffung von zwei verschiedenen Serializer zum Lesen und Schreiben, wie so ...

serializers.py:

class ChildSerializer(serializers.ModelSerializer): 
    class Meta: 
     model = Child 

class ParentSerializer(serializers.ModelSerializer): 
    class Meta: 
     abstract = True 
     model = Parent 
     fields = ('id', 'child', 'foo', 'bar', 'etc') 

class ParentReadSerializer(ParentSerializer): 
    child = ChildSerializer() 

views.py

class ParentViewSet(viewsets.ModelViewSet): 
    serializer_class = ParentSerializer 
    queryset = Parent.objects.all() 
    def get_serializer_class(self): 
     if self.request.method == 'GET': 
      return ParentReadSerializer 
     else: 
      return self.serializer_class 
+0

Ich habe, was aussieht wie das gleiche Problem, das Sie getan haben. Haben Sie jemals einen Weg gefunden, um es in einem Serializer zu betreiben? http://StackOverflow.com/Questions/41248271 – shanemgrey

24

Hier ist ein Beispiel von dem, worüber Kevins Antwort spricht, wenn Sie diesen Ansatz verwenden und zwei getrennte Felder verwenden möchten.

In Ihrem models.py ...

class Child(models.Model): 
    name = CharField(max_length=20) 

class Parent(models.Model): 
    name = CharField(max_length=20) 
    phone_number = models.ForeignKey(PhoneNumber) 
    child = models.ForeignKey(Child) 

dann serializers.py ...

class ChildSerializer(ModelSerializer): 
    class Meta: 
     model = Child 

class ParentSerializer(ModelSerializer): 
    # if child is required 
    child = ChildSerializer(read_only=True) 
    # if child is a required field and you want write to child properties through parent 
    # child = ChildSerializer(required=False) 
    # otherwise the following should work (untested) 
    # child = ChildSerializer() 

    child_id = serializers.PrimaryKeyRelatedField(
     queryset=Child.objects.all(), source='child', write_only=True) 

    class Meta: 
     model = Parent 

Einstellung source=child läßt child_id wirkt als Kind standardmäßig wäre, hätte es nicht (unser gewünschtes Verhalten) außer Kraft gesetzt werden. write_only=True macht child_id zum Schreiben verfügbar, aber verhindert, dass es in der Antwort angezeigt wird, da die ID bereits im ChildSerializer angezeigt wird.

+0

Dies funktioniert für mich !, Dank – neosergio

+0

Ich habe die folgende Fehlermeldung msg: 'Get ein TypeError beim Aufruf von Parent.Objects.Create(). Dies kann daran liegen, dass Sie ein beschreibbares Feld in der Serializer-Klasse haben, das kein gültiges Argument für Parent.objects.create() ist. Möglicherweise müssen Sie das Feld schreibgeschützt machen oder die ParentSerializer.create() -Methode überschreiben, um dies richtig zu behandeln. " –

+0

Ursprüngliche Ausnahme war:' 'Benutzer' ist ein ungültiges Schlüsselwort Argument für diese Funktion " –

2

Hier ist, wie ich dieses Problem gelöst habe.

serializers.py

class ChildSerializer(ModelSerializer): 

    def to_internal_value(self, data): 
     if data.get('id'): 
      return get_object_or_404(Child.objects.all(), pk=data.get('id')) 
     return super(ChildSerializer, self).to_internal_value(data) 

Sie müssen nur Ihren verschachteltes Kind Serializer passieren, wie Sie es aus dem Serializer dh Kind als json/Wörterbuch erhalten. In to_internal_value instantiieren wir das Kindobjekt, wenn es eine gültige ID hat, damit DRF weiter mit dem Objekt arbeiten kann.

2

Es gibt einen Weg, um ein Feld erstellen/aktualisieren Betrieb zu ersetzen: für Ihre Antwort

class ChildSerializer(ModelSerializer): 
    class Meta: 
     model = Child 

class ParentSerializer(ModelSerializer): 
    child = ChildSerializer() 

    # called on create/update operations 
    def to_internal_value(self, data): 
     self.fields['child'] = serializers.PrimaryKeyRelatedField(
      queryset=Child.objects.all()) 
     return super(ParentSerializer, self).to_internal_value(data) 

    class Meta: 
     model = Parent 
+0

Wenn Sie DRF 3.0 verwenden, ist dies eine gute Lösung, aber eine Sache zu beachten ist, dass das übergeordnete Element nach dem Erstellen von Parent keine verschachtelte Child-Serialisierung zurückgegeben hat, wird es flach sein (nur der Primärschlüssel). Um das zu beheben, müssen Sie auch die Methode to_representation überschreiben. Ich füge das in meiner Antwort zu einer doppelten Frage hinzu: http://stackoverflow.com/questions/26561640/django-rest-framework-read-nested-data-write-integer/39362061#39362061 – jeffjv

Verwandte Themen