How To Create Django Models
In the previous tutorial, “How To Create a Django App and Connect it to a Database,” we covered how to create a MySQL database, how to create and start a Django application, and how to connect it to a MySQL database.
In this tutorial, we will create the Django models that define the fields and behaviors of the Blog application data that we will be storing. These models map the data from your Django application to the database. It’s what Django uses to generate the database tables via their object relational mapping (ORM) API, referred to as “models.”
This tutorial is part of the Django Development series and is a continuation of that series.
If you have not followed along with this series, we are making the following assumptions:
- You have Django version 3 or higher installed.
- You have connected your Django app to a database. We are using MySQL, and you can achieve this connection by following part two of the Django series, “How To Create a Django App and Connect it to a Database.”
- You are working with a Unix-based operating system, preferably an Ubuntu 20.04 cloud server as this is the system we have tested on. If you would like to set up Django on a similar environment, please refer to our tutorial, “How To Install Django and Set Up a Development Environment on Ubuntu 20.04.”
As this tutorial is largely dealing with Django models, you may be able to follow along even if you have a somewhat different setup.
Step 1 — Create Django Application
To be consistent with the Django philosophy of modularity, we will create a Django app within our project that contains all of the files necessary for creating the blog website.
Whenever we begin doing work in Python and Django, we should activate our Python virtual environment and move into our app’s root directory. If you followed along with the series, you can achieve this by typing the following.
- cd ~/my_blog_app
- . env/bin/activate
- cd blog
From there, let’s run this command:
- python manage.py startapp blogsite
This will create our app along with a
At this point in the tutorial series, you’ll have the following directory structure for your project:
my_blog_app/ └── blog ├── blog │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── urls.cpython-38.pyc │ │ └── wsgi.cpython-38.pyc │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── blogsite │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── manage.py
The file we will focus on for this tutorial, will be the
models.py file, which is in the
Step 2 — Add the Posts Model
First we need to open and edit the
models.py file so that it contains the code for generating a
Post model. A
Post model contains the following database fields:
title— The title of the blog post.
slug— Where valid URLs are stored and generated for web pages.
content— The textual content of the blog post.
created_on— The date on which the post was created.
author— The person who has written the post.
Now, move into the directory where the
models.py file is contained.
- cd ~/my_blog_app/blog/blogsite
cat command to show the contents of the file in your terminal.
- cat models.py
The file should have the following code, which imports models, along with a comment describing what is to be placed into this
from django.db import models # Create your models here.
Using your favorite text editor, add the following code to the
models.py file. We’ll use
nano as our text editor, but you are welcome to use whatever you prefer.
- nano models.py
Within this file, the code to import the models API is already added, we can go ahead and delete the comment that follows. Then we’ll import
slugify for generating slugs from strings, Django’s
User for authentication, and
django.urls to give us greater flexibility with creating URLs.
from django.db import models from django.template.defaultfilters import slugify from django.contrib.auth.models import User from django.urls import reverse
Then, add the class method on the model class we will be calling
Post, with the following database fields,
author. Add these below your import statements.
... class Post(models.Model): title = models.CharField(max_length=255) slug = models.SlugField(unique=True, max_length=255) content = models.TextField() created_on = models.DateTimeField(auto_now_add=True) author = models.TextField()
Next, we will add functionality for the generation of the URL and the function for saving the post. This is crucial, because this creates a unique link to match our unique post.
... def get_absolute_url(self): return reverse('blog_post_detail', args=[self.slug]) def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.title) super(Post, self).save(*args, **kwargs)
Now, we need to tell the model how the posts should be ordered, and displayed on the web page. The logic for this will be added to a nested inner
Meta class. The
Meta class generally contains other important model logic that isn’t related to database field definition.
... class Meta: ordering = ['created_on'] def __unicode__(self): return self.title
Finally, we will add the
Comment model to this file. This involves adding another class named
models.Models in its signature and the following database fields defined:
name— The name of the person posting the comment.
text— The text of the comment itself.
post— The post with which the comment was made.
created_on— The time the comment was created.
... class Comment(models.Model): name = models.CharField(max_length=42) email = models.EmailField(max_length=75) website = models.URLField(max_length=200, null=True, blank=True) content = models.TextField() post = models.ForeignKey(Post, on_delete=models.CASCADE) created_on = models.DateTimeField(auto_now_add=True)
At this point
models.py will be complete. Ensure that your
models.py file matches the following:
from django.db import models from django.template.defaultfilters import slugify from django.contrib.auth.models import User from django.urls import reverse class Post(models.Model): title = models.CharField(max_length=255) slug = models.SlugField(unique=True, max_length=255) content = models.TextField() created_on = models.DateTimeField(auto_now_add=True) author = models.TextField() def get_absolute_url(self): return reverse('blog_post_detail', args=[self.slug]) def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.title) super(Post, self).save(*args, **kwargs) class Meta: ordering = ['created_on'] def __unicode__(self): return self.title class Comment(models.Model): name = models.CharField(max_length=42) email = models.EmailField(max_length=75) website = models.URLField(max_length=200, null=True, blank=True) content = models.TextField() post = models.ForeignKey(Post, on_delete=models.CASCADE) created_on = models.DateTimeField(auto_now_add=True)
Be sure to save and close the file. If you’re using nano, you can do so by typing
models.py file set up, we can go on to update our
Step 3 — Update Settings
Now that we’ve added models to our application, we must inform our project of the existence of the
blogsite app that we’ve just added. We do this by adding it to the
INSTALLED_APPS section in
Navigate to the directory where your
- cd ~/my_blog_app/blog/blog
From here, open up your
settings.py file, with nano, for instance.
- nano settings.py
With the file open, add your
blogsite app to the
INSTALLED_APPS section of the file, as indicated below.
# Application definition INSTALLED_APPS = [ 'blogsite', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
blogsite app added, you can save and exit the file.
At this point, we are ready to move on to apply these changes.
Step 4 — Make Migrations
With our models
Comment added, the next step is to apply these changes so that our
MySQL database schema recognizes them and creates the necessary tables.
First, we must package up our model changes into individual migration files using the command
makemigrations. These files are similar to that of
commits in a version control system like Git.
Now, if you navigate to
~/my_blog_app/blog/blogsite/migrations and run
ls, you’ll notice that there is only an
__init__.py file. This will change once we add the migrations.
Change to the blog directory using
cd, like so:
- cd ~/my_blog_app/blog
Then run the
makemigrations command on
- python manage.py makemigrations
You should then receive the following output in your terminal window:
OutputMigrations for 'blogsite': blogsite/migrations/0001_initial.py - Create model Post - Create model Comment
Remember, when we navigated to
/~/my_blog_app/blog/blogsite/migrations and it only had the
__init__.py file? If we now
cd back to that directory we’ll notice that two items have been added:
0001_initial.py file was automatically generated when you ran
makemigrations. A similar file will be generated every time you run
less 0001_initial.py from the directory it’s in if you’d like to read over what the file contains.
Now navigate to
- cd ~/my_blog_app/blog
Since we have made a migration file, we must apply the changes these files describe to the database using the command
migrate. But first let’s check which migrations currently exist, using the
- python manage.py showmigrations
Outputadmin [X] 0001_initial [X] 0002_logentry_remove_auto_add [X] 0003_logentry_add_action_flag_choices auth [X] 0001_initial [X] 0002_alter_permission_name_max_length [X] 0003_alter_user_email_max_length [X] 0004_alter_user_username_opts [X] 0005_alter_user_last_login_null [X] 0006_require_contenttypes_0002 [X] 0007_alter_validators_add_error_messages [X] 0008_alter_user_username_max_length [X] 0009_alter_user_last_name_max_length [X] 0010_alter_group_name_max_length [X] 0011_update_proxy_permissions blogsite [ ] 0001_initial contenttypes [X] 0001_initial [X] 0002_remove_content_type_name sessions [X] 0001_initial
You’ll notice that all migrations are checked except for the one for
0001_initial which we just created with the models
Now let’s check which
SQL statements will be executed once we make the migrations, using the following command. It takes in the migration and the migration’s title as an argument:
- python manage.py sqlmigrate blogsite 0001_initial
Revealed below is the actual SQL query being made behind the scenes.
Output-- -- Create model Post -- CREATE TABLE `blogsite_post` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `title` varchar(255) NOT NULL, `slug` varchar(255) NOT NULL UNIQUE, `content` longtext NOT NULL, `created_on` datetime(6) NOT NULL, `author` longtext NOT NULL); -- -- Create model Comment -- CREATE TABLE `blogsite_comment` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(42) NOT NULL, `email` varchar(75) NOT NULL, `website` varchar(200) NULL, `content` longtext NOT NULL, `created_on` datetime(6) NOT NULL, `post_id` integer NOT NULL); ALTER TABLE `blogsite_comment` ADD CONSTRAINT `blogsite_comment_post_id_de248bfe_fk_blogsite_post_id` FOREIGN KEY (`post_id`) REFERENCES `blogsite_post` (`id`);
Let’s now perform the migrations so that they get applied to our MySQL database.
- python manage.py migrate
We will receive the following output:
OutputOperations to perform: Apply all migrations: admin, auth, blogsite, contenttypes, sessions Running migrations: Applying blogsite.0001_initial... OK
You have now successfully applied your migrations.
It is important to keep in mind that there are 3 caveats to Django migrations with MySQL as your backend, as stated in the Django documentation.
- Lack of support for transactions around schema alteration operations. In other words, if a migration fails to apply successfully, you will have to manually unpick the changes you’ve made in order to attempt another migration. It is not possible to rollback, to an earlier point, before any changes were made in the failed migration.
- For most schema alteration operations, MySQL will fully rewrite tables. In the worst case, the time complexity will be proportional to the number of rows in the table to add or remove columns. According to the Django documentation, this could be as slow as one minute per million rows.
- In MySQL, there are small limits on name lengths for columns, tables and indices. There is also a limit on the combined size of all columns and index covers. While some other backends can support higher limits created in Django, the same indices will fail to be created with a MySQL backend in place.
For each database you consider for use with Django, be sure to weigh the advantages and disadvantages of each.
Step 5 — Verify Database Schema
With migrations complete, we should verify the successful generation of the MySQL tables that we’ve created via our Django models.
To do this, run the following command in the terminal to log into MySQL. We’ll use the
djangouser we created in the previous tutorial.
- mysql blog_data -u djangouser
Now, select our database
blog_data. If you don’t know the database you are using, you can show all databases with
SHOW DATABASES; in SQL.
- USE blog_data;
Then type the following command to view the tables.
- SHOW TABLES;
This SQL query should reveal the following:
Output+----------------------------+ | Tables_in_blog_data | +----------------------------+ | auth_group | | auth_group_permissions | | auth_permission | | auth_user | | auth_user_groups | | auth_user_user_permissions | | blogsite_comment | | blogsite_post | | django_admin_log | | django_content_type | | django_migrations | | django_session | +----------------------------+ 12 rows in set (0.01 sec)
Among the tables are
blogsite_post. These are the models that we’ve just made ourselves. Let’s validate that they contain the fields we’ve defined.
- DESCRIBE blogsite_comment;
Output+------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int | NO | PRI | NULL | auto_increment | | name | varchar(42) | NO | | NULL | | | email | varchar(75) | NO | | NULL | | | website | varchar(200) | YES | | NULL | | | content | longtext | NO | | NULL | | | created_on | datetime(6) | NO | | NULL | | | post_id | int | NO | MUL | NULL | | +------------+--------------+------+-----+---------+----------------+ 7 rows in set (0.00 sec)
- DESCRIBE blogsite_post;
Output+------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int | NO | PRI | NULL | auto_increment | | title | varchar(255) | NO | | NULL | | | slug | varchar(255) | NO | UNI | NULL | | | content | longtext | NO | | NULL | | | created_on | datetime(6) | NO | | NULL | | | author | longtext | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 6 rows in set (0.00 sec)
We have verified that the database tables were successfully generated from our Django model migrations.
You can close out of MySQL with
D and when you are ready to leave your Python environment, you can run the
Deactivating your programming environment will put you back to the terminal command prompt.
In this tutorial, we’ve successfully added models for basic functionality in a blog web application. You’ve learned how to code
migrations work and the process of translating Django
models to actual
MySQL database tables.