Flask forms, custom validation errors/functions - python

I have a login() function in my form, I would like to return errors on specific fields in it, my questions is, how I could do something like that, this is my current function:
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
def validate_email(self, email):
user = models.User.query.filter_by(email=email.data).first()
if user is None:
raise ValidationError('Such user not found')
def login(self, user):
if check_password_hash(user.password, self.password.data):
flash('Logged in successfully!', category='success')
login_user(user, remember=self.remember.data)
return True
else:
raise LoginException('Wrong Password')
Should I just maybe flash the errors or should I remake it into a seperate validate password function? If there is a way I could for example return the LoginException so it gets displayed under my password field, I would be happy to know.

I think I have found a solution for you. Is that what you want?
def valid_credentials(form, field):
user = User.query.filter_by(email=form.email.data).first()
return user and user.verify_password(form.password.data)
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
def validate_email(self, field):
if not valid_credentials(self, field):
raise ValidationError('Invalid user credentials.')
def validate_password(self, field):
if not valid_credentials(self, field):
raise ValidationError('Invalid user credentials.')
class User(db.Model):
email = db.Column(db.String,
nullable=False, unique=True, index=True)
password_hash = db.Column(db.String,
nullable=False, unique=False, index=False)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
You can also move valid_credentials (form, field) into the form and/or only pass one validation function to the validators list.
Make sure that you do not tell the user which form field contains the error and that the user may be able to guess the access data more easily.

Related

Custom Flask Validation Error not triggering getting Unique constraint error instead

I have created a registration form with validation but instead of this error Unique Constraint error is coming how can I fix this
Below is the register class
class Registerform(FlaskForm):
username = StringField(label="Username", validators=[
Length(min=3, max=20), DataRequired()])
password1 = PasswordField(label="Password", validators=[
Length(min=6, max=20), DataRequired()])
password2 = PasswordField(label="Confirm Password", validators=[
EqualTo('password1', message='Both password fields must be equal!'), DataRequired()])
Submit = SubmitField(label='Submit')
def validate_existing_user(self, username):
checkexisting = User.query.filter_by(username=username.data).first()
print(checkexisting)
if checkexisting:
raise ValidationError(
"Oops Username already exists please try with a new Username")
Below is the registration route
#app.route("/register", methods=['POST', 'GET'])
def registerpage():
form = Registerform()
if form.validate_on_submit():
usernameinput = form.username.data
userpasswordinput = form.password1.data
hashedpass = bcrypt.generate_password_hash(userpasswordinput)
print(f"Normal pass is {userpasswordinput} & its hash is {hashedpass}")
table_row = User(username=usernameinput,
password_hash=hashedpass, )
db.session.add(table_row)
db.session.commit()
print("User added successfully")
flash('User added successfully you can Login now',
category='success')
return redirect(url_for('login_page'))
return render_template("registration.html", title='Registration', form=form)
Below is the DB model its sqllite
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
def __repr__(self):
return '<Username %s> <pwd %s>' % (self.username, self.password_hash)
Kindly assist I am new to flask
The Documentation explains how to build a custom validator. The key is to name the validation methode like the attribute of your form.
So instead of using validate_existing_user() you have to name the methode validate_username. That way wtforms will know that it hast to map the custom-validation methode to the username attribute.

How to split data from StringField

I have registration form where users can put their first name, last name, email and password.
I'm trying to get that email and split it to get the domain and check my database if that email already registered, but i'm not able to split the data, no matter what I change I keep getting different errors.
forms.py
class RegistrationForm(FlaskForm):
first_name = StringField(
"First Name", validators=[DataRequired(), Length(min=2, max=20)]
)
last_name = StringField(
"Last Name", validators=[DataRequired(), Length(min=2, max=20)]
)
email = StringField("Email", validators=[DataRequired(), Email()])
password = PasswordField("Password", validators=[DataRequired()])
confirm_password = PasswordField(
"Confirm Password", validators=[DataRequired(), EqualTo("password")]
)
submit = SubmitField("Sign Up")
# Here i'm trying to split the email so I can use the domain.
domain = email.split("#")[1]
def validate_email(self, email, domain):
user_email = User.query.filter_by(email=email.data).first()
org_domain = Organization.query.filter_by(domain=domain.data).first()
if user_email:
raise ValidationError("That email is taken. Please choose a different one.")
elif org_domain:
raise ValidationError(
"Your domain is managed by someone else, please contact your administrator. If you need help please contact our support"
)
routes.py
#users.route("/register", methods=["GET", "POST"])
def register():
if current_user.is_authenticated:
return redirect(url_for("main.dashboard"))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode(
"utf-8"
)
organization = Organization(
domain=form.email.data.split("#")[1],
)
db.session.add(organization)
db.session.commit()
admin = User(
first_name=form.first_name.data,
last_name=form.last_name.data,
display_name=form.first_name.data + " " + form.last_name.data,
email=form.email.data,
password=hashed_password,
org_id=organization.id,
)
db.session.add(admin)
db.session.commit()
flash("Your account has been created! You are now able to log in", "success")
return redirect(url_for("users.login"))
return render_template("users/register.html", title="Register", form=form)
I got it to work by splitting it under def validate_email(self, email):
domain = str(email).split("#")[1]
domain = domain.split('"')[0]

