I am writing a login page using wtforms .The fields are username and password.Below are the two validations defined for two fields.Is there a way to stop the execution of validation function on password field if validation method on username raises exception
def validate_username(self, field):
# Check if not None for that username!
print('Inside check_username')
if User.query.filter_by(username=field.data).first() is None:
raise ValidationError(f'Sorry username : {field.data} is not registered!')
def validate_password(self, field):
# Below is not the actual code.This method will contain code to check of valid password.
raise ValidationError(f'Sorry username : {field.data} is not registered!')
What is happening right now is both of the above methods are been called which is expected but I don't want to check for password if the username doesn't exist
You cannot stop wtforms for trying to validate each field.
I suggest you simply override validate:
def validate(self):
if not super().validate():
return False
if User.query.filter_by(username=self.username.data).first() is None:
self.errors["username"] = f'Sorry username : {self.username.data} is not registered!'
return False
if not check_that_password():
self.errors["password"] = f"Bad password for user {self.username.data}"
return False
return True
Related
I have some Form with two urlfields, both not required. The form is used to set the value of JSONField in a Model from these two urlfields (for user convenience), everything works fine. If a user enters something except an URL into url1 or url2, django shows validation error at the form "Enter a valid URL".
Now I want to make a user to input URL in ANY of these urlfields. I'm overriding clean method for that:
class MyForm(forms.ModelForm):
url1 = forms.URLField(required=False)
url2 = forms.URLField(required=False)
def clean(self):
cleaned_data = super(MyForm, self).clean()
if not cleaned_data['url1'] and not cleaned_data['url2']:
raise ValidationError(
_("You should enter at least one URL"),
code='no_urls'
)
return cleaned_data
It works, BUT there is a problem: if user enters some "non-URL" data into url1 or url2 and submits the form, Django raises KeyError with Exception Value: 'url1' (or 'url2') instead of showing a validation error at the form
What's wrong? Thanks!
As documented in quite a few places - notably the part about cross validation -, cleaned_data only contains valid data - the fields that didn't validate wont show up here. You have to account for this one way or another - by testing for key existence or, as shown in the cross-validation example snippet, using dict.get():
def clean(self):
cleaned_data = super(MyForm, self).clean()
# boolean algebra 101: "not A and not B" => "not (A or B)"
if not (cleaned_data.get('url1') or cleaned_data.get('url2')):
raise ValidationError(
_("You should enter at least one URL"),
code='no_urls'
)
return cleaned_data
I'm trying to write a serializer (in Django REST Framework) to update a user's account details. Here is the update() method:
def update(self, instance, validated_data):
...
if all([item in self.validated_data for item in ["password", "confirm_password", "old_password"]]):
user = authenticate(username=self.context["request"].user.username, password=self.validated_data["old_password"])
if user is not None:
if self.validated_data["password"] == self.validated_data["confirm_password"]:
validate_password(self.validated_data["password"])
user.set_password(self.validated_data["password"])
user.save()
else:
raise serializers.ValidationError({"confirm_password": "Passwords do not match"})
else:
raise serializers.ValidationError({"old_password": "Password incorrect"})
self.validated_data.pop("password")
return super(UserInfoSerializer, self).update(instance, validated_data)
When I perform a PATCH request to the view with "password", "confirm_password" and "old_password" as fields, it appears to have worked. Then when I try to log into the account again, it fails (using both old and new passwords). When I check the admin settings and view the user I am trying to edit, I get the following:
Invalid password format or unknown hashing algorithm.
Raw passwords are not stored, so there is no way to see this user's
password, but you can change the password using this form.
I believe User.set_password() is supposed to handle hashing/etc. automatically, so why do I get this error?
You deleted password from self.validated_data but not from validated_data dict which passed to superclass's update method. Try this:
validated_data.pop("password") # remove self, just leave validated_data
return super(UserInfoSerializer, self).update(instance, validated_data)
In case anyone is interested in using my code, here is the final working code:
def update(self, instance, validated_data):
...
if all([item in validated_data for item in ["password", "confirm_password", "old_password"]]):
user = authenticate(username=instance.username, password=validated_data["old_password"])
if user is not None and user == instance:
if validated_data["password"] == validated_data["confirm_password"]:
validate_password(validated_data["password"])
instance.set_password(validated_data["password"])
instance.save() # change the password on the current instance object, otherwise changes will be overwritten
login(self.context["request"], instance) # without this line, the user is auto-logged out upon changing their password
else:
raise serializers.ValidationError({"confirm_password": "Passwords do not match"})
else:
raise serializers.ValidationError({"old_password": "Password incorrect"})
if "password" in validated_data:
validated_data.pop("password")
return super(UserInfoSerializer, self).update(instance, validated_data)
I am trying to make one form for both inserting and updating data. I have read these:
Model validation on update in django
django exclude self from queryset for validation
In my project, however, I am not using ModelForm.
forms.py:
This is the form the user sees when registering his/her username and first_name. It is also the form an existing user sees when trying to change his/her username and/or first_name.
from django import forms
from .models import User
class SettingsForm(forms.Form):
username = forms.CharField(max_length=16)
first_name = forms.CharField(max_length=32)
# ... and many more form fields
def clean_slug(self):
"""Make sure that the username entered by the user will be unique in the database"""
username = self.cleaned_data['username']
try:
product = User.objects.get(username=username)
except User.DoesNotExist:
# Good, there is no one using this username
pass
else:
# There is alreaady a user with this username
raise forms.ValidationError('This username has been used. Try another.')
return username
The form cleaning works as intended for inserting data. However, when updating data, it complains that the username has been used (naturally, since the username already exists in the database).
How do I update the data without raising ValidationError when using a Form (and without using ModelForm)?
(The reasons for not using ModelForm in this case are: we may stop using the the orm, SettingsForm may contain a mix of fields from different models, some fields may be repeated hundreds of times in the form that is displayed to the user, we also need custom fields not tied to any model, ... and other scenarios that make using ModelForm quite challenging (impossible?). The purpose of this question is to find out if there are ways of achieving the desired result without using ModelForm.)
You have three cases:
The user is new
The user exists and doesn't change his/her username
The user exists and changes his/her username
You need to check if the username already exists only in the first two cases.
If it's an existing user you should pass the User instance to the form so you can use it in the clean_slug function, let's say in self.user variable.
Then you could just add two lines in the clean_slug function and it should work as you wish:
def clean_slug(self):
"""Make sure that the username entered by the user will be unique in the database"""
username = self.cleaned_data['username']
# If the user exists and the username has not been changed,
# just return the username
if self.user and self.user.username == username:
return username
try:
product = User.objects.get(username=username)
except User.DoesNotExist:
# Good, there is no one using this username
pass
else:
# There is alreaady a user with this username
raise forms.ValidationError('This username has been used. Try another.')
return username
The ValidationError is obviously because you're instantiating the SettingsForm when the username already exists, as you've already stated.
So if you want to add a form that can do double-duty, I would add an __init__ to SettingsForm that takes an is_update and saves it as a member variable, like so...
def __init__(self, is_update=False, **kwargs):
self.is_update = is_update
return super(SettingsForm, self).__init__(**kwargs)
then modify your clean_slug() to be:
def clean_slug(self):
username = self.cleaned_data['username']
try:
product = User.objects.get(username=username)
except User.DoesNotExist:
# Good, there is no one using this username
pass
else:
if not self.is_update: # for 'create' usage
# There is already a user with this username
raise forms.ValidationError('This username has been used. Try another.')
else: # for 'update' usage
pass
return username
You actually want your form to do two different things depending if it is a create or an update.
So either have two forms with a different clean_slug method or pass in an argument telling the form if it is an update or a create (there is another answer from neomanic showing this way).
Personally I think the easiest way would be to subclass your form and change the clean slug method. The use the new form for updates and your original form for creates.
class UpdateSettingsForm(settingsForm):
def clean_slug(self):
username = self.cleaned_data['username']
return username
So I created a custom form field to validate for duplicate usernames. I'm using Django + Mongoengine as my database. I have that plugged and working with the django authentication system so I'm assuming it can be accessed from forms.py? Maybe that assumption is incorrect. So I have the field
class UsernameField(CharField):
def to_python(self, value):
if not value:
return ""
return value
def validate(self, value):
super(CharField, self).validate(value)
try:
# If the user object exists it won't throw an Error/Exception
user=User.objects.get(username=value)
raise ValidationError("Username already exists")
except:
pass
But when I actually use it in my form, it always seems to validate correctly even though I've called checked if form.is_valid() is True
You're raising exceptions in the try block but then snuffing them out in the except block with pass. Try this, it will check for the existing user and only fails if it exists.
try:
# If the user object doesn't exist, it validates
user=User.objects.get(username=value)
except django.core.exceptions.DoesNotExist:
pass
else:
#user does exist, barf.
raise ValidationError("Username already exists")
Bah, it was a dumb mistake on my part. For some reason I forgot that the Error I was trying to raise would be caught by the try and I would get sent to the except route. Changing it to this works
class UsernameField(CharField):
def to_python(self, value):
if not value:
return ""
return value
def validate(self, value):
super(CharField, self).validate(value)
usernameDuplicate = False
try:
# If the user object exists it won't throw an Error/Exception
user=User.objects.get(username=value)
usernameDuplicate = True
except:
pass
if usernameDuplicate==True:
raise ValidationError("Username already exists")
I have a Django form with a username and email field. I want to check the email isn't already in use by a user:
def clean_email(self):
email = self.cleaned_data["email"]
if User.objects.filter(email=email).count() != 0:
raise forms.ValidationError(_("Email not available."))
return email
This works, but raises some false negatives because the email might already be in the database for the user named in the form. I want to change to this:
def clean_email(self):
email = self.cleaned_data["email"]
username = self.cleaned_data["username"]
if User.objects.filter(email=email, username__ne=username).count() != 0:
raise forms.ValidationError(_("Email not available."))
return email
The Django docs say that all the validation for one field is done before moving onto the next field. If email is cleaned before username, then cleaned_data["username"] won't be available in clean_email. But the docs are unclear as to what order the fields are cleaned in. I declare username before email in the form, does that mean I'm safe in assuming that username is cleaned before email?
I could read the code, but I'm more interested in what the Django API is promising, and knowing that I'm safe even in future versions of Django.
Update
.keyOrder no longer works. I believe this should work instead:
from collections import OrderedDict
class MyForm(forms.ModelForm):
…
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
field_order = ['has_custom_name', 'name']
reordered_fields = OrderedDict()
for fld in field_order:
reordered_fields[fld] = self.fields[fld]
for fld, value in self.fields.items():
if fld not in reordered_fields:
reordered_fields[fld] = value
self.fields = reordered_fields
Previous Answer
There are things that can alter form order regardless of how you declare them in the form definition. One of them is if you're using a ModelForm, in which case unless you have both fields declared in fields under class Meta they are going to be in an unpredictable order.
Fortunately, there is a reliable solution.
You can control the field order in a form by setting self.fields.keyOrder.
Here's some sample code you can use:
class MyForm(forms.ModelForm):
has_custom_name = forms.BooleanField(label="Should it have a custom name?")
name = forms.CharField(required=False, label="Custom name")
class Meta:
model = Widget
fields = ['name', 'description', 'stretchiness', 'egginess']
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
ordered_fields = ['has_custom_name', 'name']
self.fields.keyOrder = ordered_fields + [k for k in self.fields.keys() if k not in ordered_fields]
def clean_name(self):
data = self.cleaned_data
if data.get('has_custom_name') and not data.get('name'):
raise forms.ValidationError("You must enter a custom name.")
return data.get('name')
With keyOrder set, has_custom_name will be validated (and therefore present in self.cleaned_data) before name is validated.
The Django docs claim that it's in order of the field definition.
But I've found that it doesn't always hold up to that promise.
Source: http://docs.djangoproject.com/en/dev/ref/forms/validation/
These methods are run in the order
given above, one field at a time. That
is, for each field in the form (in the
order they are declared in the form
definition), the Field.clean() method
(or its override) is run, then
clean_(). Finally, once
those two methods are run for every
field, the Form.clean() method, or its
override, is executed.
There's no promise that the fields are processed in any particular order. The official recommendation is that any validation that depends on more than one field should be done in the form's clean() method, rather than the field-specific clean_foo() methods.
The Form subclass’s clean() method. This method can perform any
validation that requires access to multiple fields from the form at
once. This is where you might put in things to check that if field A
is supplied, field B must contain a valid email address and the like.
The data that this method returns is the final cleaned_data attribute
for the form, so don’t forget to return the full list of cleaned data
if you override this method (by default, Form.clean() just returns
self.cleaned_data).
Copy-paste from https://docs.djangoproject.com/en/dev/ref/forms/validation/#using-validators
This means that if you want to check things like the value of the email and the parent_email are not the same you should do it inside that function. i.e:
from django import forms
from myapp.models import User
class UserForm(forms.ModelForm):
parent_email = forms.EmailField(required = True)
class Meta:
model = User
fields = ('email',)
def clean_email(self):
# Do whatever validation you want to apply to this field.
email = self.cleaned_data['email']
#... validate and raise a forms.ValidationError Exception if there is any error
return email
def clean_parent_email(self):
# Do the all the validations and operations that you want to apply to the
# the parent email. i.e: Check that the parent email has not been used
# by another user before.
parent_email = self.cleaned_data['parent_email']
if User.objects.filter(parent_email).count() > 0:
raise forms.ValidationError('Another user is already using this parent email')
return parent_email
def clean(self):
# Here I recommend to user self.cleaned_data.get(...) to get the values
# instead of self.cleaned_data[...] because if the clean_email, or
# clean_parent_email raise and Exception this value is not going to be
# inside the self.cleaned_data dictionary.
email = self.cleaned_data.get('email', '')
parent_email = self.cleaned_data.get('parent_email', '')
if email and parent_email and email == parent_email:
raise forms.ValidationError('Email and parent email can not be the same')
return self.cleaned_data