yossi
yossi

Reputation: 13315

django rest framework create user with password

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

Answers (4)

Erikw
Erikw

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

aliva
aliva

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

BaiJiFeiLong
BaiJiFeiLong

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

Alwx
Alwx

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

Related Questions