El Mega Tutorial de Flask, Parte V: Acceso de Usuarios

El tutorial a continuación es la traducción libre realizada por el autor de este blog, del post escrito en inglés por Miguel. Por lo que aunque pueda variar algo respecto al original, no se realizó ningún aporte cuantitativo al mismo(en todo caso se reduce a usuarios GNU/Linux exclusivamente). Y ha sido el propio Miguel el que con sus respuestas ha ayudado ha aportar mayor información respecto al original.

Este es el quinto artículo de la serie en la que documento mi experiencia escribiendo aplicaciones web en Python usando el microframework Flask.

El objetivo de esta serie es desarrollar una aplicación decente de microblogging que demostrando mi total falta de originalidad he decidido llamar microblog.

NOTA: Este artículo fué revisado en septiembre de 2014 para estar acorde a las versiones actuales de Python y Flask.

Aquí un índice de todos los artículos de la serie que han sido publicados hasta la fecha:

  • Parte I: Hola Mundo!
  • Parte II: Plantillas
  • Parte III: Formularios
  • Parte IV: Base de datos
  • Parte V: Acceso de Usuarios (este artículo)
  • Parte VI: Páginas de perfil y Avatares [aún no traducido]
  • Parte VII: Unit Testing [aún no traducido]
  • Parte VIII: Seguidores, contactos y amigos [aún no traducido]
  • Parte IX: Paginado [aún no traducido]
  • Parte X: Búsqueda completa de texto [aún no traducido]
  • Parte XI: Soporte Email [aún no traducido]
  • Parte XII: Lavado de cara [aún no traducido]
  • Parte XIII: Fechas y Tiempos [aún no traducido]
  • Parte XIV: I18n y L10n [aún no traducido]
  • Parte XV: Ajax [aún no traducido]
  • Parte XVI: Depuración, Pruebas y Perfilado [aún no traducido]
  • Parte XVII: Despliegue en GNU/Linux (incluso la Raspberry Pi!) [aún no traducido]
  • Parte XVIII: Despliegue en la nube Heroku [aún no traducido]

Recap

In the previous chapter of the series we created our database and learned how to populate it with users and posts, but we haven’t hooked up any of that into our app yet. And two chapters ago we’ve seen how to create web forms and left with a fully implemented login form.

In this article we are going to build on what we learned about web forms and databases and write our user login system. At the end of this tutorial our little application will register new users and log them in and out.

To follow this chapter along you need to have the microblog app as we left it at the end of the previous chapter. Please make sure the app is installed and running.

Configuration

As in previous chapters, we start by configuring the Flask extensions that we will use. For the login system we will use two extensions, Flask-Login and Flask-OpenID. Flask-Login will handle our users logged in state, while Flask-OpenID will provide authentication. These extensions are configured as follows (file app/__init__.py):

import os
from flask.ext.login import LoginManager
from flask.ext.openid import OpenID
from config import basedir

lm = LoginManager()
lm.init_app(app)
oid = OpenID(app, os.path.join(basedir, 'tmp'))

The Flask-OpenID extension requires a path to a temp folder where files can be stored. For this we provide the location of our tmp folder.

Python 3 Compatiblity

Unfortunately version 1.2.1 of Flask-OpenID (the current official version) does not work well with Python 3. Check what version you have by running the following command:

$ flask/bin/pip freeze

If you have a version newer than 1.2.1 then the problem is likely resolved, but if you have 1.2.1 and are following this tutorial on Python 3 then you have to install the development version from GitHub:

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

Note that you need to have git installed for this to work.

Revisiting our User model

The Flask-Login extension expects certain methods to be implemented in our User class. Outside of these methods there are no requirements for how the class has to be implemented.

Below is our Flask-Login friendly User class (file 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 '<User %r>' % (self.nickname)

The is_authenticated method has a misleading name. In general this method should just returnTrue unless the object represents a user that should not be allowed to authenticate for some reason.

The is_active method should return True for users unless they are inactive, for example because they have been banned.

The is_anonymous method should return True only for fake users that are not supposed to log in to the system.

Finally, the get_id method should return a unique identifier for the user, in unicode format. We use the unique id generated by the database layer for this. Note that due to the differences in unicode handling between Python 2 and 3 we have to provide two alternative versions of this method.

User loader callback

Now we are ready to start implementing the login system using the Flask-Login and Flask-OpenID extensions.

First, we have to write a function that loads a user from the database. This function will be used by Flask-Login (file app/views.py):

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

Note how this function is registered with Flask-Login through the lm.user_loader decorator. Also remember that user ids in Flask-Login are always unicode strings, so a conversion to an integer is necessary before we can send the id to Flask-SQLAlchemy.

The login view function

Next let’s update our login view function (file 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'])

Notice we have imported several new modules, some of which we will use later.

The changes from our previous version are very small. We have added a new decorator to our view function. The oid.loginhandler tells Flask-OpenID that this is our login view function.

At the top of the function body we check if g.user is set to an authenticated user, and in that case we redirect to the index page. The idea here is that if there is a logged in user already we will not do a second login on top.

The g global is setup by Flask as a place to store and share data during the life of a request. As I’m sure you guessed by now, we will be storing the logged in user here.

The url_for function that we used in the redirect call is defined by Flask as a clean way to obtain the URL for a given view function. If you want to redirect to the index page you may very well use redirect('/index'), but there are very good reasons to let Flask build URLs for you.

The code that runs when we get a data back from the login form is also new. Here we do two things. First we store the value of the remember_me boolean in the flask session, not to be confused with the db.session from Flask-SQLAlchemy. We’ve seen that the flask.g object stores and shares data though the life of a request. The flask.session provides a much more complex service along those lines. Once data is stored in the session object it will be available during that request and any future requests made by the same client. Data remains in the session until explicitly removed. To be able to do this, Flask keeps a different session container for each client of our application.

The oid.try_login call in the following line is the call that triggers the user authentication through Flask-OpenID. The function takes two arguments, the openid given by the user in the web form and a list of data items that we want from the OpenID provider. Since we defined our User class to include nickname and email, those are the items we are going to ask for.

The OpenID authentication happens asynchronously. Flask-OpenID will call a function that is registered with the oid.after_login decorator if the authentication is successful. If the authentication fails the user will be taken back to the login page.

The Flask-OpenID login callback

Here is our implementation of the after_login function (file app/views.py):

@oid.after_login
def after_login(resp):
    if resp.email is None or resp.email == "":
        flash('Invalid login. Please try again.')
        return redirect(url_for('login'))
    user = User.query.filter_by(email=resp.email).first()
    if user is None:
        nickname = resp.nickname
        if nickname is None or nickname == "":
            nickname = resp.email.split('@')[0]
        user = User(nickname=nickname, email=resp.email)
        db.session.add(user)
        db.session.commit()
    remember_me = False
    if 'remember_me' in session:
        remember_me = session['remember_me']
        session.pop('remember_me', None)
    login_user(user, remember = remember_me)
    return redirect(request.args.get('next') or url_for('index'))

The resp argument passed to the after_login function contains information returned by the OpenID provider.

The first if statement is just for validation. We require a valid email, so if an email was not provided we cannot log the user in.

Next, we search our database for the email provided. If the email is not found we consider this a new user, so we add a new user to our database, pretty much as we have learned in the previous chapter. Note that we handle the case of a missing nickname, since some OpenID providers may not have that information.

After that we load the remember_me value from the Flask session, this is the boolean that we stored in the login view function, if it is available.

Then we call Flask-Login’s login_user function, to register this is a valid login.

Finally, in the last line we redirect to the next page, or the index page if a next page was not provided in the request.

The concept of the next page is simple. Let’s say you navigate to a page that requires you to be logged in, but you aren’t just yet. In Flask-Login you can protect views against non logged in users by adding the login_required decorator. If the user tries to access one of the affected URLs then it will be redirected to the login page automatically. Flask-Login will store the original URL as thenext page, and it is up to us to return the user to this page once the login process completed.

For this to work Flask-Login needs to know what view logs users in. We can configure this in the app’s module initializer (file app/__init__.py):

lm = LoginManager()
lm.init_app(app)
lm.login_view = 'login'

The g.user global

If you were paying attention, you will remember that in the login view function we check g.user to determine if a user is already logged in. To implement this we will use the before_request event from Flask. Any functions that are decorated with before_request will run before the view function each time a request is received. So this is the right place to setup our g.user variable (fileapp/views.py):

@app.before_request
def before_request():
    g.user = current_user

This is all it takes. The current_user global is set by Flask-Login, so we just put a copy in the gobject to have better access to it. With this, all requests will have access to the logged in user, even inside templates.

The index view

In a previous chapter we left our index view function using fake objects, because at the time we did not have users or posts in our system. Well, we have users now, so let’s hook that up:

@app.route('/')
@app.route('/index')
@login_required
def index():
    user = g.user
    posts = [
        { 
            'author': {'nickname': 'John'}, 
            'body': 'Beautiful day in Portland!' 
        },
        { 
            'author': {'nickname': 'Susan'}, 
            'body': 'The Avengers movie was so cool!' 
        }
    ]
    return render_template('index.html',
                           title='Home',
                           user=user,
                           posts=posts)

There are only two changes to this function. First, we have added the login_required decorator. This will ensure that this page is only seen by logged in users.

The other change is that we pass g.user down to the template, instead of the fake object we used in the past.

This is a good time to run the application.

When you navigate to http://localhost:5000 you will instead get the login page. Keep in mind that to login with OpenID you have to use the OpenID URL from your provider. You can use one of the OpenID provider links below the URL text field to generate the correct URL for you.

As part of the login process you will be redirected to your provider’s web site, where you will authenticate and authorize the sharing of some information with our application (just the email and nickname that we requested, no passwords or other personal information will be exposed).

Once the login is complete you will be taken to the index page, this time as a logged in user.

Feel free to try the remember_me checkbox. With this option enabled you can close and reopen your web browser and will continue to be logged in.

Logging out

We have implemented the log in, now it’s time to add the log out.

The view function for logging out is extremely simple (file app/views.py):

@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))

