domingo, 8 de enero de 2023

7.- Django - Bases de datos. PostgreSQL en local.

  ¿Te pierdes entre tantos temas? 📚✨ Descubre todo lo que ofrece este blog en un solo vistazo [👉 Ver índice completo]

Como ya comentamos Django utiliza por defecto Sqlite3 como gestor de la base de datos. Eso está muy bien para proyectos personales o de poco tamaño. Sin embargo para proyectos más profesionales que necesiten más rendimiento podemos usar otro tipo, como por ejemplo PostgreSQL. 

Para poder trabajar con PostgreSQL vamos a necesitar una interfaz que nos permita gestionar este gestor de base de datos. 

Nos vamos a la página oficial, a la página de descargas y seleccionamos la versión, que sea acorde con nuestro sistema operativo. Yo estoy trabajando con una versión Ubuntu de Linux, así que seguiré los pasos que dicen en la página para instalarlo. Lógicamente tu usa las instrucciones que sean acordes a tu sistema operativo.

# Instalamos la aplicación.
$ sudo apt-get install postgresql postgresql-contrib

# Entramos en la consola de postgresql
$ sudo su - postgres
postgres@lenovo:~$ psql
psql (14.5 (Ubuntu 14.5-0ubuntu0.22.04.1))
Type "help" for help.

# Creamos un usuario y su contraseña. 
# En el ejemplo usuario = "chemaHG" y contraseña="%gH345bPQ"
postgres=# create user chemahg with password '%gH345bPQ';
CREATE ROLE

# Damos permisos de master al usuario recien creado.
postgres=# alter user chemahg with superuser;
ALTER ROLE

# Creamos una base de datos de ejemplo. Se pude poner el nombre que se quiera.
# En el ejemplo tutorial_db
postgres=# create database tutorial_db owner chemahg;
CREATE DATABASE

# Para listar las bases de datos que tenemos hasta el momento:
postgres=# \l
postgres=# \list

# Para salir del terminal de postgresql y volver al terminal.
postgres-# \q
postgres@lenovo:~$ exit
logout

Para manejar las bases de datos de PostgreSql de forma visual podemos instalar el gestor pgAdmin.

En la pagina de pgAdmin podemos ver las formas de instalación para las diferentes plataformas.

En Ubuntu es seguir las instrucciones que nos ponen en la página de descargas, que son bastante largas.

# Si no tienes el programa curl para linux instalarlo.
$ sudo apt-get install curl

# Instalar la clave pública para el repositorio (si no lo hicimos 
previamente()
curl -fsS https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo gpg --dearmor -o /usr/share/keyrings/packages-pgadmin-org.gpg

# Crear el archivo de configuración del repositorio.
sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/packages-pgadmin-org.gpg] https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmin4.list && apt update'

# Ahora hay que elegir entre uno de los tres modos de instalación. Yo voy a
instalar la versión para escritorio. 

# Install for both desktop and web modes:
sudo apt install pgadmin4

# Install for desktop mode only:
sudo apt install pgadmin4-desktop

# Install for web mode only: 
sudo apt install pgadmin4-web
Si instalas la versión web hay que configurar el servidor con:
sudo /usr/pgadmin4/bin/setup-web.sh
Ahora hay que cambiar algunas cosas en la configuración. Buscamos la aplicación y la lanzamos (es un icono con un elefante azul). Nada más comenzar el programa nos pedirá que instalemos una contraseña maestra. Como el usuario que creamos antes está configurado como master podemos poner la misma.

Luego le damos a crear un nuevo servidor.

En la primera pantalla donde pone "name", ponemos el nombre que queramos para este servidor. Yo le pondré "mibase"



En el apartado "Connection"



Donde pone Host name/addres ya que estamos trabajando en local ponemos "localhost" y rellenamos también los campos Username y Password con la contraseña del usuario que creamos previamente.

Le damos a guardar y ya deberíamos tener funcionando la base de datos. Podemos ver la que creamos previamente llamada tutorial-db y desde aquí podemos crear la base de datos usando lenguaje SQL o también podemos usar el entorno contextual para manejar la base.




Ahora viene lo importante. Tenemos que conectar esta base que hemos creado "tutorial_db" con DJANGO. Para poder hacer esto necesitamos tener instalada una librería que por defecto no esta instalada en DJANGO que es psycogp

Lo instalamos con:

$ pip3 install psycopg2-binary
Collecting psycopg2-binary
  Downloading psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.0 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.0/3.0 MB 6.2 MB/s eta 0:00:00
Installing collected packages: psycopg2-binary
Successfully installed psycopg2-binary-2.9.5
Luego tenemos que ir a Django y dentro del directorio de la carpeta de trabajo buscamos el archivo settings.py (en mi caso dentro de la carpeta tiendaVirtual) y buscamos la clave que gestiona la conexión con la base de datos. Por defecto tenemos la de sqlite, que debería ser algo así:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

y tenemos que sustituirla por esta:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'tutorial_db',
        'USER': 'chemahg',
        'PASSWORD': '%gH345bPQ',
        'HOST': '127.0.0.1',
        'DATABASE_PORT': '5432',
    }
}

