Week 9
Flask, Rotas, Decoradores, Requisições, Respostas, Sessões, Cookies.
- Shorts
- Lab 9
- Problem Set 9
Notas
Lecture 9
- Bem-vindo!
- Estático para Dinâmico
- Flask
- Layout
- POST
- Frosh IMs
- Flask e SQL
- Sessão
- Armazenamento
- API
- JSON
- Resumo
Bem-vindo!
- Nas semanas anteriores, você aprendeu várias linguagens de programação, técnicas e estratégias.
- De fato, esta aula foi muito menos uma aula de C ou uma aula de Python e muito mais uma aula de programação, para que você possa acompanhar as tendências futuras.
- Nas últimas semanas, você aprendeu como aprender sobre programação.
- Hoje, vamos passar do HTML e CSS para combinar HTML, CSS, SQL, Python e JavaScript, para que você possa criar suas próprias aplicações web.
Estático para Dinâmico
- Até este ponto, todo o HTML que você viu era pré-escrito e estático.
- No passado, quando você visitava uma página, o navegador baixava uma página HTML e você conseguia visualizá-la.
- Páginas dinâmicas se referem à capacidade do Python e de linguagens similares de criar arquivos HTML sob demanda. Assim, você pode ter páginas da web que são geradas por opções selecionadas pelo usuário.
- Você já usou o
http-server
no passado para servir suas páginas da web. Hoje, vamos utilizar um novo servidor que pode analisar um endereço da web e executar ações com base na URL fornecida.
Flask
- Flask é uma biblioteca de terceiros que permite hospedar aplicativos da web usando o framework Flask dentro do Python.
- Você pode executar o Flask executando
flask run
. - Para fazer isso, você precisará de um arquivo chamado
app.py
e uma pasta chamadatemplates
. - Para começar, crie uma pasta chamada
templates
e crie um arquivo chamadoindex.html
com o seguinte código:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>hello</title> </head> <body> hello, {{ name }} </body> </html>
Observe o duplo marcador
{{ name }}
que é um espaço reservado para algo que será fornecido posteriormente pelo nosso servidor Flask. -
Em seguida, na mesma pasta em que a pasta
templates
aparece, crie um arquivo chamadoapp.py
e adicione o seguinte código:# Greets user from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): return render_template("index.html", name=request.args.get("name", "world"))
Observe que este código define
app
como a aplicação Flask. Em seguida, ele define a rota/
deapp
como retornando o conteúdo deindex.html
com o argumentoname
. Por padrão, a funçãorequest.args.get
irá procurar pelo argumentoname
fornecido pelo usuário. Se nenhum nome for fornecido, ele irá usarworld
como padrão. -
Por fim, adicione um arquivo final na mesma pasta que
app.py
chamadorequirements.txt
que contém apenas uma única linha de código:Flask
Observe que apenas
Flask
aparece neste arquivo. -
Você pode executar este arquivo digitando
flask run
na janela do terminal. Se o Flask não for executado, verifique se a sintaxe está correta em cada um dos arquivos acima. Além disso, se o Flask não for executado, certifique-se de que seus arquivos estejam organizados da seguinte maneira:/templates index.html app.py requirements.txt
Uma vez que você o iniciar, será solicitado que você clique em um link. Ao navegar para aquela página da web, tente adicionar
?name=[Seu Nome]
à URL base na barra de URL do seu navegador. -
Melhorando nosso programa, sabemos que a maioria dos usuários não digitará argumentos na barra de endereço. Em vez disso, os programadores contam com os usuários para preencherem formulários em páginas da web. Portanto, podemos modificar o index.html da seguinte forma:
<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>hello</title> </head> <body> <form action="/greet" method="get"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <button type="submit">Greet</button> </form> </body> </html>
Observe que um formulário é criado agora, que recebe o nome do usuário e o envia para uma rota chamada
/greet
. -
Além disso, podemos alterar o
app.py
da seguinte maneira:# Adds a form, second route from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") @app.route("/greet") def greet(): return render_template("greet.html", name=request.args.get("name", "world"))
Observe que o caminho padrão exibirá um formulário para o usuário inserir seu nome. A rota
/greet
passará oname
para essa página da web. -
Para finalizar esta implementação, você precisará de outro modelo para
greet.html
conforme mostrado abaixo:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>hello</title> </head> <body> hello, {{ name }} </body> </html>
Observe que esta rota agora renderizará a saudação ao usuário, seguida pelo nome deles.
Layout
- Ambas as nossas páginas da web,
index.html
egreet.html
, possuem grande parte dos mesmos dados. Não seria bom permitir que o corpo seja único, mas copiar o mesmo layout de página para página? -
Primeiro, crie um novo modelo chamado
layout.html
e escreva o código da seguinte forma:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>hello</title> </head> <body> {% block body %}{% endblock %} </body> </html>
Observe que a tag
{% block body %}{% endblock %}
permite a inserção de código de outros arquivos HTML. -
Em seguida, modifique o seu arquivo
index.html
da seguinte maneira:{% extends "layout.html" %} {% block body %} <form action="/greet" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <button type="submit">Greet</button> </form> {% endblock %}
Observe que a linha
{% extends "layout.html" %}
informa ao servidor de onde obter o layout desta página. Em seguida, o{% block body %}{% endblock %}
indica qual código deve ser inserido emlayout.html
. -
Por fim, altere
greet.html
da seguinte forma:{% extends "layout.html" %} {% block body %} hello, {{ name }} {% endblock %}
Observe como esse código é mais curto e compacto.
POST
- Você pode imaginar cenários em que não é seguro utilizar
get
, pois nomes de usuário e senhas apareceriam na URL. -
Podemos utilizar o método
post
para ajudar com esse problema, modificando o arquivoapp.py
da seguinte forma:# Switches to POST from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") @app.route("/greet", methods=["POST"]) def greet(): return render_template("greet.html", name=request.form.get("name", "world"))
Observe que
POST
é adicionado à rota/greet
e que usamosrequest.form.get
em vez derequest.args.get
. - Isso diz ao servidor para procurar mais profundamente no envelope virtual e não revelar os itens em
post
na URL. -
Ainda assim, este código pode ser avançado ainda mais utilizando uma única rota para ambos
get
epost
. Para fazer isso, modifiqueapp.py
da seguinte forma:# Uses a single route from flask import Flask, render_template, request app = Flask(__name__) @app.route("/", methods=["GET", "POST"]) def index(): if request.method == "POST": return render_template("greet.html", name=request.form.get("name", "world")) return render_template("index.html")
Observe que tanto o método
get
quanto o métodopost
são feitos em uma única rota. No entanto, orequest.method
é utilizado para rotear corretamente com base no tipo de roteamento solicitado pelo usuário.
Frosh IMs
- Frosh IMs ou froshims é um aplicativo da web que permite que os estudantes se inscrevam em esportes intermurais.
-
Crie uma pasta digitando
mkdir froshims
no terminal. Em seguida, digitecd froshims
para navegar até essa pasta. Dentro dela, crie um diretório chamado templates digitandomkdir templates
. Por fim, digitecode app.py
e escreva o código da seguinte forma:Observe que é fornecida uma opção de
failure
, de modo que uma mensagem de falha será exibida ao usuário se o camponame
ousport
não for preenchido corretamente. -
Em seguida, crie um arquivo na pasta
templates
chamadoindex.html
digitandocode templates/index.html
e escreva o código da seguinte forma:{% extends "layout.html" %} {% block body %} <h1>Register</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <select name="sport"> <option disabled selected>Sport</option> {% for sport in sports %} <option value="{{ sport }}">{{ sport }}</option> {% endfor %} </select> <button type="submit">Register</button> </form> {% endblock %}
-
Em seguida, crie um arquivo chamado
layout.html
digitandocode templates/layout.html
e escreva o código da seguinte forma:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>froshims</title> </head> <body> {% block body %}{% endblock %} </body> </html>
-
Quarto, crie um arquivo no diretório "templates" chamado
success.html
da seguinte forma:{% extends "layout.html" %} {% block body %} You are registered! {% endblock %}
-
Por fim, crie um arquivo chamado
failure.html
na pasta de templates, da seguinte forma:{% extends "layout.html" %} {% block body %} You are not registered! {% endblock %}
-
Você pode imaginar como podemos querer aceitar o registro de muitos registrantes diferentes. Podemos melhorar
app.py
da seguinte forma:# Implements a registration form, storing registrants in a dictionary, with error messages from flask import Flask, redirect, render_template, request app = Flask(__name__) REGISTRANTS = {} SPORTS = [ "Basketball", "Soccer", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", sports=SPORTS) @app.route("/register", methods=["POST"]) def register(): # Validate name name = request.form.get("name") if not name: return render_template("error.html", message="Missing name") # Validate sport sport = request.form.get("sport") if not sport: return render_template("error.html", message="Missing sport") if sport not in SPORTS: return render_template("error.html", message="Invalid sport") # Remember registrant REGISTRANTS[name] = sport # Confirm registration return redirect("/registrants") @app.route("/registrants") def registrants(): return render_template("registrants.html", registrants=REGISTRANTS)
Observe que um dicionário chamado
REGISTRANTS
é usado para registrar o esporte selecionado porREGISTRANTS[name]
. Além disso, observe queregistrants=REGISTRANTS
passa o dicionário para este modelo. -
Além disso, crie um novo modelo chamado
registrants.html
da seguinte maneira:{% extends "layout.html" %} {% block body %} <h1>Registrants</h1> <table> <thead> <tr> <th>Name</th> <th>Sport</th> </tr> </thead> <tbody> {% for name in registrants %} <tr> <td>{{ name }}</td> <td>{{ registrants[name] }}</td> </tr> {% endfor %} </tbody> </table> {% endblock %}
Observe que
{% for name in registrants %}...{% endfor %}
irá iterar por cada um dos registrants. Muito poderoso poder iterar em uma página web dinâmica! - Executando
flask run
e inserindo vários nomes e esportes, você pode navegar até/registrants
para ver quais dados foram registrados. - Agora você tem uma aplicação web! No entanto, existem algumas falhas de segurança! Como tudo é feito do lado do cliente, um adversário poderia alterar o HTML e hackear um site. Além disso, esses dados não persistirão se o servidor for desligado. Será que há alguma maneira de fazer com que nossos dados persistam mesmo quando o servidor for reiniciado?
Flask e SQL
- Assim como vimos como o Python pode interagir com um banco de dados SQL, podemos combinar o poder do Flask, Python e SQL para criar uma aplicação web onde os dados persistirão!
- Para implementar isso, você precisará seguir algumas etapas.
-
Primeiro, modifique o arquivo
requirements.txt
da seguinte maneira:cs50 Flask
-
Modifique o
index.html
da seguinte maneira:{% extends "layout.html" %} {% block body %} <h1>Register</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> {% for sport in sports %} <input name="sport" type="radio" value="{{ sport }}"> {{ sport }} {% endfor %} <button type="submit">Register</button> </form> {% endblock %}
-
Modifique
layout.html
da seguinte forma:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>froshims</title> </head> <body> {% block body %}{% endblock %} </body> </html>
-
Garanta que o
failure.html
apareça da seguinte forma:{% extends "layout.html" %} {% block body %} You are not registered! {% endblock %}
-
Modifique
registrants.html
para aparecer da seguinte forma:{% extends "layout.html" %} {% block body %} <h1>Registrants</h1> <table> <thead> <tr> <th>Name</th> <th>Sport</th> <th></th> </tr> </thead> <tbody> {% for registrant in registrants %} <tr> <td>{{ registrant.name }}</td> <td>{{ registrant.sport }}</td> <td> <form action="/deregister" method="post"> <input name="id" type="hidden" value="{{ registrant.id }}"> <button type="submit">Deregister</button> </form> </td> </tr> {% endfor %} </tbody> </table> {% endblock %}
Observe que um valor oculto
registrant.id
é incluído para que seja possível usar esseid
posteriormente no arquivoapp.py
-
Por fim, modifique o arquivo
app.py
da seguinte forma:# Implements a registration form, storing registrants in a SQLite database, with support for deregistration from cs50 import SQL from flask import Flask, redirect, render_template, request app = Flask(__name__) db = SQL("sqlite:///froshims.db") SPORTS = [ "Basketball", "Soccer", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", sports=SPORTS) @app.route("/deregister", methods=["POST"]) def deregister(): # Forget registrant id = request.form.get("id") if id: db.execute("DELETE FROM registrants WHERE id = ?", id) return redirect("/registrants") @app.route("/register", methods=["POST"]) def register(): # Validate submission name = request.form.get("name") sport = request.form.get("sport") if not name or sport not in SPORTS: return render_template("failure.html") # Remember registrant db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport) # Confirm registration return redirect("/registrants") @app.route("/registrants") def registrants(): registrants = db.execute("SELECT * FROM registrants") return render_template("registrants.html", registrants=registrants)
Observe que a biblioteca
cs50
é utilizada. Uma rota é incluída para o métodoregister
para o métodopost
. Essa rota irá receber o nome e o esporte obtidos do formulário de registro e executar uma consulta SQL para adicionar onome
e oesporte
à tabelaregistrants
. A rotaderegister
direciona para uma consulta SQL que irá obter oid
do usuário e utilizar essa informação para cancelar o registro deste indivíduo. - Você pode ler mais na documentação do Flask.
Session
- Embora o código acima seja útil do ponto de vista administrativo, onde um administrador de back-office poderia adicionar e remover indivíduos do banco de dados, pode-se imaginar como esse código não é seguro para implementar em um servidor público.
- Por um lado, atores mal-intencionados poderiam tomar decisões em nome de outros usuários ao clicar no botão "deregister" - efetivamente excluindo a resposta registrada do servidor.
- Serviços da web como o Google utilizam credenciais de login para garantir que os usuários tenham acesso apenas aos dados corretos.
- Na verdade, podemos implementar isso usando cookies. Cookies são pequenos arquivos que são armazenados no seu computador, de forma que o seu computador possa se comunicar com o servidor e efetivamente dizer: "Eu sou um usuário autorizado que já fez login".
- Na forma mais simples, podemos implementar isso criando uma pasta chamada
login
e, em seguida, adicionando os seguintes arquivos. -
Primeiro, crie um arquivo chamado
requirements.txt
que contenha o seguinte:Flask Flask-Session
Note que além do
Flask
, também incluímos oFlask-Session
, que é necessário para suportar sessões de login. -
Segundo, em uma pasta
templates
, crie um arquivo chamadolayout.html
que aparece da seguinte forma:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>store</title> </head> <body> {% block body %}{% endblock %} </body> </html>
Observe que isso fornece um layout muito simples com um título e um corpo.
-
Terceiro, crie um arquivo na pasta
templates
chamadoindex.html
que aparece da seguinte forma:{% extends "layout.html" %} {% block body %} {% if session["name"] %} You are logged in as {{ session["name"] }}. <a href="/logout">Log out</a>. {% else %} You are not logged in. <a href="/login">Log in</a>. {% endif %} {% endblock %}
Observe que este arquivo verifica se
session["name"]
existe. Se existir, será exibida uma mensagem de boas-vindas. Caso contrário, será recomendado que você acesse uma página para fazer login. -
Quarto, crie um arquivo chamado
login.html
e adicione o seguinte código:{% extends "layout.html" %} {% block body %} <form action="/login" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <button type="submit">Log In</button> </form> {% endblock %}
Observe que este é o layout de uma página básica de login.
-
Finalmente, crie um arquivo na pasta
login
chamadoapp.py
e escreva o código da seguinte forma:from flask import Flask, redirect, render_template, request, session from flask_session import Session # Configure app app = Flask(__name__) # Configure session app.config["SESSION_PERMANENT"] = False app.config["SESSION_TYPE"] = "filesystem" Session(app) @app.route("/") def index(): if not session.get("name"): return redirect("/login") return render_template("index.html") @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": session["name"] = request.form.get("name") return redirect("/") return render_template("login.html") @app.route("/logout") def logout(): session["name"] = None return redirect("/")
Observe as importações modificadas no início do arquivo, incluindo
session
, que permitirá o suporte a sessões. Mais importante, observe comosession["name"]
é utilizado nas rotaslogin
elogout
. A rotalogin
atribuirá o nome de login fornecido asession["name"]
. No entanto, na rotalogout
, o logout é implementado simplesmente definindosession["name"]
comoNone
. - Você pode ler mais sobre sessões na documentação do Flask.
Store
- Passando para um exemplo final de utilização da capacidade do Flask de habilitar uma sessão.
-
Analisamos o seguinte código para
store
emapp.py
. O código a seguir foi mostrado:from cs50 import SQL from flask import Flask, redirect, render_template, request, session from flask_session import Session # Configure app app = Flask(__name__) # Connect to database db = SQL("sqlite:///store.db") # Configure session app.config["SESSION_PERMANENT"] = False app.config["SESSION_TYPE"] = "filesystem" Session(app) @app.route("/") def index(): books = db.execute("SELECT * FROM books") return render_template("books.html", books=books) @app.route("/cart", methods=["GET", "POST"]) def cart(): # Ensure cart exists if "cart" not in session: session["cart"] = [] # POST if request.method == "POST": id = request.form.get("id") if id: session["cart"].append(id) return redirect("/cart") # GET books = db.execute("SELECT * FROM books WHERE id IN (?)", session["cart"]) return render_template("cart.html", books=books)
Observe que o
cart
é implementado usando uma lista. Itens podem ser adicionados a essa lista usando os botõesAdd to Cart
embooks.html
. Ao clicar nesse botão, o métodopost
é invocado, onde oid
do item é acrescentado aocart
. Ao visualizar o carrinho, ao invocar o métodoget
, é executado SQL para exibir uma lista dos livros no carrinho.
API
- Uma Interface de Programação de Aplicativos ou API é uma série de especificações que permitem a interface com outro serviço. Por exemplo, poderíamos utilizar a API do IMDB para acessar seu banco de dados. Também poderíamos integrar APIs para lidar com tipos específicos de dados baixáveis de um servidor.
- Vimos um exemplo chamado
shows
. -
Ao analisar o arquivo
app.py
, vimos o seguinte:# Searches for shows using Ajax from cs50 import SQL from flask import Flask, render_template, request app = Flask(__name__) db = SQL("sqlite:///shows.db") @app.route("/") def index(): return render_template("index.html") @app.route("/search") def search(): q = request.args.get("q") if q: shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%") else: shows = [] return render_template("search.html", shows=shows)
Observe que a rota
search
executa uma consulta SQL. -
Ao olhar para
search.html
, você notará que é muito simples:{% for show in shows %} <li>{{ show["title"] }}</li> {% endfor %}
Observe que ele fornece uma lista com marcadores.
-
Por fim, ao olhar para
index.html
, observe que o código AJAX é utilizado para alimentar a pesquisa:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>shows</title> </head> <body> <input autocomplete="off" autofocus placeholder="Query" type="search"> <ul></ul> <script> let input = document.querySelector('input'); input.addEventListener('input', async function() { let response = await fetch('/search?q=' + input.value); let shows = await response.text(); document.querySelector('ul').innerHTML = shows; }); </script> </body> </html>
Observe que um ouvinte de eventos é utilizado para consultar dinamicamente o servidor e fornecer uma lista que corresponda ao título fornecido. Isso irá localizar a tag
ul
no HTML e modificar a página da web de acordo para incluir a lista das correspondências. - Você pode ler mais na documentação AJAX.
JSON
- JavaScript Object Notation ou JSON é um arquivo de texto com dicionários contendo chaves e valores. Essa é uma maneira bruta e amigável para computadores obterem muitos dados.
- JSON é uma maneira muito útil de obter dados do servidor.
-
Você pode ver isso em ação no arquivo
index.html
que examinamos juntos:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>shows</title> </head> <body> <input autocomplete="off" autofocus placeholder="Query" type="text"> <ul></ul> <script> let input = document.querySelector('input'); input.addEventListener('input', async function() { let response = await fetch('/search?q=' + input.value); let shows = await response.json(); let html = ''; for (let id in shows) { let title = shows[id].title.replace('<', '<').replace('&', '&'); html += '<li>' + title + '</li>'; } document.querySelector('ul').innerHTML = html; }); </script> </body> </html>
Embora o acima possa ser um tanto criptico, ele fornece um ponto de partida para você pesquisar sobre JSON por conta própria e ver como ele pode ser implementado em suas próprias aplicações web.
- Você pode ler mais na documentação do JSON.
Resumindo
Nesta lição, você aprendeu como utilizar Python, SQL e Flask para criar aplicações web. Especificamente, discutimos...
- GET
- POST
- Flask
- Session
- AJAX
- JSON
Até a próxima, na nossa aula final!