Python Django 开发
大约 8 分钟约 2426 字
Python Django 开发
简介
Django 是 Python 最成熟的"全栈"Web 框架,内置 ORM、Admin 后台、认证系统、模板引擎和 URL 路由,遵循 MVT(Model-View-Template)架构。它强调" batteries included"和约定优于配置,适合快速构建内容管理系统、后台管理平台和企业内部工具。
特点
实现
项目结构与模型定义
# models.py - 数据模型定义
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
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)
class Meta:
verbose_name = "分类"
verbose_name_plural = "分类"
ordering = ["-created_at"]
def __str__(self):
return self.name
class Article(models.Model):
STATUS_CHOICES = [
("draft", "草稿"),
("published", "已发布"),
("archived", "已归档"),
]
title = models.CharField("标题", max_length=200, db_index=True)
slug = models.SlugField("URL 别名", max_length=200, unique=True)
content = models.TextField("内容")
summary = models.CharField("摘要", max_length=500, blank=True)
status = models.CharField("状态", max_length=20, choices=STATUS_CHOICES, default="draft")
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="articles")
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="articles")
tags = models.ManyToManyField("Tag", blank=True, related_name="articles")
published_at = models.DateTimeField("发布时间", null=True, blank=True)
created_at = models.DateTimeField("创建时间", auto_now_add=True)
updated_at = models.DateTimeField("更新时间", auto_now=True)
class Meta:
verbose_name = "文章"
verbose_name_plural = "文章"
ordering = ["-published_at"]
indexes = [
models.Index(fields=["status", "-published_at"]),
]
def __str__(self):
return self.title
class Tag(models.Model):
name = models.CharField("标签名", max_length=50, unique=True)
def __str__(self):
return self.name视图与序列化(Django REST Framework)
# serializers.py
from rest_framework import serializers
from .models import Article, Category, Tag
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ["id", "name"]
class CategorySerializer(serializers.ModelSerializer):
article_count = serializers.IntegerField(read_only=True)
class Meta:
model = Category
fields = ["id", "name", "description", "article_count"]
class ArticleListSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.username", read_only=True)
category_name = serializers.CharField(source="category.name", read_only=True)
class Meta:
model = Article
fields = ["id", "title", "slug", "summary", "status", "author_name", "category_name", "published_at"]
class ArticleDetailSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.username", read_only=True)
category = CategorySerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Article
fields = "__all__"
# views.py - API 视图
from rest_framework import viewsets, filters, status, permissions
from rest_framework.decorators import action
from rest_framework.response import Response
from django.utils import timezone
from .models import Article, Category, Tag
from .serializers import ArticleListSerializer, ArticleDetailSerializer, CategorySerializer
class ArticleViewSet(viewsets.ModelViewSet):
"""文章 API ViewSet"""
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ["title", "content"]
ordering_fields = ["published_at", "created_at"]
ordering = ["-published_at"]
def get_queryset(self):
queryset = Article.objects.select_related("author", "category").prefetch_related("tags")
status_filter = self.request.query_params.get("status")
if status_filter:
queryset = queryset.filter(status=status_filter)
return queryset
def get_serializer_class(self):
if self.action == "list":
return ArticleListSerializer
return ArticleDetailSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=["post"])
def publish(self, request, pk=None):
article = self.get_object()
article.status = "published"
article.published_at = timezone.now()
article.save()
return Response({"status": "published", "published_at": article.published_at})
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.annotate(
article_count=models.Count("articles")
)
serializer_class = CategorySerializerURL 配置与中间件
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r"articles", views.ArticleViewSet)
router.register(r"categories", views.CategoryViewSet)
urlpatterns = [
path("api/", include(router.urls)),
path("api/auth/", include("rest_framework.urls")),
]
# middleware.py - 自定义中间件
import time
import logging
logger = logging.getLogger(__name__)
class RequestTimingMiddleware:
"""请求计时中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start = time.time()
response = self.get_response(request)
elapsed = time.time() - start
if elapsed > 1.0:
logger.warning(f"慢请求: {request.method} {request.path} 耗时 {elapsed:.3f}s")
response["X-Response-Time"] = f"{elapsed:.3f}s"
return response
# settings.py 关键配置片段
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"myapp",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageAddRmMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"myapp.middleware.RequestTimingMiddleware",
]
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}Django Admin 自定义
# admin.py - Admin 后台配置
from django.contrib import admin
from django.utils.html import format_html
from .models import Article, Category, Tag
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ["name", "description", "article_count", "created_at"]
search_fields = ["name"]
prepopulated_fields = {"slug": ("name",)}
def article_count(self, obj):
return obj.articles.count()
article_count.short_description = "文章数"
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author", "category", "status", "published_at", "is_recent"]
list_filter = ["status", "category", "author", "created_at"]
search_fields = ["title", "content"]
prepopulated_fields = {"slug": ("title",)}
date_hierarchy = "published_at"
list_editable = ["status"]
readonly_fields = ["created_at", "updated_at"]
filter_horizontal = ["tags"]
actions = ["make_published"]
@admin.action(description="发布选中的文章")
def make_published(self, request, queryset):
from django.utils import timezone
updated = queryset.filter(status="draft").update(
status="published", published_at=timezone.now()
)
self.message_user(request, f"已发布 {updated} 篇文章")
def is_recent(self, obj):
if obj.published_at and (timezone.now() - obj.published_at).days < 7:
return format_html('<span style="color: green;">新发布</span>')
return "-"
is_recent.short_description = "近期发布"Django 信号与自定义管理命令
# signals.py - Django 信号
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import Article
@receiver(post_save, sender=Article)
def on_article_created(sender, instance, created, **kwargs):
"""文章创建后触发"""
if created:
from django.core.cache import cache
cache.delete("article_list_cache")
# 发送通知、更新搜索索引等
@receiver(pre_delete, sender=Article)
def on_article_delete(sender, instance, **kwargs):
"""文章删除前清理关联资源"""
# 删除文章的封面图片、评论等
pass
# 在 apps.py 中注册信号
# class MyAppConfig(AppConfig):
# def ready(self):
# import myapp.signals# management/commands/generate_sitemap.py - 自定义管理命令
from django.core.management.base import BaseCommand
from django.contrib.sitemaps import views as sitemap_views
from django.http import HttpRequest
class Command(BaseCommand):
help = "生成站点地图"
def add_arguments(self, parser):
parser.add_argument("--output", type=str, default="sitemap.xml")
parser.add_argument("--domain", type=str, default="https://example.com")
def handle(self, *args, **options):
self.stdout.write(f"生成站点地图到: {options['output']}")
# 生成逻辑
self.stdout.write(self.style.SUCCESS("站点地图生成完成"))
# management/commands/cleanup_expired.py
from django.core.management.base import BaseCommand
from django.utils import timezone
from datetime import timedelta
class Command(BaseCommand):
help = "清理过期数据"
def handle(self, *args, **options):
from myapp.models import Article
cutoff = timezone.now() - timedelta(days=365)
expired = Article.objects.filter(
status="draft", updated_at__lt=cutoff
)
count = expired.count()
expired.delete()
self.stdout.write(f"清理了 {count} 篇过期草稿")Django 测试
# tests/test_views.py
from django.test import TestCase, Client
from django.urls import reverse
from .models import Article, Category
class ArticleViewTest(TestCase):
def setUp(self):
self.client = Client()
self.category = Category.objects.create(name="技术", description="技术类文章")
def test_article_list(self):
"""测试文章列表 API"""
Article.objects.create(
title="测试文章",
slug="test-article",
content="内容",
category=self.category,
author_id=1,
status="published",
published_at=timezone.now(),
)
response = self.client.get("/api/articles/")
self.assertEqual(response.status_code, 200)
def test_article_create_requires_auth(self):
"""测试创建文章需要认证"""
response = self.client.post("/api/articles/", {
"title": "未认证文章",
})
self.assertIn(response.status_code, [401, 403])
class ArticleModelTest(TestCase):
def test_article_str(self):
article = Article(title="测试")
self.assertEqual(str(article), "测试")
def test_slug_auto_generate(self):
article = Article.objects.create(
title="Django 开发指南",
slug="django-dev-guide",
content="内容",
category=self.category,
author_id=1,
)
self.assertEqual(article.slug, "django-dev-guide")
# 运行测试
# python manage.py test myapp.tests --verbosity=2
# python manage.py test myapp.tests.ArticleViewTest --parallel 4Django ORM 高级查询优化
from django.db import models
from django.db.models import Count, Sum, Avg, Q, F, Prefetch
from django.utils import timezone
# 1. select_related:外键预加载(一对一、外键)
# 避免 N+1 查询
articles = Article.objects.select_related("author", "category").all()
# 2. prefetch_related:多对多预加载
articles = Article.objects.prefetch_related("tags").all()
# 3. Prefetch 对象:控制预加载查询
from django.db.models import Prefetch
categories = Category.objects.prefetch_related(
Prefetch(
"articles",
queryset=Article.objects.filter(status="published").only("id", "title"),
)
)
# 4. 聚合查询
stats = Article.objects.aggregate(
total=Count("id"),
published=Count("id", filter=Q(status="published")),
avg_views=Avg("view_count"),
)
# 5. 批量操作(避免循环中逐条保存)
from django.db.models import F
# 批量更新(一条 SQL)
Article.objects.filter(status="draft").update(status="published")
# 使用 F() 表达式原子操作
Article.objects.filter(id=1).update(view_count=F("view_count") + 1)
# 6. 条件表达式
from django.db.models import Case, When, Value, CharField
articles = Article.objects.annotate(
status_label=Case(
When(status="draft", then=Value("草稿")),
When(status="published", then=Value("已发布")),
default=Value("其他"),
output_field=CharField(),
)
)
# 7. only / defer:延迟加载
# only:只加载指定字段
articles = Article.objects.only("id", "title", "slug")
# defer:延迟加载指定字段
articles = Article.objects.defer("content")
# 8. 慢查询监控
from django.db import connection, reset_queries
import logging
logger = logging.getLogger(__name__)
def log_slow_queries(threshold=1.0):
"""记录慢查询"""
for query in connection.queries:
time = float(query["time"])
if time > threshold:
logger.warning(f"慢查询 ({time:.3f}s): {query['sql'][:200]}")优点
缺点
总结
Django 是构建内容管理、后台系统和快速原型的最佳选择。它的"全栈"特性让开发者无需花时间选型和拼装组件,专注于业务逻辑即可。配合 DRF 可以快速构建 RESTful API,Admin 后台则让数据管理开箱即用。但要注意接受 Django 的约定,不要试图把它改造成另一个框架。
关键知识点
- MVT 架构:Model 负责数据、View 负责业务逻辑、Template 负责展示
- Django ORM 的 select_related(外键预加载)和 prefetch_related(多对多预加载)
- DRF 的 ViewSet + Router 模式可以大幅减少 API 代码量
- settings.py 是 Django 项目的核心配置文件,注意区分不同环境
项目落地视角
- 使用 django-environ 管理多环境配置,敏感信息走环境变量
- API 项目统一使用 DRF,配合分页、过滤、版本管理等中间件
- Admin 后台用于运营人员数据管理,核心业务操作添加审计日志
- 部署时使用 gunicorn + nginx,静态文件由 nginx 直接服务
常见误区
- 把所有逻辑写在 views.py 中,不按职责拆分 Service 层
- 忽略 Django ORM 的查询效率,产生 N+1 查询问题
- 在 Django 中尝试实现高并发实时服务(应考虑 FastAPI + WebSocket)
- 直接修改 Django 内置 User 模型而非使用自定义 User 模型
进阶路线
- 学习 Django 的信号(Signals)和自定义管理命令
- 研究 django-celery-beat 集成分布式定时任务
- 了解 Django 的 async views 和 ASGI 部署
- 探索 Django 在微服务架构中的定位和演进方向
适用场景
- 内容管理系统、博客平台、电商后台
- 企业内部管理系统、审批流程、数据报表
- 需要 Admin 后台快速管理的 CRUD 密集型应用
落地建议
- 新项目直接使用自定义 User 模型,避免后期迁移困难
- 统一使用 DRF 构建 API,前后端分离架构
- 配置好 logging、中间件和异常处理,确保生产可诊断
排错清单
- 检查 settings.py 中的 INSTALLED_APPS 和 MIDDLEWARE 配置是否完整
- 确认数据库迁移是否全部执行(python manage.py showmigrations)
- 排查 URL 配置是否正确,使用 python manage.py show_urls 查看路由表
复盘问题
- Django Admin 的使用范围是否合理?是否有应该通过 API 而非 Admin 管理的操作?
- 项目的数据库查询是否存在 N+1 问题?是否使用 select_related 优化?
- Django 版本升级计划是什么?是否及时跟进安全更新?
