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.')
Related
I have a small question about Django so I have a custom Registration Form which inherits from "UserCreationForm" and therefore I use "fields" to define the fields I want to display. For this test just ("username", "email",). But when I go to see my form which is displayed "Password" and "Password confirmation" are also present. Wanting to understand, I therefore went to see the UserCreationForm class of django but this one has a Meta class which has fields = ("username"), so I don't really see why I have the passwords which are also present. I'm sorry the explanation is a bit laborious but I would like to understand so if anyone has a little explanation thank you very much.
My custom class:
class CustomSignupForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ("username", "email",)
The UserCreationForm of django:
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"),
strip=False,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
class Meta:
model = User
fields = ("username",)
field_classes = {"username": UsernameField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self._meta.model.USERNAME_FIELD in self.fields:
self.fields[self._meta.model.USERNAME_FIELD].widget.attrs[
"autofocus"
] = True
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError(
self.error_messages["password_mismatch"],
code="password_mismatch",
)
return password2
def _post_clean(self):
super()._post_clean()
# Validate the password after self.instance is updated with form data
# by super().
password = self.cleaned_data.get("password2")
if password:
try:
password_validation.validate_password(password, self.instance)
except ValidationError as error:
self.add_error("password2", error)
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
Here is what it gives:
Display of my form
The reason this happens is that the password1 and password2 field are defined in the UserCreationForm class. The form fields do not need to be listed in the fields attribute: all field objects in the class are form fields.
A ModelForm will in essence add form field objects to the class, and thus automates that process, but any field object defined in the class is a form field, that is how a simple Form [Django-doc] behaves, and a ModelForm is just a subclass of Form.
Based on your comment you want to reorder the fields. You can do so with the .field_order attribute [Django-doc]:
class CustomSignupForm(UserCreationForm):
field_order = ('username', 'email', 'password1', 'password2', 'newsletter')
class Meta:
model = CustomUser
fields = ('username', 'email', 'newsletter')
I have a model 'Admin' related to a (custom) user within a one to one field. To update all the fields I'm using django-extra-views app that basically use Django formset. It works well but for updating the password.
Models
class Users(AbstractUser):
# common fields for all the users
class Admins(models.Model):
# Custom fields for the admins
Forms:
class AdminProfileForm(forms.ModelForm):
class Meta:
model = Users
fields = ['first_name', 'last_name']
password1 = forms.CharField(widget=forms.PasswordInput, label='New Password')
password2 = forms.CharField(widget=forms.PasswordInput, label='Confirm Password')
Views:
class AdminInline(InlineFormSet):
model = Admins
fields = ['admins_fiels_here']
class AdminProfile(BaseContext, UpdateWithInlinesView):
....
inlines = [AdminInline]
form_class = AdminProfileForm
def get_object(self):
return Users.objects.get(pk=self.request.user.id)
def forms_valid(self, formset, inlines):
password1 = formset.cleaned_data['password1']
password2 = formset.cleaned_data['password2']
if password2 and password1:
if password1 != password2:
# raise a ValidationError message
user = self.request.user # at this point password is 'old_passord'
user.set_password(password1) # now is 'password1'
user.save() # 'password1' is correctly saved in db
return super(AdminProfile, self).forms_valid(formset, inlines)
As I describe above, the new password is saved but when super(AdminProfile, self).forms_valid(formset, inlines) ends its works the password results to be the old one.
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
class UpdateProfile(UpdateView):
model = User
fields = ['first_name', 'last_name', 'username', 'email']
template_name = 'account/update.html'
success_url = '/accounts/profile/'
def get_object(self, queryset=None):
return self.request.user
def clean_email(self):
email = self.cleaned_data.get('email')
username = self.cleaned_data.get('username')
if email and User.objects.filter(email=email).exclude(username=username).count() > 0:
raise forms.ValidationError(u'This email address is already registered.')
return email
I have the following code above. How do I use UpdateView to check if the email field is unique. If the user added an email that is the same as someone else, I want to add that to form.errors array and prevent the form from saving. What should I do? I tried everything.
In your models.py
UserModel(models.Model):
...
email = models.EmailField(unique=True)
...
This will ensure that you don't have to write any logic on your view to ensure uniqueness of a field.
So in my models I have a specfic profile that has a one to one relationship with a more general profile that has a one to one relationship with the django user model. I want to be able to fill out one form in the django admin and create instances of all three models, with relationships already set up.
I haven't messed around with the django admin too much, so I'm not entirely sure how to make it work. Here is my failed attempt:
class CreateSpecializedProfileAdminForm(forms.ModelForm):
class Meta:
exclude = ['profile']
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
email = forms.EmailField(max_length=30)
password = forms.CharField(max_length=30)
confirm_password = forms.CharField(max_length=30)
def clean(self):
password = self.cleaned_data['password']
confirm_password = self.cleaned_data['confirm_password']
if len(self.cleaned_data['password']) < 6:
raise forms.ValidationError('Password must be at least 6 characters.')
if password != confirm_password:
raise forms.ValidationError('Passwords must match.')
return super(CreateSpecializedProfileAdminForm, self).clean()
def save(self, commit=True):
from django.contrib.auth.models import User
first = self.cleaned_data['first_name']
last = self.cleaned_data['last_name']
email = self.cleaned_data['email']
password = self.cleaned_data['password']
user = User.objects.create_user(email, email, password)
user.first_name = first
user.last_name = last
user.save()
profile = UserProfile()
profile.user_auth = user
profile.save()
specialized_profile = SpecializedProfile()
specialized_profile.profile = profile
specialized_profile.save()
return specialized_profile
class SpecializedProfileAdmin(admin.ModelAdmin):
form = CreateSpecializedProfileAdminForm
admin.site.register(SpecializedProfile, SpecializedProfileAdmin)
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form
remove class Meta and move exclude = ['profile'] to ModelAdmin
class SpecializedProfileAdmin(admin.ModelAdmin):
exclude = ['profile']
form = CreateSpecializedProfileAdminForm