Aula 9
- Bem-vindo!
- Estática para dinâmica
- Flask
- Layout
- POST
- Frosh IMs
- Flask e SQL
- Sessão
- Armazenar
- API
- JSON
- Resumindo
Bem-vindo!
- Nas semanas anteriores, você aprendeu várias linguagens de programação, técnicas e estratégias.
- Na verdade, esta aula tem sido muito menos uma aula de C ou aula de Python e muito mais uma aula de programação, para que você possa seguir tendências futuras.
- Nas últimas semanas, você aprendeu como aprender sobre programação.
- Hoje, mudaremos de HTML e CSS para combinar HTML, CSS, SQL, Python e JavaScript para que você possa criar seus próprios aplicativos da web.
Estática para dinâmica
- Até este momento, todo HTML que você viu foi pré-escrito e estático.
- No passado, quando você visitava uma página, o navegador baixava uma página HTML e você poderia visualizá-la.
- As páginas dinâmicas referem-se à capacidade do Python e linguagens semelhantes de criar arquivos HTML em tempo real. Assim, você pode ter páginas da Web geradas por opções selecionadas pelo usuário.
- Você usou o
http-server
anteriormente para fornecer suas páginas da Web. Hoje, usaremos um novo servidor capaz de 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 no Python.
- Você pode executar o flask executando
flask run
. - Para 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
{{ name }}
que é um placeholder para algo que será fornecido posteriormente por 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:# Cumprimenta o usuário 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 o aplicativo Flask. Em seguida, define a rota/
deapp
como o retorno do conteúdo deindex.html
com o argumento dename
. Por padrão, a funçãorequest.args.get
procurará oname
fornecido pelo usuário. Se nenhum nome for fornecido, ele será definido comoworld
. -
Por fim, adicione um arquivo final na mesma pasta de
app.py
chamadorequirements.txt
que tem apenas uma única linha de código:Flask
Observe que somente
Flask
aparece neste arquivo. -
Você pode executar esse arquivo digitando
flask run
na janela do terminal. Caso o Flask não execute, verifique se sua sintaxe está correta em cada um dos arquivos acima. Além disso, se o Flask não for executado, verifique se seus arquivos estão organizados da seguinte forma:/modelos index.html app.py requirements.txt
Assim que conseguir executar, você será solicitado a clicar em um link. Quando você navegar até essa página da web, tente adicionar
?name=[SeuNome]
à 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ços. Em vez disso, os programadores contam com os usuários para preencher formulários em páginas da web. Da mesma forma, podemos modificar o index.html da seguinte maneira:
<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>olá</title> </head> <body> <form action="/greet" method="get"> <input autocomplete="off" autofocus name="name" placeholder="Nome" type="text"> <button type="submit">Cumprimentar</button> </form> </body> </html>
Observe que um formulário agora é criado que pega o nome do usuário e então o passa para uma rota chamada
/greet
. -
Além disso, podemos alterar
app.py
da seguinte forma:# Adiciona um formulário, segunda rota 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", "mundo"))
Observe que o caminho padrão exibirá um formulário para o usuário inserir seu nome. A rota
/greet
passaráname
para essa página da web. -
Para finalizar essa implementação, você precisará de outro modelo para
greet.html
da seguinte forma:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>olá</title> </head> <body> olá, {{ name }} </body> </html>
Observe que essa rota agora exibirá a saudação para o usuário, seguida por seu nome.
Layout
- Ambas as nossas páginas da web,
index.html
egreet.html
, têm muitos dados em comum. Não seria ótimo permitir que o corpo fosse único, mas copiar o mesmo layout de página para página? -
Primeiro, crie um novo template 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
{% block body %}{% endblock %}
permite a inserção de outro código de outros arquivos HTML. -
Em seguida, modifique seu
index.html
da seguinte forma:{% 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 onde obter o layout desta página. Em seguida,{% block body %}{% endblock %}
informa qual código deve ser inserido emlayout.html
. -
Finalmente, altere
greet.html
da seguinte forma:{% extends "layout.html" %} {% block body %} hello, {{ name }} {% endblock %}
Observe como este código é mais curto e compacto.
POST
- Você pode imaginar cenários em que não é seguro utilizar
get
, pois nomes de usuários e senhas apareceriam na URL. -
Podemos utilizar o método
post
para ajudar neste problema modificandoapp.py
da seguinte forma:# Altera para 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 informa ao servidor para procurar mais profundamente no envelope virtual e não revelar os itens em
post
na URL. -
Ainda assim, esse código pode ser refinado ainda mais, utilizando uma rota única para
get
epost
. Para fazer isso, modifiqueapp.py
da seguinte forma:# Usa uma rota única 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
get
epost
são feitos em um único roteamento. No entanto,request.method
é utilizado para rotear adequadamente com base no tipo de roteamento solicitado pelo usuário.
Frosh IMs
- O Frosh IMs ou froshims é um aplicativo da web que permite que os alunos se inscrevam em esportes intercolegiais.
-
Crie uma pasta digitando
mkdir froshims
na janela do terminal. Em seguida, digitecd froshims
para navegar até essa pasta. Dentro dela, crie um diretório chamado models digitandomkdir templates
. Por fim, digitecode app.py
e escreva o código como segue:# Implementa um formulário de inscrição usando um menu de seleção de flask import Flask, render_template, request app = Flask(__name__) ESPORTES = [ "Basquete", "Futebol", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", esportes=ESPORTES) @app.route("/registrar", methods=["POST"]) def registrar(): # Validade a submissão se não request.form.get("nome") ou request.form.get("esporte") não em ESPORTES: return render_template("falha.html") # Confirme o registro return render_template("sucesso.html")
Observe que uma opção de
falha
é fornecida, de modo que uma mensagem de falha será exibida ao usuário se o camponome
ouesporte
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" %} {% bloco body %} <h1>Registrar</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Nome" type="text"> <select name="sport"> <option disabled selected>Esporte</option> {% for sport in sports %} <option value="{{ sport }}">{{ sport }}</option> {% endfor %} </select> <button type="submit">Registrar</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> {% bloco body %}{% endblock %} </body> </html>
-
Em quarto lugar, crie um arquivo nos modelos chamado
success.html
da seguinte forma:{% extends "layout.html" %} {% bloco body %} Você está registrado! {% endblock %}
-
Por fim, crie um arquivo nos modelos chamado
failure.html
da seguinte forma:{% extends "layout.html" %} {% bloco body %} Você não está registrado! {% endblock %}
-
Você pode imaginar como queremos aceitar o registro de muitos registradores diferentes. Podemos melhorar o
app.py
da seguinte maneira:# Implementa um formulário de registro, armazenando os registradores em um dicionário, com mensagens de erro from flask import Flask, redirect, render_template, request app = Flask(__name__) REGISTRANTS = {} SPORTS = [ "Basquete", "Futebol", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", sports=SPORTS) @app.route("/register", methods=["POST"]) def register(): # Valida o nome name = request.form.get("name") if not name: return render_template("error.html", message="Nome ausente") # Valida o esporte sport = request.form.get("sport") if not sport: return render_template("error.html", message="Esporte ausente") if sport not in SPORTS: return render_template("error.html", message="Esporte inválido") # Lembra do registrador REGISTRANTS[name] = sport # Confirma o registro 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 osport
selecionado porREGISTRANTS[name]
. Além disso, observe queregistrants=REGISTRANTS
passa o dicionário para esse template. -
Além disso, crie um novo template chamado
registrants.html
da seguinte maneira:{% extends "layout.html" %} {% block body %} <h1>Registrantes</h1> <table> <thead> <tr> <th>Nome</th> <th>Esporte</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 registrantes. Muito poderoso para conseguir iterar em uma página da web dinâmica! -
Executando
flask run
e inserindo vários nomes e esportes, você pode navegar até/registrants
para ver quais dados foram registrados. - Você tem agora um aplicativo da web! No entanto, existem algumas falhas de segurança! Por estar tudo no lado do cliente, um adversário poderia alterar o HTML e hackear um site. Além disso, esses dados não persistirão caso o servidor seja desligado. Existe alguma maneira de fazer com que nossos dados persistam mesmo quando o servidor for reiniciado?
Flask e SQL
- Da mesma forma que vimos como o Python pode se conectar a um banco de dados SQL, podemos combinar o poder do Flask, Python e SQL para criar um aplicativo web onde os dados serão persistentes!
- Para implementar isso, você precisará seguir algumas etapas.
-
Primeiro, modifique
requirements.txt
como a seguir:cs50 Flask
-
Modifique
index.html
como a seguir:{% extends "layout.html" %} {% block body %} <h1>Registrar</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Nome" type="text"> {% for sport in sports %} <input name="sport" type="radio" value="{{ sport }}"> {{ sport }} {% endfor %} <button type="submit">Registrar</button> </form> {% endblock %}
-
Modifique
layout.html
como a seguir:<!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>
-
Verifique se
failure.html
é mostrado como a seguir:{% extends "layout.html" %} {% block body %} Você não está registrado! {% endblock %}
-
Modifique
registrants.html
para ser mostrado como a seguir:{% extends "layout.html" %} {% block body %} <h1>Registrados</h1> <table> <thead> <tr> <th>Nome</th> <th>Esporte</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">Cancelar registro</button> </form> </td> </tr> {% endfor %} </tbody> </table> {% endblock %}
-
Observe que um valor oculto
registrant.id
é incluído para que seja possível usar oid
depois emapp.py
Por fim, modifique app.py
conforme segue:
```python
Implementa um formulário de registro, armazena registrados em um banco de dados SQLite, com suporte para cancelamento de registro
from cs50 import SQL from flask import Flask, redirect, render_template, request
app = Flask(name)
db = SQL("sqlite:///froshims.db")
SPORTS = [ "Basquetebol", "Futebol", "Ultimate Frisbee" ]
@app.route("/") def index(): return render_template("index.html", sports=SPORTS)
@app.route("/deregister", methods=["POST"]) def deregister():
# Esquece o registrado
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():
# Valida envio
name = request.form.get("name")
sport = request.form.get("sport")
if not name or sport not in SPORTS:
return render_template("failure.html")
# Lembra do registrado
db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport)
# Confirma registro
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 para register
** é incluída para o método post
. Essa rota pegará o nome e esporte obtidos a partir do formulário de registro e executará uma consulta SQL para adicionar o nome
e o esporte
à tabela registrants
. A rota deregister
direciona para uma consulta SQL que obterá o id
do usuário e utilizará essa informação para cancelar o registro desse indivíduo.
Você pode saber mais na documentação do Flask.
Sessão
- Enquanto o código acima é útil sob um ponto de vista administrativo, onde um administrador de back-office pode adicionar e remover usuários do banco de dados, pode-se imaginar como este código não é seguro para implementar num servidor público.
- De um lado, agentes maliciosos poderiam tomar decisões em nome de outros usuários ao clicar no botão de cancelamento de cadastro - excluindo efetivamente sua resposta gravada do servidor.
- Serviços da web como o Google usam credenciais de login para garantir que os usuários tenham acesso apenas aos dados certos.
- Na verdade, podemos implementar isso usado cookies. Cookies são arquivos pequenos que são armazenados em seu computador, assim sendo o seu computador pode se comunicar com o servidor e efetivamente dizer, "Sou um usuário autorizado que já fez login."
- Na forma mais simples, podemos implementar isso criando uma pasta chamada
login
e então adicionando os seguintes arquivos. -
Primeiro, crie um arquivo chamado
requirements.txt
que lê como a seguir:Flask Flask-Session
Observe que além de
Flask
, também incluímosFlask-Session
, que é necessário para suportar sessões de login. -
Segundo, numa pasta
templates
, crie um arquivo chamadolayout.html
que aparece como segue:<!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 oferece um layout muito simples com um título e um corpo.
-
Terceiro, crie um arquivo na pasta
templates
chamadoindex.html
que aparece como segue:{% extends "layout.html" %} {% block body %} {% if session["name"] %} Você está logado como {{ session["name"] }}. <a href="/logout">Sair</a>. {% else %} Você não está logado. <a href="/login">Logar</a>. {% endif %} {% endblock %}
Observe que este arquivo procura ver se
session["name"]
existe. Se existir, ele mostrará uma mensagem de boas-vindas. Se não existir, recomendará que você navegue até uma página para fazer login. -
Em quarto lugar, crie um arquivo com o nome
login.html
e adicione o código a seguir:{% extends "layout.html" %} {% block body %} <form action="/login" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Nome" type="text"> <button type="submit">Fazer login</button> </form> {% endblock %}
Note que esse é o layout de uma página básica de login.
-
Por fim, crie um arquivo na pasta
login
chamadoapp.py
e escreva o código como se segue:from flask import Flask, redirect, render_template, request, session from flask_session import Session # Configure o app app = Flask(__name__) # Configure a sessão 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("/")
Note os imports modificados na parte superior do arquivo, incluindo
session
, que permitirá que você use sessões. Mais importante, note comosession["name"]
é usado nas rotaslogin
elogout
. A rotalogin
atribuirá o nome de login fornecido e o atribuirá asession["name"]
. No entanto, na rotalogout
, o logout é implementado simplesmente configurandosession["name"]
comoNone
. -
Você pode ler mais sobre sessões na documentação do Flask.
Armazenamento
- Passando para um exemplo final de utilização da capacidade do Flask de habilitar uma sessão.
- Examinamos o código a seguir para
Armazenamento
emapp.py
. O código a seguir foi mostrado:
```python from cs50 import SQL from flask import Flask, redirect, render_template, request, session from flask_session import Session
Configurar aplicativo
app = Flask(name)
Conectar ao banco de dados
db = SQL("sqlite:///store.db")
Configurar sessão
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():
# Garantir que o carrinho exista
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)
```
Note que cart
é implementado usando uma lista. Os itens podem ser adicionados a esta lista usando os botões Adicionar ao Carrinho
em books.html
. Ao clicar em tal botão, o método post
é invocado, onde o id
do item é anexado ao cart
. Ao visualizar o carrinho, invocando o método get
, o SQL é executado para exibir uma lista dos livros no carrinho.
API
- Uma interface de programação de aplicativo ou API é uma série de especificações que permite que você se conecte com outro serviço. Por exemplo, podemos utilizar a API do IMDB para nos conectarmos com o banco de dados deles. Podemos até integrar APIs para controlar tipos específicos de dados baixáveis de um servidor.
- Vimos um exemplo chamado
shows
. -
Ao analisar
app.py
, vimos o seguinte:# Pesquisa programas usando 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 observar
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 analisar
index.html
, observe que um código AJAX é utilizado para impulsionar 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="Pesquisa" 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 evento é utilizado para consultar dinamicamente o servidor para fornecer uma lista que corresponda ao título fornecido. Isso localizará a tag
ul
no HTML e modificará a página da Web de acordo para incluir a lista de correspondências. -
Você pode obter mais informações na documentação do AJAX.
JSON
- JavaScript Object Notation ou JSON é um arquivo de texto de dicionários com chaves e valores. Essa é uma maneira bruta e amigável ao computador de obter muitos dados.
- JSON é uma forma muito útil de obter dados do servidor.
-
Você pode ver isso em ação em
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>
Enquanto o acima pode ser algo críptico, ele oferece um ponto de partida para você pesquisar por JSON por conta própria para ver como ele pode ser implementado em seus próprios aplicativos web.
-
Você pode ler mais na documentação JSON.
Resumo
Nesta lição, você aprendeu como utilizar Python, SQL e Flask para criar aplicativos web. Especificamente, discutimos…
- GET
- POST
- Flask
- Session
- AJAX
- JSON
Vejo vocês na próxima vez para nossa aula final!