İçeriğe geç

Django Rest Framework Serializer Tips 2

Merhabalar. Serializer tips 2. yazısına hoşgeldiniz.

Kaldığımız yerden DRF serializerlar hakkında ipuçlarına ve ileri seviye kullanımlara göz atmaya devam edeceğiz. Bugün depth keyword, SerializerMethodField ve bir kaç farklı kullanıma daha göz atacağız.

Serinin ilk yazısını okumak isterseniz.

Django Rest Framework Serializer Tips 1


SerializerMethodField.

SerializerMethodField serializasyon sırasında nesneler üzerinde hesaplanabilen salt okunur alanlar oluşturmak için kullanılır. Her nesne için, serializer sınıfı üzerinde tanımlı bir metodun döndüğü dinamik değer, serializasyon çıktısına eklenir.

from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
    days_since_joined = serializers.SerializerMethodField()
    class Meta:
        model = User
        fields = ["username","id","days_since_joined"]
    def get_days_since_joined(self, obj):
        return (timezone.now() - obj.date_joined).days

Parametre olarak sadece method_name değerini alır. Fakat bu değerin sağlanmadığı durumda, gerekli metod  get_<field-name> formatı ile serializer üstünde arandıktan sonra çalıştırılacaktır. Örn.

Metod ismi belirterek çağrım:

dummy = serializers.SerializerMethodField("populate_dummy_value")
   {
        "id": 1,
        "username": "egehan",
        "days_since_joined": 7
    }

Read only fields.

Serializer üzerinde tanımlanan alanlar, read_only argümanı True olarak geçirilirse, deserializasyon sırasında bu veri dikkate alınmayacaktır ve sadece çıktıya dahil olacaktır. Bazı durumlarda birden fazla alanı yeniden tanımlamadan salt okunur olarak işaretlemek isteyebiliriz.

Çoklu şekilde salt okunur alanlar tanımlamak için Meta sınıfına aşağıdaki gibi bir ekleme yapabilirsiniz. Örn.

Bu işlemi gerçekleştirmenin başka bir yöntemi ise bir önceki yazıda üstünde konuştuğumuz extra_kwargs.

class UserSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = User
        exclude = ("password",)
        read_only_fields = ["email", "is_staff", "is_superuser"]

Nested serialiazsyon ve depth keyword.

DRF ile nested serialiazsyon işlemleri yapılırken 2 tür kullanım bulunur.

  • Serialize edilecek olan nesnenin, ilişkisel olduğu diğer model nesnelerini de serialize edecek olan sınıfı açıkça belirtmek.
  • Depth keyword ile ilişkisel alanları ekstra bir tanımlama yapmadan katmansal serialize etmek.

İlk seçeneği hızlıca örnekleyecek olursak; (Serinin ilk yazısındaki event modelini kullanalım.)

class Event(models.Model):
    name = models.CharField(max_length=50)
    is_public = models.BooleanField(default=True)
    start_at = models.DateTimeField()
    end_at = models.DateTimeField()
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    def __str__(self) -> str:
        return self.name

Ve aşağıdaki gibi bir serializer tanımlayalım.

class EventSerializer(serializers.ModelSerializer):
    class Meta:
        model = Event
        fields = "__all__"

Bu serializerdan geçirilmiş olan bir event nesnesi aşağıdaki gibi bir çıktı dönecektir.

{
    "id": 8,
    "name": "Special Event",
    "is_public": true,
    "start_at": "2021-07-10T10:42:00Z",
    "end_at": "2021-08-10T10:42:00Z",
    "owner": 1
}

owner alanı doğrudan PrimaryKeyRelatedField olarak serialize edildi ve sadece nesne idsi döndü.

owner alanı için açıkca bir serializer sınıfı oluşturup, belirtelim ve nested bir serialiazsyon yapalım.