how to get data of another field in wtform validate_?

I am trying to do password validation on wtform like this:
email = StringField('Email', validators=[DataRequired(), Email()])
def validate_password(self, password):
print(self)
user = dbSession.execute(
"SELECT * FROM users WHERE email = :email",
{"email": self.email.data}
).fetchone()
print(user)
if not bcrypt.check_password_hash(user.password, password.data):
raise ValidationError('Incorrect Password.')
i want to get the email from other field, but i guess it's not working, i tried email.data but it isn't defined. also, it's not logging in console. in js, you log an object like this. I wanted to see the self's properties and the user, i want to log like this in python.
console.log('user =', user);
help?
This answer reformulates this little snippet
Sometimes you are in the situation where you need to validate a form with custom logic that can not necessarily be reduced to a validator on a single field. A good example are login forms where you have to make sure a user exists in the database and has a specific password.
class LoginForm(Form):
email = TextField('Email', [validators.InputRequired(), Email()])
password = PasswordField('Password', [validators.InputRequired()])
def __init__(self, *args, **kwargs):
Form.__init__(self, *args, **kwargs)
self.user = None
def validate(self):
rv = Form.validate(self)
if not rv:
return False
user = User.query.filter_by(
email=self.email.data).first()
if user is None:
self.email.errors.append('Unknown email')
return False
if not bcrypt.check_password_hash(user.password, self.password.data):
self.password.errors.append('Invalid Password')
return False
self.user = user
return True

Not hashing password in Flask