But we are also missing a link to logout in the template. We are going to put this link in the top navigation bar which is in the base layout (file app/templates/base.html):

<html>
  <head>
    {% if title %}
    <title>{{ title }} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog:
        <a href="{{ url_for('index') }}">Home</a>
        {% if g.user.is_authenticated() %}
        | <a href="{{ url_for('logout') }}">Logout</a>
        {% endif %}
    </div>
    <hr>
    {% with messages = get_flashed_messages() %}
    {% if messages %}
    <ul>
    {% for message in messages %}
        <li>{{ message }} </li>
    {% endfor %}
    </ul>
    {% endif %}
    {% endwith %}
    {% block content %}{% endblock %}
  </body>
</html>

Note how easy it is to do this. We just needed to check if we have a valid user set in g.user and if we do we just add the logout link. We have also used the opportunity to use url_for in our template.

Final words

We now have a fully functioning user login system. In the next chapter we will be creating the user profile page and will be displaying user avatars on them.

In the meantime, here is the updated application code including all the changes in this article:

Download microblog-0.5.zip.

See you next time!

Miguel

El Mega Tutorial de Flask, Parte IV: Base de Datos

El tutorial a continuación es la traducción libre realizada por el autor de este blog, del post escrito en inglés por Miguel. Por lo que aunque pueda variar algo respecto al original, no se realizó ningún aporte cuantitativo al mismo(en todo caso se reduce a usuarios GNU/Linux exclusivamente). Y ha sido el propio Miguel el que con sus respuestas ha ayudado ha aportar mayor información respecto al original.

Este es el cuarto artículo de la serie en la que documento mi experiencia escribiendo aplicaciones web en Python usando el microframework Flask.

El objetivo de esta serie es desarrollar una aplicación decente de microblogging que demostrando mi total falta de originalidad he decidido llamar microblog.

NOTA: Este artículo fué revisado en septiembre de 2014 para estar acorde a las versiones actuales de Python y Flask.

Aquí un índice de todos los artículos de la serie que han sido publicados hasta la fecha:

  • Parte I: Hola Mundo!
  • Parte II: Plantillas
  • Parte III: Formularios
  • Parte IV: Base de datos (este artículo)
  • Parte V: Acceso de Usuario [aún no traducido]
  • Parte VI: Páginas de perfil y Avatares [aún no traducido]
  • Parte VII: Unit Testing [aún no traducido]
  • Parte VIII: Seguidores, contactos y amigos [aún no traducido]
  • Parte IX: Paginado [aún no traducido]
  • Parte X: Búsqueda completa de texto [aún no traducido]
  • Parte XI: Soporte Email [aún no traducido]
  • Parte XII: Lavado de cara [aún no traducido]
  • Parte XIII: Fechas y Tiempos [aún no traducido]
  • Parte XIV: I18n y L10n [aún no traducido]
  • Parte XV: Ajax [aún no traducido]
  • Parte XVI: Depuración, Pruebas y Perfilado [aún no traducido]
  • Parte XVII: Despliegue en GNU/Linux (incluso la Raspberry Pi!) [aún no traducido]
  • Parte XVIII: Despliegue en la nube Heroku [aún no traducido]

Recapitulando

En el capítulo previo creamos nuestro formulario de ingreso, con un envío de datos y validación de campos vacíos.

En este artículo crearemos nuestra base de datos y la configuraremos para que podamos guardar nuestros usuarios en ella.

Para seguir este capítulo necesitas tener la aplicación microblog como la dejamos al final del capítulo anterior. Por favor asegurate de tener la aplicación instalada y funcionando correctamente.

Corriendo Python scripts desde la línea de comnado

En este capítulo vamos a escribir unos scripts que simplifiquen el manejo de nuestra base de datos. Antes de que entremos a ello, veamos primero como un script Python es ejecutado en la línea de comandos.

Para darle permiso de ejecución al script, ejecuta:

$ chmod a+x script.py

El script tiene una linea shebang, la cual apunta hacia el interprete que debe ser usado. Todo script al que se le ha dado permiso de ejecución y tiene una linea shebang puede ser ejecutado así:

./script.py <arguments>

Base de datos en Flask

Usaremos la extensión Flask-SQLAlchemy para manejar nuestra aplicación. Esta extensión provee un wrapper para el proyecto SQLAlchemy, el cual es un Object Relational Mapper o ORM.

Los ORM permiten a las aplicaciones con base de datos trabajar con objetos en vez de tablas y SQL. Las operaciones realizadas en los objetos son traducidos a comandos de base de datos transparente para el ORM. Conocer SQL puede ser muy útil cuando trabajamos con ORM, pero no aprenderemos SQL en este tutorial, dejaremos que Flask-SQLAlchemy  hable SQL por nosotros.

Migraciones

La mayoría de los tutoriales que he visto cubren la creación y uso de una base de datos pero no tocan adecuadamente el problema de actualizar una base de datos mientras la aplicación crece. Por lo general, terminas teniendo que borrar la base de datos vieja y crear una nueva cada vez que necesites actualizar, perdiendo todos los datos. Y si los datos no pueden ser recreados fácilmente te vez forzado a escribir los scripts de importación y exportación tu mismo.

Afortunadamente, tenemos una mejor opción.

Usaremos SQLAlchemy-migrate para que haga el seguimiento de las actualizaciones de la base de datos por nosotros. Cuesta un poco mas de trabajo poner la base de datos en funcionamiento pero es un pequeño precio a pagar por no tener que preocuparte nunca mas con migraciones manuales de base de datos.

Suficiente teoría, empecemos!.

Configuración

Para nuestra aplicación usaremos una base de dato sqlite. Las base de datos sqlite son la opción mas conveniente para aplicaciones pequeñas, debido a que cada base de datos se guarda en un archivo único y no es necesario iniciar un servidor de base de datos.

Tenemos un par de parámetros a añadir en nuestro archivo de configuración (file config.py):

import os
basedir = os.path.abspath(os.path.dirname(__file__))

SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')

La SQLALCHEMY_DATABASE_URI es requerida por la extensión Flask-SQLAlchemy y no es mas que la dirección local a nuestro archivo de base de datos.

La The SQLALCHEMY_MIGRATE_REPO es la carpeta donde alojaremos los archivos de migración SQLAlchemy-migrate.

Finalmente, cuando inicializamos nuestra aplicación también necesitamos iniciar nuestra base de datos. Así quedaría nuestro archivo init actualizado (file app/__init__.py):

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)

from app import views, models

Nota los dos cambios realizados. Estamos creando ahora un objeto db que será nuestra base de datos,  también estamos importando un nuevo modulo llamado models. A continuación vamos a escribir este nuevo módulo.

El modelo de base de datos

Los datos que guardamos en nuestra base de datos serán representado por una colección de clases que son referidas como modelos de base de datos. La capa ORM hará las traducciones necesarias y convertirá los objetos creados a partir de estas clases  a filas en la base de datos.

Empecemos creando un modelo que representerá a nuestros usuarios. Usando la herramienta WWW SQL Designer he realozado el siguiente diagrama para representar nuestras tablas de usuarios:

users table

El campo id está por lo general en todos los modelos, y es usada como la primary key. Cada usuario en la base de datos le será asignado un único valor de id, guardado en este campo. Afortunadamente esto se hace de manera automática por lo que solo necesitamos proveer el campo id.

Los campos de nickname y email son definidos como strings (o VARCHAR en la jerga de base de datos), y sus longitudes máxima son específicadas para que la base de datos pueda optimizar espacio.

Ahora que hemos decidido como queremos que luzca la taba de usuarios, el trabajo de escribir el código será muy fácil (file app/models.py):

from app import db

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)

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

La clase User que hemos creado contiene varios campos, definidos como variables de clase. Los campos son creados como instancias de la clase db.Column, la cual toma el tipo de campo como un argumento, además otros argumentos opcionales que nos permiten, por ejemplo, indicar cuales campos son indexados y únicos.

El método __repr__ le dice a Python como imprimir objetos de esta clase. La usaremos para realizar depuraciones en el código.

Creando la base de datos

Con le modelo y la configuración en su lugar estamos listos para crear nuestro archivo de base de datos. El paquete SQLAlchemy-migrate viene con herramientas de línea de comandos y APIs para crear base de datos en una forma que permite facilitar actualizaciones en el futuro, y para ello la usaremos. Encuentro incómodo usar las herramientas de línea de comandos, en vez de ello he escrito mi propio set de pequeños scripts que invocan los APIs de migración.

He aquí un script que crea la base de datos (file db_create.py):

#!flask/bin/python
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
from app import db
import os.path
db.create_all()
if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):
    api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')
    api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
else:
    api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))

Nota como este script es completamente genérico. Todas las direcciones específicas de la aplicación son importadas desde el archivo de configuración. Cuando empieces tu propio proyecto puedes copiar el script  para el nuevo directorio de la aplicación y funcionará correctamente.

Para crear la base de datos solo necesites ejecutar este script:

./db_create.py

Despues que corras el comando obtendrás un nuevo archivo app.db. Esta es una base de datos sqlite vacía, creada desde el principio para soportar migraciones. También tendrás un directorio db_repository con algunos archivos adentro. Este es el lugar donde SQLAlchemy-migrate guarda sus archivos de datos. Nota que no necesitamos regenerar el repositorio si ya existe. Esto nos permitirá recrear la base de datos mientras dejamos el repositorio existente si lo necesitamos.

