Kerangka Kerja Django Rest - Bagaimana menambahkan bidang tersuai di ModelSerializer

89

Saya membuat ModelSerializerdan ingin menambahkan bidang khusus yang bukan bagian dari model saya.

Saya menemukan deskripsi untuk menambahkan bidang ekstra di sini dan saya mencoba yang berikut:

customField = CharField(source='my_field')

Saat saya menambahkan bidang ini dan memanggil validate()fungsi saya maka bidang ini bukan bagian dari attrdict. attrberisi semua bidang model yang ditentukan kecuali bidang ekstra. Jadi saya tidak dapat mengakses bidang ini dalam validasi yang ditimpa, bukan?

Ketika saya menambahkan bidang ini ke daftar bidang seperti ini:

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

kemudian saya mendapatkan error karena customFieldbukan bagian dari model saya - apa yang benar karena saya ingin menambahkannya hanya untuk serializer ini.

Apakah ada cara untuk menambahkan bidang khusus?

Ron
sumber
Bisakah Anda memperluas "Tapi ketika bidang saya tidak ada dalam daftar bidang model yang ditentukan dalam serializer itu bukan bagian dari kamus-validate () attr.", Saya tidak yakin itu sangat jelas.
Tom Christie
Juga "ia mengeluh - benar - bahwa saya tidak memiliki bidang customField dalam model saya.", Dapatkah Anda menjelaskan secara eksplisit tentang pengecualian yang Anda lihat - terima kasih! :)
Tom Christie
Saya memperbarui posting saya dan berharap itu lebih jelas sekarang. Saya hanya ingin tahu bagaimana saya dapat menambahkan bidang yang bukan bagian dari model saya ...
Ron

Jawaban:

63

Anda melakukan hal yang benar, kecuali bahwa CharField(dan kolom lain yang diketik) adalah untuk kolom yang bisa ditulis.

Dalam hal ini Anda hanya menginginkan bidang hanya baca sederhana, jadi gunakan saja:

customField = Field(source='get_absolute_url')
Tom Christie
sumber
4
terima kasih, tapi saya ingin bidang yang bisa ditulisi. Saya memberikan token pengguna tertentu yang mengidentifikasi pengguna saya. tetapi dalam model saya, saya memiliki pengguna dan bukan token. jadi saya ingin meneruskan token dan "mengubahnya" menjadi objek pengguna melalui kueri.
Ron
hal berikutnya adalah sumber itu perlu menargetkan atribut model, bukan? dalam kasus saya, saya tidak memiliki atribut untuk ditunjukkan.
Ron
Saya tidak mengerti sedikitpun pengguna / token dari komentar tersebut. Tetapi jika Anda ingin menyertakan bidang di serializer yang dilucuti sebelum Anda mengembalikan ke contoh model, Anda dapat menggunakan .validate()metode untuk menghapus atribut. Lihat: django-rest-framework.org/api-guide/serializers.html#validation Itu akan melakukan apa yang Anda butuhkan, meskipun saya tidak benar-benar memahami kasus penggunaan, atau mengapa Anda menginginkan bidang yang dapat ditulis yang terkait dengan properti read-onlyget_absolute_url ?
Tom Christie
lupakan tentang get_absolute_urlSaya baru saja menyalin & menempelkannya dari dokumen. Saya hanya ingin bidang yang dapat ditulis normal yang dapat saya akses di validate(). Saya hanya menebak bahwa saya membutuhkan sourceatribut ...
Ron
Itu lebih masuk akal. :) Nilai harus diteruskan untuk memvalidasi, jadi saya akan memeriksa ulang bagaimana Anda membuat contoh serializer, dan apakah nilai itu benar-benar diberikan.
Tom Christie
82

Ternyata ada solusinya tanpa menyentuh sama sekali modelnya. Anda dapat menggunakan SerializerMethodFieldyang memungkinkan Anda untuk menyambungkan metode apa pun ke serializer Anda.

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

    def get_foo(self, obj):
        return "Foo id: %i" % obj.pk
Idaho
sumber
6
Seperti yang disebutkan OP dalam komentar ini , mereka menginginkan bidang yang dapat ditulis, yang SerializerMethodFieldbukan
esmail
14

... untuk kejelasan, jika Anda memiliki Metode Model yang ditentukan dengan cara berikut:

class MyModel(models.Model):
    ...

    def model_method(self):
        return "some_calculated_result"

Anda dapat menambahkan hasil dari memanggil metode tersebut ke serializer Anda seperti:

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

ps Karena bidang khusus sebenarnya bukan bidang dalam model Anda, Anda biasanya ingin membuatnya menjadi hanya-baca, seperti:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
sumber
3
Bagaimana jika saya ingin dapat ditulis?
Csaba Toth
1
@Csaba: Anda akan hanya perlu menulis kustom menyimpan dan penghapusan kait untuk konten tambahan: Lihat "Simpan dan penghapusan kait" di bawah "Metode" ( Di sini ) Anda harus menulis kustom perform_create(self, serializer), perform_update(self, serializer), perform_destroy(self, instance).
Lindauson
13

di sini menjawab pertanyaan Anda. Anda harus menambahkan ke akun model Anda:

@property
def my_field(self):
    return None

sekarang Anda dapat menggunakan:

customField = CharField(source='my_field')

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

va-dev
sumber
6
Saya telah menggunakan pendekatan ini ketika masuk akal tetapi tidak bagus untuk menambahkan kode tambahan ke model untuk hal-hal yang hanya benar-benar digunakan untuk panggilan API tertentu.
Andy Baker
1
Anda dapat menulis model proxy untuk
ashwoods
10

Untuk menunjukkan self.author.full_name, saya mendapat kesalahan dengan Field. Ini bekerja dengan 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
sumber
6

Dengan versi terakhir Django Rest Framework, Anda perlu membuat metode dalam model Anda dengan nama bidang yang ingin Anda tambahkan.

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

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

    class Meta:
        model = Foo
        fields = ('foo',)
Guillaume Vincent
sumber
4

Saya sedang mencari solusi untuk menambahkan bidang kustom yang dapat ditulis ke serializer model. Saya menemukan yang satu ini, yang belum tercakup dalam jawaban atas pertanyaan ini.

Sepertinya Anda memang perlu membuat Serializer sederhana Anda sendiri.

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

Sekarang Anda dapat menggunakan Serializer ini untuk menambahkan kolom kustom ke 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

Ini juga berfungsi, jika Model MyModelbenar-benar memiliki properti yang dipanggil my_custom_fieldtetapi Anda ingin mengabaikan validatornya.

David Schumann
sumber
jadi tidak berfungsi jika my_custom_field bukan milik MyModel kan? Saya mendapat kesalahan. Bidang serializer mungkin salah dinamai dan tidak cocok dengan atribut atau kunci apa pun pada MyModelinstance.
Sandeep Balagopal
2

Setelah membaca semua jawaban di sini, kesimpulan saya adalah tidak mungkin melakukan ini dengan bersih. Anda harus bermain kotor dan melakukan sesuatu yang hadkish seperti membuat bidang write_only dan kemudian mengganti metode validatedan to_representation. Inilah yang berhasil untuk saya:

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
sumber