Django学习之旅--第6课:数据操控 - Django ORM 增删改查(CRUD)高级技巧实战

引言:从数据存储到业务逻辑的跨越

在Django开发中,模型(Model)与对象关系映射(ORM)是构建动态应用的核心。如果说第5课我们掌握了数据存储的基础架构,那么本节课将深入数据操作的核心——通过ORM实现复杂的业务逻辑。无论是服务筛选、订单管理还是数据统计,高效的ORM查询能力都是提升开发效率的关键。对于副业开发者而言,熟练运用ORM意味着能够快速响应客户需求,比如实现按价格区间筛选服务、统计热门订单等功能,这些都是平台商业化的基础。

一、ORM查询基础回顾

在进入高级查询之前,我们先回顾基础的ORM操作。这些操作是高级查询的基石,需要熟练掌握:

# 进入Django Shell
python manage.py shell

from services.models import Service, Order

# 创建测试数据
Service.objects.create(name="网站开发", description="企业网站建设", price=1999.00, is_active=True)
Service.objects.create(name="数据分析", description="业务数据可视化", price=2999.00, is_active=True)
Service.objects.create(name="LOGO设计", description="品牌标识设计", price=999.00, is_active=False)

# 获取所有对象
all_services = Service.objects.all()
print(f"总服务数: {all_services.count()}")

# 获取单个对象(带异常处理)
try:
    web_dev = Service.objects.get(id=1)
    print(f"获取服务: {web_dev.name}")
except Service.DoesNotExist:
    print("服务不存在")

基础查询中,all()get()是最常用的方法,但在实际业务中,我们需要更灵活的查询方式来满足复杂需求。

二、高级查询技巧实战

2.1 条件筛选:filter与exclude的灵活运用

Django的filter()exclude()方法是ORM查询的核心,通过字段查找类型可以实现丰富的查询条件:

# 获取所有活跃服务
active_services = Service.objects.filter(is_active=True)
print(f"活跃服务数: {active_services.count()}")

# 获取价格大于2000的服务
expensive_services = Service.objects.filter(price__gt=2000)
print(f"高价服务数: {expensive_services.count()}")

# 组合条件:名称包含"设计"且非活跃的服务
design_services = Service.objects.filter(
    name__contains="设计", 
    is_active=False
)
print(f"非活跃设计服务: {design_services.count()}")

# 排除价格低于1000的服务
valid_services = Service.objects.exclude(price__lt=1000)
print(f"有效服务数: {valid_services.count()}")

2.2 字段查找类型大全

Django支持丰富的字段查找类型,以下是常用类型及示例:

查找类型 示例 说明
exact name__exact=“网站开发” 精确匹配(默认可省略)
iexact name__iexact=“Website” 不区分大小写精确匹配
contains description__contains=“企业” 包含指定字符串
icontains description__icontains=“Business” 不区分大小写包含
in id__in=[1,3,5] 值在列表中
gt/gte price__gt=1000 大于/大于等于
lt/lte price__lte=500 小于/小于等于
startswith name__startswith=“数据” 以指定字符串开头
range created_at__range=(start_date, end_date) 日期范围查询

实战技巧:在复杂查询中,使用Q对象可以组合多个逻辑条件,实现OR查询:

from django.db.models import Q

# 查询名称包含"开发"或价格低于1000的服务
mixed_services = Service.objects.filter(
    Q(name__contains="开发") | Q(price__lt=1000)
)

三、关联查询:外键关系的双向操作

3.1 完善订单模型

首先,我们需要修改下上节课中的订单模型,建立与服务模型的外键关联:

# services/models.py
class Order(models.Model):
    STATUS_CHOICES = [
        ('P', '待支付'),
        ('O', '进行中'),
        ('C', '已完成'),
    ]
    
    customer_name = models.CharField(max_length=100, verbose_name="客户姓名")
    customer_email = models.EmailField(verbose_name="客户邮箱")
    service = models.ForeignKey(
        Service, 
        on_delete=models.CASCADE,
        related_name="orders",  # 反向关联名称
        verbose_name="关联服务"
    )
    order_amount = models.DecimalField(
        max_digits=10, 
        decimal_places=2, 
        verbose_name="订单金额"
    )
    order_date = models.DateTimeField(auto_now_add=True, verbose_name="下单日期")
    status = models.CharField(
        max_length=1, 
        choices=STATUS_CHOICES, 
        default='P',
        verbose_name="订单状态"
    )
    
    def __str__(self):
        return f"{self.customer_name} - {self.service.name}"
    
    class Meta:
        verbose_name = "订单"
        verbose_name_plural = "订单管理"
        ordering = ['-order_date']  # 按下单时间倒序

