diff --git a/Large-app-how-to.md b/Large-app-how-to.md index d4656dc..13fcee8 100644 --- a/Large-app-how-to.md +++ b/Large-app-how-to.md @@ -11,8 +11,6 @@ Please feel free to fix and add you own tips. [http://flask.pocoo.org/docs/installation/](Flask Installation) I recommend using virtual env: easy and allow multiple environment on the same machine and doesn't even require you to have super user right on the machine (as the libs are localy installed). - - ## Flask-SQLAlchemy SQL provide an easy and advanced way to serialize your object to different type of relational database. In your virutal env, install SQLAlchemy from pip: @@ -173,11 +171,15 @@ First about the constants file, I like to have my constants their own file and ### First form -Now that we've done our object model, time to build the form that goes with it. We'll start with a registration form. The form will request the user's name, email and password. We'll use validators to ensure the user submitted correct values. Finally, a Repcaptcha field (provided by flask) will avoid machine registration. Just in case you plan on having Term of Service, I added a BooleanField called accept_tos. Since this field is required, the user will have to check the checkbox generated by this field on the box. +Now that we've done our object model, time to build the form that goes with it. We'll start with a registration and login form. The registration form will request the user's name, email and password. We'll use validators to ensure the user submitted correct values. Finally, a Repcaptcha field (provided by flask) will avoid machine registration. Just in case you plan on having Term of Service, I added a BooleanField called accept_tos. Since this field is required, the user will have to check the checkbox generated by this field on the box. The login form will have only email and password with the same validators. from flaskext.wtf import Form, TextField, PasswordField, BooleanField, RecaptchaField from flaskext.wtf import Required, Email, EqualTo + class LoginForm(Form): + email = TextField('Email address', [Required(), Email()]) + password = PasswordField('Password', [Required()]) + class RegisterForm(Form): name = TextField('NickName', [Required()]) email = TextField('Email address', [Required(), Email()]) @@ -195,7 +197,22 @@ Form more details of what can be done with WTF check [http://wtforms.simplecodes ### First view -The view is where we'll declare our Blueprint. Using url_prefix will prefix every url you set using route. A nice feature from WTF-Flask is the form.validate_on_submit: it check that the current request is POST and that the form validates. +The view is where we'll declare our Blueprint. Using url_prefix will prefix every url you set using route. A nice feature from WTF-Flask is the form.validate_on_submit: it check that the current request is POST and that the form validates. Once the user is logged in we want to redirect the user to his profile (/users/me/), method that is we will have to protect with a decorator: + + from functools import wraps + + from flask import g + + def requires_login(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user is None: + flash(u'You need to be signed in for this page.') + return redirect(url_for('users.login', next=request.path)) + return f(*args, **kwargs) + return decorated_function + +This decorator is checking that g.user has a value assigned to it, otherwise it means that the user isn't authenticated, we then add a message to be displayed to the user on the next page and redirect him to the login view. You probably wonder how `g.user` get defined, it's in the user's `views.py`, through the `before_request`. You'll realize later that if you pull a lot of information from your user's profile (historical data, friends, messages, activities...) this might become a bottle neck and caching user through their id might be a good solution (as long as you centralize your object modifications and clear this cache on every update). from flask import Blueprint, request, render_template, flash, g, session, redirect, url_for from werkzeug import check_password_hash, generate_password_hash @@ -203,16 +220,48 @@ The view is where we'll declare our Blueprint. Using url_prefix will prefix ever from app import db from app.users.forms import RegisterForm from app.users.models import User + from app.users.decorators import requires_login mod = Blueprint('user', __name__, url_prefix='/users') @mod.route('/me/') + @requires_login def home(): - # dummy view just displaying hello - return "Hello" + return render_template("users/profile.html", user=g.user) + + @mod.before_request + def before_request(): + """ + pull user's profile from the database before every request are treated + """ + g.user = None + if 'user_id' in session: + g.user = User.query.get(session['user_id']); + + @mod.route('/login/', methods=['GET', 'POST']) + def login(): + """ + Login form + """ + form = LoginForm(request.form) + # make sure data are valid, but doesn't validate password is right + if form.validate_on_submit(): + user = User.query.filter_by(email=form.email.data).first() + # we use werzeug to validate user's password + if user and check_password_hash(user.password, form.password.data): + # the session can't be modified as it's signed, + # it's a safe place to store the user id + session['user_id'] = user.id + flash('Welcome %s' % user.name, INFO_MSG) + return redirect(url_for('user.home')) + flash('Wrong email or password', ERROR_MSG) + return render_template("users/login.html", form=form) @mod.route('/register/', methods=['GET', 'POST']) def register(): + """ + Registration Form + """ form = RegisterForm(request.form) if form.validate_on_submit(): # create a user instance not yet stored in the database @@ -233,7 +282,7 @@ The view is where we'll declare our Blueprint. Using url_prefix will prefix ever ## First template -Jinja is integrated within Flask. One of the great feature of Jinja is the inheritance and the logic available (conditional structure, loop, context modification...). We'll create a `base.html` template from which we'll inherit from on each of our template. You can even have more than 1 inheritance (like having your template inheriting from twocolumn.html template which itself inherit from main.html) +Jinja is integrated within Flask. One of the great feature of Jinja is the inheritance and the logic available (conditional structure, loop, context modification...). We'll create a `base.html` template from which we'll inherit from on each of our template. You can even have more than 1 inheritance (like having your template inheriting from twocolumn.html template which itself inherit from main.html). The base template is also a good place to display flash messages (`get_flashed_messages`), so every template will now display messages when needed.
@@ -247,7 +296,14 @@ Jinja is integrated within Flask. One of the great feature of Jinja is the inher {% endblock %} -