Django Rest Framework - Hinzufügen eines benutzerdefinierten Felds in ModelSerializer

88

Ich habe ein ModelSerializerFeld erstellt und möchte ein benutzerdefiniertes Feld hinzufügen, das nicht Teil meines Modells ist.

Ich habe hier eine Beschreibung zum Hinzufügen zusätzlicher Felder gefunden und Folgendes versucht:

customField = CharField(source='my_field')

Wenn ich dieses Feld hinzufüge und meine validate()Funktion aufrufe, ist dieses Feld nicht Teil des attrDiktats. attrenthält alle angegebenen Modellfelder mit Ausnahme der zusätzlichen Felder. Ich kann also in meiner überschriebenen Validierung nicht auf dieses Feld zugreifen, oder?

Wenn ich dieses Feld wie folgt zur Feldliste hinzufüge:

class Meta:
    model = Account
    fields = ('myfield1', 'myfield2', 'customField')

dann erhalte ich eine Fehlermeldung, weil sie customFieldnicht Teil meines Modells ist - was ist richtig, weil ich sie nur für diesen Serializer hinzufügen möchte.

Gibt es eine Möglichkeit, ein benutzerdefiniertes Feld hinzuzufügen?

Ron
quelle
Könnten Sie auf "Aber wenn mein Feld nicht in der im Serializer angegebenen Modellfeldliste enthalten ist, ist es nicht Teil des validate () attr-Wörterbuchs." Erweitern, bin ich mir nicht sicher, ob das sehr klar ist.
Tom Christie
Auch "es beschwert sich - richtig -, dass ich kein Feld customField in meinem Modell habe.", Könnten Sie die Ausnahme, die Sie sehen, explizit angeben - danke! :)
Tom Christie
Ich habe meinen Beitrag aktualisiert und hoffe, dass es jetzt klarer ist. Ich möchte nur wissen, wie ich ein Feld hinzufügen kann, das nicht Teil meines Modells ist ...
Ron

Antworten:

62

Sie tun das Richtige, außer dass CharField(und die anderen eingegebenen Felder) für beschreibbare Felder sind.

In diesem Fall möchten Sie nur ein einfaches schreibgeschütztes Feld. Verwenden Sie stattdessen einfach:

customField = Field(source='get_absolute_url')
Tom Christie
quelle
4
Danke, aber ich möchte ein beschreibbares Feld. Ich übergebe ein bestimmtes Benutzertoken, das meinen Benutzer identifiziert. aber in meinem Modell habe ich den Benutzer und nicht das Token. Daher möchte ich das Token übergeben und über eine Abfrage in ein Benutzerobjekt "konvertieren".
Ron
Das nächste ist, dass die Quelle auf ein Modellattribut abzielen muss, oder? In meinem Fall habe ich kein Attribut, auf das ich zeigen kann.
Ron
Ich verstehe das Benutzer- / Token-Bit des Kommentars nicht. Wenn Sie jedoch ein Feld in den Serializer aufnehmen möchten, das vor der Wiederherstellung in einer Modellinstanz entfernt wird, können Sie .validate()das Attribut mithilfe der Methode entfernen. Siehe: django-rest-framework.org/api-guide/serializers.html#validation Das würde tun, was Sie brauchen, obwohl ich den Anwendungsfall nicht wirklich verstehe oder warum Sie ein beschreibbares Feld wollen, das an a gebunden ist schreibgeschützte Eigenschaft get_absolute_url?
Tom Christie
Vergiss das, was get_absolute_urlich gerade aus den Dokumenten kopiert und eingefügt habe. Ich möchte nur ein normales beschreibbares Feld, auf das ich im zugreifen kann validate(). Ich habe nur vermutet, dass ich das sourceAttribut brauchen würde ...
Ron
Das macht mehr Sinn. :) Der Wert sollte zur Validierung übergeben werden, daher würde ich überprüfen, wie Sie den Serializer instanziieren und ob dieser Wert wirklich bereitgestellt wird.
Tom Christie
82

In der Tat gibt es eine Lösung, ohne das Modell überhaupt zu berühren. Sie können verwenden, mit SerializerMethodFielddenen Sie eine beliebige Methode an Ihren Serializer anschließen können.

class FooSerializer(ModelSerializer):
    foo = serializers.SerializerMethodField()

    def get_foo(self, obj):
        return "Foo id: %i" % obj.pk
Idaho
quelle
4
Wie OP in diesem Kommentar erwähnt , wollen sie ein beschreibbares Feld, das SerializerMethodFieldnicht
esmail
14

