Tutorial de Python Django

models.py models.py por nyuhuhuu. CC BY. Imagen con calidad y tamaño reducidos.

En este tutorial de Python Django, vamos a explicar cómo instalar Django y PostgreSQL. También explicaremos cómo crear una aplicación web de lista de tareas utilizando el API y las utilidades de línea de comandos que Django nos ofrece.

Este tutorial aplica para la versión 2.7 de Python y 1.11 de Django.

Instalando Django y PostgreSQL

Para comenzar, necesitaremos instalar la librería Django. Podemos instalarla globalmente en el sistema usando pip:

$ sudo pip install Django

Si no tenemos pip instalado en nuestro sistema, podemos instalarlo siguiendo las instrucciones de la sección Cómo instalar pip usando una distribución Linux del artículo Cómo instalar un paquete Python con pip.

Los usuarios Archlinux deben ejecutar pip2 en lugar de pip, si instalaron pip usando el gestor de paquetes.

Es recomendable usar virtualenv en conjunto con el archivo requirements.txt cuando instalemos librerías para nuestros proyectos.

Para aprovechar todo el potencial que Django nos ofrece, se hace necesaria una base de datos. Django actualmente ofrece soporte para los motores PostgreSQL, MySQL, Oracle y SQLite. En este tutorial vamos a usar PostgreSQL. Para instalarlo ejecutamos los siguientes comandos:

>> Archlinux
$ sudo pacman -S postgresql
$ sudo -u postgres pg_ctl initdb -D /var/lib/postgres/data

>> Fedora
$ sudo dnf install postgresql-server postgresql-contrib
$ sudo -u postgres pg_ctl initdb -D /var/lib/pgsql/data

>> CentOS
$ sudo yum install postgresql-server postgresql-contrib
$ sudo -u postgres pg_ctl initdb -D /var/lib/pgsql/data

>> Debian, Ubuntu
$ sudo apt-get install postgresql postgresql-contrib postgresql-client

>> macOS (con Homebrew)
$ brew install postgresql
$ initdb /usr/local/var/postgres

Debemos asegurarnos que PostgreSQL este configurado para recibir conexiones desde localhost. Para esto, abrimos el archivo pg_hba.conf ubicado en el directorio de configuración de PostgreSQL y lo modificamos de las siguiente forma:

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
# IPv6 local connections:
host    all             all             ::1/128                 md5

En sistemas Archlinux, el archivo de configuración se encuentra en el directorio /var/lib/postgres/data.

En sistemas Fedora y CentOS, el archivo de configuración se encuentra en el directorio /var/lib/pgsql/data.

En sistemas Debian y Ubuntu, el archivo de configuración se encuentra en el directorio /etc/postgresql/<version>/main.

En sistemas macOS, si PostgreSQL fue instalado usando Homebrew, el archivo de configuración se encuentra en el directorio /usr/local/var/postgres.

Como se puede apreciar, el método de autenticación para las dos últimas entradas debe ser md5 en lugar de ident. Esto le permitirá a la aplicación acceder a la base de datos usando el usuario y la contraseña que se van a definir más adelante en el archivo settings.py.

Para que los cambios al archivo de configuración se apliquen, debemos reiniciar PostgreSQL de la siguiente forma:

>> Archlinux
$ sudo -u postgres pg_ctl restart -D /var/lib/postgres/data

>> Fedora, CentOS
$ sudo -u postgres pg_ctl restart -D /var/lib/pgsql/data

>> Debian, Ubuntu
$ sudo -u postgres pg_ctlcluster <version> main restart

>> macOS
$ pg_ctl restart -D /usr/local/var/postgres

Ahora instalamos algunos paquetes necesarios para compilar el driver psycopg2 que Django necesita para comunicarse con una base de datos PostgreSQL:

>> Archlinux
$ sudo pacman -S postgresql-libs make automake gcc

>> Fedora
$ sudo dnf install postgresql-devel python-devel make automake gcc gcc-c++

>> CentOS
$ sudo yum install postgresql-devel python-devel make automake gcc gcc-c++

