Flask Form Won't Write to SQLAlchemy DB - python

I have created ContactForm as a quick WTForm within my HTML templates. When I go through my application and try to use the contact form to add the name and email values in the ContactForm to my User class it doesn't work. When I query User class in DB I just get empty brackets []. Somebody, please help me!
class ContactForm(FlaskForm):
name = StringField('Name', [InputRequired()])
email = StringField('Email Address', [InputRequired()])
submit = SubmitField('Sign Up')
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=False, nullable=False)
email = db.Column(db.String, unique=False, nullable=False)
def __init__(self, name, email):
self.name = name
self.email = email
def __repr__(self, name, email):
return f"{name},{email}"
#app.route('/contact', methods=['GET'])
def contact():
form = ContactForm()
if form.validate_on_submit():
name = form.name.data
email = form.email.data
user = User(name, email)
db.session.add(user)
db.session.commit()
flash('Success')
return redirect(url_for('home'))
else:
return render_template('contact.html', form=form)
return render_template('contact.html', form=form)

Please try with methods POST in your route.

Related

Flask login is not working outside my initial flask Login route.?

I am utilizing a react frontend and a flask backend for my app. I am trying to utilize flask_login to authenticate and track user information throughout my various api calls. I have worked through the flask login docs and cannot for the life of me figure out my user information is not being remembered between route calls.
class User(db.Model, UserMixin):
__tablename__='users'
id= db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True)
email = db.Column(db.String(100), unique=True)
password = db.Column(db.String(50))
date_joined = db.Column(db.Date, default=datetime.utcnow)
# role = db.Column(db.String(50), defualt="user")
def __init__(self, username, email, password):
self.username = username
self.email = email
self.password = password
def to_json(self):
return {
'id' : self.id,
'username' : self.username,
'email' : self.email,
'date_joined' : self.date_joined,
}
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'Login'
login_manager.session_protection = "strong"
login_serializer = URLSafeTimedSerializer(app.secret_key)
#login_manager.user_loader
#cross_origin(supports_credentials=True)
def load_user(user_id):
print(user_id, file=sys.stderr) #This isn't printing anything. I am not sure this route is being hit
return User.query.get(user_id)
#app.route('/Login', methods=['GET', 'POST'])
#cross_origin(supports_credentials=True)
def Login():
if request.method == 'POST':
session.pop('user_id', None)
session['username'] = request.get_json()['username']
session['password'] = request.get_json()['password']
user = db.session.query(User).filter(User.username==session['username']).first()
if user and user.password == session['password']:
session['user_id'] = user.id
login_user(user, remember=True, force=True)
# print(current_user, file=sys.stderr) #This successfully prints out a user
return jsonify(current_user.id)
else:
return jsonify(-99999)
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
Now when I try to access my current_user information with a separate route call, I get nothing. When I test if current_user.is_authenticated:...it is not. I don't know what is happening here. Is the session getting reset? Am I logging out of the user after logging in? I have no idea.
#app.route('/AnswerQuestion/<int:question_id>', methods=['GET', 'POST'])
#cross_origin(supports_credentials=True)
def AnswerQuestion(question_id):
# question = Question.query.get(question_id)
print( current_user.get_id(), file=sys.stderr)
return jsonify(question.to_json())
returns

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.

Flask Webapp - Verify Email after Registration - Best Practice

