TREINAMENTO

DESENVOLVIMENTO WEB

Júlia Rizza

contato@juliarizza.com

O que é Flask?

É um micro-framework Python criado para que você possa facilmente programar todo um conjunto de ferramentas que, juntas, formam um site.

preparando o ambiente

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.

preparando o ambiente

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.

entrando no ambiente

Agora que nosso ambiente está pronto, podemos iniciá-lo e "entrar" nele:

. flask/bin/activate

Quando quiser "sair", basta digitar deactivate.

ORGANIZANDO O ambiente

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

modelo mvc

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:

  • MODEL: é a camada responsável por tudo relacionado à dados, sua manipulação e sua validação. Às vezes, pode ficar responsável por configurações de mais alto nível.
  • CONTROLLER: é a camada que cuida da lógica que está por trás de cada ação do programa, que realiza todas as operações do software, a parte que realmente trabalha.
  • VIEW: é a camada que interage com o usuário, aquilo que ele vê e clica quando está navegando pela aplicação. Resumindo, é a interface gráfica.

modelo mvc

primeiro passo

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

primeiro passo

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)

ROTAS

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

ROTAS

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

personalizando as rotas

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

métodos http

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

TEMPLATES

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

TEMPLATES

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

herança de TEMPLATES

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

banco de dados & orm

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

banco de dados & orm

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

modelando o db

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

CONFIGURANDO MIGRAÇÕES

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

CONFIGURANDO MIGRAÇÕES

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

migrando

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:

FORMULÁRIOS

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

FORMULÁRIOS

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

FORMULÁRIOS

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

FORMULÁRIOS

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

FORMULÁRIOS

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

FORMULÁRIOS

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

SENHAS E CRIPTOGRAFIA

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.

SENHAS E CRIPTOGRAFIA

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

LOGIN

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

LOGIN

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

LOGIN

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

LOGIN

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

LOGIN

{% 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

PERFIL DO USUÁRIO

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

PERFIL DO USUÁRIO

{% 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

ENVIANDO EMAILS

...

# email server
MAIL_SERVER = 'smtp.google.com'
MAIL_PORT = 465
MAIL_USERNAME = None
MAIL_PASSWORD = None

# administrator list
ADMINS = ['you@example.com']
config.py

ENVIANDO EMAILS

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

ENVIANDO EMAILS

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

COLOCANDO NO AR

Vários cloud hosts aceitam Flask de forma gratuita (e, claro, com planos mais $$ também). Por exemplo:

  • Heroku
  • OpenShift
  • Webfaction
  • Google App Engine (GAE)

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/

TREINAMENTO

desenvolvimento web

Júlia Rizza

contato@juliarizza.com

Made with Slides.com