Models

If you are building a dynamic application using a web framework, data storage, using a database is essential. Recall that the presentation layer is the layer that the user interacts with. Any data posted from the presentation layer needs to be persisted and also kept for retrieval for later use.

Model

The solution is to use a model. The object equivalent of a database table used in Django and acts as a single definitive source of information about your data.

Working with Database

If you are a developer on the back end of an application, working with databases can be achieved in one of two ways.

  1. The first way is to work directly in the database to create the required tables. Then in your application, you would need to create custom queries written in the Structured Query Language or SQL to store and retrieve the data.
  2. The second way is to use a framework. You’ve already learned that frameworks are designed to assist developers by providing them with a platform for rapid development and one-way developers can speed up their development is by using models to interact with the database.

Models

A model is the single definitive source of information about your data. It contains the essential fields and behaviors of the data you’re storing. Generally, each model maps to a single database table.

Models are an integral part of the Django framework.

The structure of model

Each model is a Python class that subclasses django.db.models.Model.

Each attribute of the model represents a database field. This means that instead of having to write a custom query for adding and retrieving database records, you can use a model to do it instead.

Django gives you an automatically generated database access API to access the database with Python code.

Essentially, you can think of a model as a Python object and models help developers create, read, update, and delete objects, commonly called the CRUD operations.

Making a table using SQL syntax

You may recall that to create a table in a database using SQL, you would need to write a query using the create table syntax. When the code runs, a table called user will be created with three columns, ID, first name, and last name.

CREATE TABLE user (
	"id" serial NOT NULL PRIMARY KEY,
	"first_name" varchar(30) NOT NULL,
	"last_name" varchar(30) NOT NULL
);

Making a table using django model

To do this using a model in Django, you define a class that will subclass the django.db.models.Model. You may notice that the ID or a primary key is not specified in the class. This is because Django automatically adds it, but you can override it if needed.

from django.db import models
 
class User(models.Model):
	first_name = models.CharField(max_length=30)
	last_name = models.CharField(max_length=30)

WARNING

At this point, it’s important to know that you cannot create a database table by only defining a model class in Python. To complete this process, you need to use something called migrations.

Django automatically provides all these methods (CRUD operations) out of the box.

Create

You may recall that creating records in SQL requires you to use the insert statement.

INSERT INTO user(id, first_name, last_name)
VALUES (1, "John", "Jones");

In Django, creating records requires you to create a new object from the class user and then persisted using the save function.

new_user = User(id=1, "John", "Jones")
new_user.save()

Read

Un SQL to retrieve information, you need to write a SQL query using the select statement.

SELECT * FROM user WHERE id = 1

In Django, you can use the get method which is bound to the user.objects.

user = User.objects.get(id=1)

Update

Suppose you have a table of users with a user named John Jones with the ID of one. You want to update the user’s last name to Smith. In SQL, you use the update statement to update an existing record in a database.

UPDATE user
SET last_name = "Smith"
WHERE id = 1

To achieve the same result in Django, you use the methods get and save. The code works by checking if a user exists with the ID of one and then updates the user’s last name to Smith.

user = User.objects.get(id=1)
user.last_name = "Smith"
user.save()

Delete

With SQL, the delete statement is required to delete records in the database.

DELETE FROM user WHERE id = 1

To achieve the same result with Django, you use the delete method.

User.objects.filter(id=1).delete()

It’s important to note that these are only some of the many methods Django provides when working with models.


Model relationships

In this reading, you will explore the different types of relationships between Django models.

Primary key

In a relational database, each table that represents an entity has one column that has a unique value for each row. Such a column or field is known as the Primary Key.

If the primary key of one table appears as one of the fields in another table while having its own primary key, then it is called a Foreign Key. 

For example, in a Products table, the ProductID field is its primary key.

In the Customers’ table, the ProductID field, which refers to the product purchased by the customer, becomes the foreign key.

The idea behind designing related tables is to avoid data redundancy unnecessary repetition of the same data in many rows and ensure data integrity.

If the unique ProductID in the Customers’ table is replaced by a longer naming convention for the product field, it will have to be entered every time a customer buys the product. This can lead to typing errors.

Similarly, if a product’s ID is referred to in the Customers’ table and is removed, other product details, such as price, will not be available.

Relational databases have a mechanism to prevent the deletion of the primary key if it is being used in the related table so that the data integrity is intact.

Since the Django models are mapped to the corresponding tables in the database, you can define a relationship such as this between the two model fields.

Types of Relationships

