webentwicklung-frage-antwort-db.com.de

Django REST Framework - Serialisierung optionaler Felder

Ich habe ein Objekt mit optionalen Feldern. Ich habe meinen Serializer folgendermaßen definiert:

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

Ich dachterequired=False würde das Feld umgehen, wenn es nicht existiert. In der Dokumentation wird jedoch erwähnt, dass dies die Deserialisierung und nicht die Serialisierung beeinflusst.

Ich erhalte folgende Fehlermeldung:

'Product' object has no attribute 'Classification'

Was passiert, wenn ich versuche, auf .data der serialisierten Instanz zuzugreifen. (Bedeutet das nicht, dass es eine Deserialisierung ist, die das erhöht?)

Dies geschieht für Instanzen, die keine Classification haben. Wenn ich Classification aus der Serialisiererklasse weglasse, funktioniert das einwandfrei.

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

24
Aziz Alfoudari

Die Serialisierer sind absichtlich so konzipiert, dass sie einen festen Satz von Feldern verwenden, so dass Sie einen der Schlüssel nicht ohne weiteres freigeben können.

Sie können ein SerializerMethodField verwenden, um entweder den Feldwert oder None zurückzugeben, wenn das Feld nicht vorhanden ist, oder Sie könnten keine Serialisierer verwenden und schreiben einfach eine Ansicht, in der die Antwort direkt zurückgegeben wird.

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

10
Tom Christie

Django REST Framework 3.0+
Dynamische Felder werden jetzt unterstützt, siehe http://www.Django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields - Dieser Ansatz definiert alle Felder in der Serializer, und ermöglicht Ihnen dann, selektiv diejenigen zu entfernen, die Sie nicht möchten.

Oder Sie könnten auch so etwas für einen Model Serializer machen, wo Sie Meta.fields in der Serializer-Instanz durcheinanderbringen:

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 müssen Tom allerdings fragen, ob dies der "richtige Weg" ist, da er möglicherweise nicht in den langfristigen Plan passt.

Django REST Framework <3.0
Versuchen Sie so etwas:

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 das Erstellen mehrerer Serialisierer mit unterschiedlichen Feldgruppen. Ein Serializer erbt von einem anderen und fügt zusätzliche Felder hinzu. Dann können Sie mit der get_serializer_class-Methode den entsprechenden Serializer in der Ansicht auswählen. Hier ist ein aktuelles Beispiel, wie ich diesen Ansatz zum Aufrufen verschiedener Serialisierer zum Anzeigen unterschiedlicher Benutzerdaten verwende, 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 der Darstellung

Ein anderer Ansatz, den ich in Sicherheitskontexten verwendet habe, ist das Entfernen von Feldern in der Methode to_representation. 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 rufen Sie dann in Ihrem Serializer diese Methode auf

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 unkompliziert und flexibel, kostet jedoch die Serialisierung von Feldern, die manchmal nicht angezeigt werden. Aber das ist wahrscheinlich in Ordnung.

19
Mark Chackerian

Die unten beschriebene Methode erledigte die Arbeit für mich ... ziemlich einfach, leicht und funktionierte für mich.

Verwendete DRF-Version = Djangorestframework (3.1.0)

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

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

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

Und das wird den Trick tun.

Hinweis: Damit können alle Felder optional und nicht nur ein bestimmtes sein. Wenn Sie nur bestimmte Informationen wünschen, können Sie die Methode überschreiben (d. H. Aktualisieren) und Gültigkeitsprüfungen für verschiedene Felder hinzufügen.

0
Uri Shalit

Von den "Es ist ein schrecklicher Hack, der sich auf spezifische Implementierungsdetails von DRF und Django stützt, aber es funktioniert (zumindest für jetzt)". Hier habe ich einige zusätzliche Debugging-Daten in die Antwort einer "create" -Methode eingefügt 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 ein vorübergehender Ansatz, mit dem ich die durchsuchbare API verwenden kann, um einige der rohen Antwortdaten anzuzeigen, die während des Erstellungsprozesses von einem bestimmten Remote-Service empfangen wurden. In Zukunft neige ich dazu, diese Funktion beizubehalten, verstecke sie jedoch hinter einem Flag "Report Debugging Info" in der Erstellungsanforderung, anstatt standardmäßig die Informationen der unteren Ebene zurückzugeben.

0
ncoghlan

DynamicSerializer für DRF 3, mit dem dynamisch angegeben werden kann welche Felder werden im Serializer verwendet, welche werden ausgeschlossen und welche werden optional benötigt!

  1. Mixin erstellen
    class DynamicSerializerMixin:
        """
        A Serializer that takes an additional `fields` argument that
        controls which fields should be used.
        """

        def __init__(self, *args, **kwargs):
            # Don't pass the 'fields' arg up to the superclass
            fields = kwargs.pop("fields", None)
            excluded_fields = kwargs.pop("excluded_fields", None)
            required_fields = kwargs.pop("required_fields", None)

            # Instantiate the superclass normally
            super().__init__(*args, **kwargs)

            if fields is not None:
                # Drop any fields that are not specified in the `fields` argument.
                allowed = set(fields)
                existing = set(self.fields)
                for field_name in existing - allowed:
                    self.fields.pop(field_name)

                if isinstance(fields, dict):
                    for field, config in fields.items():
                        set_attrs(self.fields[field], config)

            if excluded_fields is not None:
                # Drop any fields that are not specified in the `fields` argument.
                for field_name in excluded_fields:
                    self.fields.pop(field_name)

            if required_fields is not None:
                for field_name in required_fields:
                    self.fields[field_name].required = True
  1. Initialisieren/Anpassen Ihres Serializers durch Hinzufügen von DynamicSerializerMixin zur Vererbung

class UserProfileSerializer(DynamicSerializerMixin, serializers.ModelSerializer):

    class Meta:
        model = User
        fields = (
            "id",
            'first_name', 'last_name'
            "email",
            "is_staff",
        )
  1. Benutze es :)
class RoleInvitationSerializer(serializers.ModelSerializer):
    invited_by = UserProfileSerializer(fields=['id', 'first_name', 'last_name'])

oder in Aktion apis

    @action(detail=True, serializer_class=YourSerialzierClass)
    def teams_roles(self, request, pk=None):
        user = self.get_object()
        queryset = user.roles.all()
        serializer = self.get_serializer(queryset, many=True, excluded_fields=['user'])
        return Response(data=serializer.data)
0
pymen