【后端】Django详解:Python Web开发框架
前言
Django是一个高级的Python Web框架,由Lawrence Journal-World的开发团队于2005年创建并开源。它以”快速开发、干净设计”为核心理念,遵循DRY(Don’t Repeat Yourself)原则,为开发者提供了一个功能完整、开箱即用的Web开发解决方案。
为什么选择Django?
- 完整的框架:内置ORM、模板引擎、表单处理、用户认证、管理后台等功能
- 安全性优先:内置多种安全防护机制,如CSRF保护、SQL注入防护、XSS防护等
- 可扩展性强:支持大规模应用开发,Instagram、Pinterest等知名网站都在使用
- 丰富的生态:拥有庞大的第三方包生态系统和活跃的社区支持
- 文档完善:官方文档详细全面,学习曲线相对平缓
本文内容概览
本文将全面介绍Django框架的核心概念和实践应用,包括:
- MTV架构模式:理解Django的设计哲学和架构思想
- ORM系统详解:掌握数据库操作和模型设计的最佳实践
- 模板引擎系统:学习Django模板语言和前端集成
- 表单处理机制:实现数据验证和用户交互
- 用户认证授权:构建安全的用户管理系统
- REST API开发:使用Django REST Framework构建现代API
- 高级特性应用:中间件、缓存、信号等进阶功能
- 性能优化策略:提升应用性能和用户体验
- 部署与运维:生产环境的配置和最佳实践
一、Django核心概念
(一)MTV架构模式
1. Model(模型)
- 数据层:定义数据结构和业务逻辑
- ORM映射:对象关系映射,简化数据库操作
- 数据验证:内置数据验证机制
2. Template(模板)
- 表现层:负责数据的展示
- 模板语言:Django模板语言(DTL)
- 模板继承:支持模板继承和包含
3. View(视图)
- 控制层:处理用户请求和业务逻辑
- 函数视图:基于函数的视图
- 类视图:基于类的视图
# models.py - 模型示例
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator, MaxValueValidator
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Categories"
ordering = ['name']
def __str__(self):
return self.name
class Product(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
('archived', 'Archived'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
description = models.TextField()
price = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(0)]
)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='products'
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='products'
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='draft'
)
featured = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
indexes = [
models.Index(fields=['status', 'created_at']),
models.Index(fields=['category', 'status']),
]
def __str__(self):
return self.title
def get_absolute_url(self):
from django.urls import reverse
return reverse('product_detail', kwargs={'slug': self.slug})
class Review(models.Model):
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name='reviews'
)
user = models.ForeignKey(
User,
on_delete=models.CASCADE
)
rating = models.IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(5)]
)
comment = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ['product', 'user']
ordering = ['-created_at']
def __str__(self):
return f'{self.user.username} - {self.product.title}'
# views.py - 视图示例
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
from django.db.models import Q, Avg, Count
from django.contrib import messages
from .models import Product, Category, Review
from .forms import ProductForm, ReviewForm
# 函数视图示例
def product_list(request):
products = Product.objects.filter(status='published')
# 搜索功能
query = request.GET.get('q')
if query:
products = products.filter(
Q(title__icontains=query) |
Q(description__icontains=query)
)
# 分类筛选
category_id = request.GET.get('category')
if category_id:
products = products.filter(category_id=category_id)
# 排序
sort_by = request.GET.get('sort', '-created_at')
if sort_by in ['title', '-title', 'price', '-price', 'created_at', '-created_at']:
products = products.order_by(sort_by)
# 分页
paginator = Paginator(products, 12)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
# 获取分类列表
categories = Category.objects.annotate(
product_count=Count('products', filter=Q(products__status='published'))
).filter(product_count__gt=0)
context = {
'page_obj': page_obj,
'categories': categories,
'current_category': category_id,
'query': query,
'sort_by': sort_by,
}
return render(request, 'products/list.html', context)
def product_detail(request, slug):
product = get_object_or_404(
Product.objects.select_related('category', 'author'),
slug=slug,
status='published'
)
# 获取评论
reviews = product.reviews.select_related('user').all()
# 计算平均评分
avg_rating = reviews.aggregate(Avg('rating'))['rating__avg']
# 相关产品
related_products = Product.objects.filter(
category=product.category,
status='published'
).exclude(id=product.id)[:4]
# 处理评论表单
review_form = None
if request.user.is_authenticated:
if request.method == 'POST':
review_form = ReviewForm(request.POST)
if review_form.is_valid():
review = review_form.save(commit=False)
review.product = product
review.user = request.user
review.save()
messages.success(request, '评论提交成功!')
return redirect('product_detail', slug=slug)
else:
# 检查用户是否已经评论过
existing_review = reviews.filter(user=request.user).first()
if not existing_review:
review_form = ReviewForm()
context = {
'product': product,
'reviews': reviews,
'avg_rating': avg_rating,
'related_products': related_products,
'review_form': review_form,
}
return render(request, 'products/detail.html', context)
# 类视图示例
class ProductListView(ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
paginate_by = 12
def get_queryset(self):
queryset = Product.objects.filter(status='published')
# 搜索
query = self.request.GET.get('q')
if query:
queryset = queryset.filter(
Q(title__icontains=query) |
Q(description__icontains=query)
)
# 分类筛选
category_id = self.request.GET.get('category')
if category_id:
queryset = queryset.filter(category_id=category_id)
return queryset.select_related('category', 'author')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.annotate(
product_count=Count('products', filter=Q(products__status='published'))
).filter(product_count__gt=0)
return context
class ProductDetailView(DetailView):
model = Product
template_name = 'products/detail.html'
context_object_name = 'product'
slug_field = 'slug'
def get_queryset(self):
return Product.objects.filter(status='published').select_related(
'category', 'author'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
product = self.object
# 获取评论和平均评分
reviews = product.reviews.select_related('user').all()
context['reviews'] = reviews
context['avg_rating'] = reviews.aggregate(Avg('rating'))['rating__avg']
# 相关产品
context['related_products'] = Product.objects.filter(
category=product.category,
status='published'
).exclude(id=product.id)[:4]
return context
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
form_class = ProductForm
template_name = 'products/create.html'
def form_valid(self, form):
form.instance.author = self.request.user
messages.success(self.request, '产品创建成功!')
return super().form_valid(form)
@login_required
def ajax_add_review(request, product_id):
if request.method == 'POST':
product = get_object_or_404(Product, id=product_id, status='published')
# 检查用户是否已经评论过
existing_review = Review.objects.filter(
product=product, user=request.user
).first()
if existing_review:
return JsonResponse({
'success': False,
'error': '您已经评论过这个产品了'
})
form = ReviewForm(request.POST)
if form.is_valid():
review = form.save(commit=False)
review.product = product
review.user = request.user
review.save()
return JsonResponse({
'success': True,
'message': '评论提交成功!',
'review': {
'user': review.user.username,
'rating': review.rating,
'comment': review.comment,
'created_at': review.created_at.strftime('%Y-%m-%d %H:%M')
}
})
else:
return JsonResponse({
'success': False,
'errors': form.errors
})
return JsonResponse({'success': False, 'error': '无效的请求方法'})
(二)URL配置
# urls.py - URL配置
from django.urls import path, include
from django.contrib import admin
from . import views
# 项目主URL配置
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.home, name='home'),
path('products/', include('products.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('api/', include('api.urls')),
]
# 应用URL配置 (products/urls.py)
app_name = 'products'
urlpatterns = [
path('', views.ProductListView.as_view(), name='list'),
path('create/', views.ProductCreateView.as_view(), name='create'),
path('<slug:slug>/', views.ProductDetailView.as_view(), name='detail'),
path('<slug:slug>/edit/', views.ProductUpdateView.as_view(), name='edit'),
path('<int:product_id>/review/', views.ajax_add_review, name='add_review'),
path('category/<int:category_id>/', views.category_products, name='category'),
]
二、Django ORM系统
(一)模型定义与关系
1. 字段类型
# 常用字段类型示例
from django.db import models
from django.contrib.auth.models import User
class ExampleModel(models.Model):
# 文本字段
title = models.CharField(max_length=200) # 短文本
content = models.TextField() # 长文本
slug = models.SlugField(unique=True) # URL友好的字符串
# 数字字段
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.IntegerField(default=0)
rating = models.FloatField()
# 日期时间字段
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
publish_date = models.DateField()
# 布尔字段
is_active = models.BooleanField(default=True)
is_featured = models.BooleanField(default=False)
# 选择字段
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
('archived', 'Archived'),
]
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
# 文件字段
image = models.ImageField(upload_to='images/%Y/%m/%d/')
document = models.FileField(upload_to='documents/')
# 关系字段
author = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag', blank=True)
2. 模型关系
# 一对一关系
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
# 一对多关系
class Category(models.Model):
name = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=200)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='articles'
)
# 多对多关系
class Tag(models.Model):
name = models.CharField(max_length=50)
class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag, through='PostTag')
# 自定义中间表
class PostTag(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
added_by = models.ForeignKey(User, on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ['post', 'tag']
(二)查询操作
1. 基本查询
# 基本查询示例
from django.db.models import Q, F, Count, Avg, Sum, Max, Min
from .models import Product, Category, Review
# 获取所有对象
all_products = Product.objects.all()
# 过滤查询
published_products = Product.objects.filter(status='published')
expensive_products = Product.objects.filter(price__gt=100)
recent_products = Product.objects.filter(created_at__gte='2024-01-01')
# 排除查询
non_draft_products = Product.objects.exclude(status='draft')
# 获取单个对象
product = Product.objects.get(id=1)
product = Product.objects.get(slug='example-product')
# 安全获取对象
from django.shortcuts import get_object_or_404
product = get_object_or_404(Product, slug='example-product')
# 检查存在性
exists = Product.objects.filter(slug='example').exists()
# 计数
count = Product.objects.filter(status='published').count()
# 字段查找
products = Product.objects.filter(
title__icontains='django', # 包含(忽略大小写)
price__range=(10, 100), # 范围
created_at__year=2024, # 年份
category__name='Technology' # 关联查询
)
2. 复杂查询
# Q对象复杂查询
from django.db.models import Q
# OR查询
products = Product.objects.filter(
Q(title__icontains='python') | Q(title__icontains='django')
)
# AND查询
products = Product.objects.filter(
Q(status='published') & Q(price__lt=50)
)
# NOT查询
products = Product.objects.filter(
~Q(status='draft')
)
# 复杂组合
products = Product.objects.filter(
Q(status='published') &
(Q(title__icontains='python') | Q(description__icontains='python'))
)
# F对象字段比较
from django.db.models import F
# 比较字段值
products = Product.objects.filter(updated_at__gt=F('created_at'))
# 字段运算
Product.objects.update(price=F('price') * 1.1) # 价格上涨10%
# 聚合查询
from django.db.models import Count, Avg, Sum, Max, Min
# 聚合函数
stats = Product.objects.aggregate(
total_count=Count('id'),
avg_price=Avg('price'),
total_value=Sum('price'),
max_price=Max('price'),
min_price=Min('price')
)
# 分组聚合
category_stats = Category.objects.annotate(
product_count=Count('products'),
avg_price=Avg('products__price')
).filter(product_count__gt=0)
# 子查询
from django.db.models import OuterRef, Subquery
# 获取每个分类的最新产品
latest_products = Product.objects.filter(
category=OuterRef('pk')
).order_by('-created_at')
categories_with_latest = Category.objects.annotate(
latest_product_title=Subquery(
latest_products.values('title')[:1]
)
)
3. 性能优化
# select_related - 一对一和外键关系
products = Product.objects.select_related(
'category', 'author'
).all()
# prefetch_related - 多对多和反向外键关系
products = Product.objects.prefetch_related(
'reviews', 'tags'
).all()
# 组合使用
products = Product.objects.select_related(
'category', 'author'
).prefetch_related(
'reviews__user', 'tags'
).all()
# 自定义预取
from django.db.models import Prefetch
products = Product.objects.prefetch_related(
Prefetch(
'reviews',
queryset=Review.objects.select_related('user').filter(rating__gte=4)
)
).all()
# 只获取需要的字段
products = Product.objects.values('id', 'title', 'price')
products = Product.objects.only('title', 'price') # 延迟加载其他字段
products = Product.objects.defer('description') # 延迟加载指定字段
# 批量操作
# 批量创建
Product.objects.bulk_create([
Product(title='Product 1', price=10),
Product(title='Product 2', price=20),
])
# 批量更新
Product.objects.filter(category_id=1).update(status='published')
# 批量删除
Product.objects.filter(status='draft').delete()
三、模板系统
(一)模板语法
1. 基础模板
<!-- base.html - 基础模板 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}默认标题{% endblock %}</title>
<!-- CSS -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'home' %}">我的网站</a>
<div class="navbar-nav ms-auto">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'profile' %}">{{ user.username }}</a>
<a class="nav-link" href="{% url 'logout' %}">退出</a>
{% else %}
<a class="nav-link" href="{% url 'login' %}">登录</a>
<a class="nav-link" href="{% url 'register' %}">注册</a>
{% endif %}
</div>
</div>
</nav>
<!-- 消息提示 -->
{% if messages %}
<div class="container mt-3">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
</div>
{% endif %}
<!-- 主要内容 -->
<main class="container my-4">
{% block content %}
{% endblock %}
</main>
<!-- 页脚 -->
<footer class="bg-dark text-light py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-6">
<p>© 2024 我的网站. 保留所有权利.</p>
</div>
<div class="col-md-6 text-end">
<a href="{% url 'about' %}" class="text-light">关于我们</a> |
<a href="{% url 'contact' %}" class="text-light">联系我们</a>
</div>
</div>
</div>
</footer>
<!-- JavaScript -->
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
2. 产品列表模板
<!-- products/list.html -->
{% extends 'base.html' %}
{% load static %}
{% block title %}产品列表{% endblock %}
{% block content %}
<div class="row">
<!-- 侧边栏 -->
<div class="col-md-3">
<div class="card">
<div class="card-header">
<h5>分类筛选</h5>
</div>
<div class="card-body">
<ul class="list-unstyled">
<li><a href="{% url 'products:list' %}"
class="{% if not current_category %}fw-bold{% endif %}">全部</a></li>
{% for category in categories %}
<li>
<a href="{% url 'products:list' %}?category={{ category.id }}"
class="{% if current_category == category.id|stringformat:'s' %}fw-bold{% endif %}">
{{ category.name }} ({{ category.product_count }})
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
<!-- 搜索框 -->
<div class="card mt-3">
<div class="card-header">
<h5>搜索</h5>
</div>
<div class="card-body">
<form method="get">
<div class="mb-3">
<input type="text" name="q" class="form-control"
placeholder="搜索产品..." value="{{ query }}">
</div>
<button type="submit" class="btn btn-primary">搜索</button>
</form>
</div>
</div>
</div>
<!-- 主要内容 -->
<div class="col-md-9">
<!-- 排序和结果统计 -->
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<p class="mb-0">找到 {{ page_obj.paginator.count }} 个产品</p>
</div>
<div>
<select class="form-select" onchange="location.href=this.value">
<option value="{% url 'products:list' %}?sort=-created_at"
{% if sort_by == '-created_at' %}selected{% endif %}>最新</option>
<option value="{% url 'products:list' %}?sort=price"
{% if sort_by == 'price' %}selected{% endif %}>价格从低到高</option>
<option value="{% url 'products:list' %}?sort=-price"
{% if sort_by == '-price' %}selected{% endif %}>价格从高到低</option>
<option value="{% url 'products:list' %}?sort=title"
{% if sort_by == 'title' %}selected{% endif %}>名称A-Z</option>
</select>
</div>
</div>
<!-- 产品网格 -->
<div class="row">
{% for product in page_obj %}
<div class="col-md-4 mb-4">
<div class="card h-100">
{% if product.image %}
<img src="{{ product.image.url }}" class="card-img-top"
alt="{{ product.title }}" style="height: 200px; object-fit: cover;">
{% endif %}
<div class="card-body d-flex flex-column">
<h5 class="card-title">{{ product.title }}</h5>
<p class="card-text">{{ product.description|truncatewords:20 }}</p>
<p class="card-text">
<small class="text-muted">{{ product.category.name }}</small>
</p>
<div class="mt-auto">
<div class="d-flex justify-content-between align-items-center">
<span class="h5 mb-0 text-primary">¥{{ product.price }}</span>
<a href="{{ product.get_absolute_url }}" class="btn btn-primary">查看详情</a>
</div>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="alert alert-info text-center">
<h4>没有找到产品</h4>
<p>请尝试调整搜索条件或浏览其他分类。</p>
</div>
</div>
{% endfor %}
</div>
<!-- 分页 -->
{% if page_obj.has_other_pages %}
<nav aria-label="产品分页">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}">
首页
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}">
上一页
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}">
{{ num }}
</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}">
下一页
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category={{ current_category }}{% endif %}">
末页
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
{% endblock %}
(二)模板标签和过滤器
1. 内置标签和过滤器
<!-- 常用模板标签 -->
{% load static %}
{% load humanize %}
<!-- 条件判断 -->
{% if user.is_authenticated %}
<p>欢迎,{{ user.username }}!</p>
{% elif user.is_anonymous %}
<p>请登录</p>
{% else %}
<p>未知状态</p>
{% endif %}
<!-- 循环 -->
{% for product in products %}
<div class="product-item">
<h3>{{ product.title }}</h3>
<p>{{ product.description|truncatewords:30 }}</p>
<!-- 循环变量 -->
<small>第 {{ forloop.counter }} 个产品</small>
{% if forloop.first %}<span class="badge">最新</span>{% endif %}
</div>
{% empty %}
<p>没有产品</p>
{% endfor %}
<!-- 常用过滤器 -->
<p>价格:{{ product.price|floatformat:2 }}</p>
<p>创建时间:{{ product.created_at|date:"Y-m-d H:i" }}</p>
<p>相对时间:{{ product.created_at|timesince }}前</p>
<p>描述:{{ product.description|linebreaks }}</p>
<p>标题:{{ product.title|title }}</p>
<p>长度:{{ product.title|length }}</p>
<p>默认值:{{ product.subtitle|default:"无副标题" }}</p>
<!-- 人性化显示 -->
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>数字:{{ large_number|intcomma }}</p>
<p>序数:{{ number|ordinal }}</p>
2. 自定义模板标签
# templatetags/product_tags.py
from django import template
from django.db.models import Count, Avg
from ..models import Product, Category
register = template.Library()
@register.simple_tag
def get_featured_products(count=5):
"""获取推荐产品"""
return Product.objects.filter(
status='published',
featured=True
)[:count]
@register.simple_tag
def get_popular_categories(count=10):
"""获取热门分类"""
return Category.objects.annotate(
product_count=Count('products')
).filter(product_count__gt=0).order_by('-product_count')[:count]
@register.inclusion_tag('products/tags/product_card.html')
def product_card(product, show_category=True):
"""产品卡片组件"""
return {
'product': product,
'show_category': show_category,
}
@register.filter
def multiply(value, arg):
"""乘法过滤器"""
try:
return float(value) * float(arg)
except (ValueError, TypeError):
return 0
@register.filter
def rating_stars(rating):
"""评分星星显示"""
if not rating:
return ''
full_stars = int(rating)
half_star = 1 if rating - full_stars >= 0.5 else 0
empty_stars = 5 - full_stars - half_star
stars = '★' * full_stars + '☆' * half_star + '☆' * empty_stars
return stars
register.filter('multiply', multiply)
register.filter('rating_stars', rating_stars)
<!-- 使用自定义标签 -->
{% load product_tags %}
<!-- 简单标签 -->
{% get_featured_products 3 as featured %}
<div class="featured-products">
{% for product in featured %}
{% product_card product %}
{% endfor %}
</div>
<!-- 包含标签 -->
{% product_card product show_category=False %}
<!-- 自定义过滤器 -->
<p>总价:{{ product.price|multiply:quantity }}</p>
<p>评分:{{ product.avg_rating|rating_stars }}</p>
四、表单处理
(一)Django表单
1. 表单定义
# forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from .models import Product, Review, Category
class ProductForm(forms.ModelForm):
"""产品表单"""
class Meta:
model = Product
fields = ['title', 'slug', 'description', 'price', 'category', 'image', 'featured']
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '请输入产品标题'
}),
'slug': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '产品URL标识符'
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 5,
'placeholder': '请输入产品描述'
}),
'price': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'category': forms.Select(attrs={
'class': 'form-select'
}),
'image': forms.FileInput(attrs={
'class': 'form-control',
'accept': 'image/*'
}),
'featured': forms.CheckboxInput(attrs={
'class': 'form-check-input'
})
}
def clean_slug(self):
"""验证slug唯一性"""
slug = self.cleaned_data['slug']
if Product.objects.filter(slug=slug).exclude(pk=self.instance.pk).exists():
raise ValidationError('该URL标识符已存在')
return slug
def clean_price(self):
"""验证价格"""
price = self.cleaned_data['price']
if price <= 0:
raise ValidationError('价格必须大于0')
return price
class ReviewForm(forms.ModelForm):
"""评论表单"""
class Meta:
model = Review
fields = ['rating', 'comment']
widgets = {
'rating': forms.Select(
choices=[(i, f'{i}星') for i in range(1, 6)],
attrs={'class': 'form-select'}
),
'comment': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': '请分享您的使用体验...'
})
}
def clean_comment(self):
"""验证评论内容"""
comment = self.cleaned_data['comment']
if len(comment) < 10:
raise ValidationError('评论内容至少需要10个字符')
return comment
class ContactForm(forms.Form):
"""联系表单"""
SUBJECT_CHOICES = [
('general', '一般咨询'),
('support', '技术支持'),
('business', '商务合作'),
('complaint', '投诉建议'),
]
name = forms.CharField(
max_length=100,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '您的姓名'
})
)
email = forms.EmailField(
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': '您的邮箱'
})
)
subject = forms.ChoiceField(
choices=SUBJECT_CHOICES,
widget=forms.Select(attrs={
'class': 'form-select'
})
)
message = forms.CharField(
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 6,
'placeholder': '请详细描述您的问题或建议...'
})
)
def clean_message(self):
"""验证消息内容"""
message = self.cleaned_data['message']
if len(message) < 20:
raise ValidationError('消息内容至少需要20个字符')
return message
class CustomUserCreationForm(UserCreationForm):
"""自定义用户注册表单"""
email = forms.EmailField(
required=True,
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': '邮箱地址'
})
)
first_name = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '名字'
})
)
last_name = forms.CharField(
max_length=30,
required=True,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '姓氏'
})
)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
widgets = {
'username': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': '用户名'
}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password1'].widget.attrs.update({
'class': 'form-control',
'placeholder': '密码'
})
self.fields['password2'].widget.attrs.update({
'class': 'form-control',
'placeholder': '确认密码'
})
def clean_email(self):
"""验证邮箱唯一性"""
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise ValidationError('该邮箱已被注册')
return email
def save(self, commit=True):
"""保存用户"""
user = super().save(commit=False)
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
if commit:
user.save()
return user
2. 表单视图处理
# views.py - 表单处理视图
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth import login
from django.core.mail import send_mail
from django.conf import settings
from .forms import ProductForm, ContactForm, CustomUserCreationForm
def create_product(request):
"""创建产品"""
if request.method == 'POST':
form = ProductForm(request.POST, request.FILES)
if form.is_valid():
product = form.save(commit=False)
product.author = request.user
product.save()
messages.success(request, '产品创建成功!')
return redirect('products:detail', slug=product.slug)
else:
form = ProductForm()
return render(request, 'products/create.html', {'form': form})
def edit_product(request, slug):
"""编辑产品"""
product = get_object_or_404(Product, slug=slug, author=request.user)
if request.method == 'POST':
form = ProductForm(request.POST, request.FILES, instance=product)
if form.is_valid():
form.save()
messages.success(request, '产品更新成功!')
return redirect('products:detail', slug=product.slug)
else:
form = ProductForm(instance=product)
return render(request, 'products/edit.html', {
'form': form,
'product': product
})
def contact_view(request):
"""联系表单"""
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# 发送邮件
subject = f"联系表单:{form.cleaned_data['subject']}"
message = f"""
姓名:{form.cleaned_data['name']}
邮箱:{form.cleaned_data['email']}
主题:{form.cleaned_data['subject']}
消息内容:
{form.cleaned_data['message']}
"""
try:
send_mail(
subject,
message,
settings.DEFAULT_FROM_EMAIL,
[settings.CONTACT_EMAIL],
fail_silently=False,
)
messages.success(request, '您的消息已发送,我们会尽快回复!')
return redirect('contact')
except Exception as e:
messages.error(request, '发送失败,请稍后重试。')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
def register_view(request):
"""用户注册"""
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
messages.success(request, f'欢迎,{user.username}!注册成功。')
return redirect('home')
else:
form = CustomUserCreationForm()
return render(request, 'registration/register.html', {'form': form})
(二)表单模板
<!-- products/create.html -->
{% extends 'base.html' %}
{% block title %}创建产品{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3>创建新产品</h3>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<!-- 显示表单错误 -->
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.title.id_for_label }}" class="form-label">产品标题 *</label>
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger small">
{{ form.title.errors }}
</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.slug.id_for_label }}" class="form-label">URL标识符 *</label>
{{ form.slug }}
{% if form.slug.errors %}
<div class="text-danger small">
{{ form.slug.errors }}
</div>
{% endif %}
<div class="form-text">用于生成产品页面URL</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="{{ form.description.id_for_label }}" class="form-label">产品描述 *</label>
{{ form.description }}
{% if form.description.errors %}
<div class="text-danger small">
{{ form.description.errors }}
</div>
{% endif %}
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.price.id_for_label }}" class="form-label">价格 *</label>
<div class="input-group">
<span class="input-group-text">¥</span>
{{ form.price }}
</div>
{% if form.price.errors %}
<div class="text-danger small">
{{ form.price.errors }}
</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.category.id_for_label }}" class="form-label">分类 *</label>
{{ form.category }}
{% if form.category.errors %}
<div class="text-danger small">
{{ form.category.errors }}
</div>
{% endif %}
</div>
</div>
</div>
<div class="mb-3">
<label for="{{ form.image.id_for_label }}" class="form-label">产品图片</label>
{{ form.image }}
{% if form.image.errors %}
<div class="text-danger small">
{{ form.image.errors }}
</div>
{% endif %}
<div class="form-text">支持JPG、PNG格式,建议尺寸800x600像素</div>
</div>
<div class="mb-3 form-check">
{{ form.featured }}
<label class="form-check-label" for="{{ form.featured.id_for_label }}">
设为推荐产品
</label>
</div>
<div class="d-flex justify-content-between">
<a href="{% url 'products:list' %}" class="btn btn-secondary">取消</a>
<button type="submit" class="btn btn-primary">创建产品</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
// 自动生成slug
document.getElementById('id_title').addEventListener('input', function() {
const title = this.value;
const slug = title.toLowerCase()
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-')
.replace(/^-+|-+$/g, '');
document.getElementById('id_slug').value = slug;
});
</script>
{% endblock %}
五、用户认证与权限
(一)用户认证系统
1. 认证配置
# settings.py
AUTH_USER_MODEL = 'accounts.User' # 自定义用户模型
# 认证后端
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'accounts.backends.EmailBackend', # 邮箱登录
]
# 登录/登出重定向
LOGIN_URL = '/accounts/login/'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
# 密码验证
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 8,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
2. 自定义用户模型
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True)
bio = models.TextField(max_length=500, blank=True)
birth_date = models.DateField(null=True, blank=True)
location = models.CharField(max_length=100, blank=True)
website = models.URLField(blank=True)
# 邮箱验证
email_verified = models.BooleanField(default=False)
email_verification_token = models.CharField(max_length=100, blank=True)
# 隐私设置
is_profile_public = models.BooleanField(default=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
def get_full_name(self):
return f'{self.first_name} {self.last_name}'.strip() or self.username
def get_short_name(self):
return self.first_name or self.username
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# 社交媒体链接
github_url = models.URLField(blank=True)
linkedin_url = models.URLField(blank=True)
twitter_url = models.URLField(blank=True)
# 通知设置
email_notifications = models.BooleanField(default=True)
sms_notifications = models.BooleanField(default=False)
# 统计信息
profile_views = models.PositiveIntegerField(default=0)
last_login_ip = models.GenericIPAddressField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
3. 认证视图
# accounts/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.core.mail import send_mail
from django.utils.crypto import get_random_string
from django.urls import reverse
from .forms import CustomUserCreationForm, UserProfileForm
from .models import User
def register_view(request):
"""用户注册"""
if request.user.is_authenticated:
return redirect('home')
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
# 发送验证邮件
token = get_random_string(50)
user.email_verification_token = token
user.save()
verification_url = request.build_absolute_uri(
reverse('verify_email', kwargs={'token': token})
)
send_mail(
'验证您的邮箱',
f'请点击链接验证邮箱:{verification_url}',
'noreply@example.com',
[user.email],
fail_silently=False,
)
messages.success(request, '注册成功!请检查邮箱并验证。')
return redirect('login')
else:
form = CustomUserCreationForm()
return render(request, 'registration/register.html', {'form': form})
@login_required
def profile_view(request):
"""用户资料"""
if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES, instance=request.user)
if form.is_valid():
form.save()
messages.success(request, '资料更新成功!')
return redirect('profile')
else:
form = UserProfileForm(instance=request.user)
return render(request, 'accounts/profile.html', {'form': form})
def verify_email(request, token):
"""邮箱验证"""
try:
user = User.objects.get(email_verification_token=token)
user.email_verified = True
user.email_verification_token = ''
user.save()
messages.success(request, '邮箱验证成功!')
return redirect('login')
except User.DoesNotExist:
messages.error(request, '验证链接无效或已过期。')
return redirect('home')
(二)权限系统
1. 权限装饰器
# decorators.py
from functools import wraps
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
def owner_required(model):
"""要求用户是对象的所有者"""
def decorator(view_func):
@wraps(view_func)
@login_required
def _wrapped_view(request, *args, **kwargs):
obj_id = kwargs.get('pk') or kwargs.get('id')
obj = get_object_or_404(model, pk=obj_id)
if hasattr(obj, 'author') and obj.author != request.user:
raise PermissionDenied
elif hasattr(obj, 'user') and obj.user != request.user:
raise PermissionDenied
elif hasattr(obj, 'owner') and obj.owner != request.user:
raise PermissionDenied
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
def staff_required(view_func):
"""要求用户是员工"""
@wraps(view_func)
@login_required
def _wrapped_view(request, *args, **kwargs):
if not request.user.is_staff:
raise PermissionDenied
return view_func(request, *args, **kwargs)
return _wrapped_view
2. 基于类的权限
# mixins.py
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.exceptions import PermissionDenied
class OwnerRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
"""要求用户是对象所有者的混入类"""
def test_func(self):
obj = self.get_object()
return (
hasattr(obj, 'author') and obj.author == self.request.user or
hasattr(obj, 'user') and obj.user == self.request.user or
hasattr(obj, 'owner') and obj.owner == self.request.user
)
class StaffRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
"""要求用户是员工的混入类"""
def test_func(self):
return self.request.user.is_staff
# 使用示例
class ProductUpdateView(OwnerRequiredMixin, UpdateView):
model = Product
form_class = ProductForm
template_name = 'products/edit.html'
def form_valid(self, form):
messages.success(self.request, '产品更新成功!')
return super().form_valid(form)
六、Django REST Framework
(一)API配置
1. 基础配置
# settings.py
INSTALLED_APPS = [
# ... 其他应用
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
# ... 其他中间件
]
# DRF配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
}
# CORS配置
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000",
]
2. 序列化器
# serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Product, Category, Review
class CategorySerializer(serializers.ModelSerializer):
product_count = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ['id', 'name', 'description', 'product_count']
def get_product_count(self, obj):
return obj.products.filter(status='published').count()
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'first_name', 'last_name']
class ReviewSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Review
fields = ['id', 'user', 'rating', 'comment', 'created_at']
read_only_fields = ['user', 'created_at']
class ProductListSerializer(serializers.ModelSerializer):
category = CategorySerializer(read_only=True)
author = UserSerializer(read_only=True)
avg_rating = serializers.SerializerMethodField()
review_count = serializers.SerializerMethodField()
class Meta:
model = Product
fields = [
'id', 'title', 'slug', 'description', 'price',
'category', 'author', 'image', 'featured',
'avg_rating', 'review_count', 'created_at'
]
def get_avg_rating(self, obj):
return obj.reviews.aggregate(models.Avg('rating'))['rating__avg']
def get_review_count(self, obj):
return obj.reviews.count()
class ProductDetailSerializer(ProductListSerializer):
reviews = ReviewSerializer(many=True, read_only=True)
class Meta(ProductListSerializer.Meta):
fields = ProductListSerializer.Meta.fields + ['reviews']
class ProductCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = [
'title', 'slug', 'description', 'price',
'category', 'image', 'featured'
]
def validate_slug(self, value):
if Product.objects.filter(slug=value).exclude(pk=self.instance.pk if self.instance else None).exists():
raise serializers.ValidationError('该URL标识符已存在')
return value
def validate_price(self, value):
if value <= 0:
raise serializers.ValidationError('价格必须大于0')
return value
3. API视图
# api/views.py
from rest_framework import generics, status, filters
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from django_filters.rest_framework import DjangoFilterBackend
from django.db.models import Q
from .serializers import *
from .models import Product, Category, Review
class CategoryListView(generics.ListAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class ProductListCreateView(generics.ListCreateAPIView):
serializer_class = ProductListSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['category', 'featured', 'status']
search_fields = ['title', 'description']
ordering_fields = ['created_at', 'price', 'title']
ordering = ['-created_at']
def get_queryset(self):
return Product.objects.filter(status='published').select_related(
'category', 'author'
).prefetch_related('reviews')
def get_serializer_class(self):
if self.request.method == 'POST':
return ProductCreateUpdateSerializer
return ProductListSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class ProductDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Product.objects.filter(status='published')
lookup_field = 'slug'
permission_classes = [IsAuthenticatedOrReadOnly]
def get_serializer_class(self):
if self.request.method in ['PUT', 'PATCH']:
return ProductCreateUpdateSerializer
return ProductDetailSerializer
def get_queryset(self):
return Product.objects.filter(status='published').select_related(
'category', 'author'
).prefetch_related('reviews__user')
def perform_update(self, serializer):
# 只允许作者更新
if self.get_object().author != self.request.user:
raise PermissionDenied('您只能编辑自己的产品')
serializer.save()
def perform_destroy(self, instance):
# 只允许作者删除
if instance.author != self.request.user:
raise PermissionDenied('您只能删除自己的产品')
instance.delete()
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def add_review(request, product_slug):
"""添加产品评论"""
try:
product = Product.objects.get(slug=product_slug, status='published')
except Product.DoesNotExist:
return Response(
{'error': '产品不存在'},
status=status.HTTP_404_NOT_FOUND
)
# 检查用户是否已经评论过
if Review.objects.filter(product=product, user=request.user).exists():
return Response(
{'error': '您已经评论过这个产品了'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = ReviewSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, product=product)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET'])
def search_products(request):
"""产品搜索"""
query = request.GET.get('q', '')
if not query:
return Response({'results': []})
products = Product.objects.filter(
Q(title__icontains=query) | Q(description__icontains=query),
status='published'
).select_related('category', 'author')[:10]
serializer = ProductListSerializer(products, many=True)
return Response({'results': serializer.data})
七、缓存与性能优化
(一)缓存配置
1. Redis缓存
# settings.py
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
'KEY_PREFIX': 'myapp',
'TIMEOUT': 300,
}
}
# 会话存储
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
2. 缓存使用
# utils/cache.py
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
from functools import wraps
import hashlib
def cache_result(timeout=300, key_prefix=''):
"""缓存函数结果装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 生成缓存键
cache_key = f"{key_prefix}:{func.__name__}:{hashlib.md5(str(args + tuple(kwargs.items())).encode()).hexdigest()}"
# 尝试从缓存获取
result = cache.get(cache_key)
if result is not None:
return result
# 执行函数并缓存结果
result = func(*args, **kwargs)
cache.set(cache_key, result, timeout)
return result
return wrapper
return decorator
# 使用示例
@cache_result(timeout=600, key_prefix='products')
def get_featured_products():
return Product.objects.filter(
status='published',
featured=True
).select_related('category')[:5]
@cache_result(timeout=3600, key_prefix='stats')
def get_site_statistics():
return {
'total_products': Product.objects.filter(status='published').count(),
'total_categories': Category.objects.count(),
'total_reviews': Review.objects.count(),
}
(二)数据库优化
1. 查询优化
# 优化查询示例
class OptimizedProductViewSet(viewsets.ModelViewSet):
def get_queryset(self):
# 使用select_related和prefetch_related
return Product.objects.select_related(
'category', 'author'
).prefetch_related(
'reviews__user', 'tags'
).filter(status='published')
def list(self, request, *args, **kwargs):
# 使用缓存
cache_key = f"products_list_{request.GET.urlencode()}"
cached_data = cache.get(cache_key)
if cached_data:
return Response(cached_data)
response = super().list(request, *args, **kwargs)
cache.set(cache_key, response.data, 300)
return response
# 数据库索引
class Product(models.Model):
# ... 字段定义
class Meta:
indexes = [
models.Index(fields=['status', 'created_at']),
models.Index(fields=['category', 'status']),
models.Index(fields=['featured', 'status']),
]
八、部署与配置
(一)生产环境配置
1. 设置文件分离
# settings/base.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
# 基础配置
SECRET_KEY = os.environ.get('SECRET_KEY')
DEBUG = False
ALLOWED_HOSTS = []
# 应用配置
DJANGO_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
THIRD_PARTY_APPS = [
'rest_framework',
'corsheaders',
'django_filters',
]
LOCAL_APPS = [
'accounts',
'products',
'api',
]
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# 数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
# settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# 安全设置
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
# 静态文件
STATIC_ROOT = '/var/www/static/'
MEDIA_ROOT = '/var/www/media/'
# 日志配置
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '/var/log/django/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
},
}
2. Docker部署
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制项目文件
COPY . .
# 收集静态文件
RUN python manage.py collectstatic --noinput
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DEBUG=False
- SECRET_KEY=your-secret-key
- DB_NAME=myapp
- DB_USER=postgres
- DB_PASSWORD=password
- DB_HOST=db
depends_on:
- db
- redis
volumes:
- static_volume:/app/static
- media_volume:/app/media
db:
image: postgres:13
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:6-alpine
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- static_volume:/static
- media_volume:/media
depends_on:
- web
volumes:
postgres_data:
static_volume:
media_volume:
九、最佳实践与总结
(一)开发最佳实践
1. 项目结构
myproject/
├── manage.py
├── requirements/
│ ├── base.txt
│ ├── development.txt
│ └── production.txt
├── myproject/
│ ├── __init__.py
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── development.py
│ │ └── production.py
│ ├── urls.py
│ └── wsgi.py
├── apps/
│ ├── accounts/
│ ├── products/
│ └── api/
├── static/
├── media/
├── templates/
└── tests/
2. 代码质量
# 使用类型提示
from typing import List, Optional
from django.http import HttpRequest, HttpResponse
def get_products(request: HttpRequest, category_id: Optional[int] = None) -> HttpResponse:
products: List[Product] = Product.objects.filter(status='published')
if category_id:
products = products.filter(category_id=category_id)
return render(request, 'products/list.html', {'products': products})
# 使用常量
class ProductStatus:
DRAFT = 'draft'
PUBLISHED = 'published'
ARCHIVED = 'archived'
CHOICES = [
(DRAFT, 'Draft'),
(PUBLISHED, 'Published'),
(ARCHIVED, 'Archived'),
]
# 模型方法
class Product(models.Model):
# ... 字段定义
def is_published(self) -> bool:
return self.status == ProductStatus.PUBLISHED
def can_be_edited_by(self, user) -> bool:
return self.author == user or user.is_staff
(二)安全最佳实践
1. 输入验证
# 表单验证
class ProductForm(forms.ModelForm):
def clean_title(self):
title = self.cleaned_data['title']
# 防止XSS
title = bleach.clean(title, tags=[], strip=True)
if len(title) < 3:
raise ValidationError('标题至少需要3个字符')
return title
# API验证
class ProductSerializer(serializers.ModelSerializer):
def validate_price(self, value):
if value < 0:
raise serializers.ValidationError('价格不能为负数')
if value > 999999:
raise serializers.ValidationError('价格过高')
return value
2. 权限控制
# 基于对象的权限
class ProductPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# 读取权限:所有人
if request.method in permissions.SAFE_METHODS:
return True
# 写入权限:仅作者
return obj.author == request.user
(三)总结
Django是一个功能强大且成熟的Web框架,具有以下优势:
1. 核心优势
- 快速开发:内置管理后台、ORM、模板引擎
- 安全性:内置CSRF、XSS、SQL注入防护
- 可扩展性:丰富的第三方包生态
- 文档完善:官方文档详细且更新及时
2. 适用场景
- 内容管理系统:博客、新闻网站
- 电商平台:在线商店、市场平台
- 企业应用:CRM、ERP系统
- API服务:RESTful API、微服务
3. 学习建议
- 掌握基础:Python基础、Web开发概念
- 实践项目:从简单项目开始,逐步增加复杂度
- 阅读源码:理解Django内部机制
- 关注社区:参与开源项目,学习最佳实践
Django为Python Web开发提供了一个完整的解决方案,无论是快速原型开发还是大型企业应用,都能找到合适的解决方案。通过合理的架构设计和最佳实践,可以构建出高性能、可维护的Web应用。