encode / django-rest-framework

Web APIs for Django. 🎸
https://www.django-rest-framework.org
Other
27.83k stars 6.76k forks source link

3.15 is raising required error on model nullable fields #9378

Open Enorio opened 2 months ago

Enorio commented 2 months ago

I have a model with nullable fields. Upgrading from 3.14 to 3.15 I'm getting this error: AssertionError: You cannot call .save() on a serializer with invalid data.

When I print the serializer.errors after serializer.is_valid(), it says those fields are required. Is it possible that this is a bug, or do I have to manually override the field in the serializer to have the required=False kwarg?

sevdog commented 2 months ago

Could you provide a code sample of your model, serializer and data for checking?

Enorio commented 1 month ago
class Foo(...):
    ...
    value = models.IntegerField(null=True)

class FooSerializer(serializers.ModelSerializer):
    class Meta:
        model = Foo
        fields = '__all__'

serializer = FooSerializer(data=data) # This data does not have the "value" field
serializer.is_valid()
saved_data = serializer.save()

Raises this error AssertionError: You cannot call .save() on a serializer with invalid data.

sevdog commented 1 month ago

The value field does not provide any default, so it is expected to fail if no value for that field is provided.

The real issue would be how did that ever worked without a default in the first place? It should have worked if you had default=None or blank=True on your model's field (but I am not confident in the latter alone would work).

Usually a nullable field which is not required in forms is defined as:

class Foo(...):
    ...
    value = models.IntegerField(null=True, default=None, blank=True)
Enorio commented 1 month ago

I'm using a django v3.y.z for now, don't know if that's the problem. I have a lot of values with null=True without the default kwarg and never had a problem in the serializer. My guess is if you have only null, this counts as default=None?

sevdog commented 1 month ago

My guess is if you have only null, this counts as default=None?

It is not documented nor in the codebase of django such behaviour for null or default. Django and the majority of DBMS does not suppose the default based on the assumption that a field is nullable.

I can only guess that there is something else which is providing a value for those fields in your project (a middleware? the frontend? custom views? custom serializers? custom base models?).

Enorio commented 1 month ago

I don't think so, what I have it's pretty standard. I'll look better into it and check the differences between these 2 DRF versions.

BPC-AnonymousBeast commented 5 days ago

I don't think so, what I have it's pretty standard. I'll look better into it and check the differences between these 2 DRF versions.

I tried to reproduce the error you are getting. I have pretty much done what you have done. Infact, inspired by another similar issue, I have also added a foreign key constraint. I'm able to get back null even if I don't add that field in the body of the post request.

 class Color(models.Model):
      color_name = models.CharField(max_length=100)

      def __str__(self) -> str:
          return self.color_name

  class Person(models.Model):
      name = models.CharField(max_length=100)
      lastname = models.CharField(max_length=100,null=True)
      age = models.IntegerField(null=True)
      color = models.ForeignKey(Color, on_delete=models.CASCADE,related_name= 'color', null=True)
  class PeopleSerialiser(serializers.ModelSerializer):
      # my_id = serializers.PrimaryKeyRelatedField()

      class Meta:
          model = Person
          fields = "__all__"
 @api_view(['GET','POST'])
  def person(request):
      if request.method == 'GET':
          objs = Person.objects.all()
          serializer = PeopleSerialiser(objs, many = True)

          return Response(serializer.data)
image

If you want to make some changes to my code and try again to reproduce the error. Please let me know. Note : I'm using djangorestframework 3.15.1 : -

image
Enorio commented 1 day ago

Can you try your view with the is_valid(), like this? Didn't try your code, but my error comes from the is_valid() method, that adds info to the self._errors

@api_view(['GET','POST'])
 def person(request):
     if request.method == 'GET':
         objs = Person.objects.all()
         serializer = PeopleSerialiser(objs, many = True)
         serializer.is_valid(raise_exception=True)

         return Response(serializer.data)