There are three types of relationships that exist:

  • One-to-One,
  • One-to-Many, and
  • Many-to-Many.

Let’s explore these by beginning with a One-to-One relationship.

One-to-One relationship

If a primary key is in one model, and only one record exists in the other related model, the two models are said to have a one-to-one relationship.

Let’s use an example of a college model and a principal model. A college can have only one principal or it can be similarly phrased as one person can be a principal of only one college.

The college model can be described as follows:

class college(Model): 
    CollegeID = models.IntegerField(primary_key = True) 
    name = models.CharField(max_length=50) 
    strength = models.IntegerField() 
    website=models.URLField() 

In the principal model, you must provide the CollegeID field as the foreign key. The on_delete option specifies the behavior in case the associated object in the primary model is deleted. The values are:

  • CASCADE: deletes the object containing the ForeignKey
  • PROTECT: Prevent deletion of the referenced object.
  • RESTRICT: Prevent deletion of the referenced object by raising RestrictedError

The principal model has the following field structure:

class Principal(models.Model): 
    CollegeID = models.OneToOneField( 
                College, 
                on_delete=models.CASCADE 
                ) 
    Qualification = models.CharField(max_length=50) 
    email = models.EmailField(max_length=50) 

When you run a migration on these models, respective tables are created with equivalent SQL queries:

CREATE TABLE "myapp_college" ("CollegeID" integer NOT NULL PRIMARY KEY, "name" varchar(50) NOT NULL, "strength" integer NOT NULL, "website" varchar(200) NOT NULL); 
CREATE TABLE "myapp_principal" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "Qualification" varchar(50) NOT NULL, "email" varchar(50) NOT NULL, "CollegeID_id" integer NOT NULL UNIQUE REFERENCES "myapp_college" ("CollegeID") DEFERRABLE INITIALLY DEFERRED); 

One-to-Many Relationship

In a One-to-Many relationship, one object of a model can be associated with one or more objects of another model. For example, a teacher is qualified to teach a subject, but there can be more than one teacher in a college who teaches the same subject.

The subject model is as below:

class Subject(models.Model): 
    Subjectcode = models.IntegerField(primary_key = True) 
    name = models.CharField(max_length=30) 
    credits = model.IntegerField() 

The teacher model has its own primary key. It has a Foreignkey relating this model with the subject model.

class Teacher(models.Model): 
    TeacherID = models.ItegerField(primary_key=True) 
    subjectcode=models.ForeignKey( 
                Subject,  
                on_delete=models.CASCADE 
                  ) 
    Qualification = models.CharField(max_length=50) 
    email = models.EmailField(max_length=50) 

The following SQL queries will be executed when you run the migration:

CREATE TABLE "myapp_subject" ("Subjectcode" integer NOT NULL PRIMARY KEY, "name" varchar(30) NOT NULL, "credits" integer NOT NULL, "Qualification" varchar(50) NOT NULL, "email" varchar(50) NOT NULL);
CREATE TABLE "myapp_teacher" ("TeacherID" integer NOT NULL PRIMARY KEY, "Qualification" varchar(50) NOT NULL, "email" varchar(50) NOT NULL, "subjectcode_id" integer NOT NULL REFERENCES "myapp_subject" ("Subjectcode") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "myapp_teacher_subjectcode_id_bef86dea" ON "myapp_teacher" ("subjectcode_id");

Many-to-Many Relationship

In a Many-to-Many relationship, multiple objects of one model can be associated with multiple objects of another model.

Let’s redefine the relationship between the subject and teacher models in the above example. If more than one teacher can teach the same subject, a single teacher can teach more than one subject. So, there is a Many-to-Many relationship between the two. Django implements this with a Many-to-Many Field type. Let’s use it in the subject model.

The teacher model is straightforward.

class Teacher(models.Model): 
    TeacherID = models.ItegerField(primary_key=True) 
    Qualification = models.CharField(max_length=50) 
    email = models.EmailField(max_length=50) 

The design of the Subject model class reflects the Many-to-Many relationship.

class Subject(models.Model): 
    Subjectcode = models.IntegerField(primary_key = True) 
    name = models.CharField(max_length=30) 
    credits = model.IntegerField() 
    teacher = model.ManyToManyField(Teacher) 

When the migrations are done, the following SQL queries will be executed to establish a many-to-many relationship.

CREATE TABLE “myapp_teacher” (“TeacherID” integer NOT NULL PRIMARY KEY, “Qualification” varchar(50) NOT NULL, “email” varchar(50) NOT NULL);

CREATE TABLE "myapp_teacher" ("TeacherID" integer NOT NULL PRIMARY KEY, "Qualification" varchar(50) NOT NULL, "email" varchar(50) NOT NULL);
CREATE TABLE "myapp_subject" ("Subjectcode" integer NOT NULL PRIMARY KEY, "name" varchar(30) NOT NULL, "credits" integer NOT NULL);
CREATE TABLE "myapp_subject_teacher" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "subject_id" integer NOT NULL REFERENCES "myapp_subject" ("Subjectcode") DEFERRABLE INITIALLY DEFERRED, "teacher_id" integer NOT NULL REFERENCES "myapp_teacher" ("TeacherID") DEFERRABLE INITIALLY DEFERRED);
CREATE UNIQUE INDEX "myapp_subject_teacher_subject_id_teacher_id_9b6a3c00_uniq" ON "myapp_subject_teacher" ("subject_id", "teacher_id");
CREATE INDEX "myapp_subject_teacher_subject_id_e87c76e7" ON "myapp_subject_teacher" ("subject_id");
CREATE INDEX "myapp_subject_teacher_teacher_id_359f8cce" ON "myapp_subject_teacher" ("teacher_id");