>> Debian, Ubuntu
$ sudo apt-get install build-essential postgresql-server-dev-all python-dev

>> macOS
$ xcode-select --install

Para finalizar instalamos el driver psycopg2:

$ sudo pip install psycopg2

Creando un nuevo proyecto

El código fuente explicado en este artículo puede ser descargado desde Github o Bitbucket.

Ahora que tenemos Django instalado, podemos crear un nuevo proyecto:

$ django-admin startproject todo_django

De esta forma, Django creará automáticamente un directorio con la siguiente estructura:

todo_django/
  manage.py
  todo_django/
    __init__.py
    settings.py
    urls.py
    wsgi.py

El archivo manage.py sirve para interactuar con los comandos administrativos de nuestro proyecto, como por ejemplo python manage.py migrate, el cual sirve para generar y modificar las tablas de la base de datos.

En el directorio interno todo_django/ encontramos el archivo settings.py que contiene algunas variables de configuración del framework, como por ejemplo, información sobre las bases de datos o ubicación de los archivos estáticos de nuestro sitio o aplicación web.

En el archivo urls.py se definen las rutas o URLs de nuestro sitio web. Explicaremos con mayor detalle el contenido de este archivo más adelante. Por último, el archivo wsgi.py actúa como punto de entrada para servir el proyecto cuando sea desplegado al servidor de producción.

Configurando el proyecto

Luego de haber instalado las librerías necesarias y después de haber creado el nuevo proyecto, debemos configurarlo de acuerdo con nuestras necesidades. Por ahora vamos a configurar la base de datos. Para esto, abrimos el archivo settings.py y modificamos la variable DATABASES:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'todo_django_db',
        'USER': 'todo_django_user',
        'HOST': 'localhost',
        'PASSWORD': 'password'
    }
}

La configuración anterior le indica a Django que nuestra base de datos es de tipo PostgreSQL, con el nombre todo_django_db y el usuario todo_django_user.

Django es un framework altamente configurable. Desafortunadamente, no es posible explicar todas y cada una de las configuraciones en este tutorial. Pero para el lector interesado, existe una página de referencia en inglés que explica todas las configuraciones disponibles.

Ahora debemos crear la base de datos y asignarle el usuario y la contraseña definidos en el archivo de configuración:

$ sudo -u postgres createuser -P -d todo_django_user
Enter password for new role:
Enter it again:

$ createdb todo_django_db -U todo_django_user -h localhost
Password:

Por último, ejecutamos el siguiente comando para crear las tablas, que Django define por defecto, en la base de datos:

$ python manage.py migrate

Los usuarios Archlinux deben ejecutar python2 en lugar de python.

Probando la instalación de Django y PostgreSQL

Para probar que Django ha sido instalado correctamente, ejecutamos el siguiente comando en el directorio del proyecto:

$ python manage.py runserver

Este comando ejecuta un servidor HTTP de prueba que viene por defecto con Django. No es recomendable utilizarlo para servir una aplicación web en un servidor de producción debido a que no está optimizado para grandes cantidades de tráfico ni posee el nivel de seguridad apropiado.

Abrimos nuestro navegador web e ingresamos la dirección http://localhost:8000. Si todo va bien, veremos algo como esto:

Python Django funciona!

¡Felicitaciones! Nuestra instalación de Django y PostgreSQL funciona a la perfección. Ahora estamos listos para implementar nuestra pequeña aplicación de lista de tareas.

Creación de la aplicación

Hasta el momento hemos instalado algunas librerías y herramientas y configurado la base de datos. Como se mencionó al principio del tutorial, vamos a desarrollar una aplicación de lista de tareas que le permita al usuario agregar y marcar como completada una o más tareas.

Para crear la aplicación, ejecutamos el siguiente comando dentro del directorio del proyecto:

$ python manage.py startapp todo

El comando automáticamente crea un directorio todo, dentro del directorio del proyecto, con la siguiente estructura:

todo_django/
  todo/
    __init__.py
    admin.py
    models.py
    tests.py
    views.py
    migrations/
      __init__.py