执行数据库迁移:

python manage.py makemigrations services
python manage.py migrate

3.2 双向关联查询实战

建立关联后,我们可以在服务与订单之间进行双向查询:

# 创建关联订单
web_dev = Service.objects.get(name="网站开发")
Order.objects.create(
    customer_name="张三",
    customer_email="[email protected]",
    service=web_dev,
    order_amount=web_dev.price,
    status='O'
)

# 正向查询:从订单到服务(已知订单查对应的服务)
order = Order.objects.first()
print(f"订单服务: {order.service.name}")  # 输出"网站开发"

# 反向查询:从服务到订单(已知服务查所有订单)
service = Service.objects.get(name="网站开发")
service_orders = service.orders.all()  # 使用related_name
print(f"{service.name}的订单数: {service_orders.count()}")

性能优化提示:当需要批量查询关联对象时,使用select_relatedprefetch_related可以减少数据库查询次数:

# 优化正向查询(减少JOIN操作)
orders = Order.objects.select_related('service').all()
for order in orders:
    print(order.service.name)  # 不会额外查询

# 优化反向查询(处理多对多关系)
services = Service.objects.prefetch_related('orders').all()
for service in services:
    print(f"{service.name}{service.orders.count()}个订单")

四、聚合与注解:数据统计的强大工具

4.1 聚合函数(aggregate)的应用

聚合函数用于对查询集进行统计计算,返回一个包含统计结果的字典:

from django.db.models import Avg, Max, Min, Sum, Count

# 计算所有服务的平均价格
avg_price = Service.objects.aggregate(Avg('price'))
print(f"平均价格: {avg_price['price__avg']}")  # 输出: 1999.0

# 同时计算多个统计值
stats = Service.objects.aggregate(
    min_price=Min('price'),
    max_price=Max('price'),
    total_services=Count('id'),
    total_revenue=Sum('price')
)
print(f"最低价格: {stats['min_price']}")
print(f"最高价格: {stats['max_price']}")
print(f"服务总数: {stats['total_services']}")
print(f"总价值: {stats['total_revenue']}")

4.2 注解(annotate)实现分组统计

annotate方法用于为查询集中的每个对象添加一个统计值,非常适合分组统计:

from django.db.models import Count, Sum

# 为每个服务添加订单数量注解
services_with_orders = Service.objects.annotate(
    order_count=Count('orders')
).filter(is_active=True).order_by('-order_count')

# 输出每个服务的订单数
for service in services_with_orders:
    print(f"{service.name}: {service.order_count}个订单")

# 计算每个服务的总订单金额
services_with_revenue = Service.objects.annotate(
    total_revenue=Sum('orders__order_amount')
).filter(is_active=True)

# 输出每个服务的总收入
for service in services_with_revenue:
    print(f"{service.name}: 总收入¥{service.total_revenue or 0}")

五、项目实战:实现服务筛选功能

5.1 视图函数的筛选逻辑

现在我们实现一个完整的服务筛选功能,包括关键词搜索、价格区间和分类筛选:

# services/views.py
from django.shortcuts import render, get_object_or_404
from .models import Service
from django.db.models import Q

def service_list(request):
    # 获取查询参数
    search_query = request.GET.get('search', '')
    min_price = request.GET.get('min_price')
    max_price = request.GET.get('max_price')
    category = request.GET.get('category')
    sort = request.GET.get('sort', 'created_at')
    order = request.GET.get('order', 'desc')
    
    # 初始查询集 - 只获取活跃服务
    services = Service.objects.filter(is_active=True)
    
    # 应用搜索条件
    if search_query:
        services = services.filter(
            Q(name__icontains=search_query) | 
            Q(description__icontains=search_query)
        )
    
    # 应用价格区间筛选
    if min_price:
        services = services.filter(price__gte=min_price)
    if max_price:
        services = services.filter(price__lte=max_price)
    
    # 应用分类筛选
    if category:
        services = services.filter(category=category)
    
    # 应用排序
    if order == 'asc':
        services = services.order_by(sort)
    else:
        services = services.order_by('-' + sort)
    
    # 传递筛选条件到模板,用于保留查询参数
    context = {
        'services': services,
        'search_query': search_query,
        'min_price': min_price,
        'max_price': max_price,
        'category': category,
        'sort': sort,
        'order': order
    }
    return render(request, 'services/list.html', context)

