Flask_User and Blueprint - how to structure the app? - python

I am creating my first larger Python Flask app and one of the modules I will be using is Flask_User. To make all a bit more manageable I wanted to impost a blueprint and factory app.
The structure is simple:
app.py
config.py
auth/
__init__.py
models.py
So the goal is to have all authentication stuff in auth section. However, it comes down to where to initialize UserManager not to have errors, circular references, etc.
user_manager = UserManager(app, db, User)
How code looks like - app.py
from flask import Flask
# Configuration
from config import Config, HomeDevelopmentConfig
# Extensions
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
# INITIATE DB
db = SQLAlchemy()
migrate = Migrate() # this will run SQLAlchemy as well
from flask_user import UserManager
from auth.models import User
user_manager = UserManager()
# APPLICATION FACTORY
def create_app(config_class=Config):
# Create app object
app = Flask(__name__)
# Load configuration
app.config.from_object(config_class)
# Register extensions
db.init_app(app)
migrate.init_app(app, db)
user_manager = UserManager(app, db, User) # !!! this will not work - circular reference to User as User needs db
# Close session with DB at the app shut down
#app.teardown_request
def shutdown_session(exception=None):
db.session.remove()
with app.app_context():
# Register blueprints
# Main flask app
from main import bp_main
app.register_blueprint(bp_main)
# Authentication (flask-user)
from auth import bp_auth
app.register_blueprint(bp_auth)
return app
# RUN APPLICATION
if __name__ == '__main__':
app = create_app(HomeDevelopmentConfig)
app.run()
models.py (should contain data structure for authentication)
from app import db
from flask_user import UserMixin #, UserManager
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), nullable=False, unique=True)
password = db.Column(db.String(255), nullable=False, server_default='')
first_name = db.Column(db.String(100), nullable=False, server_default='')
last_name = db.Column(db.String(100), nullable=False, server_default='')
email_confirmed_at = db.Column(db.DateTime())
active = db.Column('is_active', db.Boolean(), nullable=False, server_default='0')
# Define the relationship to Role via UserRoles
roles = db.relationship('Role', secondary='user_roles')
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), unique=True)
class UserRoles(db.Model, UserMixin):
__tablename__ = 'user_roles'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))
and finally init.py for authentication:
from flask import blueprints
bp_auth = blueprints.Blueprint('auth', __name__)
from auth import models
Looks like mission impossible, but maybe you have some good hints. What works is to put all the code into app.py, but that make

Related

Trying to drop all tables in flask, RuntimeError: No application found. Either work inside a view function or push an application context

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.

Schedule SQLAlchemy to clear all rows from a table

I'm trying to create a function that can be scheduled to delete all rows within an SQLAlchemy model.
I'm trying to use apscheduler to accomplish this task. But I keep getting an error that says:
sqlalchemy.orm.exc.UnmappedInstanceError: Class 'flask_sqlalchemy.model.DefaultMeta' is
not mapped; was a class (app.models.User) supplied where an instance was required?
Am I missing something?
Here is my app/__init__.py:
from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from config import Config
app = Flask(__name__)
db = SQLAlchemy()
login = LoginManager()
app.config.from_object(Config)
db.init_app(app)
login.init_app(app)
login.login_view = 'login'
from app import routes, models
and here is my manage.py:
from apscheduler.schedulers.background import BackgroundScheduler
from app import app, db
from flask_migrate import Migrate
from flask_script import Manager
from app.models import User
manager = Manager(app)
migrate = Migrate(app, db)
def clear_data():
db.session.delete(User)
print("Deleted User table!")
#manager.command
def run():
scheduler = BackgroundScheduler()
scheduler.add_job(clear_data, trigger='interval', seconds=5)
scheduler.start()
app.run(debug=True)
if __name__ == '__main__':
manager.run()
Also, here's my model:
from app import db, login
from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
api_token = db.Column(db.String(50), unique=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
todos = db.relationship('Todo', backref='owner', lazy='dynamic')
def __repr__(self):
return '<models.py {}>'.format(self.username)
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)
#login.user_loader
def load_user(id):
return User.query.get(int(id))
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Todo {}>'.format(self.body)
As your error indicated, it is expecting an instances of an object, and you instead passed it a class. I think the issue is the first line in the clear_data function:
db.session.delete(User)
It was expecting an instances of a User record to delete, and doesn't know how to delete the whole table using just the model.
Check out this answer on how to delete all rows in a table. There are a few ways to do this, but this may be the least change for you:
db.session.query(User).delete()
In this case you are adding the step of SELECTing all the records in the table User maps to, then deleting them.
P.S.: as mentioned in the linked answer, you need to .commit() your session, otherwise it won't stick, and will rollback after you close the connection.
db.session.commit()
Code snippet:
db.session.query(model_name).delete()
db.session.commit()

