SQL


脥ndice

  1. Introducci贸n
  2. SQL
  3. Tablas
  4. SELECT
  5. Unir Tablas
  6. Modelos Django
  7. Migraciones
  8. Shell (Caparaz贸n)
  9. Django Admin
  10. Muchas m谩s relaciones
  11. Usuarios

Introducci贸n a SQL

  • Hasta ahora, hemos discutido c贸mo construir p谩ginas web simples utilizando HTML y CSS, y c贸mo utilizar Git y GitHub para llevar un registro de los cambios en nuestro c贸digo y colaborar con otros. Tambi茅n nos familiarizamos con el lenguaje de programaci贸n Python y comenzamos a utilizar Django para crear aplicaciones web.
  • Hoy aprenderemos a usar SQL y modelos de Django para almacenar y acceder eficientemente a datos.

SQL

Imagen de logo SQL

Base de Datos

Antes de adentrarnos en el uso de SQL, es fundamental comprender c贸mo se almacena la informaci贸n. En SQL, generalmente trabajamos con bases de datosrelacionales, donde la informaci贸n se organiza entablas. Cada tabla consta de columnas espec铆ficas y un n煤mero variable de filas.

Para nuestra ilustraci贸n en SQL, consideremos un sitio web de aerol铆nea que gestiona informaci贸n sobre vuelos y pasajeros. En la siguiente tabla, registramos diversos vuelos, cada uno con un origen, destino y duraci贸n asociados.

Tabla de vuelos, url de la imagen: /images/flights1.png

Existen varios sistemas de gesti贸n de bases de datos relacionales com煤nmente utilizados para almacenar informaci贸n y que pueden interactuar f谩cilmente con comandos SQL:

Los dos primeros, MySQL y PostgreSQL, son sistemas m谩s robustos que generalmente se ejecutan en servidores distintos de aquellos que ejecutan un sitio web. SQLite, por otro lado, es un sistema m谩s liviano que puede almacenar todos sus datos en un 煤nico archivo. Utilizaremos SQLite a lo largo de este curso, ya que es el sistema predeterminado utilizado por Django.

Tipos de Columnas

As铆 como trabajamos con varios tipos de variables en Python, SQLite tiene tipos que representan diferentes formas de informaci贸n. Otros sistemas de gesti贸n pueden tener tipos de datos diferentes, pero todos son bastante similares a los de SQLite:

  • TEXT: Para cadenas de texto (por ejemplo, el nombre de una persona)
  • NUMERIC: Una forma m谩s general de datos num茅ricos (por ejemplo, una fecha o un valor booleano)
  • INTEGER: Cualquier n煤mero no decimal (por ejemplo, la edad de una persona)
  • REAL: Cualquier n煤mero real (por ejemplo, el peso de una persona)
  • BLOB(Objeto Binario Grande): Cualquier otro dato binario que deseemos almacenar en nuestra base de datos (por ejemplo, una imagen)

Tablas

Ahora, para comenzar a utilizar SQL para interactuar con una base de datos, empecemos por crear una nueva tabla. El comando para crear una nueva tabla se parece a algo como esto:

sql
1CREATE TABLE flights(
2 id INTEGER PRIMARY KEY AUTOINCREMENT,
3 origin TEXT NOT NULL,
4 destination TEXT NOT NULL,
5 duration INTEGER NOT NULL
6);

En el comando anterior, estamos creando una nueva tabla que hemos decidido llamar "flights", y hemos a帽adido cuatro columnas a esta tabla:

  1. id: A menudo es 煤til tener un n煤mero que nos permita identificar de manera 煤nica cada fila en una tabla. Aqu铆 hemos especificado que "id" es un n煤mero entero y tambi茅n que es nuestra clave primariaPRIMARY-KEY, lo que significa que es nuestro identificador 煤nico. Adem谩s, hemos especificado que ser谩 AUTOINCREMENT, lo que significa que no tendremos que proporcionar un id cada vez que agreguemos a la tabla, ya que se har谩 autom谩ticamente.
  2. origin: Hemos especificado que este ser谩 un campo de texto, y al escribir NOT NULL hemos requerido que tenga un valor.
  3. destination: Nuevamente hemos especificado que este ser谩 un campo de texto y hemos evitado que sea nulo.
  4. duration: Nuevamente, este valor no puede ser nulo, pero esta vez se representa como un n煤mero entero en lugar de texto.

Acabamos de ver las restricciones NOT NULL y PRIMARY KEY al hacer una columna, pero hay varias otrasrestrinccionesdisponibles:

  • CHECK: Asegura que se cumplan ciertas restricciones antes de permitir que se agregue/modifique una fila.
  • DEFAULT: Proporciona un valor predeterminado si no se proporciona ning煤n valor.
  • NOT NULL: Garantiza que se proporcione un valor.
  • PRIMARY KEY: Indica que esta es la principal manera de buscar una fila en la base de datos.
  • UNIQUE: Asegura que no haya dos filas con el mismo valor en esa columna.
  • ...

