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-serverno 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.pye uma pasta chamadatemplates. - Para começar, crie uma pasta chamada
templatese crie um arquivo chamadoindex.htmlcom 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
templatesaparece, crie um arquivo chamadoapp.pye 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
appcomo a aplicação Flask. Em seguida, ele define a rota/deappcomo retornando o conteúdo deindex.htmlcom o argumentoname. Por padrão, a funçãorequest.args.getirá procurar pelo argumentonamefornecido pelo usuário. Se nenhum nome for fornecido, ele irá usarworldcomo padrão. -
Por fim, adicione um arquivo final na mesma pasta que
app.pychamadorequirements.txtque contém apenas uma única linha de código:FlaskObserve que apenas
Flaskaparece neste arquivo. -
Você pode executar este arquivo digitando
flask runna 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.txtUma 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.pyda 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
/greetpassará onamepara essa página da web. -
Para finalizar esta implementação, você precisará de outro modelo para
greet.htmlconforme 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.htmlegreet.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.htmle 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.htmlda 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.htmlda 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
postpara ajudar com esse problema, modificando o arquivoapp.pyda 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/greete que usamosrequest.form.getem vez derequest.args.get. - Isso diz ao servidor para procurar mais profundamente no envelope virtual e não revelar os itens em
postna URL. -
Ainda assim, este código pode ser avançado ainda mais utilizando uma única rota para ambos
getepost. Para fazer isso, modifiqueapp.pyda 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
getquanto o métodopostsã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 froshimsno terminal. Em seguida, digitecd froshimspara navegar até essa pasta. Dentro dela, crie um diretório chamado templates digitandomkdir templates. Por fim, digitecode app.pye 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 camponameousportnão for preenchido corretamente. -
Em seguida, crie um arquivo na pasta
templateschamadoindex.htmldigitandocode templates/index.htmle 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.htmldigitandocode templates/layout.htmle 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.htmlda seguinte forma:{% extends "layout.html" %} {% block body %} You are registered! {% endblock %} -
Por fim, crie um arquivo chamado
failure.htmlna 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.pyda 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=REGISTRANTSpassa o dicionário para este modelo. -
Além disso, crie um novo modelo chamado
registrants.htmlda 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 rune inserindo vários nomes e esportes, você pode navegar até/registrantspara 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.txtda seguinte maneira:cs50 Flask -
Modifique o
index.htmlda 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.htmlda 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.htmlapareça da seguinte forma:{% extends "layout.html" %} {% block body %} You are not registered! {% endblock %} -
Modifique
registrants.htmlpara 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 esseidposteriormente no arquivoapp.py -
Por fim, modifique o arquivo
app.pyda 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étodoregisterpara 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 onomee oesporteà tabelaregistrants. A rotaderegisterdireciona para uma consulta SQL que irá obter oiddo 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
logine, em seguida, adicionando os seguintes arquivos. -
Primeiro, crie um arquivo chamado
requirements.txtque contenha o seguinte:Flask Flask-SessionNote 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.htmlque 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
templateschamadoindex.htmlque 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.htmle 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
loginchamadoapp.pye 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 rotasloginelogout. A rotaloginatribuirá 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
storeemapp.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 Cartembooks.html. Ao clicar nesse botão, o métodoposté invocado, onde oiddo 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
searchexecuta 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
ulno 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.htmlque 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!