Create all tables with Flask-SQLAlchemy

I have some difficulties with the library lask-sqlalchemy, I tried to create my database with the code bellow, i have no error but no table created.
_init.py
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
app.config.from_json("C:/Users/lquastana/PycharmProjects/flask_demo/conf/api-config.json")
app.app_context().push()
db.py
from flask_sqlalchemy import SQLAlchemy
from api_lab import app
db = SQLAlchemy(app)
member.py
from api_lab.models.db import db
class Member(db.Model):
__table_args__ = {"schema": "public"}
id = db.Column(db.BigInteger,
primary_key=True,
autoincrement=True)
id_company = db.Column(db.BigInteger,
db.ForeignKey("compagny.id"),
index=True,
nullable=False)
nom = db.Column(db.String(80), unique=True, nullable=False)
age = db.Column(db.Integer, unique=True, nullable=False)
def __repr__(self):
return '<Member %r>' % self.nom
run_api.py
from api_lab import app
from api_lab.models.db import db
def main():
host = app.config["ENV"]["HOST"]
port = app.config["ENV"]["PORT"]
debug = app.config["ENV"]["DEBUG"]
app.run(host=host, port=port, debug= debug)
if __name__ == '__main__':
db.create_all()
#main()
You should put all initialization related logic into init.py
Use SQLAlchemy to setup a db connection with your app.config
init.py
from api_lab import app
from api_lab.models.db import db
from flask_sqlalchemy import SQLAlchemy
app.config.from_json("C:/Users/lquastana/PycharmProjects/flask_demo/conf/api-config.json")
app.app_context().push()
db = SQLAlchemy(app)
# initialize the DB
db.create_all()

Using proper file structure with SQLAlchemy and how to add data to db

I am trying to build a simple blogging platform to learn Python and Flask. I am using SQLAlchemy to connect to a Postgres db hosted on Heroku and flask_s3 to serve static files from an AWS bucket. Im mostly following along from this:
https://gist.github.com/mayukh18/2223bc8fc152631205abd7cbf1efdd41/
All was going well, it is correctly hosted on Heroku and connceted to AWS S3 bucket, ready to go. I am stuck however on how to add blog posts to the database through some kind of form or route that will allow me to fill in the attributes of a blog post (found in Post in models.py below)
I have html templates that get rendered and the following three files, app.py, manage.py and models.py.
app.py:
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_s3 import FlaskS3
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = '*URI for DB hosted on heroku'
app.config['FLASKS3_BUCKET_NAME'] = 'my S3 bucket on AWS'
db = SQLAlchemy(app)
s3 = FlaskS3(app)
from models import Post
#routes to templates to be rendered
if __name__ == '__main__'
app.run(debug=True)
manage.py:
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from app import app, db
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
and models.py:
from manage import db,app
class Post(db.Model):
__tablename__ = 'blogposts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), index=True, unique=True)
content = db.Column(db.Text, index=True, unique=True)
date = db.Column(db.DateTime, index=True, unique=True)
tag = db.Column(db.String(120), index=True, unique=True)
cover = db.Column(db.String(120), index=True, unique=True)
def __repr__(self):
return '<Post: %r>' % (self.title)
my file strucure is:
-blog
--__pycache__
--migrations
--static
--templates
app.py
manage.py
models.py
Pipfile
Pipfile.lock
Procfile
I want to work on this locally (before I publish anything to Heroku) but have no idea what to do from here. Anyone have advice on how to go about creating a route to add blog posts and saving them to a local Postgres instance before I go about pushing finalized blog posts to Heroku?
the gist I have been following has something like this as a route:
#app.route('/add/')
def webhook():
#post attributes defined
p = Post(id = id, title = title, date = datetime.datetime.utcnow, content = content, tag = tag, cover = cover)
print("post created", p)
db.session.add(p)
db.session.commit()
return "post created"
when I try and run it locally I get the following error so I'm not sure I have the files connecting properly.
File "/Users/Mb/Desktop/datadude/app.py", line 15, in <module>
from models import Post
ImportError: cannot import name 'Post'
Sorry if this is the wrong place for this. If there is a better place to ask for advice let me know.
The problem exists in circular dependencies.
You could move initializing of SQLAlchemy to models.py. Then only run method init_app on db object in app.py.
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Post(db.Model):
__tablename__ = 'blogposts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), index=True, unique=True)
content = db.Column(db.Text, index=True, unique=True)
date = db.Column(db.DateTime, index=True, unique=True)
tag = db.Column(db.String(120), index=True, unique=True)
cover = db.Column(db.String(120), index=True, unique=True)
def __repr__(self):
return '<Post: %r>' % (self.title)
app.py
import datetime
from flask import Flask, render_template
from flask_s3 import FlaskS3
from models import db
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = '*URI for DB hosted on heroku'
app.config['FLASKS3_BUCKET_NAME'] = 'my S3 bucket on AWS'
db.init_app(app)
s3 = FlaskS3(app)
from models import Post
#app.route('/add/')
def webhook():
#post attributes defined
p = Post(id = id, title = title, date = datetime.datetime.utcnow, content = content, tag = tag, cover = cover)
print("post created", p)
db.session.add(p)
db.session.commit()
return "post created"
#routes to templates to be rendered
if __name__ == '__main__':
app.run(debug=True)
You can read more about it https://github.com/slezica/bleg/blob/master/data/posts/2014-03-08-avoiding-circular-dependencies-in-flask.md