def service_detail(request, service_id):
    # 获取服务对象,不存在时返回404
    service = get_object_or_404(Service, id=service_id)
    # 获取该服务的所有订单
    orders = service.orders.all()
    return render(request, 'services/detail.html', {
        'service': service,
        'orders': orders
    })

5.2 筛选模板的实现

接下来创建模板文件,实现筛选表单和服务列表展示:


{% extends "homepage/base.html" %}
{% load static %}

{% block content %}
<div class="container">
    <h1>服务列表h1>
    
    
    <form method="get" class="filter-form">
        <input type="text" name="search" placeholder="搜索服务..." 
               value="{{ search_query }}">
        
        <div class="price-range">
            <input type="number" name="min_price" placeholder="最低价"
                   value="{{ min_price }}">
            <span>-span>
            <input type="number" name="max_price" placeholder="最高价"
                   value="{{ max_price }}">
        div>
        
        <select name="category">
            <option value="">所有分类option>
            

5.3 服务详情页模板


{% extends "homepage/base.html" %}
{% load static %}

{% block content %}
<div class="container">
    <h1>{{ service.name }}h1>
    <div class="service-detail">
        <div class="description">
            <p>{{ service.description }}p>
        div>
        <div class="info">
            <div class="price">价格: ¥{{ service.price }}div>
            <div class="category">分类: {{ service.get_category_display }}div>
            <div class="duration">周期: {{ service.get_duration_display }}div>
            <div class="status">状态: {% if service.is_active %}已上架{% else %}已下架{% endif %}div>
        div>
    div>
    
    <h2>相关订单 ({{ orders.count }}个)h2>
    <ul class="order-list">
        {% for order in orders %}
        <li class="order-item">
            <div class="order-info">
                <span>客户: {{ order.customer_name }}span>
                <span>状态: {{ order.get_status_display }}span>
                <span>日期: {{ order.order_date|date:"Y-m-d" }}span>
            div>
            <div class="order-amount">金额: ¥{{ order.order_amount }}div>
        li>
        {% empty %}
        <li class="no-orders">暂无订单li>
        {% endfor %}
    ul>
    
    <a href="{% url 'create_order' service.id %}" class="btn primary">立即订购a>
div>
{% endblock %}

5.4 CSS样式实现

/* static/css/services.css */
.filter-form {
    display: grid;
    grid-template-columns: 3fr 1fr 1fr 1fr auto;
    gap: 10px;
    margin-bottom: 30px;
    align-items: end;
}

.price-range {
    display: flex;
    gap: 5px;
    align-items: center;
}

.service-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 20px;
}

.service-card {
    border: 1px solid #e1e1e1;
    border-radius: 8px;
    padding: 20px;
    transition: transform 0.3s, box-shadow 0.3s;
}

.service-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

.price {
    font-size: 1.5rem;
    color: #e74c3c;
    margin: 10px 0;
    font-weight: bold;
}

.btn {
    display: inline-block;
    background: #3498db;
    color: white;
    padding: 8px 15px;
    text-decoration: none;
    border-radius: 4px;
    margin-top: 10px;
}

.service-detail {
    display: flex;
    flex-wrap: wrap;
    gap: 30px;
    margin-bottom: 30px;
}

.description {
    flex: 2;
}

.info {
    flex: 1;
    background: #f8f9fa;
    padding: 20px;
    border-radius: 8px;
}

.order-list {
    list-style: none;
    padding: 0;
}

.order-item {
    border: 1px solid #e1e1e1;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 10px;
}

.order-info {
    display: flex;
    flex-wrap: wrap;
    gap: 15px;
    margin-bottom: 5px;
}

.order-amount {
    font-weight: bold;
    color: #e74c3c;
}

