DRF serializer skips nested serializer validation when many=True - python

I have a serializer which refers to another serializer with many=True. Simplified version:
class SerializerA(serializers.Serializer):
id = serializers.CharField()
quantity = serializers.IntegerField()
class SerializerB(serializers.Serializer):
name = serializers.CharField()
items = SerializerA(many=True, required=True)
When I'm getting data without items field specified, serializer.is_valid() for some reason returns True. But if 'many' argument set to False, validation works as expected.
Why is that happening?

I assume you're trying to perform PUT or POST. In this case, DRF do not support nested relations out of the box and if you don't have anything in the field items - validator returns true. It should return an error if you will have something in the items.
To make POST/PUT work you need to override .create() and .update() method of the SerializerB.

Related

How to test uniqueness in a Model.clean() function?

I have a model with a UniqueConstraint:
class MyModel(models.Model)
name = models.CharField()
title = models.CharField()
class Meta:
constraints = [ models.UniqueConstraint(
fields=['name', 'title'],
name="unique_name_and_title") ]
This works fine and raises an IntegrityError when 2 objects with the same values are created.
The problem is UniqueConstraint doesn't present a pretty ValidationError to the user. Usually, I would add these in the Model.clean() class, but if I do this then it will fail on an Update because the instance being updated will already be present:
def clean(self):
if MyModel.objects.filter(title=self.title, name=self.name):
raise ValidationError({'title':'An object with this name+title already exists'})
I How do I create a ValidationError that passes if it's an UPDATE not an INSERT?
I know I could do this on a ModelForm and use self.instance to check if the instance already exists, but I want to apply this to the Model class and not have to rely on a ModelForm.
You can exclude the object from the queryset you check:
def clean(self):
qs = MyModel.objects.exclude(pk=self.pk).filter(title=self.title, name=self.name)
if qs.exists():
raise ValidationError({'title':'An object with this name+title already exists'})
return super().clean()
If the object is not yet saved, it will check for .exclude(pk=None), but that will not exclude any objects, since the primary key is non-nullable.
It is more efficient to use .exists() [Django-doc] here, since it limits the bandwidth from the database to the Django/Python layer.

Is it possible to add non model field to method body in Django?

Is it possible to add non model field for instance to PATCH body? Let's take as an example that I would like to change password of the user. In my model I have only field password however in PATCH I would like to add old_password to authenticate user and then update password from password field from body. Any ideas? I found SerializerMethodField but I am not sure whether it is possible to do what I have described above.
you can simply add your fields to your serializer
class MyPatchSerializer(...):
old_password = serializers.CharField(...)
fields = [..., 'old_password']
if you want to validate this field simply add add a validate_old_pasword(self, value) to your serializer, docs
then in your viewset class you need to override get_serializer_class (docs), this way you tell DRF that if user is sending a PUT/PATCH request instead of default serializer you should use MyPatchSerializer
class MyViewSet(...):
...
def get_serializer(self):
if self.action in ('update', 'partial_update'):
return MyPatchSerializer
return self.serializer_class

I want my django rest framework serializer to accept input but not add to model