Ahora que hemos visto c贸mo crear una tabla, veamos c贸mo podemos agregar filas a ella. En SQL, hacemos esto mediante el comando INSERT:

sql
1INSERT INTO flights
2(origin, destination, duration)
3VALUES ("New York", "London", 415);

En el comando anterior, hemos especificado el nombre de la tabla en la que deseamos realizar la inserci贸n, luego proporcionamos una lista de los nombres de las columnas para las cuales proporcionaremos informaci贸n, y luego especificamos los VALORES que deseamos llenar en esa fila de la tabla, asegur谩ndonos de que los VALORES est茅n en el mismo orden que nuestra lista correspondiente de columnas. Es importante destacar que no es necesario proporcionar un valor para "id" porque se incrementa autom谩ticamente.

SELECT

Una vez que se ha poblado una tabla con algunas filas, es probable que deseemos una manera de acceder a los datos dentro de esa tabla. Hacemos esto utilizando la consulta SELECT de SQL. La consulta SELECT m谩s simple en nuestra tabla de vuelos podr铆a verse algo as铆:

sql
1SELECT * FROM flights;

La instrucci贸n anterior (*) recupera todos los datos de nuestra tabla de vuelos.

tabla de vuelos, url de la imagen: /images/all.png

Puede ser el caso de que realmente no necesitemos todas las columnas de la base de datos, solo origen y destino. Para acceder solo a estas columnas, podemos reemplazar el * con los nombres de las columnas a las que queremos acceder. La siguiente consulta devuelve todos los or铆genes y destinos.

sql
1SELECT origin, destination FROM flights;
Tabla de vuelos, url de la imagen: /images/flights1.png

A medida que nuestras tablas se vuelven m谩s grandes, tambi茅n querremos reducir las filas que devuelve nuestra consulta. Hacemos esto agregando un WHERE seguido de alguna condici贸n. Por ejemplo, el siguiente comando selecciona solo la fila con un id de 3:

sql
1SELECT * FROM flights WHERE id = 3;
Tabalasde vuelos, url de la imagen: /images/where0.png

Podemos filtrar por cualquier columna, 隆no solo por id!

sql
1SELECT * FROM flights WHERE origin = "New York";
Tabalas de vuelos, url de la imagen: /images/where1.png

Trabajar con SQL en la terminal

Ahora que conocemos algunos comandos b谩sicos de SQL, 隆vamos a probarlos en la terminal! Para trabajar con SQLite en tu computadora, primero debes descargar SQLite. (No se usar谩 en este curso, pero tambi茅n puedes descargarDB Browserpara una forma m谩s amigable de ejecutar consultas SQL).

Podemos empezar creando un archivo para nuestra base de datos, ya sea creando manualmente un nuevo archivo o ejecutandotouch flights.sqlen la terminal. Ahora, si ejecutamossqlite3 flights.sqlen la terminal, nos llevar谩 a un prompt de SQLite donde podemos ejecutar comandos SQL:

sql
1# Ingresando al prompt de SQLite
2(base) % sqlite3 flights.sql
3SQLite version 3.26.0 2018-12-01 12:34:55
4Ingrese ".help" para obtener sugerencias de uso.
5
6# Creando una nueva tabla
7sqlite> CREATE TABLE flights(
8 ...> id INTEGER PRIMARY KEY AUTOINCREMENT,
9 ...> origin TEXT NOT NULL,
10 ...> destination TEXT NOT NULL,
11 ...> duration INTEGER NOT NULL
12 ...> );
13
14# Listando todas las tablas actuales (solo vuelos por ahora)
15sqlite> .tables
16flights
17
18# Consultando todo dentro de vuelos (que ahora est谩 vac铆o)
19sqlite> SELECT * FROM flights;
20
21# Agregando un vuelo
22sqlite> INSERT INTO flights
23 ...> (origin, destination, duration)
24 ...> VALUES ("Nueva York", "Londres", 415);
25
26# Verificando la nueva informaci贸n, que ahora podemos ver
27sqlite> SELECT * FROM flights;
281|Nueva York|Londres|415
29
30# Agregando algunos vuelos m谩s
31sqlite> INSERT INTO flights (origin, destination, duration) VALUES ("Shangh谩i", "Par铆s", 760);
32sqlite> INSERT INTO flights (origin, destination, duration) VALUES ("Estambul", "Tokio", 700);
33sqlite> INSERT INTO flights (origin, destination, duration) VALUES ("Nueva York", "Par铆s", 435);
34sqlite> INSERT INTO flights (origin, destination, duration) VALUES ("Mosc煤", "Par铆s", 245);
35sqlite> INSERT INTO flights (origin, destination, duration) VALUES ("Lima", "Nueva York", 455);
36
37# Consultando esta nueva informaci贸n
38sqlite> SELECT * FROM flights;
391|Nueva York|Londres|415
402|Shangh谩i|Par铆s|760
413|Estambul|Tokio|700
424|Nueva York|Par铆s|435
435|Mosc煤|Par铆s|245
446|Lima|Nueva York|455
45
46# Cambiando la configuraci贸n para hacer que la salida sea m谩s legible
47sqlite> .mode columns
48sqlite> .headers yes
49
50# Consultando toda la informaci贸n nuevamente
51sqlite> SELECT * FROM flights;
52id origin destination duration
53---------- ---------- ----------- ----------
541 Nueva York Londres 415
552 Shangh谩i Par铆s 760
563 Estambul Tokio 700
574 Nueva York Par铆s 435
585 Mosc煤 Par铆s 245
596 Lima Nueva York 455
60
61# Buscando solo aquellos vuelos que tienen origen en Nueva York
62sqlite> SELECT * FROM flights WHERE origin = "Nueva York";
63id origin destination duration
64---------- ---------- ----------- ----------
651 Nueva York Londres 415
664 Nueva York Par铆s 435

Tambi茅n podemos utilizar m谩s que solo la igualdad para filtrar nuestros vuelos. Para valores enteros y reales, podemos usar mayor que o menor que:

sql
1SELECT * FROM flights WHERE duration > 500;
Tablas de vuelos, url de la imagen: /images/500.png

Y tambi茅n podemos utilizar otras l贸gicas(AND, OR)como en Python:

sql
1SELECT * FROM flights WHERE duration > 500 AND destination = "Paris";
Tablas de vuelos, url de la imagen: /images/500andparis.png
sql
1SELECT * FROM flights WHERE duration > 500 OR destination = "Paris";
Tabla de vuelos, url de la imagen: /images/500orparis.png

Tambi茅n podemos usar la palabra clave IN para ver si un dato es una de varias opciones:

sql
1SELECT * FROM flights WHERE origin IN ("New York", "Lima");
Tabla de vuelos, url de la imagen: /images/in.png

Incluso podemos usar expresiones regulares para buscar palabras de manera m谩s amplia utilizando la palabra claveLIKE. La siguiente consulta encuentra todos los resultados con una"a"en el origen, utilizando"%"como un car谩cter comod铆n.

sql
1SELECT * FROM flights WHERE origin LIKE "%a%";
Tabla de vuelos, url de la imagen: /images/like.png

Funciones

UPDATE

UPDATE (ACTUALIZAR) Hasta ahora, hemos visto c贸mo agregar y buscar en tablas, pero tambi茅n es posible que deseemos actualizar filas de una tabla que ya existe. Hacemos esto utilizando el comandoUPDATE, como se muestra a continuaci贸n. Como podr铆as haber deducido al leer esto en voz alta, el comando encuentra todos los vuelos que van de Nueva York a Londres y luego establece sus duraciones en 430.

sql
1UPDATE flights
2SET duration = 430
3WHERE origin = "New York"
4AND destination = "London";

DELETE

Tambi茅n es posible que deseemos la capacidad de eliminar filas de nuestra base de datos, y podemos hacer esto utilizando el comando DELETE. El siguiente c贸digo eliminar谩 todos los vuelos que aterrizan en Tokio:

sql
1DELETE FROM flights WHERE destination = "Tokyo";

Otras Cl谩usulas

Nota

Hay varias cl谩usulas adicionales que podemos usar para controlar las consultas que nos devuelven resultados:

  • LIMIT: Limita el n煤mero de resultados devueltos por una consulta.
  • ORDER BY: Ordena los resultados bas谩ndose en una columna especificada.
  • GROUP BY: Agrupa los resultados seg煤n una columna especificada.
  • HAVING: Permite aplicar restricciones adicionales basadas en el n煤mero de resultados.

Unir tablas

Hasta ahora, solo hemos estado trabajando con una tabla a la vez, pero en la pr谩ctica, muchas bases de datos est谩n pobladas por varias tablas que se relacionan de alguna manera. En nuestro ejemplo de vuelos, imaginemos que tambi茅n queremos agregar un c贸digo de aeropuerto junto con la ciudad. De la manera en que est谩 actualmente configurada nuestra tabla, tendr铆amos que agregar dos columnas m谩s para cada fila. Tambi茅n estar铆amos repitiendo informaci贸n, ya que tendr铆amos que escribir en varios lugares que la ciudad X est谩 asociada con el c贸digo Y.

Una forma de resolver este problema es decidir tener una tabla que lleve un registro de los vuelos y luego otra tabla que lleve un registro de los aeropuertos. La segunda tabla podr铆a tener un aspecto as铆:

Tabla de aeropuertos, url de la imagen: /images/airports.png

Ahora tenemos una tabla que relaciona c贸digos y ciudades. En lugar de almacenar todo el nombre de la ciudad en nuestra tabla de vuelos, ahorrar铆amos espacio de almacenamiento si pudi茅ramos simplemente guardar elidde ese aeropuerto. Por lo tanto, deber铆amos reescribir la tabla de vuelos en consecuencia. Dado que estamos utilizando la columna id de la tabla de aeropuertos para poblarorigin_idydestination_id, llamamos a esos valoresClaves For谩neas.

Tabla de aeropuertos, url de la imagen: /images/flights2.png

Adem谩s de los vuelos y los aeropuertos, una aerol铆nea podr铆a querer almacenar datos sobre sus pasajeros, como en qu茅 vuelo estar谩 cada pasajero. Utilizando la potencia de las bases de datos relacionales, podemos agregar otra tabla que almacene nombres y apellidos, y una clave for谩nea que represente el vuelo en el que se encuentran.

Tabla de aeropuertos, url de la imagen: /images/simple_pass.png

Podemos hacerlo a煤n mejor, ya que una misma persona puede estar en m谩s de un vuelo. Para tener en cuenta esto, podemos crear una tabla depersonasque almacene nombres y apellidos, y una tabla depasajerosque vincule personas con vuelos.

Personas:
Tabla de aeropuertos, url de la imagen: /images/people.png
Pasajeros:
Tabla de aeropuertos, url de la imagen: /images/passengers.png

Debido a que en este caso una sola persona puede estar en muchos vuelos y un solo vuelo puede tener muchas personas, llamamos a la relaci贸n entrevuelosypersonasuna relaci贸n de Muchos a Muchos. La tabla depasajerosque conecta ambas se conoce como una tabla de asociaci贸n.

Unir Query

Aunque ahora almacenamos nuestros datos de manera m谩s eficiente, parece que puede ser m谩s dif铆cil realizar consultas en nuestros datos. Afortunadamente, SQL cuenta con una consultaJOINdonde podemos combinar dos tablas con el fin de realizar otra consulta.

Por ejemplo, digamos que queremos encontrar el origen, destino y nombre de cada viaje que un pasajero est谩 tomando. Tambi茅n, para simplificar en esta tabla, vamos a estar utilizando la tabla depasajerosno optimizada que incluye el id del vuelo, el nombre y apellido. La primera parte de esta consulta se ve bastante familiar:

sql
1SELECT first, origin, destination
2FROM ...

Pero nos encontramos con un problema aqu铆 porque el nombrefirstse almacena en la tabla de pasajeros, mientras que el origenoriginy el destinodestinationse almacenan en la tabla de vuelos. Resolvemos esto uniendo las dos tablas utilizando el hecho de que elflight_iden la tabla de pasajeros corresponde aliden la tabla de vuelos:

sql
1SELECT first, origin, destination
2FROM flights JOIN passengers
3ON passengers.flight_id = flights.id;
Tabla de aeropuertos, url de la imagen: /images/join.png

Acabamos de utilizar algo llamadoINNER JOIN, lo que significa que estamos ignorando las filas que no tienen coincidencias entre las tablas. Pero existen otros tipos de joins, incluyendoLEFT JOINs,RIGHT JOINsyFULL OUTER JOINs, que no discutiremos en detalle aqu铆.

Indexado

Una forma de hacer que nuestras consultas sean m谩s eficientes al trabajar con tablas grandes es crear un 铆ndice similar al 铆ndice que podr铆as ver en la parte posterior de un libro de texto. Por ejemplo, si sabemos que a menudo buscaremos pasajeros por su apellido, podr铆amos crear un 铆ndice desde el apellido hasta el id utilizando el comando:

sql
1CREATE INDEX name_index ON passengers (last);

Vulnerabilidades SQL

Ahora que conocemos los conceptos b谩sicos de c贸mo utilizar SQL para trabajar con datos, es importante se帽alar las principales vulnerabilidades asociadas con el uso de SQL. Comenzaremos con laInyecci贸n SQL.

Importante

Las inyecciones SQL pueden ocurrir en campos de inicio de sesi贸n y en cualquier formulario o entrada de datos que interact煤e con una base de datos a trav茅s de consultas SQL. Los campos de inicio de sesi贸n son un objetivo com煤n para este tipo de ataques. Es esencial implementar pr谩cticas de seguridad adecuadas, como la validaci贸n de la entrada del usuario y el uso de consultas parametrizadas, para prevenir este tipo de ataques. Ejemplo:

Usuario: ' OR '1'='1'; -- Contrase帽a: (dejar en blanco)

Si el sitio no est谩 protegido contra inyecciones SQL, esta entrada podr铆a manipular la consulta SQL y permitir al atacante eludir la autenticaci贸n y acceder a informaci贸n no autorizada.

Cuando un usuario malintencionado ingresa c贸digo SQL como entrada en un sitio para eludir las medidas de seguridad del sitio. Por ejemplo, supongamos que tenemos una tabla que almacena nombres de usuario y contrase帽as, y luego un formulario de inicio de sesi贸n en la p谩gina principal del sitio. Podr铆amos buscar al usuario utilizando una consulta como:

sql
1SELECT * FROM users
2WHERE username = username AND password = password;

Un usuario llamado Neo podr铆a ir a este sitio y escribir"Neo"como nombre de usuario y"12345"como contrase帽a, en cuyo caso la consulta se ver铆a as铆:

sql
1SELECT * FROM users
2WHERE username = "neo" AND password = "12345";

Un hacker, por otro lado, podr铆a escribir "neo"--como nombre de usuario y nada como contrase帽a. Resulta que--representa un comentario en SQL, lo que significa que la consulta se ver铆a as铆:

sql
1SELECT * FROM users
2WHERE username = "neo"--" AND password = "12345";

Esto podr铆a permitir al hacker acceder a informaci贸n sin proporcionar una contrase帽a v谩lida. La Inyecci贸n SQL es un problema de seguridad grave, y es crucial tomar medidas para prevenirlo, como validar y sanitizar la entrada del usuario, y utilizar consultas preparadas o procedimientos almacenados. Debido a que en esta consulta se ha comentado la verificaci贸n de la contrase帽a, el hacker puede iniciar sesi贸n en la cuenta de Neo sin conocer su contrase帽a. Para resolver este problema, podemos utilizar:

  1. Caracteres de escape para asegurarnos de que SQL trate la entrada como texto plano y no como c贸digo SQL.
  2. Una capa de abstracci贸n sobre SQL que incluya su propio conjunto de caracteres de escape, para que no tengamos que escribir consultas SQL nosotros mismos.
  3. La otra vulnerabilidad principal cuando se trata de SQL se conoce como una Condici贸n de Carrera,Race Condition.

    Una condici贸n de carrera es una situaci贸n que ocurre cuando m煤ltiples consultas a una base de datos se realizan simult谩neamente. Cuando no se manejan adecuadamente, pueden surgir problemas en los momentos precisos en que las bases de datos se actualizan. Por ejemplo, supongamos que tengo $150 en mi cuenta bancaria. Una condici贸n de carrera podr铆a ocurrir si inicio sesi贸n en mi cuenta bancaria tanto en mi tel茅fono como en mi computadora e intento retirar $100 en cada dispositivo. Si los desarrolladores de software del banco no manejan correctamente las condiciones de carrera, podr铆a ser posible retirar $200 de una cuenta que solo tiene $150.

    Una soluci贸n potencial para este problema ser铆a bloquear la base de datos. No se permitir铆a ninguna otra interacci贸n con la base de datos hasta que se haya completado una transacci贸n. En el ejemplo del banco, despu茅s de hacer clic en la p谩gina "Realizar un retiro" en mi computadora, el banco podr铆a no permitirme acceder a esa p谩gina en mi tel茅fono.

Modelos Django

Losmodelos de Djangoson un nivel deabstracci贸nsobre SQL que nos permite trabajar con bases de datos utilizando clases y objetos de Python en lugar de consultas SQL directas.

Comencemos a usar modelos creando un proyecto de Django para nuestra aerol铆nea y creando una aplicaci贸n dentro de ese proyecto.

bash
1django-admin startproject airline
2cd airline
3python manage.py startapp flights

Ahora tendremos que pasar por el proceso de agregar una aplicaci贸n como de costumbre:

  1. Agrega 'flights' a la lista INSTALLED_APPS ensettings.py.
  2. Agrega una ruta para 'flights' enurls.py:
  3. python
    1path("flights/", include("flights.urls")),
  4. Crea un archivourls.pydentro de la aplicaci贸n "flights" y ll茅nalo con las importaciones y listas est谩ndar deurls.py.

Ahora, en lugar de crear rutas reales y empezar con views.py, crearemos algunos modelos en el archivo models.py. En este archivo, delinearemos los datos que queremos almacenar en nuestra aplicaci贸n. Luego, Django determinar谩 la sintaxis SQL necesaria para almacenar informaci贸n sobre cada uno de nuestros modelos. Echemos un vistazo a c贸mo podr铆a ser el modelo para un vuelo individual:

python
1class Flight(models.Model):
2origin = models.CharField(max_length=64)
3destination = models.CharField(max_length=64)
4duration = models.IntegerField()

Echemos un vistazo a lo que est谩 sucediendo en esta definici贸n de modelo:

  • En la primera l铆nea, creamos un nuevo modelo que extiende la clase de modelo de Django.
  • A continuaci贸n, agregamos campos para origen, destino y duraci贸n. Los dos primeros son camposChar, lo que significa que almacenan cadenas, y el tercero es un campoInteger. Estos son solo dos de las muchasclases de campos integradasen Django.
  • Especificamos longitudes m谩ximas de 64 para los dos campos de caracteres. Puedes verificar las especificaciones disponibles para un campo dado consultando ladocumentaci贸n.

Migraciones

Ahora, aunque hemos creado un modelo, a煤n no tenemos una base de datos para almacenar esta informaci贸n. Para crear una base de datos a partir de nuestros modelos, navegamos al directorio principal de nuestro proyecto y ejecutamos el siguiente comando:

bash
1python manage.py makemigrations

Este comando crea algunos archivos en Python que crear谩n o editar谩n nuestra base de datos para poder almacenar lo que tenemos en nuestros modelos. Deber铆as obtener una salida que se vea algo as铆 como la que se muestra a continuaci贸n, y si navegas a tu directorio de migraciones, notar谩s que se cre贸 un nuevo archivo para nosotros.

Ejemlo de migraciones

A continuaci贸n, para aplicar estas migraciones a nuestra base de datos, ejecutamos el siguiente comando:

bash
1python manage.py migrate

Ahora ver谩s que se han aplicado algunas migraciones predeterminadas junto con las nuestras, y tambi茅n notar谩s que ahora tenemos un archivo llamadodb.sqlite3en el directorio de nuestro proyecto.

Ejemlo de migraciones

Shell

Ahora, para empezar a trabajar a帽adiendo informaci贸n y manipulando esta base de datos, podemos ingresar a la consola de Django, donde podemos ejecutar comandos de Python dentro de nuestro proyecto.

shell
1python manage.py shell
2Python 3.7.2 (default, Dec 29 2018, 00:00:04)
3Type 'copyright', 'credits' or 'license' for more information
4IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.
python
1# Importar nuestro modelo de vuelo
2En [1]: from flights.models import Flight
3
4# Crear un nuevo vuelo
5En [2]: f = Flight(origin="Nueva York", destination="Londres", duration=415)
6
7# Insertar ese vuelo en nuestra base de datos
8En [3]: f.save()
9
10# Consultar todos los vuelos almacenados en la base de datos
11En [4]: Flight.objects.all()
12Out[4]: <QuerySet [<Flight: Flight object (1)>]>

Cuando consultamos nuestra base de datos, vemos que obtenemos solo un vuelo llamadoFlight object (1). Este nombre no es muy informativo, pero podemos solucionarlo. Dentro de models.py, definiremos una funci贸n__str__que proporciona instrucciones sobre c贸mo convertir un objeto Flight en una cadena:

python
1class Flight(models.Model):
2origin = models.CharField(max_length=64)
3destination = models.CharField(max_length=64)
4duration = models.IntegerField()
5
6def __str__(self):
7 return f"{self.id}: {self.origin} to {self.destination}"

Ahora, cuando regresamos a la consola, nuestra salida es un poco m谩s legible:

python
1# Crear una variable llamada 'flights' para almacenar los resultados de una consulta
2En [7]: flights = Flight.objects.all()
3
4# Mostrar todos los vuelos
5En [8]: flights
6Out[8]: <QuerySet [<Flight: 1: Nueva York a Londres>]>
7
8# Aislar solo el primer vuelo
9En [9]: flight = flights.first()
10
11# Imprimir informaci贸n del vuelo
12En [10]: flight
13Out[10]: <Flight: 1: Nueva York a Londres>
14
15# Mostrar el ID del vuelo
16En [11]: flight.id
17Out[11]: 1
18
19# Mostrar el origen del vuelo
20En [12]: flight.origin
21Out[12]: 'Nueva York'
22
23# Mostrar el destino del vuelo
24En [13]: flight.destination
25Out[13]: 'Londres'
26
27# Mostrar la duraci贸n del vuelo
28En [14]: flight.duration
29Out[14]: 415

Este es un buen comienzo, pero pensando en lo mencionado anteriormente, no queremos tener que almacenar el nombre de la ciudad como origen y destino para cada vuelo, as铆 que probablemente queremos otro modelo para un aeropuerto que est茅 de alguna manera relacionado con el modelo de vuelo:

python
1class Airport(models.Model):
2code = models.CharField(max_length=3)
3city = models.CharField(max_length=64)
4
5def __str__(self):
6 return f"{self.city} ({self.code})"
7
8class Flight(models.Model):
9origin = models.ForeignKey(Airport, on_delete=models.CASCADE, related_name="departures")
10destination = models.ForeignKey(Airport, on_delete=models.CASCADE, related_name="arrivals")
11duration = models.IntegerField()
12
13def __str__(self):
14 return f"{self.id}: {self.origin} to {self.destination}"

Hemos visto todo en nuestra nueva claseAirportantes, pero los cambios en los campos origen y destino dentro de la claseFlightson nuevos para nosotros:

  • Especificamos que los campos origen y destino sonForeignKey, lo que significa que se refieren a otro objeto.
  • Al ingresarAirportcomo nuestro primer argumento, estamos especificando el tipo de objeto al que se refiere este campo.
  • El siguiente argumento,on_delete=models.CASCADE, proporciona instrucciones sobre qu茅 debe suceder si se elimina un aeropuerto. En este caso, especificamos que cuando se elimine un aeropuerto, todos los vuelos asociados a 茅l tambi茅n deben ser eliminados. Hayotras varias opcionesadem谩s deCASCADE.
  • Proporcionamos un nombre relacionado, que nos brinda una forma de buscar todos los vuelos con un aeropuerto dado como origen o destino.
  • Cada vez que realizamos cambios enmodels.py, debemos realizar migraciones y luego migrar. Ten en cuenta que es posible que debas eliminar tu vuelo existente de Nueva York a Londres, ya que no encaja con la nueva estructura de la base de datos.
