用 Django 搞定第一个 Python 网站:我的入门实战记录
两年前,我刚从嵌入式转行做后端开发的时候,第一次接触 Web 开发框架,用的就是 Django。那时候对“请求-响应”模型、路由配置、数据库建模这些概念还很模糊,完全是靠官方文档一步步摸索出来的。
今天想通过这篇文章,把我当时搭建第一个网站的整个过程还原一下,并结合工作中遇到的真实问题,分享一些个人经验,希望能帮到正在学 Django 的小伙伴。
背景介绍:为什么是 Django?

事情要从一个内部项目说起。公司当时需要做一个简易的工单管理系统,用于技术部门之间的任务流转和状态追踪。时间紧,需求简单但要求能快速上线,而且我们团队人手有限。
作为一个刚入职的新手,在导师的支持下,我接下了这个任务,决定尝试用 Python + Django 来实现这个系统。虽然之前没做过完整的 Web 项目,但凭借对 Python 的熟悉程度,以及 Django 提供的大量内置功能,我觉得值得一试。
目标很简单:
- 实现用户登录
- 创建、查看和更新工单
- 工单分类、优先级、状态管理
- 记录提交者、处理人等基本信息
遇到的第一个坑:不知道从哪里开始

万事开头难,一开始我不知道应该先写哪部分代码。Django 太强大了,它给你提供了一个项目骨架、大量的工具函数,还有 Admin 后台这样的神器。但对于新手来说,这种“开箱即用”的能力反而会让人迷茫。
我当时的想法是:“我要先定义数据结构吗?还是先设计页面?”最终我选择了一个最基础的做法——先搭数据模型。
于是我在 models.py 中定义了这样一个 Ticket 模型:
from django.db import models
from django.contrib.auth.models import User
class Ticket(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
priority_choices = (
('low', 'Low'),
('medium', 'Medium'),
('high', 'High'),
)
status_choices = (
('open', 'Open'),
('in_progress', 'In Progress'),
('closed', 'Closed'),
)
title = models.CharField(max_length=200)
description = models.TextField()
priority = models.CharField(max_length=10, choices=priority_choices, default='medium')
status = models.CharField(max_length=15, choices=status_choices, default='open')
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='created_tickets')
assigned_to = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_tickets')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
这是我第一次正式使用 ORM 建模,虽然看起来有点啰嗦,但它解决了我对数据逻辑的梳理问题。这也让我明白一点:Django 的核心是数据驱动的,先设计好数据库结构,再逐步往上层构建接口和页面,是比较合理的顺序。
第一次上线前的压力测试翻车


项目差不多跑通后,我就想着部署上线看看效果。在本地运行 runserver 没问题,但在内网服务器上部署之后,发现当多人并发访问时,经常卡住甚至报错。
这是个真实的问题。虽然只是测试环境,但暴露出我对 Django 性能优化和部署策略的无知。
原因分析如下:
- 使用默认的
runserver模式启动生产服务(这本身就不推荐) - 没有进行任何静态资源收集(CSS/JS 无法加载)
- 所有请求都走同步模式,没有任何并发处理机制
解决办法:
- 更换 WSGI 服务器:用 Gunicorn 替代 runserver
- 配置 Nginx 做反向代理并处理静态文件
- 设置合适的 Gunicorn worker 数量(通常建议设为 CPU 核心数)
- 加入 Redis 缓存提高访问效率
- 开启数据库连接池(避免频繁创建连接)
部署完成后,我们用了 Locust 做了一次简单的压力测试。结果表明:在 100 并发的情况下,平均响应时间控制在 200ms 内,性能有了明显提升。
这次教训也让我意识到:Django 是个强大的框架,但不加优化直接上线等于给自己挖坑。
数据库设计中的“细节控”
工单系统后期还需要支持分类、标签、评论、附件等多个功能模块。这时候我发现,一开始设计的数据结构如果不够灵活,后面就会被各种业务需求逼着反复重构。
比如:
- 最初只考虑一个工单属于一个人,后来发现需要支持多个协作人
- 初期没有给每条工单的操作加上变更日志,导致审计困难
- 附件字段一开始用 CharField 存路径,后来发现文件上传失败概率很高,必须引入 FileField 或第三方存储方案
这里我想特别提醒大家几点关于数据库设计的经验:
1. 给每个表加上 update_time 字段(比 auto_now 好用)
不要完全依赖 auto_now,因为有时候你想手动控制更新时间。可以自己封装一个方法:
class BaseModel(models.Model):
create_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(null=True)
def save(self, *args, **kwargs):
if not self.id or kwargs.pop('update_fields', None):
self.update_time = timezone.now()
return super().save(*args, **kwargs)
2. 不要用 Integer 表示状态,优先用 Char + choices
刚开始我试图用数字表示状态(比如 0: open, 1: closed),结果维护起来非常痛苦。改成字符串 + choices 后,代码可读性大大提高,也方便前端展示。
3. 设计外键关系时要考虑是否允许为空、删除策略
比如上面例子中的 assigned_to 字段设置为 on_delete=models.SET_NULL,这样即使指定的人被删除,也不会级联删除工单,避免误操作。
性能优化小技巧(真实踩坑总结)

