Blueprints y vistas¶
Una función de vista es el código que escribes para responder a las peticiones de tu aplicación. Flask utiliza patrones para hacer coincidir la URL de la solicitud entrante con la vista que debe manejarla. La vista devuelve datos que Flask convierte en una respuesta saliente. Flask también puede ir en la otra dirección y generar una URL a una vista basada en su nombre y argumentos.
Crear un Blueprint¶
Un Blueprint
es una forma de organizar un grupo de vistas y otro código relacionados. En lugar de registrar las vistas y otro código directamente con una aplicación, se registran con un blueprint. Entonces el blueprint se registra con la aplicación cuando está disponible en la función de fábrica.
Flaskr tendrá dos blueprints, uno para las funciones de autenticación y otro para las funciones de las entradas del blog. El código para cada blueprint irá en un módulo separado. Dado que el blog necesita conocer la autenticación, escribirás primero el de autenticación.
import functools
from flask import (
Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash
from flaskr.db import get_db
bp = Blueprint('auth', __name__, url_prefix='/auth')
Esto crea un Blueprint
llamado 'auth'
. Al igual que el objeto de aplicación, el blueprint necesita saber dónde está definido, por lo que se pasa nombre__
como segundo argumento. El url_prefix
se añadirá a todas las URLs asociadas al blueprint.
Importa y registra el blueprint de la fábrica usando app.register_blueprint()
. Coloca el nuevo código al final de la función de fábrica antes de devolver la app.
def create_app():
app = ...
# existing code omitted
from . import auth
app.register_blueprint(auth.bp)
return app
El Blueprint de autenticación tendrá vistas para registrar nuevos usuarios y para iniciar y cerrar la sesión.
La primera vista: Registro¶
Cuando el usuario visita la URL /auth/register
, la vista register
devolverá HTML con un formulario para que lo rellenen. Cuando envíen el formulario, se validará su entrada y se mostrará de nuevo el formulario con un mensaje de error o se creará el nuevo usuario y se irá a la página de inicio de sesión.
Por ahora sólo escribirás el código de la vista. En la siguiente página, escribirás las plantillas para generar el formulario HTML.
@bp.route('/register', methods=('GET', 'POST'))
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
if not username:
error = 'Username is required.'
elif not password:
error = 'Password is required.'
if error is None:
try:
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.commit()
except db.IntegrityError:
error = f"User {username} is already registered."
else:
return redirect(url_for("auth.login"))
flash(error)
return render_template('auth/register.html')
Esto es lo que hace la función de vista register
:
@bp.route
asocia la URL/register
con la función de la vistaregister
. Cuando Flask reciba una petición a/auth/register
, llamará a la vistaregister
y utilizará el valor devuelto como respuesta.Si el usuario ha enviado el formulario,
request.method
será'POST'
. En este caso, empieza a validar la entrada.request.form
es un tipo especial dedict
que mapea las claves y valores del formulario enviado. El usuario introducirá sunombre de usuario
y sucontraseña
.Valida que
nombre de usuario
ycontraseña
no estén vacíos.Si la validación tiene éxito, inserte los nuevos datos del usuario en la base de datos.
db.execute
toma una consulta SQL con marcadores de posición?
para cualquier entrada del usuario, y una tupla de valores para reemplazar los marcadores de posición. La biblioteca de la base de datos se encargará de escapar los valores para que no seas vulnerable a un ataque de inyección SQL.Por seguridad, las contraseñas nunca deben ser almacenadas en la base de datos directamente. En su lugar, se utiliza
generate_password_hash()
para hacer un hash seguro de la contraseña, y ese hash se almacena. Como esta consulta modifica los datos, es necesario llamar después adb.commit()
para guardar los cambios.Se producirá un
sqlite3.IntegrityError
si el nombre de usuario ya existe, lo que debería mostrarse al usuario como otro error de validación.
Después de almacenar al usuario, se le redirige a la página de inicio de sesión.
url_for()
genera la URL para la vista de inicio de sesión basándose en su nombre. Esto es preferible a escribir la URL directamente ya que permite cambiar la URL más tarde sin cambiar todo el código que enlaza con ella.redirect()
genera una respuesta de redirección a la URL generada.Si la validación falla, se muestra el error al usuario.
flash()
almacena mensajes que pueden ser recuperados al renderizar la plantilla.Cuando el usuario navega inicialmente a
auth/register
, o hay un error de validación, se debe mostrar una página HTML con el formulario de registro.render_template()
renderizará una plantilla que contiene el HTML, que escribirás en el siguiente paso del tutorial.
Inicio de sesión¶
Esta vista sigue el mismo patrón que la vista registro
anterior.
@bp.route('/login', methods=('GET', 'POST'))
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM user WHERE username = ?', (username,)
).fetchone()
if user is None:
error = 'Incorrect username.'
elif not check_password_hash(user['password'], password):
error = 'Incorrect password.'
if error is None:
session.clear()
session['user_id'] = user['id']
return redirect(url_for('index'))
flash(error)
return render_template('auth/login.html')
Hay algunas diferencias con la vista register
:
El usuario se consulta primero y se almacena en una variable para su uso posterior.
fetchone()
devuelve una fila de la consulta. Si la consulta no devuelve ningún resultado, devuelveNone
. Más tarde, se utilizaráfetchall()
, que devuelve una lista de todos los resultados.check_password_hash()
realiza el hash de la contraseña enviada de la misma forma que el hash almacenado y los compara de forma segura. Si coinciden, la contraseña es válida.session
es unadict
que almacena datos a través de las peticiones. Cuando la validación tiene éxito, elid
del usuario se almacena en una nueva sesión. Los datos se almacenan en una cookie que se envía al navegador, y éste la devuelve con las siguientes peticiones. Flask firma de forma segura los datos para que no puedan ser manipulados.
Ahora que el id
del usuario está almacenado en el session
, estará disponible en las siguientes peticiones. Al principio de cada petición, si un usuario está conectado, su información debe ser cargada and made available to other views.
@bp.before_app_request
def load_logged_in_user():
user_id = session.get('user_id')
if user_id is None:
g.user = None
else:
g.user = get_db().execute(
'SELECT * FROM user WHERE id = ?', (user_id,)
).fetchone()
bp.before_app_request()
registra una función que se ejecuta antes de la función de vista, sin importar la URL solicitada. load_logged_in_user
comprueba si hay un id de usuario almacenado en la session
y obtiene los datos de ese usuario de la base de datos, almacenándolos en g.user
, que dura lo que dure la petición. Si no hay id de usuario, o si el id no existe, g.user
será None
.
Cierre de sesión¶
Para cerrar la sesión, es necesario eliminar el id de usuario de la session
. Entonces load_logged_in_user
no cargará un usuario en las siguientes peticiones.
@bp.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
Requerir autenticación en otras vistas¶
La creación, edición y eliminación de entradas del blog requerirá que el usuario esté conectado. Se puede utilizar un decorador para comprobar esto para cada vista a la que se aplique.
def login_required(view):
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is None:
return redirect(url_for('auth.login'))
return view(**kwargs)
return wrapped_view
Este decorador devuelve una nueva función de vista que envuelve la vista original a la que se aplica. La nueva función comprueba si hay un usuario cargado y redirige a la página de inicio de sesión en caso contrario. Si se carga un usuario, se llama a la vista original y continúa normalmente. Utilizarás este decorador cuando escribas las vistas del blog.
Endpoints y URLs¶
La función url_for()
genera la URL de una vista basándose en un nombre y unos argumentos. El nombre asociado a una vista también se llama endpoint, y por defecto es el mismo que el nombre de la función de la vista.
Por ejemplo, la vista hello()
que fue añadida a la fábrica de aplicaciones anteriormente en el tutorial tiene el nombre 'hello'
y puede ser enlazada con url_for('hello')
. Si tuviera un argumento, que verás más adelante, se enlazaría con url_for('hello', who='World')
.
Cuando se utiliza un blueprint, el nombre del blueprint se antepone al nombre de la función, por lo que el endpoint de la función login
que escribiste arriba es 'auth.login'
porque lo agregaste al blueprint 'auth'
.
Continuar con Plantillas.