Nuestra primera migración

Ahora que tenemos definido nuestro modelo, podemos incorporarlo dentro de nuestra base de datos. Consideraremos cualquier cambio a la estructura de nuestra base de datos de la aplicación como una migración, por lo que esta será nuestra primera, la cual nos llevara desde una base de datos vacía hacia una base de datos que pueda guardar usuarios.

Para generar una migración uso otro pequeño script en Python como ayuda (archivo db_migrate.py):

#!flask/bin/python
import imp
from migrate.versioning import api
from app import db
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
migration = SQLALCHEMY_MIGRATE_REPO + ('/versions/%03d_migration.py' % (v+1))
tmp_module = imp.new_module('old_model')
old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
exec(old_model, tmp_module.__dict__)
script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata)
open(migration, "wt").write(script)
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('New migration saved as ' + migration)
print('Current database version: ' + str(v))

El script luce complicado, pero en realidad no lo es tanto. La manera en que SQLAlchemy-migrate crea una migración es mediante la comparación de la estructura de la base de datos (obtenida en nuestro caso desde el archivo app.db) contra la estructura de nuestros modelos (obtenidos desde el archivo app/models.py). Las diferencias entre las dos son guardadas como un script de migración dentro del repositorio de migración. El script de migración sabe como aplicar una migración o eliminarla, por lo que siempre es posible degradar o ascender un formato de base de datos. .

Aun cuando nunca he tenido problemas generando migraciones automáticamente con el script de arriba, he podido ver que ciertas veces era difícil determinar que cambios fueron hechos solo por la comparación el formato viejo con el nuevo. Para facilitar a SQLAlchemy-migrate la determinación de los cambios nunca renombro los campos existentes.  Limito mis cambios a remover o añadir modelos o campos, o cambios de tipos de campos existentes. Y siempre reviso el script de migración generado para asegurarme que está todo bien.

Sobra decir que nunca deberías intentar migrar tu base de datos sin tener un respaldo, en caso de algo salga mal. También nunca corras una migración por primera vez en una base de datos de producción, siempre asegúrate de que la migración funcione correctamente en un base de datos de desarrollo.

Vayamos entonces a guardar nuestra migración:

$ ./db_migrate.py

Y la salida desde el script sería:

New migration saved as db_repository/versions/001_migration.py
Current database version: 1

El script muestra donde fue guardado el script de migración, y también imprime la versión actual de la base de datos. La base de datos vacía fué la versión 0, luego de haber migrado para incluir usuarios nos encontramos en a versión 1.

Mejoras y degradaciones de la base de datos

En este punto puedes estar preguntándote porque es tan importante estar guardando migraciones de base de datos. .

Imagina que tienes tu aplicación en tu máquina de desarrollo, y también tienes una copia desplegada en un servidor de producción que está en línea y en uso.

Digamos que para el nuevo lanzamiento de tu aplicación tienes que introducir un cambio a tus modelos, por ejemplo una nueva tabla necesita ser añadida. Sin migraciones tu tendrías que buscar como cambiar el formato de tu base de datos, tanto en tu máquina de desarrollo como en tu servidor, lo que puede resultar un montón de trabajo.

En cambio si tienes soporte para migración de base de datos, cuando estés listo para un nuevo lanzamiento solo necesitarías guardar una nueva migración, copiar los scripts de migración a tu servidor de producción y correr un script simple que aplique los cambios por ti. La actualización de base de datos puede ser realizada con este pequeño script de Python (archivo db_upgrade.py):

#!flask/bin/python
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('Current database version: ' + str(v))

Cuando corras el script de arriba, la base de datos será actualizada a su última versión, mediante la aplicación de scripts de migración guardados en el repositorio de base de datos.

No es una necesidad común tener que degradar una base de datos hacia un viejo formato, pero solo por si acaso, SQLAlchemy-migrate soporta eso tan bien (archivo db_downgrade.py):

#!flask/bin/python
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v - 1)
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('Current database version: ' + str(v))

Este script degradará la base de datos en una versión. Puedes correrlo multiples veces para degradarla a mas versiones.

Relaciones de base de datos

Las bases de datos relacionadas son las que guardan relaciones entre sus campos de datos. Considera el caso de un usuario que escribe una publicación blog. El usuario tendrá un registro en la tabla de users, y la publicación tendrá un registro en la tabla de posts. La manera mas eficiente de registrar quien escribe una publicación dada es enlazar los dos registro relacionados.

Una vez es establecido un enlace entre usuario y publicación existen dos tipos de consultas que podríamos necesitar usar. El mas trivial es cuando tienes una publicación blog y necesitas saber que usuario la escribió. Una consulta mas compleja es la reserva del mismo. Si tienes un usuario, podrías querer saber todas las publicaciones que el escribió el mismo. Flask-SQLAlchemy nos ayudará con ambos tipos de consultas.

Expandamos nuestra base de datos para guardar publicaciones, para que podamos ver las relaciones en acción. Para esto iremos a nuestra herramienta de diseño y crearemos una tabla de posts:

users table

Nuestra tabla de posts tendrá el requerido id, el cuerpo de la publicación y un timestamp. Nada extraño por ahora, pero el campo user_id merece una explicación.

Dijimos que queríamos enlazar los usuarios a sus publicaciones escritas. La manera de hacerlo es anexando un campo a la publicación que contenga el id del usuario que la escribió. Este id es llamado foreign key. Nuestra herramienta de diseño de base de datos muestra las foreign keys como un enlace entre la foreign key y el campo id de la tabla a la que es referido. Este tipo de enlace es llamado one-to-many relationship, un(one) usuario escribe muchos(many) publicaciones.

Modifiquemos nuestros modelos para reflejar estos cambios (app/models.py):

from app import db

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 __repr__(self):
        return '<User %r>' % (self.nickname)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    body = db.Column(db.String(140))
    timestamp = db.Column(db.DateTime)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    def __repr__(self):
        return '<Post %r>' % (self.body)

Hemos añadido la clase Post, la cual representará las publicaciones blog escritas por los usuarios. El campo user_id en la clase Post fue incializado como una foreign key, por lo que Flask-SQLAlchemy sabe que este es el campo que enlazará con un usuario.

Nota que tambien hemos añadido un nuevo campo a la clase User llamado posts, esto se ha hecho como un campo  db.relationship, el cual en realidad no es un campo de base de datos, por lo que no está en nuestro diagrama de base de datos. Para una relación one-to-many un campo db.relationship es normalmente definido en el lado de “one”. Con esta relación obtenemos un miembro user.posts que obtiene una lista de las publicaciones del usuario. El primer argumento de db.relationship indica las “many” clases de esta relación. El argumento backref define un campo que será añadido a los objetos de las “many” clases que apuntan de vuelta al objeto “one”. En nuestro caso esto significa que podemos usar use post.author para obtener la instancia User que creó la publicación. No te preocupes si estos detalles no tienen mucho sentido para ti aún pues veremos ejemplos de esto al final de este artículo.

Guardemos otra migración con este cambio. Corre:

$ ./db_migrate.py

Y el script responderá:

New migration saved as db_repository/versions/002_migration.py
Current database version: 2

No es realmente necesario guardar cada cambio al modelo de base de datos como migraciones por separado, una migración es normalmente guardada como hitos significantes en la historia de un proyecto. Sin embargo haremos mas migraciones de lo necesario solo para mostrar como el sistema de migraciones funciona.

Hora de jugar

Hemos invertido bastante tiempo definiendo nuestra base de datos, pero aun no hemos visto como funciona. Dado que nuestra aplicación no tiene código para base de dato aun usemos nuestra propia base de datos en el interprete Python.

Entonces manos a la obra, iniciemos Python.

flask/bin/python

Una vez en la línea de comandos de Python introduce lo siguiente:

>>> from app import db, models
>>>

Esto trae nuestra base de datos y modelos a la memoria.

Creemos un nuevo usuario:

>>> u = models.User(nickname='john', email='john@email.com')
>>> db.session.add(u)
>>> db.session.commit()
>>>

Los cambios en la base de datos son realizados en el contexto de una sesión. Multiples cambios pueden ser acumulados en una sesión y una vez han sido registrados todos los cambios puedes emitir un único db.session.commit(), el cual escribe los cambios automáticamente. Si en cualquier momento mientras trabajas en una sesión ocurre un error, una llamada a db.session.rollback() revertirá la base de datos a su estado anterior a la sesión. Si no se realiza un commit o un rollback entonces el sistema por hará un rollback por defecto. Las sesiones garantizan que la base de datos nunca presente un estado inconsistente.

Añadamos otro usuario:

>>> u = models.User(nickname='susan', email='susan@email.com')
>>> db.session.add(u)
>>> db.session.commit()
>>>

Ahora podemos consultar quienes son nuestros usuarios:

>>> users = models.User.query.all()
>>> users
[<User u'john'>, <User u'susan'>]
>>> for u in users:
...     print(u.id,u.nickname)
...
1 john
2 susan
>>>

Para esto hemos usado el miembro query, el cual está disponible en todos los modelos de clases. Nota como el miembro  id fue automáticamente configurado para nosotros.

Aquí otra manera de hacer las consultas. Si sabemos la id de un usuario podemos encontrar los datos para el usuario como sigue acontinuación:

>>> u = models.User.query.get(1)
>>> u
<User u'john'>
>>>

Ahora añadamos una publicación de blog:

>>> import datetime
>>> u = models.User.query.get(1)
>>> p = models.Post(body='my first post!', timestamp=datetime.datetime.utcnow(), author=u)
>>> db.session.add(p)
>>> db.session.commit()

