İçeriğe geç

Django Rest Framework Serializer Fields Custom Error Messages

Giriş.

Merhabalar.

Uygulamalarımızda, kullanıcılara gösterilen hata mesajları önemlidir. Anlamlı ve yol gösterici mesajlar ile kullanıcıların yapmaya çalıştıkları işlemi doğru bir şekilde nasıl yapabilecekleri konusunda onlara yol göstermiş ve kullanıcı deneyimini arttırmış oluruz.

İyi bir örnek 😀

 

Bugün Django Rest Framework ile geliştirilen bir projede, serializer alanları tarafından kullanıcıya dönülen validation mesajlarının, alanları yeniden tanımlamadan nasıl değiştirilebileceğinden bahsedeceğiz. Hazırsak başlayalım.


Isınmalar.

Projede, basitçe söyle bir model ve serializer tanımlı olsun.

Model.

from django.db import models
from django.conf import settings
class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

Serializer.

from rest_framework import serializers
class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"

Bu tanımlamalardan sonra, bu serializerı kullanan endpointe bir kaç hatalı istek atıldığında, aşağıdakına benzer validation hata mesajları alınacaktır. Bu mesajlar doğrudan DRF varsayılan mesajlarıdır.

{
    "title": [
        "This field may not be blank."
    ],
    "content": [
        "This field may not be blank."
    ],
    "owner": [
        "This field is required."
    ]
}
{
    "title": [
        "Ensure this field has no more than 100 characters."
    ],
    "content": [
        "This field is required."
    ],
    "owner": [
        "Invalid pk \"11\" - object does not exist."
    ]
}

FE ekibinin doğrudan bu mesajları fieldlar ile ilişkilendirerek kullanması çok olağan bir davranış. Fakat son örnekteki owner alanındaki mesajdan ziyade daha son kullanıcıya yakın mesajlar oluşturmak ve dönmek isteyebiliriz.


Varsayılan mesajlar nasıl belirleniyor?

Her serializer alanı farklı durumlarda dönülmek üzere varsayılan hata mesajlarını içermektedir. Ve validasyonda takılan kısma göre key value olarak bir dict üzerinde tutulur.

Örneğin CharField.

class CharField(Field):
    default_error_messages = {
        'invalid': _('Not a valid string.'),
        'blank': _('This field may not be blank.'),
        'max_length': _('Ensure this field has no more than {max_length} characters.'),
        'min_length': _('Ensure this field has at least {min_length} characters.'),
    }

(Farklı durumlar için mesajlar, field oluşturalacağı zaman miras alınan diğer sınıfların mesajları ile de birleştirilerek oluşturulur. Field sınıfı da aşağıdaki mesajlara sahiptir. CharField sınıfı bu base mesajları da varsayılan olarak kullanır.)

 default_error_messages = {
        'required': _('This field is required.'),
        'null': _('This field may not be null.')
    }

Eğer field ilişkiselse, örn PrimaryKeyRelatedField veya SlugRelatedField vs yukarıdaki durumlara ek olarak gelen değer ile arama queryset üzerinde arama yapıldığında bir instance bulunaması durumunda dönülecek olan does_not_exist keyi ve mesajını da içerir.


Mesajları override etmek.

ModelSerializer sınıflarında, alanı yeniden tanımlama zorunluluğu olmadan, Meta sınıf üzerinde bulunan extra_kwargs dict’i key value olarak fieldar üzerinde çalışır ve belirtilen kwargsları field constructurer’ına geçirir.

Mesajlar, field üzerinde error_messages attribute’i üzerinde tutulur.

extra_kwargs icinde, field için error_messages adlı bir dict tanımlar isek burada tanımlı olan keylere göre mesajlar override edilecektir.

Örneğin;

