Django: Modelos de base de datos


Django gestiona la estructura de la base de datos mediante Modelos, en estos definiremos las tablas de la base de datos y sus campos. Mediante los Modelos y los comandos de migración podremos modificar la estructura de forma transparente si necesidad de acceder a la CLI de la base de datos y conservando los datos previos a la migración.

Antes de comenzar es recomendable leer los artículos anteriores sobre Django ya que son los pasos previos a este artículo:


Si hemos seguido los pasos de los artículos anteriores ya tendremos las tablas necesarias para las Apps que hemos incluido pero faltan las tablas de nuesta App rxWod, para generarlas debemos crear nuestros modelos, cada modelo es una tabla y cada atributo del modelo una columna en la tabla.

En nuestro caso vamos a definir un modelo para los ejercicios y otro para las rutinas de entrenamiento.

vi rxWod/models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator, MinValueValidator, validate_comma_separated_integer_list, MinLengthValidator, MaxLengthValidator
from django.utils.translation import gettext_lazy as _

# Exercise
class Exercise(models.Model):
    exercise_id = models.IntegerField(default=-1, unique=True, null=False, blank=False)
    name = models.CharField(max_length=200, unique=False, null=False, blank=False)
    description = models.CharField(max_length=200, unique=False, null=False, blank=False)
    default_value = models.IntegerField(default=1, validators=[MinValueValidator(1)])
    
    EXERCISE_CATEGORY = [
        (0, _('Shoulders')),
        (1, _('Back')),
        (2, _('Biceps')),
        (3, _('Triceps')),
        (4, _('Chest')),
        (5, _('Core')),
        (6, _('Gluteus')),
        (7, _('Quadriceps')),
        (8, _('Hamstring')),
        (9, _('Cardio')),
        (10, _('Lumbar')),
        (11, _('Grip')),
    ]
    category_id = models.PositiveSmallIntegerField(default=1, choices=EXERCISE_CATEGORY, null=False, blank=False)

    EXERCISE_LEVEL = [
        (0, 'N1'),
        (1, 'N2'),
        (2, 'RX'),
        (3, 'RX+'),
    ]
    level = models.PositiveSmallIntegerField(default=0, choices=EXERCISE_LEVEL, null=False, blank=False)
    url = models.URLField()
    is_metabolic = models.BooleanField(default=False, null=False, blank=False)

    def __str__(self):
        return_value = str(self.name) + ' [' + str(self.get_category_id_display()) + '] [' + str(self.get_level_display() + ']')
        return return_value


# Routine
class Routine(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    # Each routine has 10 exercises separated by ,: 1,2,3,4,5,6,7,8,9,10
    exercise_ids = models.CharField(validators=[validate_comma_separated_integer_list], max_length=512, blank=True, null=False)
    exercise_repetitions = models.CharField(validators=[validate_comma_separated_integer_list], max_length=512, blank=True, null=False)
    rounds = models.IntegerField(default=1, validators=[MinValueValidator(1), MaxValueValidator(4)])
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False)
    # There are 10 exercise categories, percentages are separated by ,: 1,2,3,4,5,6,7,8,9,10
    percentages = models.CharField(validators=[validate_comma_separated_integer_list], max_length=512, blank=False, null=False)
    level = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])

    def __str__(self):
        return str(self.date)

Como vemos cada tabla es definida como clase y cada clase tiene atributos que son los campos, cada campo se define de un tipo y se asignan validadores de este modo se restringe el tipo de datos, también podemos ver que hay campos con elecciones predefinidas como es el caso de los niveles o las categorías de los ejercicios, estos campos tienen un '_' para que se apliquen traducciones a ellos, pero eso ya lo explicaremos en artículos posteriores.

El modelo Routine tiene una característica interesante y es que se ha vinculado como ForeignKey User con un on_delete=models.CASCADE, de este modo si se elimina un usuario las rutinas de dicho usuario serán eliminadas de forma automática.

El método __str__(self) define que campo se considera identificativo para cada instancia del modelo, esto resultará útil mas adelante cuando utilicemos el sistema de administración de objetos de Django ya que cada ejercicio quedará identificado por el string compuesto por su nombre, su categoría y su nivel, en cambio cada rutina será identificada por su fecha de creación. Ademas si desde nuestras vistas realizamos querys y hacemos un print de los objetos retornados, el print nos mostrará el nombre retornado por __str__.

Para la App de autenticación no creamos ningún modelo ya que vamos a utilizar los creados por la App django.contrib.auth que ya vienen por defecto.

Definimos los nombres de la clase de las Apps:

vi rxWod/apps.py
from django.apps import AppConfig

class RxwodConfig(AppConfig):
    name = 'rxWod'
vi usersAuth/apps.py
from django.apps import AppConfig

class UsersauthConfig(AppConfig):
    name = 'usersAuth'

Damos de alta nuestras Apps, para ello debemos indicar el dotted path como muestra a continuación mas el nombre de la clase:

rxWod/apps.py: rxWod.apps + RxwodConfig
usersAuth/apps.py: usersAuth.apps + UsersauthConfig

Por lo tanto las líneas a añadir en las Apps serían(las dos primeras del array mostrado):

vi rxWodProject/settings.py
INSTALLED_APPS = [
    'rxWod.apps.RxwodConfig',
    'usersAuth.apps.UsersauthConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Activamos el venv del proyecto:

cd rxWod
source bin/activate
cd rxWodProject/

Generamos las tablas indicadas en el modelo, para ello primero hay que generar la migración, este no es mas que un fichero con los comandos a ejecutar sobre la base de datos:

python manage.py makemigrations
Migrations for 'rxWod':
  rxWod/migrations/0001_initial.py
    - Create model Exercise
  - Create model Routine

Antes de ralizar la migración podemos comprobar que no vaya a dar ningún problema:

python manage.py check
System check identified no issues (0 silenced).

Realizamos la migración:

python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, rxWod, sessions
Running migrations:
Applying rxWod.0001_initial... OK

Comprobamos que se hayan creado las tablas:

psql -U rxwod_user rxwod
rxwod=> \dt
                    List of relations
 Schema |            Name            | Type  |   Owner    
--------+----------------------------+-------+------------
 public | auth_group                 | table | rxwod_user
 public | auth_group_permissions     | table | rxwod_user
 public | auth_permission            | table | rxwod_user
 public | auth_user                  | table | rxwod_user
 public | auth_user_groups           | table | rxwod_user
 public | auth_user_user_permissions | table | rxwod_user
 public | django_admin_log           | table | rxwod_user
 public | django_content_type        | table | rxwod_user
 public | django_migrations          | table | rxwod_user
 public | django_session             | table | rxwod_user
 public | rxWod_exercise             | table | rxwod_user
 public | rxWod_routine              | table | rxwod_user
(12 rows)

rxwod-> \q

El sistema de migraciones de bases de datos permite alterar los modelos sin necesidad de eliminar la base de datos o las tablas, de este modo cambiamos la estructura sin perder los datos existentes, además proporciona un histórico de los cambios realizados en el esquema y la posibilidad de revertir migraciones en caso de problemas.

Si te ha gustado el artículo puedes invitarme a un redbull aquí.
Si tienes cualquier pregunta siempre puedes enviarme un Email o escribir en el grupo de Telegram de AlfaExploit.
Autor: kr0m -- 01/03/2021 21:19:04 -- Categoria: Programacion Django