... aus Gründen der Übersichtlichkeit, wenn Sie eine Modellmethode wie folgt definiert haben:

class MyModel(models.Model):
    ...

    def model_method(self):
        return "some_calculated_result"

Sie können das Ergebnis des Aufrufs dieser Methode wie folgt zu Ihrem Serializer hinzufügen:

class MyModelSerializer(serializers.ModelSerializer):
    model_method_field = serializers.CharField(source='model_method')

ps Da das benutzerdefinierte Feld in Ihrem Modell nicht wirklich ein Feld ist, möchten Sie es normalerweise schreibgeschützt machen, wie folgt:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
quelle
2
Was ist, wenn ich möchte, dass es beschreibbar ist?
Csaba Toth
1
@Csaba: Sie müssen nur zu schreiben benutzerdefinierte speichern und Löschen Haken für den zusätzlichen Inhalt: Siehe „Speichern und Löschen Haken“ unter „Methoden“ ( hier ) Sie werden zu schreiben benutzerdefinierte benötigen perform_create(self, serializer), perform_update(self, serializer), perform_destroy(self, instance).
Lindauson
13

Hier Antwort auf Ihre Frage. Sie sollten Ihrem Modellkonto Folgendes hinzufügen:

@property
def my_field(self):
    return None

Jetzt können Sie verwenden:

customField = CharField(source='my_field')

Quelle: https://stackoverflow.com/a/18396622/3220916

va-dev
quelle
6
Ich habe diesen Ansatz verwendet, wenn es sinnvoll ist, aber es ist nicht großartig, Modellen zusätzlichen Code für Dinge hinzuzufügen, die nur für bestimmte API-Aufrufe verwendet werden.
Andy Baker
1
Sie können ein Proxy-Modell für die
Ashwoods
9

Zu zeigen self.author.full_name, ich habe einen Fehler mit Field. Es funktionierte mit ReadOnlyField:

class CommentSerializer(serializers.HyperlinkedModelSerializer):
    author_name = ReadOnlyField(source="author.full_name")
    class Meta:
        model = Comment
        fields = ('url', 'content', 'author_name', 'author')
François Constant
quelle
6

Bei der letzten Version von Django Rest Framework müssen Sie in Ihrem Modell eine Methode mit dem Namen des Felds erstellen, das Sie hinzufügen möchten.

class Foo(models.Model):
    . . .
    def foo(self):
        return 'stuff'
    . . .

class FooSerializer(ModelSerializer):
    foo = serializers.ReadOnlyField()

    class Meta:
        model = Foo
        fields = ('foo',)
Guillaume Vincent
quelle
3

Ich suchte nach einer Lösung zum Hinzufügen eines beschreibbaren benutzerdefinierten Felds zu einem Modell-Serialisierer. Ich habe diesen gefunden, der in den Antworten auf diese Frage nicht behandelt wurde.

Es scheint, als müssten Sie tatsächlich Ihren eigenen einfachen Serializer schreiben.

class PassThroughSerializer(serializers.Field):
    def to_representation(self, instance):
        # This function is for the direction: Instance -> Dict
        # If you only need this, use a ReadOnlyField, or SerializerField
        return None

    def to_internal_value(self, data):
        # This function is for the direction: Dict -> Instance
        # Here you can manipulate the data if you need to.
        return data

Jetzt können Sie diesen Serializer verwenden, um einem ModelSerializer benutzerdefinierte Felder hinzuzufügen

class MyModelSerializer(serializers.ModelSerializer)
    my_custom_field = PassThroughSerializer()

    def create(self, validated_data):
        # now the key 'my_custom_field' is available in validated_data
        ...
        return instance

Dies funktioniert auch, wenn das Modell MyModeltatsächlich eine Eigenschaft namens hat my_custom_field, Sie jedoch die Validatoren ignorieren möchten.

David Schumann
quelle
0

Nachdem ich alle Antworten hier gelesen habe, bin ich zu dem Schluss gekommen, dass es unmöglich ist, dies sauber zu machen. Sie haben schmutzig zu spielen und etwas hadkish zu tun wie ein WRITE_ONLY Feld zu erzeugen und dann die außer Kraft setzen validateund to_representationMethoden. Das hat bei mir funktioniert:

class FooSerializer(ModelSerializer):

    foo = CharField(write_only=True)

    class Meta:
        model = Foo
        fields = ["foo", ...]

    def validate(self, data):
        foo = data.pop("foo", None)
        # Do what you want with your value
        return super().validate(data)

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data["foo"] = whatever_you_want
        return data
Ariel
quelle