lunes, 23 de enero de 2023

8.- Django - Panel de Administración.

Normalmente la gestión de una página web se hace desde un panel de control. Se puede añadir nuevos usuarios, añadir registros a las tablas que hemos creado etc.  Al crear el proyecto con el comando "startproject" por defecto el panel de administración ya aparece habilitado. Fijaros que en el proyecto ya hay un archivo llamado "admin.py" que sirve para esto y también que en settings.py por defecto donde tenemos INSTALLED_APPS ya aparece la librería django.contrib.admin y otras para manejar esto.

Si ejecutamos el servidor con:

$ python manage.py runserver

y vamos a la dirección:

/localhost:8000/admin ya vemos como nos aparece el panel de administración:


Panel de Administración de Django



Para acceder necesitas un usuario y una contraseña. Esto se consigue creando un SUPERUSUARIO que maneje esto:


$ python manage.py createsuperuser


Nos preguntará por un nombre de usuario, correo electrónico y contraseña. Una vez creado ya nos podremos loguear y veremos la siguiente pantalla.


pantalla inicio administrador de Django

De momento está muy vacío pero ya nos permite crear grupos y usuarios para la administración.

Vamos a ver como incluir las tablas de la base de datos en el panel de administración. De esta forma podremos crear registros sin tener que abrir el shell de python. Lo haremos directamente de forma gráfica.

Edita el archivo admin.py de la aplicación gestionPedidos. Si lo abrimos veremos lo siguiente:

from django.contrib import admin

# Register your models here.

A modo de comentario nos dice que registremos aquí nuestros modelos. En el ejemplo Clientes, Artículos y Pedidos. Así que vamos a importarlos y luego añadir el código para que se muestren en el panel de administración:

from django.contrib import admin

# Register your models here.

# Importamos los modelos
from .models import Clientes, Articulos, Pedidos

# Los registramos en el pandel de administración.
admin.site.register(Clientes)
admin.site.register(Articulos)
admin.site.register(Pedidos)

Para ver si hemos tenido éxito ejecutamos el servidor y vamos a la url del panel de administración y ya nos aparecen las tablas. Como curiosidad DJANGO añade una "s" al final de todas las tablas con lo que si el nombre de la tabla ya terminaba en "s" esta se repetirá.

Panel de administración con las tablas

Si pinchamos en cualquiera de las tablas ya podemos hacer cualquier cosa con los registros (crear, actualizar, borrar etc). Por ahora todos los campos de la tabla aparecen en negrita lo que quiere decir que al añadir uno nuevo es obligatorio rellenarlos. Esto más tarde veremos como modificarlo para poder añadir campos opcionales.

Por defecto cada registro de la base de datos aparece como un objeto. No lo hace así la tabla Artículos que ya teníamos creada, porque habíamos definido en models.py el método __str__(). Así que si queremos ver la información de esta manera hay que añadir este método como vimos en el capitulo anterior.

Para poder establecer un campo como opcional hay que modificar el modelo (models.py) añadiendo esto al campo que queramos que sea opcional (blank=True, null=True). Por ejemplo para poner como opcional el campo correo electrónico de la tabla clientes, haríamos lo siguiente:

gestionPedidos/models.py

...
# 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(blank=True, null=True)
    telefono = models.CharField(max_length=9)

class Articulos(models.Model):
...

Como siempre que se hacen modificaciones al modelo hay que realizar las correspondientes migraciones.