.no-results, .no-orders {
    width: 100%;
    padding: 20px;
    text-align: center;
    color: #777;
    border: 1px dashed #e1e1e1;
    border-radius: 8px;
}

六、实战任务完整实现

任务1:服务详情页实现

  1. URL配置:在services/urls.py中添加详情页路由
# freelance_portal/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    ...   # 原有路由
    path('services/', include('services.urls')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)


# services/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('list/', views.service_list, name='service_list'),

    path('/', views.service_detail, name='service_detail'),

]
  1. 视图函数:已在5.1节实现service_detail视图

  2. 模板文件:已在5.3节实现detail.html模板

任务2:添加排序功能

在筛选表单中添加排序选项,并在视图中处理排序逻辑,已在5.1节完整实现。

任务3:订单管理后台

  1. 注册Order模型到Admin
# services/admin.py
from django.contrib import admin
from .models import Service, Order

@admin.register(Service)
class ServiceAdmin(admin.ModelAdmin):
    list_display = ('name', 'price', 'is_active', 'category', 'created_at')
    list_filter = ('is_active', 'category')
    search_fields = ('name', 'description')
    ordering = ('-created_at',)

@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    list_display = ('customer_name', 'service', 'order_amount', 'status', 'order_date')
    list_filter = ('status', 'order_date')
    search_fields = ('customer_name', 'customer_email', 'service__name')
    raw_id_fields = ('service',)  # 使用下拉框搜索而非下拉列表
  1. 订单管理功能
    • 按状态筛选订单(待支付、进行中、已完成)
    • 按日期范围筛选
    • 搜索客户姓名或服务名称
    • 快速查看订单金额和关联服务

七、技术要点与性能优化

7.1 查询优化核心技巧

  1. 减少数据库查询次数

    • 使用select_related预加载一对一/外键关系:
      orders = Order.objects.select_related('service').all()
      
    • 使用prefetch_related预加载多对多/反向关系:
      services = Service.objects.prefetch_related('orders').all()
      
  2. 限制查询字段

    • 使用only()获取特定字段:
      services = Service.objects.only('name', 'price')
      
    • 使用defer()排除特定字段:
      services = Service.objects.defer('description')
      
  3. 避免循环查询

    # 反例:循环中查询数据库
    for service in services:
        orders = service.orders.count()  # 每次循环都会查询数据库
    
    # 正例:使用annotate一次性查询
    services = Service.objects.annotate(order_count=Count('orders'))
    for service in services:
        orders = service.order_count  # 直接使用注解结果
    

7.2 复杂查询解决方案

  1. Q对象实现复杂条件

    from django.db.models import Q
    
    # 查询(名称包含"开发"且价格>2000)或分类为"设计"的服务
    services = Service.objects.filter(
        (Q(name__contains="开发") & Q(price__gt=2000)) | Q(category="DS")
    )
    
  2. F表达式实现字段间比较

    from django.db.models import F
    
    # 查找价格高于平均价格的服务
    avg_price = Service.objects.aggregate(Avg('price'))['price__avg']
    services = Service.objects.filter(price__gt=F('price').avg())
    

八、总结与进阶方向

8.1 今日成果清单

✅ 掌握ORM高级查询技巧,包括条件筛选、关联查询
✅ 学会使用聚合函数和注解进行数据统计
✅ 完成服务筛选功能的全流程实现(视图、模板、样式)
✅ 实现服务详情页和订单管理后台基础功能

8.2 下一阶段预告

第7课将深入Django Admin后台定制,你将学会:

  • 自定义Admin界面布局和功能
  • 添加自定义操作和筛选器
  • 实现数据导出和可视化统计
  • 为项目平台打造专业的管理后台

通过本节课的学习,你已经掌握了Django ORM的核心操作能力,这是开发任何动态网站的基础。接下来的Admin定制将进一步提升项目的完整性,为客户提供专业的管理工具。记住,熟练运用ORM不仅能提高开发效率,更能为你的项目注入强大的业务逻辑能力。

关注我,让我们在知识的路上结伴而行,每天解锁一个成长新视角,共同把学习变成照亮生活的光!

▼ 点击关注微信公众号 ▼ Python 知行学舍

你可能感兴趣的:(Django学习之旅,django,学习,数据库)