从零跑通 Django:一个双非学生的第一个 Python 网站实战
去年冬天,我还在公司里吭哧吭哧地维护那套老掉牙的 Java 后端服务——没错,就是那种连日志格式都靠手动拼字符串、部署全靠运维大哥人肉拷贝 JAR 包的“经典”架构。作为一个在分布式系统方向摸爬滚打三年多的老油条(虽然学校是双非,但代码量绝对不输985),其实我对 Django 一直有点偏见:总觉得它“太重”、“不够底层”、“玩具框架”。直到上周五晚上,产品经理甩过来一句:“我们要做个内部工具原型,下周三演示,你用 Python 快速搭一下,前端简单点就行。”
那一刻,我盯着屏幕上的 Slack 消息,心里一万只草泥马奔腾而过。但转念一想:跳槽面试老被问“会不会 Django”,简历上写“熟悉 Python 生态”却连个 Hello World 都没跑过,确实有点虚。行吧,就当给自己补课了。
为什么选 Django?别杠,先跑起来
说实话,作为 Vim 党,我平时连 VS Code 都很少碰,更别说 PyCharm 这种“全家桶”了。但 Django 的魅力在于——开箱即用。不用配 Maven,不用搞 Webpack,甚至不用手动连数据库驱动。一条 pip install django + django-admin startproject,就能看到那个经典的火箭图标页面。对于被 Spring Boot 的 YAML 嵌套折磨到麻木的我来说,这简直是救赎。
而且,Django 自带 admin 后台、用户认证、ORM、路由……这些在 Java 里要集成一堆 Starter 才能搞定的功能,它原生就有。对我们这种既要写后端又要糊前端的“全栈苦力”来说,省下的时间够多改三版需求了(感谢产品经理的反复横跳)。
项目目标:做一个简单的任务看板
既然是入门,那就做最经典的 Todo List 吧,但加点料:支持多人协作、任务状态流转(待办/进行中/完成)、以及——必须有前端交互。别笑,很多教程到模板渲染就结束了,但现实项目里没有 JavaScript 的页面根本没法交差。
技术栈很朴素:
- 后端:Django 4.2(最新 LTS)
- 前端:原生 HTML + CSS + 一点点 JavaScript(拒绝 Vue/React,毕竟只是内部工具)
- 数据库:SQLite(开发阶段够用,上线再换 PostgreSQL)
第一步:初始化项目 & App
# 创建虚拟环境(别偷懒!)
python -m venv .venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
pip install django
# 创建项目
django-admin startproject taskboard
cd taskboard
# 创建核心 App
python manage.py startapp tasks
然后在 taskboard/settings.py 里注册 App:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'tasks', # ← 加上这行
]
第二步:设计数据库模型 —— 别小看 ORM
作为搞过分布式事务的人,我对数据一致性有执念。虽然只是个小项目,但模型设计不能糊弄。
# tasks/models.py
from django.db import models
from django.contrib.auth.models import User
class Task(models.Model):
STATUS_CHOICES = [
('todo', '待办'),
('doing', '进行中'),
('done', '已完成'),
]
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='todo')
assignee = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assigned_tasks')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
注意几个细节:
- 用
ForeignKey关联 Django 内置的User模型,省去自己写登录逻辑 on_delete=models.CASCADE:用户删了,任务也跟着删(业务上合理)- 时间字段自动管理,避免手动赋值出错
然后生成并执行迁移:
python manage.py makemigrations
python manage.py migrate
第三步:写视图 —— 别只用函数视图!
很多教程只教 FBV(Function-Based View),但作为“有点经验”的人,我直接上 CBV(Class-Based View),因为:
- 代码更结构化
- 复用性强(比如分页、权限校验)
- 更容易扩展成 API
# tasks/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import ListView, CreateView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Task
from .forms import TaskForm
class TaskListView(LoginRequiredMixin, ListView):
model = Task
template_name = 'tasks/list.html'
context_object_name = 'tasks'
def get_queryset(self):
# 只显示当前用户的任务
return Task.objects.filter(assignee=self.request.user)
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
form_class = TaskForm
template_name = 'tasks/form.html'
success_url = '/tasks/'
def form_valid(self, form):
form.instance.assignee = self.request.user
return super().form_valid(form)
class TaskUpdateView(LoginRequiredMixin, UpdateView):
model = Task
form_class = TaskForm
template_name = 'tasks/form.html'
success_url = '/tasks/'
配套的表单:
# tasks/forms.py
from django import forms
from .models import Task
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ['title', 'description', 'status']
widgets = {
'description': forms.Textarea(attrs={'rows': 3}),
}
第四步:配置 URL 路由
# taskboard/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('tasks/', include('tasks.urls')),
path('', lambda r: redirect('/tasks/')), # 根路径跳转
]
# tasks/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.TaskListView.as_view(), name='task-list'),
path('new/', views.TaskCreateView.as_view(), name='task-create'),
path('<int:pk>/edit/', views.TaskUpdateView.as_view(), name='task-update'),
]
第五步:写前端模板 —— 别忘了 JavaScript!
终于到前端了!虽然我是个后端,但被产品经理骂过“页面点不动像卡死”之后,深知 交互体验的重要性。
先写基础 HTML(用 Bootstrap 快速美化,别手写 CSS):
<!-- tasks/templates/tasks/list.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>我的任务看板</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<h1>我的任务</h1>
<a href="{% url 'task-create' %}" class="btn btn-primary mb-3">新建任务</a>
<div class="row">
{% for task in tasks %}
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ task.title }}</h5>
<p class="card-text">{{ task.description|truncatewords:15 }}</p>
<small class="text-muted">状态: {{ task.get_status_display }}</small>
<div class="mt-2">
<a href="{% url 'task-update' task.pk %}" class="btn btn-sm btn-outline-secondary">编辑</a>
<!-- 重点:这里加 JS 交互 -->
<button class="btn btn-sm btn-success ms-1"
onclick="updateTaskStatus({{ task.pk }}, 'done')">
完成
</button>
</div>
</div>
</div>
</div>
{% empty %}
<p>暂无任务,快去创建吧!</p>
{% endfor %}
</div>
</div>
<!-- 引入 jQuery 和自己的 JS -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script>
function updateTaskStatus(taskId, newStatus) {
if (!confirm('确定要完成这个任务吗?')) return;
$.ajax({
url: `/api/tasks/${taskId}/`,
type: 'PATCH',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
},
data: JSON.stringify({status: newStatus}),
contentType: 'application/json',
success: function() {
location.reload(); // 简单粗暴,实际可局部刷新
},
error: function(xhr) {
alert('操作失败:' + xhr.responseText);
}
});
}
</script>
{% csrf_token %} <!-- 必须放在 body 里,供 JS 读取 -->
</body>
</html>
关键点:
- 用
onclick绑定按钮,避免表单提交刷新页面 - 通过 AJAX 发送 PATCH 请求更新状态
- CSRF Token 处理:Django 默认开启 CSRF 保护,JS 请求必须带上 token(很多人在这里踩坑!)
- 成功后
location.reload()—— 虽然糙,但快。真要优化可以只更新卡片状态
第六步:加个简易 API —— 为前端解耦做准备
虽然现在用的是服务端渲染,但未来可能要拆前后端分离。提前暴露 API 是好习惯。
# tasks/views.py 新增
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import json
@method_decorator(csrf_exempt, name='dispatch')
class TaskAPIUpdateView(View):
def patch(self, request, pk):
try:
task = Task.objects.get(pk=pk, assignee=request.user)
data = json.loads(request.body)
task.status = data.get('status', task.status)
task.save()
return JsonResponse({'status': 'success'})
except Task.DoesNotExist:
return JsonResponse({'error': 'Task not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=400)
# urls.py 补充
path('api/tasks/<int:pk>/', TaskAPIUpdateView.as_view()),
吐槽:
csrf_exempt虽然方便,但生产环境千万别这么干!应该用django-cors-headers+ 正确的 CSRF 策略。不过入门阶段,先跑通再说。
调试与踩坑记录
坑1:静态文件 404
开发时 CSS/JS 加载失败?记得在 settings.py 开启:
DEBUG = True # 开发环境必须为 True
# 并确保 urls.py 包含:
from django.conf import settings
from django.conf.urls.static import static
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
坑2:CSRF Failed
前端 AJAX 报 403?除了上面提到的 token,还要确认:
- 模板里有
{% csrf_token %} - JS 读取的是
input[name=csrfmiddlewaretoken]的 value - 不要用
csrf_exempt除非你清楚后果
坑3:外键关联用户报错
如果创建任务时没传 assignee,会报 NOT NULL constraint failed。所以 CBV 里一定要重写 form_valid:
def form_valid(self, form):
form.instance.assignee = self.request.user # ← 关键!
return super().form_valid(form)
性能与生产考虑
虽然只是 demo,但作为老后端,我忍不住想提几点:
| 问题 | 开发阶段 | 生产建议 |
|---|---|---|
| 数据库 | SQLite | 换 PostgreSQL/MySQL |
| 静态文件 | 直接 serve | 用 Nginx 或 CDN |
| 并发 | 单进程 | Gunicorn + Nginx |
| 安全 | DEBUG=True | 关闭 DEBUG,设 ALLOWED_HOSTS |
| 缓存 | 无 | 加 Redis 缓存热点数据 |
特别是 ALLOWED_HOSTS,上线前必须配,否则 500 错误让你怀疑人生:
# settings.py
ALLOWED_HOSTS = ['your-domain.com', 'localhost']
最后:为什么我觉得 Django 值得学?
以前总觉得“大厂不用 Django”,但最近面试几家独角兽,发现不少内部工具、数据分析平台都在用它。原因很简单:开发效率碾压一切。对我这种想跳槽的人来说,掌握一个能快速产出 MVP 的框架,比死磕底层原理更实用。
而且,Django 的设计哲学(DRY、明确优于隐式)和我在分布式系统中学到的原则高度一致。ORM 虽然“魔法”,但理解其生成的 SQL 后,反而觉得比手写 MyBatis 更安全。
写在结尾
折腾了两天,终于把这套东西跑起来了。周一演示时,产品经理居然说“这个完成按钮的动效不错”(其实只是弹了个 confirm)……算了,能糊弄过去就行。
如果你也是双非出身、自学编程、在小公司挣扎求生的程序员,别被“必须精通源码”的焦虑绑架。先做出能跑的东西,再慢慢深挖。Django 不是银弹,但它能让你在 deadline 前保住头发。
代码已开源(假装有):github.com/yourname/taskboard-django
(其实还没 push,但先占个坑位)
下期预告:《用 Celery + Redis 给 Django 任务看板加异步通知》——毕竟,老板说“要有邮件提醒”,而我又不想阻塞主线程。
作者:某双非院校大二(心理年龄30+)自学者,Vim 党,分布式系统爱好者,正在逃离舒适区。
写作于 2024 年 6 月的一个加班夜,咖啡已凉。

评论 0