(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py makemigrations
Migrations for 'gestionPedidos':
  gestionPedidos/migrations/0002_alter_clientes_email.py
    - Alter field email on clientes
(miEntorno) chema@lenovo:~/Cursos/DJANGO/tiendaVirtual$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, gestionPedidos, sessions
Running migrations:
  Applying gestionPedidos.0002_alter_clientes_email... OK
y ya el campo no aparece en negrita, así que no es un campo obligatorio:

Campo opcional en una tabla



Como cambiar como aparecen o se reflejan los nombres de los campos en las tablas en el panel de administración.

Por defecto Django traslada tal cual los nombres de los campos de las tablas. Con una particularidad que los pone la primera letra del nombre del campo en Mayúsculas. También si el nombre del campo fuera compuesto (Ej nombre_cliente) quitaría el guión bajo y pondría el nombre de la primera palabra en mayúsculas.

¿Pero que ocurre si queremos ver a través del panel de administración un nombre diferente? Imaginemos que en vez de que en la tabla de clientes el campo "dirección" queremos que aparezca como "el domicilio".

Para que esto ocurra hay que ir al modelo, localizar donde se encuentra dicho campo y en el método .CharField agregar un segundo argumento, verbosename con el nombre que queremos que aparezca en el panel de administración. Para ver los cambios no es necesario reiniciar el servidor.

gestionPedidos/models.py

...
# 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, verbosename="el domicilio")
    # Email ya valido ya esta creado
    email = models.EmailField(blank=True, null=True)
    telefono = models.CharField(max_length=9)

class Articulos(models.Model):
...
cambio nombre de campo en panel de administración.


Como ver los nombre de los campos, en vez de un objeto en el panel de administración.

Por defecto cuando entramos en una tabla el panel de administración nos muestra cada registro como un objeto. Anteriormente mejoramos esto para la tabla artículos definiendo un método __str__(self) dentro de la clase que define esta tabla. ¿Pero como hacer para que en vez de un objeto el panel de administración nos muestre los campos que queramos nosotros? Por ejemplo que en la tabla clientes se muestre el nombre, dirección y teléfono.

Para ello debemos ir al archivo admin.py y crear una clase que herede de admin.ModelAdmin lo que nos va a permitir hacer modificaciones en los modelos. Antes de registrar las tablas creamos una clase de la siguiente forma y luego la registramos junto a la tabla.

gestionPedidos/admin.py

from django.contrib import admin

# Register your models here.

# Importamos los modelos
from gestionPedidos.models import Clientes
from gestionPedidos.models import Articulos
from gestionPedidos.models import Pedidos

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")

# Los registramos en el pandel de administración.
admin.site.register(Clientes, PanelCliente)
admin.site.register(Articulos)
admin.site.register(Pedidos)

Ya podemos reiniciar el servidor y ver si ha funcionado.

mostrar nombres de campos en vez del objeto


Añadir una casilla de búsqueda de registros.

Cuando tengamos muchos registros nos puede venir bien tener una casilla que nos permita buscar registros basándonos en algún o algunos campos.

Para ello volvemos al archivo admin.py y debajo de la clase que creamos antes "PanelClientes" y debajo de list_display añadimos la propiedad search_fields con los campos por los que se pueda realizar la búsqueda.

gestionPedidos/admin.py

...

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")
    # para que aparezca un campo de búsqueda y permita buscar por los campos
    # nombre y teléfono
    search_fields=("nombre", "telefono")

# Los registramos en el panel de administración.
admin.site.register(Clientes, PanelCliente)
admin.site.register(Articulos)
admin.site.register(Pedidos)
...
casilla para buscar registros


¿Cómo filtrar los registros que tengamos en las tablas?

Si cogemos la tabla Articulos que tenía unos cuantos artículos (juegos) vamos a ver como podemos filtrar los registros en base por ejemplo al campo "categoría".

Para ello tenemos que ir al archivo "admin.py" de la aplicación gestionPedidos y crear una nueva clase al igual que en su día hicimos para crear una pestaña de buscador. Creamos una nueva clase que herede de admin.ModelAdmin y usamos la instrucción list_filter de la siguiente forma:


gestionPedidos/admin.py

...

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")
    # para que aparezca un campo de búsqueda y permita buscar por los campos
    # nombre y teléfono
    search_fields=("nombre", "telefono")

# Para establecer filtros de una tabla en base a un criterio (campo)
class PanelArticulos(admin.ModelAdmin):
    list_filter=("categoria",)

# Los registramos en el panel de administración.
admin.site.register(Clientes, PanelCliente)
admin.site.register(Articulos, PanelArticulos)
admin.site.register(Pedidos)
...


panel de administración con filtros


Otra forma de registrar las tablas en en el panel de administración es usando decoradores. En el ejemplo anterior se haría de la siguiente forma:

gestionPedidos/admin.py

...

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
@admin.register(Clientes)
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")
    # para que aparezca un campo de búsqueda y permita buscar por los campos
    # nombre y teléfono
    search_fields=("nombre", "telefono")

# Para establecer filtros de una tabla en base a un criterio (campo)
@admin.register(Articulos)
class PanelArticulos(admin.ModelAdmin):
    list_filter=("categoria",)

# Los registramos en el panel de administración.
admin.site.register(Pedidos)
...

¿Cómo ordenar los registros que tengamos en las tablas en base a unos determinados campos?

Para conseguir esto vamos a usar la propiedad ordering = []. 

En el contexto de Django y la configuración del administrador (admin.py), se utiliza
 para especificar el orden predeterminado en el que se mostrarán los objetos del modelo en la vista de lista del panel de administración. Por ejemplo, vamos a ver como podemos ordenar los clientes para que se muestren por orden alfabético y si existiesen clientes con el mismo nombre que se ordenen por su dirección. Es decir, en este caso, los objetos se ordenarán primero por el campo 'nombre' y luego por el campo 'direccion'.

gestionPedidos/admin.py

...

# Para personalizar que campos se ven en las tablas en el panel de administración
# ya que si no cada registro se ve como un objeto.
class PanelClientes(admin.ModelAdmin):
    list_display("nombre","direccion","telefono")
    # para que aparezca un campo de búsqueda y permita buscar por los campos
    # nombre y teléfono
    search_fields=("nombre", "telefono")
    ordering = ["nombre", "direccion"]

# Para establecer filtros de una tabla en base a un criterio (campo)
class PanelArticulos(admin.ModelAdmin):
    list_filter=("categoria",)

# Los registramos en el panel de administración.
admin.site.register(Clientes, PanelCliente)
admin.site.register(Articulos, PanelArticulos)
admin.site.register(Pedidos)
...

Puedes encontrar mas información sobre el Administrador de Django en el siguiente sitio web https://docs.djangoproject.com/en/4.2/ref/contrib/admin/

¿Cómo cambiar el idioma del panel de administración?


Tenemos que ir al archivo "settings.py" y buscar la clave "LANGUAGE_CODE" comentarla por si queremos volver a ponerla en ingles y poner el idioma que queramos. Yo la pondré en español. También cambiaré la zona horaria a la de España que en verano son dos horas menos.


tiendaVirtual/settings.py

...

# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'es-eu'

TIME_ZONE = 'Europe/Madrid'

USE_I18N = True

USE_TZ = True
...

Agregar usuarios con diferentes perfiles al panel de administración.


A parte de nuestro usuario que tiene un perfil de  Superusuario, es decir, puede realizar cualquier tipo de acción, podemos crear nuevos usuarios que pueden tener los mismos permisos que nosotros o no. 

Si es parte del staff puede entrar en el panel de administración de Django. Pero esto no implica que pueda modificar todo, puede estar limitado a ciertas acciones.

Puede ser un usuario activo que son aquellos que pueden acceder a urls que necesitan autentificación dentro de nuestro sitio.

Para añadir un nuevo usuario entramos en "Autentificación y autorización" y "añadir usuario".

registrar un nuevo usuario


Aunque tengamos el usuario creado, necesitamos que sea staff si queremos que pueda acceder al panel de administración. Pero si entra sin que le concedamos permisos no vera nada, tendremos por tanto que definir con los permisos, que es lo que puede hacer.


Pero para no tener que ir asignando permisos de forma individual podemos crear un grupo ya con todos ellos y asignarlo a los usuarios que necesitemos, lo cual es más fácil.



domingo, 8 de enero de 2023

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

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': os.path.join(BASE_DIR, 'db.sqlite3'), } }

y tenemos que sustituirla por esta:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'tutorial_db',
        'USER': 'chemahg',
        'PASSWORD': 'Ademar74',
        '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.

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 froma 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 abres la tabla verás que tiene 14. 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 ejempo 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"'
>>>