Primeros pasos con Django
Tabla de Contenidos
Introducción:
Django es un framework de desarrollo web de código abierto, escrito en Python, que respeta el patrón de diseño conocido como MVC (Modelo–Vista–Controlador)
Homepage: https://www.djangoproject.com/
Documentación: https://docs.djangoproject.com/es/3.1/
Instalación:
Preparando el entorno de trabajo
Instalar módulo para crear entornos virtuales en Python (3):
# apt install python3-venv
Creamos una carpeta para crear nuestros proyectos Django.
$ mkdir django $ cd django
Creamos un entorno virtual para Python 3.8 (dependerá la versión que tengan instalada):
$ python3 -m venv venv3.8
Activamos el entorno virtual;
$ source venv3.8/bin/activate
Para desactivarlo:
$ deactivate
Utilizamos pip para gestionar los módulos en el entorno virtual:
$ pip list Package Version ------------- ------- pip 20.1.1 pkg-resources 0.0.0 setuptools 44.0.0
Django
Instalamos Django (última versión por defecto), instalará también algunas dependencias:
$ pip install django Collecting django Downloading Django-3.1.2-py3-none-any.whl (7.8 MB) |████████████████████████████████| 7.8 MB 1.5 MB/s Collecting sqlparse>=0.2.2 Using cached sqlparse-0.3.1-py2.py3-none-any.whl (40 kB) Collecting pytz Downloading pytz-2020.1-py2.py3-none-any.whl (510 kB) |████████████████████████████████| 510 kB 13.0 MB/s Collecting asgiref~=3.2.10 Using cached asgiref-3.2.10-py3-none-any.whl (19 kB) Installing collected packages: sqlparse, pytz, asgiref, django Successfully installed asgiref-3.2.10 django-3.1.2 pytz-2020.1 sqlparse-0.3.1
Nos quedará la siguiente lista de módulos instalados:
$ pip list Package Version ------------- ------- asgiref 3.2.10 Django 3.1.2 pip 20.1.1 pkg-resources 0.0.0 pytz 2020.1 setuptools 44.0.0 sqlparse 0.3.1
Si quisieramos instalar otro entorno virtual, ya sea en la misma máquina o en otra, podemos exportar la lista de módulos y sus versiones para recrear el entorno. Con la opción freeze de pip podemos obtener la lista y redirigir a un archivo, el que luego podemos utilizar para instalar los módulos en el nuevo entorno:
// guardar lista de módulos $ pip freeze > requirements.txt // instalar lista. $ pip install -r requirements.txt
Proyecto1
Este ejemplo muestra el paso a paso (con Django ya instalado) la evolución de un primer proyecto.
Creación del proyecto
Al ubicarnos en la carpeta django para los proyectos antes creada tenemos hasta ahora:
$ ls -l -rw-r--r-- 1 amvaldesj amvaldesj 59 Oct 6 09:35 requirements.txt drwxr-xr-x 6 amvaldesj amvaldesj 4096 Oct 6 09:30 venv3.8
Creamos entonces en este nivel el proyecto que nombraremos proyecto1:
$ django-admin startproject proyecto1
Y la carpeta de trabajo nos queda:
$ ls -l drwxr-xr-x 3 amvaldesj amvaldesj 4096 Oct 6 09:36 proyecto1 -rw-r--r-- 1 amvaldesj amvaldesj 59 Oct 6 09:35 requirements.txt drwxr-xr-x 6 amvaldesj amvaldesj 4096 Oct 6 09:30 venv3.8
Ingresamos a la carpeta proyecto1 para trabajarlo:
$ cd proyecto1 $ ls -l -rwxr-xr-x 1 amvaldesj amvaldesj 665 Oct 6 09:36 manage.py drwxr-xr-x 2 amvaldesj amvaldesj 4096 Oct 6 09:36 proyecto1
Podemos probar como se ve hasta ahora ejecutando el script manager.py, el cual tiene múltiples opciones y una de ellas es levantar un servicio web local:
$ python manage.py runserver
Y abrimos en una navegador web la url http://127.0.0.1:8000/', debería aparecer una página de bienvenida y un cohete despegando...
Base de Datos
Las configuraciones de Django se realizan en el archivo settings.py, el que en nuestro ejemplo se encuentra proyecto1/settings.py'.
Si usamos alguna base de datos, se debe modificar las opciones del motor de base de datos a utilizar y que por defecto es sqlite. Vamos a usar esta por defecto.
Realizamos la carga inicial de la base de datos. Se crearan las tablas iniciales que requiere Django:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying sessions.0001_initial... OK
TIP Si modificamos/creamos modelos (Model) debemos primero preparar la migración com makemigrations y luego aplicar migrate:
$ python manage.py makemigrations $ python manage.py migrate
Administración
Necesitamos en primer lugar crear un usuario que nos permita administrar Django. Creamos el usuario admin con clave admin:
$ python manage.py createsuperuser Username (leave blank to use 'amvaldesj'): admin Email address: admin@test.cl Password: Password (again): The password is too similar to the username. This password is too short. It must contain at least 8 characters. This password is too common. Bypass password validation and create user anyway? [y/N]: y Superuser created successfully.
Levantamos el servidor local y visitamos el link: http://127.0.0.1:8000/admin
Creando una Aplicación
Hemos creado el proyecto1 sin embargo ahora debemos crear las aplicaciones que lo conformarán. Las aplicaciones ser pueden ver como diferentes módulos que son parte del proyecto. Creamos la aplicación principal:
$ python manage.py startapp principal
Debemos agregar esta aplicación en las configuraciones del proyecto para que sea reconocida (en settings.py):
Los Templates
Django utiliza templates para generar las páginas resultantes en HTML, debemos configurar dónde estarán almacenados estos templates. Creamos una carpeta templates y luego la agregamos a las configuraciones en settings.py:
$ mkdir templates
1 import os
2
3 TEMPLATES = [
4 {
5 'BACKEND': 'django.template.backends.django.DjangoTemplates',
6 'DIRS': [os.path.join(BASE_DIR, "templates")],
7 'APP_DIRS': True,
8 'OPTIONS': {
9 'context_processors': [
10 'django.template.context_processors.debug',
11 'django.template.context_processors.request',
12 'django.contrib.auth.context_processors.auth',
13 'django.contrib.messages.context_processors.messages',
14 ],
15 },
16 },
17 ]
Creamos un template básico en templates/principal.html el cual iremos modificando más adelante:
1 <html lang="en">
2 <head>
3 <meta charset="utf-8">
4 <meta name="viewport" content="width=device-width, initial-scale=1">
5 <meta name="description" content="">
6 <meta name="author" content="">
7 <title>{{ settings.APP_NAME }} {{ settings.APP_VERSION }}</title>
8 </head>
9
10 <body>
11 <h1>Hola!</h1>
12 </body>
13 </html>
Podemos crear algunas variables en settings.py y que luego podemos utilizar en nuestros templates. El template anterior los utiliza para mostrar en el title de la página el nombre del proyecto y la versión (ver settings.APP_NAME y settings.APP_VERSION).
Rutas iniciales
En el archivo urls.py vamos a agregar las rutas que Django debe reconocer para direccionar a las vistas correspondientes. Vamos a modificar el archivo para que contenga las siguientes rutas. admin/ viene por defecto. Agregaremos una ruta para la página de inicio, en este caso la expresión regular r'^$' que corresponde a solicitar la raíz direcciona a la vista home, que luego debemos crear en views.py. Agregamos además el módulo url.
Vista página inicial
Ya creadas las rutas debemos crear ahora la vista que se invocará cuando se solicita la página inicial. En el archivo views.py agregamos la vista home. Importamos además el módulo settings, el que se lo pasaremos al template que se retorna en la vista:
Visualizamos la página: http://127.0.0.1:8000/
Creando modelos (Model)
Ya tenemos funcionando nuestro proyecto (la aplicación principal), ahora debemos modificar la base de datos incorporando las tablas (Modelos) necesarias para nuestra plataforma. Esta base de datos registrará información de mascotas y sus dueños. Para eso utilizaremos 3 modelos para representar las cardinalidades 1:N y N:M correspondientes. Este ejemplo mostrará paso a paso la incorporación de cada uno de estos modelos a la base de datos.
El Model Mascota (inicial)
Modelo inicial para Mascota (en models.py):
Preparamos la migración:
$ python manage.py makemigrations Migrations for 'principal': principal/migrations/0001_initial.py - Create model Mascota
Aplicamos los cambios a la Base de datos:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, principal, sessions Running migrations: Applying principal.0001_initial... OK
Actualizamos el template principal.html y agregamos un link:
1 <p><a href="/mascotas/">Mascotas</a></p>
Creamos la url para mascotas en urls.py:
1 url(r'^mascotas/$', views.mascotas, name='mascotas'),
Necesitamos crear en views.py una vista que responderá por la ruta recién creada, en este caso views.mascotas. Necesitamos importar de models.py el modelo Mascota para poder usarlo y traer los objetos de este tipo que estén en la base de datos. Para obtener las mascotas se ejecuta el método Mascota.objects.all():
En la vista anterior se hace referencia a un nuevo template mascotas.html que recibe en la llamada los objetos settings y mascotas. Tales objetos llevan información que en el template se mostrará. Se puede ver que se agregan algunos tags para indicar que hay instrucciones que se deben interpretar y ejecutar, es el caso de los tags if y for.
1 <html lang="en">
2 <head>
3 <meta charset="utf-8">
4 <meta name="viewport" content="width=device-width, initial-scale=1">
5 <meta name="description" content="">
6 <meta name="author" content="">
7 <title>{{ settings.APP_NAME }} {{ settings.APP_VERSION }}</title>
8 </head>
9
10 <body>
11 <h1>Mascotas!</h1>
12
13 {% if mascotas %}
14 <h1>Lista de Mascotas</h1>
15 <ul>
16 {% for m in mascotas %}
17 <li>{{ m.nombre }} ({{ m.edad }})</li>
18 {% endfor %}
19 </ul>
20 {% else %}
21 <h1>No hay Mascotas...</h1>
22 {% endif %}
23 </body>
24 </html>
Probamos el link: http://127.0.0.1:8000/mascotas/
Agregar Mascotas desde la Shell de Django
Podemos utilizar esta shell para ingresar datos a la base de datos rápidamente y comprobar como funciona nuestra página. Debemos importar el Model que nos interesa, en este caso Mascota. Obtenemos las mascotas con Mascota.objects.all() y en este caso no hay ninguna. Luego, creamos un objeto Mascota(nombre='Pinina', edad=3) y lo guardamos en la base de datos con el método save().
En la terminal ejecutamos:
$ python manage.py shell >>> from principal.models import Mascota >>> mascotas = Mascota.objects.all() >>> mascotas <QuerySet []> >>> m = Mascota(nombre='Pinina', edad=3) >>> m <Mascota: Mascota object (None)> >>> m.save() >>> mascotas = Mascota.objects.all() >>> mascotas <QuerySet [<Mascota: Mascota object (1)>]>
Probamos el link: http://127.0.0.1:8000/mascotas/
Agregar Mascotas desde la Administración de Django
Django provee desde la página de administración la posibilidad de poder gestionar la información de los modelos (datos de la base de datos) de una manera más amigable y rápida. Podemas agregar los modelos que necesitemos gestionar, por ejemplo el de Mascota. Los pasos son los siguientes:
editamos el archivo principal/admin.py e importamos el archivo dónde está la definción de los modelos y lo registramos para que Django lo reconozca.
Luego ingresamos a la página de administración http://127.0.0.1:8000/admin/ y podemos ver que el modelo aparece disponible para su gestión (CRUD).
Podemos actualizar la definición del modelo Mascota para que se vea por defecto el nombre.
El Model Persona (cardinalidad 1:N)
Vamos a crear el model Persona y consideraremos que una persona puede ser dueño de varias mascotas y que una mascota tiene (o no) un único dueño. Actualizamos el model Mascota para agregar como clave foranea el atributo dueno. El archivo models.py se vería algo así:
Preparamos los cambios:
$ python manage.py makemigrations Migrations for 'principal': principal/migrations/0002_auto_20201006_1531.py - Create model Persona - Add field dueno to mascota
Y los aplicamos a la base de datos:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, principal, sessions Running migrations: Applying principal.0002_auto_20201006_1531... OK
Actualizamos el template 'mascotas.html' para mostrar ahora el nombre del dueño de las mascotas:
Agregar Persona y asociar a Mascota
Agregamos una persona, otra mascota y las asociamos desde la shell de Django:
$ python manage.py shell >>> from principal.models import Mascota, Persona >>> p = Persona(nombre='Alejandro') >>> p <Persona: Persona object (None)> >>> m = Mascota(nombre='Pelusa', edad=1, dueno=p) >>> p.save() >>> p <Persona: Persona object (1)> >>> m.save() >>> m <Mascota: Mascota object (2)>
Probamos el link: http://127.0.0.1:8000/mascotas/
El Model Color (cardinalidad N:M)
Las mascotas pueden ser de varios colores y un color lo pueden tener más de una mascota. Creamos entonces el model Color y actualizamos el model Mascota que incorporará la relación Muchos a Muchos con la sentencia models.ManyToManyField(to=Color):
1 class Color(models.Model):
2 nombre = models.CharField(max_length=100)
3
4 class Persona(models.Model):
5 nombre = models.CharField(max_length=200)
6
7 class Mascota(models.Model):
8 nombre = models.CharField(max_length=200)
9 edad = models.IntegerField()
10 dueno = models.ForeignKey(to=Persona, on_delete=models.SET_NULL, null=True)
11 colores = models.ManyToManyField(to=Color)
Preparamos los cambios para la base de datos:
$ python manage.py makemigrations Migrations for 'principal': principal/migrations/0003_auto_20201006_1549.py - Create model Color - Add field colores to mascota
Y luego los aplicamos:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, principal, sessions Running migrations: Applying principal.0003_auto_20201006_1549... OK
Actualizamos el template mascotas.html para mostrar ahora los colores que pueda tener una mascota. También, mostramos el id de cada mascota (Django asigna este campo automáticamente como clave primaria a cada Model, a menos que explicitamente le indiquemos uno):
1 {% if mascotas %}
2 <h1>Lista de Mascotas</h1>
3 <ul>
4 {% for m in mascotas %}
5 <li>
6 ID: {{ m.id }}
7 Nombre: {{ m.nombre }}
8 edad: {{ m.edad }}
9 dueño: {{ m.dueno.nombre }}
10 colores:
11 {% for color in m.colores.all %}
12 {{ color.nombre }}
13 {% endfor %}
14 </li>
15 {% endfor %}
16 </ul>
17 {% else %}
18 <h1>No hay Mascotas...</h1>
19 {% endif %}
Agregar Colores y asociar a Mascota
Desde la shell de Django creamos algunos colores y se los asociamos a la mascota con id=1. Obtenemos el objeto mascota con tal identificador mediante el método get() y el filtro correspondiente (Mascota.objects.get(id=1)):
$ python manage.py shell >>> from principal.models import Mascota, Color >>> m = Mascota.objects.get(id=1) >>> m <Mascota: Mascota object (1)> >>> blanco = Color(nombre="Blanco") >>> negro = Color(nombre="Negro") >>> blanco.save() >>> negro.save() >>> m.colores.add(blanco) >>> m.colores.add(negro)
Probamos el link: http://127.0.0.1:8000/mascotas/
Screenshots
Página principal:
Lista de mascotas:
Página de administración de Django:
Descargar proyecto1
Proyecto1 - Autenticación
Vamos a utilizar la máquinaria que provee Django para la autenticación, permitiéndonos utilizar los usuarios que creemos desde la administración. Por ahora, esta actualización muestra cómo utilizar las características de django.contrib.auth.urls. Nos basamos en la página https://developer.mozilla.org/es/docs/Learn/Server-side/Django/Authentication para este ejemplo.
Registration template
Asumiendo que tenemos instalada y funcionando la versión anterior. Crearemos la carpeta registration bajo templates y en ella guardaremos el template login.html. La estructura del proyecto sería:
(venv3.8) amvaldesj@primate:proyecto1$ tree . ├── db.sqlite3 ├── manage.py ├── principal │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20201006_1531.py │ │ ├── 0003_auto_20201006_1549.py │ │ ├── __init__.py │ │ └── __pycache__ │ │ ├── 0001_initial.cpython-38.pyc │ │ ├── 0002_auto_20201006_1531.cpython-38.pyc │ │ ├── 0003_auto_20201006_1549.cpython-38.pyc │ │ └── __init__.cpython-38.pyc │ ├── models.py │ ├── __pycache__ │ │ ├── admin.cpython-38.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── models.cpython-38.pyc │ │ └── views.cpython-38.pyc │ ├── tests.py │ └── views.py ├── proyecto1 │ ├── asgi.py │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── urls.cpython-38.pyc │ │ └── wsgi.cpython-38.pyc │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── requirements.txt └── templates ├── mascotas.html ├── principal.html └── registration └── login.html
El contenido del template login.html se muestra a continuación. Se pueden ver varias validaciones que el mismo Django se encarga de realizar, además del formuario para el ingreso de las credenciales.
1 <p></p>
2
3 {% if form.errors %}
4 <p>El usuario y/o clave son incorrectos, intente de nuevo.</p>
5 {% endif %}
6
7 {% if next %}
8 {% if user.is_authenticated %}
9 <p>Your account doesn't have access to this page. To proceed, please login with an account that has access.</p>
10 {% else %}
11 <p>Ingrese sus credenciales.</p>
12 {% endif %}
13 {% endif %}
14
15 <form method="post" action="{% url 'login' %}">
16 {% csrf_token %}
17 <table>
18 <tr>
19 <td>{{ form.username.label_tag }}</td>
20 <td>{{ form.username }}</td>
21 </tr>
22 <tr>
23 <td>{{ form.password.label_tag }}</td>
24 <td>{{ form.password }}</td>
25 </tr>
26 </table>
27 <input type="submit" value="Ingresar" />
28 <input type="hidden" name="next" value="{{ next }}" />
29 </form>
Nuevas rutas accounts
Actualizamos el archivo urls.py para incluir las rutas para la autenticación (accounts). Agregamos también el módulo include que permite incluir un conjunto de rutas.
Rutas por defecto
Necesitamos indicar a Django algunas rutas por defecto para el manejo de la autenticación, qué rutas llamar para el login y el logout. Actualizamos el archivo settings.py. LOGIN_URL indica la ruta a llamar cuando se requiera autenticación, en este caso, Django cargar el template login.html antes creado. LOGIN_REDIRECT_URL indica la ruta a llamar luego de la autenticación exitosa, en este caso la página principal.
Decoradores (@login_required)
Ahora, necesitamos indicar qué vistas (en views.py) requieren de un usuario autenticado para poder ser ejecutadas. Vamos a agregar en views.py un decorador que nos permita explicitar estas vistas. Utilizamos para esto entonces el decorador @login_required. En nuestro ejemplo, tanto la página principal como el de las mascotas requiere de un usuario autenticado.
1 from django.contrib.auth.decorators import login_required
2
3 # Create your views here.
4 @login_required
5 def home(request):
6 return render(request, "principal.html", {'settings': settings})
7
8 @login_required
9 def mascotas(request):
10 # obtenemos las mascotas de la BD
11 mascotas = Mascota.objects.all()
12 # pasamos el resultado al template.
13 return render(request, "mascotas.html", {'settings': settings, 'mascotas': mascotas})
Pruebas
Probamos el link: http://127.0.0.1:8000/ No debemos estar previamente autenticados en la página de administración de Django, si es así, cerramos la sessión. Deberíamos ver una página como la siguiente:
Notar que la URL ha cambiado (Django redireccionó) a http://127.0.0.1:8000/accounts/login/?next=/ que es lo configurado en settings.py. Ingresamos las credenciales incorrectas admin con clave admin0 y debería mostrar la página indicando en problema:
Si intentamos acceder directamente a una ruta, por ejemplo http://127.0.0.1:8000/mascotas/, Django nos redirije a la página de login:
Actualizamos los templates principal.html y mascotas.html y les agregamos algunos enlaces (para cerrr la sesión). En principal.html mostramos el nombre del usuario autenticado utilizando user.get_username.
principal.html
1 <html lang="en">
2 <head>
3 <meta charset="utf-8">
4 <meta name="viewport" content="width=device-width, initial-scale=1">
5 <meta name="description" content="">
6 <meta name="author" content="">
7 <title>{{ settings.APP_NAME }} {{ settings.APP_VERSION }}</title>
8 </head>
9
10 <body>
11 <p><a href="/mascotas/">Mascotas</a> | <a class="nav-link" href="{% url 'logout' %}?next=/"> Salir</a></p>
12 <h1>Hola! <b>{{ user.get_username }}</b> </h1>
13
14 </body>
15 </html>
mascotas.html
1 <html lang="en">
2 <head>
3 <meta charset="utf-8">
4 <meta name="viewport" content="width=device-width, initial-scale=1">
5 <meta name="description" content="">
6 <meta name="author" content="">
7 <title>{{ settings.APP_NAME }} {{ settings.APP_VERSION }}</title>
8 </head>
9
10 <body>
11 <a class="nav-link" href="/">Home</a> | <a class="nav-link" href="{% url 'logout' %}?next=/"> Salir</a>
12 <h1>Mascotas!</h1>
13
14 {% if mascotas %}
15 <h1>Lista de Mascotas</h1>
16 <ul>
17 {% for m in mascotas %}
18 <li>
19 ID: {{ m.id }}
20 Nombre: {{ m.nombre }}
21 edad: {{ m.edad }}
22 dueño: {{ m.dueno.nombre }}
23 colores:
24 {% for color in m.colores.all %}
25 {{ color.nombre }}
26 {% endfor %}
27 </li>
28 {% endfor %}
29 </ul>
30 {% else %}
31 <h1>No hay Mascotas...</h1>
32 {% endif %}
33 </body>
34 </html>
Las correspondientes vistas serían: