Django 应用
概览
构建全面的 Django 网络应用,包括适当的模型设计、视图层次结构、数据库操作、用户认证和管理员功能,遵循 Django 约定和最佳实践。
何时使用
- 创建 Django 网络应用
- 设计模型和数据库模式
- 实现视图和 URL 路由
- 构建认证系统
- 使用 Django ORM 进行数据库操作
- 创建管理员界面和仪表板
指南
1. Django 项目设置
django-admin startproject myproject
cd myproject
python manage.py startapp users
python manage.py startapp products
2. ORM 模型设计
# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.contrib.auth import get_user_model
class CustomUser(AbstractUser):
ROLE_CHOICES = [
('admin', '管理员'),
('user', '普通用户'),
]
profile_picture = models.ImageField(upload_to='profiles/', null=True)
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
indexes = [
models.Index(fields=['email']),
models.Index(fields=['role']),
]
def __str__(self):
return self.email
# products/models.py
User = get_user_model()
class Product(models.Model):
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
]
title = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(unique=True)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.PositiveIntegerField(default=0)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='products')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
unique_together = ['slug', 'owner']
def __str__(self):
return self.title
class ProductReview(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='reviews')
author = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)])
comment = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ['product', 'author']
3. 视图与类和函数方法
# products/views.py
from django.views import View
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render, redirect, get_object_or_404
from django.http import JsonResponse
from django.db.models import Q, Count, Avg
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from .models import Product, ProductReview
from .serializers import ProductSerializer
# 带有认证的类视图
class ProductListView(LoginRequiredMixin, ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
paginate_by = 20
def get_queryset(self):
queryset = Product.objects.filter(status='published')
# 搜索和过滤
search = self.request.GET.get('q')
if search:
queryset = queryset.filter(
Q(title__icontains=search) | Q(description__icontains=search)
)
# 价格范围过滤
min_price = self.request.GET.get('min_price')
max_price = self.request.GET.get('max_price')
if min_price:
queryset = queryset.filter(price__gte=min_price)
if max_price:
queryset = queryset.filter(price__lte=max_price)
return queryset.annotate(
review_count=Count('reviews'),
avg_rating=Avg('reviews__rating')
).order_by('-created_at')
# REST API ViewSet
class ProductViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = ProductSerializer
queryset = Product.objects.all()
def get_queryset(self):
return Product.objects.filter(owner=self.request.user)
@action(detail=True, methods=['post'])
def add_review(self, request, pk=None):
product = self.get_object()
serializer = ProductReviewSerializer(data=request.data)
if serializer.is_valid():
serializer.save(product=product, author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=True, methods=['get'])
def reviews(self, request, pk=None):
product = self.get_object()
reviews = product.reviews.all()
serializer = ProductReviewSerializer(reviews, many=True)
return Response(serializer.data)
4. 认证和权限
# users/views.py
from django.contrib.auth import authenticate, login, logout
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_protect
from rest_framework.authtoken.models import Token
from rest_framework.permissions import BasePermission
class IsOwner(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
@require_http_methods(['POST'])
@csrf_protect
def login_view(request):
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(request, username=email, password=password)
if user is not None:
login(request, user)
token, created = Token.objects.get_or_create(user=user)
return JsonResponse({
'success': True,
'token': token.key,
'user_id': user.id
})
return JsonResponse({'error': 'Invalid credentials'}, status=401)
@require_http_methods(['POST'])
def logout_view(request):
logout(request)
return JsonResponse({'success': True})
5. 数据库查询和优化
# products/queries.py
from django.db.models import Q, Count, Avg, F, Case, When, Value
from django.db.models.functions import Coalesce
from .models import Product, ProductReview
# 使用 select_related 和 prefetch_related 优化查询
def get_product_details(product_id):
return Product.objects.select_related('owner').prefetch_related(
'reviews__author'
).get(id=product_id)
# 聚合查询
def get_top_products():
return Product.objects.annotate(
review_count=Count('reviews'),
avg_rating=Avg('reviews__rating'),
total_reviews=Count('reviews', distinct=True)
).filter(review_count__gt=0).order_by('-avg_rating')[:10]
# 复杂过滤
def search_products(query, category=None, min_price=None, max_price=None):
queryset = Product.objects.filter(
Q(title__icontains=query) | Q(description__icontains=query)
)
if category:
queryset = queryset.filter(category=category)
if min_price:
queryset = queryset.filter(price__gte=min_price)
if max_price:
queryset = queryset.filter(price__lte=max_price)
return queryset.select_related('owner')
# 批量操作
def bulk_update_stock(updates):
products_to_update = []
for product_id, new_stock in updates.items():
product = Product.objects.get(id=product_id)
product.stock = new_stock
products_to_update.append(product)
Product.objects.bulk_update(products_to_update, ['stock'])
6. URL 路由
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework.authtoken.views import obtain_auth_token
from products.views import ProductViewSet
router = DefaultRouter()
router.register(r'products', ProductViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
path('api-token-auth/', obtain_auth_token),
]
7. 管理员界面定制
# products/admin.py
from django.contrib import admin
from .models import Product, ProductReview
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['title', 'price', 'stock', 'status', 'owner', 'created_at']
list_filter = ['status', 'created_at', 'owner']
search_fields = ['title', 'description']
readonly_fields = ['created_at', 'updated_at']
fieldsets = (
('基本信息', {
'fields': ('title', 'slug', 'owner')
}),
('详细信息', {
'fields': ('description', 'price', 'stock', 'status')
}),
('元数据', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
def save_model(self, request, obj, form, change):
if not change:
obj.owner = request.user
super().save_model(request, obj, form, change)
@admin.register(ProductReview)
class ProductReviewAdmin(admin.ModelAdmin):
list_display = ['product', 'author', 'rating', 'created_at']
list_filter = ['rating', 'created_at']
readonly_fields = ['created_at']
最佳实践
✅ 应该做
- 使用模型进行数据库操作
- 在频繁查询的字段上实现适当的索引
- 使用 select_related 和 prefetch_related 进行查询优化
- 实施认证和权限
- 使用 Django 表单进行表单验证
- 缓存昂贵的查询
- 使用管理命令进行批量操作
- 实施日志记录以进行调试
- 使用中间件处理跨领域问题
- 验证用户输入
❌ 不应该做
- 没有 ORM 使用原生 SQL
- 没有优化的 N+1 查询问题
- 将机密信息存储在代码中
- 直接信任用户输入
- 除非必要,否则不要重写模型中的 init
- 在视图中进行同步重操作
- 在生产中暴露堆栈跟踪
- 除非必要,否则不要使用继承模型
完整示例
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'rest_framework',
'users',
'products'
]
AUTH_USER_MODEL = 'users.CustomUser'
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
# models.py + views.py (见上文部分)
# urls.py + admin.py (见上文部分)