From 37fe6573dd5900d72e1fe456475b975b1abd3cd2 Mon Sep 17 00:00:00 2001 From: mattoufoutu Date: Mon, 6 Feb 2012 06:46:46 -0800 Subject: [PATCH] add syntax coloration and fix a few typos --- Large-app-how-to.md | 117 +++++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/Large-app-how-to.md b/Large-app-how-to.md index 8755c26..6c738bd 100644 --- a/Large-app-how-to.md +++ b/Large-app-how-to.md @@ -1,31 +1,35 @@ This document is an attempt to describe the first step of a large project structure with flask and some basic modules: * SQLAlchemy -* WTF (What The Form) +* WTForms Please feel free to fix and add you own tips. # Installation ## Flask + [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). +I recommend using virtualenv: easy and allows multiple environments on the same machine and doesn't even require you to have super user rights 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: + +SQLAlchemy provide an easy and advanced way to serialize your object to different type of relational database. In your virutalenv, install SQLAlchemy from pip: pip install flask-sqlalchemy -[http://packages.python.org/Flask-SQLAlchemy/](More here about SQL Alchemy flask package) +[http://packages.python.org/Flask-SQLAlchemy/](More here about the Flask-SQLAlchemy package) ## Flask-WTF -WTF (What the Form) Provides a easy way to handle user's data submission. + +WTForms provides an easy way to handle user's data submission. pip install Flask-WTF -[http://packages.python.org/Flask-WTF/](More here about What The Form flask package) +[http://packages.python.org/Flask-WTF/](More here about the Flask-WTF package) # Overview + Ok, so from now, we should have all the libs ready. Here the folder structures: /config.py @@ -36,7 +40,7 @@ Ok, so from now, we should have all the libs ready. Here the folder structures: /app/constants.py /app/static/ -For every module (or sub app... ) well have this file structure (here for the users module) +For every module (or sub app... ) well have this file structure (here for the `users` module) /app/users/__init__.py /app/users/views.py @@ -45,7 +49,7 @@ For every module (or sub app... ) well have this file structure (here for the us /app/users/models.py /app/users/decorators.py -for every module that need templating (jinja) we store those in the templates folder + module directory. +For every module that needs templating (jinja) we store those in the templates folder + module directory. /app/templates/404.html /app/templates/base.html @@ -53,22 +57,26 @@ for every module that need templating (jinja) we store those in the templates fo /app/templates/users/register.html ... -for the static file you should serve them with a dedicated http server, but being in at a dev stage, we'll let flask serve them. Flask will automagically serve static files from this static folder. If you want to use another folder... you can read about that here: http://flask.pocoo.org/docs/api/#application-object +You should serve your static files with a dedicated http server, but during the development, we'll let flask serve them. Flask will automagically serve static files from the `static/` folder. If you want to use another folder... you can read about that here: http://flask.pocoo.org/docs/api/#application-object /app/static/js/main.js /app/static/css/reset.css /app/static/img/header.png -We'll create 4 modules, a user module (manage user's registration, login, password lost, profile edit and maybe Third party Login/Registration) an emails module intended to be used by a queuing server, and a posts and comments modules +We'll create 4 modules, an user module (manage user's registration, login, lost password, profile edit and maybe third-party login/registration) an emails module intended to be used by a queuing server, and a posts and comments modules ## Config + `/run.py` will be used to launch the web server. +```python from tol import app app.run(debug=True) +``` -`/shell.py` will allow you to get a console and enter commands within your flask environment. Maybe not as nice as debugging with pdb, but always usefull (when you will initialize your database) +`/shell.py` will allow you to get a console and enter commands within your flask environment. Maybe not as nice as debugging with pdb, but always usefull (when you will initialize your database). +```python #!/usr/bin/env python import os import readline @@ -78,9 +86,11 @@ We'll create 4 modules, a user module (manage user's registration, login, passwo from app import * os.environ['PYTHONINSPECT'] = 'True' +``` `/config.py` will be storing all the module configurations. Here, the database is setup to use SQLite, because it's a very convenient dev env database. Most likely `/config.py` won't be a part of your repository and will be different on your test and production servers. +```python import os _basedir = os.path.abspath(os.path.dirname(__file__)) @@ -101,24 +111,26 @@ We'll create 4 modules, a user module (manage user's registration, login, passwo RECAPTCHA_PUBLIC_KEY = 'blahblahblahblahblahblahblahblahblah' RECAPTCHA_PRIVATE_KEY = 'blahblahblahblahblahblahprivate' RECAPTCHA_OPTIONS = {'theme': 'white'} - - del os +``` * `_basedir` is a trick for you to get the folder where the script runs -* `DEBUG` indicate that it is a dev environment, you'll get the very helpful error page from flask when an error occur. -* `SECRET_KEY` will be use to sign the cookies. Change it and all your user will have to login again. -* `ADMINS` +* `DEBUG` indicates that it is a dev environment, you'll get the very helpful error page from flask when an error occur. +* `SECRET_KEY` will be use to sign the cookies. Change it and all your users will have to login again. +* `ADMINS` will be used if you need to email informations to the site administrators. * `SQLALCHEMY_DATABASE_URI` and `DATABASE_CONNECT_OPTIONS` are SQLAlchemy connection options (hard to guess ) * `THREAD_PAGE` my understanding was 2/core... might be wrong :) -* `CSRF_ENABLED` `CSRF_SESSION_KEY` is protecting against form post fraud -* WTF comes with REPCAPTCHA field ready to use... just need to go to repcatcha website and get your public and private key. +* `CSRF_ENABLED` and `CSRF_SESSION_KEY` are protecting against form post fraud +* `RECAPTCHA_*` WTForms comes with a `RecaptchaField` ready to use... just need to go to recaptcha website and get your public and private key. ## First module -We'll start with the users modules. In order, we'll define the models, the constants linked to this model, the form and finally the first view and it's template. -### First model (and it's constants file) -The `/app/users/models.py` +We'll start with the users modules. In order, we'll define the models, the constants linked to this model, the form and finally the first view and its template. +### First model (and its constants file) + +The `/app/users/models.py`: + +```python from app import db from app.users import constants as USER @@ -145,9 +157,11 @@ The `/app/users/models.py` def __repr__(self): return '' % (self.name) +``` -and it's constants in the `/app/users/constants.py` file: +and its constants in the `/app/users/constants.py` file: +```python # User role ADMIN = 0 STAFF = 1 @@ -167,15 +181,17 @@ and it's constants in the `/app/users/constants.py` file: NEW: 'new', ACTIVE: 'active', } +``` -First about the constants file, I like to have my constants their own file and inside my module for 2 main reasons. Your constants will probably be used in your models, forms and views. The second reason is that it's a better organization for you to find them. Also, importing your constants as the module in uppercase indicate the constant type and the module name (like USER for users.constants) will avoid you name conflicts. +First about the constants file, I like to have my constants in their own file and inside my module for 2 main reasons. Your constants will probably be used in your models, forms and views. The second reason is that it's a better organization for you to find them. Also, importing your constants as the module in uppercase indicate the constant type and the module name (like `USER` for `users.constants`) will avoid you name conflicts. ### 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 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. Here's the '/app/users/forms.py' file: +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 Recaptcha 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. Here's the `/app/users/forms.py` file: - from flaskext.wtf import Form, TextField, PasswordField, BooleanField, RecaptchaField - from flaskext.wtf import Required, Email, EqualTo +```python + from flask.ext.wtf import Form, TextField, PasswordField, BooleanField, RecaptchaField + from flask.ext.wtf import Required, Email, EqualTo class LoginForm(Form): email = TextField('Email address', [Required(), Email()]) @@ -190,16 +206,18 @@ Now that we've done our object model, time to build the form that goes with it. EqualTo('confirm', message='Passwords must match') ]) accept_tos = BooleanField('I accept the TOS', [Required()]) - repcaptcha = RecaptchaField() + recaptcha = RecaptchaField() +``` The first parameters for the field is the label we'll want to display for the field. For example the name field will be labelled as NickName on the form. For the password fields, another useful validator got used here, EqualTo, it compares the data contained in the current field with the data of the other specified field. -Form more details of what can be done with WTF check [http://wtforms.simplecodes.com/docs/dev/] +Form more details of what can be done with WTForms check [http://wtforms.simplecodes.com/docs/dev/] ### 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. 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 (`/app/users/decorators.py`): +The view is where we'll declare our Blueprint. Using url_prefix will prefix every url you set using route. A nice feature from Flask-WTF is the `form.validate_on_submit` method: it check that the current request is a POST and that the form validates. Once the user is logged in we want to redirect the user to his profile (`/users/me/`). To prevent unauthenticated users to access this page, we'll create a decorator to protect it (`/app/users/decorators.py`): +```python from functools import wraps from flask import g @@ -212,9 +230,11 @@ The view is where we'll declare our Blueprint. Using url_prefix will prefix ever 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). Following are the views definition in `/app/users/views.py`: +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` gets 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). Following are the views definition in `/app/users/views.py`: +```python from flask import Blueprint, request, render_template, flash, g, session, redirect, url_for from werkzeug import check_password_hash, generate_password_hash @@ -265,26 +285,28 @@ This decorator is checking that g.user has a value assigned to it, otherwise it """ form = RegisterForm(request.form) if form.validate_on_submit(): - # create a user instance not yet stored in the database + # create an user instance not yet stored in the database user = User(form.name.data, form.email.data, \ generate_password_hash(form.password.data)) # Insert the record in our database and commit it db.session.add(user) db.session.commit() - # Log user in, as user know has an id + # Log the user in, as he now has an id session['user_id'] = user.id - # flash will had a message to be displayed to the user + # flash will display a message to the user flash('Thanks for registering') # redirect user to the 'home' method of the user module. return redirect(url_for('user.home')) return render_template("users/register.html", form=form) +``` ## 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 `/app/templates/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. +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 `/app/templates/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 inherits 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. +```jinja {% block title %}My Site{% endblock %} @@ -309,9 +331,11 @@ Jinja is integrated within Flask. One of the great feature of Jinja is the inher