using Flask-Migrate together with Flask-Security - python

I'm trying to get a basic Flask-Security app working with Flask-Migrate. I have two main py files: app.py and db_migrate.py
app.py:
from flask import Flask, render_template, request, session
from flask.ext.babel import Babel
from flask.ext.mail import Mail
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin
import os
basedir = os.path.abspath(os.path.dirname(__file__)) #should be __ file __ with no spaces
# Create app
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'super-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'app.db')
app.config['DEFAULT_MAIL_SENDER'] = 'info#site.com'
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_CONFIRMABLE'] = True
app.config['SECURITY_RECOVERABLE'] = True
app.config.from_object('config.email')
# Setup mail extension
mail = Mail(app)
# Setup babel
babel = Babel(app)
#babel.localeselector
def get_locale():
override = request.args.get('lang')
if override:
session['lang'] = override
rv = session.get('lang', 'en')
return rv
# Create database connection object
db = SQLAlchemy(app)
# Setup Flask-Security
from db_manager import User, Role #THIS IS PROBABLY WRONG!
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
#db.create_all()
# Views
#app.route('/')
def home():
return render_template('index.html')
if __name__ == '__main__':
app.run()
db_migrate.py:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin
import os
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'app.db')
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
# 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())
favcolor = db.Column(db.String(255))
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
def __str__(self):
return '<User id=%s email=%s>' % (self.id, self.email)
if __name__ == '__main__':
manager.run()
I have run the migration tool to initialize and migrate the db once, to create a new db, and it worked:
python db_manager.py db init
python db_manager.py db migrate
I tried to run app.py. It serves correctly on localhost, but then when I try to log a user in, I get the following OperationalError:
OperationalError: (OperationalError) no such table: user u'SELECT user.id AS user_id, user.email AS user_email, user.password AS user_password, user.active AS user_active, user.confirmed_at AS user_confirmed_at, user.favcolor AS user_favcolor \nFROM user \nWHERE lower(user.email) LIKE lower(?)\n LIMIT ? OFFSET ?' (u'xxx#xxx.com', 1, 0)
Basically, I doubt that I'm creating user_datastore and security correctly, as I probably shouldn't be importing User and Role in that way -- but I'm not sure how to access them properly.
EDIT:
I added this final command, thanks to suggestion:
python db_manager.py db ugrade
But, now I get this error when I try to confirm a user registration via email:
(InvalidRequestError: Object '' is already attached to session '1' (this is '3')

The workflow with Flask-Migrate/Alembic is as follows:
db init
This you do once when you create the migration repository and never again.
db migrate
You run this to generate a migration script. The output of the command tells you where the migration script was created, and shows a summary of what was put in it. Your database has not been modified at this stage.
review the migration script
This is very important. Automatic migrations are not perfect, you have to review the generated script and make any corrections that are necessary.
db upgrade
This applies the migration to your database, effectively making the necessary schema changes.
You can now use your database. When you make more changes to your models return to Step 2 and repeat the cycle.
From your description you maybe missed step 4, the upgrade call.
As a side note, you have some duplication between your two scripts, you should try to consolidate them. Take a look at how people build Flask applications split across multiple modules or packages.

Related

db.create_all() not generating db

I'm trying to test Flask with SQLAlchemy and I stumbeld accross this problem. First, I have to note that I read all of the related threads and none of them solves my problem. I have a problem that db.create_all() doesn't generate the table I defined. I have model class in file person.py:
from website import db
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, nullable=False)
password = db.Column(db.String)
width = db.Column(db.Integer)
height = db.Column(db.Integer)
agent = db.Column(db.String)
user_data_dir = db.Column(db.String)
And in my website.py which is the file from where I launch the app:
from flask import Flask, jsonify, render_template, request
from flask_sqlalchemy import SQLAlchemy
# create the extension
db = SQLAlchemy()
def start_server(host, port, debug=False):
from person import Person
# create the app
app = Flask(__name__,
static_url_path='',
static_folder='web/static',
template_folder='web/templates')
# configure the SQLite database, relative to the app instance folder
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database0.db"
# initialize the app with the extension
db.init_app(app)
print('initialized db')
print('creating tables...')
with app.app_context():
db.create_all()
db.session.add(Person(username="example33"))
db.session.commit()
person = db.session.execute(db.select(Person)).scalar()
print('persons')
print(person.username)
if __name__ == '__main__':
start_server(host='0.0.0.0', port=5002, debug=True)
I think the problem might be that the Person class is not importing properly, because when I put the class inside the start_server function it executes fine and creates the table, but I don't know why this is happening. I followed all the advice and imported it before everything, and also I share the same db object between the 2 files
There is probably a better way to do this but this is the only way I could get this to work. You need to create a models.py file or w.e you wanna call it. Then all your database stuff goes in there. The db engine, ALL your models and a function to initialize it all. The reason is, you are having import issues where Person is imported but not fully and so the db doesn't have it in its metadata.
models.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, nullable=False)
password = db.Column(db.String)
width = db.Column(db.Integer)
height = db.Column(db.Integer)
agent = db.Column(db.String)
user_data_dir = db.Column(db.String)
# All other models
def initialize_db(app: Flask):
db.init_app(app)
with app.app_context():
db.create_all()
main.py
from flask import Flask
import models
def start_server(host, port, debug=False):
app = Flask(__name__)
# configure the SQLite database, relative to the app instance folder
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database0.db"
# initialize the app with the extension
models.initialize_db(app)
db = models.db
with app.app_context():
db.session.add(models.Person(username="example33"))
db.session.commit()
person = db.session.execute(db.select(models.Person)).scalar()
print('persons')
print(person.username)
if __name__ == '__main__':
start_server(host='0.0.0.0', port=5002, debug=True)
I am reading the documentation,
which explains that the function will
Create all tables stored in this metadata.
That leads me to believe Person is not associated with the db metadata.
You mentioned
when I put the class inside the start_server function it ... creates the table
Your from person import Person is nice enough,
but I suspect we wanted a simple import person.
In many apps the idiom would be import models.
Failing that, you may be able to point
create_all in the right direction
with this optional parameter:
tables – Optional list of Table objects, which is a subset of the total tables in the MetaData
Please let us know
what technical approach worked for you.