I don't know why the password is not hashing using Bcrypt. I think I am doing it right. I initilized Bcrypt correctly and I am using mongoengine. Everytime I look at the database it still shows the unencrypyed passwrod in text.
users/models.py
from app import db, bcrypt
class User(db.Document):
username = db.StringField(required=True)
first_name = db.StringField(required=True)
last_name = db.StringField(required=True)
email = db.EmailField(required=True)
password = db.StringField(required=True)
meta = {'collection': 'users'}
#property
def hash_password(self):
return self.password
#hash_password.setter
def set_hash_password(self, password):
self.password = bcrypt.generate_password_hash(password)
def verify_password(self, password):
return bcrypt.check_password_hash(self.password, password)
users/views.py
#userV.route('/signup', methods=['GET', 'POST'])
def signup():
form = SignUpForm()
if form.validate_on_submit():
user = User(
first_name=form.first_name.data,
last_name=form.last_name.data,
username=form.username.data,
email=form.email.data,
password=form.password.data
).save()
flash('You can now login')
return render_template('user.html', variable="You can now login " + user.username)
return render_template('signup.html', form=form)
users/auth/forms.py
class SignUpForm(Form):
username = StringField('Username', validators=[
InputRequired(message="Username is required"),
Regexp('^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]$', 0, 'Usernames must have only letters, numbers or underscores')
])
first_name = StringField('First Name', validators=[
InputRequired(message="First name is required")
])
last_name = StringField('Last Name', validators=[
InputRequired(message="Last name is required")
])
email = StringField('Email Address', validators=[
InputRequired(message="Email is required"),
Email(message="This is not a valid email")
])
password = PasswordField('Password', validators=[
InputRequired(message="Password is required"),
Length(min=6, message="The password is not long enough")
])
accept_tos = BooleanField('Accept Terms of Service', validators=[
InputRequired(message="You have to accept the Terms of Service in order to use this site")
])
submit = SubmitField('Signup')
def validate(self):
if not Form.validate(self):
return False
if User.objects(username=self.username.data).first():
raise ValidationError('Username already in use')
if User.objects(email=self.email.data).first():
raise ValidationError('Email already registered')
return True
This is the outcome when I search mongodb shell. The password is not hashed.
{ "_id" : ObjectId("555df97deddd5543c360888a"), "username" : "FullMetal", "first_name" : "Edward", "last_name" : "Elric", "email" : "fullmetalalchemist#gmail.com", "password" : "equalexchange" }
The property is called hash_password not password. I don't see where hash_password is getting assigned (that's when its setter gets called). Also your setter method should have exactly the same name as the property itself, in this case hash_password not (set_hash_password). You can then do
user = User(hash_password=form.password.data)
Unfortunately, due to the way mongoengine.Document.__init__ works, you wont be able to use your field this way. You have two options to make it work:
Option 1: First create the User object without the password, then set the hash_password, then save
user = User(first_name=form.first_name.data,
last_name=form.last_name.data,
username=form.username.data,
email=form.email.data)
user.hash_password = form.password.data
user.save()
Option 2: Requires overriding the __init__ method for User
class User(db.Document):
def __init__(self, *args, **kwargs):
if 'hash_password' in kwargs:
self.hash_password = kwargs.pop('hash_password')
super(User, self).__init__(*args, **kwargs)
Now you can use User as you initially wanted:
user = User(first_name=form.first_name.data, hash_password=form.password.data)
Python #property decorator doesn't work with old-style classes. I made this demo - note the class inheriting from object, which makes it a new-style class. Have a look and modify this to suit your need
class User(object):
def __init__(self, username, first_name, last_name, email, password):
print "Initializing"
self.username = username
self.first_name = first_name
self.last_name = last_name
self.email = email
self.password = password
#property
def password(self):
print "getting password"
return self._password
#password.setter
def password(self, password):
print "Setting password"
self._password = bcrypt.generate_password_hash(password)
def verify_password(self, password):
return bcrypt.check_password_hash(self.password, password)
As I mentioned earlier, if all else fails, I'd solve this by performing the logic in my view. That's what I'd have done in the first place, tbh. Python favors expressiveness.
I have omitted the other parts
user = User(password=bcrypt.generate_password_hash(form.password.data))
And just removing the #property setter and getter in the User class.
Here is what I do in the same circumstances:
class User(db.Document):
...
password = db.StringField(max_length=255, required=True)
...
def set_password(self, password):
self.password = bcrypt.generate_password_hash(password)
...
Then, in views.py I do the following:
user = User(..)
user.set_password(form.password.data)
user.save()
This way your logic stays within your model, but can be called easily from outside.

How can I create custom form for User model Django

I want to create my own form for user createion of django.contrib.auth.models.User in Django, but I cant find a good example. Can someone help me?
you want to create a form?
create a form say forms.py
from django.contrib.auth.models import User
from django import forms
class CreateUserForm(forms.Form):
required_css_class = 'required'
username = forms.RegexField(regex=r'^[\w.#+-]+$',
max_length=30,
label="Username",
error_messages={'invalid': "This value may contain only letters, numbers and #/./+/-/_ characters."})
email = forms.EmailField(label="E-mail")
password1 = forms.CharField(widget=forms.PasswordInput,
label="Password")
password2 = forms.CharField(widget=forms.PasswordInput,
label="Password (again)")
def clean_username(self):
existing = User.objects.filter(username__iexact=self.cleaned_data['username'])
if existing.exists():
raise forms.ValidationError("A user with that username already exists.")
else:
return self.cleaned_data['username']
def clean_email(self):
#if you want unique email address. else delete this function
if User.objects.filter(email__iexact=self.cleaned_data['email']):
raise forms.ValidationError("This email address is already in use. Please supply a different email address.")
return self.cleaned_data['email']
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("The two password fields didn't match.")
return self.cleaned_data
create a view say views.py
def create_inactive_user(request):
if request.method=='POST':
frm=CreateUserForm(request.POST)
if frm.is_valid():
username, email, password = frm.cleaned_data['username'], frm.cleaned_data['email'], frm.cleaned_data['password1']
new_user = User.objects.create_user(username, email, password)
new_user.is_active = True # if you want to set active
new_user.save()
else:
frm=CreateUserForm()
return render(request,'<templatepath>',{'form'=frm})
it is better to use django-registration

Categories

Resources