In this reading, you learned about the various types of relationships between models and how Django handles them.


Creating models

I’ve already created a project called menuproject and an app called menuapp. To build a model, I write code in the models.py file.

from django.db import models
 
# Create your models here.
 
class Menu(models.Model):
    name = models.CharField(max_length=100)
    cuisine = models.CharField(max_length=100)
    price = models.IntegerField()
python manage.py shell
>>> from menuapp.models import Menu
Traceback (most recent call last):
  File "<console>", line 1, in <module>
ImportError: cannot import name 'Menu' from 'menuapp.models' (S:\Django\test6\menuproject\menuapp\models.py)

It gives error because I have not added the model inside the settings.py file.

INSTALLED_APP = [
	'menuapp.apps.MenuappConfig',
	# other things ,
]

So once I’ve added the model to the installed apps list, I need to perform something called migrations.

python manage.py makemigrations

Notice that it creates a model, menu and a python file. If you go to the python file, you can see that django internally has created the columns and the table menu using the built in SQLite database.

python manage.py migrate

Now I’m ready to go back inside the shell and work with the menu model I created.

>>> from menuapp.models import Menu
>>> Menu.objects.all()
<QuerySet []>

Menu.objects.all() returns all the entries in the database.

>>> m = Menu.objects.create(name='pasta', cuisine='italian', price=10)
>>> Menu.objects.all()
<QuerySet [<Menu: Menu object (1)>]>
>>> n = Menu.objects.create(name='taco', cuisine='mexican', price=2)
>>> Menu.objects.all()
<QuerySet [<Menu: Menu object (1)>, <Menu: Menu object (2)>]>

Notice that there are two objects but this is not very informative as only the names of the menu objects one and two are displayed. Django provides a way to change this using something called custom methods.

For example, I can use one of the building custom methods and customize the string.

# models.py
 
from django.db import models
 
# Create your models here.
 
class Menu(models.Model):
    name = models.CharField(max_length=100)
    cuisine = models.CharField(max_length=100)
    price = models.IntegerField()
 
	def __str__(self):
		return self.name + " : " + self.cuisine
>>> from menuapp.models import Menu 
>>> Menu.objects.all()
<QuerySet [<Menu: pasta : italian>, <Menu: taco : mexican>]>

Now that I’ve demonstrated how to create a database table and add entries to it, let me demonstrate how to update entries.

Suppose I want to access an entry and update it. To do this, I can use the get method and pass the primary key of value to which refers to the entry of taco.

>>> p = Menu.objects.get(pk=2)
>>> p.cuisine = 'chinese'
>>> p.save()
>>> Menu.objects.all()
<QuerySet [<Menu: pasta : italian>, <Menu: taco : chinese>]>

Another option to view and update the database is using your browser to access the django admin portal. However, it’s also important to understand how the code works internally as I’ve demonstrated.


Migrations

In the world of web application development, application requirements constantly change and it’s a developer’s job to implement these changes. For example, a common task is to alter the data model of the application. To do this in Django, you use something called migrations.

Recall that Django is designed to work with a relational database management system like PostgreSQL, MySQL, or SQLite, and in a relational database, data is organized in tables.

By now, you know that you can use models to represent data tables stored in the database. Recall that when using models, Django provides many methods that allow you to add, update, and delete data right from the code without having to write SQL.

This is made possible by using something called an object-relational mapper, or ORM.

