Home > Enterprise >  Django REST Framework - Custom serializer overrides model constraints
Django REST Framework - Custom serializer overrides model constraints

Time:04-27

My goal is to capitalize all letters coming in a specific field from the user payload in Django Rest Framework.

Here the model:

class Order(models.Model):
    class Item(models.TextChoices):
        FIRST= 'FIRST', _('FIRST')
        SECOND= 'SECOND', _('SECOND')
    ...
    order_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
    the_item = models.CharField(max_length=8, choices=Item.choices)

The only values admited are "FIRST" and "SECOND" in uppercase.

I used this approach to capitalize the letters:

In Django Rest Framework:

from rest_framework import serializers

class UpperCaseSerializerField(serializers.CharField):

    def __init__(self, *args, **kwargs):
        super(UpperCaseSerializerField, self).__init__(*args, **kwargs)

    def to_representation(self, value):
        value = super(UpperCaseSerializerField, self).to_representation(value)
        if value:
            return value.upper()

and used in serializers.py like:

class OrderCreateSerializer(serializers.ModelSerializer):
    
    #Force uppercase
    item = UpperCaseSerializerField()
    class Meta:
        model = Order
        fields = ['order_id', 'item' ]

This actually works, because the input is converted in uppercase, BUT, the model constraints are not taken anymore into account.

Payload Example 1:

{ "the_item": "first" } - LEGIT, converted in FIRST

Payload Example 2:

{ "the_item": "FIRST" } - LEGIT, it's already uppercase

Payload Example 3:

{ "the_item": "1234567" } - COMPLETELY WRONG INPUT, but it is accepted! It should be not!

It's clear that it only cares about capitalizing and completely ignoring the model structure, which does not admit strings different from 'FIRST' and 'SECOND'.

So, removing item = UpperCaseSerializerField() "restores" the model constraints check, but I lose the capability to capitalize all letters.

Am I missing something? Is there a way to keep both?

CodePudding user response:

I think that declaring the serializer field explicitly prevents the serializer to reuse the model validation logic for that specific field, as you noticed already, because maybe in some cases you really want to get rid of some validation.

You could rather quickly get the same validation by creating another field class for ChoiceFields with something along these lines:

class UpperCaseSerializerChoiceField(serializers.ChoiceField):

    def to_representation(self, value):
        value = super().to_representation(value)
        if value:
            return value.upper()

and re-add the choices on the serializer:

class OrderCreateSerializer(serializers.ModelSerializer):
    
    item = UpperCaseSerializerChoiceField(choices=Order.Item.choices)
    class Meta:
        model = Order
        fields = ['order_id', 'item' ]

Note that I didn't try the code

Edit: According to the source code, the method to_internal_value is responsible for validating the choices (which actually make sense, to_representation is called when rendering the data, ie. for an API response).

So using something like the following should help:

class UpperCaseSerializerChoiceField(serializers.ChoiceField):

    def to_internal_value(self, data):
        if data:  # In case the field is nullable
            data = data.upper()
        return super().to_internal_value(data)
  • Related