0

I have created custom Userclass in django(AbstarctUser). Everything works fine but my password is getting stored as plain text in database even after registering in admin.py. I do not have any forms.py explicitly defined.

Also I am using nested serializers following tutorial.

My code is as below

from django.contrib import admin
from .models import BasicUserInfo
from django.contrib.auth.admin import UserAdmin


class BasicUserAdmin(UserAdmin):
    pass

admin.site.register(BasicUserInfo, BasicUserAdmin)

Edited to add Models and views

Models.py

class BasicUserInfo(AbstractUser):
    email = models.EmailField(primary_key=True, unique=True, db_index=True)

class UserInfo(models.Model):
    user = models.ForeignKey(BasicUserInfo, on_delete=models.CASCADE)

Views.py

serializer = AddUserSerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
    serializer.create(validated_data=request.data)

Serializers.py

class BasicUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = BasicUserInfo
        fields = ('username', 'password', 'email')

    print("hete")

    def create(self, validated_data):
        retval =  BasicUserInfo.objects.create(**validated_data)
        password = validated_data.pop('password')
        self.password = make_password(password)
       # self._password = password
        return retval

class AddUserSerializer(serializers.ModelSerializer):
    user = BasicUserSerializer(required=True)

    class Meta:
        model = UserInfo
        fields = ('phoneNo')

    def create(self, validated_data):
        user_data = validated_data.pop('user')
        user = BasicUserSerializer.create(BasicUserSerializer(), validated_data=user_data)
        user_info, created = UserInfo.objects.update_or_create(user=user, phoneNo=validated_data.pop('phoneNo'))
        return user_info
rtindru
  • 5,107
  • 9
  • 41
  • 59
Rashmi
  • 15
  • 8
  • Show your models and views. Django admin just displays info passed through them. – Chiefir May 23 '18 at 17:41
  • What does `AddUserSerializer` look like? – rtindru May 23 '18 at 18:51
  • user = BasicUserSerializer(required=True) class Meta: model = UserInfo fields = ('phoneNo') def create(self, validated_data): user_data = validated_data.pop('user') user = BasicUserSerializer.create(BasicUserSerializer(), validated_data=user_data) user_info, created = UserInfo.objects.update_or_create(user=user, phoneNo=validated_data.pop('phoneNo')) – Rashmi May 23 '18 at 19:12

2 Answers2

1

The trick is to use user.set_password(password) -> this internally triggers the password hashing mechanism: Here's the Django code that does this:

def set_password(self, raw_password):
    self.password = make_password(raw_password)
    self._password = raw_password

def make_password(password, salt=None, hasher='default'):
    """
    Turn a plain-text password into a hash for database storage

    Same as encode() but generate a new random salt. If password is None then
    return a concatenation of UNUSABLE_PASSWORD_PREFIX and a random string,
    which disallows logins. Additional random string reduces chances of gaining
    access to staff or superuser accounts. See ticket #20079 for more info.
    """
    if password is None:
        return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
    hasher = get_hasher(hasher)
    salt = salt or hasher.salt()
    return hasher.encode(password, salt)

So the problem is serializers.create(**validated_data) is not performing the make_password operation. The above answer works perfectly fine, except it does two things differently - It saves the user twice (once in serailizer.create and again during `user.save()) - It does not hande everything within the serializer, part of the work is being split b/w the serializer and the view.

If you want to keep it all within the serializer, you can do the following:

class AddUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = BasicUserInfo

    def validate_password(self, value):
        return make_password(value)

Update:

I've made a bunch of edits; and tried to explain why. Please read patiently, and incorporate changes as you see fit.

class BasicUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = BasicUserInfo
        fields = ('username', 'password', 'email')

    def validate_password(self, value):
        return make_password(value)

class AddUserSerializer(serializers.ModelSerializer): 
    user = BasicUserSerializer(required=True) 
    class Meta: 
        model = UserInfo 
        fields = ('phoneNo') 

    def create(self, validated_data): 
        user_data = validated_data.pop('user') 
        user_serializer = BasicUserSerializer(data=user_data)
        if user_serializer.is_valid(raise_exception=True):
            user = user_serializer.save()
        validated_data['user'] = user
        return UserInfo.objects.create(**validated_data)
rtindru
  • 5,107
  • 9
  • 41
  • 59
  • This returns Hashed password. But my database still contains rawpassword in it – Rashmi May 23 '18 at 19:03
  • Please share your full serializer declaration; view file, etc. Partial snapshots are not useful and lead to incomplete understanding of the issue. – rtindru May 23 '18 at 19:12
  • class BasicUserSerializer(serializers.ModelSerializer): class Meta: model = BasicUserInfo fields = ('username', 'password', 'email') print("hete") def create(self, validated_data): retval = BasicUserInfo.objects.create(**validated_data) password = validated_data.pop('password') self.password = make_password(password) # self._password = password return retval – Rashmi May 23 '18 at 19:16
  • class AddUserSerializer(serializers.ModelSerializer): user = BasicUserSerializer(required=True) class Meta: model = UserInfo fields = ('phoneNo') def create(self, validated_data): user_data = validated_data.pop('user') user = BasicUserSerializer.create(BasicUserSerializer(), validated_data=user_data) user_info, created = UserInfo.objects.update_or_create(user=user, phoneNo=validated_data.pop('phoneNo')) return user_info – Rashmi May 23 '18 at 19:16
  • This throws me a runtime error. "save() takes 1 positional argument but 2 were given". Also are we overriding save function here? – Rashmi May 23 '18 at 19:50
  • No it still does not. When I try to post the AddUserSerializer does not get updated. I get error for user NOT NULL constraint failed. Trying to figure out – Rashmi May 24 '18 at 15:17
  • What's the json you are posting – rtindru May 24 '18 at 15:19
  • { "user":{ "username":"person124", "email":"person124@xyz.com", "password":"abc" }, "phoneNo": "9538521221" } – Rashmi May 24 '18 at 15:20
  • Got it, updating code. Error is that the newly created user's ID needs to be set for the add user serialize to save – rtindru May 24 '18 at 15:30
  • Also there is assertion error : AssertionError at /userinfo/ The `.create()` method does not support writable nested fields by default. Write an explicit `.create()` method for serializer `userinfo.serializers.AddUserSerializer`, or set `read_only=True` on nested serializer fields. – Rashmi May 24 '18 at 15:48
  • Ran into another problem. Cannot assign "'person124@xyz.com'": "UserInfo.user" must be a "BasicUserInfo" instance. – Rashmi May 24 '18 at 15:54
  • Hi Thanks a lot.. It worked. Had to minor change. Used validated_data['user_id'] = user.pk instead of validated_data['user'] = user.pk – Rashmi May 24 '18 at 16:11
0

You should not use like this:

serializer = AddUserSerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
    serializer.create(validated_data=request.data)

if password in validated data

it is better to use like this:

password = request.data.pop('password', '')
if not password:
    raise ValidationError('password must not be empty')
serializer = AddUserSerializer(data=request.data)
serializer.is_valid(raise_exception=ValueError):
user = serializer.create(validated_data=request.data)
user.set_password(password)
user.save()
Andrei Berenda
  • 1,946
  • 2
  • 13
  • 27
  • I am getting Validation error when trying to pop. My JSON format is like below:{ "user":{ "username":"randomguy", "email":"randomguy@gmail.com", "password":"djangoproject" }, "phoneNo": "1234"} – Rashmi May 23 '18 at 18:44
  • 1
    you should set your password = serializers.CharField(requered=False), and handle this field by yourself (the code above) – Andrei Berenda May 24 '18 at 04:12