Why isn't my Django User Model's Password

2019-01-14 22:37发布

I am using the Django REST Framework (DRF) to create an endpoint with which I can register new users. However, when I hit the creation endpoint with a POST, the new user is saved via a serializer, but the password is saved in cleartext in the database. The code for my serializer is as follows:

from django.contrib.auth import get_user_model
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = get_user_model()
        fields = ['password', 'username', 'first_name', 'last_name', 'email']
        read_only_fields = ['is_staff', 'is_superuser']
        write_only_fields = ['password']

Please note that I am using the default User model from the Django auth package, and that I am very new to working with DRF! Additionally, I have found this question which provides a solution, but this appears to require two database interactions -- I do not believe that this is efficient, but that might be an incorrect assumption on my part.

3条回答
兄弟一词,经得起流年.
2楼-- · 2019-01-14 23:12

The issue is DRF will simply set the field values onto the model. Therefore, the password is set on the password field, and saved in the database. But to properly set a password, you need to call the set_password() method, that will do the hashing.

There are several ways to do this, but the best way on rest framework v3 is to override the update() and create() methods on your Serializer.

class UserSerializer(serializers.ModelSerializer):
    # <Your other UserSerializer stuff here>

    def create(self, validated_data):
        password = validated_data.pop('password', None)
        instance = self.Meta.model(**validated_data)
        if password is not None:
            instance.set_password(password)
        instance.save()
        return instance

    def update(self, instance, validated_data):
        for attr, value in validated_data.items():
            if attr == 'password':
                instance.set_password(value)
            else:
                setattr(instance, attr, value)
        instance.save()
        return instance

Two things here:

  1. we user self.Meta.model, so if the model is changed on the serializer, it still works (as long as it has a set_password method of course).
  2. we iterate on validated_data items and not the fields, to account for optionally excludeed fields.

Also, this version of create does not save M2M relations. Not needed in your example, but it could be added if required. You would need to pop those from the dict, save the model and set them afterwards.

FWIW, I thereby make all python code in this answer public domain worldwide. It is distributed without any warranty.

查看更多
我命由我不由天
3楼-- · 2019-01-14 23:24

This worked for me.

class UserSerializer(serializers.ModelSerializer):
    def create(self, *args, **kwargs):
        user = super().create(*args, **kwargs)
        p = user.password
        user.set_password(p)
        user.save()
        return user

    def update(self, *args, **kwargs):
        user = super().update(*args, **kwargs)
        p = user.password
        user.set_password(p)
        user.save()
        return user

    class Meta:
        model = get_user_model()
        fields = "__all__" 
查看更多
混吃等死
4楼-- · 2019-01-14 23:31

just override the create and update methods of the serializer:

   def create(self, validated_data):
        user = get_user_model(**validated_data)
        user.set_password(validated_data['password'])
        user.save()
        return user

    def update(self, instance, validated_data):
        for f in UserSerializer.Meta.fields + UserSerializer.Meta.write_only_fields:
            set_attr(instance, f, validated_data[f])
        instance.set_password(validated_data['password'])
        instance.save()
        return instance
查看更多
登录 后发表回答