I removed some fields from my model, but I want the serializer to still accept the fields as input. How do I have fields the serializer accepts but doesn't use?
class EventBaseSerializer(ModelSerializer):
class Meta:
model = models.Event
fields = ("id", "name")
#unused_fields = ("last_name")
From http://www.django-rest-framework.org/api-guide/serializers/
You can add extra fields to a ModelSerializer or override the default
fields by declaring fields on the class, just as you would for a
Serializer class.
class AccountSerializer(serializers.ModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True)
groups = serializers.PrimaryKeyRelatedField(many=True)`
class Meta:
model = Account
If you want a field to be used for input but not output, you'll need to add this field to the fields list and mark it as write_only likely with extra_kwargs
In my case, I want to get data other than the model has and use them in the serializer methods for something else. But the default "create" method of the serializer try to create the object using those foreign fields too and should give an error like:
Got a TypeError when calling MyModel.objects.create(). This may be because you have a writable field on the serializer class that is not a valid argument to MyModel.objects.create(). You may need to make the field read-only, or override the MyModelCreateSerializer.create() method to handle this correctly.
I fixed the issue by popping them from the serializer's data after using them as I want and everything continued just fine.
class MyModelCreateSerializer(serializers.ModelSerializer):
foreign_input_1 = serializers.DateField(write_only=True)
foreign_input_2 = serializers.DateField(write_only=True)
class Meta:
model = MyModel
fields = '__all__'
def validate(self, data):
MySecondModel.objects.create(foreign_input_1=data.pop('foreign_input_1'),
foreign_input_1=data.pop('foreign_input_2'))
return data
Don't forget to use the write_only=True parameter on the foreign fields. The serializer will try to read them from the object in any data return operation, like returning the value of the created object.

Strange behavior with Django Rest Framework

I have an API developed with Django Rest Framework. I have a model with some nullable fields, they are defined with the setting
required = False
in the serializer. When I want to update an instance of this model, with a PUT request to the api, I succeed If I send the request parameters as form data, but If I send a json with request payload, the API returns a 400 bad request, stating that my non-required parameters can not be null, as in:
"gender":["This field may not be null."]
When I inspect the requests, the one with form data (which succeeds) sends:
email=abc%40abc.com&first_name=John&gender=&id=13&image_url=http%3A%2F%&last_name=Doe
And the one with json data (which fails with a 400 error) sends:
{
"id":13,
"email":"abc#abc.com",
"first_name":"John",
"last_name":"Doe",
"image_url":"http://...",
"gender":null
}
Any ideas what could be the reason?
EDIT: Model and serializer fields for gender:
In models.py:
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, null=True, blank=True)
In serializers:
gender = serializers.CharField(required=False, source='userprofile.gender')
EDIT:
From the docs:
Note: If your <field_name> is declared on your serializer with the parameter required=False then this validation step will not take place if the field is not included.
So validation step will take place if this fields is included, but still, as it is defined as nullable in the db, it should pass the validation.
When you mark a field as required=False it means that your request data can miss that field and value.
You send this in your request:
email=abc%40abc.com&first_name=John&gender=&id=13&image_url=http%3A%2F%&last_name=Doe
So you're sending a value for gender, I guess your problem is in your models, where gender is not marked as null=True. If you remove gender from your request, this should work.
email=abc%40abc.com&first_name=John&id=13&image_url=http%3A%2F%&last_name=Doe
You can use in serializers.CharField the options default. This way must work
There is a serializer option, allow_null in Django Rest Framework. I had to set it to True for nullable fields in the serializer. It started working after that.
gender = serializers.CharField(required=False, allow_null=True, source='userprofile.gender'
Though I still do not know why I need to set this flag explicitly, as the field is already defined as nullable in the model.

Overwriting serializer with request data including nulls for absent keys

Let's imagine there is a serializer like this:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = (
'title',
'description'
)
Where description is nullable. What I want is that the request data completely overwrite the serializer data on PUT request (when updating an existing model instance obviously). If I do:
event_serializer = EventSerializer(event, data=request_data)
It does overwrite everything, but it doesn't nullify description if it is absent from the request. Is there a way to do that without doing manually:
data['description'] = data.get('description', None)
One option is to define the description field on the serializer and use default like:
class EventSerializer(serializers.ModelSerializer):
# Use proper field type here instead of CharField
description = serializers.CharField(default=None)
class Meta:
model = Event
fields = (
'title',
'description'
)
See the documentation as well:
default
If set, this gives the default value that will be used for the
field if no input value is supplied. If not set the default behavior
is to not populate the attribute at all.
May be set to a function or other callable, in which case the value
will be evaluated each time it is used.
Note that setting a default value implies that the field is not
required. Including both the default and required keyword arguments is
invalid and will raise an error.

Categories