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?
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')
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!")
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?
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')
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!")