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'.
Related
I found below in Django source code
class AbstractBaseUser(models.Model):
...
def get_username(self):
"""Return the username for this User."""
return getattr(self, self.USERNAME_FIELD)
...
I searched out the whole Django source code, but did not find out where the USERNAME_FIELD was defined.
Can anyone help on this please?
https://github.com/django/django/blob/main/django/contrib/auth/models.py#L377
USERNAME_FIELD was defined in AbstractUser
You can find USERNAME_FIELD in AuthenticationForm class of django which inherites forms.FORM class of Form Api.
It is also defined in models.py in AbstractUser class, refer Django GitHub Code.
It is given in constructor method that is __init__() of AuthenticationForm.
From Django original Code:
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
username = UsernameField(widget=forms.TextInput(attrs={'autofocus': True}))
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}),
)
error_messages = {
'invalid_login': _(
"Please enter a correct %(username)s and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def __init__(self, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
super().__init__(*args, **kwargs)
# Set the max length and label for the "username" field.
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
username_max_length = self.username_field.max_length or 254
self.fields['username'].max_length = username_max_length
self.fields['username'].widget.attrs['maxlength'] = username_max_length
if self.fields['username'].label is None:
self.fields['username'].label = capfirst(self.username_field.verbose_name)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username is not None and password:
self.user_cache = authenticate(self.request, username=username, password=password)
if self.user_cache is None:
raise self.get_invalid_login_error()
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to
allow login by active users, and reject login by inactive users.
If the given user cannot log in, this method should raise a
``ValidationError``.
If the given user may log in, this method should return None.
"""
if not user.is_active:
raise ValidationError(
self.error_messages['inactive'],
code='inactive',
)
def get_user(self):
return self.user_cache
def get_invalid_login_error(self):
return ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
Here focus on __init__() for get clearification about USERNAME_FIELD.
It is used for getting user field from User model or you can normally say for getting username.
You can find it in your local machine using below path:
C:\Users\Username\AppData\Local\Programs\Python\Python39\Lib\site-packages\django\contrib\admin\forms.py.
Remember: AppData folder will only appear if you select hidden items.
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.
Background context
I'm using django rest framework and django-rest-auth to build an API. My authentication scheme doesn't use the default authentication of just the email/username and password and requires other data present on the Profile model.
For this reason, I'm using a custom registration serializer and overriding the REGISTER_SERIALIZER class in the settings.
This is the serializer as follows:
class UserProfileSerializer(serializers.ModelSerializer):
phone_number = PhoneNumberField()
class Meta(object):
model = Profile
fields = ('phone_number', 'birth_date', 'anniversary', 'user_type',)
class UserRegisterSerializer(serializers.Serializer):
username = serializers.CharField(
max_length=get_username_max_length(),
min_length=allauth_settings.USERNAME_MIN_LENGTH,
required=allauth_settings.USERNAME_REQUIRED
)
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
password1 = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True)
profile = UserProfileSerializer()
..............
Validation methods
..............
def custom_signup(self, request, user):
pass
def get_cleaned_data(self):
return {
'username': self.validated_data.get('username', ''),
'password1': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', '')
}
def save(self, request):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
adapter.save_user(request, user, self)
self.custom_signup(request, user)
setup_user_email(request, user, [])
profile_data = self.validated_data.pop('profile', {})
user_repo.create_object(user=user, **profile_data)
return user
The problem
The serializer and the corresponding endpoint works in that it is creating the user and the associated profile, but in the serializing step when returning the response, I get the following error:
KeyError: "Got KeyError when attempting to get a value for field `profile` on serializer `UserRegisterSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `OrderedDict` instance.
Original exception text was: 'profile'."
As you can see, the field exists on the serializer. I tried adding the field to the Meta classe's field property but it didn't work.
I've narrowed it down to the function to_representation in rest_framework/serializers.py at line number 488. The error occurs when the serializer tries call get_attribute on on the nested serializer.
Thanks!
Edit : This is the view in library which uses this serializer
Edit-1 : Stack trace of the error
Below is the content of my forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=20, required=True)
last_name = forms.CharField(max_length=20, required=True)
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2', 'first_name', 'last_name')
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
#validation of email id----------
email1 = user.email
(fisrt, second,) = email1.split("#")
(domain, exn,) = second.split(".")
if not domain == "tmail":
raise forms.ValidationError("Domain must be 'tmail'")
if commit:
user.save()
return user
I am able to print form (including fields email, first_name & last_name) & register the user successfully only issue I have is, Its not performing the validation step. (code below the "#validation of email id----------")
Even if the domain is other than "tmail" it is accepting it without raising any validation error & then registering the user into database. Please help & let me know in case you need more info.
It is too late to validate the email in the save() method. Django lets you validate individual fields by defining a method clean_<fieldname>. This will be called when the form is validated.
def clean_email(self):
email = self.cleaned_data['email']
(first, second,) = email1.split("#")
(domain, exn,) = second.split(".")
if domain != "tmail":
raise forms.ValidationError("Domain must be 'tmail'")
return email
See the docs on cleaning a specific field attribute for more info.
Why are you doing validation in save? All validations must happen before save, such that if any exception does comes in save it is most probably bug in your code. Forms specifically have 'clean' methods especially for this purpose. For specific field validation, you can use "clean_{field_name}". Though if your validation depends on multiple fields, you need to use "clean" method.
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)