Aquí establecemos nuestro timestamp en la zona horaria UTC. Todos los timestamps guardados en nuestra base de datos estarán en UTC. Podemos tener usuarios de todo el mundo escribiendo publicaciones y necesitamos usar unidades de tiempo uniformes. En un futuro tutorial veremos como mostrar estos tiempos en las zonas de tiempo locales de los usuarios.

Puedes haber notado que no establecimos el campo user_id de la clase Post. En cambio, guardamos un objeto User dentro del campo author. El campo author es un campo virtual que fue añadido por Flask-SQLAlchemy para ayudar con las relaciones, hemos definido el nombre de este campo en el argumento backref de db.relationship en nuestro modelo. Con esta información el campo ORM sabrá como completar el user_id por nosotros.

Para completar esta sesión, miremos un poco mas de consultas que podemos realizar:

# get all posts from a user
>>> u = models.User.query.get(1)
>>> u
<User u'john'>
>>> posts = u.posts.all()
>>> posts
[<Post u'my first post!'>]

# obtain author of each post
>>> for p in posts:
...     print(p.id,p.author.nickname,p.body)
...
1 john my first post!

# a user that has no posts
>>> u = models.User.query.get(2)
>>> u
<User u'susan'>
>>> u.posts.all()
[]

# get all users in reverse alphabetical order
>>> models.User.query.order_by('nickname desc').all()
[<User u'susan'>, <User u'john'>]
>>>

La documentación oficial de Flask-SQLAlchemy es el mejor lugar para aprender sobre la cantidad de opciones disponibles para consultar la base de datos.

Antes de que terminemos, borremos el probador de usuarios y publicaciones que hemos creado, para que podamos empezar desde una base de datos limpia en el próximo capítulo:

>>> users = models.User.query.all()
>>> for u in users:
...     db.session.delete(u)
...
>>> posts = models.Post.query.all()
>>> for p in posts:
...     db.session.delete(p)
...
>>> db.session.commit()
>>>

Palabras finales

Este fue un extenso tutorial. Hemos aprendido lo básico del trabajo con las bases de datos, pero aún no hemos incorporado la base de datos dentro de nuestra aplicación. En el siguiente capítulo pondremos todo lo que aprendido en práctica cuando desarrollemos nuestro sistema de ingreso de usuario.

Mientras tanto, su no has estado escribiendo la aplicación al leer, puede que queiras descargarlas en su estado actual:

Descargar microblog-0.4.zip.

Nota que no hemos incluido una base de datos en el archivo zip de arriba, pero el repositorio de migración está ahí. Para crear una nueva base de datos solo usa el script db_create.py, luego usa db_upgrade.py para actualizar la base de datos a la última versión.

Espero verte de vuelta!

Miguel

El Mega Tutorial de Flask, Parte III: Formularios Web

El tutorial a continuación es la traducción libre realizada por el autor de este blog, del post escrito en inglés por Miguel. Por lo que aunque pueda variar algo respecto al original, no se realizó ningún aporte cuantitativo al mismo(en todo caso se reduce a usuarios GNU/Linux exclusivamente). Y ha sido el propio Miguel el que con sus respuestas ha ayudado ha aportar mayor información respecto al original.

Este es el tercer artículo de la serie en la que documento mi experiencia escribiendo aplicaciones web en Python usando el microframework Flask.

El objetivo de esta serie es desarrollar una aplicación decente de microblogging que demostrando mi total falta de originalidad he decidido llamar microblog.

NOTA: Este artículo ha sido revisado en septiembre de 2014 para estar en sincronía con las versiones actuales de Python y Flask.

Aquí un índice de los artículos de la serie que han sido publicados hasta la fecha:

  • Parte I: Hola Mundo!
  • Parte II: Plantillas
  • Parte III: Formularios Web (este artículo)
  • Parte IV: Base de datos [en traducción]
  • Parte V: Acceso de Usuario [aún no traducido]
  • Parte VI: Páginas de perfil y Avatares [aún no traducido]
  • Parte VII: Unit Testing [aún no traducido]
  • Parte VIII: Seguidores, contactos y amigos [aún no traducido]
  • Parte IX: Paginado [aún no traducido]
  • Parte X: Búsqueda completa de texto [aún no traducido]
  • Parte XI: Soporte Email [aún no traducido]
  • Parte XII: Lavado de cara [aún no traducido]
  • Parte XIII: Fechas y Tiempos [aún no traducido]
  • Parte XIV: I18n y L10n [aún no traducido]
  • Parte XV: Ajax [aún no traducido]
  • Parte XVI: Depuración, Pruebas y Perfilado [aún no traducido]
  • Parte XVII: Despliegue en GNU/Linux (incluso la Raspberry Pi!) [aún no traducido]
  • Parte XVIII: Despliegue en la nube Heroku [aún no traducido]

Recapitulando

En el capítulo anterior de la serie definimos una plantilla simple para la página de inicio y usamos objetos falsos como marcadores para cosas que no teníamos todavía, como usuarios o publicaciones de blog.

En este artículo taparemos uno de esos muchos agujeros que todavía tenemos en la aplicación, veremos como trabajar con los formularios web.

Los formularios web son una de las partes mas básicas que constituyen cualquier aplicación web. Usaremos los formularios web para permitir a los usuarios escribir publicaciones blog y también para ingresar a la aplicación.

Para poder seguir este capítulo necesitas tener la aplicación microblog tal cual como se dejó en el final del capítulo anterior. Porfavor asegurate de tenerla instalada y funcionando correctamente.

Configuración

Para manejar nuestros formularios web vamos a usar la extensión Flask-WTF, la cual se integra perfectamente con el proyecto  WTForms y permite un trabajo cómodo para aplicaciones Flask.

Muchas extensiones de Flask requieren ciertos parámetros establecidos, por lo que vamos a crear un archivo de configuración dentro de nuestra carpeta raíz microblog para que sea de fácil acceso si luego se necesita editarlo. Empezaremos con algo como esto (archivo config.py):

WTF_CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'

Muy simple verdad?, son solo dos sentencias necesarias para nuestra extensión Flask-WTF. La configuración WTF_CSRF_ENABLED activa la prevención CSRF (esta opción está habilitada por defecto en la versiones actuales de Flask-WTF). En la mayoría de los casos querrás tener esta opción habilitada para mayor seguridad en tu aplicación.

La configuración SECRET_KEY es solo necesaria cuando se tiene activada la CSRF, y es usada para crear una ficha criptográfica que es usada para validar un formulario. Cuando escribas tus propias aplicaciones asegurate de establecer la SECRET_KEY  como una combinación difícil de adivinar.

Ahora que tenemos nuestro archivo de configuración necesitamos decirle a Flask que lo lea y lo use. Podemos hacerlo justo después de ser creado el objeto Flask. (archivo app/__init__.py):

from flask import Flask

app = Flask(__name__)
app.config.from_object('config')

from app import views

Formulario de ingreso de usuario

Los formularios son presentados en Flask-WTF como clases, específicamente subclases de la clase básica Form. Una subclase de form simplemente define los campos del formulario como variables de clase.

Ahora crearemos un formulario de ingreso para que los usuarios se identifiquen con el sistema. El mecanismo de ingreso que usaremos en nuestra aplicación no será el estándar usuario/clave, sino que nuestros usuarios ingresarán usando su  OpenID. Las OpenID tienen el beneficio de que proveedor de la OpenID realiza el proceso de autentificación, por lo que nuestro sitio mas seguro para nuestros usuarios ya no validamos contraseñas .

El ingreso OpenID solo requiere un campo de cadena de texto(string). También anexaremos una caja de verificación  “recuérdame” en el formulario, así los usuarios podrán tener una cokiee instalada en sus navegadores que les recuerde su ingreso cuando regresen.

Nuestro primer formulario quedaría (archivo app/forms.py):

from flask.ext.wtf import Form
from wtforms import StringField, BooleanField
from wtforms.validators import DataRequired

class LoginForm(Form):
    openid = StringField('openid', validators=[DataRequired()])
    remember_me = BooleanField('remember_me', default=False)

Creo que la clase se explica por si sola. Importamos la clase Form, y los dos tipos de campos de formularios que necesitamos, StringField y BooleanField.

DataRequired por otra parte es una validador, una función que puede ser atada a un campo para realizar una validación a los datos enviados por el usuario. El validador The DataRequired simplemente chequea que los campos no estén vacíos. Existen muchos mas validadores incluidos en Flask-WTF, en el futuro usaremos algunos mas de ellos.

Plantillas para formularios

También necesitamos una plantilla que contenga HTML que presente el formulario. La buena noticia es que la clase LoginForm que acabamos de crear sabe como presentar los campos de formulario como HTML, por lo que solo necesitamos concentrarnos en la presentación. Aquí tenemos nuestra plantilla (archivo app/templates/login.html):

<!-- extend from base layout -->
{% extends "base.html" %}

{% block content %}</pre>
<h1>Sign In</h1>
<form action="" method="post" name="login">{{ form.hidden_tag() }}
Please enter your OpenID:
{{ form.openid(size=80) }}


{{ form.remember_me }} Remember Me

<input type="submit" value="Sign In" /></form>
<pre>{% endblock %}

Nota que en esta plantilla estamos reusando la plantilla base.html mediante la declaración de herencia de plantilla extends. Haremos esto con todas nuestras plantillas para asegurar la misma presentación en todas las páginas.

Hay algunas diferencias interesantes entre un formulario HTML normal y nuestra plantilla. Esta plantilla espera un objeto formulario instanciado desde la clase formulario que definimos en un argumento de la plantilla llamado form. Nos encagaremos de enviar este argumento de plantilla a la plantilla siguiente cuando escribamos la función view que presenta esta plantilla.

