oscarmlage oscarmlage

Desarrollo web con Python: Flask

Written by oscarmlage on

Hace algún tiempo empecé a utilizar Flask para un proyecto personal. Flask es un mini framework de desarrollo en python que me ha convencido desde el principio por su sencillez. Intentaré exponer un pequeño ejemplo para que os hagáis una idea de cómo funciona.

Y como Álex ha escrito un completo tutorial sobre Pylons/Pyramid con un ejemplo de aplicación, no quería ser menos y explicar mis aventuras y desventuras con esta otra pequeña joya de Python así que allá vamos.

Instalación 

Flask está basado en  Werkzeug, una librería WSGI para Python. Si usamos pip y virtualenv para crear el entorno virtual y comenzar a trabajar (situación que ya he explicado en su día) vemos que las dependencias de Flask son mínimas:

$ sudo easy_install pip
$ pip install virtualenv
$ mkdir -p flaskblog/{src,env}
$ cd flaskblog/
$ virtualenv --distribute --no-site-packages env/
$ pip -E env/ install flask
Downloading Flask-0.8.tar.gz
Downloading/unpacking Werkzeug>=0.6.1 (from flask)
Downloading/unpacking Jinja2>=2.4 (from flask)
Installing collected packages: flask, Jinja2, Werkzeug
$ pip -E env/ install flask-wtf
Downloading Flask-WTF-0.5.2.tar.gz
Downloading/unpacking WTForms (from flask-wtf)
Installing collected packages: flask-wtf, WTForms
$ pip -E env/ install flask-sqlalchemy

Una  vez creado el entorno virtual instalamos Flask, que depende de Werkzeug y de Jinja2 como sistema de templates. También instalamos Flask-WTF para tener integración con WTForms y hacer más sencillo el uso de formularios con funciones avanzadas (csrf, etc...). Y por último Flask-SQLAlchemy como complemento ORM para la base de datos.

Requirements y configuración

A continuación vamos a entrar por primera vez en el entorno y crear el fichero de requisitos - yo normalmente le llamo requirements.txt - para poder regenerar el entorno virtual tantas veces y en tantas máquinas como queramos:

$ source env/bin/activate
(env)$ pip freeze > src/requirements.txt

Si luego queremos regenerar el entorno a partir del fichero lo haremos ejecutando el siguiente comando:

$ pip install -E nuevo_env/ -r src/requirements.txt 

Empezamos a montar el esqueleto de la aplicación dentro del directorio src/ creando un directorio para los templates, como queremos que nuestra aplicación pueda tener varios templates a elegir, creamos también el "por defecto":

(env)$ mkdir -p templates/default/

Ya que estamos empezando una aplicación desde cero nos gustaría tener un mínimo de configuración para la misma así que vamos a usar un fichero para que guarde ciertos valores variables:

(env)$ cat config.py
import os
DEBUG = True
_basedir = os.path.abspath(os.path.dirname(__file__))
DATA_PATH = os.path.join(_basedir, 'data')
DEFAULT_TPL = 'default'
USERNAME = 'admin'
PASSWORD = 'default'
SECRET_KEY = 'devel secret key'
URL = 'http://localhost:5000/'
TITLE = 'OurApplication'
VERSION = '0.1'
LANG = 'es'
LANG_DIRECTION = 'ltr'
YEAR = '2012'
del os

Con la configuración y el entorno listos, lo siguiente será empezar la aplicación propiamente dicha, así que manos a la obra.

Primeros pasos con Flask

Vamos a reducir el grueso de la aplicación a un solo archivo, le llamaremos blog.py - ya sé que no suena muy original pero imagino que será entendible -. Nuestro blog.py será el controlador principal, en él incluiremos de forma excepcional el modelo, los formularios y los métodos que se encargarán de la lógica y de llamar a las plantillas. Para empezar por el principio definimos el archivo como una aplicación Flask y cargamos la configuración que antes hemos creado:

# -*- coding: utf-8 -*-
"""
OurApplication
~~~~~~~~~~~~~~
:copyright: (c) 2011 by Oscar M. Lage.
:license: BSD, see LICENSE for more details.
"""
import os
from flask import Flask, render_template, request, redirect
from werkzeug.routing import Rule
# Flask application and config
app = Flask(__name__)
app.config.from_object('config')
# Middleware to serve the static files
from werkzeug import SharedDataMiddleware
import os
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
  '/': os.path.join(os.path.dirname(__file__), 'templates', app.config['DEFAULT_TPL'])
})
# Index
@app.route('/')
def index():
        return render_template(app.config['DEFAULT_TPL']+'/index.html',
                            conf = app.config)
if __name__ == '__main__':
    app.run()

