Learn / Frameworks / Django / Models and Migrations

Django · Lesson 2 of 5

Models and Migrations

Describe tables in Python, migrate them and query with the ORM.

  • Beginner
  • 17 min read
  • 3 objectives

Before this lessonLesson 1: Introduction and Setup

What you will learn

  • Define models
  • Run migrations
  • Query with filter/get/order_by

A Django model is a Python class describing one database table. Each attribute is a column. You never write SQL for the common cases; the ORM (object-relational mapper) does it for you.

Defining models

# blog/models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Post(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="posts")
    title = models.CharField(max_length=200)
    body = models.TextField()
    published = models.BooleanField(default=False)
    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created"]

    def __str__(self):
        return self.title

Common fields: CharField, TextField, IntegerField, BooleanField, DateTimeField, EmailField, ForeignKey (one-to-many) and ManyToManyField. Django adds an id primary key automatically.

Migrations

Migrations are versioned scripts that carry model changes into the database. Change a model, generate a migration, then apply it.

python manage.py makemigrations
python manage.py migrate

Commit migration files to git so teammates and servers apply the same changes.

Querying with the ORM

Open the shell with python manage.py shell and try:

from blog.models import Author, Post

ada = Author.objects.create(name="Ada")
Post.objects.create(author=ada, title="Hello", body="First post", published=True)

Post.objects.all()                              # everything
Post.objects.filter(published=True)             # WHERE published
Post.objects.filter(title__contains="Hel")      # LIKE
Post.objects.exclude(author=ada)
Post.objects.order_by("-created")[:5]           # ORDER BY / LIMIT
Post.objects.get(id=1)                          # exactly one, or raises
ada.posts.count()                               # via related_name

Querysets are lazy: nothing hits the database until you iterate, slice or evaluate them, so you can chain filters freely. Lookups use double underscores: __gt, __in, __icontains, __startswith, and even across relations, author__name="Ada".

Updating and deleting

post = Post.objects.get(id=1)
post.title = "Updated"
post.save()

Post.objects.filter(published=False).update(published=True)
post.delete()

Avoiding the N+1 problem

Looping over posts and printing post.author.name runs one extra query per post. Fetch related rows up front:

posts = Post.objects.select_related("author")        # JOIN for foreign keys
posts = Author.objects.prefetch_related("posts")    # 2 queries for reverse/many-to-many
# Write your solution here
Up next · Lesson 3Views, URLs and TemplatesRoute a URL to a view and render HTML with the template language.