from django.contrib.auth.models import User
class OwnerSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id","username", "first_name", "last_name", "email"]
class EventSerializer(serializers.ModelSerializer):
    owner = OwnerSerializer(read_only=True)
    class Meta:
        model = Event
        fields = "__all__"

Yeni hali ile bir nesneyi serialize edersek;

{
        "id": 8,
        "owner": {
            "id": 1,
            "username": "egehan",
            "first_name": "Egehan",
            "last_name": "Gundogdu",
            "email": "egehn.gundogdu@gmail.com"
        },
        "name": "Special Event",
        "is_public": true,
        "start_at": "2021-07-10T10:42:00Z",
        "end_at": "2021-08-10T10:42:00Z"
    }

Açıkça bir tanımlama ile owner alanınıda kendi serializerı ile serialize edip daha detaylı bir veri dönmüş olduk.

Not. Nested serializasyon yapıldığı durumlarda, DRF doğrudan nested olan nesne için bir update ya da create mekanizması barındırmıyor. Bu yapıyı create ya da update metodlarını, ezerek açıkça tanımlamanız gerekmekte. Ya da şuradaki pakete bir göz atabilirsiniz.

 

Depth keyword.

Nested serialiazsyon yapılacakken DRF’in pek bilinmeyen harika bir özelliği depth. Diyelim ki bir e ticaret sitesi üzerinde çalışıyor ve adres yapısını geliştiriyoruz. Aşağıdaki gibi katmanlı bir şekilde, bir yapı kurguladık.

Ülke modeli -> Şehir modeli -> İlçe Modeli -> Mahalle modeli -> Adres modeli.

Yukarıdaki şematiğe göre birbiriyle ilişkili 5 modelimiz var.

Tanımlamalar.


class Country(models.Model):
    name = models.CharField(max_length=50)
    def __str__(self) -> str:
        return self.name
class City(models.Model):
    name = models.CharField(max_length=50)
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    def __str__(self) -> str:
        return self.name
class Township(models.Model):
    name = models.CharField(max_length=50)
    city = models.ForeignKey(City, on_delete=models.CASCADE)
    def __str__(self) -> str:
        return self.name
class District(models.Model):
    name = models.CharField(max_length=50)
    township = models.ForeignKey(Township, on_delete=models.CASCADE)
    def __str__(self) -> str:
        return self.name
class Adddres(models.Model):
    name = models.CharField(max_length=50)
    district = models.ForeignKey(District, on_delete=models.CASCADE)
    raw_adddres = models.TextField()
    def __str__(self) -> str:
        return self.name

depth keywordü değer olarak bir integer alır. Aldığı integer değere göre katmanlı bir şekilde serialize edilen nesnenin ilişkisel olduğu alanlarıda takip ederek gerekli serializasyon işlemini gerçekleştirir. Örnekleyelim.

Aşağıdaki gibi bir serializer tanımlayıp bir nesneyi serialize edelim.

class AddressSerializer(serializers.ModelSerializer):
    class Meta:
        model = Adddres
        fields = "__all__"
 {
        "id": 1,
        "name": "My Local Address",
        "raw_adddres": "lorem ipsum dolor sit amet.",
        "district": 1
}

Buraya kadar aslında farklı bir durum yok. İlişkisel bir alan olan district için PrimaryKeyRelatedField üzerinden id değeri döndü.

Fakat mahalle nesnesini açıkca serialize edilen verinin içine eklemek istiyorsak?

Aşağıdaki gibi depth değerini ekleyelim.

class AddressSerializer(serializers.ModelSerializer):
    class Meta:
        model = Adddres
        fields = "__all__"
        depth = 1

Dönen veriye göz atalım.

 {
        "id": 1,
        "name": "My Local Address",
        "raw_adddres": "lorem ipsum dolor sit amet.",
        "district": {
            "id": 1,
            "name": "Kötekli Mah",
            "township": 1
        }
}

