Django REST Framework: menambahkan bidang tambahan ke ModelSerializer

149

Saya ingin membuat serial suatu model, tetapi ingin memasukkan bidang tambahan yang mengharuskan dilakukannya beberapa pencarian basis data pada contoh model yang akan diserialisasi:

class FooSerializer(serializers.ModelSerializer):
  my_field = ... # result of some database queries on the input Foo object
  class Meta:
        model = Foo
        fields = ('id', 'name', 'myfield')

Apa cara yang tepat untuk melakukan ini? Saya melihat bahwa Anda dapat meneruskan "konteks" ekstra ke serializer, apakah jawaban yang tepat untuk diteruskan di bidang tambahan dalam kamus konteks? Dengan pendekatan itu, logika mendapatkan bidang yang saya butuhkan tidak akan mandiri dengan definisi serializer, yang ideal karena setiap instance serial akan perlu my_field. Di tempat lain dalam dokumentasi serialisator DRF dikatakan "bidang tambahan dapat sesuai dengan properti apa pun atau dapat dipanggil pada model". Apakah bidang tambahan yang saya bicarakan? Haruskah saya mendefinisikan fungsi dalam Foodefinisi model yang mengembalikan my_fieldnilai, dan dalam serializer saya menghubungkan my_field ke callable itu? Seperti apa itu?

Terima kasih sebelumnya, senang untuk menjelaskan pertanyaan jika perlu.

Neil
sumber

Jawaban:

226

Saya pikir SerializerMethodField adalah apa yang Anda cari:

class FooSerializer(serializers.ModelSerializer):
  my_field = serializers.SerializerMethodField('is_named_bar')

  def is_named_bar(self, foo):
      return foo.name == "bar" 

  class Meta:
    model = Foo
    fields = ('id', 'name', 'my_field')

http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

JP
sumber
19
apakah mungkin menambahkan validasi ke bidang seperti itu? pertanyaan saya adalah: bagaimana cara menerima nilai POST khusus yang dapat divalidasi dan memproses di handler post_save ()?
Alp
20
Perhatikan bahwa SerializerMethodField hanya-baca, jadi ini tidak akan berfungsi untuk POST / PUT / PATCH yang masuk.
Scott A
24
Dalam DRF 3, diubah menjadi field_name = serializers.SerializerMethodField()dandef get_field_name(self, obj):
Programmer Kimia
1
whats the foosaat def sebuah SerializerMethodField? ketika menggunakan CreateAPIView, apakah foo telah disimpan kemudian dapat menggunakan metode is_named_bar ()?
244boy
Jadi, berdasarkan jawaban ini, jika Anda ingin memberikan 12 bidang tambahan ke serializer Anda, Anda perlu mendefinisikan 12 metode khusus untuk setiap bidang yang baru saja mengembalikan foo.field_custom?
AlxVallejo
41

Anda dapat mengubah metode model Anda menjadi properti dan menggunakannya dalam serializer dengan pendekatan ini.

class Foo(models.Model):
    . . .
    @property
    def my_field(self):
        return stuff
    . . .

class FooSerializer(ModelSerializer):
    my_field = serializers.ReadOnlyField(source='my_field')

    class Meta:
        model = Foo
        fields = ('my_field',)

Sunting: Dengan versi terbaru kerangka istirahat (saya mencoba 3.3.3), Anda tidak perlu mengubah ke properti. Metode model hanya akan berfungsi dengan baik.

