用 Django 搞定第一个 Python 网站:我的入门实战记录

CPU烧开水
2025-06-28 13:13
阅读 2415

两年前,我刚从嵌入式转行做后端开发的时候,第一次接触 Web 开发框架,用的就是 Django。那时候对“请求-响应”模型、路由配置、数据库建模这些概念还很模糊,完全是靠官方文档一步步摸索出来的。

今天想通过这篇文章,把我当时搭建第一个网站的整个过程还原一下,并结合工作中遇到的真实问题,分享一些个人经验,希望能帮到正在学 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 的核心是数据驱动的,先设计好数据库结构,再逐步往上层构建接口和页面,是比较合理的顺序。


第一次上线前的压力测试翻车

API接口文档-1

第一次上线前的压力测试翻车

项目差不多跑通后,我就想着部署上线看看效果。在本地运行 runserver 没问题,但在内网服务器上部署之后,发现当多人并发访问时,经常卡住甚至报错。

这是个真实的问题。虽然只是测试环境,但暴露出我对 Django 性能优化和部署策略的无知。

原因分析如下:

  • 使用默认的 runserver 模式启动生产服务(这本身就不推荐)
  • 没有进行任何静态资源收集(CSS/JS 无法加载)
  • 所有请求都走同步模式,没有任何并发处理机制

解决办法:

  1. 更换 WSGI 服务器:用 Gunicorn 替代 runserver
  2. 配置 Nginx 做反向代理并处理静态文件
  3. 设置合适的 Gunicorn worker 数量(通常建议设为 CPU 核心数)
  4. 加入 Redis 缓存提高访问效率
  5. 开启数据库连接池(避免频繁创建连接)

部署完成后,我们用了 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,这样即使指定的人被删除,也不会级联删除工单,避免误操作。


性能优化小技巧(真实踩坑总结)

API接口文档-2

随着用户增多和功能扩展,系统慢慢暴露出一些性能瓶颈。我们在以下几个方面做了优化:

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

最热最新
暂无评论
CPU烧开水Lv.1
0
影响力
0
文章
0
粉丝