How To Create Django Models
How To Create Django Models

Tutorial

How To Create Django Models

PythonDjangoDevelopmentProgramming ProjectUbuntu 20.04

Introduction

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.”

Prerequisites

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:

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 blogsite directory.

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 blogsite directory.

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

Use the 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 models.py file.

models.py
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 reverse from django.urls to give us greater flexibility with creating URLs.

models.py
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, title, slug, content, created_on and author. Add these below your import statements.

models.py
...
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.

models.py
...
    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.

models.py
...
    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 Comment with models.Models in its signature and the following database fields defined:

  • name — The name of the person posting the comment.
  • email — The email address 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.
models.py
...
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:

models.py
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 CTRL and X, then Y, then ENTER.

With the models.py file set up, we can go on to update our settings.py file.

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 settings.py.

Navigate to the directory where your settings.py lives.

  • 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.

settings.py
# 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 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 Post and 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 manage.py.

  • python manage.py makemigrations

You should then receive 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 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: __pycache__ and 0001_initial.py. The 0001_initial.py file was automatically generated when you ran makemigrations. A similar file will be generated every time you run makemigrations.

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 ~/my_blog_app/blog:

  • 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 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’ll notice that all migrations are checked except for the one for 0001_initial which we just created with the models Post and Comment.

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:

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 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 withSHOW 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_comment and 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 CTRL + D and when you are ready to leave your Python environment, you can run the deactivate command:

  • deactivate

Deactivating your programming environment will put you back to the terminal command prompt.

Conclusion

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

Creative Commons License