ModelSerializer doesn't inherit model field default values unless db_index true, and inconsistent response depending on POST Content-Type header

800 views
Skip to first unread message

Mark Mikofski

unread,
Aug 4, 2017, 8:19:36 PM8/4/17
to Django REST framework
A vanilla ModelSerializer does not inherit the model field default values unless db_index=true, which is okay for a POST request with JSON iff Content-Type header is set to application/json.
But if the the content type is not set, then if the field is not in the body of the POST, and the field is a boolean, then data is set to default_empty_html which is "false".
Is this expected?

I apologize if this is the expected behavior!
Also I didn't try this with the newest versions of Django or DRF.

Versions:
rest_framework==3.5.4
django==1.10
python==2.7.13

Here is a vanilla django model with 2 fields, both have defaults, but one is indexed (?):
class MyModel(models.Model):
    field1 = models.BooleanField(
        "indexed boolean field", default=False, db_index=True
    )
    field2 = models.BooleanField(default=True)  # not indexed, no label
    field3 = models.CharField('blah', max_length=100)

And a vanilla DRF model serializer
class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = '__all__'

If I serialize a dictionary in the django shell, and call the serializer's save() method, the correct defaults are used
from myapp.serializers import MyModelSerializer

x = {'field3': 'blah blah blah'}
s = MyModelSerializer(data=x)
s.is_valid()
o = s.save()
assert o.field1  # OK!
assert o.field2  # OK!

but if I use a test client, then it doesn't return the correct defaults
from rest_framework.test import APIClient
from django.contrib.auth.models import User
from django.conf import settings

settings.ALLOWED_HOSTS.append(u'testserver')
me = User.objects.get(username='me')
client = APIClient()
client.force_authenticate(user=me)
r = client.post('/mymodel/', x)
o = r.json()
assert o['field1']  # OK!
assert o['field2']  # FAILS!

if I use the test client with the correct content type, now it works again!
r = client.post('/mymodel/', x, format='json')  # it's JSON not HTML!
o = r.json()
assert o['field1']  # OK!
assert o['field2']  # OK!

why? If I look at the fields in the serializer, only the indexed fields have defaults, some of the fields have labels even though they aren't explicitly defined in the model
s.fields
{
  'field1': BooleanField(default=True, label='indexed boolean field', required=False),
  'field2': BooleanField(label='field2', required=False),
  'field3': CharField(label='blah', max_length=100)
}

And the serializer fields are treated differently depending on whether or not they are assumed be HTML based on a call to rest_framework.utils.html.is_html_input which returns true if the data has the getlist attribute.
If the data is JSON or a Python dictionary, it doesn't have getlist, so is_html_input returns False and the field's get_value(data) call returns the empty field.
But if the data is posted without the JSON content type header, then since the field has no default value, the default_empty_html value is returned instead, which is False for boolean fields.

:-(

I guess the moral of the story is make sure you are using the correct content type in your POST request, or you never know what you might get? If anyone has any response that will make me feel less crazy, I would appreciate it.
Thanks! And thanks Tom Christie and all of the other maintainers for DRF. I love it!

:-)
Reply all
Reply to author
Forward
0 new messages