El argumento de plantilla The form.hidden_tag() será reemplazado con un campo oculto que implementa la prevención CSRF que habilitamos en la configuración. Este campo necesita estar en todos tus formularios si tienes la CSRF habilitado. La buena noticia es que Flask-WTF se encarga de esto por nosotros, solo necesitamos asegurarnos de incluirlo en el formulario.

Los campos de nuestro formulario son presentados por los objetos de campos, solo necesitamos referirlos al argumento de plantilla {{form.field_name}} en el lugar donde cada campo deba ser insertado. Algunos campos incluso pueden tomar argumentos. En nuestro caso, nosotros preguntamos por el siguiente campo de texto para generar nuestro campo openid con un ancho de 80 caracteres.

Dado que no hemos definido el botón de enviar en la clase formulario, tendremos que definirlo como un campo regular. El campo de envío no carga ningún dato por lo que no necesitamos definirlo en la clase formulario.

Formularios views

El paso final para que podamos ver nuestro formulario es escribir una función view que presente la plantilla.

Esto es bastante simple dado que necesitamos pasar un objeto formulario a la plantilla. Así quedaría nuestra nueva función view (file app/views.py):

from flask import render_template, flash, redirect
from app import app
from .forms import LoginForm

# index view function suppressed for brevity

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    return render_template('login.html', 
                           title='Sign In',
                           form=form)

Básicamente, importamos nuestra clase LoginForm instanciando un objeto desde ella, y lo enviamos hacia la plantilla. Esto es todo lo requerido para tener los campos de formulario presentados.

Ignoremos por ahora las importaciones flash y redirect. Las usaremos dentro de un momento.

Otro cambio es el argumento methods en el decorador de ruta. Esto le dice a Flask que esta función view acepta requerimientos GET y POST. Sin esto la función view solo aceptará requerimientos GET. Nosotros queremos recibir requerimientos POST pues estos son los que traerán los datos del formulario ingresados por el usuario.

En este punto ya puedes probar la aplicación y ver el formulario en tu explorador web. Recuerda que debes abrir la dirección http://localhost:5000/login en tu explorador debido a que esta es la ruta que hemos asociado a la función view de ingreso.

Aún no hemos escrito la parte que acepta la data, por lo que al presionar el botón enviar no ocurrirá nada por el momento.

Recibiendo la data del formulario

La otra área donde Flask-WTF hace nuestro trabajo fácil es manejando los datos enviados. Aquí está una versión actualizada de nuestra función view de ingreso que valida y guarda los datos del formulario (archivo app/views.py):

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="%s", remember_me=%s' %
              (form.openid.data, str(form.remember_me.data)))
        return redirect('/index')
    return render_template('login.html', 
                           title='Sign In',
                           form=form)

El método validate_on_submit realiza todo el procesamiento del formulario. Si lo llamas cuando el formulario está siendo presentado al usuario (por ejemplo antes de que el usuario tenga chance de ingresar datos en el) retornará False, en ese caso sabrás que tienes que presentar la plantilla.

Cuando validate_on_submit es llamado como parte de un requerimiento de envío de formulario, recogerá todos los datos, hará todas las validaciones relacionadas a los campos, y si todo está correcto retornará True, indicando que los datos son validos y pueden ser procesados. Esta es tu indicación de que los datos son seguros para ser incorporados a la aplicación.

Si al menos uno de los campos falla la validación entonces la función retornará False lo que causará que el formulario sea presentado de nuevo al usuario, dando así una oportunidad al usuario de corregir cualquier error. Luego veremos como mostrar un mensaje de error cuando la validación falle.

Cuando validate_on_submit retorna True nuestra función view de ingreso llama dos nuevas funciones, importadas desde Flask. La función flash es una manera fácil de mostrar un mensaje en la siguiente página presentada al usuario. En este caso la usaremos para depuración, dando que no tenemos toda la infraestructura necesaria para ingresar usuarios todavía, en cambio mostraremos los datos enviados. La función flash es también extremadamente útil en servidores de producción para proveer retroalimentación hacia al usuario respecto a una acción.

Los mensajes mostrados al usuario no aparecerán automáticamente en nuestra página, nuestras plantillas necesitan mostrar los mensajes en una forma que funcione con la disposición de nuestro sitio. Añadiremos estos mensajes a la plantilla base, para que todas las plantillas hereden esta funcionalidad. Esta sería la plantilla base actualizada (archivo app/templates/base.html):

<html>
    <head>
        {% if title %}
        <title>{{ title }} - microblog</title>
        {% else %}
        <title>microblog</title>
        {% endif %}
    </head>
    <body>
        <div>Microblog: <a href="/index">Home</a></div>
        <hr>
        {% with messages = get_flashed_messages() %}
            {% if messages %}
                <ul>
                {% for message in messages %}
                    <li>{{ message }} </li>
                {% endfor %}
                </ul>
            {% endif %}
        {% endwith %}
        {% block content %}{% endblock %}
    </body>
</html>

Afortunadamente la técnica para mostrar el mensaje rápido se explica por si sola. Una propiedad interesante de los mensajes rápidos es que una vez son requeridos a través de la función get_flashed_messages son eliminados de la lista de mensajes, por lo que estos mensajes aparecen en la primera página requeridos por el usuario luego de que la función  flash sea llamada y posteriormente desaparecen.

La otra función nueva que usamos en nuestro view de ingreso es redirect. Esta función le dice al navegador del cliente que navegue a una página difrente en vez de la requerida. En nuetra función view la usamos para redirigir a la página index que elaboramos en los capitos previos. Nota que los mensajes rápido apareceran incluso si una función view termina en una redirección.

Este es un buen momento para iniciar la aplicación y probar como funciona el formulario. Asegurate de tratar enviar el formulario con el campo de openid vacío, para ver como el validador DataRequired detiene el proceso de envío.

Mejorando la validación de campo

Con el estado actual de la aplicación, los formularios que son enviados con datos invalidos no serán aceptados. En cambio, el formulario será presentado de nuevo al usuario para su correción. Esto es exactamente lo que queremos.

Lo que nos falta es una indicativo al usuario de lo que está incorrecto en el formulario. Afortunadamente, Flask-WTF tambien hace de esto una tarea sencilla.

Cuando un campo falla en la validación Flask-WTF añade un mensaje de error descriptivo al objeto formulario. Estos mensajes están disponibles para la plantilla, así que solo necesitamos añadir un poco de lógica para presentarlos.

Aquí está nuestra plantilla de ingreso con los mensajes de validación de campos (archivo app/templates/login.html):

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}</pre>
    <h1>Sign In</h1>
    <form action="" method="post" name="login">
        {{ form.hidden_tag() }}
        <p>
            Please enter your OpenID:
            {{ form.openid(size=80) }}
            {% for error in form.openid.errors %}
                <span style="color: red;">[{{ error }}]</span>
            {% endfor %}<br>
        </p>
        <p>{{ form.remember_me }} Remember Me</p>
        <p><input type="submit" value="Sign In"></p>
    </form>
{% endblock %}

El único cambio que hemos hecho es añadir un bucle for que presenta cualquier mensaje añadido por los validadores debajo del campo de openid. Como regla general, cualquiera de los campos que tengan validadores atados tendrán errores añadidos como form.field_name.errors. En nuestro caso usaremos form.openid.errors. Mostraremos estos mensajes en estilo rojo para llamar la atención del usuario.

Lidiando con las OpenID

En la práctica, encontraremos que mucha gente nisiquiera sabe que ya tiene algunas OpenIDs. No es algo bien conocido que un gran número de provedores de servicios en la internet soportan la autentificación mediante OpenID para sus miembros… Por ejemplo, si tienes una cuenta con Google, tu ya tienes una OpenID con ellos. De la misma manera pasa con Yahoo, AOL, Flickr y muchos otros proveedores. (Actualización: Google está clausurando su servicio de OpenID el 15 de abril de 2015).

Para facilitar el ingreso a nuestro sitio a los usuarios con uno de estos comunmente disponibles OpenID, anadiremos enlaces hacia algunos de ellos así el usuario no tiene que escribir su OpenID a mano.

Empezaremos a hacerlo definiendo una lista de proveedore de OpenID que queramos presentar. Podemos hacer estos en nuestro archivo de configuración. (archivo config.py):

WTF_CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'

OPENID_PROVIDERS = [
    {'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id'},
    {'name': 'Yahoo', 'url': 'https://me.yahoo.com'},
    {'name': 'AOL', 'url': 'http://openid.aol.com/'},
    {'name': 'Flickr', 'url': 'http://www.flickr.com/'},
    {'name': 'MyOpenID', 'url': 'https://www.myopenid.com'}]

Ahora veamos como usar esta matriz en nuetra función view de ingreso:

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="%s", remember_me=%s' %
              (form.openid.data, str(form.remember_me.data)))
        return redirect('/index')
    return render_template('login.html', 
                           title='Sign In',
                           form=form,
                           providers=app.config['OPENID_PROVIDERS'])

Aquí tomamos la configuración buscando con su llave en app.config. La matriz es posteriormente añadida a la  render_template llamada como un argumento de plantilla.

Tal y como adivinaste, falta un paso mas para terminar. Ahora necestitamos especificar como quisieramos presentar estos enlaces de proveedores en nuestra plantilla de ingreso. (archivo app/templates/login.html):

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
<script type="text/javascript">
function set_openid(openid, pr)
{
    u = openid.search('<username>')
    if (u != -1) {
        // openid requires username
        user = prompt('Enter your ' + pr + ' username:')
        openid = openid.substr(0, u) + user
    }
    form = document.forms['login'];
    form.elements['openid'].value = openid
}
</script>
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{ form.hidden_tag() }}
    <p>
        Please enter your OpenID, or select one of the providers below:<br>
        {{ form.openid(size=80) }}
        {% for error in form.openid.errors %}
          <span style="color: red;">[{{error}}]</span>
        {% endfor %}<br>
        |{% for pr in providers %}
          <a href="javascript:set_openid('{{ pr.url }}', '{{ pr.name }}');">{{ pr.name }}</a> |
        {% endfor %}
    </p>
    <p>{{ form.remember_me }} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %} 

