Django Rest Framework, normal şartlarda model içindeki ilişkili alanların yalnızca pkprimary key alanını serialize eder. POST, PUT, PATCH ya da DELETE istekleri atarken bir problem yaşamıyoruz. Ancak GET ile bir modeli aldığımızda ilişkili alanlara ait daha detaylı bilgiye ihtiyaç duyabiliyoruz.

Bu problemi kendim yaşadım ve sağolsun stackoverflow'dan bir arkadaşçözümü söyledi. Ben de aynı problemi yaşayanlar vardır diyerekten bu yazıyı yazdım.

· · ·

Makaleler ve kategoriler için iki tane serializer ve tabii onların veritabanı modelleri var. Category modeli Article modeline ForeignKey ile bağlı.


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('pk', 'name', 'slug')

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = ('pk', 'title', 'slug', 'body', 'category')

İlk seçenek serializerMeta sınıfında depth(derinlik) özelliğini belirlemek. Derinliği ne kadar artırırsanız, o kadar iç-içe (nested) ilişkili model elde edersiniz.

Örneğin, depth özelliğini belirlemediğinizde (ya da 0 yaptığınızda) Article modeli için aşağıdaki çıktıyı alacaksınız.


{
    "pk": 1,
    "title": "Süper Bir Makale",
    "slug": "super-bir-makale",
    "body": "Ne güzel bir içerik. :)",
    "category": 1
}

Eğer depth özelliğini 1 yaparsanız sonuç şuna benzer olur;


{
    "pk": 1,
    "title": "Süper Bir Makale",
    "slug": "super-bir-makale",
    "body": "Ne güzel bir içerik. :)",
    "category": {
        "pk": 1,
        "name": "Güzel Makaleler",
        "slug": "guzel-makaleler"
    }
}

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = ('pk', 'title', 'slug', 'body', 'category')
        depth = 1

Eğer bu özelliği kullanırsanız nesne içindeki tüm ilişkili alanların derinliği artırılmış olur. Diğer yandan, sadece GET istekleri için değil, diğer tüm istekler için aynı derinlik kullanılır.

Yani, yeni bir Article eklemek için POST yaptığınızda DRF sizden yalnızca kategori PK değeri yerine tüm kategori içeriğini bekleyecektir. Dolayısıyla yalnızca PK gönderdiğinizde aşağıdaki hatayı alırsınız.


"Invalid data. Expected a dictionary, but got int." 

Diğer bir yöntem, depth kullanmak yerine ilgili ilişkili alanı serializer sınıfı içinde ezmek. Bahsi geçen alanı sınıf içinde ModelSerializer olarak tekrar tanımlayarak sıradan bir alan yerine bir serializer gibi davranmasını sağlayabiliriz.

Önceki yöntemden farkı, bu yöntem ile sadece istediğimiz alanların derinliğini artırmış oluruz. Böylece gereksiz veritabanı sorgularından ve o alanı serileştirmek için harcanan CPU masrafından kurtulmuş oluruz.


class ArticleSerializer(serializers.ModelSerializer):
    category = CategorySerializer()

    class Meta:
        model = Article
        fields = ('pk', 'title', 'slug', 'body', 'category')

Ancak bu yöntemi kullandığımızda da, bir önceki yöntemdeki gibi POST istekleri için ilişkili nesneyi komple göndermememiz gerekecek. Tabii, işlem POST olduğu için, her ne kadar masraftan kaçınmadan(?) tüm kategoriyi yollamak çözüm değil çünkü bahsi geçen kategoriyi tekrar oluşturmaya çalışacak, dolayısıyla PK alanı benzersiz olmadığı için hata fırlatacak. Ki fırlatmasa da yapılan işlem yanlış tabii ki.

Peki çözüm ne?

Bu sorunu aşmak için ModelSerializer sınıfından miras gelen to_representation methodunu kullanırız.


class ArticleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Article
        fields = ('pk', 'title', 'slug', 'body', 'category')

    def to_representation(self, instance):
        representation = super(ArticleSerializer, self).to_representation(instance)
        representation['category'] = CategorySerializer(instance.category).data
        return representation

Bu method parametre olarak ilgili modelin bir örneğini (instance) alıyor ve geriye sunulacak veriyi döndürüyor.

Hemen yukarıdaki örnekte, üst sınıfın aynı isimli methodunun çıktısını (dictionary) aldıktan sonra, "category" değerini, methoda parametre olarak gelen model örneğini kullanarak oluşturduğumuz CategorySerializer nesnesinin verisiyle değiştirdik.

Yani ilk başta category alanı şöyle iken:


{
    "category": 1
}

Biz değiştirdikten sonra şöyle oldu:


{
    "category": {
        "pk": 1,
        "name": "Güzel Makaleler",
        "slug": "guzel-makaleler"
    }
}

Tamam! Artık veriyi POST ederken (safe olmayan methodları kullanırken desem sanırım daha doğru olur) yalnızca PK kullanıyoruz. GET ile alırken de tüm nesneyi alıyoruz. Kolay gelsin!

Siz daha önce bu problem ile karşılaştınız mı? Karşılaştıysanız nasıl çözdünüz?

Cevaplarınızı yorumlarda bekliyorum. :)