Definición del modelo de datos

En el modelo de datos se define la información que es indispensable para el funcionamiento del sitio o aplicación web. En Django, el modelo de datos se compone de un conjunto de clases y cada una representa una tabla en la base de datos. Para nuestro proyecto de lista de tareas, vamos a almacenar la descripción de la tarea y un valor para saber si ya fue completada o no.

Para definir el modelo de datos, implementamos la clase Todo en el archivo models.py:

from django.db import models

class Todo(models.Model):
    description = models.CharField(max_length=128)
    is_done = models.BooleanField(default=False)

El atributo description es una cadena de caracteres y tiene una longitud máxima de 128 caracteres. El atributo is_done es un valor booleano, es decir que solamente puede ser verdadero o falso y su valor por defecto es falso.

Ahora debemos agregar nuestra aplicación todo al archivo de configuración settings.py, dentro de la variable INSTALLED_APPS:

INSTALLED_APPS = (
    'todo'
)

De esta forma estamos activando la aplicación que acabamos de crear para poder generar las tablas de la base de datos:

$ python manage.py makemigrations todo
$ python manage.py migrate

El comando makemigrations crea un archivo .py dentro del directorio migrations de la aplicación todo. Este archivo contiene código Python generado automáticamente que define las operaciones que el ORM de Django debe hacer en la base de datos.

El comando migrate le indica a Django que debe aplicar en la base de datos, las operaciones del archivo que se acabó de generar.

Definición de las vistas

Dentro de la terminología que usa Django, las vistas son un conjunto de funcionalidades que permiten el procesamiento de la información contenida en el modelo de datos para presentarle al usuario un resultado específico.

Por ejemplo, una vista puede consultar todas las tareas en la base de datos para presentarle al usuario una página web con un listado de tareas, cuando este accede a una URL específica.

Las vistas pueden ser definidas como funciones o como clases. En este tutorial vamos a definir las vistas usando clases. La primera vista que vamos a definir es el listado de tareas, implementando el siguiente fragmento de código en el archivo views.py:

from django.views.generic import View
from django.template.response import TemplateResponse
from todo.models import Todo

class TodoListView(View):
    def get(self, request,_args, _*kwargs):
        context = {
            'todo_list': Todo.objects.all()
        }
        return TemplateResponse(request, 'todo_list.html', context)

La función get() de la vista corresponde a la petición GET de HTTP. Dicha función devuelve una respuesta al navegador con los datos de la petición HTTP almacenados en la variable request y con el contenido de la plantilla todo_list.html.

En el diccionario context incluimos todos los datos que queremos mostrar en la plantilla. En este caso incluimos el listado de todas las tareas almacenadas en la base de datos, obtenido al invocar a la función Todo.objects.all().

Ahora vamos a implementar la vista que permite crear una nueva tarea:

from django.views.generic import View
from django.http import HttpResponseRedirect
from todo.models import Todo

class TodoAddView(View):
    def get(self, request,_args, _*kwargs):
        return TemplateResponse(request, 'todo_add.html', {})

    def post(self, request,_args, _*kwargs):
        description = request.POST['description']
        Todo.objects.create(description=description)
        return HttpResponseRedirect('/')

Esta vista puede recibir tanto peticiones GET como peticiones POST. Cuando la petición es GET, la vista le presenta al usuario el formulario para crear una nueva tarea.

Cuando la petición es POST, la vista almacena los valores contenidos en los campos del formulario HTML en el diccionario de datos request.POST para que puedan ser usados más adelante.

Al invocar la función Todo.objects.create() estamos creando una nueva tarea en la base de datos con la descripción obtenida del formulario, mediante la función request.POST['description'].

Los parámetros que recibe la función create() son los mismos que definimos en el modelo de datos para la clase Todo, es decir description e is_done.

Por último, implementamos la vista que le permitirá al usuario marcar una tarea como completada:

from django.views.generic import View
from django.http import HttpResponseRedirect
from todo.models import Todo

