Django ORM – 多表实例

以下是用中文详细讲解 Django ORM(对象关系映射)在多表操作中的使用指南,聚焦于多表关联的增删改查(CRUD)操作,涵盖一对多、多对多关系、关联查询、过滤器和最佳实践。我会保持简洁清晰,适合初学者和进阶用户,并提供实用示例,关键代码将包裹在 ““` 标签中以生成 artifact。如果你有特定问题或需要更深入的内容,请随时告知!


什么是 Django ORM 多表操作?

Django ORM 的多表操作涉及多个模型之间的关联(如一对多、多对多),通过外键(ForeignKey)、多对多字段(ManyToManyField)等定义关系。ORM 允许开发者以 Python 对象的方式操作关联表,简化复杂 SQL 查询。

  • 作用
  • 处理模型间关系(如商品与分类、文章与标签)。
  • 实现关联查询、过滤和数据管理。
  • 自动生成高效 SQL,减少手动编码。
  • 特点
  • 支持正向和反向查询。
  • 提供 select_relatedprefetch_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
“`

  1. 访问:
  • 商品列表:http://127.0.0.1:8000/
  • 商品详情:http://127.0.0.1:8000/items/1/
  • 添加商品:http://127.0.0.1:8000/items/create/

总结

Django ORM 的多表操作通过 ForeignKeyManyToManyField 实现一对多和多对多关系,支持正向、反向和跨表查询。select_relatedprefetch_related 优化性能,结合表单和模板实现数据管理。遵循最佳实践(如查询优化、异常处理)可构建高效、安全的应用。

如果你需要更复杂的 ORM 示例(如聚合查询、复杂关联)、特定功能的深入讲解,或调试多表问题的方法,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注