Reputation: 13315
Using django-rest-framework 3 and django 1.8
I am trying to create a user using django-rest-framework ModelViewSerializer. problem is that the default objects.create method used by DRF leave the password as plain text.
The problem is that DRF serialzer create method is using objects.create
querysets/#create method instead of using objects.create_user
method.
code from serializers.py line 775
instance = ModelClass.objects.create(**validated_data)
What is the best solution for this? i can override the serializer.create method to use objects.user_create instead of objects.create but it does not feel like the right solution.
rest of code:
from django.contrib.auth.models import User
from rest_framework import viewsets
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email','password')
write_only_fields = ('password',)
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer = UserSerializer()
Upvotes: 16
Views: 21004
Reputation: 764
In addition to @aliva's answer where you miss out on the functionalities in serializers.Modelserializer.create()
(which could be quite nice to preserve, for example handling of many-to-many relations), there is a way to keep this.
By using the user.set_password()
method, the password can also be correctly set, like:
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
user = super().create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
This has the benefit of keeping the super class' functionality, but the downside of an additional write to the database. Decide which trade-off is more important to you :-).
See documentation for set_password.
Upvotes: 10
Reputation: 5740
you can override create
in UserSerializer:
class UserSerializer(serializers.ModelSerializer):
# ....
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
other solutions can be overriding perform_create
in ViewSet class or you can write your own create
method in your viewset class
class UserViewSet(viewsets.ModelViewSet):
def create(self, request, format=None):
# create user here
# do not call seriailzer.save()
UPDATE: after @freethebees commented, overriding perform_create
also works, so here is the code snippet:
class UserViewSet(viewsets.ModelViewSet, mixins.CreateModelMixin):
def perform_create(self, serializer):
# use User.objects.create_user to create user
pass
NOTE: this answer gives 3 solutions, choose the one you think it better fits your needs and your project's ecosystem
NOTE 2
I personally prefer overriding create
in UserViewSet
(second code snippet) because there you can simply return your custom Response
(for example return user profile after login)
Upvotes: 22
Reputation: 4665
A complete example that support POST
and PUT
/PATCH
without another SQL UPDATE statement.
class MyUserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = '__all__'
def create(self, validated_data):
if "password" in validated_data:
from django.contrib.auth.hashers import make_password
validated_data["password"] = make_password(validated_data["password"])
return super().create(validated_data)
def update(self, instance, validated_data):
if "password" in validated_data:
from django.contrib.auth.hashers import make_password
validated_data["password"] = make_password(validated_data["password"])
return super().update(instance, validated_data)
Upvotes: 3
Reputation: 197
There is even better option to validate password in serializer
from django.contrib.auth.hashers import make_password
class UserSerializer(serializers.ModelSerializer):
def validate_password(self, value: str) -> str:
return make_password(value)
Upvotes: 9