Harika. 1 katman aşağıya inerek ilişkisel olan district nesnesinin verileri de çıktıya eklendi. Fakat diyelim ki ilçe bilgisini de açıkca çıktıya eklemek istiyoruz. Tek yapmamız gereken derinliği 1 arttırmak ve daha aşağıya inmek 🙂

class AddressSerializer(serializers.ModelSerializer):
    class Meta:
        model = Adddres
        fields = "__all__"
        depth = 2

 

{
        "id": 1,
        "name": "My Local Address",
        "raw_adddres": "lorem ipsum dolor sit amet.",
        "district": {
            "id": 1,
            "name": "Kötekli Mah",
            "township": {
                "id": 1,
                "name": "Menteşe",
                "city": 1
            }
        }
    }

Umarım serializer üstündeki derinliğin tam olarak ne anlam ifade ettiği konusunda hemfikiriz.

Kurguladığımız yapıya göre address modeli üzerinden 4 adet model takip edilebilir, depth değerini 4 olarak ayarlarsak;


{
    "id": 1,
    "name": "My Local Address",
    "raw_adddres": "lorem ipsum dolor sit amet.",
    "district": {
        "id": 1,
        "name": "Kötekli Mah",
        "township": {
            "id": 1,
            "name": "Menteşe",
            "city": {
                "id": 1,
                "name": "Muğla",
                "country": {
                    "id": 1,
                    "name": "Türkiye"
                }
            }
        }
    }
}

Tamam görünüyor!

Not. Burada dikkat ettiyseniz, serialize edilen ilişkisel nesnelerin tüm alanları üretilen çıktıya dahil edildi. depth özelliği ile serialize edilen nesnelerde herhangi bir alan kontrolü yapamıyoruz. fields=”__all__” olarak işlem görüyor. Hassas bilgi içeren senaryolarda ilk seçenekteki gibi, açıkca bir serializer sınıfı tanımlamak yararınıza olacaktır.


Farklı okuma/yazma serializer sınıfları.

Bazı senaryolarda okuma (yukarıdaki adres islemi güzel bir örnek) sırasında çok fazla ilişkisel alan bulunduğundan nested serializerlar fazlasıyla kompleks bir veri döner. Fakat yazma sırasında bu nested durumuna ihtiyacımız olmayabilir.

Custom bir create metodu impelemente etmenin geliştirme maliyetinden ve çok fazla veriyi validasyondan geçirmeye çalışan serializerın yavaşlığından kurtulmak isterseniz aynı endpoint üzerinde yazma ve okuma sırasında kullanılacak olan serializer sınıfını dinamik değiştirebilirsiniz.

Generic viewlar ya da viewsetler kullanıyor iseniz;

from rest_framework import viewsets
from your_app.serializers import ExampleWriteSerializer, ExampleReadSerializer
class ExampleViewSet(viewsets.ModelViewSet):
    queryset = Example.objects.all()
    def get_serializer_class(self):
        if self.action in ["create", "update", "partial_update", "destroy"]:
            return ExampleWriteSerializer
        # or
        if self.request.method not in ["GET", "OPTIONS", "HEAD"]:
            return ExampleWriteSerializer
        return ExampleReadSerializer

Böylelikle gelen her istekte, metoda göre serializer dinamik olarak değiştirilecektir.

Bu çalışan bir yaklaşım olsa da, bu işlemle alakalı daha detaylı ve tekrar kullanılabilir bir çözüme göz atmak isterseniz.


Sonuç.

Yazarken çok çok keyif aldığım, 2 yazıdan oluşan güzel bir seri oldu. Serializerlar üzerindeki hakimiyetinizi umarım biraz daha yukarıya çekebilmişimdir.

Bu yazıyı beğendiyseniz, paylaşarak daha fazla insana ulaşmasında yardımcı olabilirsiniz.

İyi günler, iyi bayramlar dilerim!

 

Tarih:Blog

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle