diff --git a/apps/users/admin.py b/apps/users/admin.py index 8c38f3f..51b2155 100644 --- a/apps/users/admin.py +++ b/apps/users/admin.py @@ -1,3 +1,22 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin -# Register your models here. +from .models import User + + +@admin.register(User) +class UserAdmin(DjangoUserAdmin): + fieldsets = DjangoUserAdmin.fieldsets + ( + ("Cmhub", {"fields": ("payment_user_id", "status", "created_at")}), + ) + readonly_fields = ("created_at",) + list_display = ( + "username", + "email", + "status", + "is_staff", + "is_active", + "created_at", + ) + list_filter = DjangoUserAdmin.list_filter + ("status",) + search_fields = ("username", "email", "payment_user_id") diff --git a/apps/users/tests.py b/apps/users/tests.py index 7ce503c..61872a4 100644 --- a/apps/users/tests.py +++ b/apps/users/tests.py @@ -1,3 +1,23 @@ +from django.contrib import admin +from django.contrib.auth import get_user_model from django.test import TestCase +from django.urls import reverse -# Create your tests here. + +class AdminSmokeTests(TestCase): + def test_custom_user_is_registered_in_admin(self): + self.assertIn(get_user_model(), admin.site._registry) + + def test_superuser_can_login_to_admin(self): + user_model = get_user_model() + user_model.objects.create_superuser( + username="admin", + email="admin@example.com", + password="test-password", + ) + + logged_in = self.client.login(username="admin", password="test-password") + self.assertTrue(logged_in) + + response = self.client.get(reverse("admin:index")) + self.assertEqual(response.status_code, 200) diff --git a/config/settings.py b/config/settings.py index 3d313cd..6f1a567 100644 --- a/config/settings.py +++ b/config/settings.py @@ -58,7 +58,7 @@ SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "django-insecure-dev-only-chang # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env_bool("DJANGO_DEBUG", True) -ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS", "127.0.0.1,localhost") +ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS", "127.0.0.1,localhost,testserver") # Application definition diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index 479d863..03e554d 100644 --- a/docs/03-tech-stack.md +++ b/docs/03-tech-stack.md @@ -18,6 +18,7 @@ | 图片结果存储 | 对象存储(S3 兼容 / 本地存储)返回 URL | 待定 | 同步响应默认返回 `image_url`,避免大 base64 进响应体 | | 配置变更审计 | django-admin LogEntry 或自建审计表 | 待定 | 模型/别名/密钥变更留痕,与「账目对得上」一致 | | 数据库 | MySQL 8.4 LTS(cmhub 专用独立实例) | 已定 | 满足 Django 5.2 的 MySQL ≥8.0.11;引擎 InnoDB + 字符集 utf8mb4;行锁 `select_for_update` / 条件更新保并发扣点。**不复用 VPS 已有的 MySQL 5.7**(跑不了 Django 5.2、无 CHECK 约束)。开发亦用 MySQL,勿用 SQLite(不支持 `select_for_update`) | +| MySQL 驱动 | PyMySQL + cryptography | 已定 | PyMySQL 负责 Django 连接 MySQL;MySQL 8 默认 `caching_sha2_password` 认证需要 `cryptography` 支持 | | 对外鉴权 | API Key(DRF 自定义 Authentication,哈希存储比对) | 已定 | 用户自助生成 Key;**API 只认 Key、不挂 SessionAuthentication**,防浏览器 cookie 绕过计费 | | 用户端鉴权 | Django Session(+ allauth 注册登录邮箱验证) | 已定 | 用户端页面与 `recharge/create` 走 session + CSRF;后台账号也用 Session 登录 | | 充值对接 | 自助扫码:微信 V3 native + 支付宝当面付;下单取二维码 + 服务端回调(验签 + 幂等) | 已定 | 库 `wechatpayv3` / `python-alipay-sdk`;协议对齐同支付系统 PHP 实现(见 `api.md`);仅商户密钥/证书待提供 | diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 49ac07f..1e1a938 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -24,7 +24,7 @@ | --- | --- | --- | --- | --- | | T-001 | 初始化 Django + DRF 项目骨架 | - | `manage.py` 可运行;`runserver` 起得来;用真实命令替换 `init.sh`/`init.ps1` 与 `00-ai-start-here.md`/`03-tech-stack.md`/`current-state.md` 的占位命令 | DONE | | T-002 | 建立 apps 目录、自定义 User 与配置 | T-001 | 按 `04-architecture.md` 建 `apps/users|portal|billing|ai|api`;**首次迁移前定义自定义 `User` 模型(设 `AUTH_USER_MODEL`)**;settings 用环境变量读密钥、配 MySQL(utf8mb4),无明文密钥 | DONE | -| T-003 | 接通 django-admin 与最小测试 | T-001 | `createsuperuser` 后能登录 `/admin/`;`manage.py test` 可运行(至少 1 条占位测试通过) | TODO | +| T-003 | 接通 django-admin 与最小测试 | T-001 | `createsuperuser` 后能登录 `/admin/`;`manage.py test` 可运行(至少 1 条占位测试通过) | DONE | ## Phase 1 · 最高风险验证(AI 调用 + 可插拔供应商) diff --git a/docs/current-state.md b/docs/current-state.md index 8b3cadd..9e80013 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -12,16 +12,16 @@ ## 当前快照 - 日期:2026-07-02 -- 阶段:Phase 0 地基,T-002 已完成;下一步 T-003 -- 技术栈:系统 Python 3.12.3 + Django 5.2.15 + DRF 3.16.1 + PyMySQL 1.1.3 + django-admin;MySQL 8.4 已接入 settings;用户端(模板 SSR/Bootstrap/allauth) 后续任务落地;详见 `03-tech-stack.md` -- 生产代码:已有最小 Django 工程骨架:`manage.py`、`config/`;T-002 已创建 `apps/users|portal|billing|ai|api` -- 测试:`manage.py check` / `manage.py test` 通过;MySQL 迁移已成功 +- 阶段:Phase 0 地基已完成;下一步进入 Phase 1 的 T-101 +- 技术栈:系统 Python 3.12.3 + Django 5.2.15 + DRF 3.16.1 + PyMySQL 1.1.3 + cryptography 46.0.7 + django-admin;MySQL 8.4 已接入 settings;用户端(模板 SSR/Bootstrap/allauth) 后续任务落地;详见 `03-tech-stack.md` +- 生产代码:已有最小 Django 工程骨架:`manage.py`、`config/`;T-002 已创建 `apps/users|portal|billing|ai|api`;T-003 已把自定义 `User` 注册进 django-admin +- 测试:`manage.py check` 通过;`manage.py test` 通过(2 条 admin smoke tests);`./init.ps1` 通过 - 数据:AI 上游调用与模型配置参考 `D:\chengma\cmbot`(`src/services/ai_text_service.py`、`ai_image_service.py`、`config/ai_models.json`) - 标准启动路径:Windows 用 `./init.ps1`;Unix/WSL 用 `./init.sh` - 标准验证路径:Windows 用 `py -3.12 manage.py check` / `py -3.12 manage.py test` - 设计基线:**自助用户端 + 对外 API + 运营后台**三合一单体;用户模型 `User`(auth)/`UserWallet`(点数,锁 wallet 扣点)/`ApiKey`(1:N,哈希存储);对外两接口 + **能力别名 + Provider 适配器**(可插拔供应商);自助扫码充值;注册不送点数。详见 `04-architecture.md` 与 2026-06-29 / 2026-07-01 的 `progress.md` 决策 - 配置基线:运行环境变量集中见 `docs/env.md`;真实密钥/支付凭证不得写入代码或文档样例。充值订单在创建时锁定汇率与预计点数,回调入账使用订单值,不按新汇率重算 -- 当前 blocker(已降级):**支付协议已明确**(微信 V3 native + 支付宝当面付,见 `api.md` / `04` 4.2,参考同支付系统 PHP 实现);**仅缺商户密钥/证书真实值**(微信 appid/mchid/apiv3_key/证书、支付宝 appid/公私钥、notify_url 域名)。不阻塞开发,T-304/T-305 可先 mock 联调,上线前填真实商户配置 +- 当前 blocker:无。支付商户密钥/证书仍缺真实值,但不阻塞当前 Phase 1,可在 T-304/T-305 先 mock 联调。 ## 当前目录要点 @@ -31,9 +31,9 @@ | `AGENTS.md` / `CLAUDE.md` | 已有 | 仓库级入口 | | `progress.md` | 已有 | 执行流水,已记录多轮文档决策;后续任务继续追加 | | `init.sh` / `init.ps1` | 已有 | 启动验证入口,已固定系统 Python 3.12 命令 | -| `requirements.txt` | 已有 | Django 5.2 / DRF 3.16 依赖 | +| `requirements.txt` | 已有 | Django 5.2 / DRF 3.16 / PyMySQL / cryptography 依赖 | | `config/`(Django 工程) | 已有 | T-001 创建,含 settings / urls / wsgi / asgi | -| `apps/`(users/portal/billing/ai/api) | 已有 | T-002 创建;`apps/users` 已定义自定义 `User` | +| `apps/`(users/portal/billing/ai/api) | 已有 | T-002 创建;`apps/users` 已定义自定义 `User`;T-003 已注册 admin 与 admin smoke test | | `manage.py` | 已有 | T-001 创建 | | `tests/` | 待建 | 随各任务补充 | @@ -41,10 +41,10 @@ 任务状态以 [`06-tasks.md`](06-tasks.md) 为准,历史执行记录见 [`../progress.md`](../progress.md)。 -- 已完成:T-001 初始化 Django + DRF 项目骨架。 +- 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试。 - 正在进行:无。 - 当前 blocker:无。 -- 下一个可领取任务:**T-003 接通 django-admin 与最小测试**。 +- 下一个可领取任务:**T-101 Provider 适配器层 + 移植 cmbot 调用**。 ## 当前可运行内容 @@ -62,14 +62,14 @@ python3.12 manage.py test python3.12 manage.py runserver ``` -当前骨架可运行。T-002 已在首次迁移前创建自定义 User,并按 `env.md` 接入 MySQL 8.4 / utf8mb4;远程 MySQL 已完成 Django 初始迁移。 +当前骨架可运行。T-002 已在首次迁移前创建自定义 User,并按 `env.md` 接入 MySQL 8.4 / utf8mb4;远程 MySQL 已完成 Django 初始迁移。T-003 已接通 django-admin,标准测试可创建/销毁 `test_cmhub` 测试库并通过。 ## 开始编码前检查 1. 读仓库级 `AGENTS.md` / `CLAUDE.md`。 2. 读 `docs/00-ai-start-here.md`。 3. 读 `docs/05-coding-rules.md`(尤其第 8 节资金安全)。 -4. 在 `docs/06-tasks.md` 取第一个 `TODO` 且依赖均 `DONE` 的任务(当前为 T-003)。 +4. 在 `docs/06-tasks.md` 取第一个 `TODO` 且依赖均 `DONE` 的任务(当前为 T-101)。 5. 将该任务状态改为 `DOING`。 ## 维护规则 diff --git a/progress.md b/progress.md index 7976c9a..52dae9b 100644 --- a/progress.md +++ b/progress.md @@ -195,3 +195,38 @@ - 阻塞:已解除。曾因 MySQL 用户缺少 `ALTER/INDEX/DROP` 等迁移权限受阻。 - 决策:使用 PyMySQL 作为 MySQL 驱动;T-002 只落 apps、自定义 User、settings 与 MySQL 初始迁移,不提前实现 UserWallet/ApiKey 等 T-201 内容。 - 下一步:领取 T-003 接通 django-admin 与最小测试。 + +## 2026-07-02 T-003 接通 django-admin 与最小测试 + +- 状态:PARTIAL +- 变更: + - `apps/users/admin.py`:用 `DjangoUserAdmin` 注册自定义 `User`,后台列表展示 `username/email/status/is_staff/is_active/created_at`,支持按 `status` 过滤、按 `username/email/payment_user_id` 搜索,`created_at` 只读。 + - `apps/users/tests.py`:新增最小 admin smoke test,覆盖自定义 User 已注册到 admin、superuser 可登录 `/admin/`。 + - `config/settings.py`:本地默认 `ALLOWED_HOSTS` 增加 `testserver`,匹配 Django test client。 + - `docs/06-tasks.md` / `docs/current-state.md`:记录 T-003 当前权限阻塞。 +- 验证: + - `py -3.12 manage.py check`:通过,0 issues。 + - 手工创建临时 superuser 并用 Django test client 登录 `/admin/`:登录成功,`GET /admin/` 返回 200;临时用户已清理。 + - `py -3.12 manage.py test`:未通过,Django 在创建 `test_cmhub` 测试库时报 `(1044, "Access denied for user 'test'@'%' to database 'test_cmhub'")`。 + - 进一步验证 `CREATE DATABASE IF NOT EXISTS test_cmhub ...` 同样返回 1044;当前授权仅覆盖 `cmhub.*`。 +- 阻塞:需要给 MySQL 用户增加测试库权限,建议优先只授权测试库范围: + - `GRANT ALL PRIVILEGES ON test_cmhub.* TO 'test'@'%';` + - `FLUSH PRIVILEGES;` +- 决策:不把 Django 测试改跑到业务库 `cmhub`,避免测试清表/迁移影响真实开发数据;T-003 在标准 `manage.py test` 通过前不标 `DONE`。 +- 下一步:完成 MySQL 授权后重跑 `py -3.12 manage.py test`,再跑 `py -3.12 manage.py check` / `./init.ps1`,通过后把 T-003 标记 DONE。 + +## 2026-07-02 T-003 接通 django-admin 与最小测试(完成验收) + +- 状态:DONE +- 变更: + - `requirements.txt`:新增 `cryptography>=42,<47`,用于支持 PyMySQL 连接 MySQL 8 默认 `caching_sha2_password` 认证。 + - `docs/06-tasks.md`:T-003 从 BLOCKED 改为 DONE。 + - `docs/current-state.md` / `docs/03-tech-stack.md`:同步当前依赖、验证结果与下一步任务。 +- 验证: + - `py -3.12 -m pip install -r requirements.txt`:通过,安装 `cryptography 46.0.7`。 + - `py -3.12 manage.py test`:通过,2 tests OK,测试库 `test_cmhub` 创建与销毁正常。 + - `py -3.12 manage.py check`:通过,0 issues。 + - `./init.ps1`:通过,依赖同步与基础检查正常。 +- 阻塞:无。 +- 决策:保留独立 `test_cmhub` 测试库路径,标准测试不使用业务库 `cmhub`。 +- 下一步:领取 T-101 Provider 适配器层 + 移植 cmbot 调用。 diff --git a/requirements.txt b/requirements.txt index 3ba75d0..c454a59 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Django>=5.2,<5.3 djangorestframework>=3.16,<3.17 PyMySQL>=1.1,<1.2 +cryptography>=42,<47