Login con Flask

Archivada en Desarrollo Web

Login con Flask

Esta es la traducción de la quinta parte del megatutorial de Flask hecho por Miguel Gringberg[^1]. La aplicación tiene por nombre microblog y se trata de crear una aplicación similar a Twitter, aumentando la complejidad de cada capítulo.

Configuración

Al igual que hicimos en capítulos previos, empezaremos configurando las extensiones de Flask que vamos a usar. Para el sistema de usuarios usaremos dos extensiones: Flask-Login y Flask-OpenID. La primera maneja a los usuarios registrados, mientras que Flask-OpenID se encarga de la autenticación. Estas extensiones se configuran en el archivo app/__init__.py:

from flask.ext.login import LoginManager
from flask.ext.openid import OpenID
from config import BASEDIR

lm = LoginManager()
lm.init_app(app)
lm.login_view = 'login'
oid = OpenID(app, BASEDIR.child('tmp'))

La extensión Flask-OpenID requiere una ruta a un directorio temporal para almacenar archivos. Por eso debemos especificar la ubicación del directorio temporal tmp, y a diferencia del tutorial del autor, yo utilicé el módulo Unipath con el que estoy más acostumbrado, simplemente defino la constante BASEDIR en el archivo config.py

from unipath import Path
BASEDIR = Path(__file__).ancestor(1)

Y a partir de ahí tengo un objeto con el que puedo definir, crear, verificar o borrar rutas y archivos.

Compatibilidad con Python 3

Debes asegurarte que la versión de Flask-OpenID sea la 1.2.4 o superior. Para verificarlo, ejecuta el siguiente comando:

pip freeze

Si obtienes una inferior a la mencionada, debes desinstalar la versión actual e instalar la nueva.

$ pip uninstall flask-openid
$ pip install git+git://github.com/mitsuhiko/flask-openid.git

Toma en cuenta que debes tener instalado el programa git.

Revisando el modelo User

La extensión Flask-Login necesita que se implementen ciertos métodos en la clase User, pero no hay ningún requisito especial sobre como implementarlos.

Aquí está la clase User tal como aparece en el archivo app/models.py:

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nickname = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    posts = db.relationship('Post', backref='author', lazy='dynamic')

    def is_authenticated(self):
        return True

    def is_active(self):
        return True

    def is_anonymous(self):
        return False

    def get_id(self):
        try:
            return unicode(self.id)  # python 2
        except NameError:
            return str(self.id)  # python 3

    def __repr__(self):
        return '<Usuario %r>' % (self.nickname)

El método is_authenticated tiene un nombre un tanto confuso. En general este método debe regresar True a menos que el objeto que representa al usuario no esté autenticado por alguna razón.

El método is_anonymous suele retornar True solo si los usuarios no están registrados en el sistema.

Finalmente, el método get_id regresa el identificador único del usuario en formato unicode. Usamos el identificador generado por la base de datos. Debido a la diferencia en el manejo de unicode entre Python 2 y 3 se proporcionan alternativas para ambas versiones.

Llamando al cargador de User

Ahora estamos listos para implementar el sistema de ingreso usando las extensiones Flask-Login y Flask-OpenID.

Primero vamos a escribir una función que cargue los usuarios desde la base de datos. Esta función será utilizada por Flask-Login en el archivo app/views.py:

@lm.user_loader
def load_user(id):
    return User.query.get(int(id))

Hay que hacer notar que esta función se registra con Flask-Login usando el decorador lm.user_loader. Y también es importante tener presente que los identificadores de usuario que proporciona Flask-Login son siempre cadenas unicode, así que debemos convertirlos en enteros antes de enviar el id a Flask-Alchemy.

La función login

Ahora vamos a actualizar la función login() en el mismo archivo app/views.py:

from flask import render_template, flash, redirect, session, url_for, request, g
from flask.ext.login import login_user, logout_user, current_user, login_required
from app import app, db, lm, oid
from .forms import LoginForm
from .models import User

@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
    if g.user is not None and g.user.is_authenticated():
        return redirect(url_for('index'))
    form = LoginForm()
    if form.validate_on_submit():
        session['remember_me'] = form.remember_me.data
        return oid.try_login(form.openid.data, ask_for=['nickname', 'email'])
    return render_template(
        'login.html',
        title='Sign In',
        form=form,
        providers=app.config['OPENID_PROVIDERS']
    )

Para esta función utilizamos varios módulos, que explicaremos mas tarde.

Los cambios con respecto a nuestra función anterior son muy pequeños. Hemos agregado un nuevo decorador a nuestra función, oid.loginhandler que le dice a Flask-OpenID que esta la función que maneja el ingreso de usuarios.

Al principio del cuerpo de la función vemos que g.users se asigna a un usuario autenticado, y en su caso lo redirigimos a la página inicial. La idea es que si es un usuario ya registrado no lo mandemos de nuevo a la página de login.

El objeto global g lo proporciona Flask como un lugar para almacenar y compartir datos durante toda la vida de una solicitud (o request, como lo llamaremos de ahora en adelante). Y como seguramente habrán adivinado, lo usaremos para almacenar a los usuarios registrados.

La función url_for la usamos en la llamada redirect definida por Flask como una forma limpia de obtener la URL de una vista dada. Si quieres redireccionar a la página inicial, podemos usar redirect('/index) pero hay muy buenas razones para que Flask construya las URL por nosotros[^2].

Javier Sanchez Toledano

Auditor Líder ISO 9000 ● Desarrollo Web Full Stack ● Django/Python ● JavaScript · NodeJS · Angular · VueJS · EmberJS ● Swift 4 ● WordPress · Genesis Framework ● Lector ● Fotografo aficionado ● Generación X ● Soy de Tlaxcala

Tlaxcala, México

Comentarios