Featured image of post Menggunakan ModelSerializer di Django REST Framework

Menggunakan ModelSerializer di Django REST Framework

Penjelasan mengenai ModelSerializer di Django REST Framework. Auto-generated field, konfigurasi Meta (fields, read_only_fields, extra_kwargs), dan penggunaan depth untuk relasi nested.

Menggunakan ModelSerializer di Django REST Framework (Auto Fields, Konfigurasi Meta, tuple fields, depth)

Kali ini kita membahas lebih detail mengenai ModelSerializer, yang merupakan salah satu cara handal untuk melakukan sebagian besar CRUD API di DRF.

ModelSerializer ini sangat berguna karena membuat kita tidak perlu menuliskan kode repetitif dan rutin jika dilakukan secara manual, sambil tetap memberikan kontrol yang baik terhadap:

  • field mana yang diekspos
  • field mana yang read-only atau write-only
  • bagaimana relasi direpresentasikan
  • validasi apa yang diterapkan

Kalau kita paham ModelSerializer dengan baik, kita bisa membangun API yang rapi jauh lebih cepat.


Bagaimana ModelSerializer Membuat Field Secara Otomatis

Saat kita menggunakan:

1
2
3
4
class TodoItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = TodoItem
        fields = ["id", "title", "description", "priority", "is_done", "due_date", "created_at"]

DRF akan memeriksa model Django kita dan secara otomatis membuat field serializer berdasarkan tipe field modelnya.

Contoh mapping-nya:

  • models.CharField -> serializers.CharField
  • models.TextField -> serializers.CharField
  • models.BooleanField -> serializers.BooleanField
  • models.DateField -> serializers.DateField
  • models.DateTimeField -> serializers.DateTimeField
  • models.ForeignKey -> serializers.PrimaryKeyRelatedField (perilaku default)

Auto-generation ini jadi alasan kenapa ModelSerializer biasanya menjadi pilihan utama kalau endpoint kita berbasis model.


Konfigurasi Meta yang Wajib Kita Tahu

Di ModelSerializer, sebagian besar perilaku dikonfigurasi lewat class Meta di dalamnya.

Opsi yang utama:

  • model: model Django mana yang terhubung ke serializer ini
  • fields: field model mana yang dimasukkan ke output dan input serializer
  • read_only_fields: field yang muncul di output tapi tidak bisa di-submit/diupdate oleh client
  • extra_kwargs: kustomisasi per-field tanpa deklarasi field manual
  • depth: level representasi nested otomatis untuk relasi

Contoh sederhana:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from rest_framework import serializers
from serializer_lab.models import TodoItem


class TodoItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = TodoItem
        fields = ["id", "title", "description", "priority", "is_done", "due_date", "created_at"]
        read_only_fields = ["id", "created_at"]
        extra_kwargs = {
            "description": {"allow_blank": True},
        }

Memilih fields: List Eksplisit vs __all__

List Eksplisit (direkomendasikan untuk API)

1
fields = ["id", "title", "description", "priority", "is_done", "due_date", "created_at"]

Kenapa ini lebih aman:

  • mencegah field terekspos secara tidak sengaja jika ada perubahan di model
  • menjaga API contract tetap stabil
  • memudahkan saat melakukan review kode

fields = __all__ (lebih praktis, tapi berisiko)

1
fields = "__all__"

Berguna untuk prototyping internal dengan cepat, tapi beresiko untuk API production karena field model yang baru bisa ikut terekspos secara tidak sengaja.

Secara umum:

  • API publik/production: pakai field dengan list yang eksplisit
  • eksperimen cepat lokal: __all__ bisa digunakan sementara

read_only_fields dan extra_kwargs

read_only_fields

Gunakan ini untuk field yang dikelola server seperti id, timestamp, dan value memerlukan komputasi.

1
read_only_fields = ["id", "created_at"]

Jika client mengirimkan field yang sudah ditandai read only, DRF akan mengabaikannya untuk operasi write.

extra_kwargs

Gunakan ini untuk menyesuaikan field yang dibuat otomatis tanpa harus redeclare setiap field secara manual.

Contoh:

1
2
3
4
extra_kwargs = {
    "title": {"min_length": 3},
    "description": {"required": False},
}

Ini membuat serializer tetap clean sembari tetap memastikan validasinya terkontrol.


Representasi Relasi dan depth

Secara default, objek relasi di ModelSerializer direpresentasikan sebagai primary key.

Maksud dari “Direpresentasikan sebagai primary key” adalah, DRF mengembalikan objek relasi sebagai nilai ID-nya (contohnya, author: 3), bukan sebagai objek nested yang lebih mudah dibaca manusia seperti { "id": 3, "name": "Alice" }.

Contoh output default untuk FK field author:

1
2
3
4
5
{
  "id": 10,
  "title": "Example",
  "author": 3
}

Menggunakan depth

Kita bisa meminta DRF secara otomatis untuk menampilkan relasi yang nested:

1
2
3
4
class Meta:
    model = Post
    fields = ["id", "title", "author"]
    depth = 1

Ini akan menghasilkan data objek relasi yang nested secara otomatis.

Perbandingan: tanpa depth vs depth = 1 vs depth = 2

Asumsikan relasi:

  • Post -> author (FK ke Author)
  • Author -> company (FK ke Company)

Tanpa depth (default, primary key saja):

1
2
3
4
5
{
    "id": 10,
    "title": "Learning DRF",
    "author": 3
}

depth = 1 (hanya relasi langsung yang dinest):

1
2
3
4
5
6
7
8
9
{
    "id": 10,
    "title": "Learning DRF",
    "author": {
        "id": 3,
        "name": "Alice",
        "company": 7
    }
}

depth = 2 (nest satu level lagi di dalam relasi nested):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
    "id": 10,
    "title": "Learning DRF",
    "author": {
        "id": 3,
        "name": "Alice",
        "company": {
            "id": 7,
            "name": "TechNova"
        }
    }
}

Yang perlu diperhatikan:

  • depth yang lebih tinggi berarti data nested lebih banyak
  • depth yang lebih tinggi juga bisa berarti query lebih berat dan response lebih besar

Kenapa depth itu praktis tapi terbatas

Kelebihan:

  • sangat cepat untuk demo atau tools internal
  • tidak perlu mendefinisikan nested serializer di awal

Kekurangan:

  • kontrol terhadap field nested jadi kurang eksplisit
  • bisa men-trigger query yang berat kalau queryset tidak dioptimasi
  • bisa mengekspos lebih banyak data relasi dari yang diinginkan

Untuk API production, nested serializer yang eksplisit biasanya lebih baik daripada terlalu bergantung pada depth.


Latihan Praktik

Latihan ini dibuat tidak berhubungan dengan materi lainnya. Kita akan membuat app baru supaya latihan materi ini tidak bergantung dari materi lain.

Buat dan daftarkan app baru

1
python manage.py startapp modelserializer_lab

Tambahkan app ini ke INSTALLED_APPS di config/settings.py:

1
2
3
4
INSTALLED_APPS = [
    # ...existing apps...
    "modelserializer_lab",
]

Buat model sederhana di modelserializer_lab/models.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from django.db import models


class TaskCard(models.Model):
    PRIORITY_CHOICES = [
        ("low", "Low"),
        ("medium", "Medium"),
        ("high", "High"),
    ]

    title = models.CharField(max_length=120)
    summary = models.TextField(blank=True)
    priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default="medium")
    is_done = models.BooleanField(default=False)
    due_date = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

Jalankan migration:

1
2
python manage.py makemigrations modelserializer_lab
python manage.py migrate

Buat serializer di modelserializer_lab/serializers.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from rest_framework import serializers
from .models import TaskCard


class TaskCardSerializer(serializers.ModelSerializer):
    class Meta:
        model = TaskCard
        fields = ["id", "title", "summary", "priority", "is_done", "due_date", "created_at"]
        read_only_fields = ["id", "created_at"]
        extra_kwargs = {
            "title": {"min_length": 3},
            "summary": {"required": False, "allow_blank": True},
        }


class TaskCardAllFieldsSerializer(serializers.ModelSerializer):
    class Meta:
        model = TaskCard
        fields = "__all__"

Cek field yang digenerate otomatis di Django shell

1
python manage.py shell
1
2
3
4
from modelserializer_lab.serializers import TaskCardSerializer, TaskCardAllFieldsSerializer

print(TaskCardSerializer())
print(TaskCardAllFieldsSerializer())

Perhatikan bagaimana DRF meng-generate tipe field dan pengaturan validator secara otomatis.

Verifikasi perilaku read_only_fields

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from modelserializer_lab.serializers import TaskCardSerializer

payload = {
    "id": 999,
    "title": "Ship API docs",
    "summary": "Read-only test",
    "priority": "medium",
    "is_done": False,
    "created_at": "2020-01-01T00:00:00Z",
}

ser = TaskCardSerializer(data=payload)
print(ser.is_valid())
print(ser.errors)
obj = ser.save()
print(obj.id)          # id yang di-generate DB, bukan 999
print(obj.created_at)  # timestamp server, bukan timestamp payload

Catatan: Secara default, DRF biasanya tidak mengeluarkan validation error saat client mengirim field yang read-only.

  1. Value yang masuk akan diabaikan.
  2. Validasi tetap bisa lolos tanpa pesan error.
  3. Saat save, Django menggunakan nilai yang dikelola database (misalnya auto-increment id dan auto timestamp).

Jadi kalau client mengirim id 999, DRF biasanya akan mengabaikan value itu dan menyimpan data dengan id asli berikutnya.

Verifikasi validasi extra_kwargs

1
2
3
4
5
6
7
8
bad_payload = {
    "title": "ab",
    "priority": "low",
}

