I ran today into a special situation. Previously I had the following in my view.py
def register_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.cleaned_data['username'],
password=form.cleaned_data['password2'],
email=form.cleaned_data['email']
)
return HttpResponseRedirect('/register/success/')
else:
form = RegistrationForm()
variables = RequestContext(request, {'form':form})
return render_to_response('registration/register.html', variables)
It was pretty straight forward retrieving the username, email and password to create a new user after she has registered. But now I have refactored it to use a hash code as the username and utilize the email alone to register and login.
The shortened RegistrationForm looks like this:
class RegistrationForm(forms.ModelForm):
email = forms.EmailField(label=_("Email"))
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput))
class Meta:
model = User
fields = ("email",)
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
email = self.cleaned_data['email']
user.username = md5(email).digest().encode('base64')[:-1]
if commit:
user.save()
return user
The new form doesn't have the username any longer, since it is calculated and not entered by the user any more. But how do I retrieve the username from the view ? The new code is not from me and I have it from a blog. Maybe the key is here in the Meta class? From the documentation I wasn't able to fully understood what he is trying to achieve with the Meta class here...
Many Thanks,
EDIT:
Ok I think I understand now how the subclassing should work. I tried to subclass the User class like this:
class cb_user_model_backend(ModelBackend):
def create_user(self, email=None, password=None):
"""
Creates and saves a User with the given email and password only.
"""
now = timezone.now()
username = md5(email).digest().encode('base64')[:-1]
email = UserManager.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False,
last_login=now, date_joined=now)
user.set_password(password)
user.save(using=self._db)
return user
The problem I am facing now are two errors, self._db and self.model, were meant to be on the base user class. How do get to them from here?
Edit 2:
PyCharm complains that the two self._db and seld.model don't exit on current cb_user_model_backend.
Note the View is refactored to take two parameters:
user = User.objects.create_user(
password=form.cleaned_data['password2'],
email=form.cleaned_data['email']
)
When running it stack trace is:
Exception Type: TypeError
Exception Value:
create_user() takes at least 2 arguments (3 given)
Try subclassing your save method in your models.py:
def save(self, *args, **kwargs):
if not self.id:
self.username = md5(self.email).digest().encode('base64')[:-1]
super(ModelName, self).save(*args, **kwargs)
After calling user.save(), user.username should yield the generated username in your views. Hope this helps.
EDIT:
If you want to call create_user(**kwargs), you could do the following in your views.py:
email = self.cleaned_data['email']
username = md5(email).digest().encode('base64')[:-1]
u = User.objects.create_user(username = username, email = email, password = password)
Related
In my django account app I want to check if inputed email exist in database (basic django db.sqlite3).
forms.py:
from django import forms
from django.contrib.auth.models import User
class UserRegistrationForm(forms.ModelForm):
password = forms.CharField(label='Hasło', widget=forms.PasswordInput)
password2 = forms.CharField(label='Powtórz hasło', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('username', 'first_name', 'email')
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError('Hasła nie są identyczne.')
return cd['password2']
views.py:
def register(request):
if request.method == "POST":
user_form = UserRegistrationForm(request.POST)
if user_form.is_valid():
# Creating new user object, without saving in database
new_user = user_form.save(commit=False)
# Setting new password
new_user.set_password(
user_form.cleaned_data['password'])
# Saving user object
new_user.save()
return render(request,
'account/register_done.html',
{'new_user': new_user})
else:
user_form = UserRegistrationForm()
return render(request,
'account/register.html',
{'user_form': user_form})
Now when i enter the same email for another user, form creates that user.
I think is it possible to make this in that way?
1). make email as variable like password and password2
2). remove email from meta
3). create method clean_email() with checking if email exist in db if not raise error
I don't know how to get to emails in db
Thanks for all help!
Below is_valid(): in your views.py do this
if user_form.is_valid():
new_user = user_form.save(commit=False)
email=user_form.cleaned_data['email']
if not User.objects.filter(email=email).exists():
//the rest of your code
else:
//some error message
This ensures that a new user is only created if they do not already exist.
If you working on an app that requires a username, email, and password length of x(in this case 8), then do this.
if not User.objects.filter(username=username).exists():
if not User.objects.filter(email=email).exists():
if len(password) < 8:
// some error message alert
return('register') // the same page
//continue with the rest of your code
.....
return ('login-page')
return('register') the same page
If this solves your issue please don't forget to accept this as the correct answer.
My aim - Trying to create an authentication system in Django, and allows user to signup again with same username if their account is not activated.
If an user try to register with a certain username and that username already exists then update that user with this current user.
My Approach - I have created a form using "UserCreationForm" and defining my own methods to validate the form, and allows the user even if username already exists but user.is_active = False.
Code
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import User
class SignupForm(UserCreationForm):
email = forms.EmailField(max_length=200, help_text='Required')
name = forms.CharField()
institution = forms.CharField()
def clean_username(self):
username = self.cleaned_data.get('username')
user = None
try:
try:
user = User.objects.get(username=username)
print("is user active username", user.is_active)
except ObjectDoesNotExist as e:
pass
except Exception as e:
raise e
if not user:
pass
elif not user.is_active:
pass
else:
raise forms.ValidationError("This Username Already Exists")
except Exception as e:
raise e
return username
class Meta:
model = User
fields = ('username', 'email', 'institution', 'password1', 'password2')
views.py
from .forms import SignupForm
def regUser(form):
'''
It will save Django user and student.
It recieves form.cleaned_data as argument
'''
print("reg user line 1")
user = User.objects.create_user(username=form['username'],
email=form['email'],
password=form['password1'],
is_active=False,
)
# notice is_active = False, becuase we will activate account if user will
# verify the email
user.save()
student = Student(user=user, name=form['name'], institution=form['institution'])
student.save()
return user
def signup(request):
if request.user.is_authenticated:
return redirect('accounts:home')
if request.method == 'POST':
form = SignupForm(request.POST)
if form.is_valid():
user = regUser(form.cleaned_data)
current_site = get_current_site(request)
message = render_to_string('accounts/acc_active_email.html', {
'user':user, 'domain':current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
mail_subject = 'Activate your account.'
to_email = form.cleaned_data.get('email')
email = EmailMessage(mail_subject, message, to=[to_email])
email.send()
return render(request, 'accounts/signup.html', {'email_sent': True})
else:
for field in form:
for error in field.errors:
messages.error(request, error)
return redirect("accounts:signup")
return render(request, 'accounts/signup.html')
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
subject = models.CharField(max_length=250, default="default_value")
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
name = models.CharField(max_length=250, default="default_value")
institution = models.CharField(max_length=250, default="default_value")
def save(self):
super().save()
def __str__(self):
return self.user.username
My problem - I am getting error " A user with that username already exists" I don't why and from where this error is coming when I am trying to created a new user with already registered username but it account is not activated yet (user.is_active = False).
As much I know clean_fields functions are called to validate the form when we use "form.is_valid()" in our views.py, But I have already overridden the "clean_username" according to my need so why I am getting this error ? and from where it is generated ? and how to solve this ?
Also I would like to mention when user is registered it is also registered "student" that's why I am using "regUser" function. Also I read some other questions similar to this on stackoverflow this and this some answer was that inherit the form from "forms.Form" not from "UserCreationForm" but why can anyone explain?
Please not mark this question as already answered I have gone through all questions and it was not helpful.
The User model you defined inherited the AbstactUser, and username is defined there.
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
...
The attribute unique for username field means that username must be unique among all users, not only active users.
(ref. https://docs.djangoproject.com/en/3.1/ref/models/fields/#unique)
If what you want is to allow users to have the same username among inactive users, set unique=False.
But, if 'inactive' means 'discarded' in your User model, I would recommend changing user's username to garbage value when the user is being inactivated.
When trying to authenticate a user created through a view in DRF Browsable API, I get
No active account found with the given credentials
The view:
class MyUserCreate(APIView):
def post(self, request, format='json'):
serializer = MyUserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The serializer:
class MyUserSerializer(serializers.ModelSerializer):
username = serializers.CharField(
required=True,
validators=[UniqueValidator(queryset=MyUser.objects.all())],
min_length=5,
max_length=20
),
password = serializers.CharField(
required=True,
max_length=256
)
class Meta:
model = MyUser
fields = ('username', 'password')
def create(self, validated_data):
password = make_password(validated_data['password'])
user = MyUser.objects.create_user(validated_data['username'], password)
return user
The password is being hashed. At this stage, went on to the admin page and tested to login there too with the created account and got
Made sure the custom user model had is_active, is_superuser and is_staff and checked if that would fix the issue but it didn't.
class MyUserManager(BaseUserManager):
def create_user(self, username, password, **extra_fields):
user = self.model(
username=username
)
user.is_staff=True
user.is_active=True
user.is_superuser=True
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(username, password, **extra_fields)
class MyUser(AbstractBaseUser):
objects = MyUserManager()
class Meta:
# managed = False
db_table = 'user_entity'
user_id = models.AutoField(primary_key=True, db_column='userId')
username = models.CharField(db_column='username', unique=True, max_length=20)
password = models.CharField(db_column='userPassword', max_length=256)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=True)
USERNAME_FIELD = 'username'
def __str__(self):
return str(self.user_id) + " (%s)" % str(self.username)
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
Then, tried to create the user using python manage.py createsuperuser... doing so then I'm able to both login in the admin pannel and authenticate that user with the Token Obtain Pair view shown initially.
First of all. Thank you for providing all the details. It is always easier to debug. Coming to the issue, the problem is that you are using make_password explicitly.
If you would look through the Django's set_password documentation, you'd find that it takes care of hashing.
What you are doing is, first you are hashing your password, and providing it as a raw value to set_password method via create_user. The set_password would assume that as a raw value and would hash it again. Hence, your original password is not used at all.
You can just remove the use of make_password and instead change your serializer's create method to
class MyUserSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
user = MyUser.objects.create_user(validated_data['username'], validated_data["password"])
return user
This should resolve your query. I hope it helps! :)
I have the following error:
AttributeError: 'User' object has no attribute 'set_password'
The problem is I didn't override the class User:
My model.py:
class User(models.Model):
username = models.CharField(max_length=30)
password = models.CharField(max_length=30)
email = models.EmailField()
def __str__(self):
return self.username
My view.py:
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
user = form.save(commit=False)
print type(user)
# Cleaning and normalizing data
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user.set_password(password)
user.save()
# returns User objects if the credential are correct
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect('website:home')
return render(request, self.template_name, {'form': form})
And this is my form.py:
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control',
'type': 'password',
'placeholder': 'Enter your password'}))
class Meta:
model = models.User
I don't really know also if I should override the User class. In which case I should and in which case I shouldn't?
You need to inherit from AbstractUser to get access to set_password attribute. Instead of using models.Model use:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
...
Your User model is not the same as django's User model.
Reference custom user model
from django.contrib.auth.hashers import make_password
replace
user.set_password(password) by user.password = make_password('password')
it clear and work for me.
The User model in Django has .set_password but if you made your own you should try OneToOneField(User) from there you just have to make sure you save both in the views.
user_form = UserForm(data=request.POST)
if user_form.is_valid():
user = user_form.save()
user.set_password(user.password)
profile = user.userprofile
profile.bio = request.POST['bio']
profile.save()
I made a custom registration form, that inherits from the UserCreationForm. However, when you try to submit, with one of the fields empty, i get a KeyError on required.
This seems to happen somewhere in the django source code, however I'm pretty sure it comes because of my custom clean method.
Form:
class RegistrationForm(UserCreationForm):
"""
edit the User Registration form to add an emailfield
"""
class Meta:
model = User
fields = ('username', 'password1', 'password2')
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
#add custom errormessages
self.fields['username'].error_messages = {
'invalid': 'Invalid username'
}
self.fields['password2'].label = "Confirm Password"
#make sure username is lowered and unique
def clean_username(self):
username = self.cleaned_data.get('username')
try:
User.objects.get(username__iexact=username)
raise forms.ValidationError("This username is already in use.")
except User.DoesNotExist:
pass
return username
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
if commit:
user.save()
return user
The errorlog http://pastebin.com/8Y6Tp7Rw
Note: I am using django 1.8
You are replacing all of the 'username' field error_messages dict with your own dict. Instead, you should update the error_messages dict with your custom message like so:
self.fields['username'].error_messages.update({
'invalid': 'Invalid username'
})
It looks like that you have changed the original error messages for the 'username' field (you are not adding, but overriding):
#add custom errormessages
self.fields['username'].error_messages = {
'invalid': 'Invalid username'
}
So when you leave the username empty, it failed to find the key 'required'.