# django
**Repository Path**: song-xiaom/django
## Basic Information
- **Project Name**: django
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-09-20
- **Last Updated**: 2026-09-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 第二次作业 · 基于 djoser 的用户应用模块
> 在**第一次作业**(用仓颉 Skill 蒸馏 Django 官方教程第 1–8 部分 → 生成 `django-first-app` Skill
> → 用它开发出第一个 Django 应用 `polls`)的基础上,使用 **djoser** 搭建用户应用模块,实现:
>
> ① 用户注册 ② 登录/退出 ③ 修改密码 ④ 邮箱认证 ⑤ 登录状态验证 ⑥ JWT 权限认证 ⑦ 用户投票
---
## 一、快速开始(3 分钟验收)
```bash
cd djangotutorial
# 1) 建虚拟环境并装依赖
python -m venv ../.venv
../.venv/Scripts/python -m pip install -r requirements.txt # Windows
# python -m venv .venv && .venv/bin/pip install -r requirements.txt # macOS / Linux
# 2) 跑测试(应输出 OK / Ran 72 tests)
../.venv/Scripts/python manage.py test
# 3) 启动
../.venv/Scripts/python manage.py runserver
```
打开 会自动跳到**演示台 `/demo/`**。
> `db.sqlite3` **已随仓库提交**,里面已经灌好演示账号与示例数据,
> 所以**不需要**再执行 `migrate` 和 `createsuperuser`。
> 想自己重建一遍:`python manage.py migrate && python manage.py seed_demo_data`。
### 演示账号
| 用途 | 入口 | 账号 | 口令 |
|---|---|---|---|
| 演示台(调 REST 接口) | | `alice@example.com` | `Mango-Cactus-77!` |
| 投票站(服务端渲染) | | 同上 | 同上 |
| 管理后台 | | `admin@example.com` | `admin123456` |
> 皆为本地 SQLite 的开发演示账号,生产环境请勿沿用。
### 一条命令端到端验收
```bash
# 终端 1 开着 runserver,另开一个终端:
python scripts/smoke_test.py
```
它会用真实 HTTP 请求把 7 条要求逐条打一遍,并打印 20 项 PASS/FAIL。
---
## 二、目录结构
```
django/ ← 本仓库(Gitee: song-xiaom/django)
├── README.md 本文件
├── .gitignore
├── djangotutorial/ Django 项目(作业一的项目在此扩展而来)
│ ├── manage.py
│ ├── requirements.txt 依赖锁定
│ ├── db.sqlite3 已灌好演示数据
│ │
│ ├── mysite/ ── 项目层(配置与装配)
│ │ ├── settings.py 作业二的四处扩展都集中在这里
│ │ └── urls.py 全站路由总表
│ │
│ ├── users/ ── ★ 作业二新增:用户应用模块
│ │ ├── models.py 自定义 User(email 作为唯一登录标识)
│ │ ├── serializers.py djoser 序列化器定制(带上 nickname)
│ │ ├── views.py 登录状态验证 / token 诊断接口
│ │ ├── urls.py /api/auth/ 全部 REST 路由
│ │ ├── web_views.py + web_urls.py 网页版的 Session 登录/退出
│ │ ├── admin.py 自定义用户模型的后台
│ │ ├── tests.py 26 个测试
│ │ └── migrations/0001_initial.py
│ │
│ ├── polls/ ── 作业一的投票应用(本次扩展)
│ │ ├── models.py Question / Choice + ★ Vote
│ │ ├── services.py ★ 投票业务规则的唯一实现处
│ │ ├── serializers.py / api_views.py / api_urls.py ★ REST 接口
│ │ ├── views.py 网页版视图(投票改为需登录)
│ │ ├── admin.py 注册 Vote / Choice,问题页内联投票记录
│ │ ├── tests.py 33 个测试
│ │ ├── management/commands/seed_demo_data.py ★ 一键灌演示数据
│ │ ├── templates/polls/ 模板(新增 my_votes.html)
│ │ └── static/polls/
│ │
│ ├── frontend/ ── ★ 作业二新增:演示页
│ │ ├── templates/frontend/index.html
│ │ └── static/frontend/{app.js,style.css}
│ │
│ ├── templates/ 项目级模板
│ │ ├── registration/login.html 网页版登录页
│ │ └── email/{activation,password_reset,password_changed_confirmation}.html
│ │ ★ 覆盖 djoser 自带英文邮件模板
│ └── scripts/
│ ├── check_toolbar.py 作业一的验证脚本
│ └── smoke_test.py ★ 端到端验收脚本
│
├── docs/ ★ 作业二文档
│ ├── 01-任务拆解与设计说明.md
│ ├── 02-API接口清单.md
│ ├── 03-验收演示步骤.md
│ └── 04-与作业一的对照.md
│
└── skill/ ★ Skill(延续作业一的形态)
├── django-first-app/ 作业一用仓颉蒸馏出的 Skill(原样保留)
└── django-user-module-djoser/ 作业二按同样体例新蒸馏的 Skill
```
---
## 三、七条要求落在哪里
| # | 要求 | 实现 | 接口 / 位置 | 测试 |
|---|---|---|---|---|
| 1 | 用户注册 | djoser `UserViewSet.create` + 自定义序列化器(带昵称) | `POST /api/auth/users/` | `users.tests.RegistrationTests` |
| 2 | 登录、退出 | simplejwt 签发/刷新;退出 = 把 refresh token 拉黑 | `POST /api/auth/jwt/create/`、`/jwt/logout/` | `LoginAndJwtTests` |
| 3 | 修改密码 | djoser `set_password`(校验当前口令 + 两次新口令一致) | `POST /api/auth/users/set_password/` | `ChangePasswordTests` |
| 4 | 邮箱认证 | 注册后 `is_active=False`,邮件里 uid+token 激活;另有忘记密码重置 | `POST /api/auth/users/activation/`、`/reset_password(_confirm)/` | `EmailActivationTests`、`PasswordResetTests` |
| 5 | 登录状态验证 | `me`(未登录 401)与 `status`(永远 200 带诊断) | `GET /api/auth/users/me/`、`/api/auth/status/` | `LoginStatusTests` |
| 6 | JWT 权限认证 | 全局默认 `IsAuthenticated`,公开接口显式 `AllowAny` | `settings.REST_FRAMEWORK` / 各视图 `permission_classes` | `PollsApiTests` |
| 7 | 用户投票 | `Vote` 模型(user+question 唯一)+ 统一业务层;网页与接口都要求登录 | `POST /api/polls/questions/{id}/vote/`、网页 `/polls//vote/` | `CastVoteServiceTests`、`WebVoteViewTests`、`PollsApiTests` |
---
## 四、几个关键设计决定
**1. 换掉登录标识为 email,就必须重建数据库。**
Django 与 djoser 都明确要求自定义用户模型在**第一次 migrate 之前**建好。所以作业一的
`db.sqlite3` 被弃用,改由 `seed_demo_data` 命令重新灌出等价的演示数据。
(如果偷懒沿用旧库,后面所有与用户相关的外键都会指向错误的表。)
**2. 「退出登录」不能只清前端。**
JWT 是无状态的,服务端删不掉已发出的 token。所以引入了
`rest_framework_simplejwt.token_blacklist` 应用,退出时把 refresh token 写进黑名单表 ——
否则旧 token 在 7 天有效期内仍能换出新的 access token,这个"退出"就是假的。
测试 `test_logout_blacklists_refresh_token` 专门锁这条。
**3. 投票规则只写一遍。**
`polls/services.py::cast_vote()` 是"同一用户同一问题只有一票、重复投=改票、计数器维护"
的**唯一**实现处,网页表单和 REST 接口都调它。
沿用作业一的教训:只在其中一个入口做限制就是假安全。
**4. 唯一性交给数据库。**
`UniqueConstraint(user, question)` 落在迁移里,应用层写错了也兜得住;
`Choice.votes` 计数器保留为冗余字段,但只允许 `cast_vote()` 写入。
**5. 权限默认收紧、开口显式。**
`REST_FRAMEWORK["DEFAULT_PERMISSION_CLASSES"] = IsAuthenticated`,
需要公开的接口(看题目、看结果)逐个写 `permission_classes = [AllowAny]`。
反过来做(默认放开、靠人记得加限制)迟早会漏。
**6. 邮件默认走控制台后端。**
激活链接直接打印在 `runserver` 终端里,验收不需要真实 SMTP。
要发真邮件只需设置一组环境变量(见 `settings.py` 的 `MAILERS` 分支与 `.env.example` 说明):
`DJANGO_MAIL_BACKEND=smtp` 等。
**7. 演示页零依赖、零构建。**
原生 HTML + JS,由 Django 静态文件服务托管,`runserver` 一条命令就能点完 7 条功能,
不必额外装 node/npm。它同时是"前后端分离"的最小范例:页面不持有服务端状态,
身份全在 JWT 里。
---
## 五、常用命令
```bash
cd djangotutorial
P=../.venv/Scripts/python # Windows;macOS/Linux 用 .venv/bin/python
$P manage.py test # 全部测试(72 个)
$P manage.py test users # 只跑用户模块
$P manage.py test polls # 只跑投票模块
$P manage.py seed_demo_data --reset # 重置演示数据
$P manage.py check # 系统检查
$P manage.py makemigrations --check --dry-run # 确认没有漏掉的迁移
```
---
## 六、文档索引
| 文档 | 内容 |
|---|---|
| [`docs/01-任务拆解与设计说明.md`](docs/01-任务拆解与设计说明.md) | 任务如何拆、每个技术选择的理由与代价 |
| [`docs/02-API接口清单.md`](docs/02-API接口清单.md) | 全部接口的方法/路径/权限/入参/返回/curl 示例 |
| [`docs/03-验收演示步骤.md`](docs/03-验收演示步骤.md) | 手把手 8 步演示,含预期现象与排错 |
| [`docs/04-与作业一的对照.md`](docs/04-与作业一的对照.md) | 逐文件 diff 视角:改了什么、为什么、动了哪些原测试 |