For now, just know that it maps the relational database to objects represented as Django models. The models define the database fields which correspond to the columns in their associated database tables.

For example, if you have a model called User, you know it will represent a table called User and each attribute of the model has a column representation in the database. So, in the Python code, if the user class has an attribute of first_name, the table will have a column of first_name.

It’s common that during the life cycle of the development of the application, changes to the structure of the database or schema will be required.

For example, the initial model may need to be extended with additional attributes. This means adding a new attribute, which will add a new column from the perspective of the database. Or, suppose you need to perform a different operation, like changing a column name or even deleting a model.

For these operations to work, Python uses something called database migrations.

Migration

Migrations are how Django records changes made to models and implements these changes to the database schema. Migrations are tied into Django models and stored as migration files in a Migrations folder inside each app.

How migration works

Suppose you wanted to add a new column called City to the User table. Without an ORM like the one Django provides, a developer must login to the database and run a SQL alter statement. You may recall that you can use the alter statements to alter a specific table to add a column of whatever type is required. When the statement runs, the User table updates with another column called City.

ALTER TABLE user
ADD COLUMN city VARCHAR(255) NOT NULL

In Django, the User table is created using a model which you may recall is a class-based representation of the User table in the database. So instead of writing the SQL query, you only need to add the new attribute to the model then run the migration scripts to implement the changes. Once the migration scripts run, the changes are applied.

class User(models.Model):
	first_name = models.CharField(max_length=30)
	last_name = models.CharField(max_length=30)
# Just adding an attribute
 
class User(models.Model):
	first_name = models.CharField(max_length=30)
	last_name = models.CharField(max_length=30)
	city = models.CharField(max_length=30)
manage.py makemigrations
manage.py migrate

Django provides a list of CLI commands that allows you to apply the migrations.

First, you must create a migration script and then apply the migrations.

The migration script is a set of instructions on what models to create against the database. You apply the change to the model, run the migration file, and Django will take care of the rest.

You do not need to write any SQL as the application handles everything.

Why use migrations instead of SQL

Migrations do more than just implement SQL commands. They can help with syncing issues, version control, and database maintenance. It’s good to think of migrations as a version control system for your database schema.

Syncing issues

With migrations, you have less possibility of syncing issues between what the model has and what the database contains.

When working in a team, each developer usually has their own local copy of the database so they can code and test against it. Migration scripts are kept in the code repository. When a developer on the team makes changes, they run the migration script to update their local copy of database to the latest version.

Version control

All the changes are kept in version control, which provides an entire history of changes across the application. Developers can also use this to determine what changes have been added and by whom. Generally in application development, the more you can track changes that occur in the app, the fewer problems you will encounter.

The alternative is running scripts in the database directly, which is usually more problematic as it is outside version control.

Ease of maintenance

Along with helping syncing issues and version control, maintaining all the database changes from the code base makes it easier for the development team. They don’t have to worry about creating SQL queries directly against the database or where to store these files so all the developers can run them.


How to use migrations

Django translates the models into respective database tables in the backend database with a mechanism known as migration. It also propagates any changes in the model structure such as adding, modifying or removing a field attribute of a model class to the mapped table.

Django’s migration system has the following commands:

  • makemigrations
  • migrate
  • sqlmigrate
  • showmigrations

Django’s migration is a version control system. Whenever you add a new model or effect changes in an existing model, you need to run the makemigrations command. It creates a script for making changes in the mapped table. Every time you run the makemigrations command and Django detects the changes, a script with its name and version number is created. To implement the changes according to the migration script, you need to run the migrate command.

Migrating Models of INSTALLED APPS

When you create a Django project with the startproject command, certain apps are installed by default. These apps are listed in the INSTALLED_APPS section in the project’s settings.py file.

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

Data needs to be stored via these apps for their functionality to work. For example, the auth package controls the users, groups, and permissions, so there must be corresponding tables created in the database. Django uses the SQLite database by default. For that purpose, you run the migrate command.

python manage.py migrate

Then, the tables required by the INSTALLED_APPS are created.

Let’s create an app inside our Django project.

(django) C:\django\myproject> python manage.py startapp myapp

This creates a myapp package folder inside the outer myproject folder. Inside myapp, a migrations package is also created, which is empty to begin with.

Using the makemigrations command

Open the models.py file and add a person model to it.

from django.db import models 
class Person(models.Model): 
    name = models.CharField(max_length=20) 
    email = models.EmailField() 
    phone = models.CharField(max_length=20) 

The first step towards creating the Person table in the database is to run the makemigrations command.

