django registration and login with custom user model - django

Custom User Model (models.py):
class Users(AbstractBaseUser):
user_id = models.AutoField(primary_key=True)
user_email = models.EmailField(max_length=100, unique=True)
password = models.CharField(max_length=100)
registration_date = models.DateField(auto_now_add=True)
last_login = models.DateField(auto_now_add=True)
is_administrator = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
email_verified = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'user_email'
Custom User Manager (models.py)
class UserManager(BaseUserManager):
def create_user(self, user_email, password):
user = self.model(
email=self.normalize_email(user_email)
)
user.set_password(password)
user.save(using=self._db)
return user
Registration Form (forms.py):
class RegistrationForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput)
password_confirm = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = get_user_model()
fields = ('user_email',)
def clean_password(self):
password1 = self.cleaned_data.get("password")
password2 = self.cleaned_data.get("password_confirm")
if password1 and password2 and password1 != password2:
raise forms.ValidationError( self.error_messages['password_mismatch'],
code='password_mismatch' )
return password2
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.registration_date = datetime.date.today()
user.last_login = datetime.date.today()
user.set_password("password")
if commit:
user.save()
return user
Register view (views.py)
def register(request):
if request.method == "POST":
form = RegistrationForm(request.POST)
if form.is_valid:
form.save()
else:
form = RegistrationForm()
return render(request, 'web/page.html', {'form' : form})
This is my first question on .. anything online (I'm an avid user of the search facility), but I've been struggling with this for days and I feel like it shouldn't be so difficult.
For my question, it is more like a two part query.
1) I want a custom user model, but want to use as much of what Django is offering. But getting rid of the username field and adding a couple other fields seems to require me to make "custom" everything else. Including a custom user manager and custom forms - not ideal! The registration form above works but I was hoping for something a bit more simple, along the lines of:
class RegistrationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = get_user_model()
fields = ('user_email', 'password1', 'password2')
but I seem to be struggling, running it gives me the error:
The Users could not be created because the data didn't validate.
Ideally I would want something simple like that without the error. I'm hoping it to give fewer errors and hopefully more future proof if Django adds new security features.
2) Why tf won't the login code below work?
Login Form (forms.py):
class LoginForm(forms.Form):
user_email = forms.EmailField(max_length=100)
password = forms.CharField(max_length=100)
Login View (views.py):
def login_user(request):
if request.method == "POST":
user_email = request.POST.get('email')
password = request.POST.get('password')
UserModel = get_user_model()
user = UserModel.objects.get(user_email=user_email)
if user.check_password(password):
login(request, user)
return render(request, 'login.html')
I'm pretty sure the problem lies with the second if statement. It just won't give a True value when the correct password is entered. Having tried print(user.user_id), the correct id is given for the email and password entered. Having played with some plaintext passwords, it works, but I rather like the set_password and check_password functions - provided they work!
Many thanks!

I worked it out. Both naive errors!
For point two, the Login View (views.py) code works well, the problem was the Registration Form (forms.py). In particular:
user.set_password("password")
was literally saving 'password' as the password. Changed to:
user.set_password(self.cleaned_data["password_confirm"])
made it work.
Not that it matters (for point one), because the 4 line Registration Form inheriting the UserCreationForm worked all along and will replace the first one. Turns out the issue was I wasn't putting a valid password (minimum 8 letters). Why it validates the data in views.py during:
if form.is_valid:
but then tells me off later I have no idea. I wish Django would be a bit more detailed on what the issue was!

I would recommend that you look into Django all-auth. It's an open source solution to user registration and management that offers a lot of customization, and works well with custom user models.
https://github.com/pennersr/django-allauth

Related

Django is hashing my passwords different when creating a User vs trying to authenticate a user

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')

How to resolve the error: TypeError at /signup save() missing 1 required positional argument: 'self'

I'm trying to register users using AbstractUser but I'm getting an error. Although I'm getting error, users still get registered but I can't handle login with the registered users.
The error goes thus:
The error goes thus:
The post request
My views.py goes thus:
def signup(request):
if request.method == 'POST':
first_name = request.POST['first-name']
last_name = request.POST['last-name']
username = request.POST['username']
email = request.POST['email']
phone_no = request.POST['phone-no']
password = request.POST['password']
password2 = request.POST['password2']
if password==password2:
if CustomUser.objects.filter(username=username).exists():
messages.info(request, 'Username Taken')
return redirect('signup')
elif CustomUser.objects.filter(email=email).exists():
messages.info(request, 'Email Already Exist')
return redirect('signup')
else:
CustomUser.objects.create_user(first_name=first_name, last_name=last_name, username=username, email=email, phone_no=phone_no, password=password)
CustomUser.save(commit=False)
return redirect('login')
else:
messages.info(request, 'Passwords Not Matching')
return redirect('signup')
else:
return render(request, 'signup.html')
And my models.py:
class CustomUser(AbstractUser):
id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
username = models.CharField(max_length=100, unique=True)
email = models.EmailField(unique=True)
password = models.CharField(max_length=150)
phone_no = models.CharField(max_length=20)
is_end_user = models.BooleanField(default=True)
is_smart_earner = models.BooleanField(default=False)
is_top_user = models.BooleanField(default=False)
Remove the CustomUser.save(commit=False). It makes no sense, the record is created with the .create_user(…) method [Django-doc].
You can only call the .save(…) method [Django-doc] on model objects, since these represent a record in the corresponding table. Using it on the CustomUser class makes not much sense, since it represents a table, and table itself is not altered when creating a record.
Furthermore your .save() call uses a parameter commit=…, but that does not make much sense either: the .save(…) of a ModelForm can accept a commit=… parameter, not the one for a Model.
I would however advise to work with a ModelForm. Such ModelForm can automatically validate unique fields, and save the created user. Django already has a basic ModelForm for this: the UserCreationForm [Django-doc]. With some small changes, it can likely work for your custom user model.

