É um micro-framework Python criado para que você possa facilmente programar todo um conjunto de ferramentas que, juntas, formam um site.
O primeiro passo é separar o ambiente do Flask. É como se estivéssemos criando uma máquina virtual onde só estivesse instalado o Flask para a nossa aplicação, que não será afetada pelos outros apps do nosso computador.
mkdir meu_projeto
cd meu_projeto
virtualenv flask
Obs: se você não tiver alguns dos módulos instalados, como o virtualenv, basta instalá-los com o pip install.
Agora podemos instalar todos os módulos que precisamos dentro deste ambiente virtual. Vamos começar com esses e, mais para frente, poderemos instalar outros:
flask/bin/pip install Flask
flask/bin/pip install flask-login
flask/bin/pip install flask-mail
flask/bin/pip install flask-sqlalchemy
flask/bin/pip install flask-migrate
flask/bin/pip install flask-wtf
Obs: se você não tiver alguns dos módulos instalados, como o virtualenv, basta instalá-los com o pip install.
Se você estiver usando Windows, lembre-se de configurar o Python e o pip nas suas variáveis de ambiente.
Agora que nosso ambiente está pronto, podemos iniciá-lo e "entrar" nele:
. flask/bin/activate
Quando quiser "sair", basta digitar deactivate.
Para que nossa aplicação fique organizada, precisamos utilizar um padrão de organização para o código. Este padrão é a forma como os nossos arquivos serão separados e estruturados e irá facilitar muito a vida de quem for contribuir no nosso código.
meu_projeto
├── app
│ ├── models
│ ├── controllers
│ ├── static
│ └── templates
└── flask
O modelo (ou padrão) MVC é uma das formas mais populares de organização de código. Ele considera três pilares de divisão:
Com a aplicação já organizada, podemos começar a montar a nossa aplicação. A primeira coisa que devemos fazer é iniciar o Flask. Quando iniciamos o Flask sem nenhuma outra lógica da aplicação, ele apenas executa um micro-servidor que é embutido da linguagem Python. Ou seja, ele faz com que seja possível acessar um endereço web local pelo nosso navegador.
from flask import Flask
app = Flask(__name__)
if __name__ == "__main__":
app.run(debug=True)
app.py
Para melhor organização, é recomendado dividir o script anterior em dois. Um será o nosso módulo principal e o outro será o script de execução do programa.
from flask import Flask
app = Flask(__name__)
from app import app
if __name__ == "__main__":
app.run(debug=True)
app/__init__.py
run.py
E então executá-lo:
$ python run.py
* Running on http://127.0.0.1:5000/ (Press Ctrl+C to quit)
Nossa aplicação está rodando em um servidor, mas ela ainda não possui páginas, então não temos nada para ver. Para criar uma página, precisamos definir sua rota, que é seu nome na URL, e em seguida criá-la no formato de uma função.
from flask import Flask
app = Flask(__name__)
@app.route("/")
@app.route("/index")
def index():
return "Hello world!"
app/__init__.py
As rotas serão, geralmente, parte do nosso controller. Por isso, é interessante separá-las em módulos a parte.
from app import app
@app.route("/")
@app.route("/index")
def index():
return "Hello world!"
app/controllers/index.py
from flask import Flask
app = Flask(__name__)
from app.controllers import index
app/__init__.py
Podemos personalizar nossas rotas de diversas formas, mas as principais delas são: recebendo argumentos e restringindo métodos.
from app import app
@app.route("/", methods=["GET"])
@app.route("/<nome>")
@app.route("/index/<string:nome>")
def index(nome="World"):
return "Hello %s!" % nome
app/controllers/index.py
Toda a internet se comunica por meio de um protocolo denominado Protocolo HTTP. Um método HTTP está ali para indicar o que o cliente deseja com aquela comunicação, ou seja, se ele quer inserir, editar, ler ou excluir algo, como um CRUD.
Os métodos são:
DELETE
PUT
POST
GET
Os templates compõem a view, ou seja, a parte que o usuário irá ver e interagir. Eles podem ser de diferentes formatos, mas o padrão é que sejam HTML.
<html>
<head>
<meta charset="utf-8" />
<title>Hello!</title>
</head>
<body>
<p>Oi, mundo!</p>
</body>
</html>
from flask import render_template
from app import app
@app.route("/", methods=["GET"])
@app.route("/index")
def index():
return render_template("index.html")
app/controllers/index.py
app/templates/index.html
** Indentação não é obrigatória
Além disso, nós podemos personalizar templates com códigos em Python inseridos no meio do HTML. Quem cuida dessa "tradução" é a ferramenta de renderização em que o Flask se baseia, o Jinja.
<html>
<head>
<meta charset="utf-8" />
<title>Hello!</title>
</head>
<body>
{% if nome %}
<p>Oi, {{ nome }}!</p>
{% else %}
<p>Oi, mundo!</p>
{% endif %}
</body>
</html>
from flask import render_template
from app import app
@app.route("/", methods=["GET"])
@app.route("/index/<usr_nome>")
def index(usr_nome=None):
return render_template("index.html",
nome=usr_nome)
app/controllers/index.py
app/templates/index.html
Por fim, podemos aplicar o conceito de herança aos nossos templates. Ou seja, eu posso manter um template como base e fazer com que outros tenham aquela base + um conteúdo diferente.
<html>
<head>
<meta charset="utf-8" />
<title>Meu projeto</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
app/templates/base.html
{% extends "base.html" %}
{% block content %}
{% if nome %}
<p>Oi, {{ nome }}!</p>
{% else %}
<p>Oi, mundo!</p>
{% endif %}
{% endblock %}
app/templates/index.html
Para conseguir trabalhar com banco de dados de maneira fácil no Flask, vamos utilizar uma ORM bastante popular, a SQLAlchemy. Para isso, vamos configurar nossa aplicação:
import os
basedir = os.path.abspath(os.path.dirname(__file__))
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
# Para DBs externos:
# SQLALCHEMY_DATABASE_URI = 'postgresql://usr:senha@end_ip:porta/nome_db'
config.py
Para conseguir trabalhar com banco de dados de maneira fácil no Flask, vamos utilizar uma ORM bastante popular, a SQLAlchemy. Para isso, vamos configurar nossa aplicação:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object("config")
db = SQLAlchemy(app)
from app.controllers import index
app/__init__.py
Vamos "traduzir" para o Python os projetos que havíamos modelado usando diagramas antes. Por exemplo, uma tabela para usuários:
from app import db
class Usuario(db.Model):
id = db.Column(db.Integer, primary_key=True)
usuario = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String, index=True, unique=True)
senha = db.Column(db.String)
def __repr__(self):
return '<Usuario %r>' % (self.usuario)
app/models/tables.py
Para que nossas mudanças na estrutra das tabelas do nosso banco de dados sejam aplicadas, precisamos trabalhar com migrações. Para isso, configuramos uma extensão, o Flask-Migrate.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
app = Flask(__name__)
app.config.from_object("config")
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
from app.controllers import index
app/__init__.py
Para que nossas mudanças na estrutra das tabelas do nosso banco de dados sejam aplicadas, precisamos trabalhar com migrações. Para isso, configuramos uma extensão, o Flask-Migrate.
from app import manager
if __name__ == "__main__":
manager.run()
run.py
Com as migrações configuradas, temos que migrar nosso banco de dados pela primeira vez para aplicar a criação da nova tabela.
$ python run.py db init
$ python run.py db migrate
$ python run.py db upgrade
$ python run.py runserver
E temos uma nova forma de executar nossa aplicação:
Para trabalhar com formulários, iremos utilizar a extensão WTForms, que já é bem integrada com o framework. Para isso, adicionamos mais algumas configurações:
import os
basedir = os.path.abspath(os.path.dirname(__file__))
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
WTF_CSRF_ENABLED = True
SECRET_KEY = 'muito-dificil'
config.py
Em seguida, descrevemos os formulários como se estivéssemso descrevendo tabelas do banco de dados. Usamos validadores para falar o que um campo pode ou não receber.
from flask_wtf import Form
from wtforms import StringField, PasswordField, BooleanField
from wtforms.validators import DataRequired
class FormCadastro(Form):
usuario = StringField('usuario', validators=[DataRequired()])
senha = PasswordField('senha', validators=[DataRequired()])
class FormLogin(Form):
usuario = StringField('usuario', validators=[DataRequired()])
senha = PasswordField('senha', validators=[DataRequired()])
lembrar = BooleanField('lembrar', default=False)
app/models/forms.py
Devemos aplicar o formulário em um template para que o usuário seja capaz de interagir com ele:
{% extends "base.html" %}
{% block content %}
<h1>Registre-se</h1>
<form action="" method="POST" name="registro">
{{ form.hidden_tag() }}
<b>Usuário</b>
{{ form.usuario(size=100) }}
<br>
<b>Senha</b>
{{ form.senha() }}
<input type="submit" value="Registrar" />
</form>
{% endblock %}
app/templates/registro.html
E criar uma nova página para esse template:
from flask import render_template
from app import app
from app.models.forms import FormCadastro
@app.route("/registro", methods=['GET', 'POST'])
def registro():
form = FormCadastro()
return render_template("registro.html",
form=form)
app/controllers/user.py
** Não esqueça de adicionar o novo controller aos imports do arquivo __init__.py
Por fim, para receber os dados que o usuário nos passa por meio do formulário, devemos verificar a inserção:
from flask import render_template
from app import app
from app.models.forms import FormCadastro
@app.route("/registro", methods=['GET', 'POST'])
def registro():
form = FormCadastro()
if form.validate_on_submit():
flash('{0} cadastrado!'.format(form.usuario.data))
return redirect(url_for('index'))
return render_template("registro.html",
form=form)
app/controllers/user.py
E agora, podemos fazer algo com esses dados. No caso, gravá-los no banco de dados.
from flask import render_template
from app import app, db
from app.models.forms import FormCadastro
from app.models.tables import Usuario
@app.route("/registro", methods=['GET', 'POST'])
def registro():
form = FormCadastro()
if form.validate_on_submit():
u = Usuario(usuario=form.usuario.data, senha=form.senha.data)
db.session.add(u)
db.session.commit()
flash('{0} cadastrado!'.format(form.usuario.data))
return redirect(url_for('index'))
return render_template("registro.html",
form=form)
app/controllers/user.py
Para não armazenarmos uma senha em texto puro, devemos sempre salvar uma hash que represente essa senha, ou seja, a senha criptografada. Dessa forma, se alguém conseguir burlar nossa segurança e tentar ver as senhas dos usuários, só verá uma bagunça de caracteres.
Funciona com a antiga Cifra de César, a mensagem: "Ataquem amanhã." é traduzida para "Dwdtxhp dpdqkd.". Só quem sabe qual código foi utilizado para reescrever essa mensagem, sabe como voltar à mensagem original.
from werkzeug.security import generate_password_hash, \
check_password_hash
from app import db
class Usuario(db.Model):
id = db.Column(db.Integer, primary_key=True)
usuario = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String, index=True, unique=True)
senha = db.Column(db.String)
def definir_senha(self, nova_senha):
self.senha = generate_password_hash(nova_senha)
def conferir_senha(self, senha):
return check_password_hash(self.senha, senha)
def __repr__(self):
return '<Usuario %r>' % (self.usuario)
app/models/tables.py
A autenticação de usuários trabalha com, pelo menos, uma tabela de usuários e uma sessão que armazena informações do usuário ativo.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from flask_login import LoginManager
app = Flask(__name__)
app.config.from_object("config")
.
.
.
lm = LoginManager()
lm.init_app(app)
app/__init__.py
class Usuario(db.Model):
...
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return True
@property
def is_anonymous(self):
return False
def get_id(self):
try:
return unicode(self.id) # python 2
except NameError:
return str(self.id) # python 3
...
app/models/tables.py
from flask import render_template
from app import app, db, lm
from app.models.forms import FormCadastro
from app.models.tables import Usuario
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
@app.route("/registro", methods=['GET', 'POST'])
def registro():
form = FormCadastro()
...
return render_template("registro.html",
form=form)
app/controllers/user.py
from flask import render_template
from flask_login import login_user
from app import app, db, lm
from app.models.forms import FormCadastro, FormLogin
from app.models.tables import Usuario
...
@app.route("/login", methods=['GET', 'POST'])
def login():
form = FormLogin()
if form.validate_on_submit():
usr = Usuario.query(Usuario.usuario == form.usuario.data).first()
if usr and usr.checar_senha(form.senha.data):
login_user(usr, remember_me=form.lembrar.data)
flash("Login com sucesso!")
else:
flash("Login inválido!")
return render_template("login.html",
form=form)
app/controllers/user.py
{% extends "base.html" %}
{% block content %}
<h1>Login</h1>
<form action="" method="POST" name="login">
{{ form.hidden_tag() }}
<b>Usuário</b>
{{ form.usuario(size=100) }}
<br>
<b>Senha</b>
{{ form.senha() }}
<input type="submit" value="Entrar" />
</form>
{% endblock %}
app/templates/login.html
from flask import render_template
from flask_login import login_user
from app import app, db, lm
from app.models.forms import FormCadastro, FormLogin
from app.models.tables import Usuario
...
@app.route("/perfil/<int:id>", methods=['GET'])
def perfil(id):
usr = Usuario.query(Usuario.id == id).first()
return render_template('perfil.html',
usr=usr)
app/controllers/user.py
{% extends 'base.html' %}
{% block content %}
<h1>Perfil</h1>
<ul>
<li><b>Nome:</b> {{ usr.nome }}</li>
<li><b>Idade:</b> {{ usr.idade }} anos</li>
<li><b>CPF:</b> {{ usr.cpf }}</li>
</ul>
{% endblock %}
app/templates/perfil.html
...
# email server
MAIL_SERVER = 'smtp.google.com'
MAIL_PORT = 465
MAIL_USERNAME = None
MAIL_PASSWORD = None
# administrator list
ADMINS = ['you@example.com']
config.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from flask_login import LoginManager
from flask_mail import Mail
app = Flask(__name__)
app.config.from_object("config")
.
.
.
mail = Mail(app)
config.py
from flask import render_template
from app import app, mail
from flask_mail import Message
...
@app.route('/contato')
def contato():
form = FormContato()
if form.validate_on_submit():
msg = Message(form.assunto.data,
sender=form.email.data,
recipients=app.config['ADMINS'])
msg.body = form.conteudo.data
mail.send(msg)
return render_template('contato.html',
form=form)
app/controllers/index.py
Vários cloud hosts aceitam Flask de forma gratuita (e, claro, com planos mais $$ também). Por exemplo:
Cada um deles possui um passo-a-passo de como subir sua aplicação para seu servidor.
Além disso, você pode hospedar o Flask em servidor próprio ou VPS seguindo esse guia: https://juliarizza.wordpress.com/2015/03/02/deploy-de-flask-na-amazon-ec2-com-apache/