(django) C:\django\myproject>python manage.py makemigrations 
Migrations for 'myapp': 
  myapp\migrations\0001_initial.py 
    - Create model Person 

Notice that in the migrations package, a migration script 0001_initial.py, is created. It indicates what the script intends to do, which is: Create model Person.

If you open the migration file, you’ll find a migration class in it.

from django.db import migrations, models  
 
class Migration(migrations.Migration): 
 
    initial = True 
 
    dependencies = [ 
    ] 
 
    operations = [ 
        migrations.CreateModel( 
            name='Person', 
            fields=[ 
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 
                ('name', models.CharField(max_length=20)), 
                ('email', models.EmailField(max_length=254)), 
                ('phone', models.CharField(max_length=20)), 
            ], 
        ), 
    ] 

As mentioned above, you need to run the migrate command to apply the tasks in the migrations file to be performed.

(django) C:\django\myproject>python manage.py migrate        
Operations to perform: 
  Apply all migrations: admin, auth, contenttypes, myapp, sessions 
Running migrations: 
  Applying myapp.0001_initial... OK 

Have a look at the tables in your database db.sqlite3. The person table with three fields can be seen in it.

Version control

Now, let’s modify the person model class by changing the name field to Person_name and running makemigrations again.

(django) C:\django\myproject>python manage.py makemigrations 
Was person.name renamed to person.person_name (a CharField)? [y/N] y 
Migrations for 'myapp': 
  myapp\migrations\0002_rename_name_person_person_name.py 
    - Rename field name on person to person_name 

A second migration script is created in the migrations folder. Before finalizing the change, add a new field – age – in the person model and run makemigrations again.

(django) C:\django\myproject>python manage.py makemigrations 
Migrations for 'myapp': 
  myapp\migrations\0003_person_age.py 
    - Add field age to person 

Showmigrations command

Now there are two unmigrated changes in the model. Run the showmigrations command:

(django) C:\django\myproject>python manage.py showmigrations 
. . . 
. . . 
myapp 
[X] 0001_initial 
[ ] 0002_rename_name_person_person_name 
[ ] 0003_person_age 
. . . 

The initial migration (file numbered 0001) has already migrated. The X mark is indicative of this. However, the next two migrations don’t show the X mark, which means they are pending. If we run the migrate command, both modifications will be reflected in the table structure.

(django) C:\django\myproject>python manage.py migrate        
Operations to perform: 
  Apply all migrations: admin, auth, contenttypes, myapp, sessions 
Running migrations: 
  Applying myapp.0002_rename_name_person_person_name... OK 
  Applying myapp.0003_person_age... OK 

As mentioned earlier, Django’s migration mechanism provides efficient version control. You may want to fall back upon the table structure before adding the age field. Run the migrate command and specify which migration file to be used so that the migrations after it will be undone or unapplied.

(django) C:\django\myproject>python manage.py migrate myapp 0002_rename_name_person_person_name   
Operations to perform: 
  Target specific migration: 0002_rename_name_person_person_name, from myapp 
Running migrations: 
  Rendering model states... DONE 
  Unapplying myapp.0003_person_age... OK 

sqlmigrate Command

Lastly, the sqlmigrate command shows the SQL query or queries executed when a certain migration script is run. For example, the first migration over the myapp’s person model is intended to create the person table. The sqlmigrate command for this script shows the CREATE TABLE statement for this purpose.

(django) C:\django\myproject>python manage.py sqlmigrate myapp 0001_initial  
BEGIN; 
-- 
-- Create model Person 
-- 
CREATE TABLE "myapp_person" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(20) NOT NULL, "email" varchar(254) NOT NULL, "phone" varchar(20) NOT NULL); 
COMMIT; 

In this reading, you learned about when to use migrations, best practices and that the migration system in Django manages data creation and modification very effectively and efficiently.


Working with Migrations

I’ve already created a project in VS code containing an app called myapp.

Inside the models.py file, I’ve created a class called Menuitems. Recall that this class is equivalent to a table in SQL and contains three attributes, name, course, and year with respective fields.

# models.py
class Menuitems(models.Model):
	name = models.CharField(max_length=200)
	course = models.CharField(max_length=300)
	year = models.IntegerField()
python manage.py makemigrations
python manage.py migrate

The difference

  • makemigrations is Django’s way of preparing the changes to be made in the model. For example, it creates a database such as this one named db.sqlite3. So you can think of makemigrations as the command that generates the SQL commands
  • and migrate as the command where those SQL commands are executed.

Once I’ve run this command, notice that a folder called migrations is created inside the app folder, which consists of the file that keeps a record of the migrations. If I open the file, notice that the code contains the table columns, names, and auto assigned primary key called ID.

Changing the model

What if I want to make some changes to the model? For example, suppose I want to change the attribute course to category and save this file.

For this change to take effect,I have to run the makemigrations command again. Notice that Django displays a message that asks, “Was menuitem.course renamed to menuitem.category?” To confirm, I type yes and press “Enter”.

Now, notice that another file is created inside the migrations folder. If I open that file, notice that it contains the code changes performed in that migration.

Display all migrations performed

To do this, I can run the command python manages.py and then show migrations.

The X symbol represents the commits to the migrations.

Revert back

I can do this by typing python manage.py migrate, then I need to specify the name of the app and the final number, for example, 0001.

But before I execute, I can add a flag such as plan. Django will then display the changes it will revert to.

This time, I execute this command without the plan flag. And notice that even if the changes are not displayed in the Python file, the changes have been reflected in the database I created.

sqlmigrate

I can run this command against some migrated version. Notice that when I execute it, the corresponding sql commands are displayed for the migrations I have performed.


A history of changes

Every model created in the project provides a full history of how it was created, when it was added, and any changes that have occurred. Web application development requirements tend to change quite a bit in small increments.

For example, adding an extra attribute to a model or changing an attribute name. Additionally, there are occasions when multiple users use more than one database and migrations ensure schema changes are applied and updated on each database.

In Django, a developer must create the change directly in the model, then apply the migration by using the migration scripts.

Another important advantage of using migrations is to avoid repetition. Once you create some model, writing SQL queries to create corresponding databases for it is repetitive. Migrations generated from the models help prevent duplication of efforts. This is in line with one of the core principles of Django, don’t repeat yourself (DRY).

How django keeps history

It all begins with the file structure, migration files are stored in a migration folder. If you expand the migrations folder, notice that it automatically updates the file system after the migration has run. You can explore the details of the migrations using the show migrations command.

Django automatically generates the file names in line with the actions performed in the given migrations or the timestamp.

The X symbol represents the status of applying migrations after making them.

After applying migrations, Django does not apply a new migration to the same database again, unless it detects changes.

Django migration table

Behind the scenes, Django creates a new table called Django migrations. With reference to the migration files.

A new row is inserted after each migration tracking the changes, apps and the timestamp of the migration.

  • The first column is the ID and this is auto-incremented based on the position in the table.
  • The second column is the app that the migration is associated to. Recall that in Django, you can have multiple applications inside the project.
  • The third column is the name and refers to the file name of the migration.
  • The final column is the timestamp of when the migration was applied.

Every time a migration script is run, this table is updated with the latest changes. This also means that it’s checked prior to the migration script running. This is done so that it knows which scripts have been run and which ones need to be applied.

Migrations can also be applied to a specific app by placing the app name after that make migrations command.

manage.py makemigrations <appname>

Contents of a migration file

Migration files are basically Python code. Inside the migrations class, it will typically contain two important sequences or list items, such as dependencies and operations.

  • Dependencies refer to the previous migration, that must be applied before this.
  • Operations refer to actions performed in the given migration. Some of the commonly used operations are:
    • CreateModel, which creates a model and drops a table.
    • DeleteModel, which deletes a model and drops a table.
    • AddField, which adds a database column and field definition creations are additions.
    • AlterField, which creates a database column and field definition changes.
    • AddIndex, which creates a database index.

So for example, an operation such as DeleteModel will generate a corresponding SQL query such as DELELETE TABLE Customers.

migration.DeleteModel("Customers"),
DELETE TABLE Customers

Models using Foreign Keys

Foreign keys

In Django, foreign key is an ORM field representing a database table column. You use it to create the relationships between tables in your database.

Imagine the scenario of building an online menu for the Little Lemon restaurant. The solution begins with creating a table to add the menu items for the web page.

Now, Little Lemon is a restaurant that offers many cuisines. In placing each cuisine item in its correct category requires some skill. To do this successfully, start by creating another model for categories and assign the different categories to each of the menu items.

Let’s begin by selecting the models.py file. Here, you need to create two models. The first model is for the menu category and the second model is for the menu. Later you will add subcategories in the menu category and use the foreign key to reference these categories.

It’s important to know that adding both models is done the same way as adding models independently.

Write the models.ForeignKey command in the category_id. Inside the foreign key are two required fields.

from django.db import models
 
# Create your models here.
 
