This page looks best with JavaScript enabled

Django Database Models

 ·  🎃 kr0m

Django manages the database structure through Models, where we define the tables of the database and their fields. Through Models and migration commands, we can modify the structure transparently without the need to access the database CLI and preserving the data prior to migration.

Before starting, it is recommended to read the previous articles on Django as they are the previous steps to this article:


If we have followed the steps of the previous articles, we will already have the necessary tables for the Apps that we have included, but we still need the tables of our rxWod App. To generate them, we must create our models, where each model is a table and each attribute of the model is a column in the table.

In our case, we are going to define a model for exercises and another for training routines.

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)

As we can see, each table is defined as a class and each class has attributes that are the fields. Each field is defined with a type and validators are assigned to restrict the data type. We can also see that there are fields with predefined choices such as exercise levels or categories. These fields have an ‘_’ so that translations are applied to them, but we will explain this in later articles.

The Routine model has an interesting feature, which is that it has been linked as a ForeignKey User with an on_delete=models.CASCADE. This way, if a user is deleted, the routines of that user will be automatically deleted.

The str(self) method defines which field is considered as the identifier for each instance of the model. This will be useful later when we use Django’s object administration system, as each exercise will be identified by the string composed of its name, category, and level, while each routine will be identified by its creation date. Additionally, if we perform queries from our views and print the returned objects, the print will show the name returned by str.

For the authentication app, we did not create any models since we will use the ones created by the django.contrib.auth App that come by default.

We define the class names of the 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'

We register our apps, for which we must indicate the dotted path as shown below, plus the class name:

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

Therefore, the lines to add in the apps would be (the first two of the shown array):

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',
]

We activate the project’s venv:

cd rxWod
source bin/activate
cd rxWodProject/

We generate the tables indicated in the model, for which we must first generate the migration, which is nothing more than a file with the commands to be executed on the database:

python manage.py makemigrations

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

Before performing the migration, we can check that it will not cause any problems:

python manage.py check

System check identified no issues (0 silenced).

We perform the migration:

python manage.py migrate

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

We check that the tables have been created:

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

The database migration system allows altering models without the need to delete the database or tables. This way, we can change the structure without losing existing data. Additionally, it provides a history of the changes made to the schema and the ability to revert migrations in case of issues.

If you liked the article, you can treat me to a RedBull here