- We’ll do: Book ↔ Category
One book can have many categories
One category can contain many books
Tables in TABULAR form with sample records
books
id |
author_id |
title |
published_year |
price |
is_published |
|---|---|---|---|---|---|
1 |
1 |
1984 |
1949 |
9.99 |
true |
2 |
1 |
Animal Farm |
1945 |
7.99 |
true |
3 |
2 |
Harry Potter |
1997 |
19.99 |
true |
categories
id |
name |
|---|---|
1 |
Fiction |
2 |
Politics |
3 |
Fantasy |
4 |
Classics |
Join table (auto): myapp_book_categories
(links books ↔ categories)
id |
book_id |
category_id |
|---|---|---|
1 |
1 |
1 |
2 |
1 |
2 |
3 |
1 |
4 |
4 |
2 |
1 |
5 |
2 |
2 |
6 |
3 |
1 |
7 |
3 |
3 |
This join table is the real many-to-many data.
models.py (Many-to-Many)
from django.db import models
from django.core.validators import MinValueValidator
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "authors"
def __str__(self):
return self.name
class AuthorProfile(models.Model):
author = models.OneToOneField(
Author,
on_delete=models.CASCADE,
related_name="profile",
)
bio = models.TextField(blank=True)
website = models.URLField(blank=True)
birth_year = models.PositiveSmallIntegerField(null=True, blank=True)
class Meta:
db_table = "author_profiles"
def __str__(self):
return f"Profile of {self.author.name}"
# NEW TABLE
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
class Meta:
db_table = "categories"
def __str__(self):
return self.name
class Book(models.Model):
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name="books",
)
title = models.CharField(max_length=200)
published_year = models.PositiveSmallIntegerField(validators=[MinValueValidator(1)])
price = models.DecimalField(max_digits=8, decimal_places=2)
is_published = models.BooleanField(default=True)
# MANY-TO-MANY
categories = models.ManyToManyField(
Category,
related_name="books",
blank=True,
)
class Meta:
db_table = "books"
constraints = [
models.UniqueConstraint(
fields=["author", "title"],
name="uniq_book_per_author",
)
]
def __str__(self):
return self.title
Run migrations
python manage.py makemigrations myapp
python manage.py migrate
Practice ORM in Django shell (mini program)
Open shell:
python manage.py shell
from myapp.models import Author, Book, Category
# create author + books
a = Author.objects.create(name="George Orwell", email="orwell@example.com")
b1 = Book.objects.create(author=a, title="1984", published_year=1949, price=9.99)
b2 = Book.objects.create(author=a, title="Animal Farm", published_year=1945, price=7.99)
# create categories
fiction = Category.objects.create(name="Fiction")
politics = Category.objects.create(name="Politics")
classics = Category.objects.create(name="Classics")
# add categories to book (this writes into the join table)
b1.categories.add(fiction, politics, classics)
b2.categories.add(fiction, politics)
# check results
b1.categories.all()
fiction.books.all()
Reverse Many-to-Many queries
Book → Categories
b1.categories.all()
Category → Books (reverse)
politics.books.all()
Key takeaways
ManyToManyField = a third table (join table)
Forward: book.categories.all()
Reverse: category.books.all()