Flask-Security: how to set up the database outside the main py file?

I'm using Flask-Security 1.7.4 together with Flask 0.10.1.
I have no problem running my website if the database scheme is stored in the python file app.py:
from flask.ext.security import RoleMixin, UserMixin, SQLAlchemyUserDatastore, Security
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
#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)
companyid = db.Column(db.String(255), unique=True)
lastname = db.Column(db.String(255))
firstname = db.Column(db.String(255))
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)
However I would like to separate more my code so I can have:
app_folder
|__ app.py # Main file for the app
|__ security.py # File that contain the database for the login/roles
So I tried:
in security.py:
db = SQLAlchemy()
# 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)
companyid = db.Column(db.String(255), unique=True)
lastname = db.Column(db.String(255))
firstname = db.Column(db.String(255))
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)
And in app.py:
from flask.ext.security import RoleMixin, UserMixin, SQLAlchemyUserDatastore, Security
from flask.ext.sqlalchemy import SQLAlchemy
from security import db, user_datastore
app = Flask(__name__)
#Initiate the database connection object
db.init_app(app)
# Setup Flask-Security
security = Security(app, user_datastore)
And I'm getting the following errors:
File "C:\Work\pythonVirtualEnv\env02\lib\site-packages\sqlalchemy\engine\default.py", line 436, in do_execute
cursor.execute(statement, parameters)
OperationalError: (OperationalError) no such table: users u'SELECT users.user_id AS users_user_id, users.username AS users_username, users.password AS users_password, users.email AS users_email, users.registered_on AS users_registered_on \nFROM users \nWHERE users.password = ? AND users.email = ?\n LIMIT ? OFFSET ?' (u'test', u'demo#demo.com', 1, 0)
The things is, if I keep all the code in app.py it works flawlessly.
And of course the database to exists.
Can you please tell me what I'm doing wrong?
Thanks :)
You should use a different structure (and the advised one):
proj_folder
- run.py
- config.py
|__ app # Main folder for the app
|__ __init__.py
|__ models.py
|__ security.py
|__ views.py
On models you keep your app models
On security you keep your security models
On views you create your views and expose your urls
Then on init.py
import logging
from flask import Flask
from flask.ext.appbuilder import SQLA
from flask.ext.appbuilder import AppBuilder
from sqlalchemy import SQLAlchemy
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(name)s:%(message)s')
logging.getLogger().setLevel(logging.DEBUG)
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
from app import models, security, views
On run.py
from app import app
app.run(host='0.0.0.0', port=8080, debug=True)
Hope this helps
Take a look at https://github.com/dpgaspar/Flask-AppBuilder-Skeleton

Categories

Resources