Wasil Sergejczyk
sumber
Terima kasih @ Selamat! Saya tidak terbiasa dengan penggunaan properti dalam model Django, dan tidak dapat menemukan penjelasan yang baik tentang apa artinya. Bisakah Anda jelaskan? Apa gunanya dekorator properti di sini?
Neil
2
itu berarti Anda dapat memanggil metode ini seperti properti: yaitu variable = model_instance.my_fieldmemberikan hasil yang sama seperti variable = model_instance.my_field()tanpa dekorator. juga: stackoverflow.com/a/6618176/2198571
Wasil Sergejczyk
1
Ini tidak berfungsi, setidaknya di Django 1.5.1 / djangorestframework == 2.3.10. ModelSerializer tidak mendapatkan kelayakan bahkan jika secara eksplisit disebut dalam atribut "bidang" Meta.
ygneo
9
Anda perlu menambahkan bidang ke serializer karena tidak ada bidang model nyata : my_field = serializers.Field (sumber = 'my_field')
Marius
2
source='my_field' tidak membutuhkan lagi dan mengajukan pengecualian
Guillaume Vincent
13

Dengan versi terakhir Django Rest Framework, Anda perlu membuat metode dalam model Anda dengan nama bidang yang ingin Anda tambahkan. Tidak perlu @propertydan source='field'memunculkan kesalahan.

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

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

    class Meta:
        model = Foo
        fields = ('foo',)
Guillaume Vincent
sumber
bagaimana jika saya ingin memiliki requestobjek di dalam def foo (self) yang dapat mengubah nilai foo? (contoh pencarian berdasarkan request.user)
saran3h
1
Bagaimana jika nilai foo berasal dari permintaan?
saran3h
11

Respons saya terhadap pertanyaan serupa (di sini ) mungkin berguna.

Jika Anda memiliki Metode Model yang didefinisikan 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 bukan benar-benar bidang dalam model Anda, Anda biasanya ingin membuatnya hanya baca, seperti:

class Meta:
    model = MyModel
    read_only_fields = (
        'model_method_field',
        )
Lindauson
sumber
10

jika Anda ingin membaca dan menulis di bidang tambahan Anda, Anda dapat menggunakan serializer khusus baru, yang meluas serializers. Serializer, dan menggunakannya seperti ini

class ExtraFieldSerializer(serializers.Serializer):
    def to_representation(self, instance): 
        # this would have the same as body as in a SerializerMethodField
        return 'my logic here'

    def to_internal_value(self, data):
        # This must return a dictionary that will be used to
        # update the caller's validation data, i.e. if the result
        # produced should just be set back into the field that this
        # serializer is set to, return the following:
        return {
          self.field_name: 'Any python object made with data: %s' % data
        }

class MyModelSerializer(serializers.ModelSerializer):
    my_extra_field = ExtraFieldSerializer(source='*')

    class Meta:
        model = MyModel
        fields = ['id', 'my_extra_field']

saya menggunakan ini dalam bidang bersarang terkait dengan beberapa logika khusus

Marco Silva
sumber
2

Ini berhasil untuk saya . Jika kita hanya ingin menambahkan bidang tambahanModelSerializer, kita dapat melakukannya seperti di bawah ini, dan juga bidang tersebut dapat diberikan beberapa val setelah beberapa perhitungan pencarian. Atau dalam beberapa kasus, jika kami ingin mengirim parameter dalam respons API.

Di model.py

class Foo(models.Model):
    """Model Foo"""
    name = models.CharField(max_length=30, help_text="Customer Name")

Di serializer.py

class FooSerializer(serializers.ModelSerializer):
    retrieved_time = serializers.SerializerMethodField()
    
    @classmethod
    def get_retrieved_time(self, object):
        """getter method to add field retrieved_time"""
        return None

  class Meta:
        model = Foo
        fields = ('id', 'name', 'retrieved_time ')

Semoga ini bisa membantu seseorang.

Vinay Kumar
sumber
0

Seperti yang dikatakan Programer Kimia dalam komentar ini , dalam DRF terbaru Anda bisa melakukannya seperti ini:

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

    def get_extra_field(self, foo_instance):
        return foo_instance.a + foo_instance.b

    class Meta:
        model = Foo
        fields = ('extra_field', ...)

Sumber dokumen DRF

trici0pa
sumber