* Lo más raro son esas 3 lineas que hemos incluido en el archivo, una especie de Middleware para poder servir archivos estáticos directamente desde el template seleccionado.

Una vez tenemos la aplicación "preparada" hemos de crear la plantilla para que se pueda ejecutar sin errores, para ello dentro del directorio templates/default/ creamos el archivo index.html al que referenciamos en el controlador, el contenido del archivo podría ser algo así:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" 
      xml:lang="{{ conf['LANG'] }}" lang="{{ conf['LANG'] }}" 
      dir="{{ conf['LANG_DIRECTION'] }}">
<head>
    <title>{% block title %}{% endblock %} [{{ conf['TITLE'] }}]</title>
</head>
<body>
  <div id="header">
    <h1>{{ conf['TITLE'] }}</h1>
  </div>
  <div id="content">
    Contenido
  </div>
    <div id="footer">
        <p>{{ conf['TITLE'] }} - {{ conf['YEAR'] }}</p>
    </div>
</body>

Y ya podríamos ejecutar por primera vez nuestra aplicación desde consola para luego comprobar que todo está correcto en navegador, así que vamos a ello:

$ python blog.py 
 * Running on http://127.0.0.1:5000/
 * Restarting with reloader

Si abrimos el navegador en la url proporcionada (http://127.0.0.1:5000/) obtendremos una pantalla similar a la de la siguiente figura:

SQLAlchemy

Una vez tenemos la base y el esqueleto de la aplicación vamos a incorporar una base de datos para hacerla dinámica. Para ello usaremos Flask-SQLAlchemy. Para trabajar con SQLAlchemy agregamos una nueva variable de configuración para la base de datos:

SQLALCHEMY_DATABASE_URI = 'sqlite:///'+ os.path.join(os.path.dirname(__file__), 'database.db')

Y le decimos a blog.py el esquema que vamos a utilizar:

from flaskext.sqlalchemy import SQLAlchemy
...
db = SQLAlchemy(app)
...
# Model
class Blog(db.Model):
    __tablename__ = 'Blog'
    __mapper_args__ = dict(order_by="date desc")
    id = db.Column(db.Integer, primary_key=True)
    subject = db.Column(db.Unicode(255))
    author = db.Column(db.Unicode(255))
    date = db.Column(db.DateTime())
    content = db.Column(db.Text())

Por último creamos la base de datos desde una consola python para poder usarla desde cualquier parte del controlador, tan simple como entrar a python desde el directorio src/ donde tenemos blog.py y ejecutar lo siguiente:

>>> from blog import db
>>> db.create_all()

Ya tenemos el archivo database.db preparado para cargarlo de datos mediante los métodos correspondientes de blog.py, algunos ejemplos:

@app.route('/add', methods=['GET','POST'])
def add():
	if request.method == 'POST':
		post = Blog(request.form['subject'], request.form['content'])
		db.session.add(post)
		db.session.commit()
		return redirect(url_for('index'))
	return render_template(app.config['DEFAULT_TPL']+'/add.html',
			       conf = app.config)

Flask-WTForms

Una vez tenemos preparada la base de datos y el método que agregará nuevos posts tan solo falta crear el formulario que nos permitirá tal funcionalidad y crear el template, poco más de un par de lineas:

# Create Form
class CreateForm(Form):
    subject = TextField('Subject', [validators.required()])
    content = TextAreaField('Content', [validators.required(), validators.Length(min=1)])

Y el template con el formulario correspondiente sería el siguiente:

<form method="post" action="">
      <dl>
        {{ form.csrf }}
        {{ form.subject.label }} {{ form.subject(style="width:100%") }}
	{% for error in form.subject.errors %} {{ error }} {% endfor %}
		<br />
	{{ form.content.label }} {{form.content(style="height:100px;width:100%") }}
	{% for error in form.content.errors %} {{ error }} {% endfor %}
      </dl>
      <p><input type="submit" value="submit">
    </form>

Y ya tendríamos un formulario para agregar posts totalmente funcional. Entre esta vista, la de un listado de posts y la del detalle de los mismos (ver repositorio más abajo), tendríamos una pequeña aplicación con Flask + SQLAlchemy + WTForms.

Resumiendo

Para finalizar, además de las vistas que hemos dicho que faltaban, tendríamos que dar un poco más de colorido a los templates (css, imágenes...), crear un layout.html que se pueda extender desde el resto de plantillas y poner un poco de orden en todo lo explicado, pero como no quiero extenderme mucho más en el artículo, nada mejor que un pequeño repositorio en el que se pueden ir viendo los avances:

Espero que con este pequeño y humilde ejemplo haya quedado más o menos claro el uso de este framework. Siempre es divertido probar cosas nuevas y cuando se trata de piezas tan sencillas y bien documentadas como las tratadas el placer es doble.