En donde:

'ENGINE' es el motor de conexión de la base de datos que puedes usar el que aparece en el ejemplo de arriba y si no te funciona usar este otro:

'django.db.backends.postgresql_psycopg2'

'NAME' es el nombre que le hemos puesto a la base de datos que hemos creado al principio.

'USER' es el nombre del usuario que creamos para gestionar la base de datos.

'PASSWORD' es la contraseña de ese usuario.

'HOST' es la dirección de la base de datos local, se pude poner '127.0.0.1' o 'localhost'

'DATABASE_PORT' es el puerto por defecto que usa la aplicación (5432)

Una vez realizado lo anterior tenemos que hacer los comandos que siempre hemos de realizar cuando modifiquemos la estructura o tipo de la base de datos.

(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py makemigrations
No changes detected
# No se detectan cambios porque no se ha modificado la estructura de la base de datos.
(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, gestionPedidos, 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 gestionPedidos.0001_initial... OK
  Applying sessions.0001_initial... OK

Si todo esta ok ya están las tablas creadas en la base de datos. Podemos ir al gestor de escritorio de pgadmin y entrando en la base de datos /databases/tutorial_db/Schemas/tables/ y ahí estarán las tablas y campos que hemos creado.

Vamos a introducir un registro en la tabla de clientes. Estando en consola abrimos el Shell de Django. Lo primero que tenemos que hacer es importar el modelo y como voy a introducir clientes, le decimos que importe clientes. Una vez importado, creamos una variable con el nombre que queramos. Luego propiedad igual a valor para introducir los datos del cliente. Después utilizamos la variable con el método .save() para ejecutar la instrucción.

(miEntorno) ~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from gestionPedidos.models import Clientes
>>> cli1 = Clientes(nombre="Pepe", direccion="Calle Avellana", telefono=684000000, email="turron@mailinator.com")
>>> cli1.save()
y si vas al gestor de escritorio de PostgreSQL  ya debería haberse creado el registro:


nuevo registro visto a traves del administrador de escritorio de postgresql




Realizar consultas de tipo SELECT con DJANGO usando criterios.


Lo que vamos a ver es válido tanto para postgreSQL como para cualquier otro gestor de base de datos. Necesitamos incluir algunos datos para poder realizar las búsquedas. Lo haremos como hemos visto anteriormente o a través del gestor del escritorio. Voy a incluir en la base de datos unos cuantos artículos. 

Después vamos a ver como buscar registros basándonos en un campo en concreto. Como los artículos que tengo almacenados son videojuegos, voy a buscar los elementos que pertenezcan al elemento "Acción" dentro del campo "categoría".

En lenguaje SQL si usamos pgAdmin4 la instrucción sería:

SELECT *
FROM public."gestionPedidos_articulos"
WHERE categoria='Acción'

SALIDA:

1	"the last of us II"	        "Acción"	10
4	"Read Dead Redeption II"	"Acción"	19
Trasladando esto a código de DJANGO

(miEntorno) ~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from gestionPedidos.models import Articulos
>>> Articulos.objects.filter(categoria="Acción")
<QuerySet [<Articulos: Articulos object (1)>, <Articulos: Articulos object (4)>]>
La clave está en el método filter (equivale a WHERE en SQL) y dentro ponemos el criterio o criterios separados por coma. Al pulsar enter nos da el resultado que quizás no es el que nosotros esperamos. Nos devuelve QuerySet con una lista en la que hay dos objetos que se corresponden con lo que le hemos pedido. El que tiene el id 1 y el que tiene el id 4. Pero nosotros seguramente esperábamos que apareciera otro tipo de información, como el nombre del juego, su categoría y su precio. 

Para poder hacer esto tenemos que decirle a nuestro código python que trasforme la respuesta a una cadena de caracteres y eso lo hacemos con la función __str__().

Lo vamos a introducir en el archivo models.py dentro de la categoría "Articulos"

gestionPedidos/models.py

...
class Articulos(models.Model):
    nombre = models.CharField(max_length=30)
    categoria = models.CharField(max_length=20)
    precio = models.IntegerField()

    class Meta:
        ordering = ["nombre"]

    def __str__(self):
        return f"id {self.id}, nombre: {self.nombre}, categoría: {self.categoria}, precio: {self.precio}"
...

Hecho esto no valdría con volver a repetir la instrucción. SIEMPRE que modifiquemos algo en models.py hay que volver a realizar las migraciones pertinentes.

(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py makemigrations
No changes detected
(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, gestionPedidos, sessions
Running migrations:
  No migrations to apply.
(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> Articulos.objects.filter(categoria="Acción")
Traceback (most recent call last):
  File "<console>", line 1, in <module>
NameError: name 'Articulos' is not defined
>>> from gestionPedidos.models import Articulos
>>> Articulos.objects.filter(categoria="Acción")
<QuerySet [
<Articulos: id 1, nombre: the last of us II, categoría: Acción, precio: 10>, 
<Articulos: id 4, nombre: Read Dead Redeption II, categoría: Acción, precio: 19>
]>

Ahora si podemos leer sus valores a través de la lista que se nos proporciona.


Como utilizar más de un criterio.


Por ejemplo localizar todos los registros cuya categoría sea "Acción" y que tengan un precio superior a 15 €. En lenguaje SQL sería algo parecido a:

SELECT *
FROM public."gestionPedidos_articulos"
WHERE categoria='Acción' AND precio>15

En principio estaríamos tentados de escribir este código para obtener la consulta:

chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ source ../miEntorno/bin/activate
(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from gestionPedidos.models import Articulos
>>> Articulos.objects.filter(categoria="Acción", precio>15)
  File "<console>", line 1
    Articulos.objects.filter(categoria="Acción", precio>15)
                                                          ^
SyntaxError: positional argument follows keyword argument
Pero esto nos provoca un error porque el símbolo > o < están siendo utilizados constantemente por el Shell de Python (no nos pasará esto en otros entornos más adelante) con lo que tenemos que sustituirlos por otros:

> nombreCampo__gte
< nombreCampo__lte

Para mayor que:

>>> Articulos.objects.filter(categoria="Acción", precio__gte=15)
<QuerySet [<Articulos: id 4, nombre: Read Dead Redeption II, 
categoría: Acción, precio: 19>]>
Para menor que:

>>> Articulos.objects.filter(categoria="Acción", precio__lte=15)
<QuerySet [<Articulos: id 1, nombre: the last of us II, categoría: Acción, precio: 10>]>
Para horquillas de valores podemos combinar los comandos anteriores. Por ejemplo un juego de acción mayor de 5 euros y menor de 40:

>>> Articulos.objects.filter(categoria="Acción", precio__gte=5, precio__lte=40)
<QuerySet [<Articulos: id 1, nombre: the last of us II, categoría: Acción, precio: 10>, 
<Articulos: id 4, nombre: Read Dead Redeption II, categoría: Acción, precio: 19>]>
o también utilizar el comando __range()

>>> Articulos.objects.filter(categoria="Acción", precio__range=(5,40))
<QuerySet [<Articulos: id 1, nombre: the last of us II, categoría: Acción, precio: 10>, 
<Articulos: id 4, nombre: Read Dead Redeption II, categoría: Acción, precio: 19>]>


Existen muchos más referencias para campos de búsqueda que usan los dos guiones bajos __ que puedes encontrar en la documentación de Django sobre como realizar consultas o también una lista completa en aquí o en la documentación de Django en QuerySet Api reference


Artículos ordenados por un campo. (ORDER BY en SQL)


 Por ejemplo todos los juegos que tengan un precio superior a 25 €.

>>> Articulos.objects.filter(precio__gte=25).order_by('precio')
<QuerySet [<Articulos: id 5, nombre: FIFA23, categoría: Deporte, precio: 40>, 
<Articulos: id 2, nombre: Village, categoría: terror, precio: 49>]>
Nos devuelve todos los registros que cumplen ese criterio pero ordenados de menor a mayor precio.

Si el criterio para ordenar es un campo de texto, entonces lo ordena alfabéticamente.

Si lo queremos ordenar por precio descendente. Delante del campo incluimos un signo menos de esta forma:

>>> Articulos.objects.filter(precio__gte=25).order_by('-precio')
<QuerySet [<Articulos: id 2, nombre: Village, categoría: terror, precio: 49>, 
<Articulos: id 5, nombre: FIFA23, categoría: Deporte, precio: 40>]>

lunes, 2 de enero de 2023

6.- Django - Bases de Datos.

  ¿Te pierdes entre tantos temas? 📚✨ Descubre todo lo que ofrece este blog en un solo vistazo [👉 Ver índice completo]

Se supone que ya sabes que es una base de datos, tablas, registros etc 

Para ver este tema vamos a simular que creamos una tienda virtual. Empezaremos un nuevo proyecto independiente del que ya teníamos. Tecleamos:

$ django-admin startproject tiendaVirtual

Entramos dentro de la carpeta del nuevo proyecto creado:

$ cd tiendaVirtual

En este tema vamos a trabajar con varias aplicaciones que aunque están relacionadas van a hacer cosas distintas. Empezaremos creando una aplicación a la que llamaremos "gestiónPedidos". Django pude trabajar con diferentes aplicaciones independientes. Podríamos tener una aplicación para "Gestión de Almacén", otra para "Cobros y Pagos", otra para "Clientes" y así sucesivamente. 

En los capítulos anteriores no utilizamos Aplicaciones para nada, pero en la mayoría de los proyectos se requiere modularización  y usar a la vez diferentes aplicaciones.

Creamos la nueva aplicación:

tiendaVirtual$ python manage.py startapp gestionPedidos

Esto ha creado un nuevo directorio en nuestro proyecto:

Nueva aplicación creada

Si entramos en el, podemos ver los siguientes archivos:

archivos creados con la aplicacion


Una vez que hemos creado la primera aplicación, vamos a ver como crear las bases de datos. La clave está en el archivo models.py. Se trata de crear dentro de este archivo una clase por cada tabla que necesites que tenga tu base de datos. Cada clase hereda de models.Model. Lo bueno será que no tenemos que usar ni una sola instrucción de SQL todo esto lo hace por detrás DJANGO. 


models.py: Creación de tablas y campos.

from django.db import models

# Create your models here.

# Las tablas se crean mediante clases.

class Clientes(models.Model):
    # Dentro de la clase se crean los diferentes campos.
    nombre = models.CharField(max_length=30)
    direccion = models.CharField(max_length=50)
    # Email ya valido ya esta creado
    email = models.EmailField()
    telefono = models.CharField(max_length=9)

class Articulos(models.Model):
    nombre = models.CharField(max_length=30)
    categoria = models.CharField(max_length=20)
    precio = models.IntegerField()

class Pedidos(models.Model):
    numero = models.IntegerField
    fecha = models.DateField()
    entregado = models.BooleanField()
Hemos creado tres tablas Clientes, Artículos y Pedidos. 

En la tabla Clientes cada registro tendrá tres campos:
- nombre (campo de texto)
- dirección (campo de texto)
- email (es un tipo de campo ya preparado para aceptar emails válidos)
- teléfono (campo de texto)

En la tabla Artículos registraremos:
- nombre (campo de texto)
- categoría (campo de texto)
- precio (campo numérico)

En la tabla Pedidos registraremos:
- número de pedido (campo numérico)
- fecha (campo de fecha)
- si esta entregado o no (campo boleano)

 En el siguiente enlace puedes ver los diferentes tipos de campos que existen:


METADATOS.

Los metadatos en una tabla es "cualquier cosa que no es un campo o columna", cosas como la forma de ordenación de los registros (ordering), nombres de las tablas en la base de datos (db_table), o nombres que los humanos podamos entender tanto en singular como plural para los campos (verbose_name y verbose_name_plural).

Puedes encontrar una lista de las posibles opciones para la clase Meta en "model option reference" de la documentación de Django.

Vamos a ver algunos ejemplos.

Si cuando hagamos una consulta (query) a la base de datos queremos que los datos se muestren ordenados en base a un determinado campo usaremos la opción "ordering" de la clase Meta. Por ejemplo, si cuando consultemos la tabla "Clientes" queremos que estos se ordenen alfabéticamente podemos usar la clase Meta dentro de la clase Clientes de la siguiente forma:

models.py: Creación de tablas y campos.

from django.db import models

# Create your models here.

# Las tablas se crean mediante clases.

class Clientes(models.Model):
    # Dentro de la clase se crean los diferentes campos.
    nombre = models.CharField(max_length=30)
    direccion = models.CharField(max_length=50)
    # Email ya valido ya esta creado
    email = models.EmailField()
    telefono = models.CharField(max_length=9)
    
    class Meta:
        ordering = ["nombre"]

...
Si en vez de que aparezcan ordenados en orden ascendentes queremos que aparezcan en orden descendente usaremos un menos delante del nombre del campo. 

ordering = ["-nombre"]

También utilizando metadatos podemos crear un índice para la base de datos. Como realizaremos muchas consultas a la base de datos, consultando el número del pedido, vamos a indexar la tabla "Pedidos" en función de este campo. Esto mejorará el rendimiento de las consultas que realicemos ordenando o filtrando los datos por este campo.

models.py: Creación de tablas y campos.

from django.db import models

# Create your models here.

# Las tablas se crean mediante clases.

...

class Pedidos(models.Model):
    numero = models.IntegerField
    fecha = models.DateField()
    entregado = models.BooleanField()

    class Meta:
        indexes = [models.Index(fields=["numero"]),]



Ya tenemos la primera aplicación y modelo (3 tablas) y unos metadatos. El siguiente paso es decirle a nuestro proyecto de DJANGO que hay una aplicación nueva, porque hasta ahora no lo sabe. Hay que regístralo en "settings.py" dentro de la carpeta "tiendaVirtual". 

Ahí hay una sección llamada "INSTALLED_APPS"
que por defecto tiene todas las aplicaciones de Django existentes por el solo hecho de crearlo.  Ahí añadiremos el nombre de la aplicación:

tiendaVirtual/settings.py: Registro de aplicaciones.

...
ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'gestionPedidos',
]

MIDDLEWARE = [
...
Guardamos y a modo de prueba podemos teclear el siguiente comando para ver si todo esta bien. La siguiente salida es que el proyecto esta correctamente registrado:

/DJANGO/tiendaVirtual$ python manage.py check gestionPedidos
System check identified no issues (0 silenced).
Para crear la base de datos, con las tres tablas:

python manage.py makemigrations nombre_aplicación
Realizará la migraciones solo de esta aplicación o también puedes usarla sin el nombre de la aplicación con lo que comprobará todas las aplicaciones y realizará las migraciones en todas las tablas del proyecto.


/tiendaVirtual$ python manage.py makemigrations gestionPedidos
Migrations for 'gestionPedidos':
  gestionPedidos/migrations/0001_initial.py
    - Create model Articulos
    - Create model Clientes
    - Create model Pedidos
0001 - número de migración.

Pero ¡ojo! porque esto crea la base de datos y le dice a Django lo que debería tener la base de datos en su interior pero si miramos dentro con cualquiera de los programas que existen para ello, veremos que esta vacía.

Si tienes curiosidad de como se creará la tabla con instrucciones del código SQL puedes usar la siguiente instrucción aunque no es obligatoria, solo si tienes curiosidad de ver el código, que posteriormente se aplicará al realizar la migración.

/tiendaVirtual$ python manage.py sqlmigrate gestionPedidos 0001
BEGIN;
--
-- Create model Articulos
--
CREATE TABLE "gestionPedidos_articulos" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "nombre" varchar(30) NOT NULL, "categoria" varchar(20) NOT NULL, "precio" integer NOT NULL);
--
-- Create model Clientes
--
CREATE TABLE "gestionPedidos_clientes" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "nombre" varchar(30) NOT NULL, "direccion" varchar(50) NOT NULL, "email" varchar(254) NOT NULL, "telefono" varchar(9) NOT NULL);
--
-- Create model Pedidos
--
CREATE TABLE "gestionPedidos_pedidos" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "fecha" date NOT NULL, "entregado" bool NOT NULL);
COMMIT;

 Ahora, como último paso le decimos que las aplique, es decir ejecutamos la migración, esto si es obligatorio porque sino no se aplicarán los cambios:

/tiendaVirtual$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, gestionPedidos, 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 gestionPedidos.0001_initial... OK
  Applying sessions.0001_initial... OK
Si en el directorio del proyecto, buscas y abres el archivo db.sqlite3 (con DB Browser por ejemplo), verás que tiene 14 tablas. La mayor parte las utiliza Django para su uso pero también están las nuestras:

tablas creadas en el archivo de la base de datos


Si te fijas Django añade por defecto un campo id que se añade como clave primaria, en caso de que tu no lo hagas, que también podrías.


¿Cómo crear, actualizar y borrar registros de la base de datos?


Para comenzar vamos a hacerlo desde la consola, aunque el objetivo final será hacerlo desde formularios que creemos en la página web. Pero hasta que no veamos como hacer formularios en Django, tendremos que hacerlo desde la consola.

Para ello abriremos el "shell" de Django dentro de nuestra aplicación (tiendaVirtual) de la siguiente forma:

~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 

Lo primero que vamos a hacer es importar el modelo con el que vamos a trabajar. Por ejemplo vamos a crear un registro de la tabla Artículos. Comenzamos importando el modelo.


~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from gestionPedidos.models import Articulos
Vamos a insertar un primer Articulo dentro de la tabla artículos. Creamos una variable que la podemos llamar como queramos, yo la llamaré "juego" y luego asignaremos cada uno de los campos, teniendo en cuenta si la propiedad es de tipo texto, entero etc. Por ejemplo insertaremos en nuestra tienda virtual un primer videojuego:

"Doom", "Acción", 3 €

~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from gestionPedidos.models import Articulos
>>> juego=Articulos(nombre="Doom", categoria="Acción", precio= 3)
Para que esto tenga efecto y se cree el registro utilizamos el método .save() sobre la variable creada.

~/Cursos/DJANGO/tiendaVirtual$ python manage.py shell
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from gestionPedidos.models import Articulos
>>> juego=Articulos(nombre="Doom", categoria="Acción", precio= 3)
>>> juego.save()

y ya tenemos el registro creado.

imagen de registro creado en la base de datos


Para practicar vamos a insertar un segundo artículo.

>>> juego2=Articulos(nombre="Need for Speed", categoria="Velocidad", precio=50)
>>> juego2.save()
>>> 
Nuevo registro creado en la base de datos

Existe otra forma de insertar un registro con una sola instrucción que es usando el método "objects.create". 

El administrador predeterminado para cada modelo es objects. Este administrador recupera todos los objetos en la base de datos, aunque también podemos crear gestores de modelos personalizados (aunque no los vamos a ver en este curso).

Creemos un tercer articulo para verlo.

>>> juego3=Articulos.objects.create(nombre="Animal Crossing",categoria="Simulación", precio=45)
tercer articulo creado con una sola instrucción


Actualizar un registro.

Imaginemos que queremos modificar el precio del juego "Need for Speed" para subirlo a 60 Euros. Para realizar esta tarea, como use la variable "juego2" para crear el objeto, modificamos el precio de la siguiente forma:

>>> juego2.precio=60
>>> juego2.save()
>>>
y si vamos a la base de datos y actualizamos ya estaría modificado el precio.

Borrar registro.

Por ejemplo vamos a borrar el juego "Animal Crossing" que yo cree usando la variable "juego3". Podemos usar de nuevo esta variable u otra nueva (da igual el nombre de la variable) por ejemplo "borrar". Le especificamos el modelo (Articulos) y usamos objets.get para especificar el criterio. Podemos especificar el id, el nombre etc del registro. Yo usare el id del registro que en mi caso es el 3. Y una vez tenemos el registro seleccionado lo borramos usando el método "delete()"

>>> borrar = Articulos.objects.get(id=3)
>>> borrar.delete()
(1, {'gestionPedidos.Articulos': 1})
>>> 

resultado de borrar el registro numero 3


Para realizar una consulta que nos muestre todos los registros que tenemos almacenados en la tabla podemos usar le método .all()


>>> lista_articulos = Articulos.objects.all()
>>> lista_articulos
<QuerySet [<Articulos: Articulos object (1)>, <Articulos: Articulos object (2)>]>
>>> 
Si tienes curiosidad de ver la instrucción SQL que genera lo anterior solo tienes que ejecutar lo siguiente para verla:

>>> lista_articulos.query.__str__()
'SELECT "gestionPedidos_articulos"."id", "gestionPedidos_articulos"."nombre", "gestionPedidos_articulos"."categoria", "gestionPedidos_articulos"."precio" FROM "gestionPedidos_articulos"'
>>> 

Próximo Post - 7. Bases de Datos. Postgresql en local.

jueves, 22 de diciembre de 2022

5.- Django - Plantillas Incrustadas y Herencia de Plantillas.

  ¿Te pierdes entre tantos temas? 📚✨ Descubre todo lo que ofrece este blog en un solo vistazo [👉 Ver índice completo]

Cuando se construye un sitio web, normalmente se suele hacer una plantilla base, que tendrá la estructura básica de la plantilla, es decir, recogerá todo lo que quieres que tengan en común todas tus plantillas. A la plantilla base se le suele llamar base.html. Esto es muy útil, ya que nos va a ahorrar mucho tiempo y escritura de código. 


Por ejemplo, en nuestro sitio web todas las páginas tendrán la misma cabecera, barra de navegación y pie de página que será común a todas, solo variará el contenido de la parte principal. Si no existiera la herencia tendríamos que escribir el código html de los elementos comunes en cada una de las páginas, pero con esto, lo que haremos será escribir el código de ellos en una plantilla base o padre y usaremos ese código en todas las páginas hijas que hagamos.


Plantillas Incrustadas.


Antes de afrontar el tema de la herencia de plantillas vamos a parar un momento para ver un tema relacionado, como es el de incrustar código html que este en otro archivo:

{% include "nombre_archivo_html" %}

donde "nombre_archivo_html" contendrá el código a insertar.

¿Por qué hacer esto? 

La respuesta es que si tenemos elementos individuales, como puede ser la barra de navegación, cuyo código está en un archivo a parte del principal, si algún día tenemos que modificarlos (por ejemplo añadir más páginas a la barra de navegación) es más fácil de hacer en un código breve que tenemos controlado, que no buscar dentro de un archivo, que en nuestra caso no, porque nuestra plantilla base es muuuuy sencilla, pero que en una página profesional podría tener miles de líneas de código.

Sigamos trabajando y de paso repasamos algunos conceptos ya vistos.

Empezamos creando la plantila "base.html" dentro del directorio de "plantillas" que ya tenemos creado. Su contenido podría ser algo similar a esto:


plantillas/base.html: Plantilla base desde que se generarán las demás.

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Barra de Navegación</title>
    
</head>
<body>
    <header>
        <h1>Tutorial de Django</h1>
        <!-- Aqui ira la barra de navegación cargada desde una plantilla
        externa, es decir desde otro documento html-->
        {% include "barra_navegacion.html" %}
        <hr/>
    </header>
    <section>
        Aquí ira la parte cambiante de las páginas.
    </section>
    <footer>
        <hr/>
        <small>Aquí ira el pie de página.</small>
    </footer>
    
</body>
</html>

Como ves en el ejemplo superior se ha insertado una barra de navegación, que estará en el archivo "barra_navegación.html" dentro de una plantilla genérica.


Una vez creada la plantilla base, crearemos la barra de navegación dentro del mismo directorio "barra_navegacion.html".

    En un proyecto más extenso, para que este todo ordenado, seguramente querrás tener subcarpetas con los diferentes elementos a insertar en la plantilla base. Por ejemplo esta barra de navegación que vamos a crear podría estar en una subcarpeta llamada "superior" donde estarían todos los elementos que ocupan la parte superior de la página web. Solo tenemos que tener en cuenta que al cargarla tendremos que especificar la ruta:

{% include "superior/barra_navegacion.html" %}

Pero bueno, en nuestro ejemplo, la crearé en la misma carpeta principal llamada "plantillas".

Su estructura será:

plantillas/barra_navegacion.html: barra de navegación que se insertará en las plantillas.

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #barra {
            margin: 0;
            padding: 0;
            list-style: none;
            text-transform: uppercase;
        }
        #barra li{
            display: inline;
            margin: 0 23px;
        }
    </style>
</head>
<body>
    <nav>
        <ul id="barra">
            <li><a href="#">Inicio</a></li>
            <li><a href="#">Página Uno</a></li>
            <li><a href="#">Sobre Nosotros</a></li>
        </ul>
    </nav>
</body>
</html>

El siguiente paso es crear la vista principal usando la plantilla "base.html", dentro del archivo views.py. Es muy sencillo utilizando el método que vimos en el último punto del capitulo anterior:


views.py.

...
from django.shortcuts import render

# Vistas

# Vista principal a la que se accederá si no ponemos nada.
def principal(request):
    return render(request, "base.html")


def saludo(request):
....
y ya solo nos queda registrar la vista en el archivo urls.py. Esta vista no tendrá un nombre dentro de la misma para que sea lo primero que nos salga al cargar la aplicación. Como siempre que registremos una vista, importamos la misma desde el archivo views.py y luego la registramos:

views.py.

...
from Proyecto1.views import saludo, fecha_actual, suma_numeros, principal

urlpatterns = [
    path('admin/', admin.site.urls),
    path('saludo/', saludo),
    path("time_server/", fecha_actual),
    path("suma_numeros/<int:numero_1>/<int:numero_2>/", suma_numeros),
    path("", principal),
]
...

Si ejecutamos el servidor de Django (python manage.py runserver) este debería ser el resultado:


plantilla insertada en base.html

Herencia de Plantillas.

La plantilla base.html va a ser nuestra plantilla padre. La vamos a utilizar para construir tres plantillas hijas, que heredarán los elementos comunes de su padre (header, barra de navegación y footer), y cuya estructura se utilizará, será la base, de cada una de las páginas de nuestro sitio web. (Inicio, Pagina Uno, Sobre Nosotros). Sin embargo, ten en cuenta que si una plantilla hija tiene algún elemento que también este definido en la plantilla padre, los elementos de esta última prevalecerán sobre las de la plantilla padre. 

Por ejemplo, en la plantilla padre has definido un determinado footer y en la hija otro distinto, el footer de la plantilla hija prevalece sobre la de la padre. No habrá herencia.

Empecemos modificando la plantilla base.html que ya tenemos para indicar las partes o bloques que las otras plantillas pueden escribir. Si tuviéramos hoja de estilos tendríamos que definirlas aquí. En este fichero incluiremos los bloques:

{% block title %} {% endblock %}
{% block content %} {% endblock %}

para definir el titulo y el contenido de las páginas hijas.

Lo que hacemos es crear una plantilla cuyo código html haremos que se repita en las demás, pero dejaremos los bloques que hemos comentado anteriormente para que cada una de las páginas hijas escriba ahí su propio código. Puedes usar tantos bloques como necesites, no tienes que limitarte a estos dos. 

base.html (Será la plantilla base utilizada para crear las demás)

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{% endblock %}</title>
    
</head>
<body>
    <header>
        <h1 style="background-color: darkolivegreen; color: white; text-align:center">Tutorial de Django</h1>
        <!-- Aqui ira la barra de navegación cargada desde una plantilla
        externa, es decir desde otro documento html-->
        {% include "barra_navegacion.html" %}
        <hr/>
    </header>
    <section>
        <!--Aqui ira la parte cambiante de las páginas.-->
        {% block content %}{% endblock %}
    </section>
    <footer style="background-color: darkolivegreen; color: white; text-align:center">
        <hr/>
        <small>Aqui ira el pie de página.</small>
    </footer>
    
</body>
</html>

¿Cómo conseguimos que otra plantilla html herede de esta base que hemos creado? Pues con la etiqueta:

{% extends "[nombre_plantilla_padre]" %}

Cada plantilla que lleve esta etiqueta como primera etiqueta de código va a heredar de la plantilla padre.
Es importante que para que esto funcione esta etiqueta sea la primera que aparezca en las plantillas hijas.  La ventaja no es solo el ahorro de tiempo para crear la plantilla hija, ya que casi no hay que utilizar código, el ahorro viene si queremos modificar la estructura general, por ejemplo la cabecera, que modificando la estructura de la padre automáticamente estará modificado en todas.

Vamos a crear la plantilla "inicio.htm" que se usará cuando entremos directamente en nuestro proyecto.

Lo primero importamos la plantilla base con la etiqueta {% extends "nombre_plantilla" %} y definimos su contenido para que se modifiquen los bloques que ya hemos definido.

inicio.html

{% extends "base.html" %}

{% block title %}Pagina de Inicio{% endblock %}

{% block content %}
    <p>¡Bienvenido! </p>
    <p>Esta es la página principla del proyecto.</p>
{% endblock %}
Esta primera plantilla hija la comenzamos diciendo que use la plantilla padre con la instrucción:

{% extends "base.html" %}

Para continuar reescribiendo los dos bloques, con lo que estamos diciendo al programa que busque estos bloques en la plantilla padre y que los sustituya por los que hemos puesto en esta plantilla.

El primero {% block title %} {% endblock %} modifica el título de la vista de inicio.

El segundo {% block content %} {% endblock %} mostrará el contenido propio de la página de inicio.

Y como ahora esta plantilla va a ser la que se llame cuando entremos en el proyecto tenemos que modificar la vista que teníamos asociada:

views.py

...
# Vistas

# Vista principal a la que se accederá si no ponemos nada.
def principal(request):
    return render(request, "inicio.html")


def saludo(request):
...
Salida:

plantilla incrustada



Vamos a crear otras dos vistas y paginas para mostrar en el menú creado.

pagina1.html

{% extends "base.html" %}
{% block title %}Pagina Uno{% endblock %}
{% block content %}
    <h2>Esta es la primera página.</h2>
    <p>Sigue siendo muy sencilla.</p>
{% endblock %}
Exactamente como antes. Heredamos el código de la plantilla "base.html" 

{% extends "base.html" %}

y definimos los bloques se se insertarán y que se mostrarán en el diseño de la Página Uno (título y contenido)

También tenemos que importar y crear la nueva vista:

views.py

...
# Vistas

# Vista principal a la que se accederá si no ponemos nada.
def principal(request):
    return render(request, "inicio.html")

def pagina_uno(request):
    return render(request, "pagina1.html")
...
y definir la ruta:

urls.py

rom django.contrib import admin
from django.urls import path
from Proyecto1.views import saludo, fecha_actual, suma_numeros, principal,\
    pagina_uno

urlpatterns = [
    path('admin/', admin.site.urls),
    path('saludo/', saludo),
    path("time_server/", fecha_actual),
    path("suma_numeros/<int:numero_1>/<int:numero_2>/", suma_numeros),
    path("", principal),
    path("pagina_uno/", pagina_uno),
]
Salida:

pagina uno - inserción de plantillas

y la última plantilla:

sobre_nosotros.html

{% extends "base.html" %}
{% block title %}Sobre Nosotros{% endblock %}
{% block content %}
    <h2>Estamos empezando con DJANGO.</h2>
    <p>¡Luego juntaremos todo en una aplicación!.</p>
{% endblock %}

views.py

...
# Vista principal a la que se accederá si no ponemos nada.
def principal(request):
    return render(request, "inicio.html")

def pagina_uno(request):
    return render(request, "pagina1.html")

def sobre_nosotros(request):
    return render(request, "sobre_nosotros.html")
...

urls.py

from django.contrib import admin
from django.urls import path
from Proyecto1.views import saludo, fecha_actual, suma_numeros, principal,\
    pagina_uno, sobre_nosotros

urlpatterns = [
    path('admin/', admin.site.urls),
    path('saludo/', saludo),
    path("time_server/", fecha_actual),
    path("suma_numeros/<int:numero_1>/<int:numero_2>/", suma_numeros),
    path("", principal),
    path("pagina_uno", pagina_uno),
    path("sobre_nosotros/", sobre_nosotros)
]

Salida:

sobre nosotros -  inserción de plantillas

Para terminar de definir el menú solo nos queda hacer que la barra de navegación funcione. Aunque ya la tenemos definida vamos a construir los enlaces.

La forma menos eficaz es codificar a lo bruto el link de enlace. Por ejemplo el enlace sobre nosotros podemos construirlo como:

<li><a href="/sobre_nosotros/">Sobre Nosotros</a></li>

Pero entonces, si más adelante, cambiamos el patrón para la vista "sobre nosotros" (e.j. a /acerca_de/) la plantilla no podrá seguir enlazando correctamente. No obstante lo siguiente funcionaria para que al pulsar en el enlace nos lleve a la vista "sobre_nosotros".


barra_navegacion.html

...
<body>
    <nav>
        <ul id="barra">
            <li><a href="/">Inicio</a></li>
            <li><a href="#">Página Uno</a></li>
            <li><a href="/sobre_nosotros/">Sobre Nosotros</a></li>
        </ul>
    </nav>
</body>
...
Usar un mapeador de URL es más flexible y robusto. A pesar del nombre es muy fácil crearlo. Vamos a verlo con la vista de la pagina_uno. En el archivo "urls.py" vamos al path correspondiente en la ruta y utilizamos el parámetro "name=" con el nombre que queramos. Como hace referencia a la ruta pagina_uno utilizaré el mismo nombre. Esta parte del código quedaría de esta forma:

urls.py

from django.contrib import admin
from django.urls import path
from Proyecto1.views import saludo, fecha_actual, suma_numeros, principal,\
    pagina_uno, sobre_nosotros

urlpatterns = [
    path('admin/', admin.site.urls),
    path('saludo/', saludo),
    path("time_server/", fecha_actual),
    path("suma_numeros/<int:numero_1>/<int:numero_2>/", suma_numeros),
    path("", principal),
    path("pagina_uno/", pagina_uno, name="pagina_uno"),
    path("sobre_nosotros/", sobre_nosotros)
]
y ahora en el código de la barra de navegación utilizamos la siguiente etiqueta para crear el enlace. 

{% url 'nombre_usado_en_name' %}

Esto hace que Django cree automáticamente, mediante el mapeador de urls, la ruta para la vista que hemos definido. De forma que si mañana cambiamos el primer argumento, el nombre de la ruta en el archivo urls.py, la vista seguirá funcionado.

barra_navegacion.html

...
body>
    <nav>
        <ul id="barra">
            <li><a href="/">Inicio</a></li>
            <li><a href="{% url 'pagina_uno' %}">Página Uno</a></li>
            <li><a href="/sobre_nosotros/">Sobre Nosotros</a></li>
        </ul>
    </nav>
</body>
...
Con esto ya tenemos un proyecto plenamente funcional.

Próximo Post - 6. Bases de datos