Databases

Django comes with an ORM – Object Relational Mapper. It allows us to define Python classes instead of database tables and schemas, and interact with the database just like we’d interact with regular code.

Models

Now we can go ahead and start defining our own custom models.

Creating Django models allow us to define our database tables and relationships from Python, and interact with them just like we would interact with Python objects.

I’ve already created a model for Post. Open practical_blog/blog/models.py:

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=300)
    body = models.TextField()
    slug = models.SlugField(null=False, unique=True, max_length=200) 
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
      return self.title

Creating app-specific migrations

In order to create the database tables for our custom models, we’ll need to create new migrations for them.

You can think of migrations as a map between user-defined models in code and database tables. The migrations instruct the database on what commands need to be run to either update existing tables as models change, create new tables, or drop tables as the models are deleted.

In order for our custom model to be registered by Django, we’ll need to do two things: 1. Register the app in our project, so that Django knows where to look for models 1. Run a command to make new migrations before running them, so that Django can create database scripts for our custom models.

First, let’s register our app.

If we take a look at practical_blog/blog/apps.py file, we’ll see the following auto-generated contents:

from django.apps import AppConfig


class BlogConfig(AppConfig):
    name = 'blog'

We need to add this class, BlogConfig, in the blog.apps module to the INSTALLED_APPS list in our practical_blog/settings.py file.

Your practical_blog/blog/settings.py file should now look like this:

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

Migrations

Every time we change our models, the database needs to be changed with it.

Instructions for how to make those changes for all the databases that Django supports (Postgres, MySQL, Sqlite) are called migrations.

When you make changes to your models, you’ll need to create new instructions with the makemigrations command, and then run migrate to apply them to the database.

I’ve already included the initial migrations for our blog app in the repository.

We don’t have any changes at the moment, but if you need to run migrations in the future, run:

(env) $ python manage.py makemigrations blog
(env) $ python manage.py migrate
Comfortable with SQL? You can see the raw SQL Django generates under the hood

Working with Models

We can easily work with models on the command line, with the built-in Django shell command. The shell is different from the Python REPL because it knows about Django context when it starts up.

Start the Django shell with:

(env) $ python manage.py shell 

Then, import our Post model. We’ll create a new Post, and save it in the database.

>>> from blog.models import Post
>>> Post.objects.all()
<QuerySet []>
>>> first_post = Post(title="First Post!", body="This is my first blog post.", slug="first-post")
>>> first_post.save()
>>> Post.objects.all()
<QuerySet [<Post: First Post!>]>

Querying

We can interact with the data in the database just like if it was an object in Python.

Instead of selecting from a database, we can use the get() method to select a single record, or filter() to select multiple matching records.

>>> my_post = Post.objects.get(id=1)
>>> type(my_post)
<class 'blog.models.Post'>
>>> my_post.title
'First Post!'
>>> my_post.slug
'first-post'
>>> my_post.body
'This is my first blog post.'
>>> my_post.created_at
datetime.datetime(2020, 10, 30, 5, 4, 22, 407523, tzinfo=<UTC>)

Or, we can arbitrarily query the database.

Django even allows us to write queries that examine if values contain others values, how values compare to other values, and a lot more. Review the querysets API reference to see all the options.

Let’s use case-sensitive contains to search for blog posts by slug.

Notice that this returns a QuerySet, which looks a lot like a list. In fact, we can access objects in a QuerySet by index.

>>> posts = Post.objects.filter(slug__contains="first")
>>> len(posts)
1
>>> type(posts)
<class 'django.db.models.query.QuerySet'>
>>> post = posts[0]
>>> type(post)
<class 'blog.models.Post'>

Advantages

The Django ORM features a lot of optimizations under the hood, and you don’t need to know a lot about the internal workings of databases to start using them for your application.