For example I have the following serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
But I don't want to output password on GET (there are other fields in my real example of course). How can I do that without writing other serializer? Change the field list on the fly. Is there any way to do that?
You appear to be looking for a write-only field. So the field will be required on creation, but it won't be displayed back to the user at all (the opposite of a read-only field). Luckily, Django REST Framework now supports write-only fields with the write_only
attribute.
In Django REST Framework 3.0, you just need to add the extra argument to the extra_kwargs
meta option.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
extra_kwargs = {
'password': {
'write_only': True,
},
}
Because the password
should be hashed (you are using Django's user, right?), you are going to need to also hash the password as it is coming in. This should be done on your view, most likely by overriding the perform_create
and perform_update
methods.
from django.contrib.auth.hashers import make_password
class UserViewSet(viewsets.ViewSet):
def perform_create(self, serializer):
password = make_password(self.request.data['password'])
serializer.save(password=password)
def perform_update(self, serializer):
password = make_password(self.request.data['password'])
serializer.save(password=password)
In Django REST Framework 2.x, you need to completely redefine the password
field on the serializer.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = (
'userid',
'password'
)
In order to hash the password ahead of time in Django REST Framework 2.x, you need to override pre_save
.
from django.contrib.auth.hashers import make_password
class UserViewSet(viewsets.ViewSet):
def pre_save(self, obj, created=False):
obj.password = make_password(obj.password)
super(UserViewSet, self).pre_save(obj, created=created)
This will solve the common issue with the other answers, which is that the same serializer that is used for creating/updating the user will also be used to return the updated user object as the response. This means that the password will still be returned in the response, even though you only wanted it to be write-only. The additional problem with this is that the password may or may not be hashed in the response, which is something you really don't want to do.
Just one more thing to @Kevin Brown's solution.
Since partial update
will also execute perform_update
, it would be better to add extra code as following.
def perform_update(self, serializer):
if 'password' in self.request.data:
password = make_password(self.request.data['password'])
serializer.save(password=password)
else:
serializer.save()
this should be what you need. I used a function view but you can use class View or ViewSet (override get_serializer_class
) if you prefer.
Note that serializer_factory
also accept exclude=
but, for security reason, I prefer use fields=
serializer_factory
create a Serializer class on the fly using an existing Serializer as base (same as django modelform_factory
)
==============
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'userid',
'password'
)
@api_view(['GET', 'POST'])
def user_list(request):
User = get_user_model()
if request.method == 'GET':
fields=['userid']
elif request.method == 'POST':
fields = None
serializer = serializer_factory(User, UserSerializer, fields=fields)
return Response(serializer.data)
def serializer_factory(model, base=HyperlinkedModelSerializer,
fields=None, exclude=None):
attrs = {'model': model}
if fields is not None:
attrs['fields'] = fields
if exclude is not None:
attrs['exclude'] = exclude
parent = (object,)
if hasattr(base, 'Meta'):
parent = (base.Meta, object)
Meta = type(str('Meta'), parent, attrs)
if model:
class_name = model.__name__ + 'Serializer'
else:
class_name = 'Serializer'
return type(base)(class_name, (base,), {'Meta': Meta, })
As far as i can tell from the docs, the fastest way would be to simply have 2 serializers becalled conditionally from your view.
Also, the docs show this other alternative, but it's a little too meta:
http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
it involves creating smart initializer methods, gives an example. i'd just use 2 serializers, if i'd know those changes are the only ones i'll make. otherwise, check the example