Making a field required for POST, but optional for PUTs?

8,975 views
Skip to first unread message

Victor Hooi

unread,
Aug 9, 2013, 2:19:39 AM8/9/13
to django-res...@googlegroups.com
hi,

We have a Customer object, which inherits from Django's AbstractUser.

We also have a Customer serializer:

class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ('id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_active', 'gender', 'birth_year')

To create a new Customer (User), we send a POST to /api/v1/customers/. The view for this looks like:

class CustomerList(generics.ListCreateAPIView):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer
    def pre_save(self, obj):
        obj.password = make_password(obj.password)

The POST includes username and password, along with some optional fields (e.g. gender).

We can edit a Customer (User) by sending a PUT to /api/v1/customers/<id>/. The view for this is just:

class CustomerDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer

However, if I want to change the gender for a Customer, I need to send the password field again - we'd like to just be able to send the gender.

Is there some way to make Customer.Password require for the initial POST, but then make it optional for PUT?

(And ideally, we'd also like to hide it from GETS to /api/v1/customers/<id>/ - as in, we don't want this one to return the password hash.).

I was thinking of just creating a second Customer Serializer and using it for the Detail view, but not sure if that's a bit hacky? Also, password field is required on the model level, so not sure how that would work.

Cheers,
Victor

Yuri Prezument

unread,
Aug 10, 2013, 6:02:30 AM8/10/13
to django-res...@googlegroups.com
Sounds like in your case, you just need to use PATCH instead of PUT, or set "partial=True" in the serializer, which will allow updating the fields you want to update.

Victor Hooi

unread,
Aug 11, 2013, 7:53:49 PM8/11/13
to django-res...@googlegroups.com
Hi,

Aha, that does look like what I want =).

Hmm, I'm using a class-based Generic View at the moment - so my view looks like this:

class CustomerDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer

How exactly would you add an argument to this? Changing serializer_class = CustomerSerializer(partial=True) doesn't work, since it expects a Serializer object.

Also - how about when we serialize and return a Customer object - is it possible to customize the serializer to not show the password field then? (But still accept it for POSTs).

Cheers,
Victor

Yuri Prezument

unread,
Aug 12, 2013, 4:00:31 AM8/12/13
to django-res...@googlegroups.com
How exactly would you add an argument to this? Changing serializer_class = CustomerSerializer(partial=True) doesn't work, since it expects a Serializer object.

The easiest is to use PATCH instead of PUT. That should solve the "partial" problem.

Before DRF had patch support, I used to do something like this in the serializer init:

class PartialSerializer(serializers.ModelSerializer):
     def __init__(self, *args, **kwargs):
        kwargs['partial'] = True
        super(PartialSerializer, self).__init__(*args, **kwargs)

Victor Hooi

unread,
Aug 12, 2013, 9:19:14 PM8/12/13
to django-res...@googlegroups.com
Hi,

You're right, PATCH seems to be the correct way to go =). We've tested, all good on this side.

Also:


I noticed that PATCH doesn't seem to be explicitly available through the browserable DRF web interface - there's a form for PUT, but none for PATCH - is this intended?

In terms of stripping the password field from GET calls to each Customer (User) - could I customize the ModelSerializer to somehow set this field to None on serialization (i.e. Django object o JSON), but to operate normally on deserialise (i.e. JSON to Django object)?

Cheers,
Victor

Victor Hooi

unread,
Aug 12, 2013, 9:53:46 PM8/12/13
to django-res...@googlegroups.com

Yuri Prezument

unread,
Aug 13, 2013, 4:56:38 AM8/13/13
to django-res...@googlegroups.com
> In terms of stripping the password field from GET calls to each Customer (User) - could I customize the ModelSerializer to somehow set this field to None on serialization (i.e. Django object o JSON), but to operate normally on deserialise (i.e. JSON to Django object)?

You could override to_native() in the serializer to remove the password field, or set it to None.

Victor Hooi

unread,
Aug 13, 2013, 6:27:29 AM8/13/13
to django-res...@googlegroups.com
Hi,

Cool, thanks, I've just taken a stab at doing what you suggested.

In my customers/serializers.py:

from customers.models import Customer
from rest_framework import serializers 
 
class PasswordField(serializers.CharField):
    def to_native(self, value):
        return None 
 
class CustomerSerializer(serializers.ModelSerializer):
    password = PasswordField()

    class Meta:
        model = Customer
        fields = ('id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_active', 'gender', 'birth_year')

From my testing, it appears to works great - doing GET on a Customer gives me Null for password field, however, POSTing to create a new Customer (User) sets the password correctly =).

Does the above look correct to you? Any other suggestions you'd make?

As an aside, it'd be awesome if these small tips and examples could be collected somewhere in the documentation - the functionality is covered, but sometimes more examples really help show what's capable =).

Cheers,
Victor

Yuri Prezument

unread,
Aug 13, 2013, 6:47:34 AM8/13/13
to django-res...@googlegroups.com
Looks ok,

Another thing you could do is remove the field completely in serializer.to_native:

class CustomerSerializer(serializers.ModelSerializer):
    # ...

    def to_native(self, obj):
        native = super(CustomerSerializer, self).to_native(obj)
        native.pop('password', None)
        return native

Yuri Prezument

unread,
Aug 13, 2013, 8:06:07 AM8/13/13
to django-res...@googlegroups.com
Forgot to mention - I didn't test the above code.
Reply all
Reply to author
Forward
0 new messages