I'm exploring flask and attempting to setup a simple web app with secure registration and sign in. I'm using flask-security to do this. Unfortunately, when I navigate to the send confirmation page I'm getting the error: "smtpserverdisconnected: please run connect() first".
Below are the relevant files I've written. run.py drives the entire application.
run.py (this is next to the app folder)
#!venv/bin/python
from app import app
app.run(debug = True)
Everything below here is in the app folder
__init__.py
from flask import Flask
from flask.ext.mail import Mail
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
mail = Mail(app)
import models
import views
#app.before_first_request
def create_user():
db.create_all()
models.user_datastore.create_user(email = 'user#example.com',
password = 'password')
db.session.commit()
if __name__ == '__main__':
app.run()
models.py
from app import db, app
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import SQLAlchemyUserDatastore,\
UserMixin, RoleMixin, Security
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'))
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
views.py
from app import app
from flask import render_template
from flask.ext.security import login_required
#app.route('/')
#login_required
def index():
return render_template('index.html')
edit: also, here is the config file I'm using
DEBUG = True
SECRET_KEY = 'secret'
SQLALCHEMY_DATABASE_URI = 'sqlite://'
SECURITY_PASSWORD_HASH = 'sha512_crypt'
SECURITY_PASSWORD_SALT = 'salt'
SECURITY_CONFIRMABLE = True
SECURITY_REGISTERABLE = True
SECURITY_RECOVERABLE = True
SECURITY_TRACKABLE = True
SECURITY_CHANGEABLE = True
MAIL_SERVER = 'smtp.zoho.com'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
MAIL_DEBUG = True
MAIL_USERNAME = 'myaddress#mydomain'
MAIL_PASSWORD = 'password'
Alright, so I figured out what the problem was. By default flask-security is set up to send mail as "localhost". My mail provider is Zoho, but I'm just using them as a mail server for a domain I run. My mail settings are such that I can only send mail from certain addresses. Because 'localhost' is not one of these flask-security was not able to connect to Zoho's servers.
So the solution was to add to my config file the line
SECURITY_EMAIL_SENDER = 'valid_email#my_domain.com'
Hopefully this will save someone else some time trying to figure out why flask-security's email isn't working.
Related
I have a flask app that acts as a login system. I am trying to use #roles_required() decorator in order to check if the user is an admin, and if they are, giving them access to specific areas.
When I try log in with the correct credentials, that have been created with the correct roles, it gives me the error below.
AttributeError: type object 'User' has no attribute 'get_user_by_token'
I have located the error, it seems to be that login_user() is not working properly. I have commented the line causing an error in login_post().
Let me know if there's anything else you need. Please help!!! No one can seem to figure it out...
app.py
from flask import Flask, Blueprint, render_template, redirect, url_for, request, flash, session
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin, login_user, logout_user, login_required, current_user, LoginManager
import json
from flask_user import UserManager, roles_required
USER_ENABLE_EMAIL = False
auth = Blueprint('auth', __name__)
db = SQLAlchemy()
app = Flask(__name__)
app.config['SECRET_KEY'] = 'mysecretkey'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
db.init_app(app)
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
from app import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
app.config['USER_ENABLE_EMAIL'] = False
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
username = db.Column(db.Text, unique=True, nullable=False)
password = db.Column(db.Text, nullable=False)
# Relationships
roles = db.relationship('Role', secondary='user_roles',
backref=db.backref('users', lazy='dynamic'))
# Define the Role data model
class Role(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), unique=True)
# Define the UserRoles data model
class UserRoles(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('role.id', ondelete='CASCADE'))
user_manager = UserManager(app, db, User)
#auth.route('/')
def login():
return render_template('login.html')
#auth.route('/login', methods=['POST'])
def login_post():
username = request.form.get('username')
password = request.form.get('password')
remember = True if request.form.get('remember') else False
adminuser = 'adminuser'
adminpwd = 'adminpwd'
if username == adminuser:
if check_password_hash(adminpwd, password):
login_user(user, remember=remember) # this is the problem
return redirect('/admin/dashboard')
if not user or not check_password_hash(user.password, password):
flash('Please check your login details and try again.')
return redirect(url_for('auth.login'))
login_user(user, remember=remember) # this is the problem
return redirect(url_for('auth.profile'))
#auth.route('/profile')
#login_required
#roles_required('userClass')
def profile():
return render_template('profile.html')
#auth.route('/admin/dashboard')
#roles_required('Admin')
def admin_dashboard():
return render_template("admin_dashboard.html")
I followed some tutorial of flask factory pattern and tried to refactor my project. The database I use is a MySql container. Everything works fine before refactoring.
Now I came into an error sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: user and I guess the problem is due to isolated db instance because I never use sqllite in my project. But I fail to debug it. I also use blutprint to orgnize the strcture.
Here is the code relating to the error:
baseClasses.py
from flask_login import UserMixin
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = "login"
login_manager.login_message_category = 'info'
bcrypt = Bcrypt()
#login_manager.user_loader
def load_user(user_id):
return User.query.get(user_id)
class User(db.Model, UserMixin):
__tablename__ = "user"
usr = db.Column(db.String(50),primary_key=True,unique=True,nullable=False)
psd = db.Column(db.String(100), nullable=False)
role = db.Column(db.String(50), default="viewer")
def __repr__(self):
return f"User('{self.usr}','{self.psd}','{self.role}')"
def get_id(self):
return (self.usr)
class DataModel(db.Model):
__abstract__ = True
own_id = db.Column(db.Integer, primary_key = True, autoincrement = True, nullable = False)
name = db.Column(db.String(255), nullable = False)
project = db.Column(db.String(100), nullable = False)
last_user = db.Column(db.String(50))
path = db.Column(db.String(255), nullable = False)
model.py, the class TO is one example of all other classes, each of them has a table in the database.
from storage_flask.baseClasses import DataModel
from storage_flask.baseClasses import db
class TO(DataModel):
__tablename__ = "to"
to_version = db.Column(db.String(100), nullable = False)
title = db.Column(db.String(100), nullable = False)
issue_date = db.Column(db.DateTime, default = datetime.now().date())
change_date = db.Column(db.DateTime, default = datetime.now().date())
def __repr__(self):
return f"TO('{self.own_id}','{self.name}','{self.to_version}','{self.project}','{self.last_user}','{self.issue_date}','{self.change_date}','{self.path}','{self.title}'"
config.py
import os
class BaseConfig:
SECRET_KEY = 'aafa4f8047ce31126011638be8530da6'
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(BaseConfig):
DEBUG = True
#Database
SQLALCHEMY_DATABASE_URL = 'mysql+pymysql://root:demo#localhost:33065/TO'
init.py
import os
from flask import Flask
from storage_flask.baseClasses import db, bcrypt, login_manager, User
from storage_flask.main.routes import main
def create_app(config):
#create app instance
app = Flask(__name__.split('.')[0],static_folder="static")
app.config.from_object(config)
db.init_app(app)
bcrypt.init_app(app)
login_manager.init_app(app)
#register blueprintes
app.register_blueprint(main)
return app
run.py
from storage_flask import create_app, config
app = create_app(config.DevelopmentConfig)
if __name__ == '__main__':
app.run(host="127.0.0.1", debug=True)
routes.py of the blueprint main
from flask import render_template, url_for, flash, redirect, request, make_response, jsonify, abort, send_from_directory, Blueprint
# import flask-login functions
from flask_login import login_user, current_user, logout_user, login_required
main = Blueprint('main',__name__)
#main.route('/', methods = ['POST', 'GET'])
#login_required
def select_function():
return render_template('functionSelect.html', title = 'Function Selection')
The structure of the project would be:
|--storage_flask/
| |--main/
| | |--routes.py
| |--__init__.py
| |--baseClasses.py
| |--config.py
| |--model.py
|--run.py
I tried many ways to debug it but I always get the error. The database exists and it works before refactoring with factory pattern.
Could someone give me a hand?
I am new to flask been working on a project for a month. I have already separated all the code into file and blueprints and I want to delete all my tables by doing db.drop_all() in cmd. So i can start my database entries from scratch, but I get RuntimeError: No application found. Either work inside a view function or push an application context.
Here are part of my codes
the whole __init__.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from flask_mail import Mail
from testapp.config import Config
db=SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()
login_manager.login_view = 'users.login'
login_manager.login_message_category = 'info'
mail = Mail()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
bcrypt.init_app(app)
l login_manager.init_app(app)
mail.init_app(app)
from testapp.users.routes import users
from testapp.posts.routes import posts
app.register_blueprint(users)
app.register_blueprint(posts)
return app
My config.py:
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USERNAME = os.environ.get('EMAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('EMAIL_PASSWORD')
All my tables in models.py:
from datetime import datetime
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
from testapp import db, login_manager
from flask_login import UserMixin
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
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.jpg')
password = db.Column(db.String(60), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}')"
class Post(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"User('{self.title}', '{self.date_posted}')"
Just to follow up on that question, i was just dumbly running db.drop_all() function without in the command line inserting the app context all i had to do was import and add the app_context() function inside db.drop_all().
And here i thought something in my code was wrong, So for the people with same question dont refer my code, but just this answer.
Thank you #Adrian Klaver for the help.
This maybe a duplicate, but all the solutions to the other questions did not help me.
I am trying to modularize my code where I am moving my config files to a separate file called settings.py.
To run the code i am going running from my terminal "python3 manage.py shell"
Python not updating
then I execute
from flask_blog import db
from author.models import Author
db.create_all()
Hence the QUESTION is: Why is the database not being updated?
The three file I have that concern config are:
manage.py Handles the server set-up (in simple wordds)
settings.py Handles the database for now
models.py The database model
__init__.py The init file
The code is below is the settings.py
import os
SECRET_KEY = 'NOTHING FOR NOW'
DEBUG = True
DB_USERNAME = 'root'
DB_PASSWORD = '1234'
BLOG_DB_NAME = 'blog2'
DB_HOST = os.getenv('IP', '127.0.0.1')
DB_URI = 'mysql+pymysql://root#127.0.0.1:blog'
# DB_URI = 'mysql+pymysql://%s:%s#%s/%s' % (DB_USERNAME, DB_PASSWORD, DB_HOST, BLOG_DB_NAME)
SQLALCHEMY_DB_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = True
The other file called manage.py (below) which handles the basic project config to run.
import os, sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from flask.ext.script import Manager, Server
from flask_blog import app
manager = Manager(app)
manager.add_command("runserver", Server(
use_debugger = True,
use_reloader = True,
host = os.getenv('IP', '127.0.0.1'),
port = int(os.getenv('PORT', 5000))
))
if __name__ == "__main__":
manager.run()
And lastly the database model below. models.py
from flask_blog import db
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True)
fullname = db.Column(db.String(80))
email = db.Column(db.String(35), unique=True)
username = db.Column(db.String(80), unique=True)
password = db.Column(db.String(80))
is_author = db.Column(db.Boolean)
def __init__(self, fullname, email, username, password, is_author=False):
self.fullname = fullname
self.email = email
self.username = username
self.password = password
self.is_author = is_author
def __repr__(self):
return '<Author %r>' % self.username
The __init__.py is below
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object('settings')
db = SQLAlchemy(app)
from blog import views
from author import views
If you want to see the entire project then click here
In settings.py, try replacing
SQLALCHEMY_DB_URI = DB_URI
with
SQLALCHEMY_DATABASE_URI = DB_URI
And DB_URI = 'mysql+pymysql://root#127.0.0.1:blog'
with
DB_URI = 'mysql+pymysql://root#127.0.0.1/blog'
according to connection URI format:
dialect+driver://username:password#host:port/database
I've already configured SECURITY_PASSWORD_SALT and use "bcrypt". But every time i reload the page print encrypt_password('mypassword') will print different values, so i can't verify user input password via verify_password(form.password.data, user.password).
But I can login from flask-security built-in login view
Here is code to demonstrate the strange behavior of encrypt_password:
from flask import Flask, render_template
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import Security, SQLAlchemyUserDatastore, \
UserMixin, RoleMixin, login_required
from flask.ext.security.utils import encrypt_password, verify_password
# Create app
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.config['SECURITY_PASSWORD_HASH'] = 'sha512_crypt'
app.config['SECURITY_PASSWORD_SALT'] = 'fhasdgihwntlgy8f'
# 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():
password = encrypt_password('mypassword')
print verify_password('mypassword', password)
return password
# return render_template('index.html')
if __name__ == '__main__':
app.run()
The fact that encrypt_password() generates a new value is by design. The fact that verify_password() fails is not. It's an already reported bug in Flask-Security.
When you use the login view, a different method, verify_and_update_password() is used instead, which doesn't suffer from the same problem.
The fix is not yet part of a new release. You can fix this issue yourself by applying the change from PR #223; it replaces the verify_password() function in the flask_security/utils.py file with:
def verify_password(password, password_hash):
"""Returns ``True`` if the password matches the supplied hash.
:param password: A plaintext password to verify
:param password_hash: The expected hash value of the password (usually form your database)
"""
if _security.password_hash != 'plaintext':
password = get_hmac(password)
return _pwd_context.verify(password, password_hash)
e.g. first hash the password with HMAC+SHA512 before verifying it against the hash, just as the original encrypt_password() does, and not apply encrypt_password() as the current released version does.