class MenuCategory(models.Model):
    menu_category_name = models.CharField(max_length=200)
 
class Menu(models.Model):
    menu_item = models.CharField(max_length=200)
    price = models.IntegerField(null=False)
    category_id = models.ForeignKey(MenuCategory, on_delete=models.PROTECT(, default=None))

The first field is the class for the model to connect to. And the second field is the settings that enables on delete. So it’s defined as on delete equal to models.PROTECT. Additionally, add another argument called default=none.

The next step includes updating the admin.py file by actioning the required imports. Next, it’s time to register the models. Note, that the utility becomes more evident later in the Django admin lesson.

# admin.py
 
from django.contrib import admin
from .models import Menu
from .models import MenuCategory
 
# Register your models here.
 
admin.site.register(Menu)
admin.site.register(MenuCategory)

For now, ensure that the settings.py file in the app configuration is updated.

Go ahead and run the server, followed by performing the migrations. Notice that the output includes two models that are migrated.

Finally, run the command python3 manage.py migrate.

The next step is to check the database. So go to db.sqlite3, Right Click and select Open Database, scroll down to SQLITE EXPLORER and select myapp_menu. For this example, assume that there are entries added to the database.

Notice that there is a fourth field called category_id_id. This field has ID numbers that match the respective categories in the myapp_menu table.

Now, let’s make this more intuitive by adding another attribute called related_name= and assign it the valued labeled category_name.

class Menu(models.Model):
    menu_item = models.CharField(max_length=200)
    price = models.IntegerField(null=False)
    category_id = models.ForeignKey(MenuCategory, on_delete=models.PROTECT, default=None, related_name="category_name")

This means that the myapp_menu now lists the category name instead of category_id_id.

This is noticeable when running migrations again.

The failed category on the menu is altered. Notice that with on_delete=models.PROTECT selected, no menu items will be removed with any category that is deleted in the category table.


Object Relationship Mapping - ORM

What is ORM 

Usually, the type of system used in an Object-Oriented language such as Python contains types that are non-scalar and every time, they cannot be represented only in primitive types such as integers and strings.

On the other hand, data types in tables of a relational database are of primary types, that is integer and string.

Therefore, the program needs to convert objects in scalar data to interact with a backend database.

Object-oriented programming languages use Object Relation Mapping (ORM) to interact with Structured Query Language (SQL), both of which are incompatible.

The ORM layer maps a class to a table in a relational database, with its attributes matching the table’s field structure.

The ORM library lets you perform the database operations in an object-oriented way instead of executing raw SQL queries.

This allows you to focus more on the programming logic than the backend database operations.

Object Relational Mapping or ORM is the ability to create a SQL query using object-oriented programming language such as Python. This enables a quick turnaround time in fast production environments that need constant updates. 

Each model is a Python class that subclasses django.db.models.Model

Each attribute of the model represents a database field. Essentially you can think of a model as a Python object, and models help developers create, read, update and delete objects, commonly called the CRUD operations.

Django has its own ORM layer. Its migration mechanism propagates the models in database tables.

You need to construct a QuerySet via a Manager of your model class to retrieve objects from your database.

QuerySet

Let’s try to understand how QuerySet is constructed and handled with an example. To begin with, add the following models in the Django app named demoapp.

from django.db import models  
class Customer(models.Model): 
    name = models.CharField(max_length=255) 
 
class Vehicle(models.Model): 
    name = models.CharField(max_length=255) 
    customer = models.ForeignKey( 
        Customer, 
        on_delete=models.CASCADE, 
        related_name='Vehicle' 
    ) 

The idea is to create two tables, Customer and Vehicle, with a one-to-many relationship. Apply the migrations and you will see these two tables in the project’s database.

Next, open the Interactive shell.

It is important to know that these commands are the same if you are on Windows or Mac.

(djenv) C:\djenv\demoproject>python manage.py shell 

Create an object of the Customer class. Give a string parameter to the constructor. The save() method of the models.Model class creates a row in the Customer table.

>>> from demoapp.models import Customer 
>>> c=Customer(name="Henry") 
>>> c.save() 

Adding a model object

The Customer.objects gives the Manager of the model. It handles all the CRUD operations on the database table. For example, an object can also be created with the create() method as follows:

>>> Customer.objects.create(name="Hameed") 
<Customer: Customer object (2)> 

The create() method actually performs the INSERT operation of SQL.

Now, let us add two objects to the Vehicle model. Note that one of the fields in the Vehicle model refers to an object of the Customer table as ForeignKey. Fetch the Customer object with a primary key = 2.

