Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework serializer `source` giving weird required error

I have an API that sends me fields with field names that don't match the ones in my model (and that I have no control over) and am trying to map them in the serializer, but for some reason when I actually implement this:

class UserSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(source='customer_email')
    first_name = serializers.CharField(source='first name')
    last_name = serializers.CharField(source='last name')
    address = serializers.CharField(source='customer_address')
    phone = serializers.CharField(source='customer_phone')
    messenger_id = serializers.IntegerField(source='messenger user id')

    class Meta:
        model = User
        fields = ('id', 'url', 'email', 'first_name', 'last_name', 'address', 'phone', 'messenger_id',)

I'm getting the following error:

{
    "phone": [
        "This field is required."
    ],
    "first_name": [
        "This field is required."
    ],
    "last_name": [
        "This field is required."
    ],
    "messenger_id": [
        "This field is required."
    ],
    "email": [
        "This field is required."
    ],
    "address": [
        "This field is required."
    ]
}

However, among those fields, the only one that's actually required in my model is the email field. I used the source= parameter according to the top rated answer here, but am not sure what's causing the issue here.

Thanks!

like image 893
halsdunes Avatar asked Dec 17 '17 03:12

halsdunes


Video Answer


3 Answers

I had a similar problem with my serializer, until I realized that

  • the "fields"-attribute in class Meta represents the fields of the API and not the fields of the actual model.
  • the "source"-parameter represents the fields of the model.

So if this is approximately what your model looks like:

class User(models.Model):

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    url = models.CharField(max_length=255)
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    email = models.EmailField(max_length=255)
    address = models.CharField(max_length=255)
    phone = models.CharField(max_length=255)
    messenger_id = models.IntegerField()

Then maybe this could work:

class UserSerializer(serializers.ModelSerializer):

    customer_email = serializers.EmailField(source='email')
    customer_address = serializers.CharField(source='address')
    customer_phone = serializers.CharField(source='phone')
    messenger_user_id = serializers.IntegerField(source='messenger_id')

    class Meta:
        model = User
        fields = ('id', 'url', 'first_name', 'last_name', 'customer_email', 'customer_address', 'customer_phone', 'messenger_user_id',)
like image 62
Beolap Avatar answered Oct 16 '22 21:10

Beolap


From the Documentation:

required - Normally an error will be raised if a field is not supplied during deserialization. Set to false if this field is not required to be present during deserialization.

Setting this to False also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation.

Defaults to True.

In short, by defining the serializer fields "by hand" and not using the automatic creation, you run over the pulled "required" field - and make it default back to true. change your code like so:

email = serializers.EmailField(source='customer_email')
first_name = serializers.CharField(source='first name',required = False)
last_name = serializers.CharField(source='last name',required = False)
address = serializers.CharField(source='customer_address',required = False)
phone = serializers.CharField(source='customer_phone',required = False)
messenger_id = serializers.IntegerField(source='messenger user id',required = False)

Also, it seems you missed the underscores in some places. + I will recommend you to just use fields = ('all') or if you want to exclude some fields -> exclude = (''#enter excluded names here). so to coclude:

class UserSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(source='customer_email',required = False)
    first_name = serializers.CharField(source='first_name',required = False)
    last_name = serializers.CharField(source='last_name',required = False)
    address = serializers.CharField(source='customer_address',required = False)
    phone = serializers.CharField(source='customer_phone',required = False)
    messenger_id = serializers.IntegerField(source='messenger_user_id',required = False)

    class Meta:
        model = User
        exclude = () # insert excluded files here

To finish up, I recommened you checking if you really want to explicitly state all fields when creating the serializer class. Most cases you can avoid that and let the Django-rest-framework magic work it out for you.

like image 29
idik Avatar answered Oct 16 '22 21:10

idik


An addition to the answer of idik if you mend to combine fields from different models. According to the docs you should use the dotted notation here:

The name of the attribute that will be used to populate the field. May be a method that only takes a self argument, such as URLField(source='get_absolute_url'), or may use dotted notation to traverse attributes, such as EmailField(source='user.email').

When you have a User model with an first_name, last_name etc and a linked Customer model, it should be something along the following lines:

class UserSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(source='customer.email')
    first_name = serializers.CharField()
    last_name = serializers.CharField()
    address = serializers.CharField(source='customer.address')
    phone = serializers.CharField(source='customer.phone')
    messenger_id = serializers.IntegerField()
like image 1
Tycho Avatar answered Oct 16 '22 19:10

Tycho