I want to create a web framework by using Flask module. However, there is an error in my code that I couldn't solve it. This is my code:
from flask import Flask, render_template, url_for, request, redirect
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(200), nullable=False)
date_created = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return '<Task %r>' % self.id
#app.route('/', methods=['POST', 'GET'])
def index():
if request.method == 'POST':
task_content = request.form['content']
new_task = Todo(content=task_content)
try:
db.session.add(new_task)
db.session.commit()
return redirect('/')
except:
return 'There was an issue adding your task'
else:
tasks = Todo.query.order_by(Todo.date_created).all()
return render_template('index.html', tasks=tasks)
#app.route('/delete/<int:id>')
def delete(id):
task_to_delete = Todo.query.get_or_404(id)
try:
db.session.delete(task_to_delete)
db.session.commit()
return redirect('/')
except:
return 'There was a problem deleting that task'
#app.route('/update/<int:id>', methods=['GET', 'POST'])
def update(id):
task = Todo.query.get_or_404(id)
if request.method == 'POST':
task.content = request.form['content']
try:
db.session.commit()
return redirect('/')
except:
return 'There was an issue updating your task'
else:
return render_template('update.html', task=task)
if __name__ == "__main__":
app.run(debug=True)
The error shown for this code is
sqlite3.OperationalError: no such table: todo
My expected web framework is I can update and delete any task on the task column. My expected web framework is:
Task Added Action
Wash dishes 2/8/2019 Delete
Update
For information, the action "Delete" and "Update" are an option to delete or update that particular task. Anyone can help me to solve this error? Thanks everyone.
You have to create table todo in file test.db
Flask/SQLAlchemy doesn't create it automatically so you have to use in code (after class Todo)
db.create_all()
But use it only once and later remove.
See official documentation for flask_sqlalchemy
Related
I am new to Flask and I built a basic web app using Flask. This app will be used by a single user. The user must be connected in order to access any routes. What would be the easiest and most secure way to create new routes on my app and make sure that the user is logged in before they are able to access the page?
I added this route and I am able to access the page even if I am logged in.
#login_required
#app.route('/secret')
def secret():
return "hello world"
app.py
from flask import Flask, render_template, url_for, request, session, redirect
from flask_pymongo import PyMongo
import bcrypt
app = Flask(__name__)
app.config['MONGO_DBNAME'] = xxx'
app.config['MONGO_URI'] = 'xxxx'
mongo = PyMongo(app)
#app.route('/')
def index():
if 'username' in session:
return 'You are logged in as ' + session['username']
return render_template('index.html')
#app.route('/login', methods=['POST'])
def login():
users = mongo.db.users
login_user = users.find_one({'name' : request.form['username']})
if login_user:
if bcrypt.hashpw(request.form['pass'].encode('utf-8'), login_user['password']) == login_user['password']:
session['username'] = request.form['username']
return redirect(url_for('index'))
return 'Invalid username/password combination'
#app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'POST':
users = mongo.db.users
existing_user = users.find_one({'name' : request.form['username']})
if existing_user is None:
hashpass = bcrypt.hashpw(request.form['pass'].encode('utf-8'), bcrypt.gensalt())
users.insert({'name' : request.form['username'], 'password' : hashpass})
session['username'] = request.form['username']
return redirect(url_for('index'))
return 'That username already exists!'
return render_template('register.html')
#app.route('/logout')
def logout():
session.pop('username', None)
return render_template('index.html')
if __name__ == '__main__':
app.secret_key = 'mysecret'
app.run(debug=True, port='3500')
You can use the flask-login module for this. It handles user authentication, allowing routes to be protected with the #login_required decorator.
I recommend reading the documentation, as you need to provide a class with certain properties to represent your users and you need to implement a method that returns a user based on a given identifier.
When a user is logged in, the templates can access the current_user variable and it's properties (as defined by you; name, email etc).
Here is a simple example of the python code (I have not included the static files or templates).
from flask import Flask, render_template, request, url_for, request, redirect, abort
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from flask_pymongo import PyMongo
from flask_bcrypt import Bcrypt
from urllib.parse import urlparse, urljoin
import sys
# Import User classes
from user import User, Anonymous
# Create app
app = Flask(__name__)
# Configuration
app.config['MONGO_DBNAME'] = 'database_name'
app.config['MONGO_URI'] = 'mongo_database_uri'
app.secret_key = 'change this before production'
# Create Pymongo
mongo = PyMongo(app)
# Create Bcrypt
bc = Bcrypt(app)
# Create login manager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.anonymous_user = Anonymous
login_manager.login_view = "login"
# ROUTES
#app.route('/')
def index():
return render_template('index.html')
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
if current_user.is_authenticated:
return redirect(url_for('/index'))
return render_template('login.html')
users = mongo.db.users
user_data = users.find_one({'email': request.form['email']}, {'_id' : 0 })
if user_data:
if bc.check_password_hash(user_data['password'], request.form['pass']):
user = User(user_data['title'], user_data['first_name'], user_data['last_name'], user_data['email'], user_data['password'], user_data['id'])
login_user(user)
#Check for next argument (direct user to protected page they wanted)
next = request.args.get('next')
if not is_safe_url(next):
return abort(400)
return redirect(next or url_for('profile'))
return 'Invalid email or password'
#app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'POST':
users = mongo.db.users
existing_user = users.find_one({'email' : request.form['email']}, {'_id' : 0 })
if existing_user is None:
logout_user()
hashpass = bc.generate_password_hash(request.form['pass']).decode('utf-8')
new_user = User(request.form['title'], request.form['first_name'], request.form['last_name'], request.form['email'], hashpass)
login_user(new_user)
users.insert_one(new_user.dict())
return redirect(url_for('profile'))
return 'That email already exists!'
return render_template('register.html')
#app.route('/profile', methods=['GET'])
#login_required
def profile():
return render_template('profile.html')
#app.route('/list', methods=['GET'])
#login_required
def list():
#if current_user.id:
log(current_user.is_authenticated)
all_users = mongo.db.users.find({}, {'_id' : 0 })
return render_template('list.html', users = all_users)
#app.route('/logout', methods=['GET'])
#login_required
def logout():
logout_user()
return redirect(url_for('index'))
# Login Manager requirements
#login_manager.user_loader
def load_user(userid):
# Return user object or none
users = mongo.db.users
user = users.find_one({'id': userid}, {'_id' : 0 })
if user:
log(user)
return User(user['title'], user['first_name'], user['last_name'], user['email'], user['password'], user['id'])
return None
# Safe URL
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc
# Run app
if __name__ == '__main__':
app.run(debug=True)
I have a functioning basic template app using flask, flask-login and flask-pymongo at https://github.com/chriswilson1982/flask-mongo-app
I have created a login page with wraps & functools. I have done this previously and it worked, but this time I have stored the username & password in a PostgreSQL database and queried it using the sqlalchemy flask extension.
If the correct details have been entered, it logs you into the admin page, but if you try to type on the admin page, it doesn't redirect you back to the login page.
from flask import Flask, render_template, url_for, request, redirect, flash, session
from functools import wraps
app = Flask(__name__)
app.secret_key = "pythonAkoto"
def login_required(f):
#wraps(f)
def wrap(*args, **kwargs):
if 'logged_in' in session:
return f(*args, **kwargs)
else:
flash('You need to sign in first')
return redirect(url_for('login'))
return wrap
from sqlalchemy.orm import sessionmaker, relationship
# # this part is needed to create session to query database. this should be JUST BELOW app.config..
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, ForeignKey, select
meta = MetaData()
engine = create_engine("postgresql://postgres:password#localhost/db_name", echo = True)
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# posts database
class PostTable(Base):
__tablename__ = 'poststable'
id = Column('id', Integer, primary_key=True)
user = Column('username', String(20))
posts = Column('posts', String(30))
def __init__(self, user, posts):
self.user = user
self.posts = posts
# login database
class UserLogin(Base):
__tablename__ = 'user_login'
id = Column('id', Integer, primary_key=True)
username = Column('username', String(30))
userPW = Column('user_pw', String(20))
def __init__(self, username, user_pw):
self.username = username
self.user_pw = user_pw
Session = sessionmaker(bind=engine)
db_session = Session() # i have called this db_session to not get mixed up with sessions when logging in.
# write posts on wall
#app.route('/', methods=['GET', 'POST'])
def postpage():
title = "Posts"
name = request.form.get('name')
posts = request.form.get('posts')
if request.method == 'GET':
data = db_session.query(PostsTable).all()
flash('Write something on the wall!')
return render_template("postpage.html", title=title, data=data)
else:
db_entry = PostsTable(name, posts)
db_session.add(db_entry)
db_session.commit()
data = db_session.query(PostsTable).all()
flash(f'{name} just wrote a post!')
return render_template("postpage.html", data=data, title=title)
# login into admin page
#app.route('/login', methods=['GET', 'POST'])
def login():
title = "Login"
user = request.form.get("username")
upw = request.form.get("password")
if request.method == 'GET':
return render_template("login.html", title=title)
else:
db_user = db_session.query(UserLogin).filter(UserLogin.username==f'{user}')
db_pw = db_session.query(UserLogin).filter(UserLogin.userPW==f'{upw}')
result = db_user.first() and db_pw.first()
if result:
session['logged_in'] = True
flash(f'Hey {user}, you have just logged in!')
return redirect(url_for('admin'))
else:
error = "Invalid credentials. Please enter a valid username and/or password."
return render_template('login.html', error=error, title=title)
# admin page
#app.route('/admin')
#login_required
def admin():
title = "Admin"
return render_template("admin.html", title=title)
if __name__ == '__main__':
app.run(debug=True)
When you enter admin url, it's supposed to redirect you to the login page, but this doesn't happen; instead it takes you to the admin page without logging in!
I've done this before (without the database) and it worked fine, but this way isn't working and I can't seem to understand why.
I am building one website using Flask. The app.py, html template and db(postgresql) data is shown below.On hitting the URL http://127.0.0.1:5000/ we can enter the username and email. http://127.0.0.1:5000/users will show the content which is updated in db here it is Mak mak#mk.com.
app.py
from flask import Flask, render_template,request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =
'postgres://postgres:123#localhost/sample'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80))
email = db.Column(db.String(100), unique = True)
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return '<User %r>' %self.username
#app.route('/')
#def index():
def index():
return render_template('add_user.html')
#app.route('/post_user', methods=['POST'])
def post_user():
user = User(request.form['username'], request.form['email'])
db.session.add(user)
db.session.commit()
return redirect(url_for('index'))
#app.route('/users')
def get_users():
users = User.query.all()
return render_template('get_user.html', user = users)
#app.route('/homepage')
def display_homepage():
return 'Display homepage'
if __name__ =='__main__':
app.run()
get_user.html
{% for singleuser in users %}
<ul>
<li>singleuser.username</li>
</ul>
{% endfor %}
Sql Schema details
1 "Mak" "mak#mak.com"
in get_users (), there's a typo in the return statement
return render_template('get_user.html', users = users)
I am using Flask-Security to set up user authentication for my app but I am struggling with getting the email address or ID of the currently logged in user so I can query a table with that particular users details. I've just using the standard code.
Another question here suggested the following but it didn't work:
my_user = current_user.get_id()
Standard code:
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, \
UserMixin, RoleMixin, login_required
# Create app
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
# Create database connection object
db = SQLAlchemy(app)
# Define models
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Create a user to test with
#app.before_first_request
def create_user():
db.create_all()
user_datastore.create_user(email='matt#nobien.net', password='password')
db.session.commit()
# Views
#app.route('/')
#login_required
def home():
return render_template('index.html')
if __name__ == '__main__':
app.run()
You can use flask_login.current_user object. Its class will be what you configured Flask-Security to use to handle your users management, e.g. User for code you included.
Michael was half right, the issue is that the standard code example does not import sessions and whilst flask-security does in the back end set the session, it's not available in your flask app. This code from Michael:
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
session['email'] = request.form['email']
Whilst correct in a standard app would likely break with flask-security or at least be unnecessary as flask-security completely takes control of the /login route and handles the form securely. The only two changes needed to the flask example app are:
Changing the flask import line to:
from flask import Flask, render_template, session
This is an example of getting the user id from the session:
#app.route('/dashboard')
#login_required
def dashboard():
user_id = session["user_id"]
return name
Hope this helps someone as it took me a while to get my head around..
IMHO you can implement sessions from Flask.
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
#app.route('/')
def index():
if 'username' in session:
print("Currents user's ID is %s" % session['id']
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
session['email'] = request.form['email']
session['id'] = request.form['id']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
#app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
session.pop('email', None)
session.pop('id', None)
return redirect(url_for('index'))
# set the secret key. keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
See: http://flask.pocoo.org/docs/0.12/quickstart/#sessions
In flask 2.0.x user id is automaticly saved in session under _user_id, not sure how or where this changed, in the official flask docs it still uses g.user, but maybe its outdated? I couldnt get it to work for me.
The simplest way i could get it to check if a user was login in with if '_user_id' in sessions.keys(), the return value of session['_user_id'] is a string of User.id
Its better to use session for this. You can store the information is session then use it anywhere.
In your login function just store the value like:
first import the session from flask.Then use like this.
session['username'] = login_form.username.data
then use it like {{ session['username'] }} in your template.
I'm new to Python and Flask.
I'm following this tutorial http://douglasstarnes.com/index.php/2015/05/27/easy-authentication-with-flask-login/ to have registration and login pages and have slightly modified it to hash the passwords on registration and to verify the password against the hash on login.
Initial password registration hashing works, but verifying the hashed password stored in the database against the one given in plain text via a login form does not.
The error I am receiving is against the following line on /login page and for the following reason:
if user.count() == 1 and check_password_hash(user.password, password) == True:
AttributeError: 'BaseQuery' object has no attribute 'password'
I cannot work out why I'm receiving this error. The user gets successfully queried from the database, and the user has it's hashed password within the password column.
The query I'm using and method of returning data from the password column are similar to that included in the documentation http://flask-sqlalchemy.pocoo.org/2.1/queries/#querying-records
This is my views.py (/login and the erroring line are towards the bottom)
from flask import Flask, render_template, request, abort, redirect, url_for, flash
from flask.ext.login import LoginManager, UserMixin, login_user, logout_user, login_required
from flask.ext.sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from app import app
import os
login_manager = LoginManager(app)
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.session_protection = "strong"
db = SQLAlchemy(app)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String)
password = db.Column(db.String)
#login_manager.user_loader
def user_loader(user_id):
user = User.query.filter_by(id=user_id)
if user.count() == 1:
return user.one()
return None
#app.before_first_request
def init_request():
db.create_all()
#app.route('/secret')
#login_required
def secret():
return render_template('secret.html')
#app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
#app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return render_template('register.html')
elif request.method == 'POST':
username = request.form['txtUsername']
password = request.form['txtPassword']
user = User.query.filter_by(username=username)
if user.count() == 0:
hashed_password = generate_password_hash(password)
user = User(username=username, password=hashed_password)
db.session.add(user)
db.session.commit()
flash('You have registered the username {0}. Please login'.format(username))
return redirect(url_for('login'))
else:
flash('The username {0} is already in use. Please try a new username.'.format(username))
return redirect(url_for('register'))
else:
abort(405)
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html', next=request.args.get('next'))
elif request.method == 'POST':
username = request.form['txtUsername']
password = request.form['txtPassword']
user = User.query.filter_by(username=username)
if user.count() == 1 and check_password_hash(user.password, password) == True:
login_user(user.one(), remember=True)
flash('Welcome back {0}'.format(username))
try:
next = request.form['next']
return redirect(next)
except:
return redirect(url_for('index'))
else:
flash('Invalid login')
return redirect(url_for('login'))
else:
return abort(405)
#app.route('/')
def index():
return render_template('index.html')
Does anyone know why I cannot access the password column for the user, or in fact any column? I've tried all 3 within the database, ID, username and password.
#login_manager.user_loader
def user_loader(user_id):
user = User.query.filter_by(id=user_id).first()
if user:
return user
return None
Applying the .first() executes the query, instead of storing the Query object
it will return only the first result.
.all() returns all
edit:
or you could use user = User.query.get(user_id) assuming user_id is defined as PK
In your Login Function it should be
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, password) == True:
login_user(user)
Now user references a real User object, as opposed to a stored query. you cannot access a User Object attribute (password) from a Query Object
Don't miss .first() or .all() after the SQLQLCHAMY ORM query.
This is cause of such error always.