La plantilla se hizo algo mas larga con este cambio. Algunas OpenID incluyen el nombre de usuario, por lo que tenemos que tener un poco de magia javascript para ellas y pedir así al usuario el nombre de usuario y componer con ello el OpenID. Cuando el usuario haga clic en uno de los enlaces de los proveedores del OpenID y opcionalmente introduzca su nombre de usuario, el OpenID para ese proveedor será insertado en el campo de texto.

Abajo un pantallazo de nuestra pantalla de ingreso luego hacer clic el enlace de Google OpenID: Sign In screenshot

Palabras finales

Aunque hayamos hecho un montón de progresos con nuestro formulario de ingreso, no hemos hecho nada respecto al ingreso de usuarios a nuestro sistema. Todo lo que hemos hecho hasta ahora atañe a los aspectos de la interfaz gráfica de usuario del proceso de ingreso. Esto es poque antes de que hagamos ingresos reales necesitamos tener una base de datos donde podamos guardar nuestros usuarios.

En el siguiente capítulo pondremos a funcionar nuestra base de datos y completaremos además nuestro sistema ingreso, así que mantente atento a los siguientes artículos.

La aplicación microblog en el estado actual está disponible para descargar aquí:

Descargar microblog-0.3.zip.

Recuerda que el ambiente virtual Flask no está incluído en el archivo zip . Para instrucciones de como configurarlo revisa el primer capítulo de la serie. Deja tus comentarios o preguntas abajo.

Espero verte en el siguiente capítulo.

Miguel

El Mega Tutorial de Flask, Parte II: Plantillas

El tutorial a continuación es la traducción libre realizada por el autor de este blog, del post escrito en inglés por Miguel. Por lo que aunque pueda variar algo respecto al original, no se realizó ningún aporte cuantitativo al mismo(en todo caso se reduce a usuarios GNU/Linux exclusivamente). Y ha sido el propio Miguel el que con sus respuestas ha ayudado ha aportar mayor información respecto al original.

Este es el segundo artículo de una serie en la que documento mi experiencia escribiendo aplicaciones web en Python usando el microframework Flask.

El objetivo de esta serie de tutoriales es desarrollar una aplicación decente de microblogging, que demostrando mi completa falta de originalidad he decido nombrar microblog.

NOTA: Este artículo fue revisado en septiembre de 2014 para estar en sincronía con las recientes versiones de Python y Flask.

Índice

  • Parte I: Hola Mundo!
  • Parte II: Plantillas (este artículo)
  • Parte III: Formularios Web
  • Parte IV: Base de datos [en traducción]
  • Parte V: Acceso de Usuario [aún no traducido]
  • Parte VI: Páginas de perfil y Avatares [aún no traducido]
  • Parte VII: Unit Testing [aún no traducido]
  • Parte VIII: Seguidores, contactos y amigos [aún no traducido]
  • Parte IX: Paginado [aún no traducido]
  • Parte X: Búsqueda completa de texto [aún no traducido]
  • Parte XI: Soporte Email [aún no traducido]
  • Parte XII: Lavado de cara [aún no traducido]
  • Parte XIII: Fechas y Tiempos [aún no traducido]
  • Parte XIV: I18n y L10n [aún no traducido]
  • Parte XV: Ajax [aún no traducido]
  • Parte XVI: Depuración, Pruebas y Perfilado [aún no traducido]
  • Parte XVII: Despliegue en GNU/Linux (incluso la Raspberry Pi!) [aún no traducido]
  • Parte XVIII: Despliegue en la nube Heroku [aún no traducido]

Recapitulando

Si has seguido el capítulo anterior tendrás ya una aplicación web muy simple pero funcional, que presenta la siguiente estructura de archivos:

    microblog\
      flask\
        <virtual environment files>
      app\
        static\
        templates\
        __init__.py
        views.py
      tmp\
      run.py

Para iniciar la apliación ejecuta el script run.py y abres la dirección http://localhost:5000 en tu explorador web.

Arrancaremos exactamente donde lo dejamos, por lo que quizás querrás estar seguro de tener la aplicación anterior correctamente instalada y en funcionamiento.

Por qué necesitamos plantillas?

Pensemos como podemos expandir nuestra pequeña aplicación.

Queremos una página de inicio de nuestra aplicación microblogging que tenga un encabezado de bienvenida al usuario registrado, eso es muy común para aplicaciones de este tipo. Ignora por el momento el hecho de que no tenemos manera de ingresar usuarios, te presentaré una solución para ese apartado en un momento.

Una opción sencilla para imprimir una buen y gran encabezado sería cambiar nuestra función view por una salida HTML, quizás algo como esto:

 
from app import app 

@app.route('/')
@app.route('/index')
def index():
    user = {'nickname': 'Miguel'} # fake user
    return '''
<html>
    <head>
        <title>Home Page</title>
    </head>
    <body> 
        <h1>Hello, ''' + user['nickname'] + '''</h1>
    </body> 
</html> ''' 

Dale una prueba a la aplicación y ve como se ve en tu explorador.

Dado que todavía no tenemos soporte para usuarios he recurrido a usar un marcador para el objeto usuario, algunas veces llamado objeto falso u objeto simulado. Esto nos permite concentrarnos en ciertos aspectos de nuestra aplicación que dependen de partes del sistema que aún no han sido elaboradas.

Espero que estés de acuerdo conmigo en que la solución usada arriba para entregar HTML al explorador es bastante ineficiente. Piensa cuan complejo sería el código si tuvieses que retornar una página HTML grande y compleja con variado contenido dinámico. Y que pasaría si necesitarás cambiar el arreglo de tu sitio web en una gran aplicación con docenas de funciones views, cada una retornando código HTML directamente?. Esta es claramente una solución no escalable.

Plantillas al rescate!

Si pudieras mantener la lógica de tu aplicación separada de la disposición o presentación de tus páginas web todo sería mas sencillo, no lo crees?. Podrías incluso contratar a un diseñador web para crear un sitio web fascinante mientras tu código sobre el comportamiento de tu sitio permanece en Python. Las plantillas ayudan a implementar esta separación.

Escribamos nuestra pimera plantilla (archivo app/templates/index.html):

 
<html> 
    <head> 
        <title>{{ title }} - microblog</title>
    </head> 
    <body> 
        <h1>Hello, {{ user.nickname }}!</h1>
    </body>
</html>

Como puedes ver, escribimos una página estándar en HTML, con la única diferencia que hay algunos marcadores para el contenido dinámico encerrado en secciones {{ ... }}.

Ahora veamos como usar esta plantilla en nuestra función view (archivo app/views.py):

 
from flask import render_template
from app import app 

@app.route('/') 
@app.route('/index') 
def index(): 
    user = {'nickname': 'Miguel'} # fake user 
    return render_template('index.html', 
                           title='Home', 
                           user=user)

En este punto ya puedes probar la aplicación y ver como trabaja la plantilla. Una vez que tengas la página cargada en tu explorador puede que quieras ver el código fuente en HTML y compararlo con la plantilla original.

Para cargar la plantilla hemos tenido que importar una nueva función desde el microframework Flask llamada render_template. Esta función toma el nombre de archivo de la plantilla y una lista variable de argumentos de plantilla para devolver la plantilla armada con todos los argumentos reemplazados.

De manera oculta, la función render_template invoca el motor de plantilla Jinja2 que es parte del microframework Flask.  Jinja2 substituye los bloques {{...}} con los correspondientes valores provistos como argumentos de plantilla.

Declaraciones de control en las plantillas

Las plantillas Jinja2 tambien soportan las declaraciones de control, dadas dentro de los bloques {%...%}. Añadamos una declaración if a nuestra plantilla (archivo app/templates/index.html):

 
<html>
    <head> 
        {% if title %}
        <title>{{ title }} - microblog</title> 
        {% else %} 
        <title>Welcome to microblog</title> 
        {% endif %} 
    </head>
    <body> 
        <h1>Hello, {{ user.nickname }}!</h1> 
    </body> 
</html>

Ahora nuestra plantilla es un poco mas lista. Si tu función view olvida definir un título de  página, en vez de mostrar un título vacío la plantilla proveerá su propio título. Intenta remover el argumento de title en el render_template y llama a nuestra función view para ver como la declaración condicional funciona.

Bucles en plantillas

El usuario ingresado en nuestra aplicación microblog probablemente querrá ver las publicaciones recientes de los usuarios que sigue en la página de inicio, veamos entonces como podemos hacerlo.

Para empezar, usaremos nuestro objeto falso para crear algunos usuarios y otras publicaciones para mostrar (archivo app/views.py):

 
def index():
    user = {'nickname': 'Miguel'} # fake user
    posts = [ # fake array of posts
        { 
            'author': {'nickname': 'John'},
            'body': 'Beautiful day in Portland!'
        },
        {
            'author': {'nickname': 'Susan'},
            'body': 'The Avengers movie was so cool!'
        }
    ]
    return render_template("index.html",
                           title='Home',
                           user=user,
                           posts=posts)

Usaremos una lista para representar las publicaciones de los usuarios, en la que cada elemento tenga autor y campos de relleno. Cuando tengamos imlpementada una base de datos real guardaremos estos nombres de campo, por lo que podemos diseñar y probar nuetras plantilla usando los objetos falsos sin tener que preocuparnos de actualizarlos cuando nos mudemos a una base de datos.

