2013-07-09 6 views
17

Ich habe ein Objekt mit optionalen Feldern. Ich habe meine Serializer auf diese Weise definiert:Django REST Framework - Serialisierung von optionalen Feldern

class ProductSerializer(serializers.Serializer): 
    code = serializers.Field(source="Code") 
    classification = serializers.CharField(source="Classification", required=False) 

ich thoughtrequired=False würde die Aufgabe, unter Umgehung des Feldes tun, wenn es nicht existiert. In der Dokumentation wird jedoch erwähnt, dass dies die Deserialisierung und nicht die Serialisierung betrifft.

Ich erhalte die folgende Fehlermeldung:

'Product' object has no attribute 'Classification' 

Welche geschieht, wenn ich versuche, .data der serialisierten Instanz zugreifen. (Bedeutet das nicht, dass es eine Deserialisierung ist, die das erhöht?)

Das passiert für Instanzen, die Classification nicht haben. Wenn ich Classification aus der Serialisierungsklasse weglasse, funktioniert es gut.

Wie mache ich das richtig? Serialisieren Sie ein Objekt mit optionalen Feldern.

+0

Ist es akzeptabel, für diese Felder als 'None' serialisiert werden, oder sollte der Schlüssel gar nicht vorhanden sein? –

+0

Sie sind überhaupt nicht vorhanden, ich rufe einen SOAP-Webdienst auf, der optionale Felder mit suds hat, das Antwortobjekt stellt das zurückgegebene XML dar, das in bestimmten Fällen das optionale Feld nicht enthält. – abstractpaper

+0

Tom Ich habe gerade realisiert, was du meintest; Idealerweise möchte ich, dass sie gar nicht anwesend sind, aber ich kann vorläufig mit "None" leben. – abstractpaper

Antwort

9

Die Serializer sind bewusst so konzipiert, dass sie einen festen Satz von Feldern verwenden, so dass Sie nicht einfach einen der Tasten auswerfen können.

Sie könnten SerializerMethodField verwenden, um entweder den Feldwert zurückzugeben, oder None, wenn das Feld nicht existiert, oder Sie könnten Serialisierer überhaupt nicht verwenden und einfach eine Ansicht schreiben, die die Antwort direkt zurückgibt.

Update für REST-Framework 3.0serializer.fields kann auf einem instanziierten Serializer geändert werden. Wenn dynamische Serialisierungsklassen erforderlich sind, würde ich wahrscheinlich vorschlagen, die Felder in einer benutzerdefinierten Serializer.__init__()-Methode zu ändern.

+7

Wenn man bedenkt, wie freundlich das Framework für mein Projekt bisher war, wäre es schön, eine Standardoption für Schlüssel zu haben, die nicht standardmäßig auf 'None' existieren. – abstractpaper

+0

