Django Rest Framework - Jak dodać niestandardowe pole w ModelSerializer

89

Utworzyłem ModelSerializeri chcę dodać pole niestandardowe, które nie jest częścią mojego modelu.

Znalazłem opis, aby dodać dodatkowe pola tutaj i starałem się następujące elementy:

customField = CharField(source='my_field')

Kiedy dodam to pole i wywołam moją validate()funkcję, to pole nie jest częścią attrdyktatu. attrzawiera wszystkie określone zmienne modelu z wyjątkiem pól dodatkowych. Więc nie mogę uzyskać dostępu do tego pola w mojej nadpisanej walidacji, prawda?

Kiedy dodam to pole do listy pól w następujący sposób:

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

wtedy customFieldpojawia się błąd, ponieważ nie jest częścią mojego modelu - co jest poprawne, ponieważ chcę go dodać tylko dla tego serializatora.

Czy istnieje sposób na dodanie niestandardowego pola?

Ron
źródło
Czy mógłbyś rozwinąć „Ale kiedy moje pole nie znajduje się na liście pól modelu określonej w serializatorze, nie jest częścią słownika atr-validate ()”, nie jestem pewien, czy jest to bardzo jasne.
Tom Christie,
Również „narzeka - poprawnie - że nie mam pola customField w moim modelu.”, Czy mógłbyś wyraźnie określić wyjątek, który widzisz - dzięki! :)
Tom Christie
Zaktualizowałem swój post i mam nadzieję, że teraz jest jaśniejszy. Chcę tylko wiedzieć, jak mogę dodać pole, które nie jest częścią mojego modelu ...
Ron

Odpowiedzi:

63

Robisz dobrze, z wyjątkiem tego, że CharField(i inne wpisane pola) są przeznaczone do pól zapisywalnych.

W tym przypadku potrzebujesz prostego pola tylko do odczytu, więc zamiast tego użyj:

customField = Field(source='get_absolute_url')
Tom Christie
źródło
4
dzięki, ale chcę mieć pole z możliwością zapisu. Przekazuję pewien token użytkownika, który identyfikuje mojego użytkownika. ale w moim modelu mam użytkownika, a nie token. więc chcę przekazać token i „przekonwertować” go na obiekt użytkownika za pośrednictwem zapytania.
Ron,
następną rzeczą jest to, że źródło musi wskazywać na atrybut modelu, prawda? w moim przypadku nie mam atrybutu, na który można by wskazać.
Ron
Nie rozumiem bitu użytkownika / tokena komentarza. Ale jeśli chcesz dołączyć pole do serializatora, które zostanie usunięte przed przywróceniem do wystąpienia modelu, możesz użyć .validate()metody, aby usunąć atrybut. Zobacz: django-rest-framework.org/api-guide/serializers.html#validation To zrobi to, czego potrzebujesz, chociaż tak naprawdę nie rozumiem przypadku użycia lub dlaczego chcesz, aby pole do zapisu, które było powiązane z właściwość tylko do odczytuget_absolute_url ?
Tom Christie,
zapomnij o get_absolute_urlskopiowaniu i wklejeniu go z dokumentów. Chcę tylko normalnego pola do zapisu, do którego mam dostęp w validate(). Zgadłem, że będę potrzebował sourceatrybutu ...
Ron
To ma więcej sensu. :) Wartość powinna być przekazywana w celu walidacji, więc dokładnie sprawdzę, w jaki sposób tworzysz instancję serializatora i czy ta wartość naprawdę jest dostarczana.
Tom Christie,
82

W rzeczywistości istnieje rozwiązanie bez dotykania modelu. Możesz użyć SerializerMethodFieldktóre pozwalają na podłączenie dowolnej metody do twojego serializatora.

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

    def get_foo(self, obj):
        return "Foo id: %i" % obj.pk
Idaho
źródło
6
Jak OP wspomniał w tym komentarzu , chcą pola do zapisu, którego SerializerMethodFieldnie ma
esmail
14

... dla jasności, jeśli masz metodę modelu zdefiniowaną w następujący sposób:

class MyModel(models.Model):
    ...

    def model_method(self):
        return "some_calculated_result"

Możesz dodać wynik wywołania tej metody do swojego serializatora w następujący sposób:

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

ps Ponieważ pole niestandardowe nie jest tak naprawdę polem w Twoim modelu, zazwyczaj będziesz chciał ustawić je jako tylko do odczytu, na przykład:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
źródło
3
A jeśli chcę, aby było możliwe do zapisania?
Csaba Toth
1
@Csaba: Będziesz wystarczy napisać niestandardowy zapisać i haków usunięć dodatkowej treści: patrz „Zapisz i haki skreślenie” pod „Metody” ( Tutaj ) Musisz napisać niestandardowy perform_create(self, serializer), perform_update(self, serializer), perform_destroy(self, instance).
Lindauson
13

tutaj odpowiedź na twoje pytanie. należy dodać do swojego modelu Konto:

@property
def my_field(self):
    return None

teraz możesz użyć:

customField = CharField(source='my_field')

źródło: https://stackoverflow.com/a/18396622/3220916

va-dev
źródło
6
Użyłem tego podejścia, gdy ma to sens, ale nie jest dobrze dodawać dodatkowy kod do modeli dla rzeczy, które są naprawdę używane tylko do określonych wywołań API.
Andy Baker
1
Możesz napisać model proxy dla
jesionu
10

Aby pokazać self.author.full_name, wystąpił błąd z Field. Działało z 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
źródło
6

W ostatniej wersji Django Rest Framework musisz stworzyć w swoim modelu metodę z nazwą pola, które chcesz dodać.

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

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

    class Meta:
        model = Foo
        fields = ('foo',)
Guillaume Vincent
źródło
4

Szukałem rozwiązania umożliwiającego dodanie zapisywalnego pola niestandardowego do serializatora modelu. Znalazłem ten, który nie został uwzględniony w odpowiedziach na to pytanie.

Wygląda na to, że naprawdę musisz napisać swój własny prosty Serializer.

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

Teraz możesz użyć tego serializatora, aby dodać niestandardowe pola do ModelSerializer

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

Działa to również, jeśli Model MyModelfaktycznie ma właściwość o nazwie, my_custom_fieldale chcesz zignorować jej walidatory.

David Schumann
źródło
więc nie działa, jeśli my_custom_field nie jest własnością MyModel, prawda? Wystąpił błąd Pole serializatora może mieć niepoprawną nazwę i nie pasuje do żadnego atrybutu lub klucza w MyModelinstancji.
Sandeep Balagopal
2

Po przeczytaniu wszystkich odpowiedzi tutaj dochodzę do wniosku, że nie można tego zrobić czysto. Musisz grać nieczysto i zrobić coś szalonego, jak utworzenie pola write_only, a następnie nadpisać metody validatei to_representation. Oto, co zadziałało dla mnie:

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
źródło