14

I am following this tutorial but facing these problems I can't fix:

  1. Upon registering user, I can not log in with that user to the api because the password is not hashed "Invalid password format or unknown hashing algorithm." in admin
  2. I cannot post to 'api/accounts' or see the form in the browseable api when I am not logged in to the api

My code:

from django.contrib.auth.models import User
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('password', 'first_name', 'last_name', 'email')
        write_only_fields = ('password',)

    def restore_object(self, attrs, instance=None):
        # call set_password on user object. Without this
        # the password will be stored in plain text.
        user = super(UserSerializer, self).restore_object(attrs, instance)
        user.set_password(attrs['password'])  #somehow not hashing
        return user
mathielo
  • 6,725
  • 7
  • 50
  • 63
user3149525
  • 293
  • 2
  • 4
  • 10

6 Answers6

13

I tried the accepted answer in DRF 3.0.2 and it didn't work. The password was not being hashed.

Instead, override the create method in your model serializer

    def create(self, validated_data):
        user = User(email=validated_data['email'], username=validated_data['username'])
        user.set_password(validated_data['password'])
        user.save()
        return user

This hashes the password when you create a user using the rest framework, not post_save

wsgeorge
  • 1,928
  • 17
  • 20
  • 4
    It might be easier to user `User.objects.create_user(**validated_data)` to create the user (with hashed password) rather than doing it in two lines – aensm Nov 18 '15 at 22:27
6

Another approach for DRF 3.X:

from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password

    def create(self, validated_data):    
        if validated_data.get('password'):
            validated_data['password'] = make_password(
                validated_data['password']
            )

        user = get_user_model().objects.create(**validated_data)

        return user
psychok7
  • 5,373
  • 9
  • 63
  • 101
3

I've used wsgeorge's solution to build my own. A blank User object is created just so I can use .set_password():

def create(self, validated_data):
    user = User()
    user.set_password(validated_data['password'])
    validated_data['password'] = user.password
    return super(UserSerializer, self).create(validated_data)

Different from his answer, I do not save the user myself. I leave that to the parent class calling super.

Community
  • 1
  • 1
mathielo
  • 6,725
  • 7
  • 50
  • 63
1

Please note that set_password() does NOT save the object and since you have called the super first, your object is already saved with raw password.

Just simply use post_save() to save the password.

def post_save(self, obj, created=False):
    """
    On creation, replace the raw password with a hashed version.
    """
    if created:
        obj.set_password(obj.password)
        obj.save()
user3149525
  • 293
  • 2
  • 4
  • 10
0

Override create of model serialzier

def create(self, validated_data):
        if validated_data.get('password'):
            validated_data['password'] = make_password(validated_data['password'])
        return super(UserSerializer, self).create(validated_data)

Be sure to import

from django.contrib.auth.hashers import make_password
Emmanuel Mtali
  • 4,383
  • 3
  • 27
  • 53
-1

We can write a signal in User to solve this.

def create_hash(sender, instance=None, *args, **kwargs):
passwd = instance.password
instance.set_password(passwd)


pre_save.connect(create_hash, sender=User)
asitm9
  • 853
  • 4
  • 12
  • 21