RuntimeError: No application found. Either work inside a view function or push an application context. FLASK SQLAlchemy error

application.py
from flask import Flask, render_template
from wtform_fields import *
from models import *
app = Flask(__name__)
app.secret_key='REPLACE LATER'
app.config['SQLALCHEMY_DATABASE_URI']='*db link*'
db = SQLAlchemy(app)
#app.route("/", methods=['GET', 'POST'])
def index():
reg_form = RegistrationForm()
# Update database if validation success
if reg_form.validate_on_submit():
username = reg_form.username.data
password = reg_form.password.data
user_object = User.query.filter_by(username=username).first()
if user_object:
return "Already Taken"
user =User(username=username,password=password)
db.session.add(user)
db.session.commit()
return "Inserted into DB"
return render_template("index.html", form=reg_form)
if __name__ == "__main__":
app.run(debug=True)
models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model):
""" User model """
__tablename__="users"
id=db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(25) , unique=True, nullable=False)
password = db.Column(db.String(), nullable=False)
db.create_all()
I am getting this error:
D:\PostgreSQL\12\bin\GSTChat\venv\lib\site-packages\flask_sqlalchemy\__init__.py", line 1094, in create_all
self._execute_for_all_tables(app, bind, 'create_all')
File "D:\PostgreSQL\12\bin\GSTChat\venv\lib\site-packages\flask_sqlalchemy\__init__.py", line 1071, in _execute_for_all_tables
app = self.get_app(app)
File "D:\PostgreSQL\12\bin\GSTChat\venv\lib\site-packages\flask_sqlalchemy\__init__.py", line 1042, in get_app
raise RuntimeError(
**RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.**
I even looked for solution in the documentation but , I am not able to resolve the error.
EDIT: Adding another answer...
You can have it as you already do in the models.py file, and initialize it later in your application file:
application.py
from flask import Flask, render_template
from wtform_fields import *
from models import *
app = Flask(__name__)
# Other code you have
db.init_app(app)
Answer 2: I see you are defining db in your application.py file, and again in your models.py file. You should import it from your app in your models file.
models.py
from flask_sqlalchemy import SQLAlchemy
from app import db
class User(db.Model):
""" User model """
__tablename__="users"
id=db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(25) , unique=True, nullable=False)
password = db.Column(db.String(), nullable=False)
I hope this is helpful and solves your error! :) You may also want to put the db.create_all() in your application.py file (after importing models), instead of in a model itself. Cheers.

How can I create a table if not exist on Flask with SQLAlchemy?

