One-to-One Association (OneToOneField)
from django.db import models
class Author(models.Model):
full_name = models.CharField(max_length=32)
profile = models.OneToOneField(to="AuthorProfile", on_delete=models.CASCADE)
class AuthorProfile(models.Model):
residential_addr = models.CharField(max_length=128)
Object-based cross-table query
Forward query (use the association field deefined on the current model) Requirement: Fetch the residential address of every registered author
all_authors = Author.objects.all()
for author in all_authors:
print(author.full_name, author.profile.residential_addr)
Reverse query (use lowercase model name of the associated table) Requirement: Get names of all authors living in Guangzhou
gz_profiles = AuthorProfile.objects.filter(residential_addr="Guangzhou")
for profile in gz_profiles:
print(profile.author.full_name)
Double underscore cross-table query (for filter, values, values_list methods)
Forward query syntax: association_field__related_table_field
Requirement: Directly get the address of author named "Li Ming"
li_addr = Author.objects.filter(full_name="Li Ming").values("profile__residential_addr")
print(li_addr)
# Output example: <QuerySet [{'profile__residential_addr': 'Tianhe District, Guangzhou'}]>
Reverse query syntax: lowercase_related_model_name__field
Requirement: Get names of all authors based in Guangzhou
gz_author_names = AuthorProfile.objects.filter(residential_addr="Guangzhou").values("author__full_name")
Delete operation
Cascade delete is enabled by default, deleting a record from the main table will remove associated dependent table records:
# Delete the profile record with primary key 8, associated author record will also be removed
AuthorProfile.objects.filter(pk=8).delete()
Add operation (update follows the same logic, replace create with update)
Create main table record first, then create associated dependent table record:
Method 1: Use create method directly
new_profile = AuthorProfile.objects.create(residential_addr="Xihu District, Hangzhou")
new_author = Author.objects.create(full_name="Zhang Wei", profile=new_profile)
Method 2: Initialize model instance then call save method
new_profile = AuthorProfile(residential_addr="Xihu District, Hangzhou")
new_profile.save()
new_author = Author(full_name="Zhang Wei", profile=new_profile)
new_author.save()
One-to-Many Association (ForeignKey)
class Book(models.Model):
title = models.CharField(max_length=64)
price = models.DecimalField(max_digits=8, decimal_places=2)
publisher = models.ForeignKey(to="Publisher", on_delete=models.CASCADE)
class Publisher(models.Model):
pub_name = models.CharField(max_length=64)
contact_email = models.EmailField()
Object-based cross-table query
Forward query Requirement: Query the publisher name for each stored book
all_books = Book.objects.all()
for book in all_books:
print(book.title, book.publisher.pub_name)
Reverse query: use lowercase_related_model_name_set to get associated queryset
Requirement: Get all book titles published by the publisher with id 2
target_pub = Publisher.objects.filter(id=2).first()
published_books = target_pub.book_set.all()
for book in published_books:
print(book.title)
Double underscore cross-table query
Forward query Requirement: Fetch publisher names for all books
book_pub_list = Book.objects.values("title", "publisher__pub_name")
Reverse query Requirement: Get all book titles published by the publisher with id 2
pub_book_names = Publisher.objects.filter(id=2).values("book__title")
Delete operation
# Delete publisher with primary key 5, all associated book records will be removed
Publisher.objects.filter(pk=5).delete()
Add operation
Method 1: Pass associated object directly
new_pub = Publisher.objects.create(pub_name="Sci-Fi Literature Press", contact_email="edit@scifibooks.com")
Book.objects.create(title="Dune", price=49.8, publisher=new_pub)
Method 2: Pass primary key of associated record with [association_field]_id
existing_pub = Publisher.objects.filter(pub_name="Sci-Fi Literature Press").first()
Book.objects.create(title="Dune Messiah", price=42.5, publisher_id=existing_pub.pk)
Method 3: Initialize instance and save
new_pub = Publisher(pub_name="Sci-Fi Literature Press", contact_email="edit@scifibooks.com")
new_pub.save()
new_book = Book(title="Children of Dune", price=45.0, publisher=new_pub)
new_book.save()
Many-to-Many Association
Auto-generated junction table (ManyToManyField)
Django automatically creates a hidden junction table when ManyToManyField is defined:
class Book(models.Model):
title = models.CharField(max_length=64)
written_by = models.ManyToManyField(to="Author")
class Author(models.Model):
full_name = models.CharField(max_length=32)
Query operation
Object-based forward query Requirement: Get all authors of the book with primary key 3
target_book = Book.objects.filter(pk=3).first()
print(target_book.written_by.all())
# Output example: <QuerySet [<Author: Frank Herbert>, <Author: Brian Herbert>]>
Object-based reverse query Requirement: Get all books written by the author with primary key 2
target_author = Author.objects.filter(pk=2).first()
print(target_author.book_set.all())
# Output example: <QuerySet [<Book: Dune>, <Book: Dune Messiah>]>
Double underscore cross-table query
# Get names of all authors for the book with primary key 3
book_authors = Book.objects.filter(pk=3).values("written_by__full_name")
# Get all book titles written by the author with primary key 4
author_books = Author.objects.filter(pk=4).values("book__title")
Junction table operation methods
add operation: no return value, skips duplicate existing associations
# Forward add by primary key
target_book = Book.objects.filter(id=3).first()
target_book.written_by.add(5, 7)
# Forward add by model objects
author_list = Author.objects.filter(id__in=[5,7,9])
target_book.written_by.add(*author_list)
# Reverse add
target_author = Author.objects.filter(id=2).first()
target_author.book_set.add(4,6)
remove operation: same usage as add
target_book.written_by.remove(7)
clear operation: remove all associations for the current record
target_book.written_by.clear()
set operation: accepts iterable parameter, replaces all existing associations
target_book = Book.objects.filter(id=3).first()
new_authors = Author.objects.filter(id__in=[2,4])
target_book.written_by.set(new_authors)
Custom junction table
Define explicit junction table when you need to store extra association fields:
class Book(models.Model):
title = models.CharField(max_length=64)
class Author(models.Model):
full_name = models.CharField(max_length=32)
class BookAuthorMap(models.Model):
book = models.ForeignKey(to="Book", on_delete=models.CASCADE)
author = models.ForeignKey(to="Author", on_delete=models.CASCADE)
# Extra fields can be added here, e.g. cooperation_role, join_date
Cross-table query example: Get all authors of the book named "Foundation" Method 1: Query book first, reverse to junction table, then forward to author
target_book = Book.objects.filter(title="Foundation").first()
mapping_records = target_book.bookauthormap_set.all()
for record in mapping_records:
print(record.author.full_name)
Method 2: Query junction table directly with filter conditions
mapping_records = BookAuthorMap.objects.filter(book__title="Foundation").all()
for record in mapping_records:
print(record.author.full_name)
Method 3: Cross query directly from author table
foundation_authors = Author.objects.filter(bookauthormap__book__title="Foundation").values("full_name")
Combine custom junction table with ManyToManyField
Use through parameter to link custom junction table to ManyToManyField for simplified query:
class Book(models.Model):
title = models.CharField(max_length=64)
written_by = models.ManyToManyField(
to="Author",
through="BookAuthorMap",
through_fields=("book", "author")
)
class BookAuthorMap(models.Model):
book = models.ForeignKey(to="Book", on_delete=models.CASCADE)
author = models.ForeignKey(to="Author", on_delete=models.CASCADE)
class Author(models.Model):
full_name = models.CharField(max_length=32)
Configuration notes:
- Simplified query syntax is still supported, e.g.
Book.objects.first().written_by.all() - Only
clearoperation is supported for association modification,add,remove,setare unavailable - All add/update/delete operations for the junction table need to be done direct via the custom junction table model
Database Transactions
Use Django's atomic context manager to wrap operations requiring transaction guarantee:
from django.db import transaction
with transaction.atomic():
# All ORM operations inside this block are executed as a single transaction
# Any exception triggers full rollback of all changes
pass
MVC vs MTV Pattern
Django follows the MTV architecture, a variant of the classic MVC pattern:
MTV (Django):
- M: Models layer, responsible for database data interaction
- T: Templates layer, responsible for front-end page rendering
- V: Views layer, responsible for business logic processing and request response
Classic MVC:
- M: Models layer, same as MTV's M
- V: Views layer, responsible for front-end page display (corresponds to Django's Templates layer)
- C: Controller layer, responsible for request distribution and business logic processing (corresponds to Django's url routing + Views layer)