class TodoDoneView(View):
    def get(self, request,_args, _*kwargs):
        todo = Todo.objects.get(id=kwargs['todo_id'])
        todo.is_done = True
        todo.save()
        return HttpResponseRedirect('/')

La función Todo.objects.get() consulta una tarea de la base de datos teniendo en cuenta su id o clave primaria. Enseguida actualizamos el valor del atributo is_done para así marcar la tarea como completada.

El parámetro kwargs contiene todos los valores que puedan ser obtenidos de la URL. ¿Que valores pueden ser obtenidos? Todos los que se hayan definidos en archivo urls.py. De ello hablaremos en seguida.

Relacionando URLs con vistas

Para que las vistas que hemos creado puedan ser de alguna utilidad, debemos relacionar una URL con cada vista, de tal manera que cuando un usuario ingrese una URL específica en su navegador web, pueda ver la página apropiada. Para nuestro proyecto, vamos a modificar el archivo urls.py para agregar las siguientes relaciones:

  • La URL / con la vista que enlista todas las tareas
  • La URL /add/ con la vista que crea una nueva tarea
  • La URL /done/<todo_id>/ con la vista que marca una tarea como completada
from django.conf.urls import url
from todo import views

urlpatterns = [
    url(r'^$', views.TodoListView.as_view(), name='todo-list'),
    url(r'^add/$', views.TodoAddView.as_view(), name='todo-add'),
    url(
        r'^done/(?P<task_id>\d+)/$',
        views.TodoDoneView.as_view(),
        name='todo-done'
    )
]

Para definir una URL utilizamos la función url() que acepta como parámetros:

  • La URL mediante una expresión regular.
  • Una función que permite ejecutar la vista.
  • Un nombre para poder hacer referencia a esta URL en otros archivos o plantillas.

Cada vista expone una función as_view(), la cual sirve como punto de entrada para la ejecución de la misma.

Note como la URL definida para la vista TodoDoneView contiene la expresión regular (?P<todo_id>\d+). Estas expresiones permiten definir variables dinámicas en la URL. En este caso estamos indicándole a Django que la URL contiene un número que corresponde con el id de una tarea y que podemos obtener este valor a través de la variable kwargs['todo_id'] en la función get() de la vista.

Implementando las plantillas

Las plantillas de Django son archivos HTML que contienen algunas etiquetas especiales usadas generalmente para inyectar los datos provenientes de la ejecución de las vistas. Para nuestra aplicación de lista de tareas necesitamos implementar tres plantillas: base.html, todo_list.html y todo_add.html.

De acuerdo con la configuración por defecto, Django va a buscar las plantillas en el directorio templates/ dentro de cada aplicación. Es decir que nuestras plantillas deben ubicarse en el directorio:

todo_django/
  todo/
    templates/
      base.html
      todo_list.html
      todo_add.html

La plantilla base.html va a servir como modelo para implementar las demás plantillas. En esta plantilla incluiremos el código HTML que es común para todas las páginas de nuestra aplicación: la sección <head>, el encabezado y el pie de página.

<!DOCTYPE html>
<html>
  <head>
    <title>{% block title %}Todo list{% endblock %}</title>
  </head>
  <body>
    <h1>{% block heading %}Todo list{% endblock %}</h1>
    {% block body %}{% endblock %}
    <p>Powered by Django</p>
  </body>
</html>

Las etiquetas {% block ... %}{% endblock %} sirven para indicarle a Django que el contenido encerrado en ellas, puede ser modificado por otra plantilla. El sistema de plantillas de Django utiliza un mecanismo de extensión, que hace posible la reutilización de código HTML.

Para demostrar este concepto, implementemos la plantilla todo_list.html:

{% extends "base.html" %}

{% block body %}
  <a href="{% url 'todo-add' %}">Add todo</a>
  <ul>
    {% for todo in todo_list %}
      <li>
        {{ todo.description }}
        -
        {% if not todo.is_done %}
          <a href="{% url 'todo-done' todo.id %}">Mark as done</a>
        {% else %}
          <span>Done!</span>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
{% endblock %}