bash
1# Crear nuevas Migraciones
2python manage.py makemigration
3
4# Migrar
5python manage.py migrate

Ahora vamos a probar estos nuevos modelos en la consola de Django

python
1# Importar todos los modelos
2In [1]: from flights.models import *
3
4# Crear algunos aeropuertos nuevos
5In [2]: jfk = Airport(code="JFK", city="New York")
6In [4]: lhr = Airport(code="LHR", city="London")
7In [6]: cdg = Airport(code="CDG", city="Paris")
8In [9]: nrt = Airport(code="NRT", city="Tokyo")
9
10# Guardar los aeropuertos en la base de datos
11In [3]: jfk.save()
12In [5]: lhr.save()
13In [8]: cdg.save()
14In [10]: nrt.save()
15
16# A帽adir un vuelo y guardarlo en la base de datos
17f = Flight(origin=jfk, destination=lhr, duration=414)
18f.save()
19
20# Mostar alguna informaci贸n sobre vuelos
21In [14]: f
22Out[14]: <Flight: 1: New York (JFK) to London (LHR)>
23In [15]: f.origin
24Out[15]: <Airport: New York (JFK)>
25
26# Utilizando el nombre relacionado para consultar por el aeropuerto de llegada:
27In [17]: lhr.arrivals.all()
28Out[17]: <QuerySet [<Flight: 1: New York (JFK) to London (LHR)>]>

Comenzando nuestra aplicaci贸n

Ahora podemos comenzar a construir una aplicaci贸n en torno a este proceso de utilizar modelos para interactuar con una base de datos. Comencemos creando una ruta de 铆ndice para nuestra aerol铆nea. Dentro deurls.py:

python
1urlpatterns = [
2 path('', views.index, name="index"),
3]

Dentro deviews.py

python
1from django.shortcuts import render
2from .models import Flight, Airport
3
4# Crea tus vistas aqu铆.
5
6def index(request):
7 return render(request, "flights/index.html", {
8 "flights": Flight.objects.all()
9})

Dentro de nuestro nuevolayout.html

html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Flights</title>
5 </head>
6 <body>
7 {% block body %}
8 {% endblock %}
9 </body>
10</html>

Dentro de nuestro nuevoindex.html

html
1{% extends "flights/layout.html" %}
2
3{% block body %}
4 <h1>Flights:</h1>
5 <ul>
6 {% for flight in flights %}
7 <li>Flight {{ flight.id }}: {{ flight.origin }} to {{ flight.destination }}</li>
8 {% endfor %}
9 </ul>
10{% endblock %}

Lo que hemos hecho aqu铆 es crear una p谩gina predeterminada donde tenemos una lista de todos los vuelos que hemos creado hasta ahora. Cuando abrimos la p谩gina ahora, se ve as铆:

Ejemlo de migraciones

Ahora, agreguemos algunos vuelos m谩s a nuestra aplicaci贸n volviendo a la consola de Django:

python
1# Utilizando el comando filter para encontrar todos los aeropuertos con base en Nueva York
2En [3]: Airport.objects.filter(city="Nueva York")
3Out[3]: <QuerySet [<Airport: Nueva York (JFK)>]>
4
5# Utilizando el comando get para obtener solo un aeropuerto en Nueva York
6En [5]: Airport.objects.get(city="Nueva York")
7Out[5]: <Airport: Nueva York (JFK)>
8
9# Asignando algunos aeropuertos a nombres de variables:
10En [6]: jfk = Airport.objects.get(city="Nueva York")
11En [7]: cdg = Airport.objects.get(city="Par铆s")
12
13# Creando y guardando un nuevo vuelo:
14En [8]: f = Flight(origin=jfk, destination=cdg, duration=435)
15En [9]: f.save()

Ahora cuando visitemos de nuevo nuestro sitio veremos lo siguiente:

Ejemlo de migraciones

Django Admin

Dado que es tan com煤n que los desarrolladores tengan que crear nuevos objetos como lo hemos estado haciendo en la consola, Django viene con una interfaz de administraci贸n predeterminada que nos permite hacer esto de manera m谩s f谩cil. Para comenzar a usar esta herramienta, primero debemos crear un usuario administrativo:

bash
1python manage.py createsuperuser

Luego, sigue las instrucciones para ingresar un nombre de usuario, una direcci贸n de correo electr贸nico y una contrase帽a para el superusuario.

