I am building a website using Flask and when I try to log in and signup I get this error AttributeError: 'list' object has no attribute 'is_active'
an error like this appears when I have used login_user in the flask_login library
here I use firestore database
I have tried various solutions on StackOverflow but none of them worked, I'm very confused now
this is my code
__ini__.py
from flask import Flask
from flask_login import UserMixin
from flask_login import LoginManager
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = 'adjadlkahd'
db = connect_database()
from .views import views
from .auth import auth
from .models import User
#check user login
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
#create user model
#login_manager.user_loader
def load_user(id):
return User.get_user_id(id)
#register blueprints
app.register_blueprint(views, url_prefix='/')
app.register_blueprint(auth, url_prefix='/')
return app
cred = credentials.Certificate("websites\serviceAccountKey.json")
firebase_admin.initialize_app(cred)
def connect_database():
db = firestore.client()
return db
auth.py
from flask import Blueprint, render_template, request, redirect, url_for
from .models import User
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import login_user, login_required, logout_user, current_user
auth = Blueprint('auth', __name__)
#auth.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')
user = User.log_in(email)
if user:
if check_password_hash(user[0].to_dict()['password'], password):
login_user(user, remember=True)
redirect(url_for('views.home'))
else:
return "<h1>paass salah</h1>"
else:
return "<h1>user tidak ada</h1>"
return render_template('login.html')
#auth.route('/logout')
#login_required
def logout():
logout_user()
return redirect(url_for('auth.login'))
#auth.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
if User.check_user(email):
return "<h1>email already exist</h1>"
new_user = User(username=username, email=email,
password=generate_password_hash(password)).create_user()
login_user(new_user, remember=True)
return redirect(url_for('views.home'))
return render_template('signup.html')
models.py
from flask_login import UserMixin
from . import connect_database
import uuid
db = connect_database()
class User(UserMixin):
def __init__(self, username, email, password):
self.username = username
self.email = email
self.password = password
def create_user(self):
db.collection('user').document(self.username).set({
'id' : str(uuid.uuid4()),
'username': self.username,
'email': self.email,
'password': self.password
})
def log_in(email):
user = db.collection('user').where('email', '==', email).get()
return user
def check_user(email):
user = db.collection('user').where('email', '==', email).get()
return bool(user)
def get_user_id(id):
return db.collection('user').document(id).get()
views.py
from flask import Blueprint
from flask_login import login_required, current_user
views = Blueprint('views', __name__)
#views.route('/')
#login_required
def home():
return "<h1>logged in</h1>"
For flask login to work you need to have a user class that implements some properties and methods. When you use login_user method the argument should be an instance of that user class.
make user that your method def log_in(email) returns a user object not a list.
def log_in(email):
user = db.collection('user').where('email', '==', email).get()
return user # <-- most probably is a list.
Read the items form the user collection
Create an instance of the user class
fill the properties of the user class from the collection
return the instance
Related
I'm creating my first Flask project. The first part of the project is obviusly the login part. I made with html tho page for sign up and login. Then I wrote the flask part of the project. The code is the following, divided in the main.py file
from flask import Flask, render_template, redirect, url_for, session
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import create_engine
from os import path
from flask_login import LoginManager
db = SQLAlchemy()
DB_NAME = "database.db"
def create_app():
app = Flask(__name__,static_url_path='/static')
app.secret_key = "referendumKey"
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False
db.init_app(app)
#importation of all blueprints created
from .auth import auth
from .views import views
app.register_blueprint(auth, url_prefix='/')
app.register_blueprint(views, url_prefix='/')
#model import
from .models import Refs, User
create_database(app)
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
#login_manager.user_loader
def load_user(id):
return User.query.get(int(id))
return app
#If you haven't already created a database, it will be created
def create_database(app):
if not path.exists('website/' + DB_NAME):
db.create_all(app=app)
print('Created Database!')
Then, the authentication file (auth.py)
from flask import Blueprint, render_template, session, redirect, url_for, request, flash
from .models import User
from . import db
from flask_login import login_user, login_required, logout_user, current_user
auth = Blueprint("auth", __name__)
#auth.route("/login", methods=["GET", "POST"])
def login():
if request.method == 'POST':
name =request.form.get("username")
psw =request.form.get("psw")
user = User.query.filter_by(name=name).first()
if user:
if psw == user.password:
flash("Logged succesfully", category="success")
login_user(user, remember=True)
return redirect(url_for("views.home"))
else:
flash("Incorrect password", category="error")
else:
flash("Unexistent user", category="error")
return render_template("login.html", user=current_user)
#auth.route("/signup", methods=["GET", "POST"])
def signup():
if request.method=="POST":
username=request.form.get('username')
psw1=request.form.get('psw1')
psw2=request.form.get('psw2')
user = User.query.filter_by(name=username).first()
if user:
flash("Username already exist", category="error")
elif len(username)<3:
flash("Username too short", category="error")
elif psw1!=psw2:
flash("Passwords are different", category="error")
else:
new_user = User(name=username, password=psw1)
db.session.add(new_user)
db.session.commit()
login_user(new_user, remember=True)
flash("Account created", category="Success")
return redirect(url_for(views.home))
return render_template("signup.html", user=current_user)
#auth.route('/logout')
#login_required
def logout():
logout_user()
return redirect(url_for('auth.login'))
And, the views.py file, that should let people see the home page with content
from flask import Blueprint, render_template, request, flash, jsonify
from flask_login import login_required, current_user
from . import db
views = Blueprint('views', __name__)
#views.route("/")
#views.route("/home")
#login_required
def home():
return render_template("home.html")
To store the datatypes, I used the models in the following code
from . import db
from flask_login import UserMixin
from sqlalchemy.sql import func
"""
Definire la classe che identidica un Referendum, inserendovi:
id di colonna, la domanda, la prima e la seconda risposta
possibili, i voti che hanno ricevuto, chi ha votato già, l'id
dell'utente che possiede il ref
"""
class Refs(db.Model):
id = db.Column(db.Integer,primary_key=True)
question = db.Column(db.String(200))
fAnsw = db.Column(db.String(200))
sAnsw = db.Column(db.String(200))
fVote = db.Column(db.Integer, default=0)
sVote = db.Column(db.Integer, default=0)
voters = db.Column(db.ARRAY(db.Integer))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
"""
Definire la classe utente inserendo il suo nome, la sua
password, e tutti i Referendum da lui creato
"""
class User(db.Model,UserMixin):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(150), unique=True)
password = db.Column(db.String(150))
refs = db.relationship('Refs')
Now, there is a problem. If I try to create a new account, or to login, there is an error. The error is the following one.
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: user
[SQL: SELECT user.id AS user_id, user.name AS user_name, user.password AS user_password
FROM user
WHERE user.name = ?
LIMIT ? OFFSET ?]
[parameters: ('sadas', 1, 0)]
(Background on this error at: http://sqlalche.me/e/14/e3q8)
And it shows the follow part of the code:
user = User.query.filter_by(name=name).first()
I guess the problem is in the model of the database, because it shows up a SQLAlchemy error. Probably I used an incorrect syntax. I read a lot of github repository, and also the official documentation (https://docs.sqlalchemy.org/en/14/),and even the link relased by the compiler, but I can't understand, I think I wrote it correctly. The error can even be in the flask_login API usage, but reading error and documentation I guess it isn't. Can someone help me please? Thanks
Creating a database at runtime is not preferred. Flask app needs to be connected to a database on start. You may create tables at runtime.
I suspect that you created an empty database somewhere. So the DB file may exist but contain no tables. You may in fact have two lingering SQLite files at different locations. Inspect your directories carefully.
Consider this code:
DB_NAME = "database.db"
def create_app():
app = Flask(__name__,static_url_path='/static')
app.secret_key = "referendumKey"
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}'
This will create a SQLite file in the "current" directory (whatever it is).
Elsewhere you specify a relative path - again, it's not clear what the final path is but could very well be different than above:
def create_database(app):
if not path.exists('website/' + DB_NAME):
db.create_all(app=app)
print('Created Database!')
The fix is to use a fully qualified path for your DB, not a relative path.
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 currently have a simple Flask application that authenticates registered users with flask_login and the #login_required decorator. So certain pages are accessible only to registered users.
However, I want to create another layer of authentication where only admin users have access to the admin dashboard and admin login pages.
Here is my views.py file.
from flask import (render_template, flash, url_for, redirect)
from models import (db, app, User)
from forms import (RegisterForm, LoginForm)
from flask_login import (LoginManager, logout_user, login_user, login_required,
current_user)
login_manager = LoginManager(app)
login_manager.login_view = "login"
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
#app.route("/")
#app.route("/home")
def home():
return render_template("home.html", title="home")
#app.route("/register", methods = ["GET", "POST"])
def register():
form = RegisterForm()
if form.validate_on_submit():
username = form.username.data
email = form.email.data
password = form.password.data
confirm_password = form.confirm_password.data
user = User(username, email, password, confirm_password)
db.session.add(user)
db.session.commit()
flash("Thanks for registering, {}".format(username.capitalize()))
return redirect(url_for("login"))
return render_template("register.html", title="register",
form=form)
#app.route("/login", methods = ["GET", "POST"])
def login():
form = LoginForm()
users = User.query.all()
if form.validate_on_submit() and users != []:
email = form.email.data
password = form.password.data
user = User.query.filter_by(email=email).first()
if user.checking_password(password) and user is not None:
login_user(user)
flash("Thanks for logging in!")
return redirect(url_for("profile"))
return render_template("login.html", title="login",
form = form)
if __name__ == "__main__":
app.run(debug=True)
I tried reusing flask_login imports by renaming them with an admin_ prefix, and applying them to an admin view function, but it didn't seem to work.
from flask_login import (LoginManager as AdminManager,
logout_user as admin_logout_user,
login_user as admin_login_user,
login_required as admin_login_required,
current_user as admin_user)
admin_manager = AdminManager(app)
admin_manager.login_view = "admin_login"
#admin_manager.user_loader
def load_admin(admin_id):
return Admin.query.get(int(admin_id))
If anyone knows how I can do this, I'd be very grateful.
This is a lot easier to do in Django as an admin user page is generated out of the box, so to speak.
Typically you don’t have a separate login for admin users. Rather, you have roles on each user. So if a user has an admin role, they are able to see certain pages. Then you control access to specific routes using flask-user and the #roles_required decorator.
Attempting to deploy a Flask Web Application with a Log-In form using SQLAlchemy and SQLITE3. The application behaves as expected when running locally. However, when deployed as Azure Web Service, it is not possible to access or write to the database. Doint so results in follwing error output:
500 Internal Server Error
2019-01-05T19:13:30.235301217Z [2019-01-05 19:13:30,198] ERROR in app: Exception on /register [POST]
2019-01-05T19:13:30.235339017Z Traceback (most recent call last):
2019-01-05T19:13:30.235343817Z File
...
2019-01-05T19:13:30.235502017Z File "/home/site/wwwroot/antenv/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 724, in _commit_impl
2019-01-05T19:13:30.235505417Z self.engine.dialect.do_commit(self.connection)
2019-01-05T19:13:30.235508817Z File "/home/site/wwwroot/antenv/lib/python3.7/site-packages/sqlalchemy/engine/default.py", line 462, in do_commit
2019-01-05T19:13:30.235512317Z dbapi_connection.commit()
2019-01-05T19:13:30.235624616Z sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked (Background on this error at: http://sqlalche.me/e/e3q8)
I have troubles investigating this error as it behaves as expected locally. The Azure Web App is in a resource group not visible to others,so this can not be the source of a concurrency conflict.
models.py
from flask_login import UserMixin
from werkzeug.security import check_password_hash
from werkzeug.security import generate_password_hash
from app.extensions import db
from app.extensions import login
#login.user_loader
def load_user(id):
return User.query.get(int(id))
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
password_hash = db.Column(db.String(128))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self):
return '<User {}>'.format(self.username)
webapp.py
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from flask_login import login_required
from flask_login import login_user
from flask_login import logout_user
from werkzeug.urls import url_parse
from app.extensions import db
from app.forms import LoginForm
from app.forms import RegistrationForm
from app.models import User
server_bp = Blueprint('main', __name__)
#server_bp.route('/')
def index():
return render_template("index.html", title='Home Page')
#server_bp.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
error = 'Invalid username or password'
return render_template('login.html', form=form, error=error)
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('main.index')
return redirect(next_page)
return render_template('login.html', title='Sign In', form=form)
#server_bp.route('/logout')
#login_required
def logout():
logout_user()
return redirect(url_for('main.index'))
#server_bp.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
return redirect(url_for('main.login'))
return render_template('register.html', title='Register', form=form)
#server_bp.route('/dynamic_chart', methods=['POST', "GET"])
def dynamicchart():
req_data = request.get_json()
hour1 = req_data["hour1"]
return hour1
Any idea, help is appreciated. Thank you for your time.
I would not use SQLite for production. I also ran into this error and changed to PostgreSQL. After that, this error never appeared anymore.
I have 3 blueprints in a flask app and the dir structure is like:
main/
__init__.py
books/
users/
authors/
apps/
Every package inisde main is a blueprint.
In my main/__init__.py i have
from flask import Flask
from flask_pymongo import PyMongo
app = Flask(__name__)
from main.users.views import users
from main.admin.views import admin
app.register_blueprint(users, url_prefix='/api/users')
MONGO_HOST = os.environ['MONGO_HOST']
MONGO_PORT = os.environ['MONGO_PORT']
app.config["MONGO_URI"] = "mongodb://{}:{}/".format(MONGO_HOST, MONGO_PORT)
mongo = PyMongo(app)
How do I access mongo inside each blueprint ? Is this even correct way of using mongo here.
in official documentation it says not to use something like db=Pymongo(app)
I guess the answer comes too late for you, but eventually it will help anyways.
I usually exclude the database-lines in an external file, e.g. database.py.
and then import the mongo instance in my app and in the blueprints, respectively. Please consider the example below. For the sake of completion and comprehension I also added other elements that make sense for the functions.
database.py
from flask_pymongo import PyMongo
mongo = PyMongo()
forms.py
from wtforms import Form
from wtforms.fields import BooleanField, PasswordField, StringField
from wtforms.validators import Email, Required
class LoginForm(Form):
email = StringField('Email', validators=[Required(), Email('Not a valid email address')])
password = PasswordField('Password',validators=[Required()])
remember = BooleanField('Remember')
authentication.py
from flask import Blueprint, redirect, render_template, request
from flask_login import LoginManager, UserMixin, current_user, login_user
from werkzeug.security import check_password_hash
from database import mongo
from forms import LoginForm
authentication = Blueprint('authentication', __name__, template_folder='templates')
login_manager = LoginManager()
login_manager.login_view = 'authentication.log_in'
#authentication.route('/login', methods=['GET', 'POST'])
def log_in():
if (current_user.is_authenticated):
return redirect(url_for('get_index'))
login_form = LoginForm(request.form)
if (request.method == 'POST'):
if (login_form.validate()):
user_collection = mongo.db.user
user = user_collection.find_one({'email':login_form.email.data})
if (user) and (check_password_hash(user['password'],
login_form.password.data)):
login_user(User(user), login_form.remember)
return redirect(url_for('get_index'))
else:
return render_template('login.html', form=login_form)
elif (request.method == 'GET'):
return render_template('login.html', form=login_form)
class User(UserMixin):
def __init__(self, user):
super()
self.id = user['_id']
self.email = user['email']
def get(user_id, mongo):
user_collection = mongo.db.user
user = user_collection.find_one({'_id':ObjectId(user_id)})
return User(user)
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id, mongo)
app.py
from flask import Flask, render_template, request
from flask_login import login_required
from authentication import authentication, login_manager
from database import mongo
from forms import LoginForm
app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config['MONGO_URI'] = 'mongodb:27017/app'
mongo.init_app(app)
app.register_blueprint(authentication)
login_manager.init_app(app)
#app.route('/', methods=['GET'])
#login_required
def get_index():
if (request.method == 'GET'):
render_template('index.html')
if __name__ == '__main__':
app.run(host="0.0.0.0")