Tenemos que resolver una situación en la plantilla. La lista puede tener cualquier número de elementos, le tocará a la función view decidir cuantas publicaciones necesitan ser mostradas. La plantilla no puede asumir el número de publicaciones, por ello necesita estar preparada para mostrar cuantas publicaciones la función views envíe.

Veamos entonces como lo hacemos usando una estructura de control for (archivoe app/templates/index.html):

 
<html>
    <head> 
        {% if title %} 
        <title>{{ title }} - microblog</title>
        {% else %}
        <title>Welcome to microblog</title>
        {% endif %}
    </head> 
    <body>
        <h1>Hi, {{ user.nickname }}!</h1> 
        {% for post in posts %} 
        <div><p>{{ post.author.nickname }} says: <b>{{ post.body }}</b></p></div> 
        {% endfor %}
    </body>
</html>

Simple, verdad? Dale una prueba y asegurate de jugar añadiendo mas contenido a la matriz de publicaciones.

Herencia de plantillas

Tenemos un punto mas que tocar antes de terminar por hoy.

Nuestra aplicación web microblog necesitará una barra de navegación en el tope de la página con unos enlaces. En ella podrás obtener el enlace para editar tu perfil, para ingresar, para cerrar sesión, etc.

Podríamos añadir una barra de navegación a nuestra plantilla index.html, pero a medida que nuestra aplicación crezca necesitaríamos implementar mas páginas, y esta barra de navegación debería ser copiada y pegada en todas ellas. Por ello tendríamos que tener todas estás copias idénticas de la barra de navegación en perfecta sincronía, lo que puede convertirse en una gran carga de trabajo si tenemos montón de páginas y plantillas.

En vez de eso, podemos usar la característica de herencia de plantilla de Jinja2’s, la cual permite mover parte de la presentación de la página que es común en todas las plantillas y ponerla en una plantilla base desde la que todas las demás plantillas deriven.

Entonces, definamos una plantilla base que incluya la barra de navegación y tambien un poco de lógica que implementamos atrás (archivo app/templates/base.html):

<html>
    <head>
    {% if title %}
    <title>{{ title }} - microblog</title>
    {% else %}
    <title>Welcome to microblog</title>
    {% endif %} 
    </head>
    <body> 
        <div>Microblog: <a href="/index">Home</a></div>
        <hr> {% block content %}{% endblock %}
    </body>
</html>

En esta plantilla usamos la declaración de control block para definir el lugar donde las plantillas derivadas pueden insertarse. Los bloques obtienen nombres únicos y su contenido puede ser reemplazado o mejorado en plantillas derivadas.

Y ahora falta solamente modificar nuetra plantilla index.html heredando desde base.html (archivo app/templates/index.html):

{% extends "base.html" %}
{% block content %}
    <h1>Hi, {{ user.nickname }}!</h1>
    {% for post in posts %}
    <div><p>{{ post.author.nickname }} says: <b>{{ post.body }}</b></p></div>
    {% endfor %}
{% endblock %}

Dado que la plantilla base.html se encargará de la estructura general de la página removimos los elementos de la plantilla index.html y dejamos solo la parte de contenido. EL bloque extends estable el enlace de herencia entre las dos plantillas para que Jinja2 sepa que cuando necesite mostrar index.html incluya tambien base.html. Las dos plantillas tienen una declaración block de coincidencia con el nombre del contenido, lo que permite a Jinja2 saber como combinar las dos en una sola. Cuando lleguemos a escribir nuevas plantillas las crearemos tambien como extensión de base.html.

Palabras finales

Si quiere ahorrar tiempo, la aplicación microblog en su estado actual está disponible aquí:

Download microblog-0.2.zip.

Recuerda que el archivo zip no incluye el ambiente flask, por lo que necesitarás crearlos con las intrucciones del primer capítulo de la serie antes de correr la aplicación.

SI tienes alguna duda o comentario, dejalo en parte de abajo.

En el siguiente capítulo de la serie veremos los formularios web. Espero verlos luego!

Miguel

El Mega Tutorial de Flask, Parte I: Hola Mundo!

El tutorial a continuación es la traducción libre realizada por el autor de este blog, del post escrito en inglés por Miguel. Por lo que aunque pueda variar algo respecto al original, no se realizó ningún aporte cuantitativo al mismo(en todo caso se reduce a usuarios GNU/Linux exclusivamente). Y ha sido el propio Miguel el que con sus respuestas ha ayudado ha aportar mayor información respecto al original.

Este es el primer artículo de una serie en la cual estaré documentando mi experiencia escribiendo aplicaciones web en Python usando el microframework Flask.

NOTA: Este artículo fue revisado en septiembre de 2014 para estar sincronizado con las versiones actuales de Python y Flask.

Este es un índice de todos los artículos de la serie que han sido publicados hasta la fecha:

Índice

  • Parte I: Hola Mundo! (este artículo)
  • Parte II: Plantillas
  • Parte III: Formularios
  • Parte IV: Base de datos [en traducción]
  • Parte V: Acceso de Usuario [aún no traducido]
  • Parte VI: Páginas de perfil y Avatares [aún no traducido]
  • Parte VII: Unit Testing [aún no traducido]
  • Parte VIII: Seguidores, contactos y amigos [aún no traducido]
  • Parte IX: Paginado [aún no traducido]
  • Parte X: Búsqueda completa de texto [aún no traducido]
  • Parte XI: Soporte Email [aún no traducido]
  • Parte XII: Lavado de cara [aún no traducido]
  • Parte XIII: Fechas y Tiempos [aún no traducido]
  • Parte XIV: I18n y L10n [aún no traducido]
  • Parte XV: Ajax [aún no traducido]
  • Parte XVI: Depuración, Pruebas y Perfilado [aún no traducido]
  • Parte XVII: Despliegue en GNU/Linux (incluso la Raspberry Pi!) [aún no traducido]
  • Parte XVIII: Despliegue en la nube Heroku [aún no traducido]

Mi perfil

Soy un ingeniero de software con una cifra de dos dígitos en años de experiencia desarrollando aplicaciones complejas en distintos lenguajes. Primero aprendí Python como parte de un esfuerzo para crear lazos para una librería C++ en el trabajo. Además de Python, he escrito aplicaciones web en PHP, Ruby, Smaltalk y créanlo o no, también en C++. De todas estas, la combinación Python/Flask es la que he encontrado ser mas flexible.

ACTUALIZACIÓN: He escrito un libro titulado “Flask Web Development”, publicado en el año 2014 por O’Reilly Media. El libro presenta un uso mas actualizado y es, en general, mas avanzado que el tutorial, pero algunos tópicos son cubiertos solamente en el tutorial, por lo que ambos se complementan entre si. Visita http://flaskbook.com/ para mayor información.

La aplicación

La aplicación que voy a desarrollar como parte de este tutorial es un servidor de microblog decentemente presentado que he decido llamar microblog. Si, muy creativo, lo sé.

Estos son algunos de los temas que cubriré mientras que vamos progresando con este proyecto.

  • Administración de usuarios, incluyendo control de acceso, sesiones, roles de usuarios, perfiles y avatares de usuarios.
  • Administración de base de datos, incluyendo manejo y migración.
  • Soporte de formulario web, incluyendo validación de campos.
  • Paginado de largas listas de artículos.
  • Búsqueda de texto completa.
  • Notificaciones email a los usuarios.
  • Plantillas HTML.
  • Soporte para múltiples lenguajes.
  • Caché y otras mejoras de rendimiento.
  • Técnicas de depuración para servidores de desarrollo o producción.
  • Instalación en un servidor de producción.

Como se puede ver, voy hacer un trabajo completo. Por lo tanto, espero que unan vez terminada, esta aplicación sirva como una especie de patrón para escribir otras aplicaciones web.

Requerimientos

Si tienes un computador que corre Python entonces probablemente ya estarás listo para empezar. La aplicación del tutorial debería correr perfectamente en GNU/Linux. A menos que se indique lo contrario, el código presentado en esta serie de artículos ha sido probado en Python 2.7 y 3.4.

El tutorial asume que ya estás familiarizado con la ventana terminal (o consola) y que sabes las funciones básicas de manejo de archivos desde la misma. Si no es tu caso, te recomiendo que aprendas como crear directorios, copiar archivos, etc, usando la terminal antes de continuar.

Finalmente, deberías estar cómodo de alguna forma con el código Python. Se recomienda también tener conocimiento previo de los módulos y paquetes Python

Instalando Flask

Bien, comencemos.

Si aun no tienes instalado Python, que esperas?.

Ahora tenemos que instalar Flask y las extensiones que estaremos usando. Mi manera preferida de hacerlo es crear un ambiente virtual que contenga todo lo necesario y así no afectar la instalación principal de Python. Como beneficio extra, no necesitarás acceso root para realizar la instalación de esta manera.

Abre una ventana terminal, escoge la ubicación donde quieras tu aplicación y crea una nueva carpeta ahí para contenerla. Llamemos a la carpeta de la aplicación microblog.

Si estás usando Python 3.4, entonces haz “cd” dentro de la carpeta microblog y crea un ambiente virtual con el siguiente comando:

 $ python -m venv flask 

Quizás en algunos sistemas operativos necesites usar python3 en vez de python. El comando anterior crea una versión privada de tu interprete Python dentro de una carpeta llamada flask.

Si estás usando cualquier otra versión de Python anterior a la 3.4, necesitarás descargar e instalar virtualenv.py antes de poder crear un ambiente virtual. Probablemente tengas un paquete de la aplicación para tu distribución. Por ejemplo, si usas Ubuntu:

 $ sudo apt-get install python-virtualenv 

