Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Filling the flask

806 views

Published on

Just because Flask is a micro-framework doesn’t mean we still don’t want to have great AAA (authentication, authorization, and accounting), email services, simplified database access/migrations and form handling. Lets look at some of the most common libraries used to build the powerful simplicity of Flask into a full featured web application without having to reinvent the wheel each time.

Published in: Technology
  • Be the first to comment

Filling the flask

  1. 1. FILLING THE FLASK Created by /Jason A Myers @jasonamyers
  2. 2. OUR EMPTY FLASK I have two files setup: flaskfilled/__init__.py and config.py
  3. 3. __INIT__.PY from flask import Flask from config import config def create_app(config_name): app = Flask(__name__) app.config.from_object(config[config_name]) return app
  4. 4. CONFIG.PY import os basedir = os.path.abspath(os.path.dirname(__file__)) class Config: SECRET_KEY = 'development key' ADMINS = frozenset(['jason@jasonamyers.com', ]) class DevelopmentConfig(Config): DEBUG = True config = { 'development': DevelopmentConfig,
  5. 5. MANAGEMENT COMMANDS
  6. 6. FLASK-SCRIPT Commands for running a development server, a customised Python shell, etc pip install flask-script
  7. 7. MANAGE.PY #! /usr/bin/env python import os from flask.ext.script import Manager from flaskfilled import create_app app = create_app(os.getenv('FLASK_CONFIG') or 'default') manager = Manager(app) if __name__ == '__main__': manager.run()
  8. 8. SHELL WITH CONTEXT from flask.ext.script import Shell def make_shell_context(): return dict(app=app) manager.add_command('shell', Shell(make_context=make_shell_context))
  9. 9. $ python manage.py usage: manage.py [-?] {runserver,shell} ... positional arguments: {runserver,shell} runserver Runs the Flask development server i.e. app.run() shell Runs a Python shell inside Flask application context. optional arguments: -?, --help show this help message and exit
  10. 10. $ python manage.py shell In [1]: app.config['DEBUG'] Out[1]: True
  11. 11. DATABASE ACCESS
  12. 12. FLASK-SQLALCHEMY Single wrapper for most of SQLAlchemy Preconfigured scope session Sessions are tied to the page lifecycle pip install flask-sqlalchemy
  13. 13. __INIT__.PY from flask.ext.sqlalchemy import SQLAlchemy db = SQLAlchemy() def create_app(config_name): app = Flask(__name__) app.config.from_object(config[config_name]) db.init_app(app) return app
  14. 14. CONFIG.PY class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/dev.db"
  15. 15. FLASKFILLED/MODELS.PY from flaskfilled import db class Cookie(db.Model): __tablename__ = 'cookies' cookie_id = db.Column(db.Integer(), primary_key=True) cookie_name = db.Column(db.String(50), index=True) cookie_recipe_url = db.Column(db.String(255)) quantity = db.Column(db.Integer())
  16. 16. MANAGE.PY from flask.ext.script import Command from flaskfilled import db from flaskfilled.models import Cookies def make_shell_context(): return dict(app=app, db=db) class DevDbInit(Command): '''Creates database tables from sqlalchemy models''' def __init__(self, db): self.db = db def run(self): self.db.create_all()
  17. 17. $ python manage.py db_init $ python manage.py shell In [1]: db.metadata.tables Out[1]: immutabledict({'cookies': Table('cookies', 'stuff')}) In [2]: from flaskfilled.models import Cookie
  18. 18. c = Cookie(cookie_name="Chocolate Chip", cookie_recipe_url="http://zenofthecookie.com/chocolatechip.html" quantity=2) db.session.add(c) db.session.commit()
  19. 19. MIGRATIONS
  20. 20. FLASK-MIGRATE Ties Alembic into flask-script! pip install flask-migrate
  21. 21. MANAGE.PY from flask.ext.migrate import Migrate, MigrateCommand migrate = Migrate(app, db) manager.add_command('db', MigrateCommand)
  22. 22. INITIALIZING ALEMBIC $ python manage.py db init
  23. 23. GENERATING A MIGRATION $ python manage.py db migrate -m "initial migration" INFO [alembic.migration] Context impl SQLiteImpl. INFO [alembic.migration] Will assume non-transactional DDL. Generating flask-filled/migrations/versions/586131216f6_initial_migration.p
  24. 24. RUNNING MIGRATIONS $ python manage.py db upgrade INFO [alembic.migration] Context impl SQLiteImpl. INFO [alembic.migration] Will assume non-transactional DDL. INFO [alembic.migration] Running upgrade -> 586131216f6, initial migration
  25. 25. USER AUTHENTICATION
  26. 26. FLASK-LOGIN Simplifies logging users in and out Secures view functions with decorators Protects session cookies pip install flask-login
  27. 27. FLASKFILLED/__INIT__.PY from flask.ext.login import LoginManager login_manager = LoginManager() def create_app(config_name): app = Flask(__name__) app.config.from_object(config[config_name]) db.init_app(app) login_manager.setup_app(app) from .main import main as main_blueprint app.register_blueprint(main_blueprint) from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth')
  28. 28. MODELS.PY from werkzeug.security import generate_password_hash, check_password_hash from flaskfilled import login_manager class User(db.Model, UserMixin): __tablename__ = 'users' id = db.Column(db.Integer(), primary_key=True) username = db.Column(db.String, primary_key=True) password = db.Column(db.String) authenticated = db.Column(db.Boolean, default=False)
  29. 29. USER MODEL REQUIRED METHODS PROVIDED BY USERMIXIN def is_active(self): return True def get_id(self): return self.id def is_authenticated(self): return self.authenticated def is_anonymous(self): return False
  30. 30. USER MODEL PASSWORD HANDLING @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password)
  31. 31. SETTING UP THE AUTH BLUEPRINT AUTH/__INIT__.PY from flask import Blueprint auth = Blueprint('auth', __name__) from . import views
  32. 32. AUTH/VIEWS.PY from flask import render_template, redirect, request, url_for, flash from flask.ext.login import login_user, logout_user, login_required from . import auth from flaskfilled.models import User
  33. 33. LOGIN @auth.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form.get('username', '') password = request.form.get('password', '') user = User.query.filter_by(username=username).first() if user is not None and user.verify_password(password): login_user(user) next = request.args.get('next') return redirect(next or url_for('main.index')) else: flash('Wrong username or password.') return render_template('auth/login.html')
  34. 34. LOGOUT @auth.route('/logout') @login_required def logout(): logout_user() flash('You have been logged out.') return redirect(url_for('main.index'))
  35. 35. LOGIN TEMPLATE {% extends "base.html" %} {% block title %}Login{% endblock %} {% block page_content %} <div class="page-header"> <h1>Login</h1> </div> <div class="col-md-4"> <form action=""> Username: <input type="text" name="username"><br> Password: <input type="password" name="password"><br> <input type="submit"> </form> <br> <p>Forgot your password? <a href="{{ url_for('auth.password_reset_request <p>New user? <a href="{{ url_for('auth.register') }}">Click here to regis
  36. 36. MAIN/VIEWS.PY from flask import render_template from . import main @main.route('/', methods=['GET']) def index(): return render_template('main/index.html')
  37. 37. INDEX TEMPLATE {% extends "base.html" %} {% block title %}The Index{% endblock %} {% block page_content %} {% if not current_user.is_authenticated() %} <p><a href="{{ url_for('auth.login') }}">Click here to login</a>.</p {% else %} <p><a href="{{ url_for('auth.logout') }}">Click here to logout</a>.</ {% endif %} {% endblock %}
  38. 38. CREATE USERS MIGRATION AND APPLY IT $ python manage.py db migrate -m "User" Generating /Users/jasonamyers/dev/flask-filled/migrations/versions/8d9327f0 $ python manage.py db upgrade INFO [alembic.migration] Running upgrade 586131216f6 -> 8d9327f04f, User
  39. 39. RUN SERVER $ python manage.py runserver
  40. 40. FORMS...
  41. 41. FLASK-WTF Validation CSRF protection File Uploads pip install flask-wtf
  42. 42. AUTH/FORMS.PY from flask.ext.wtf import Form from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import Required, Length class LoginForm(Form): username = StringField('username', validators=[Required(), Length(1, 64)]) password = PasswordField('Password', validators=[Required()]) submit = SubmitField('Log In')
  43. 43. AUTH/VIEWS.PY @auth.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is not None and user.verify_password(form.password.data): login_user(user) next = request.args.get('next') return redirect(next or url_for('main.index')) else: flash('Wrong username or password.') return render_template('auth/login.html', form=form)
  44. 44. TEMPLATES/AUTH/LOGIN.HTML {% block page_content %} <div class="col-md-4"> <form action="" method="POST"> {{ form.csrf_token }} {% if form.csrf_token.errors %} <div class="warning">You have submitted an invalid CSRF token</ {% endif %} {{form.username.label }}: {{ form.username }} {% if form.username.errors %} {% for error in form.username.errors %} {{ error }} {% endfor %} {% endif %}<br> {{form.password.label }}: {{ form.password }} {% if form.password.errors %} {% for error in form.password.errors %} {{ error }}
  45. 45. AUTHORIZATION
  46. 46. FLASK-PRINCIPAL pip install flask-principal
  47. 47. __INIT__.PY from flask.ext.principal import Principal principal = Principal() def create_app(config_name): principal.init_app(app)
  48. 48. MODELS.PY roles_users = db.Table('roles_users', db.Column('user_id', db.Integer(), db.ForeignKey('users.user_id')), db.Column('role_id', db.Integer(), db.ForeignKey('roles.id'))) class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(80), unique=True) description = db.Column(db.String(255))
  49. 49. MODELS.PY - USER CLASS class User(db.Model, UserMixin): roles = db.relationship('Role', secondary=roles_users, primaryjoin=user_id == roles_users.c.user_id backref='users')
  50. 50. MODELS.PY - IDENTITY LOADER @identity_loaded.connect def on_identity_loaded(sender, identity): # Set the identity user object identity.user = current_user # Add the UserNeed to the identity if hasattr(current_user, 'id'): identity.provides.add(UserNeed(current_user.id)) # Assuming the User model has a list of roles, update the # identity with the roles that the user provides if hasattr(current_user, 'roles'): for role in current_user.roles: identity.provides.add(RoleNeed(role.name))
  51. 51. AUTH/VIEWS.PY from flask import current_app from flask.ext.principal import identity_changed, Identity @auth.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is not None and user.verify_password(form.password.data): login_user(user) identity_changed.send(current_app._get_current_object(), identity=Identity(user.user_id)) next = request.args.get('next') return redirect(next or url_for('main.index')) else:
  52. 52. AUTH/__INIT__.PY from flask.ext.principal import Permission, RoleNeed admin_permission = Permission(RoleNeed('admin'))
  53. 53. MAIN/VIEWS.PY from flaskfilled.auth import admin_permission @main.route('/settings', methods=['GET']) @admin_permission.require() def settings(): return render_template('main/settings.html')
  54. 54. SENDING MAIL
  55. 55. FLASK-MAIL Works with Flask config Simplies Message Construction
  56. 56. __INIT__.PY from flask.ext.mail import Mail mail = Mail() def create_app(config_name): mail.init_app(app)
  57. 57. __INIT__.PY from flask.ext.mail import Mail mail = Mail() def create_app(config_name): mail.init_app(app)
  58. 58. MAIN/VIEWS.PY from flask_mail import Message @main.route('/mailme', methods=['GET']) def mail(): msg = Message('COOKIES!', sender='from@example.com', recipients=['to@example.com']) msg.body = 'There all mine!' msg.html = '<b>There all mine!</b>' mail.send(msg)
  59. 59. WHAT OTHER THINGS ARE OUT THERE? flask-security flask-moment https://github.com/humiaozuzu/awesome-flask
  60. 60. QUESTIONS Jason Myers / @jasonamyers / Essential SQLAlchemy 2nd Ed O'Reilly

×