# 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 视角:改了什么、为什么、动了哪些原测试 |