Django ORM – 多表实例
以下是用中文详细讲解 Django ORM(对象关系映射)在多表操作中的使用指南,聚焦于多表关联的增删改查(CRUD)操作,涵盖一对多、多对多关系、关联查询、过滤器和最佳实践。我会保持简洁清晰,适合初学者和进阶用户,并提供实用示例,关键代码将包裹在 ““` 标签中以生成 artifact。如果你有特定问题或需要更深入的内容,请随时告知!
什么是 Django ORM 多表操作?
Django ORM 的多表操作涉及多个模型之间的关联(如一对多、多对多),通过外键(ForeignKey
)、多对多字段(ManyToManyField
)等定义关系。ORM 允许开发者以 Python 对象的方式操作关联表,简化复杂 SQL 查询。
- 作用:
- 处理模型间关系(如商品与分类、文章与标签)。
- 实现关联查询、过滤和数据管理。
- 自动生成高效 SQL,减少手动编码。
- 特点:
- 支持正向和反向查询。
- 提供
select_related
和prefetch_related
优化性能。 - 内置安全机制,防止 SQL 注入。
1. 定义模型
假设我们有一个应用 myapp
,包含三个模型:Category
(分类)、Item
(商品)、Tag
(标签),展示一对多(Category:Item
)和多对多(Item:Tag
)关系。
示例:myapp/models.py
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100, verbose_name='分类名称')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
def __str__(self):
return self.name
class Meta:
db_table = 'categories'
ordering = ['name']
class Item(models.Model):
name = models.CharField(max_length=100, verbose_name='商品名称')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='价格')
description = models.TextField(blank=True, verbose_name='描述')
is_active = models.BooleanField(default=True, verbose_name='是否上架')
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='items',
verbose_name='分类'
)
tags = models.ManyToManyField('Tag', related_name='items', verbose_name='标签')
def __str__(self):
return self.name
class Meta:
db_table = 'items'
ordering = ['-price']
class Tag(models.Model):
name = models.CharField(max_length=50, verbose_name='标签名称')
def __str__(self):
return self.name
class Meta:
db_table = 'tags'
ordering = ['name']
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100, verbose_name=’分类名称’)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=’创建时间’)
def __str__(self):
return self.name
class Meta:
db_table = 'categories'
ordering = ['name']
class Item(models.Model):
name = models.CharField(max_length=100, verbose_name=’商品名称’)
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=’价格’)
description = models.TextField(blank=True, verbose_name=’描述’)
is_active = models.BooleanField(default=True, verbose_name=’是否上架’)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name=’items’,
verbose_name=’分类’
)
tags = models.ManyToManyField(‘Tag’, related_name=’items’, verbose_name=’标签’)
def __str__(self):
return self.name
class Meta:
db_table = 'items'
ordering = ['-price']
class Tag(models.Model):
name = models.CharField(max_length=50, verbose_name=’标签名称’)
def __str__(self):
return self.name
class Meta:
db_table = 'tags'
ordering = ['name']
“`python
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100, verbose_name=’分类名称’)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=’创建时间’)
def __str__(self):
return self.name
class Meta:
db_table = 'categories'
ordering = ['name']
class Item(models.Model):
name = models.CharField(max_length=100, verbose_name=’商品名称’)
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=’价格’)
description = models.TextField(blank=True, verbose_name=’描述’)
is_active = models.BooleanField(default=True, verbose_name=’是否上架’)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name=’items’,
verbose_name=’分类’
)
tags = models.ManyToManyField(‘Tag’, related_name=’items’, verbose_name=’标签’)
def __str__(self):
return self.name
class Meta:
db_table = 'items'
ordering = ['-price']
class Tag(models.Model):
name = models.CharField(max_length=50, verbose_name=’标签名称’)
def __str__(self):
return self.name
class Meta:
db_table = 'tags'
ordering = ['name']
- **说明**:
- `Category:Item`:一对多关系(一个分类包含多个商品)。
- `Item:Tag`:多对多关系(一个商品有多个标签,一个标签可用于多个商品)。
- `related_name`:定义反向查询名称(如 `category.items`)。
- 运行迁移命令创建表:
```bash
python manage.py makemigrations
python manage.py migrate
```
---
### 2. 多表 CRUD 操作
以下展示如何对关联模型进行增删改查操作。
#### 2.1 增(Create)
创建关联记录。
##### 示例:创建记录
python
from django.shortcuts import render
from .models import Category, Item, Tag
def create_data(request):
# 创建分类
category = Category.objects.create(name=’书籍’)
# 创建商品并关联分类
item = Item.objects.create(
name='Python 编程',
price=59.99,
description='Python 入门书籍',
category=category
)
# 创建标签并关联商品
tag1 = Tag.objects.create(name='编程')
tag2 = Tag.objects.create(name='Python')
item.tags.add(tag1, tag2)
return render(request, 'myapp/success.html', {'message': '数据创建成功'})
- **说明**:
- `ForeignKey`:直接设置 `category` 字段。
- `ManyToManyField`:使用 `add()` 添加关联。
- 批量创建标签:
```python
item.tags.bulk_create([Tag(name='新标签')])
```
#### 2.2 查(Retrieve)
多表查询分为正向查询(通过外键访问)、反向查询(通过 `related_name`)和关联查询。
##### 示例:查询记录
python
from django.shortcuts import render, get_object_or_404
from .models import Category, Item, Tag
def item_list(request):
# 正向查询:获取商品及其分类
items = Item.objects.select_related(‘category’).all()
# 反向查询:获取分类下的所有商品
category = get_object_or_404(Category, pk=1)
category_items = category.items.all()
# 多对多查询:获取商品的标签
item = get_object_or_404(Item, pk=1)
tags = item.tags.all()
# 反向多对多:获取标签关联的商品
tag = get_object_or_404(Tag, pk=1)
tagged_items = tag.items.all()
# 过滤关联数据
expensive_books = Item.objects.filter(
category__name='书籍',
price__gte=50,
tags__name='Python'
)
return render(request, 'myapp/item_list.html', {
'items': items,
'category_items': category_items,
'tags': tags,
'expensive_books': expensive_books,
})
- **查询方法**:
- **正向查询**:`item.category.name`(通过外键访问)。
- **反向查询**:`category.items.all()`(通过 `related_name`)。
- **多对多查询**:`item.tags.all()` 或 `tag.items.all()`。
- **跨表过滤**:使用双下划线 `__`(如 `category__name`)。
- **优化查询**:
- `select_related('category')`:优化一对多/一对一查询,减少 SQL。
- `prefetch_related('tags')`:优化多对多查询,预加载关联数据。
```python
items = Item.objects.select_related('category').prefetch_related('tags').all()
```
- **常用过滤器**:
- `__exact`、`__contains`、`__in` 等(同单表)。
- `__isnull`:检查关联字段是否为空。
```python
items = Item.objects.filter(category__isnull=False)
```
- 示例:
```python
items = Item.objects.filter(tags__name__in=['Python', '编程'])
```
#### 2.3 改(Update)
更新关联记录。
##### 示例:更新记录
python
from django.shortcuts import render, get_object_or_404
from .models import Item, Tag
def update_item(request, pk):
item = get_object_or_404(Item, pk=pk)
if request.method == 'POST':
# 更新商品字段
item.price = 69.99
item.save()
# 更新关联:更换分类
new_category = get_object_or_404(Category, name='技术书籍')
item.category = new_category
item.save()
# 更新多对多:添加/移除标签
new_tag = Tag.objects.get(name='Django')
item.tags.add(new_tag)
old_tag = Tag.objects.get(name='Python')
item.tags.remove(old_tag)
# 清空所有标签
# item.tags.clear()
return render(request, 'myapp/success.html', {'message': '商品更新成功'})
return render(request, 'myapp/item_form.html', {'item': item})
- **说明**:
- `ForeignKey`:直接赋值新对象并保存。
- `ManyToManyField`:使用 `add()`、`remove()` 或 `clear()` 管理关联。
- 批量更新:
```python
Item.objects.filter(category__name='书籍').update(price=65.00)
```
#### 2.4 删(Delete)
删除关联记录。
##### 示例:删除记录
python
from django.shortcuts import render, get_object_or_404
from .models import Item, Category
def delete_item(request, pk):
item = get_object_or_404(Item, pk=pk)
if request.method == 'POST':
# 删除商品
item.delete()
# 删除分类(级联删除其商品,取决于 on_delete=CASCADE)
category = get_object_or_404(Category, pk=1)
category.delete()
return render(request, 'myapp/success.html', {'message': '删除成功'})
return render(request, 'myapp/confirm_delete.html', {'item': item})
- **说明**:
- 删除 `Item`:仅删除单条记录。
- 删除 `Category`:因 `on_delete=models.CASCADE`,关联的 `Item` 也会删除。
- 删除多对多关联:
```python
item.tags.clear() # 移除所有标签
```
---
### 3. 模板和视图集成
以下是将多表查询结果展示到前端的示例。
#### 示例:视图 `myapp/views.py`
python
from django.shortcuts import render, get_object_or_404, redirect
from .models import Item, Category
from .forms import ItemForm
def item_list(request):
items = Item.objects.select_related(‘category’).prefetch_related(‘tags’).all()
return render(request, ‘myapp/item_list.html’, {‘items’: items})
def item_detail(request, pk):
item = get_object_or_404(Item, pk=pk)
return render(request, ‘myapp/item_detail.html’, {‘item’: item})
def item_create(request):
if request.method == ‘POST’:
form = ItemForm(request.POST)
if form.is_valid():
item = form.save()
return redirect(‘myapp:item_detail’, pk=item.pk)
else:
form = ItemForm()
return render(request, ‘myapp/item_form.html’, {‘form’: form})
#### 示例:表单 `myapp/forms.py`
python
from django import forms
from .models import Item
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = [‘name’, ‘price’, ‘description’, ‘is_active’, ‘category’, ‘tags’]
labels = {
‘name’: ‘商品名称’,
‘price’: ‘价格’,
‘description’: ‘描述’,
‘is_active’: ‘是否上架’,
‘category’: ‘分类’,
‘tags’: ‘标签’,
}
widgets = {
‘description’: forms.Textarea(attrs={‘rows’: 4}),
‘tags’: forms.SelectMultiple(attrs={‘size’: 5}),
}
#### 示例:模板 `myapp/templates/myapp/item_list.html`
html
{% extends ‘base.html’ %}
{% block content %}
商品列表
- {{ item.name }} – ¥{{ item.price }} ({{ item.category.name }})
标签: {% for tag in item.tags.all %} {{ tag.name }}{% if not forloop.last %}, {% endif %} {% empty %} 无标签 {% endfor %} - 暂无商品
{% endblock %}
#### 示例:模板 `myapp/templates/myapp/item_detail.html`
html
{% extends ‘base.html’ %}
{% block content %}
{{ item.name }}
价格:¥{{ item.price }}
分类:{{ item.category.name }}
描述:{{ item.description|default:”无描述” }}
状态:{% if item.is_active %}上架{% else %}下架{% endif %}
标签: {% for tag in item.tags.all %} {{ tag.name }}{% if not forloop.last %}, {% endif %} {% empty %} 无标签 {% endfor %}
返回列表
{% endblock %}
#### 示例:模板 `myapp/templates/myapp/item_form.html`
html
{% extends ‘base.html’ %}
{% block content %}
添加商品
{% csrf_token %} {{ form.as_p }} 保存
取消
{% endblock %}
---
### 4. URL 路由
将视图绑定到 URL。
#### 示例:`myapp/urls.py`
python
from django.urls import path
from . import views
app_name = ‘myapp’
urlpatterns = [
path(”, views.item_list, name=’item_list’),
path(‘items//’, views.item_detail, name=’item_detail’),
path(‘items/create/’, views.item_create, name=’item_create’),
]
#### 项目级:`myproject/urls.py`
python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path(‘admin/’, admin.site.urls),
path(”, include(‘myapp.urls’, namespace=’myapp’)),
]
---
### 5. 最佳实践
1. **查询优化**:
- 使用 `select_related` 优化一对多查询:
```python
items = Item.objects.select_related('category').all()
```
- 使用 `prefetch_related` 优化多对多查询:
```python
items = Item.objects.prefetch_related('tags').all()
```
- 避免 N+1 查询问题:
```python
# 差:for item in Item.objects.all(): print(item.category.name)
# 好:items = Item.objects.select_related('category').all()
```
2. **明确字段**:
- 使用 `values()` 或 `only()` 限制返回字段:
```python
items = Item.objects.select_related('category').values('name', 'price', 'category__name')
```
3. **异常处理**:
- 使用 `get_object_or_404` 或 `try-except`:
```python
try:
item = Item.objects.get(pk=1)
except Item.DoesNotExist:
return render(request, 'myapp/error.html', {'message': '商品不存在'})
```
4. **安全性**:
- 使用 ORM 过滤器,避免拼接 SQL。
- 通过 `form.cleaned_data` 处理用户输入。
5. **调试查询**:
- 查看生成的 SQL:
```python
print(Item.objects.filter(category__name='书籍').query)
```
- 使用 Django Debug Toolbar 分析性能。
---
### 6. 常见问题
1. **关联查询返回空**:
- 检查外键或多对多关系是否正确设置。
- 确认数据库中是否存在关联数据。
2. **N+1 查询问题**:
- 使用 `select_related` 或 `prefetch_related`。
- 检查循环中是否多次查询数据库。
3. **多对多操作失败**:
- 确保 `tags.add()` 或 `tags.remove()` 作用于已保存的对象:
```python
item = Item.objects.create(name='Test')
item.tags.add(Tag.objects.get(name='TestTag'))
```
4. **迁移冲突**:
- 多人协作时,合并迁移文件:
```bash
python manage.py makemigrations --merge
```
---
### 7. 运行项目
1. 确保模型迁移:
bash
python manage.py makemigrations
python manage.py migrate
2. 启动服务器:
bash
python manage.py runserver
“`
- 访问:
- 商品列表:
http://127.0.0.1:8000/
- 商品详情:
http://127.0.0.1:8000/items/1/
- 添加商品:
http://127.0.0.1:8000/items/create/
总结
Django ORM 的多表操作通过 ForeignKey
和 ManyToManyField
实现一对多和多对多关系,支持正向、反向和跨表查询。select_related
和 prefetch_related
优化性能,结合表单和模板实现数据管理。遵循最佳实践(如查询优化、异常处理)可构建高效、安全的应用。
如果你需要更复杂的 ORM 示例(如聚合查询、复杂关联)、特定功能的深入讲解,或调试多表问题的方法,请告诉我!