Forms in Django Rest Framework - django

I'm able to create ModelSeralizers easily, but I am having a little trouble with my Forms. Is there a way to serialize the form below, or do I need to do each form in the native language if I take this to a mobile device?
class SetPasswordForm(forms.Form):
password1 = forms.CharField(label='New password',
widget=forms.PasswordInput(
attrs={'placeholder': 'New password'}))
password2 = forms.CharField(label='Verify new password',
widget=forms.PasswordInput(
attrs={'placeholder': 'Password again'}))
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(SetPasswordForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password_length = settings.MIN_PASSWORD_LENGTH
password1 = self.cleaned_data.get("password1")
if len(password1) < password_length:
raise forms.ValidationError(
"Password must be longer than "
"{} characters".format(password_length))
password2 = self.cleaned_data.get("password2")
if password1 and password2:
if password1 != password2:
raise forms.ValidationError("Passwords do not match")
return password2

You can create a serializer named PasswordSerializer which performs the same checking and validation as you are doing above in SetPasswordForm.
We create a serializer having 2 fields password1 and password2.
password1 field has min_length argument passed to it which validates that the input contains no fewer than this number of characters. Also, we define the custom error message for the case in which input is less than min_length in an error_messages dictionary. Doing this removes the validations you were performing earlier in your form and now DRF will handle that for you. Also, the serializer fields have allow_null set to False. So, if None value is sent, DRF automatically handles that.
We need to create a validate() function which checks if the passwords match or not. If the 2 passwords do not match, serializer will raise a ValidationError.
The above code transformed to a DRF serializer will be something like:
from rest_framework import serializers
class PasswordSerializer(serializers.Serializer):
password1 = serializers.CharField(min_length=settings.MIN_PASSWORD_LENGTH, error_messages={'min_length': "Password must be longer than {} characters".format(settings.MIN_PASSWORD_LENGTH)})
password2 = serializers.CharField()
def validate(self, data):
if data['password1'] != data['password2']: # Check if the 2 passwords match
raise serializers.ValidationError("Passwords do not match")
return data

Related

Django modelform clean_password2

Stumbled this def clean_password2 ModelForm.
My question is does every time this we run this view. Does it will automatically run clean_password2 to check the password or do we need to explicitly call it?
Form.py
class RegisterForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('full_name', 'email',) #'full_name',)
def clean_password2(self):
# Check that the two password entries match
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):
# Save the provided password in hashed format
user = super(RegisterForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
user.is_active = False # send confirmation email via signals
# obj = EmailActivation.objects.create(user=user)
# obj.send_activation_email()
if commit:
user.save()
return user
https://docs.djangoproject.com/en/4.0/topics/auth/customizing/#a-full-example
No, you don't have to call it explicitly as it says in the doc.
The clean_<fieldname>() method is called on a form subclass – where <fieldname> is replaced with the name of the form field attribute. This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is. This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
doc: https://docs.djangoproject.com/en/4.0/ref/forms/validation/

why does clean_password2() method work but not clean_password1() in Django usercreationform

I am trying to figure out why this works if someone could maybe explain it to me.
I've just created a custom user model (shown below) and for the password validation it uses the clean_password2(self): (shown below) method however when I try to use clean_password1(self): (shown below) the validation does not work. why? Surely using either password1 or password2 to clean the data would work since they are the same?
Django docs state that we can use clean_<fieldname>(): methods to clean/validate data and since password1 is a fieldname in my mind that should work.
Custom user model
class UserManager(BaseUserManager):
def create_user(self, email, password=None):
if not email:
raise ValueError("Users must have an email address")
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=None):
user = self.create_user(email=email, password=password)
user.is_staff = True
user.is_admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
email = models.EmailField(verbose_name="Email Address", max_length=255, unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
This works
class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(
label="Password",
help_text=password_validation.password_validators_help_text_html(),
widget=forms.PasswordInput,
)
password2 = forms.CharField(
label="Confirm Password",
help_text="Enter the same password as before for validation",
widget=forms.PasswordInput,
)
class Meta:
model = User
fields = ["email"]
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
This doesn't
def clean_password1(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password1
Summary: don't reference other fields in clean_<fieldname> methods. Do this logic in the clean method instead. https://docs.djangoproject.com/en/stable/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
The why goes deep into Django forms. The method that calls clean_<fieldname> is the following:
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
In _clean_fields, each field is resolved in the order they appear on the form, and this is the first time cleaned_data is filled in for that field. Because password1 comes before password2 in the fields list, when clean_password1 is run, cleaned_data["password2"] has not yet been set. Here is what happens to your code (I added comments):
def clean_password1(self):
password1 = self.cleaned_data.get("password1")
# `"password2"` is not present in cleaned_data, so `password2` is set to None
password2 = self.cleaned_data.get("password2")
# With `password2 == None`, this condition resolves to false
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
# `password1` is returned without any validation error
return password1
The reason clean_password2 works is because password2 comes later in the field list.

How to add Choice Field to Django forms for user registration?

I am making a registration form for 3 types of users. When a user enters email and password he/she must select one of the roles.
First I used BooleanFields and it works, but more than one checkbox can be selected. I need that user can select only one role.
I have tried ChoiceField, which I could display on the template but it does not POST any data to db.
forms.py
class RegisterForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
parent = forms.BooleanField(label="I am a Parent")
school = forms.BooleanField(label="I am a School Admin")
vendor = forms.BooleanField(label="I am a Vendor")
role_select=forms.ChoiceField(
widget=forms.RadioSelect,
label="Select your role.",
choices=(('is_parent','parent '),('is_school','school'),('is_vendor','vendor')),
)
if not parent and not school and not vendor:
raise forms.ValidationError("Users must have a role")
class Meta:
model = User
fields = ['role_select', 'parent', 'school', 'vendor', 'email'] #'full_name',)
def clean_password2(self):
# Check that the two password entries match
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):
# Save the provided password in hashed format
user = super(RegisterForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
user.role_select( self.cleaned_data['role_select'])
# user.active = False # send confirmation email
if commit:
user.save()
return user
As you see in the forms.py I have a combination of two approaches. So it has some useless lines. Which approach to use and how?
you can use Django groups where you can create a group and assign it as a choicefield when user registers. You can check my repository. I have done the same thing here. Hope it can get you some idea
https://github.com/tsephel/User-authentication-django-/tree/master/env

How to check password against previously used passwords in django

I have the following model for storing previously used hashed passwords:
class PasswordHistory(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
password = models.CharField(max_length=128, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now = True)
In the change password form I want check if the new password the user is changing to has not been used the past 5 times.
Here is my form validation:
class ProfileForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput(), required=False)
password2 = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = Employee
user_id = None
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id', None)
super(ProfileForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 != password2:
raise forms.ValidationError('Passwords do not match.')
user = User.objects.get(pk=self.user_id)
hashed_password = make_password(password1)
password_histories = PasswordHistory.objects.filter(
user=user,
password_hashed_password
)
if password_histories.exists():
raise forms.ValidationError('That password has already been used')
return password2
The problem is that the passwords are different every time, even when I attempt the same plain text password over and over again. Therefore:
if password_histories.exists():
Never returns true.
How can I compare past passwords if they are always different due to salt?
Thanks
The .set_password function indeed does not return anything, it simply sets the password. Like you say however, the hashing is based on a (random) salt, and thus the hash will be different each time. Therefore you should use the .check_password(…) function [Django-doc], to verify if it somehow matches a hashed variant:
from django.contrib.auth.hashers import check_password
class ProfileForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput(), required=False)
password2 = forms.CharField(widget=forms.PasswordInput(), required=False)
class Meta:
model = Employee
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id', None)
super(ProfileForm, self).__init__(*args, **kwargs)
def clean_password2(self):
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 != password2:
raise forms.ValidationError('Passwords do not match.')
user = User.objects.get(pk=self.user_id)
password_histories = PasswordHistory.objects.filter(
user=user
)
for pw in password_histories:
if check_password(password2, pw.password):
raise forms.ValidationError('That password has already been used')
return password2
So if we found a hashed password that matches the given raw password, we can return the password. If by the end of the for loop, we did not find any such password, we can return password2, otherwise we raise an errro.

How I can validate fields on forms on identity fields in form. Django

How I can validate fields on forms on identity fields in form instantly without POST request?
my fields in model:
password = models.CharField(max_length=45, verbose_name="")
password2 = models.CharField(max_length=45, verbose_name="")
and my fields in form:
'password': widgets.PasswordInput(attrs={'placeholder': 'New Password'}),
'password2': widgets.PasswordInput(attrs={'placeholder': 'Re-Entere Password'}),
This is the code I tried:
def clean_password(self):
password1 = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password2')
if not password2:
raise forms.ValidationError("You must confirm your password")
if password1 != password2:
raise forms.ValidationError("Your passwords do not match")
return password2
Code in view:
def saves_data_user_on_registration (request):
if request.method == 'POST':
c = {}
c.update(csrf(request))
form_user_data = Form_registration(request.POST, request.FILES)
if form_user_data.is_valid():
print form_user_data.errors
form_user_data.save()
return render_to_response('see_you_later.html', c, context_instance=RequestContext(request))
else:
print form_user_data.errors
return render_to_response('error.html', c, context_instance=RequestContext(request))
I you want to access two fields during validation you need to use clean() not clean_myfield().
Check the docs.
What do you mean with "without a POST request?".
I would never accept passwords via GET. HTTP GET requests get stored in proxies and caches!