O Fedora:

 $ sudo yum install python-virtualenv 

Si tu versión de Python es la 3.4, puedes instalar virtualenv instalado pip primero, tal cual como dice esta página. Una vez pip esté instalado, el comando indicado para instalar virtualenv es el siguiente

 $ pip install virtualenv 

Hemos visto arriba como crear un ambiente virtual en Ptyhon 3.4. Para versiones anteriores de Python que hayan sido expandidas con virtualenv, el comando que crea un ambiente virtual es el siguiente:

 $ virtualenv flask 

Cualquiera sea el método que uses para crear el ambiente virtual, terminarás con una carpeta de nombre flask que contiene un ambiente Python completo listo para usar en este proyecto.

Los ambientes virtuales pueden ser activados y desactivados, si es requerido. Un ambiente activado añade el lugar de su carpeta bin a la dirección del sistema, por ejemplo, cuando tipeas “python” estarás obteniendo la versión Python del ambiente y no la del sistema. Pero la activación de un ambiente virtual no es necesaria, es igualmente efectivo invocar el interprete especificando su ruta.

Instala flask y sus extensiones introduciendo los siguientes comandos, una detrás del otro:

 $ flask/bin/pip install flask
$ flask/bin/pip install flask-login
$ flask/bin/pip install flask-openid
$ flask/bin/pip install flask-mail
$ flask/bin/pip install flask-sqlalchemy
$ flask/bin/pip install sqlalchemy-migrate
$ flask/bin/pip install flask-whooshalchemy
$ flask/bin/pip install flask-wtf
$ flask/bin/pip install flask-babel
$ flask/bin/pip install guess_language
$ flask/bin/pip install flipflop
$ flask/bin/pip install coverage 

Estos comandos descargarán e instalarán todos los paquetes que usaremos para nuestra aplicación.

“Hola Mundo” en Flask

Ahora tienes una subcarpeta flask dentro de tu carpeta microblog que está llena con un Interprete Python y el microframework Flask y sus extensiones que usaremos para esta aplicación. Ahora es tiempo de escribir nuestra primera aplicación.

Despues de hacer cd en la carpeta microblog, creemos la estructura básica de carpeta para nuestra aplicación.

 $ mkdir app
$ mkdir app/static
$ mkdir app/templates
$ mkdir tmp 

La carpeta app estará donde pondremos nuestro paquete de la aplicación. La subcarpeta static es donde guardaremos archivos estáticos como imágenes. javascripts y hojas de estilo ccs. La subcarpeta templates es obviamente donde almacenaremos nuestras plantillas a utilizar.

Empecemos creando un simple script init para nuestro paquete app (archivo app/__init__.py):

 from flask import Flask

app = Flask(__name__)
from app import views 

El script de arriba simplemente crea el objeto de la aplicación (de clase Flask) y luego importa el módulo views, el cual aun no ha sido escrito. No confundir la variable app (la cual obtine asignada la instancia Flask) con el paquete app (desde donde importamos el módulo views).

Si te estás preguntando porque la instrucción import está al final y no al comienzo del script como siempre, la razón es para evitar las referencias circulares, porque el módulo views necesita importar la variable app definida en este script.  Colocando el import al final se evita el error circular import.

Los views son los “manejadores” que responden a los requerimientos desde el explorador web y otros clientes. En Flask los manejadores están escritos como funciones Python. Cada función view está mapeada hacia uno o mas solicitudes URL.

Escribamos nuestra primera función view (archivo app/views.py):

 from app import app

@app.route('/')
@app.route('/index')
def index():
    return "Hello, World!" 

Esta view es realmente simple, solo retorna un string, para ser mostrado en el navegador web del cliente. Los dos decoradores de ruta route arriba de la función crean el mapeado desde los URL / y / hacia esta función.

El paso final para tener una aplicación web completamente en funcionamiento es crear un script que inicialice el servidor web de desarollo con nuestra aplicación. Llamemos el script run.py, y pongamosla en la carpeta raiz.

 #!flask/bin/python
from app import app
app.run(debug=True) 

El script importa la variable app desde nuestro paquete app e invoca su método run para iniciar el servidor. Recuerda que la variable app contiene la instacia Flask que creamos anteriormente.

Para inciar la aplicación solo corre este script. Probablemente debas indicarle al sistema que el archivo es un ejecutable antes de correrlo:

 $ chmod a+x run.py 

Luego, el script puede ser ejecutado simplemente así:

 ./run.py 

Al haber incializado el servidor, este escuchará en el puerto 5000 a la espera de conexiones. Ahora abre tu explorador web e introduce el siguiente URL en el campo de direcciones.

 http://localhost:5000 

Alternativamente puedes usar la siguiente URL:

 http://localhost:5000/index 

Vez el mapeo de rutas en accion? La primera URL dirige hacia /, mientra la segunda dirige hacia /index. Ambas rutas están asociadas a nuestra función view, por lo que producen el mismo resultado. Si introduces alguna otra URL obtendrás un error, dado que solo han sido asignadas estas dos .

Cuando termines de jugar con el servidor puedes simplemente escribir Ctrl-C (en la terminal) para detenerlo.

Y con esto concluyo con la primera entrega de este tutorial.

Para aquellos que son flojos para tipear, pueden descargar el código para este tutorial abajo:

Descargar microblog-0.1.zip.

Nota que necesitar instalar Flask como se indicó arriba anteriormente antes de correr la aplicación.

Lo que viene

En la siguiente parte de la serie vamos a modificar nuestra pequeña aplicación para poder usar plantilla HTML.

Espero verte en el siguiente capítulo.

Miguel

Instalar Packet Tracer 6 en Fedora 21 + Creacion de lanzador

A continuación se mostrará como descargar e instalar el paquete de software Packet Tracer en su penúltima versión (6) para sistemas operativos GNU/Linux Fedora 21. Además se creará un lanzador para llamar al programa cómodamente desde el entorno gráfico de Gnome 3.

Descargar lo necesario

Desde el modo usuario común se descarga:

  1. Librerías
     $ wget http://www.deltaeridani.com/openssl-lib-compat-1.0.0i-1.fc20.i686.rpm
  2. Paquete Packet Tracer 6
    $ wget http://www.netadm.com.br/util/PacketTracer601_setup_linux.gz
  3. Imagen para Lanzador
     $ wget http://upload.wikimedia.org/wikipedia/en/d/dc/Cisco_Packet_Tracer_Icon.png
  4. Descarga
    Descarga de Librería

 

Instalación de librerías

A continuación se procede con la descarga e instalación de las librerías restantes

  1. Ingresar en modo super usuario
     $ sudo -s
  2. Descarga e instalación
    # yum install libICE.i686 libpng libSM.i686 libX11.i686 libXext.i686 libXi.i686 libXrandr.i686 fontconfig.i686 libgcc.i686 glib.i686 compat-libstdc++-33.i686 libstdc++.i686 libgthread-2.0.so.0 libglib-2.0.so.0
    
     # rpm -ivh openssl-lib-compat-1.0.0i-1.fc20.i686.rp­m
  3. Retorno al usuario común
     # exit

Instalación de Packet Tracer 6

Teniendo descargadas las librería se procede con la instalación en si:

  1. Descompresión del paquete
    Desde el usuario común, para poder tener acceso al directorio Home en el cual se realizó la descarga, se escribe:

     $ tar -zxvf PacketTracer601_setup_linux.gz

    Se ubica la consola en el nuevo directorio creado

     $ cd PacketTracer601

    Se ingresa al modo super usuario nuevamente

     $ sudo -s

    Y se ejecuta el script de instalación de Packet Tracer 6

     # ./install

     

    comando install
    Ejecución de script de instalación
  2. Instalación
    Se presiona la tecla “Intro” continuamente con cuidado de no presionarla mas de una vez al llegar al 99%

    Instalacion-porcentaje
    Licencia de Packet Tracer

    Se introduce “Y” seguido de la tecla “Intro” para aceptar los términos de la licencia. Y se presiona la tecla “Intro” nuevamente ante el mensaje que aparece, para establecer la ruta por defecto de instalación de packet tracer.

    Instalacion-ruta
    Aceptación de licencia y ruta de instalación por defecto
  3. Comprobación
    Al terminar la instalación, se retorna al usuario común

     # exit

    Y se comprueba que la instalación fue correcta llamando desde consola al programa

     $ packettracer
    packettracer-consola
    Instalación exitosa

     

Creación del Lanzador

  1. Ingreso Root
     $ sudo -s
  2. Se mueve el archivo de imagen
     # mv Cisco_Packet_Tracer_Icon.png /usr/share/icons/
  3. Se crea el archivo de texto para el lanzador con el editor de preferencia (vim en este caso)
     # vim /usr/share/applications/packettracer.desktop

    Se introduce los parámetros (Presione la tecla “i” y “ESC” para entrar y salir al modo de introducción de texto)

    Name= PacketTracer 6.0.1
    Comment=Networking Cisco
    GenericName=Cisco PacketTracer 6
    Type=Application
    Exec=/opt/pt/packettracer
    Icon=/usr/share/icons/Cisco_Packet_Tracer_Icon.png
    Categories=Education;
    StartupNotify=true

    Se guarda y cierra el archivo de texto escribiendo en la linea de comandos de vim:

    wq
    parametros-lanzador
    Editor de Texto VIM – Parámetros de Lanzador (note el comando para cerrar en la parte inferior izquierda)

    Y ya desde consola se retorna al usuario común

     # exit
  4. Se comprueba el funcionamiento del lanzador
    lanzador-packet tracer
    Lanzador de Packet Tracer

    Packettracer
    Packet Tracer 6 llamado desde lanzador

 

Agradecimientos
Fedora 21: Instalar packettracer 6 por Hades Canek