Explain built in User model and UserCreationForm

I understand that django comes with a User model built in and the following code works and properly saves a user to the db, I just don't understand why it works:
from django.shortcuts import render
from django.http import HttpResponse
from .forms import UserCreationForm
def index(request):
if request.method == "POST":
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
return HttpResponse('Saved')
else:
form = UserCreationForm()
return render(request, 'core/index.html', {'form':form})
what is the line form = UserCreationForm(request.POST) saying? is the (request.POST) the contents of the submitted form? and how does user = form.save() save a user to the database? I was thinking that maybe the variable had to be named user for django to recognize it as a User object but this is not the case as I changed the statement to test = form.save()
and it still saved a User to my database.
The line form = UserCreationForm(request.POST) talks of itself actually. It posts data passed through your form to the variable form
The 'request' object is an HttpRequest object that carries metadata about the request (for example in our case, we have a post request , and the request object carries the user data). Check this for more details.
Yes, user = form.save() saves a User instance in the database. UserCreationForm has a function called save() and here we're just calling that function.
The name of the variable has nothing to do with it because in the UserCreationForm's definition, the model is already defined as User. So, it already recognizes that it's the User model.
I this you should read the UserCreationForm code to make things clear in your head.
This is django doc :
[docs]class UserCreationForm(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and
password.
"""
error_messages = {
'password_mismatch': _("The two password fields didn't match."),
}
password1 = forms.CharField(label=_("Password"),
widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"),
widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification."))
class Meta:
model = User
fields = ("username",)
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(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
You can see the "save" method, it use user model with super. Result UserCreationForm use User model and can save in User database

ModelForm not finding field when submitting

I currently have this ModelForm for validating a new user registration:
class RegistrationForm(forms.ModelForm):
email = forms.CharField(max_length=75, required=True)
password = forms.PasswordInput()
password_confirm = forms.PasswordInput()
class Meta:
model = User
fields = ['username', 'email', 'password']
def clean(self):
if self.password != self.password_confirm:
self.add_error('password_confirm', 'Passwords do not match.')
The user is required to confirm his password. When submitting this form I get the following error:
ValueError at /register
'RegistrationForm' has no field named 'password_confirm'.
I tried using the self.cleaned_data as well, but still get the same error.
The fields attribute cannot be removed nor can password_confirm be added to it.
How would one go about fixing this?
password and password_confirm are defined as widgets, not form fields.
Define two CharFields and pass the widget argument:
class RegistrationForm(forms.ModelForm):
email = forms.CharField(max_length=75, required=True)
password = forms.CharField(widget=forms.PasswordInput)
password_confirm = forms.CharField(widget=forms.PasswordInput)
You need to call the super clean first, and then you should use the cleaned data rather than the field.
def clean(self):
cleaned_data = super(RegistrationForm, self).clean()
if cleaned_data.get('password') != cleaned_data.get('password_confirm'):
self.add_error('password_confirm', 'Passwords do not match.')

Extending UserCreationForm to include email, first name, and last name

I've been stuck on this for a while now and can't seem to figure out what's going on. I'm just starting to learn Django and I got my login set up and now want to implement a registration page.
I used the UserCreationForm form at first and that worked fine, but I want to add fields for Email, First name, and Last name. I figured I could just subclass UserCreationForm and add the fields but that doesn't seem to work. Also I tried overriding the save method, but it still doesn't work.
My custom form looks like this:
class RegistrationForm(UserCreationForm):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
email = forms.EmailField(max_length=75)
class Meta:
model = User
fields = ("first_name", "last_name", "email",)
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.first_name = self.cleaned_data["first_name"]
user.last_name = self.cleaned_data["last_name"]
user.email = self.cleaned_data["email"]
if commit:
user.save()
return user
The view to handle this is the following:
def Register(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
new_user = form.save();
new_user = authenticate(username=request.POST['username'], password=request.POST['password1'])
login(request, new_user)
return HttpResponseRedirect('/members/home')
else:
form = RegistrationForm()
return render_to_response('register.html', {'form' : form}, context_instance=RequestContext(request))
The form loads just fine with the new fields and everything, but when I submit I get the error:
AttributeError at /register/
'AnonymousUser' object has no attribute 'backend'
Oh, and also I'm using Django 1.3.
Any idea what I'm doing wrong?
My guess is that your meta class fields doesn't include username.
You are inheriting the form field from UserCreationForm but it's not saving to the User model and therefore authenticate is failing, and crashing on login()
The docs suggest you check whether authenticate() is successful before using login()