I've been following along to Corey Schafer's awesome youtube tutorial on the basic flaskblog. In addition to Corey's code, I`d like to add a logic, where users have to verify their email-address before being able to login. I've figured to do this with the URLSafeTimedSerializer from itsdangerous, like suggested by prettyprinted here.
The whole token creation and verification process seems to work. Unfortunately due to my very fresh python knowledge in general, I can't figure out a clean way on my own how to get that saved into the sqlite3 db. In my models I've created a Boolean Column email_confirmed with default=False which I am intending to change to True after the verification process. My question is: how do I best identify the user (for whom to alter the email_confirmed Column) when he clicks on his custom url? Would it be a good practice to also save the token inside a db Column and then filter by that token to identify the user?
Here is some of the relevant code:
User Class in my modely.py
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default_profile.jpg')
password = db.Column(db.String(60), nullable=False)
date_registered = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
email_confirmed = db.Column(db.Boolean(), nullable=False, default=False)
email_confirm_date = db.Column(db.DateTime)
projects = db.relationship('Project', backref='author', lazy=True)
def get_mail_confirm_token(self, expires_sec=1800):
s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'], expires_sec)
return s.dumps(self.email, salt='email-confirm')
#staticmethod
def verify_mail_confirm_token(token):
s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
try:
return s.loads(token, salt='email-confirm', max_age=60)
except SignatureExpired:
return "PROBLEM"
Registration Logic in my routes (using a users blueprint):
#users.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('dash.dashboard'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data, email=form.email.data, password=hashed_password)
db.session.add(user)
db.session.commit()
send_mail_confirmation(user)
return redirect(url_for('users.welcome'))
return render_template('register.html', form=form)
#users.route('/welcome')
def welcome():
return render_template('welcome.html')
#users.route('/confirm_email/<token>')
def confirm_email(token):
user = User.verify_mail_confirm_token(token)
current_user.email_confirmed = True
current_user.email_confirm_date = datetime.utcnow
return user
The last parts current_user.email_confirmed = True and current_user.email_confirm_date =datetime.utcnow are probably the lines in question. Like stated above the desired entries aren't made because the user is not logged in at this stage, yet.
Im grateful for any help on this!
Thanks a lot in advance!
Thanks to #exhuma. Here is how I eventually got it to work - also in addition I'm posting the previously missing part of email-sending.
User Class in my models.py
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default="default_profile.jpg")
password = db.Column(db.String(60), nullable=False)
date_registered = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
email_confirmed = db.Column(db.Boolean(), nullable=False, default=False)
email_confirm_date = db.Column(db.DateTime)
projects = db.relationship("Project", backref="author", lazy=True)
def get_mail_confirm_token(self):
s = URLSafeTimedSerializer(
current_app.config["SECRET_KEY"], salt="email-comfirm"
)
return s.dumps(self.email, salt="email-confirm")
#staticmethod
def verify_mail_confirm_token(token):
try:
s = URLSafeTimedSerializer(
current_app.config["SECRET_KEY"], salt="email-confirm"
)
email = s.loads(token, salt="email-confirm", max_age=3600)
return email
except (SignatureExpired, BadSignature):
return None
Send Mail function in my utils.py
def send_mail_confirmation(user):
token = user.get_mail_confirm_token()
msg = Message(
"Please Confirm Your Email",
sender="noreply#demo.com",
recipients=[user.email],
)
msg.html = render_template("mail_welcome_confirm.html", token=token)
mail.send(msg)
Registration Logic in my routes.py (using a users blueprint):
#users.route("/register", methods=["GET", "POST"])
def register():
if current_user.is_authenticated:
return redirect(url_for("dash.dashboard"))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode(
"utf-8"
)
user = User(
username=form.username.data, email=form.email.data, password=hashed_password
)
db.session.add(user)
db.session.commit()
send_mail_confirmation(user)
return redirect(url_for("users.welcome"))
return render_template("register.html", form=form)
#users.route("/welcome")
def welcome():
return render_template("welcome.html")
#users.route("/confirm_email/<token>")
def confirm_email(token):
email = User.verify_mail_confirm_token(token)
if email:
user = db.session.query(User).filter(User.email == email).one_or_none()
user.email_confirmed = True
user.email_confirm_date = datetime.utcnow()
db.session.add(user)
db.session.commit()
return redirect(url_for("users.login"))
flash(
f"Your email has been verified and you can now login to your account",
"success",
)
else:
return render_template("errors/token_invalid.html")
Only missing from my point of view is a simple conditional logic, to check if email_confirmed = True before logging in, as well as the same check inside the confirm_email(token) function to not make this process repeatable in case the user clicks on the confirmation link several times. Thanks again! Hope this is of some help to anyone else!
The key to your question is this:
My question is: how do I best identify the user (for whom to alter the email_confirmed Column) when he clicks on his custom url?
The answer can be seen in the example on URL safe serialisation using itsdangerous.
The token itself contains the e-mail address, because that's what you are using inside your get_mail_confirm_token() function.
You can then use the serialiser to retrieve the e-mail address from that token. You can do that inside your verify_mail_confirm_token() function, but, because it's a static-method you still need a session. You can pass this in as a separate argument though without problem. You also should treat the BadSignature exception from itsdangerous. It would then become:
#staticmethod
def verify_mail_confirm_token(session, token):
s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
try:
email = s.loads(token, salt='email-confirm', max_age=60)
except (BadSignature, SignatureExpired):
return "PROBLEM"
user = session.query(User).filter(User.email == email).one_or_none()
return user
Would it be a good practice to also save the token inside a db Column and then filter by that token to identify the user?
No. The token should be short-lived and should not be kept around.
Finally, in your get_mail_confirm_token implementation you are not using the URLSafeTimedSerializer class correctly. You pass in a second argument called expires_sec, but if you look at the docs you will see that the second argument is the salt, which might lead to unintended problems.

