Tutorial de 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:
¡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 vistaTodoListView
, la cual muestra una lista con todas las tareas. - La ruta
/add/
con la vistaTodoAddView
, la cual permite crear una nueva tarea. - La ruta
/done/<todo_id>/
con la vistaTodoDoneView
, 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 depath()
para definir rutas. La funciónurl()
se puede importar desdedjango.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:
Si hacemos clic en el enlace Add todo, veremos el formulario para agregar una nueva 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:
Para marcar la tarea como completada, hacemos clic en el enlace Mark as done. Como podemos ver, el enlace ha desaparecido:
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!