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]
Related
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.
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.
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.
I have a form that allows the user to change their email address. The form also prompts the user to enter their current password as part of the form.
The form does change the email address, but the user can enter any value for the password and the email address is changed.
For some reason, the password is not being checked and confirmed before the email is changed.
I cannot figure out what I have done.
Here is my form code:
class EmailChangeForm(forms.Form):
error_messages = {
'email_mismatch': _("The two e-mail address fields do not match."),
'email_inuse': _("This e-mail address cannot be used. Please select a different e-mail address."),
'password_incorrect': _("Incorrect password."),
}
current_password = forms.CharField(
label=_("Current Password"),
widget=forms.PasswordInput,
required=True
)
new_email1 = forms.EmailField(
label=_("New E-mail Address"),
max_length=254,
required=True
)
new_email2 = forms.EmailField(
label=_("Confirm New E-mail Address"),
max_length=254,
required=True
)
def __init__(self, user, *args, **kwargs):
self.user = user
super(EmailChangeForm, self).__init__(*args, **kwargs)
def clean_current_password(self):
"""
Validates that the password field is correct.
"""
current_password = self.cleaned_data["current_password"]
if not self.user.check_password(current_password):
raise forms.ValidationError(self.error_messages['password_incorrect'], code='password_incorrect',)
return current_password
def clean_new_email1(self):
"""
Prevents an e-mail address that is already registered from being registered by a different user.
"""
email1 = self.cleaned_data.get('new_email1')
if User.objects.filter(email=email1).count() > 0:
raise forms.ValidationError(self.error_messages['email_inuse'], code='email_inuse',)
return email1
def clean_new_email2(self):
"""
Validates that the confirm e-mail address's match.
"""
email1 = self.cleaned_data.get('new_email1')
email2 = self.cleaned_data.get('new_email2')
if email1 and email2:
if email1 != email2:
raise forms.ValidationError(self.error_messages['email_mismatch'], code='email_mismatch',)
return email2
def save(self, commit=True):
self.user.email = self.cleaned_data['new_email1']
if commit:
self.user.save()
return self.user
Here is my views.py code:
#login_required
def email_change(request):
language_versions = get_language_versions(user=request.user)
if request.method == 'GET':
form = EmailChangeForm(user=request.user)
elif request.method == 'POST':
form = EmailChangeForm(user=request.user, data=request.POST)
if form.is_valid():
form.save()
messages.success(request, _('successfully updated.'))
return redirect('email_change')
return render(request, 'user_settings/email_change.html', {
'display_default_language': display_default_language(request.user),
'form': form,
'languages': LANGUAGES,
'language_versions': language_versions,
'language_versions_num': len(language_versions),
})
Your clean_password() method should be called clean_current_password().
I have a form that I created with WTForms (using the Flask-WTF extension) and am using a SelectMultipleField to generate two groups of dynamic checkboxes. I'm having an issue where if I check any of the boxes from the Orientation group the form does not validate, but also does not give me any validation errors. I'm also not using any validators on that field. If I submit it without checking anything from the Orientation group, it will submit just fine. If I select any of the checkboxes from the Subreddit group, which is built almost identically, the form submits just fine.
Here is my form class:
class RegistrationForm(Form):
email = StringField('Email', validators=[Required(), Length(1,64),
Email()])
username = StringField('Username', validators=[
Required(), Length(1, 64), Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0,
'Usernames must have only letters, '
'numbers, dots or underscores')])
password = PasswordField('Password', validators=[
Required(), EqualTo('password2', message='Passwords must match.')])
password2 = PasswordField('Confirm password', validators=[Required()])
sex = SelectField('Sex', choices=[('M', 'Male'), ('F', 'Female'), ('T', 'Transgendered')], validators=[Required()])
min_age = IntegerField('Minimum Age', validators=[Required()])
max_age = IntegerField('Maximum Age', validators=[Required()])
city = StringField('City', validators=[Required()])
state = SelectField('State', validators=[Required()])
location_alias = StringField('Location Aliases')
location_radius = IntegerField('Radius of matches (in miles)',
validators=[Required(), NumberRange(min=0,
max=100,
message="Radius must be between 0-100 miles.")])
orientation = SelectMultipleField('Orientation (posts you want to see)',
option_widget=widgets.CheckboxInput(),
widget=widgets.ListWidget(prefix_label=False))
subreddits = SelectMultipleField('Subreddits',
option_widget=widgets.CheckboxInput(),
widget=widgets.ListWidget(prefix_label=False))
def validate_min_age(self, field):
if field.data < 18:
raise ValidationError("Minimum age must be at least 18.")
def validate_email(self, field):
if User.query.filter_by(email=field.data).first():
raise ValidationError("We already have a user with this email address.")
def validate_username(self, field):
if User.query.filter_by(username=field.data).first():
raise ValidationError("We already have a user with this username.")
# Data for dynamic checkboxes needs to be initialized when the form is used
def __init__(self, *args, **kwargs):
Form.__init__(self, *args, **kwargs)
self.state.choices = [(s.state, s.state) for s in State.query.all()]
self.orientation.choices = [(o.orientation, o.orientation) for o in Orientation.query.all()]
self.subreddits.choices = [(s.subreddit, s.subreddit) for s in Subreddit.query.all()]
And here is my view that loads this form and calls validate_on_submit():
#auth.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
# get or create the location object
l = Location.query.filter_by(city=form.city.data, state=form.state.data).first()
if not l:
l = Location(city=form.city.data, state=form.state.data)
db.session.add(l)
db.session.commit()
# add the locaton alias
for location_alias in form.location_alias.data.split('*'):
if location_alias:
add_location_alias(l, location_alias)
# get the orientations
orientations = []
for o in form.orientation.data:
orientation = Orientation.query.filter_by(orientation=o).first()
if orientation:
orientations.append(orientation)
# add the user
u = User(
email=form.email.data,
username=form.username.data,
password=form.password.data,
sex=form.sex.data,
min_age=form.min_age.data,
max_age=form.max_age.data,
location_id=l.id,
location_radius=form.location_radius.data,
orientations=orientations,
subreddits=[]
)
db.session.add(u)
db.session.commit()
print 'User has been added successfully.'
return redirect(url_for('main.index'))
print 'Form failed validation.'
return render_template('auth/register.html', form=form)