How to create Django models

0 Shares
0
0
0
0

Introduction

In this tutorial, we'll create Django models that define the fields and behaviors of the blog application data that we'll be storing. These models map your Django application data to the database. This is what Django uses to generate database tables through the Object Relational Mapping (ORM) API, called "models.".

Prerequisites

This tutorial is part of the Django Development series and is a continuation of that series.

If you haven't been following this series, we make the following assumptions:

  • You have installed Django version 4 or higher.
  • You have connected your Django application to a database.
  • You are working with a Unix-based operating system, preferably an Ubuntu 22.04 cloud server as this is the system we have tested on.

Since this tutorial mainly deals with Django models, you may be able to follow along even if you have a different setup.

Step 1 – Create the Django application

To keep with Django's modular philosophy, we'll create a Django application in our project that contains all the files necessary to create the blog website.

Whenever we start working in Python and Django, we need to activate our Python virtual environment and move it to the root directory of our application. If you followed along with the series, you can achieve this by typing the following.

cd ~/my_blog_app
. env/bin/activate
cd blogr code... */

From there, let's run this command:

python manage.py startapp blogsite

This program creates our blog site along with a directory.

At this point in the tutorial series, you will 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 located in the blog directory of the site.

Step 2 – Add Post Templates

First, we need to open and edit the models.py file to contain the code to generate a Post model. A Post model contains the following database fields:

  • TITLE – The title of the blog post.
  • Slug – Where valid URLs for web pages are stored and generated.
  • CONTENT – The textual content of the blog post.
  • create_on – The date the post was created.
  • AUTHOR – The person who wrote the post.

Now go to the directory where the models.py file is located.

cd ~/my_blog_app/blog/blogsite

Use the cat command to display the contents of the file in your terminal.

cat models.py

The file should contain the following code that imports the models, along with a comment explaining what goes into this models.py file.

from django.db import models
# Create your models here.

Using your favorite text editor, add the following code to the models.py file. We will use nano as our text editor, but you can use whatever you prefer.

nano models.py

In this file, the code to import the API models has already been added, we can go ahead and remove the comment below. We then import slugify to generate slugs from strings, the Django user for authentication, and the reverse from django.urls to give us more flexibility in 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

Next, add a class method with the following database fields, title, slug, content, created_on, and author, on the model class we'll call Post. 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 add a function to generate the URL and a post save function. This is very important, as it 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 sorted and displayed on the web page. The logic for this will be added to a nested meta class. The meta class generally contains other important model logic that is not related to the database field definition.

...
class Meta:
ordering = ['created_on']

def __unicode__(self):
return self.title

Finally, we add the Comment model to this file. This involves adding another class called Comment with models.Models in its signature and defining the following database fields:

  • NAME – The name of the person submitting the comment.
  • EMAIL – The email address of the person submitting the comment.
  • TEXT – The text of the comment itself.
  • POST – The post with which the comment was made.
  • create_on – When 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. Make sure 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 this by typing CTRL and X, then Y, then ENTER.

With the models.py file up and running, we can update our settings.py file.

Step 3 – Update Settings

Now that we have added models to our application, we need to inform our project about the existence of the blogsite application we just added. We do this by adding it to the INSTALLED_APPS section of settings.py.

Go to the directory where your settings.py lives.

cd ~/my_blog_app/blog/blog

From here, open your settings.py file with nano for example.

nano settings.py

By opening the file, add your site's blog app to the INSTALLED_APPS section of the file, as shown below.

# Application definition
INSTALLED_APPS = [
'blogsite',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]

With the site's blog application added, you can save the file and exit.

At this point, we are ready to move forward with implementing these changes.

Step 4 – Perform migration

With the Post and Comment models added, the next step is to apply these changes so that our MySQL database schema recognizes them and creates the necessary tables.

First, we need to package our model changes into separate migration files using the makemigrations command. These files are similar to commits in a version control system like Git.

Now, if you go to ~/my_blog_app/blog/blogsite/migrations and run ls , you will notice that there is only one __init__.py file. This will change when we add migrations.

Change to the blog directory using cd , like so:

cd ~/my_blog_app/blog

Then run the makemigrations command in manager.py.

python manage.py makemigrations

You should then get the following output in your terminal window:

Output
Migrations for 'blogsite':
blogsite/migrations/0001_initial.py
- Create model Post
- Create model Comment

Remember when we went to /~/my_blog_app/blog/blogsite/migrations and it only had the __init__.py file? If we go back to that directory now, we'll notice that two more items have been added: __pycache__ and 0001_initial.py. The 0001_initial.py file was automatically created when we ran makemigrations. A similar file will be created every time you run makemigrations.

If you want to read the contents of the file, simply run 0001_initial.py from the directory it is located in.

Now go to ~/my_blog_app/blog:

cd ~/my_blog_app/blog

Since we have created a migration file, we need to apply the changes that these files describe to the database using the migrate command. But first, let's check which migrations currently exist using the showmigrations command.

python manage.py showmigrations
Output
admin
[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 will notice that all migrations are checked, except for the one for 0001_initial that we created with the Post and Comment models.

Now let's check which SQL statements are executed after the migrations are done using the following command. It takes the migration and the migration title as an argument:

python manage.py sqlmigrate blogsite 0001_initial

Below is shown the actual SQL query being performed 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 run the migrations to apply them to our MySQL database.

python manage.py migrate

We will get the following output:

Output
Operations 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, as stated in the Django documentation, there are three caveats to migrating Django with MySQL as the backend.

  • No support for transactions around schema change operations. In other words, if a migration fails, you have to manually select the changes you made to perform another migration. There is no way to roll back to a previous point before making any changes to the failed migration.
  • For most schema change operations, MySQL completely rewrites tables. In the worst case, the time complexity is proportional to the number of rows in the table to add or remove columns. According to the Django documentation, this can be as slow as one minute per million rows.
  • In MySQL, there are small limits on the length of names for columns, tables, and indexes. There is also a limit on the combined size of all columns and index volumes. While some other backends can support the higher limits created in Django, the same indexes cannot be created with a MySQL backend.

For any database you consider for use with Django, be sure to weigh the pros and cons of each.

Step 5 – Verify the database schema

With the migration complete, we need to verify the successful generation of the MySQL tables we created through the Django models.

To do this, run the following command in the terminal to log in to MySQL.

mysql blog_data -u djangouser

Now, select our blog_data database. If you don't know which 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)

The tables include blogsite_comment and blogsite_post. These are models we created ourselves. Let's verify that they contain the fields we 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 have been successfully generated from our Django model migrations.

You can close MySQL with CTRL + D and when you are ready to exit your Python environment, you can run the disable command:

deactivate

Disabling the development environment returns you to the terminal command line.

Result

In this tutorial, we have successfully added models for basic functionality in a blog web application. You have learned how to code the models, how migrations work, and the process of translating Django models into real MySQL database tables.

Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like