ser = TaskCardSerializer(data=bad_payload)
print(ser.is_valid())
print(ser.errors)  # should include min_length error for title

Catatan mengenai peletakan validasi:

  • Validasi extra_kwargs di sini adalah validasi sisi serializer (layer API).
  • Validasi sisi serializer berjalan saat .is_valid() dan menghasilkan error yang API-friendly.
  • Kalau data dibuat langsung melalui ORM (tanpa serializer), aturan serializer ini tidak akan diterapkan.
  • Taruh kondisi / aturan bisnis di level model/database; simpan aturan terkait API di serializer.

Bandingkan fields eksplisit vs __all__

1
2
3
4
5
6
from modelserializer_lab.models import TaskCard
from modelserializer_lab.serializers import TaskCardSerializer, TaskCardAllFieldsSerializer

item = TaskCard.objects.first()
print(TaskCardSerializer(item).data)
print(TaskCardAllFieldsSerializer(item).data)

Saat ini outputnya mungkin terlihat mirip. Tetapi nantinya field yang eksplisit tetap lebih aman seiring evolusi model.


Latihan Praktik Untuk depth

Masih di dalam modelserializer_lab, tambahkan rantai relasi untuk memahami depth.

Tambahkan model relasi di modelserializer_lab/models.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Studio(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Creator(models.Model):
    studio = models.ForeignKey(Studio, on_delete=models.CASCADE, related_name="creators")
    display_name = models.CharField(max_length=100)

    def __str__(self):
        return self.display_name


class VideoClip(models.Model):
    creator = models.ForeignKey(Creator, on_delete=models.CASCADE, related_name="clips")
    title = models.CharField(max_length=120)
    duration_seconds = models.PositiveIntegerField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

Lalu migrate:

1
2
python manage.py makemigrations modelserializer_lab
python manage.py migrate

Buat serializer dengan level depth berbeda

File: modelserializer_lab/serializers.py

Jika kita lanjut di file serializer yang sama dari bagian sebelumnya, pastikan import-nya mencakup:

1
2
from rest_framework import serializers
from .models import TaskCard, VideoClip
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class VideoClipFlatSerializer(serializers.ModelSerializer):
    class Meta:
        model = VideoClip
        fields = ["id", "title", "duration_seconds", "creator"]


class VideoClipDepthOneSerializer(serializers.ModelSerializer):
    class Meta:
        model = VideoClip
        fields = ["id", "title", "duration_seconds", "creator"]
        depth = 1


class VideoClipDepthTwoSerializer(serializers.ModelSerializer):
    class Meta:
        model = VideoClip
        fields = ["id", "title", "duration_seconds", "creator"]
        depth = 2

Masukkan test data dan bandingkan outputnya

Pertama, buka Django shell dari root project kita:

1
python manage.py shell

Lalu jalankan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from modelserializer_lab.models import Studio, Creator, VideoClip
from modelserializer_lab.serializers import (
    VideoClipFlatSerializer,
    VideoClipDepthOneSerializer,
    VideoClipDepthTwoSerializer,
)

studio = Studio.objects.create(name="Northwind Studio")
creator = Creator.objects.create(studio=studio, display_name="Ari")
clip = VideoClip.objects.create(creator=creator, title="Serializer Tour", duration_seconds=95)

print(VideoClipFlatSerializer(clip).data)
print(VideoClipDepthOneSerializer(clip).data)
print(VideoClipDepthTwoSerializer(clip).data)

Perhatikan polanya:

  • flat serializer: creator hanya berupa integer primary key
  • depth = 1: creator menjadi data object nested
  • depth = 2: creator yang nested juga menyertakan studio secara nested

Ini memberi gambaran yang jelas dan terisolasi tentang bagaimana depth meng-ekspansi struktur dari response.


Kesalahan Yang Sering Terjadi

  • Menggunakan fields = "__all__" di API production tanpa meninjau risiko exposure data
  • Lupa memberi tanda di field yang dikelola server sebagai read_only_fields
  • Terlalu banyak memakai depth dan tanpa sengaja mengembalikan payload nested yang berat
  • Menganggap ModelSerializer menghilangkan kebutuhan validasi bisnis
  • Menyebarkan logika validasi dan transformasi ke banyak tempat dibandingkan menjaga logika level serializer tetap di serializer.

Poin Penting

  • ModelSerializer meng-generate field dari model secara otomatis dan mempercepat pengembangan CRUD API.
  • Gunakan fields eksplisit untuk kontrak API jangka panjang yang lebih aman.
  • read_only_fields dan extra_kwargs memberi kontrol praktis dengan boilerplate rendah.
  • depth berguna untuk output nested cepat, tapi harus dipakai dengan hati-hati.
  • Bahkan dengan ModelSerializer, strategi validasi tetap penting.

Foto cover oleh allisonsaeng dari Unsplash

Dibawah Lisensi CC BY-NC-SA 4.0
Dibangun dengan Hugo
Tema Stack dirancang oleh Jimmy