Why doesn't DRF's serializer validate PositiveSmallIntegerField?

Question:

Using Django 1.11 and Django Rest Framework 3.7, I have a Person model

class Person(models.Model):

    name = models.CharField(max_length=100)
    email = models.EmailField()
    age = models.PositiveSmallIntegerField()

with a PersonSerializer

class PersonSerializer(serializers.ModelSerializer):
    class Meta:
        model = Person
        fields = ('id', 'name', 'age', 'email')

and a ListCreate view

class PersonList(generics.ListCreateAPIView):
    queryset = Person.objects.all()
    serializer_class = PersonSerializer

Using HTTPie, I can create a Person like this:

$ http POST http://127.0.0.1:8000/api/people/ name=Alice age=26 [email protected]
HTTP/1.0 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 60
Content-Type: application/json
Date: Sun, 10 Dec 2017 15:00:28 GMT
Server: WSGIServer/0.1 Python/2.7.11
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "age": 26,
    "email": "[email protected]",
    "id": 1,
    "name": "Alice"
}

When I create a Person with a bad email address, I get an error:

$ http POST http://127.0.0.1:8000/api/people/ name=Bob age=33 email=oops
HTTP/1.0 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 42
Content-Type: application/json
Date: Sun, 10 Dec 2017 15:01:08 GMT
Server: WSGIServer/0.1 Python/2.7.11
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "email": [
        "Enter a valid email address."
    ]
}

DRF knows it’s an EmailField and automatically applies validation, so far so good.

However, when I create a Person with a bad age (negative number), I get no error:

$ http POST http://127.0.0.1:8000/api/people/ name=Charlie age=-10 email=charlie@example
.com
HTTP/1.0 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 65
Content-Type: application/json
Date: Sun, 10 Dec 2017 15:03:25 GMT
Server: WSGIServer/0.1 Python/2.7.11
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "age": -10,
    "email": "[email protected]",
    "id": 3,
    "name": "Charlie"
}

Now my database has been polluted with bad data. I have no problem doing the work to validate my inputs, but

  • DRF correctly validated the email field, leading me to believe it will validate inputs based on the type of field in the model.
  • If I had POSTed from an html form, Django’s ModelForm would and does validate both the email and age fields.
  • If I had created a Person from the standard Django Admin, it also would and does validate both the email and age fields.

Based on those facts, my questions are:

(A) Why does DRF’s serializer validate EmailField, but not PositiveSmallIntegerField?

(B) Where am I supposed to validate the ‘age’ field to make sure it’s positive? Model? Serializer? View?

Asked By: epalm

||

Answers:

In DRF, IntegerField corresponds to PositiveIntegerField so you can set max and min value limitation on it.

eg:

class PersonSerializer(serializers.ModelSerializer):
    age = serializers.IntegerField(max_value=100, min_value=1)
    class Meta:
        model = Person
        fields = ('id', 'name', 'age', 'email')
Answered By: Saji Xavier

Add validators to the field in the Model:

from django.core.validators import MinValueValidator
from django.core.validators import MaxValueValidator

class Person(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()
    age = models.PositiveSmallIntegerField(validators=[MinValueValidator(0), MaxValueValidator(120)])

If one need to perform more operation with integer fields in models

class YOURSerializer(serializers.ModelSerializer):

age= serializers.IntegerField(required=True)


    class Meta:
    model = User
    fields = '__all__'

    def validate(self, data):
      age= data.get('age', None)
    
      if age == 18:
          raise serializers.ValidationError("Age 18 people are given special discount, Congrats!")
Answered By: Saurav Fouzdar