Clase 9
- ¡Bienvenidos!
- De estático a dinámico
- Flask
- Diseño
- POST
- Mensajes instantáneos de novatos
- Flask y SQL
- Sesión
- Almacenamiento
- API
- JSON
- Resumen
¡Bienvenidos!
- En las semanas anteriores, aprendiste numerosos lenguajes de programación, técnicas y estrategias.
- De hecho, esta clase ha sido mucho menos una clase de C o clase de Python y mucho más una clase de programación, de tal manera que puedas seguir tendencias futuras.
- En las últimas semanas, aprendiste cómo aprender sobre programación.
- Hoy, pasaremos de HTML y CSS a combinar HTML, CSS, SQL, Python y JavaScript para que puedas crear tus propias aplicaciones web.
De estático a dinámico
- Hasta este punto, todo el HTML que viste estaba preescrito y era estático.
- En el pasado, cuando visitabas una página, el navegador descargaba una página HTML y podías verla.
- Las páginas dinámicas se refieren a la capacidad de Python y lenguajes similares de crear archivos HTML al vuelo. Por consiguiente, puedes tener páginas web que se generan mediante opciones seleccionadas por tu usuario.
- En el pasado, utilizaste
http-server
para alojar tus páginas web. Hoy, vamos a utilizar un servidor nuevo que puede analizar una dirección web y realizar acciones según la URL proporcionada.
Flask
- Flask es una biblioteca de terceros que te permite alojar aplicaciones web utilizando el marco Flask dentro de Python.
- Puedes ejecutar Flask mediante la ejecución de
flask run
. - Para hacerlo, necesitarás un archivo llamado
app.py
y una carpeta llamadatemplates
. -
Para comenzar, crea una carpeta llamada
templates
y crea un archivo llamadoindex.html
con el siguiente 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>
-
Observa el
{{ name }}
doble que es un marcador de posición para algo que luego proporcionará nuestro servidor Flask. -
Luego, en la misma carpeta donde aparece la carpeta
templates
, crea un archivo llamadoapp.py
y agrega el siguiente código:# Saluda al usuario 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"))
-
Observa que este código define
app
como la aplicación Flask. Luego, define la ruta/
deapp
como el contenido de retorno deindex.html
con el argumento dename
. De manera predeterminada, la funciónrequest.args.get
buscará elname
proporcionado por el usuario. Si no se proporciona ningún nombre, tomará "world" de manera predeterminada. -
Finalmente, en la misma carpeta que "app.py", agrega un archivo final denominado "requirements.txt" que tiene solo una línea de código:
Flask
Observa que solo "Flask" aparece en este archivo.
-
Puedes ejecutar este archivo ingresando "flask run" en la ventana de tu terminal. Si Flask no se ejecuta, asegúrate de que tu sintaxis sea correcta en cada uno de los archivos anteriores. Además, si Flask no se ejecuta, asegúrate de que tus archivos estén organizados así:
/templates index.html app.py requirements.txt
Una vez que lo hagas funcionar, se te pedirá que hagas clic en un enlace. Una vez que navegues a esa página web, intenta agregar "?name=[Tu nombre]" a la URL base en la barra de URL de tu navegador.
-
Mejorando nuestro programa, sabemos que la mayoría de los usuarios no ingresarán argumentos en la barra de direcciones. En cambio, los programadores confían en que los usuarios completen formularios en las páginas web. En consecuencia, podemos modificar index.html de la siguiente manera:
<!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>
Observa que ahora se crea un formulario que toma el nombre del usuario y luego se lo entrega a una ruta denominada "/greet".
-
Además, podemos cambiar "app.py" de la siguiente manera:
# Agrega un formulario, segunda ruta from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") @app.route("/greet") def greet(): name = request.args.get("name", "world") return render_template("greet.html", name=name)
Observa que la ruta predeterminada mostrará un formulario para que el usuario ingrese su nombre. La ruta "/greet" pasará el "nombre" a esa página web.
-
Para finalizar esta implementación, necesitarás otra plantilla para "greet.html" de la siguiente manera:
<!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>
Observa que esta ruta ahora mostrará el saludo al usuario, seguido de su nombre.
Presentación
- Nuestras dos páginas web,
index.html
ygreet.html
, tienen muchos de los mismos datos. ¿No sería bueno permitir que el cuerpo sea único, pero copiar la misma presentación de una página a otra? -
Primero, cree una nueva plantilla llamada
layout.html
y escriba el código como se indica a continuación:<!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 la inserción de otro código desde otros archivos HTML. -
A continuación, modifique su
index.html
de la siguiente manera:{% extends "layout.html" %} {% block body %} <form action="/greet" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <button type="submit">Saludo</button> </form> {% endblock %}
Observe que la línea
{% extends "layout.html" %}
le indica al servidor dónde obtener la presentación de esta página. Entonces,{% block body %}{% endblock %}
indica qué código se insertará enlayout.html
. -
Finalmente, cambie
greet.html
de la siguiente manera:{% extends "layout.html" %} {% block body %} hola, {{ name }} {% endblock %}
Observe cómo este código es más corto y más compacto.
POST
- Puedes imaginar situaciones en las que no es seguro utilizar
get
ya que los nombres de usuario y las contraseñas aparecerían en la URL. -
Podemos usar el método
post
para solucionar este problema modificandoapp.py
como se muestra a continuación:# Cambia a 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"))
Observa que
POST
se agrega a la ruta/greet
y que usamosrequest.form.get
en lugar derequest.args.get
. -
Esto le indica al servidor que busque más profundamente en el sobre virtual y no revele los elementos en
post
en la URL. -
Aún así, este código puede mejorarse aún más utilizando una sola ruta para
get
ypost
. Para esto, modificaapp.py
como se muestra a continuación:# Usa una sola ruta 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")
Observa que tanto
get
comopost
se realizan en un solo enrutamiento. Sin embargo,request.method
se utiliza para enrutar correctamente según el tipo de enrutamiento solicitado por el usuario.
Los servicios de mensajería instantánea para los alumnos de primer año
- Los servicios de mensajería instantánea para los alumnos de primer año o froshims es una aplicación web que permite a los estudiantes registrarse para deportes intramuros.
-
Crea una carpeta escribiendo
mkdir froshims
en la ventana de la terminal. Luego, escribecd froshims
para navegar a esta carpeta. Dentro, crea un directorio llamado templates escribiendomkdir templates
. Finalmente, escribecode app.py
y escribe el código de la siguiente manera:# Implementa un formulario de registro utilizando un menú de selección de flask import Flask, render_template, request app = Flask(__name__) DEPORTES = [ "Baloncesto", "Fútbol", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", sports=DEPORTES) @app.route("/register", methods=["POST"]) def register(): # Validar la aportación if not request.form.get("name") or request.form.get("sport") not in DEPORTES: return render_template("failure.html") # Confirmar el registro return render_template("success.html")
Observa que se proporciona una opción de
failure
o fracaso, de modo que se mostrará un mensaje de fracaso al usuario si el camponame
osport
no se rellenan correctamente. -
A continuación, crea un archivo en la carpeta
templates
llamadoindex.html
escribiendocode templates/index.html
y escribe el código de la siguiente manera:{% extends "layout.html" %} {% block body %} <h1>Registro</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Nombre" type="text"> <select name="sport"> <option disabled selected>Deporte</option> {% for sport in sports %} <option value="{{ sport }}">{{ sport }}</option> {% endfor %} </select> <button type="submit">Registrar</button> </form> {% endblock %}
-
A continuación, crea un archivo llamado
layout.html
escribiendocode templates/layout.html
y escribe el código de la siguiente manera:<!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>
-
En cuarto lugar, crea un archivo en las plantillas llamado
success.html
de la siguiente manera:{% extends "layout.html" %} {% block body %} ¡Estás registrado! {% endblock %}
-
Finalmente, crea un archivo en las plantillas llamado
failure.html
de la siguiente manera:{% extends "layout.html" %} {% block body %} ¡No estás registrado! {% endblock %}
-
Puedes imaginar cómo podríamos querer aceptar el registro de muchos registrantes diferentes. Podemos mejorar
app.py
de la siguiente manera:# Implementa un formulario de registro, almacenando registrantes en un diccionario, con mensajes de error de flask importar Flask, redirigir, render_template, solicitar app = Flask(__name__) REGISTRANTES = {} DEPORTES = [ "Basquetbol", "Fútbol", 'Ultimate Frisbee' ] @app.route("/") def index(): return render_template("index.html", deportes = DEPORTES) @app.route("/registro", métodos=["POST"]) def registrarse(): # Validar nombre nombre = request.form.get("nombre") if not nombre: return render_template("error.html", mensaje="Falta nombre") # Validar deporte deporte = request.form.get("deporte") si no deporte: return render_template("error.html", mensaje="Falta nombre") si el deporte no está en DEPORTES: return render_template("error.html", mensaje="Deporte inválido") # Recordar registrante REGISTRANTES[nombre] = deporte # Confirmar registro return redireccionar("/registrantes") @app.route("/registrantes") def registrantes(): return render_template("registrantes.html", registrantes=REGISTRANTES)
Nota que se utiliza un diccionario llamado
REGISTRANTES
para registrar eldeporte
seleccionado porREGISTRANTES[nombre]
. Además, observa queregistrantes=REGISTRANTES
pasa el diccionario a esta plantilla. -
Además, crea una nueva plantilla llamada
registrantes.html
de la siguiente manera:{% extiende "layout.html" %} {% bloque cuerpo %} <h1>Registrantes</h1> <table> <thead> <tr> <th>Nombre</th> <th>Deporte</th> </tr> </thead> <tbody> {% para nombre en registrantes %} <tr> <td>{{ nombre }}</td> <td>{{ registrantes[nombre] }}</td> </tr> {% endfor %} </tbody> </table> {% endblock %}
Ten en cuenta que
{% para nombre en registrantes %}...{% endfor %}
iterará por cada uno de los registrantes. ¡Muy poderoso para poder iterar en una página web dinámica! -
Ejecutando
flask run
y entrando varios nombres y deportes, puedes navegar a/registrantes
para ver qué datos se han registrado. - ¡Ahora tienes una aplicación web! Sin embargo, ¡hay algunas fallas de seguridad! Debido a que todo está en el lado del cliente, un adversario podría cambiar el HTML y hackear un sitio web. Además, estos datos no persistirán si el servidor se apaga. ¿Podría haber alguna forma de que nuestros datos pudieran persistir incluso cuando el servidor se reinicie?
Flask y SQL
- Así como hemos visto cómo Python puede interactuar con una base de datos SQL, podemos combinar el poder de Flask, Python y SQL para crear una aplicación web ¡donde los datos persistirán!
- Para implementar esto, necesitarás seguir varios pasos.
-
Primero, modificar
requirements.txt
de la siguiente manera:cs50 Flask
-
Modifica
index.html
de la siguiente manera:{% extends "layout.html" %} {% block body %} <h1>Registrarse</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Nombre" type="text"> {% for sport in sports %} <input name="sport" type="radio" value="{{ sport }}"> {{ sport }} {% endfor %} <button type="submit">Registrar</button> </form> {% endblock %}
-
Modifica
layout.html
de la siguiente manera:<!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>
-
Asegúrate de que
failure.html
aparezca de la siguiente manera:{% extends "layout.html" %} {% block body %} ¡No estás registrado! {% endblock %}
-
Modifica
registrants.html
para que aparezca de la siguiente manera:{% extends "layout.html" %} {% block body %} <h1>Registrados</h1> <table> <thead> <tr> <th>Nombre</th> <th>Deporte</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">Eliminar registro</button> </form> </td> </tr> {% endfor %} </tbody> </table> {% endblock %}
-
Observa que se incluye un valor oculto
registrant.id
de manera que sea posible utilizar esteid
más adelante enapp.py
-
Finalmente, modifica
app.py
de la siguiente manera:# Implementa un formulario de registro, almacenando a los registrados en una base de datos de SQLite, con soporte para la cancelación del registro from cs50 import SQL from flask import Flask, redirect, render_template, request app = Flask(__name__) db = SQL("sqlite:///froshims.db") SPORTS = [ "Basquetbol", "Fútbol", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", sports=SPORTS) @app.route("/deregister", methods=["POST"]) def deregister(): # Olvidar al 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(): # Validar el envío name = request.form.get("name") sport = request.form.get("sport") if not name or sport not in SPORTS: return render_template("failure.html") # Recordar al registrado db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport) # Confirmar el registro return redirect("/registrants") @app.route("/registrants") def registrants(): registrants = db.execute("SELECT * FROM registrants") return render_template("registrants.html", registrants=registrants)
Ten en cuenta que se utiliza la librería
cs50
. Se incluye una ruta pararegister
para el métodopost
. Esta ruta tomará el nombre y el deporte tomados del formulario de registro y ejecutará una consulta SQL para agregar elnombre
y eldeporte
a la tablaregistrants
. La rutaderegister
va a una consulta SQL que tomará elid
del usuario y utilizará esa información para cancelar el registro de esta persona. -
Puedes leer más en la documentación de Flask.
Sesión
- Si bien el código anterior es útil desde un punto de vista administrativo, donde un administrador de la oficina interna podría agregar y eliminar personas de la base de datos, uno puede imaginarse cómo este código no es seguro de implementar en un servidor público.
- Por un lado, los actores malintencionados podrían tomar decisiones en nombre de otros usuarios presionando el botón de anulación del registro, eliminando efectivamente su respuesta grabada del servidor.
- Los servicios web como Google utilizan credenciales de inicio de sesión para garantizar que los usuarios solo tengan acceso a los datos correctos.
- En realidad, podemos implementar esto usando cookies. Las cookies son pequeños archivos que se almacenan en su computadora, de modo que su computadora puede comunicarse con el servidor y decir efectivamente: "Soy un usuario autorizado que ya ha iniciado sesión".
- En la forma más simple, podemos implementar esto creando una carpeta llamada
login
y luego agregando los siguientes archivos. - Primero, crea un archivo llamado
requirements.txt
que se lee de la siguiente manera:Flask Flask-Session
Tenga en cuenta que además de Flask
, también incluimos Flask-Session
, que es necesario para admitir sesiones de inicio de sesión.
- En segundo lugar, en una carpeta
templates
, crea un archivo llamadolayout.html
que aparece de la siguiente manera:<!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>
Tenga en cuenta que esto proporciona un diseño muy simple con un título y un cuerpo.
- En tercer lugar, crea un archivo en la carpeta
templates
llamadoindex.html
que aparece de la siguiente manera:{% extends "layout.html" %} {% block body %} {% if session["name"] %} Has iniciado sesión como {{ session["name"] }}. <a href="/logout">Cerrar sesión</a>. {% else %} No has iniciado sesión. <a href="/login">Iniciar sesión</a>. {% endif %} {% endblock %}
Tenga en cuenta que este archivo busca ver si existe session["name"]
. Si es así, mostrará un mensaje de bienvenida. De lo contrario, te recomendará navegar a una página para iniciar sesión.
-
En cuarto lugar, crea un archivo llamado
login.html
y agrega el siguiente código:{% extends "layout.html" %} {% block body %} <form action="/login" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Nombre" type="text"> <button type="submit">Iniciar sesión</button> </form> {% endblock %}
Observa que este es el diseño de una página de inicio de sesión básica.
-
Finalmente, crea un archivo en la carpeta
login
llamadoapp.py
y escribe el código de la siguiente manera:from flask import Flask, redirect, render_template, request, session from flask_session import Session # Configura la aplicación app = Flask(__name__) # Configura la sesión 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("/")
Observa las importaciones modificadas en la parte superior del archivo, incluyendo
session
, que te permitirá usar sesiones. Lo más importante, observa cómo se usasession["name"]
en las rutaslogin
ylogout
. La rutalogin
asignará el nombre de inicio de sesión proporcionado y lo asignará asession["name"]
. Sin embargo, en la rutalogout
, el cierre de sesión se implementa simplemente configurandosession["name"]
aNone
. -
Puedes leer más sobre sesiones en la documentación de Flask.
Tienda
- Pasemos a un ejemplo final de utilizar la capacidad de Flask para habilitar una sesión.
-
Examinamos el siguiente código para
store
enapp.py
. Se mostró el siguiente código: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)
Tenga en cuenta que
cart
se implementa utilizando una lista. Se pueden agregar elementos a esta lista utilizando los botonesAgregar al carrito
enbooks.html
. Al hacer clic en dicho botón, se invoca el métodopost
, donde elid
del elemento se agrega alcart
. Al ver el carrito, al invocar el métodoget
, SQL se ejecuta para mostrar una lista de los libros en el carrito.
API
- Una interfaz de programa de aplicación o API es una serie de especificaciones que te permiten interactuar con otros servicios. Por ejemplo, podríamos utilizar la API de IMDB para interactuar con su base de datos. Incluso podríamos integrar API para manejar tipos específicos de datos descargables desde un servidor.
- Vimos un ejemplo denominado
shows
. -
Al ver
app.py
, encontramos lo siguiente:# Busca 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)
Ten en cuenta que la ruta
search
ejecuta una consulta SQL. -
Al ver
search.html
, notarás que es muy simple:{% for show in shows %} <li>{{ show["title"] }}</li> {% endfor %}
Ten en cuenta que proporciona una lista con viñetas.
-
Finalmente, al ver
index.html
, ten en cuenta que se utiliza el código AJAX para impulsar la búsqueda:<!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>
Ten en cuenta que se utiliza un detector de eventos para consultar el servidor de forma dinámica y proporcionar una lista que coincida con el título provisto. Esto ubicará la etiqueta ul
en el HTML y modificará la página web según corresponda para incluir la lista de coincidencias.
- Puedes obtener más información en la Documentación de AJAX.
JSON
- _ JavaScript Object Notation_ o _ JSON_ es un archivo de texto de diccionarios con claves y valores. Esta es una forma cruda y fácil de usar para las computadoras de obtener muchos datos.
- JSON es una forma muy útil de obtener datos del servidor.
- Puedes ver esto en acción en el
índice.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>
Si bien lo anterior puede ser algo críptico, te proporciona un punto de partida para que investigues JSON por tu cuenta y veas cómo se puede implementar en tus propias aplicaciones web.
- Puedes leer más en la documentación de JSON.
Resumen
En esta lección, aprendiste a utilizar Python, SQL y Flask para crear aplicaciones web. En concreto, hemos hablado de...
- GET
- POST
- Flask
- Session
- AJAX
- JSON
¡Hasta la próxima para nuestra conferencia final!