add data to wtforms by reference

I want to preload form data in wtforms with data from my database without knowing the column names.
this works so long as I know the column name I want.
form.column.data=User.query.get(1).first().column
what I want to do is go over all the columns like so:
for attr, value in User.query.get(1).__dict__.iteritems():
form.attr.data = value
doing so gives me the error:
AttributeError: 'EditUsersForm' object has no attribute 'attr'
Here is a snippet of the code I'm trying to get to work specifically.
forms.py
class EditUsersForm(ModelForm):
class Meta:
model=User
exclude=['password_hash']
models.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(32), index=True, unique=True)
password_hash = db.Column(db.String(128))
email = db.Column(db.String(120), index=True, unique=True)
posts = db.relationship('Post', backref='author', lazy='dynamic')
#mobilitys = db.relationship('Mobility', backref='username', lazy='dynamic')
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime)
def __repr__(self): # pragma: no cover
return '<user> %r' % (self.username)
views.py
#app.route('/manpower_edit_users', methods=['GET', 'POST'])
#login_required
def manpower_edit_users():
form=QueryAllUsers(request.form)
forms=EditUsersForm(request.form)
'''
if forms.validate():
print("validate")
print("forms.validate=",forms.validate)
flash("User added")
'''
if request.method == "POST":
for attr, value in User.query.get(user_list).__dict__.iteritems():
forms.attr.data=value
elif request.method == "GET":
for attr, value in User.query.get(1).__dict__.iteritems():
forms.attr.data=value
return render_template('manpower_edit_users.html',title='Manpower Edit User', form=form, forms=forms)
edit_user_form = EditUsersForm(obj = User.query.get(1))
You can populate a form from data. The keyword argument obj in documentation of WTForms said:
obj – If formdata has no data for a field, the form will try to get it from the passed object.

Flask-Bcrypt Run time error

I was finishing up a simple little user login with Flask and Bcrypt. However, when trying to login with a user that is stored in my database, I keep encountering a runtime error. It looks like some sort of loop that's breaking. Being that I'm pretty new to programming, this has been incredibly challenging to figure out what I'm doing wrong. Hopefully someone here can help out! I'll past the relevant code below:
Here's the issue I'm getting. It just keeps looping this until it exceeds the maximum:
File "C:\Users\desktop\test\app\models.py", line 37, in password
return self.password
File "C:\Users\.virtualenvs\flask\lib\site-packages\sqlalchemy\ext\hybrid.py",
line 742, in get
return self.fget(instance)
File "C:\Users\desktop\test\app\models.py", line 37, in password
Models.py
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password = db.Column(db.String(100))
posts = db.relationship('Post', backref='author', lazy='dynamic',
primaryjoin="User.id==Post.user_id")
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime)
followed = db.relationship('User',
secondary=followers,
primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id),
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic')
#hybrid_property
def password(self):
return self.password
#password.setter
def set_password(self, plaintext):
self.password = bcrypt.generate_password_hash(plaintext)
def is_correct_password(self, plaintext):
return bcrypt.check_password_hash(self.password, plaintext)
Forms.py
class LoginForm(Form):
nickname = TextField('Nickname', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
Views.py Login
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(nickname=form.nickname.data).first_or_404()
if user.is_correct_password(form.password.data):
login_user(user)
flash("you've been logged in!, 'success'")
return redirect(url_for('index'))
else:
flash('your email or password doesnt match!', 'error')
return redirect(url_for('login'))
return render_template('login.html',
title='Sign In',
form=form)
Views.py Registration
#app.route('/register', methods=('GET', 'POST'))
def register():
form = forms.RegisterForm()
if form.validate_on_submit():
user = User(nickname=form.nickname.data, emai=form.email.data, password=form.password.data)
db.session.add(user)
db.session.commit()
flash('User successfully registered')
return redirect(url_for('login'))
return render_template('register.html', form=form)
You should use different names for column and property
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
password_hash = db.Column(db.String(100))
#hybrid_property
def password(self):
return self.password_hash
#password.setter
def set_password(self, plaintext):
self.password_hash = bcrypt.generate_password_hash(plaintext)

Categories

Resources