I have my own custom User model, and its own Manger too.
models:
class MyUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True)
first_name = models.CharField(max_length=35)
last_name = models.CharField(max_length=35)
username = models.CharField(max_length=70, unique=True)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
#property
def is_staff(self):
return self.is_admin
def get_full_name(self):
return ('%s %s') % (self.first_name, self.last_name)
def get_short_name(self):
return self.username
objects = MyUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name', 'username', 'date_of_birth']
manager:
class MyUserManager(BaseUserManager):
def create_user(self, email, first_name, last_name, username, date_of_birth, password=None, **kwargs):
if not email:
raise ValueError('User must have an email address')
user = self.model(
email=self.normalize_email(email),
first_name=first_name,
last_name=last_name,
username=username,
date_of_birth=date_of_birth,
**kwargs
)
user.set_password(self.cleaned_data["password"])
user.save(using=self._db)
return user
def create_superuser(self, email, first_name, last_name, username, date_of_birth, password, **kwargs):
user = self.create_user(
email,
first_name=first_name,
last_name=last_name,
username=username,
date_of_birth=date_of_birth,
password=password,
is_superuser=True,
**kwargs
)
user.is_admin = True
user.save(using=self._db)
return user
Everything works when creating a new user without any errors. But when I try to login I can't. So I checked the user's password to confirm and the password is displayed as plain text strongpassword, and when changed admin form to get the hashed password using ReadOnlyPasswordHashField I get an error inside the password field, even though I used set_password() for the Manger inside the create_user() function.
Invalid password format or unknown hashing algorithm
However, if I manually do set_password('strongpassword') for that user it is then hashed. Could you please help me solve this problem. Thank you.
It looks like you created a user in a way that does not use your manager's create_user method, for example through the Django admin.
If you create a custom user, you need to define a custom model form and model admin that handles the password properly.
Otherwise, passwords will not hashed when a user is created through the Django admin.
The example in docs for creating a custom users shows how to create the model form and model admin.
I know it's too late now, but I'll just post this for future reference.
If you're creating a new user by calling the save function on its serializer, you'll need to override the create function of the serializer as shown below, (which is pretty obvious, but I got stuck on it for a little bit....)
class SignUpView(views.APIView):
authentication_classes = ()
permission_classes = (permissions.AllowAny,)
def post(self, request, format=None):
serializer = UserSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
min_length=6, write_only=True, required=True)
class Meta:
model = User
fields = (
'id', 'email', 'password', 'is_staff',
'is_active', 'date_joined')
def create(self, validated_data):
return User.objects.create_user(**validated_data)
Late answer but anyway, you need to make Custom User Model form too with explicit hashing.
Else just make form inheriting UserCreationForm like:
from .models import MyUser
from django.contrib.auth.forms import UserCreationForm
class UserForm(UserCreationForm):
class Meta:
model = User
fields = ['email']
Add this in your UserSerialzer.
Basically you have to override the create method in order to hash the password.
def create(self,validated_data):
user = User.objects.create(email = validated_data['email'])
user.set_password(validated_data['password'])
user.save()
return user
Related
I'm having trouble getting authentication to work and I found that the problem is my when I create a user their password is hashed in a different style than PBKDF2. Instead the passwords are always in a format like this: !iak7ijJTzsXRgsbwqtQBtXCZeU3Ccd96k2PpOCRa .
However, when I'm working in my views make_password and check_password are using PBKDF2.
Model:
class UserManager(BaseUserManager):
def create_user(self, email, password):
if not email:
raise ValueError("User must have an email address")
user = self.model(
email = self.normalize_email(email),
)
user.set_password(password)
user.save()
return user
def create_super_user(self, email, password=None):
if not email:
raise ValueError("User must have an email address")
user = self.model(
email = self.normalize_email(email),
)
user.is_admin = True
user.save()
return user
class User(AbstractBaseUser):
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
email=models.EmailField(
verbose_name='Email',
max_length=255,
unique=True
)
password=models.CharField(
max_length=255,
verbose_name='password'
)
username = None
first_name = None
last_name = None
is_active = models.BooleanField(default=True)
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
def __str__(self):
return self.email
I suspect the issue is coming from my custom Model, but it's identical to one I wrote for another app that had no issues.
Register View:
class CreateUser(APIView):
serializer_class = CreateUserSerializer
def post(self, request, format='json'):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
email = serializer.data.get('email')
password = serializer.data.get('password')
userObj = User.objects.create_user(email=email, password=password)
userObj.save()
return Response(status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Login View:
class LogIn(APIView):
serializer_class = LogInSerializer
authentication_classes = [SessionAuthentication, BasicAuthentication]
def post(self, request, format='json'):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
email = serializer.data.get('email')
password = serializer.data.get('password')
tObj = User.objects.get(email=email)
hashed_pwd = make_password("123456")
print(hashed_pwd)
print(tObj.password)
print(tObj.check_password(password))
user = authenticate(email=email, password=password)
print(user)
if user is not None:
The ! at the start of the password value has a special meaning in Django - it means that an unusable password has been set for the user - the rest of the value is just a random string that will never be successfully validated.
So the question is why is an unusable password being set? There are two possibilities I can see from your code:
UserManager.create_super_user doesn't set the user's password at all - if you are using this to create users, then no password will be set for them.
If you're using the CreateUserSerializer, then it may be that the value of password is None - we would need to see the serializer definition to confirm whether a null value would be considered valid. I think this is the most likely issue. Passing None to create_user will cause set_password to set an unusable password. You then need to investigate why an empty value is being passed to the serializer.
The problem was what solarissmoke proposed with the CreateUserSerializer. I had my password set to write only which wasn't letting my view to get to password, instead it was returning None.
I changed my view from this:
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'password')
extra_kwargs = {
'password' : {'write_only': True}
}
To this (corrected version):
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'password')
In my case, I need to have one user group which email and password will not be set on user creation. They will not require to auth themselves either.
I managed to do custom user with a nullable email, but I cannot find a way to allow blank password in API call or Django admin forms. I do not really care much about forms, but I need to have it working via API.
How could I allow to create a new custom user with a blank password and maybe set it to something meaningful if it comes blank (like set_unusable_password())?
Thanks!
My CustomUserManager:
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
# if password == "":
# user.set_password(None)
# else:
user.set_password(password)
user.save()
return user
def create_superuser(...
You can base your CustomUser on AbstractUser and use set_password
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
...
Thanks to that, you have access to the function set_password. Any user based on CustomUser will inherit :
def set_password(self, raw_password):
self.password = make_password(raw_password)
self._password = raw_password
And if you look closely make_password you will realize it possible to give it None :
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
You could check the full example on Django to have a starting point with a Manager
Your model will look like :
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = MyUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['date_of_birth']
UPDATE after you upload your Manager :
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
def create_superuser(...
You forgot the password=None in your create_user. That is why you have this error required.
This helped me:
how to mention password field in serializer?
I have declared password field in user serializer and set required=False
class CustomUserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
write_only=True,
required=False,
)
class Meta:
model = CustomUser
fields = ("id", "email", "password")
def create(self, validated_data):
validated_data['password'] = make_password(validated_data.get('password'))
user = CustomUser.objects.create_user(**validated_data)
Token.objects.create(user=user)
return user
I am trying to register users in django auth module through an API call but the users are getting registered without the password being hashed which, I suspect, is making my authentication fail. Registering the users through the admin form is hashing the password and therefore working.
I developed my own User model by extending AbstractBaseUser and also created a UserManager extending BaseUserManager and defining the create_user and create_superuser method. I developed a simple serializer for it.
I read somewhere that the password can only be hashed if I developed the Admin form as well and so I did it. In this form, I followed django documentation and developed clean_password and save functions. I also registered these forms on the app admin.py.
Lastly, I created the APIView to the POST requests where I send the registration json and use the serializer do validate and save.
model
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('The given email must be set')
user = self.model(
email=self.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, **extra_fields):
user = self.create_user(email,
password=password,
**extra_fields)
user.is_admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
email = models.EmailField(max_length=40, unique=True)
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
photo_path = models.CharField(max_length=30, blank=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
def save(self, *args, **kwargs):
super(User, self).save(*args, **kwargs)
return self
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
#property
def is_staff(self):
return self.is_admin
serializer
class UserSerializer(serializers.ModelSerializer):
class Meta(object):
model = User
fields = ('id', 'email', 'first_name', 'last_name', 'password')
extra_kwargs = {'password': {'write_only': True}}
forms
class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('email', 'photo_path')
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
class Meta:
model = User
fields = ('email', 'photo_path', 'password')
def clean_password(self):
return self.initial["password"]
admin.py
class UserAdmin(BaseUserAdmin):
form = UserChangeForm
add_form = UserCreationForm
list_display = ('email', 'first_name', 'is_staff')
list_filter = ('is_admin',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('first_name',)}),
('Permissions', {'fields': ('is_admin',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2')}
),
)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ()
admin.site.register(User, UserAdmin)
view post
class CreateUserAPIView(APIView):
permission_classes = (AllowAny,)
def post(self, request):
user = request.data
serializer = UserSerializer(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
I expected to get a user in the DB with a hashed password, like when I create a user in the admin panel. but I get a user created with a plain text password.
What I would do, is the following in your serializer. Notice the set_password. That way you make sure it is hashed
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = models.User
fields = ('username', 'password', 'email')
def create(self, validated_data):
user = super(UserSerializer, self).create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
If you are using md5 for hashing then you can use the hashlib module and hash the password before saving in create_superuser
form hashlib import md5
def create_superuser(self, email, password, **extra_fields):
user = self.create_user(email,password=md5(password),**extra_fields)
user.is_admin = True
user.save(using=self._db)
return user
Sorry for the quick auto-response but I found out that the view post code was actually not executing my model create_user code. I don't know what was connecting the .save() method of the serializer to the authentication system but it was still creating the users. I will leave this question open so someone can maybe explain what was happening. To make it work i did the following changes:
class CreateUserAPIView(APIView):
permission_classes = (AllowAny,)
def post(self, request):
user = User.objects.create_user(request.data['email'], request.data['password']);
return Response(user, status=status.HTTP_201_CREATED)
I'm making a simple django rest framework project.
This is just creating a new user, and logging in.
When I used django basic auth user model, everything worked well.
But after changing basic user model to custom user, this error comes out when creating a new user:
dict object has no attribute 'pk'
Custom user model is made referred to django docs.
Error says that:
File "/home/seokchan/server/mdocker/lib/python3.5/site-packages/django/contrib/auth/__init__.py",
line 100, in login
if _get_user_session_key(request) != user.pk or ( AttributeError: 'dict' object has no attribute 'pk'
This seems to say that user model has no pk, but I don't get it.
models.py
class MyUserManager(BaseUserManager):
def create_user(self, username, email, password=None):
if not email:
raise ValueError('Users must have an email address')
if not username:
raise ValueError('Users must have an user name')
user = self.model(
email=self.normalize_email(email),
username = username
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password):
user = self.create_user(
username,
password=password,
email = email,
)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser):
id = models.AutoField(primary_key=True)
username = models.CharField(
verbose_name='user name',
max_length=30,
unique=True,
)
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
sth_test = models.TextField(blank = True)
objects = MyUserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
def __str__(self):
return self.username
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
#property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
serializers.py
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model
fields = ('id', 'username', 'email', 'password', 'is_active')
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
username = serializers.CharField(
max_length=32,
validators=[UniqueValidator(queryset=User.objects.all())]
)
password = serializers.CharField(min_length=8, write_only=True)
def validate_email(self,value):
if User.objects.filter(email=value).exists():
raise serializers.ValidationError("err.")
return value
def create(self, validated_data):
user = User.objects.create_user(
validated_data['username'],
validated_data['email'],
validated_data['password'],
)
user.is_active = False
user.save()
message=render_to_string('accounts/account_activate_email.html',{
'user':user,
'domain':'localhost:8000/api/accounts/activate',
'uid':urlsafe_base64_encode(force_bytes(user.pk)).decode('utf-8'),
'token':account_activation_token.make_token(user)
})
mail_subject = 'Bplus'
to_email = user.email
AuthEmail = EmailMessage(mail_subject, message, to=[to_email])
AuthEmail.send()
return validated_data
views.py
class UserCreateAPI(generics.GenericAPIView):
serializer_class = CreateUserSerializer
def post(self, request, *args, **kwargs):
if len(request.data["username"]) < 4 or len(request.data["password"]) < 8:
body = {"message":"short field"}
return Response(body, status = 400)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
user_for_auth = User.objects.get(username=user['username'])
login(request, user)
return Response(
{
"user":UserSerializer(
user, context=self.get_serializer_context()
).data,
"token":AuthToken.objects.create(user_for_auth),
}
)
How can I fix this error?
Your serializer create method returns the validated data instead of the created object. Since that is a dict, that is what you end up passing to the login function.
You should have return user instead of return validated_data.
I am making a custom user model using the AbstractBaseUser and PermissionsMixin by following these two tutorials (tutorial-1 and tutorial-2).
This the model so far:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField('email address', unique=True, db_index=True)
username = models.CharField('username', unique=True, db_index=True)
joined = models.DateField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
def __unicode__(self):
return self.email
Now what I am confused about is that in tutorial-1, the author didn't made any custom manager for the custom User model. Instead he use forms for creating user.
class RegistrationForm(forms.ModelForm):
email = forms.EmailField(label = 'Email')
password1 = forms.CharField(widget = forms.PasswordInput(), label = "Password")
password2 = forms.CharField(widget = forms.PasswordInput(), label = 'Retype password')
class Meta:
model = User
fields = ['email', 'username', 'password1', 'password2']
def clean(self):
"""
Verify that the values entered into the password fields match
"""
cleaned_data = super(RegistrationForm, self).clean()
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise ValidationError("Password don't match.")
return self.cleaned_data
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
But in tutorial-2, its author made a custom manager for the custom User model.
class UserManager(BaseUserManager):
def create_user(self, email, password, **kwargs):
user = self.model(
email=self.normalize_email(email),
is_active=True,
**kwargs
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, **kwargs):
user = self.model(
email=email,
is_staff=True,
is_superuser=True,
is_active=True,
**kwargs
)
user.set_password(password)
user.save(using=self._db)
return user
Referencing with Django Docs, there's an example of custom user model, and it uses custom manager. My question is, whether it is ok not to make any other custom manager and if not what is the use of creating a custom manager?
I think this is the relevant section from the docs for you:
You should also define a custom manager for your User model. If your User model defines username, email, is_staff, is_active, is_superuser, last_login, and date_joined fields the same as Django’s default User, you can just install Django’s UserManager; however, if your User model defines different fields, you will need to define a custom manager that extends BaseUserManager providing [the create_user() and create_superuser() methods].
The example in the docs needs to define a custom manager, so that it can set the date_of_birth field.
It appears that the example in the tutorial requires a custom manager, because it uses the email as the unique identifier, and does not have a separate username field.