@TomChristie Hey Tom, jetzt, da DRF 3 draußen ist, könntest du deine Antwort auf die empfohlene Weise mit DRF 3 aktualisieren? Ich frage, weil ein paar Leute verschiedene Wege vorschlagen, DRF 3 zu verwenden, und ich bin mir nicht sicher, was die beste Option ist (siehe Marks Antwort unten oder David's Antwort hier: http://stackoverflow.com/questions/27015931/ remove-null-fields-from-django-rest-framework-response für Beispiele, wie dies mit DRF 3 erreicht werden kann.Was würdest du sagen ist der beste Weg mit DRF 3?) – user2719875

16

Django REST-Framework 3.0+
Dynamische Felder jetzt unterstützt werden, finden http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields - dieser Ansatz alle Felder in der Serializer definiert, und dann können Sie selektiv diejenigen, entfernen Sie nicht wollen.

Oder Sie könnten auch für ein Modell Serializer etwas tun, wo man herumspielen mit Meta.fields im Serializer init:

class ProductSerializer(serializers.ModelSerializer): 
    class Meta: 
     model = Product 
     fields = ('code',) 

    def __init__(self, *args, **kwargs): 
     if SHOW_CLASSIFICATION: # add logic here for optional viewing 
      self.Meta.fields = list(self.Meta.fields) 
      self.Meta.fields.append('classification') 
     super(ProductSerializer, self).__init__(*args, **kwargs) 

Sie haben würde Tom fragen aber, wenn dies ist die " richtiger Weg ", da es möglicherweise nicht in den langfristigen Plan passt.

Django REST-Framework < 3.0
versuchen, etwas wie folgt aus:

class ProductSerializer(serializers.Serializer): 
    ... 
    classification = serializers.SerializerMethodField('get_classification') 

    def get_classification(self, obj): 
     return getattr(obj, 'classification', None) 

Multiple Serializer

Ein anderer Ansatz wäre, mehrere Serializer mit verschiedenen Sätzen von Feldern zu erstellen. Ein Serializer erbt von einem anderen und fügt zusätzliche Felder hinzu. Dann können Sie in der Ansicht mit der Methode get_serializer_class den entsprechenden Serializer auswählen. Hier ist ein aktuelles Beispiel dafür, wie ich mit diesem Ansatz verschiedene Serializer aufrufen kann, um unterschiedliche Benutzerdaten darzustellen, wenn das Benutzerobjekt mit dem Anforderungsbenutzer identisch ist.

def get_serializer_class(self): 
    """ An authenticated user looking at their own user object gets more data """ 
    if self.get_object() == self.request.user: 
     return SelfUserSerializer 
    return UserSerializer 

Entfernen von Feldern aus Darstellung

Einen anderen Ansatz, den ich in Sicherheitskontexten verwendet habe, sind Felder in den to_representation Verfahren zu entfernen. Definieren Sie eine Methode, wie

def remove_fields_from_representation(self, representation, remove_fields): 
    """ Removes fields from representation of instance. Call from 
    .to_representation() to apply field-level security. 
    * remove_fields: a list of fields to remove 
    """ 
    for remove_field in remove_fields: 
     try: 
      representation.pop(remove_field) 
     except KeyError: 
      # Ignore missing key -- a child serializer could inherit a "to_representation" method 
      # from its parent serializer that applies security to a field not present on 
      # the child serializer. 
      pass 

und dann in Ihrem Serializer, rufen Sie diese Methode wie

def to_representation(self, instance): 
    """ Apply field level security by removing fields for unauthorized users""" 
    representation = super(ProductSerializer, self).to_representation(instance) 
    if not permission_granted: # REPLACE WITH PERMISSION LOGIC 
     remove_fields = ('classification',) 
     self.remove_fields_from_representation(representation, remove_fields) 
    return representation 

Dieser Ansatz ist einfach und flexibel, aber es kommt auf Kosten der Serialisierung Felder, die manchmal nicht angezeigt werden. Aber das ist wahrscheinlich in Ordnung.

+0

Dies ist oft Aufgabe, Daten bedingt auf Erlaubnis zu zeigen, ich wundere mich, dass es keinen bequemen Weg, damit umzugehen. Es sieht so aus, als würde Ihr erster Ansatz funktionieren, aber es ist eine Art Affepatching, und Ihre ProductSerializer.Meta.Felder werden jedes Mal größer, wenn Sie eine neue Instanz erstellen und Sie verlieren den ersten Zustand – meteor

+0

@meteor Ich habe am Ende ein weiteres Beispiel hinzugefügt wie ich dieses Problem heutzutage für Sicherheitszwecke löse. Ich empfehle nicht, das von Ihnen erwähnte Beispiel zu verwenden. –

+0

Ist das erste Beispiel nicht sehr nah an dem Vorschlag aus der Dokumentation? Ich bin ein Noob und verwirrt, warum das kein guter Ansatz ist? Quelle: http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields –

0

Von dem "es ist ein schrecklicher Hack, der sich auf spezifische Implementierungsdetails von DRF und Django stützt, aber es funktioniert (zumindest für jetzt)" -Dateien, hier ist der Ansatz, den ich verwendet habe, einige zusätzliche Debugging-Daten in die Antwort von ein „create“ Methode Implementierung auf einem Serializer:

def create(self, validated_data) 
    # Actual model instance creation happens here... 
    self.fields["debug_info"] = serializers.DictField(read_only=True) 
    my_model.debug_info = extra_data 
    return my_model 

Dies ist eine vorübergehende Lösung ist, dass ich die durchsuchbaren API verwenden, kann von einem bestimmten Remote-Service während des Erstellungsprozesses erhielten einige der rohen Antwortdaten angezeigt werden soll. In der Zukunft bin ich geneigt, diese Fähigkeit zu behalten, aber verstecke sie hinter einem "Berichtdebugging-Info" -Flag in der Erstellungsanfrage, anstatt die Informationen auf niedrigerer Ebene standardmäßig zurückzugeben.

0

Zu diesem Zweck haben die Serialisierer das Argument partial. Wenn der Serializer initialisiert wird, können Sie partial=True übergeben. Wenn Sie Generika oder Mixins verwenden, können Sie die Funktion get_serializer Übergehungseinrichtung wie folgt:

def get_serializer(self, *args, **kwargs): 
    kwargs['partial'] = True 
    return super(YOUR_CLASS, self).get_serializer(*args, **kwargs) 

Und das wird es tun.

Hinweis: Dadurch können alle Felder optional und nicht nur eine bestimmte sein. Wenn Sie nur spezifische Informationen wünschen, können Sie die Methode überschreiben (d. H. Aktualisieren) und für verschiedene Felder Validierungen der Existenz hinzufügen.

0

Die unten beschriebene Methode hat die Arbeit für mich erledigt. Ziemlich einfach, einfach und funktionierte für mich.

DRF-Version verwendet = djangorestframework (3.1.0)

class test(serializers.Serializer): 
    id= serializers.IntegerField() 
    name=serializers.CharField(required=False,default='some_default_value')