from rest_framework import serializers
from django.utils.translation import gettext_lazy as _
class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"
        extra_kwargs = {
            "owner": {
                "error_messages": {
                    "required": _("Please select an owner for your post."),
                    "does_not_exist": _("The given user not found."),
                    "null": _("Owner must be selected."),
                }
            },
            "title": {
                "error_messages": {
                    "max_length": _(
                        "The post title should not be longer than {max_length} characters."
                    ),
                    "blank": _("The post title must not be empty."),
                }
            },
        }

Serializerdan bir instance yaratıldığında oluşturulacak olan serializer alanlarının error_messages attributeı override edilecek ve varsayılan mesajlar ile birleştirilecektir.

Mesajlara göz atarsak;

Payload 1.

{
    "title": "",
    "content": "",
    "owner": null
}

Response 1.

{
    "title": [
        "The post title must not be empty."
    ],
    "content": [
        "This field may not be blank."
    ],
    "owner": [
        "Owner must be selected."
    ]
}

Payload 2.

{
    "title": "some title",
    "content": "and some content",
    "owner": 11
}

Response 2.

{
    "owner": [
        "The given user not found."
    ]
}

Artık, daha son kullanıcı dostu hata mesajlarımız var 🙂


Base bir serializera bu yetkinliği kazandırmak.

ModelSerializer sınıfları Meta sınıf üzerinden, fiedlar icin extra_kwargs degerlerini kabul ederek field seviyesinde override edebilse de base serializer sınıflarında bu tarz bir yetenek bulunmuyor, ki gerekliliğide tartışılabilir. Base serializer kullanacağımız durumlarda zaten alanları teker teker tanımlamıyor muyuz? 🙂

Fakat doğrudan aynı işi yapacak ve miras alınarak kullanılan base serializer sınıflarında farklı durumlar için mesajları override etmek için (örnegin yetki tanımlarına göre) bu yeteneği kolayca aşağıdaki gibi bir mixin ile kazandırabilirsiniz.

class ExtraKwargsErrorMessagesMixin:
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        if getattr(self, "extra_error_messages", None) is None:
            raise ImproperlyConfigured(
                f"extra_error_messages must be defined on serializer {self.__class__.__name__}."
            )
        if not isinstance(self.extra_error_messages, dict):
            raise ImproperlyConfigured(
                "extra_error_messages must be dict type.",
            )
        for key, value in self.extra_error_messages.items():
            self.control_key(key=key)
            self.fields[key].error_messages.update(value)
    def control_key(self, key) -> None:
        if key not in self.fields:
            raise ImproperlyConfigured(
                f"Field name `{key}` is not valid for serializer class `{self.__class__.__name__}`."
            )

Bu mixini kullanarak oluşturulacak olan serializer sınıflarında, extra_error_messages attribute’ı üzerinden fieldlar için custom mesaj tanımlaması yapabiliriz.

Örn.

class ContactUsSerializer(ExtraKwargsErrorMessagesMixin, serializers.Serializer):
    name = serializers.CharField(max_length=100)
    email = serializers.EmailField()
    extra_error_messages = {
        "email": {
            "required": _("Please enter your email address."),
            "invalid": _("Please enter a valid email address."),
        }
    }

Örnek request-response.

{
	"email": "invalid.com",
	"name": "some proper name"
}
{
    "email": [
        "Enter a valid email address."
    ]
}

Sonuç.

Bir kere daha görüldüğü üzere nereye müdahele edeceğimizi kararlaştırdıktan sonra, Django ve DRF ile çalışmak çok keyifli ve esnek. 🤔

Artık serializer üstünde alanları yeniden tanımlamadan, mesajları nasıl değiştirebileceğimizi biliyoruz. Umarım sizin içinde keyifli ve öğretici bir ipucu yazısı olmuştur.

Serializer hakkında ileri seviye kullanımları örneklediğim yazı serisine göz atmak isterseniz;

DRF serializer field API doc.

İyi çalışmalar, görüşmek üzere!

Tarih:Blog

İlk Yorumu Siz Yapın

Bir cevap yazın

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

Göster
Gizle