I'm studying now main concepts of flask and flask-sqlalchemy. Keeping in my mind info from the tutorial (intro and contexts) I'm trying to create a simple database.
My Flask app is strucured as follows:
./db/
./db/models.py
./main.py
The contents of the files is as follows:
./main.py:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object(__name__)
from db.models import create_app
dbapp = create_app()
with dbapp.test_request_context():
from db.models import db, mumsUsers
db.create_all()
db.session.rollback()
admin = mumsUsers("admin", "admin#example.com")
db.session.add(admin)
db.session.commit()
./db/models.py:
from flask.ext.sqlalchemy import SQLAlchemy
from flask import Flask, current_app
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db.init_app(app)
return(app)
class mumsUsers(db.Model):
__tablename__ = 'mumsUsers'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(80), unique=True)
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return '<User %r>' % self.username
When I check the sqlite database, I see that sqlalchemy is trying to send commit() twice. So I have to remove the unique=True parameter in order to stop application crash. Meantime when I run the following commans from the python shell:
admin = mumsUsers('admin', 'admin#example.com')
db.session.add(admin)
db.session.commit()
only one record appears (as it is expected).
Therefore my question is how to prevent double call for commit()?
Update
The appeared problem has been caused by my fault, while making looped imports. Indeed I didn't notice I called import for the application package.
Therefore please ignore this post.
The caused problem has been related to looped imports.
Please check what you import before asking.
Related
I need to ensure transactionality with flask requests. Is there a way to store a database session per request and use that one?
The easiest way is to create a simple extension for session initialization and commit changes after successful request. Here is an example:
from datetime import datetime
from flask import Flask, current_app, _app_ctx_stack
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base, DeferredReflection
from sqlalchemy import Column, String, Integer
# Base model without connection(see: DBExtension.connect()) and a few demo models
Base = declarative_base(cls=DeferredReflection)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String())
class Log(Base):
__tablename__ = 'log'
id = Column(Integer, primary_key=True, autoincrement=True)
text = Column(String())
# extension for getting db session
class DBExtension:
def __init__(self, app: Flask = None):
self.app = app
if app is not None:
self.init_app(app)
def init_app(self, app):
app.teardown_appcontext(self.teardown)
def connect(self):
# init engine and set into metadata
engine = create_engine(current_app.config['DB_CONNECTION'], echo=True)
Session = sessionmaker(bind=engine)
session = Session()
# create_all - just for demo(init structure...)
Base.metadata.create_all(engine)
Base.metadata.bind = engine
Base.prepare(engine)
return session
def teardown(self, exception):
ctx = _app_ctx_stack.top
if hasattr(ctx, 'session'):
ctx.session.close()
#property
def session(self):
# connect or get active connection
ctx = _app_ctx_stack.top
if ctx is not None:
if not hasattr(ctx, 'session'):
ctx.session = self.connect()
return ctx.session
app = Flask(__name__)
app.config.update({'DB_CONNECTION': 'sqlite:///test.db'}) # sqlite for demo
db = DBExtension(app)
#app.teardown_request
def teardown_request_func(error=None):
# request processing completed(before requests functions, endpoints and other logic)
if not error:
# one more record and commit all changes in one transaction
db.session.add(Log(text='{dt}. teardown_request'.format(dt=datetime.utcnow())))
try:
db.session.commit()
except Exception as e:
print(e)
# do something
#app.before_request
def before_request_func():
# add a new record before request
db.session.add(Log(text='{dt}. before request'.format(dt=datetime.utcnow())))
#app.route('/user/<name>')
def user_route(name: str):
# do something with db session
db.session.add(Log(text='saving user {user}'.format(user=name)))
user = User(name=name)
db.session.add(user)
# session.add(newModel) or session.delete(oneMoreModel) etc.
return 'done' # return without commit. see: teardown_request_func
if __name__ == '__main__':
app.run(host='localhost', port=5000, debug=True)
Run server, open http://localhost:5000/user/DavidBoshton and see logs(BEGIN-...-COMMIT).
can anyone tell me, how to make a separate Database just for Flask admin and maybe flask security?
this is how i am working with my PostgreSQL database just to load some tables and perform CRUD:
import flask_admin
from flask import Flask
from sqlalchemy import create_engine
app =Flask(__name__)
engine = create_engine{'postgresql://name:password#localhost/tablesdatabase')
i wish to make a separate database for flask-admin :( this is what i am trying)
admin = flask_admin.Admin(app)
app.config['SQLALCHEMY_BINDS'] = {'admindb' : 'postgresql://name:password#localhost/adminedatabase'}
admin.add_view(ModelView) ? // how can i impelement this ? with a seperate datbase ?
There is no official support, but you can customize Flask-SQLalchemy session to use different connections, here the example for using master slave connections, you can easily add as many connections as you want
from functools import partial
from sqlalchemy import orm
from flask import current_app
from flask_sqlalchemy import SQLAlchemy, get_state
class RoutingSession(orm.Session):
def __init__(self, db, autocommit=False, autoflush=True, **options):
self.app = db.get_app()
self.db = db
self._bind_name = None
orm.Session.__init__(
self, autocommit=autocommit, autoflush=autoflush,
bind=db.engine,
binds=db.get_binds(self.app),
**options,
)
def get_bind(self, mapper=None, clause=None):
try:
state = get_state(self.app)
except (AssertionError, AttributeError, TypeError) as err:
current_app.logger.info(
'cant get configuration. default bind. Error:' + err)
return orm.Session.get_bind(self, mapper, clause)
# If there are no binds configured, use default SQLALCHEMY_DATABASE_URI
if not state or not self.app.config['SQLALCHEMY_BINDS']:
return orm.Session.get_bind(self, mapper, clause)
# if want to user exact bind
if self._bind_name:
return state.db.get_engine(self.app, bind=self._bind_name)
else:
# if no bind is used connect to default
return orm.Session.get_bind(self, mapper, clause)
def using_bind(self, name):
bind_session = RoutingSession(self.db)
vars(bind_session).update(vars(self))
bind_session._bind_name = name
return bind_session
class RouteSQLAlchemy(SQLAlchemy):
def __init__(self, *args, **kwargs):
SQLAlchemy.__init__(self, *args, **kwargs)
self.session.using_bind = lambda s: self.session().using_bind(s)
def create_scoped_session(self, options=None):
if options is None:
options = {}
scopefunc = options.pop('scopefunc', None)
return orm.scoped_session(
partial(RoutingSession, self, **options),
scopefunc=scopefunc,
)
Than the default session will be master, when you want to select from slave you can call it directly, here the examples:
In your app:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql:///master'
app.config['SQLALCHEMY_BINDS'] = {
'slave': 'postgresql:///slave'
}
db = RouteSQLAlchemy(app)
Select from master
session.query(User).filter_by(id=1).first()
Select from slave
session.using_bind('slave').query(User).filter_by(id=1).first()
I have successfully followed the examples in the documentation of Flask-Dance to add Oauth from GitHub, and manage the users with SQLAlchemy. However, I am unable to add more providers. I tried to simply add another blueprint for Twitter and registering it, but I get various errors when trying to login with Twitter. Also, I end up with lots of duplicated code, so this is not a good approach.
Is there a better way to add another provider?
import sys
from flask import Flask, redirect, url_for, flash, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm.exc import NoResultFound
from flask_dance.contrib.github import make_github_blueprint, github
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin, SQLAlchemyBackend
from flask_dance.consumer import oauth_authorized, oauth_error
from flask_login import (
LoginManager, UserMixin, current_user,
login_required, login_user, logout_user
)
# setup Flask application
app = Flask(__name__)
app.secret_key = "supersekrit"
blueprint = make_github_blueprint(
client_id="my-key-here",
client_secret="my-secret-here",
)
app.register_blueprint(blueprint, url_prefix="/login")
# setup database models
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///multi.db"
db = SQLAlchemy()
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(256), unique=True)
# ... other columns as needed
class OAuth(db.Model, OAuthConsumerMixin):
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
user = db.relationship(User)
# setup login manager
login_manager = LoginManager()
login_manager.login_view = 'github.login'
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# setup SQLAlchemy backend
blueprint.backend = SQLAlchemyBackend(OAuth, db.session, user=current_user)
# create/login local user on successful OAuth login
#oauth_authorized.connect_via(blueprint)
def github_logged_in(blueprint, token):
if not token:
flash("Failed to log in with {name}".format(name=blueprint.name))
return
# figure out who the user is
resp = blueprint.session.get("/user")
if resp.ok:
username = resp.json()["login"]
query = User.query.filter_by(username=username)
try:
user = query.one()
except NoResultFound:
# create a user
user = User(username=username)
db.session.add(user)
db.session.commit()
login_user(user)
flash("Successfully signed in with GitHub")
else:
msg = "Failed to fetch user info from {name}".format(name=blueprint.name)
flash(msg, category="error")
# notify on OAuth provider error
#oauth_error.connect_via(blueprint)
def github_error(blueprint, error, error_description=None, error_uri=None):
msg = (
"OAuth error from {name}! "
"error={error} description={description} uri={uri}"
).format(
name=blueprint.name,
error=error,
description=error_description,
uri=error_uri,
)
flash(msg, category="error")
#app.route("/logout")
#login_required
def logout():
logout_user()
flash("You have logged out")
return redirect(url_for("index"))
#app.route("/")
def index():
return render_template("home.html")
# hook up extensions to app
db.init_app(app)
login_manager.init_app(app)
if __name__ == "__main__":
if "--setup" in sys.argv:
with app.app_context():
db.create_all()
db.session.commit()
print("Database tables created")
else:
app.run(debug=True)
Here is a sample project that uses multiple providers simultaneously: flask-dance-multi-provider. You can read through the code to see how it works. Does that help?
I've created the tests folder, written my first test that should open a browser, point to a page and login, then go to home page.
Test run and fail, as expected, but I can't find out why.
browser should be available, pytest-selenium is installed by pip.
import pytest
from django.contrib.auth.models import Group, Permission, User
from django.test import TestCase, RequestFactory
class CreaPageTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_homepage(self):
request = self.client.get('/new')
request.user = self.user
self.assertEqual(request.status_code, 200)
def test_login(self):
request = self.client.get('/per/login')
username_field = self.browser.find_element_by_name('username')
username_field.send_keys('peppa')
password_field = self.browser.find_element_by_name('password')
password_field.send_keys('pig')
password_field.send_keys(Keys.RETURN)
test_homepage()
> username_field = self.browser.find_element_by_name('username')
E AttributeError: 'CreaPageTest' object has no attribute 'browser'
tests/test_ore_app_views.py:27: AttributeError
what am I missing?
Any advice to examples of this kind of test is really appreciated.
You should configure self.browser inside setUp function. You are also missing an import for Keys. Code should be like this.
import pytest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from django.contrib.auth.models import Group, Permission, User
from django.test import TestCase, RequestFactory
class CreaPageTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.browser = webdriver.Firefox()
Also please refer to the docs, here http://selenium-python.readthedocs.org/getting-started.html
I trying make app with is listing cities from pre-existing postgresql database.
So far I able connect to database and make a query, but results are returned u'string',
Here my models.py
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Woonplaats(Base):
__tablename__ = 'woonplaats'
gid = Column(Integer, primary_key=True)
woonplaatsnaam = Column(String(80))
def __init__(self, woonplaatsnaam=None):
self.woonplaatsnaam = woonplaatsnaam
def __repr__(self):
return '%r' % (self.woonplaatsnaam)
And views.py
from flask import render_template, request
from app import app
from app import db
from app.models import Woonplaats
#app.route('/citylist')
def citylist():
results = db.session.query(Woonplaats).all()
print(results) #only for test
return render_template('city.html', results = results)
In the absence of a __str__ (or __unicode__), a class's __repr__ will be used. When you print out the query set, you end up printing the __repr__ of the woonplaatsnaam column. To print out the value of the column instead, add a __str__ (or __unicode__) method.
def __str__(self):
return self.woonplaatsnaam
# for Python 2.x
def __unicode__(self):
return self.woonplaatsnaam