随着用户增多和功能扩展,系统慢慢暴露出一些性能瓶颈。我们在以下几个方面做了优化:
1. 查询优化(N+1 问题)
早期列表页每次展示工单时都会单独查询每个工单关联的用户信息,导致 SQL 查询次数激增。后来改成使用 select_related():
Ticket.objects.select_related('created_by', 'assigned_to').all()
大大减少了数据库交互次数。
2. 接口返回字段按需裁剪
Django REST Framework 默认把整个对象的所有字段序列化出来。后来我们加上了字段过滤:
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = ['id', 'title', 'status', 'priority', 'created_by']
也可以更细粒度地通过 request.GET.get('fields') 动态控制输出字段。
3. 使用缓存减少重复请求
对于那些变动频率较低的接口,我们加上了缓存装饰器:
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
@method_decorator(cache_page(60 * 5), name='dispatch')
class TicketListView(APIView):
...
这个小改动让高峰时段服务器负载下降了约 40%。
一些运维上的小心思
项目上线后,我们也积累了不少运维方面的经验:
- 日志监控:用 logging 把错误信息记录下来,接入 Sentry 做异常告警
- 定时任务:用 django-cron 定义定期清理过期工单、邮件通知等功能
- 版本控制:所有 migrations 都提交到 Git,确保数据库结构可追溯
- 安全加固:关闭 DEBUG 模式、限制 Allowed Hosts、使用 HTTPS
- 自动部署:基于 GitHub Action + Ansible 实现一键部署
其中最值得一提的是迁移管理。Django 的 makemigrations 很智能,但如果多分支合并时出现冲突,一定要手工检查后再合并,否则容易造成数据库结构混乱。
回顾与心得:写给刚开始学 Django 的你
回顾这两年来的开发经历,我最大的感受是:Django 是一个非常适合中小型项目的框架,它的设计理念和生态足够成熟,但也需要开发者有一定的工程思维和架构意识。
如果你是一个刚刚开始学习 Django 的开发者,我给你几个实用建议:
✅ 先理解 Model-View-Template 的基本流程
不要急着用 DRF(Django REST Framework)造接口,先把 Django 的 MTV 架构弄清楚。只有当你明白 request 是怎么进来、response 是怎么出去的,才能更好地理解整个框架的工作原理。
✅ 不要把 views.py 写成上帝函数
很多人喜欢在 view 函数里一股脑塞进去验证逻辑、业务判断、数据处理,最后变成“面条代码”。建议抽出 service 层做逻辑封装,保持 view 只负责请求转发和数据包装。
✅ 多用中间件和信号做统一处理
Django 提供了很多钩子(hooks),比如 middleware 和 signals,我们可以利用它们做一些全局的事情:
- 用户行为埋点
- 请求日志记录
- 接口调用耗时统计
举个例子,你可以监听 ticket 修改事件:
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=Ticket)
def log_ticket_change(sender, instance, created, **kwargs):
if not created:
print(f"Ticket {instance.id} has been updated.")
这种机制非常适合做一些后台异步任务触发或日志记录。
✅ 尽早规划部署方式
别等到最后才考虑上线部署的事。越早用生产级别的 WSGI + Nginx 模式调试越好。你会发现很多本地没问题,上线就出 bug 的情况其实跟部署环境有很大关系。
写在最后
两年过去了,那个最初的工单系统已经演变成了一个多部门协同的内部平台,而我也在一次次迭代中成长为了一个更有经验的 Django 开发者。
回过头看,Django 给了我很大的自由度,同时也提供了足够的约束和规范,帮助我在复杂业务场景下写出结构清晰、易于维护的代码。
希望这篇文章能够帮你少走弯路,早点享受到写 Django 的乐趣。毕竟,能把一个想法快速落地,并看到它在线上跑起来的感觉,真的很爽。
如果你正在学习 Django,不妨动手做个自己的小项目试试看。哪怕只是一个博客系统或者 TODO List,也能让你真正理解这个框架的价值所在。
📌 一句话送给还在路上的你:
“不是学会了所有知识才去写代码,而是在写代码的过程中学会真正的编程。”
如果你想获取本文提到的完整代码,欢迎关注我的 GitHub,我会陆续开源一些 Django 学习项目。
感谢阅读,共勉!

评论 0