在Django开发中,模型(Model)与对象关系映射(ORM)是构建动态应用的核心。如果说第5课我们掌握了数据存储的基础架构,那么本节课将深入数据操作的核心——通过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()
是最常用的方法,但在实际业务中,我们需要更灵活的查询方式来满足复杂需求。
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()}")
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)
)
首先,我们需要修改下上节课中的订单模型,建立与服务模型的外键关联:
# 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
建立关联后,我们可以在服务与订单之间进行双向查询:
# 创建关联订单
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_related
和prefetch_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()}个订单")
聚合函数用于对查询集进行统计计算,返回一个包含统计结果的字典:
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']}")
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}")
现在我们实现一个完整的服务筛选功能,包括关键词搜索、价格区间和分类筛选:
# 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
})
接下来创建模板文件,实现筛选表单和服务列表展示:
{% 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>
{% 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 %}
/* 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;
}
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'),
]
视图函数:已在5.1节实现service_detail
视图
模板文件:已在5.3节实现detail.html
模板
在筛选表单中添加排序选项,并在视图中处理排序逻辑,已在5.1节完整实现。
# 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',) # 使用下拉框搜索而非下拉列表
减少数据库查询次数:
select_related
预加载一对一/外键关系:orders = Order.objects.select_related('service').all()
prefetch_related
预加载多对多/反向关系:services = Service.objects.prefetch_related('orders').all()
限制查询字段:
only()
获取特定字段:services = Service.objects.only('name', 'price')
defer()
排除特定字段:services = Service.objects.defer('description')
避免循环查询:
# 反例:循环中查询数据库
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 # 直接使用注解结果
Q对象实现复杂条件:
from django.db.models import Q
# 查询(名称包含"开发"且价格>2000)或分类为"设计"的服务
services = Service.objects.filter(
(Q(name__contains="开发") & Q(price__gt=2000)) | Q(category="DS")
)
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())
✅ 掌握ORM高级查询技巧,包括条件筛选、关联查询
✅ 学会使用聚合函数和注解进行数据统计
✅ 完成服务筛选功能的全流程实现(视图、模板、样式)
✅ 实现服务详情页和订单管理后台基础功能
第7课将深入Django Admin后台定制,你将学会:
通过本节课的学习,你已经掌握了Django ORM的核心操作能力,这是开发任何动态网站的基础。接下来的Admin定制将进一步提升项目的完整性,为客户提供专业的管理工具。记住,熟练运用ORM不仅能提高开发效率,更能为你的项目注入强大的业务逻辑能力。
关注我,让我们在知识的路上结伴而行,每天解锁一个成长新视角,共同把学习变成照亮生活的光!
▼ 点击关注微信公众号 ▼ Python 知行学舍