I am using SQLAlchemy and I have the following code:
Model:
class User(db.Model):
__tablename__ = 'user'
__table_args__ = {'schema': 'task', 'useexisting': True}
id = Column(Integer, primary_key=True, autoincrement=True)
firstname = Column(String)
.env
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
app.py
def create_app(config_file):
"""Create a Flask application using the app factory pattern."""
app = Flask(__name__)
"""Load configuration."""
app.config.from_pyfile(config_file)
"""Init app extensions."""
from .extensions import db
db.init_app(app)
This creates the SQLite file if it does not exist, but not the tables of each model.
The question is what can I do in order to create the tables for each model?
Just add:
db.create_all()
in app.py at the end of create_app().
create_all() will create the tables only when they don't exist and would not change the tables created before.
If you want to create the database and the tables from the command line you can just type:
python
from app.py import db
db.create_all()
exit()
The working example:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.secret_key = "Secret key"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///my_database.sqlite3"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
class Data(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(50))
email = db.Column(db.String(50))
phone = db.Column(db.String(50))
db.create_all()
# add a row
# comment out after the 1st run
table_row = Data(name="My Name", email="myemail#mail.com", phone="123456")
db.session.add(table_row)
db.session.commit()
print "A row was added to the table"
# read the data
row = Data.query.filter_by(name="My Name").first()
print "Found:", row.email, row.phone
if __name__ == "__main__":
app.run(debug=True)
This is for Python 2.7, to run with Python 3.x just change the the print statements to call the print() function.
NOTE:
When using automatic model class constructor the arguments passed to model class constructor must be keyword arguments or there will be an error. Otherwise you can override the __init__() inside Data() class like this:
def __init__(self, name, email, phone, **kwargs):
super(Data, self).__init__(**kwargs)
self.name = name
self.email = email
self.phone = phone
In that case you don't have to use keyword arguments.
you need first to use Shell Context Processor to load automatically all Model objects
in app.py add
# import all models from all blueprints you have
from .users.models import User
#app.shell_context_processor
def make_shell_context():
return { 'db': db, 'User': User .. }
and then use Flask shell command
(venv) $ flask shell
>>> db
<SQLAlchemy engine=sqlite:///data-dev.sqlite> # something similar to that
>>>
>>> User
<class 'api.users.models.User'>
>>>
>>> # to create database if not exists and all tables, run the command below
>>> db.create_all()
maybe you'll need Flask-Migrate for advanced operations (migrations) on your database: create new table, update tables / fields ...

Why does my database get reset after each deployment?

I am new to deploying to aws as well as flask. Basically I have a simple Flask app but every time I make a change and deploy the new code to aws elastic beanstalk, the db gets reset.
from dateutil import parser
from datetime import datetime
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask_cors import CORS
import os
application = app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'db.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
ma = Marshmallow(app)
cors = CORS(app)
#app.route('/')
def hello():
return 'hello'
.
.
.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
weight = db.Column(db.Float)
workouts = db.relationship('Workout', backref='user', lazy=True)
def __init__(self, name, weight):
self.name = name
self.weight = weight
class UserSchema(ma.Schema):
class Meta:
fields = ('id', 'name', 'weight')
user_schema = UserSchema(strict=True)
users_schema = UserSchema(many=True, strict=True)
.
.
.
db.create_all()
if __name__ == '__main__':
app.run(debug=True)
I expect that each time I
eb deploy flask-env my db wouldnt get reset but it does. For instance if i create a user and then later change something in the code and deploy, that user is gone.
You should:
Create an EBS Volume
Load the database into that volume
Attach the EBS Volume to your Beanstalk app with an .ebextension. An example can be found here.
With all that being said, this is not a highly available, well architected solution and will deteriorate at scale rapidly.
You will want to replace SQLite with an RDS instance at some point in the future before it becomes a problem.

Where is app.sqlite located?

Hi guys I've been following this old tutorial. Can someone tell me where app.sqlite will be located. Also is app.sqlite a database that I have made I'm not sure because I wanna wanna find it and checked if the table is made. Also is there a wayto change the location to where i want it :D
This is the config file.
import os
basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.sqlite')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
And this is the file where I try to make RESTful API, I haven't put anything in yet though since I'm lost.
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_pyfile('Config.py')
db = SQLAlchemy(app)
class User(db.Model):
User_ID = db.Column(db.Integer, primary_key = True)
firstName = db.Column(db.String(20))
lastName = db.Column(db.String(20))
def __init__(self,firstName, lastName):
self.firstName = firstName
self.lastName = lastName
db.createAll()
if __name__ == '__main__':
app.run()
You can place app.sqlite in the same directory as your main python file. To create all of the tables, you can add the following method to your code:
#app.before_first_request
def startup():
db.create_all()
This will run when you start your server and will create all of the tables. If you restart the server, the method will still run, but the tables won't be remade since they already exist.

Categories