>>> from demoapp.models import Customer, Vehicle  
>>> c=Customer.objects.get(pk=2) 
>>> c.name 
'Hameed' 

This object is used as the value of the customer attribute in the Vehicle object.

>>> v=Vehicle(name="Honda", customer=c) 
>>> v.save() 
>>> v=Vehicle(name="Toyota", customer=c)   
>>> v.save() 

Similarly, add two vehicles for Customer “Henry”.

>>> c=Customer.objects.get(name="Henry") 
>>> Vehicle.objects.create(name="Ford", customer=c) 
<Vehicle: Vehicle object (3)> 
>>> Vehicle.objects.create(name="Nissan", customer=c) 
<Vehicle: Vehicle object (4)> 

Fetch model objects

A QuerySet represents a collection of objects from your database. It represents a SELECT query. To fetch all the objects, use the all() method of the Manager.

model.objects.all()

The all() method returns a list of objects. You can iterate over it with the usual for loop or list comprehension technique.

>> lst=Customer.objects.all()  
>>> [c.name for c in lst] 
['Henry', 'Hameed'] 

You can apply filters to the data fetched from the model. This is used to fetch objects satisfying the given criteria. In SQL terms, a QuerySet equates to a SELECT statement, it is like applying a WHERE clause.

model.objects.filter(criteria)

For example, use the following statement to retrieve all the customers with names starting with ‘H’.

mydata = Customer.objects.filter(name__startswith='H') 

Similarly, you can retrieve the objects of the Vehicle model. Remember that the Vehicle object refers to a customer object. You can retrieve the attributes of the related customer as follows:

>>> lst=Vehicle.objects.all() 
>>> for v in lst: 
...     print (v.name, " : ", v.customer.name) 
...  
Honda :  Hameed 
Toyota :  Hameed 
Ford :  Henry 
Nissan :  Henry 

Updating and removing a model object

To update an object, such as changing the Customer’s name from Henry to Helen, assign a new value to the name attribute and save.

>>> c=Customer.objects.get(name="Henry") 
>>> c.name="Helen" 
>>> c.save() 

Similarly, the delete() method of the model manager physically removes the corresponding row in the model’s mapped table.

>>> c=Customer.objects.get(pk=4) 
>>> c.delete() 
(1, {'demoapp.Customer': 1}) 

This way, you can perform the CRUD operations on a database table using the QuerySet API instead of executing raw SQL queries.


Using ORM

When building web applications, developers use databases to store data and write sequel queries to fetch and manipulate that data as applications grow in size and complexity. So too can the amount and complexity of the sequel queries. To assist developers, Django provides a feature known as object relational mapping, or ORM, that automatically creates the required sequel queries.

ORM

Object relational mapping is an abstraction layer created to facilitate interaction between the programming language and databases. This allows programmers to use the sequel databases without needing to write sequel queries. A structured map is created internally by ORM, that generates sequel code for a given relational database, by observing the changes made to the data objects. ORM can be accessed by using the shell command.

QuerySet

Previously you may have noticed how each row entry added for a given model creates an object, you may recall the output had the prefects query set.

QuerySet

A query set is a collection of such objects. For a given model used in Django and Django uses a query set to retrieve and manipulate these objects from the database.

For example, suppose you have a model called customers. You can access the contents of this model by running the command:

Customers.objects.all()

This command returns the output as a query set. The list will contain the entries for the different objects corresponding to the row entries in a database.

For every sequel query that can be constructed, there is a corresponding command. These commands are a part of the QuerySet API.

So for example, if you want to join multiple parameters using the WHERE or limit clauses in sequel you can use the filter function and pass the conditions to it as arguments. This will select the entry for customers who have reserved a table for four people.

Customers.objects.filter(seats=4)

Django functions for queries

Considering the variety of complex queries that can be constructed in sequel, Django has several functions that support these operations. They can be categorized as follows based on the required logic to run on the query set:

  • methods that return new query sets
  • operators that return new query sets
  • methods that do not return query sets

WARNING

User double quotes for string you use in the filter function.

Customers.objects.Filter('condition') & Customers.objects.Filter('another condition')

Additional resources

The following resources will be helpful you learn more about different concepts related to Models and Migrations.

Models – official documentation

Migrations – official documentation

Other Model Instance methods (including the String Dunder method)

Using Models – Mozilla

Detailed overview of Migrations

Migration operations

Understanding ORM


PythonDjangoDatabaseOOPframework

Previous one → 7.Creating URLs and Views | Next one → 9.Models and Forms