Tutorial de Python Django

Pantallazo código fuente Python Django 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 posteriormente 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.

Django es un framework open source para desarrollo web que promueve el desarrollo rápido de aplicaciones. Está programado en el lenguaje de programación Python.

Cómo instalar Django

Para comenzar, necesitaremos instalar la librería Django. Lo más conveniente es instalarla en un entorno virtual:

$ virtualenv ~/.virtualenvs/todo_django --python=python3
$ source ~/.virtualenvs/todo_django/bin/activate
(todo_django)$ pip install Django

Si no tenemos virtualenv instalado en nuestro sistema, podemos instalarlo siguiendo las instrucciones del tutorial de Python virtualenv.

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 de base de datos más populares como PostgreSQL, MySQL, Oracle y SQLite. Para efectos de este tutorial vamos a instalar y configurar PostgreSQL.

Con PostgreSQL ya instalado en nuestra máquina, ahora debemos instalar el driver psycopg2 que Django necesita para comunicarse con una base de datos PostgreSQL.

Para que la compilación del driver se pueda realizar, debemos instalar los siguientes paquetes:

# macOS
$ xcode-select --install

# Debian o Ubuntu
$ sudo apt-get install build-essential postgresql-server-dev-all python-dev

# 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++

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

Para luego instalar el driver psycopg2:

(todo_django)$ pip install psycopg2

Cómo crear un nuevo proyecto Django

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

Ahora que tenemos Django y PostgreSQL instalados en el entorno virtual todo_django, podemos crear un nuevo proyecto:

(todo_django)$ 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.

Cómo configurar un proyecto Django

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 la primera migración para crear las tablas en la base de datos:

(todo_django)$ python manage.py migrate

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:

(todo_django)$ 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:

Aplicación web Django funcionando

¡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.

Cómo crear una aplicación Django

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:

(todo_django)$ 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:

(todo_django)$ python manage.py makemigrations todo
(todo_django)$ 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.template.response import TemplateResponse
from django.views.generic import View

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.http import HttpResponseRedirect

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:

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 ruta. ¿Que valores pueden ser obtenidos? Todos los que se hayan definidos en archivo urls.py. De ello hablaremos en seguida.

Relacionando rutas con vistas

Para que las vistas que hemos creado puedan ser de alguna utilidad, debemos relacionar cada una de las vistas con su respectiva ruta, 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 relacionar las siguientes rutas:

  • La ruta / con la vista TodoListView, la cual muestra una lista con todas las tareas.
  • La ruta /add/ con la vista TodoAddView, la cual permite crear una nueva tarea.
  • La ruta /done/<todo_id>/ con la vista TodoDoneView, que permite marcar una tarea como completada.
from django.urls import path

from todo import views

urlpatterns = [
    path(r'', views.TodoListView.as_view(), name='todo-list'),
    path(r'add/', views.TodoAddView.as_view(), name='todo-add'),
    path(
        r'done/<int:todo_id>/',
        views.TodoDoneView.as_view(),
        name='todo-done'
    )
]

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

  • Un patrón de URL.
  • Una función que permite ejecutar la vista.
  • Un nombre para poder hacer referencia a esta ruta en otros archivos Python o plantillas.

Para versiones de Django anteriores a la 2.0, se debe usar la función url() en lugar de path() para definir rutas. La función url() se puede importar desde django.conf.urls.

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

Note como el patrón de URL definido para la vista TodoDoneView contiene la expresión <int:todo_id>. Estas expresiones permiten definir variables dinámicas en la ruta. En este caso estamos indicándole a Django que la ruta 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.

Para versiones de Django anteriores a la 2.0, los patrones de URL se definen mediante expresiones regulares. La siguiente expresión regular corresponde al patrón de URL para la vista todo-done explicada anteriormente: r'^done/(?P<todo_id>\d+)$'

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 ruta en el archivo urls.py exige que se proporcione un id.

Entonces, cuando el usuario cargue la ruta /, 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:

(todo_django)$ python manage.py runserver

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

Aplicación web Django lista de tareas vacia

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

Aplicación web Django agregar tarea

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:

Aplicación web Django lista de tareas

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

Aplicación web Django tarea completada

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!