Ahora, debemos agregar nuestros modelos a la aplicaci贸n de administraci贸n al ingresar al archivoadmin.pydentro de nuestra aplicaci贸n, e importar y registrar nuestros modelos. Esto le dice a Django a qu茅 modelos queremos tener acceso en la aplicaci贸n de administraci贸n.

python
1from django.contrib import admin
2from .models import Flight, Airport
3
4# Registra tus modelos aqu铆.
5admin.site.register(Flight)
6admin.site.register(Airport)

Ahora, cuando visitamos nuestro sitio y agregamos/admina la URL, podemos iniciar sesi贸n en una p谩gina que se ve as铆:

Ejemlo de migraciones

Despu茅s de iniciar sesi贸n, ser谩s llevado a una p谩gina como la que se muestra a continuaci贸n, donde puedes crear, editar y eliminar objetos almacenados en la base de datos.

Ejemlo de migraciones

Ahora, agreguemos algunas p谩ginas m谩s a nuestro sitio. Comenzaremos por agregar la capacidad de hacer clic en un vuelo para obtener m谩s informaci贸n al respecto. Para hacer esto, creemos una ruta de URL que incluya el id de un vuelo:

python
1path("<int:flight_id>", views.flight, name="flight")

Luego, enviews.py, crearemos una funci贸nflightque tome un id de vuelo y renderice una nueva p谩gina HTML:

python
1def flight(request, flight_id):
2flight = Flight.objects.get(id=flight_id)
3return render(request, "flights/flight.html", {
4 "flight": flight
5})

Ahora crearemos una plantilla para mostrar esta informaci贸n del vuelo con un enlace de regreso a la p谩gina de inicio:

html
1{% extends "flights/layout.html" %}
2
3{% block body %}
4 <h1>Flight {{ flight.id }}</h1>
5 <ul>
6 <li>Origin: {{ flight.origin }}</li>
7 <li>Destination: {{ flight.destination }}</li>
8 <li>Duration: {{ flight.duration }} minutes</li>
9 </ul>
10 <a href="{% url 'index' %}">All Flights</a>
11{% endblock %}

Finalmente, necesitamos agregar la capacidad de enlazar de una p谩gina a otra, as铆 que modificaremos nuestra p谩gina de 铆ndice para incluir enlaces:

python
1{% extends "flights/layout.html" %}
2{% block body %}
3<h1>Flights:</h1>
4<ul>
5 {% for flight in flights %}
6 <li><a href="{% url 'flight' flight.id %}">Flight {{ flight.id }}</a>
7 : {{ flight.origin }} to {{ flight.destination }}</li>
8 {% endfor %}
9</ul>
10{% endblock %}

Nuestra p谩gina inicial se ver谩 as铆:

Ejemlo de migraciones

Y cuando hacemos clic en el vuelo 5, por ejemplo, nos lleva a esta p谩gina.

Ejemlo de migraciones

Muchas m谩s relaciones

Ahora, trabajemos en integrar a los pasajeros en nuestros modelos. Crearemos un modelo de pasajero para empezar:

python
1class Passenger(models.Model):
2first = models.CharField(max_length=64)
3last = models.CharField(max_length=64)
4flights = models.ManyToManyField(Flight, blank=True, related_name="passengers")
5
6def __str__(self):
7 return f"{self.first} {self.last}"

Como discutimos, los pasajeros tienen una relaci贸n de muchos a muchos con los vuelos, que describimos en Django utilizando el campo ManyToManyField. El primer argumento en este campo es la clase de objetos a la que est谩 relacionado. Hemos proporcionado el argumentoblank=True, lo que significa que un pasajero puede no tener vuelos. Tambi茅n hemos agregado unrelated_nameque cumple el mismo prop贸sito que antes: nos permitir谩 encontrar a todos los pasajeros en un vuelo dado.

Para realizar estos cambios, debemos realizar migraciones y migrar. Luego podemos registrar el modelo de Pasajero enadmin.pyy visitar la p谩gina de administraci贸n para crear algunos pasajeros.

Ahora que hemos agregado algunos pasajeros, actualicemos nuestra p谩gina de vuelos para que muestre todos los pasajeros en un vuelo. Primero visitaremosviews.pyy actualizaremos nuestra vista de vuelo para proporcionar una lista de pasajeros como contexto. Accedemos a la lista utilizando elrelated_nameque definimos anteriormente.

python
1def flight(request, flight_id):
2flight = Flight.objects.get(id=flight_id)
3passengers = flight.passengers.all()
4return render(request, "flights/flight.html", {
5 "flight": flight,
6 "passengers": passengers
7})

Ahora agregamos una lista de pasajeros aflight.html:

html
1<h2>Passengers:</h2>
2<ul>
3 {% for passenger in passengers %}
4 <li>{{ passenger }}</li>
5 {% empty %}
6 <li>No Passengers.</li>
7 {% endfor %}
8</ul>
Compartir

Todos los nombres de productos, logos y marcas son propiedad de sus respectivos creadores.