La etiqueta {% extends ... %} se usa para indicarle a Django que la plantilla va a utilizar el código HTML de una plantilla base. Como se puede apreciar, la plantilla todo_list.html redefine el contenido de la etiqueta {% block %} presente inicialmente en la platilla base.html.

La etiqueta {% for ... %} es utilizada para iterar sobre una colección de objetos. Funciona de forma muy similar al for en Python. Aquí estamos iterando sobre cada uno de los elementos de la variable todo_list, proveniente de la vista TodoListView.

Mediante la etiqueta {% if %}, revisamos si la tarea se ha marcado como completada o no. En caso afirmativo, un enlace se encontrará disponible para marcarla. En caso contrario, el enlace es reemplazado por texto que indica que la tarea ya fue completada.

La etiqueta {% url ... %} permite resolver automáticamente la URL de una vista teniendo en cuenta el nombre que le fue asignado en el archivo urls.py. En este caso se está refiriendo a la URL con el nombre todo-done que corresponde a la vista TodoDoneView.

Observe como además del nombre, la etiqueta {% url %} también recibe el id o clave primaria de una tarea. Esto se debe a que la definición de la URL en el archivo urls.py exige que se proporcione un id.

Entonces, cuando el usuario cargue la URL /, el código HTML completo de la página que lista las tareas se verá algo como esto:

<!DOCTYPE html>
<html>
  <head>
    <title>Todo list</title>
  </head>
  <body>
    <h1>Todo list</h1>
    <ul>
      <li>
        Todo A
        <a href="/done/1/">Mark as complete</a>
      </li>
      <li>
        Todo B
        <a href="/done/2/">Mark as complete</a>
      </li>
      ...
    </ul>
    <p>Powered by Django</p>
  </body>
</html>

Lo que en definitiva es el resultado de unir el código HTML presente en la plantilla base.html con el presente en la plantilla todo_list.html.

Igualmente, implementamos el formulario para crear tareas en la plantilla todo_add.html:

{% extends "base.html" %}

{% block title %}
    Add todo
{% endblock %}

{% block heading %}
    Add todo
{% endblock %}

{% block body %}
  <form action="{% url 'todo-add' %}" method="post">
    {% csrf_token %}
    <label for="description">Description</label>
    <input type="text" id="description" name="description">
    <input type="submit" name="submit" value="Add todo">
  </form>
{% endblock %}

La etiqueta de Django {% csrf_token %} es una medida de seguridad para proteger a la aplicación web contra ataques de tipo Cross Site Request Forgery. La etiqueta agrega un input oculto cuyo contenido es un token o valor aleatorio generado por el servidor antes de mostrar el formulario al usuario. Si el formulario no envía de vuelta éste token al servidor, no es posible procesar el formulario.

Al crear el formulario debemos tener cuidado a la hora de configurar los nombres para cada uno de los controles input. El atributo name debe coincidir con los nombres que estamos usando para obtener los datos del diccionario request.POST en la vista TodoAddView.

Probando la aplicación

Para probar nuestra aplicación, ejecutamos el servidor de prueba usando una terminal:

$ python manage.py runserver

Cargamos la URL http://localhost:8000 en el navegador web de nuestra preferencia. Debemos ver algo como esto:

Python Django app

Si hacemos clic en el enlace Add todo, veremos el formulario para agregar una nueva tarea:

Python Django app

Para agregar una nueva tarea, llenamos el campo description y hacemos clic en el botón Add todo. Veremos que el listado ahora muestra la tarea que acabamos de agregar:

Python Django app

Para marcar la tarea como completada, hacemos clic en el enlace Mark as done. Como podemos ver, el enlace ha desaparecido:

Python Django app

Y es así como hemos desarrollado una aplicación web de lista de tareas usando Python Django. Es una aplicación muy simple pero que demuestra con claridad los conceptos básicos de desarrollo web con Django.

Recuerden que el código fuente explicado en este articulo se puede descargar desde Github o Bitbucket. Hasta la próxima!