fix: avoid MySQL timezone dependency in billing admin

This commit is contained in:
QiuSW
2026-07-04 16:43:57 +08:00
parent fb1037933d
commit 0d6b97f44d
4 changed files with 40 additions and 4 deletions
-4
View File
@@ -31,7 +31,6 @@ class PointsLedgerAdmin(ReadOnlyLedgerAdmin):
list_filter = ("change_type", "created_at")
search_fields = ("user__username", "user__email", "=ref_order_id", "reason")
ordering = ("-created_at", "-id")
date_hierarchy = "created_at"
list_select_related = ("user", "ref_call")
@@ -70,7 +69,6 @@ class ExchangeRateAdmin(admin.ModelAdmin):
ordering = ("-effective_from", "-id")
readonly_fields = ("created_at", "updated_at")
list_editable = ("is_active",)
date_hierarchy = "effective_from"
@admin.register(RechargeOrder)
@@ -95,7 +93,6 @@ class RechargeOrderAdmin(ReadOnlyLedgerAdmin):
"payment_txn_no",
)
ordering = ("-created_at", "-id")
date_hierarchy = "created_at"
list_select_related = ("user",)
@@ -125,5 +122,4 @@ class CallRecordAdmin(ReadOnlyLedgerAdmin):
"result_summary",
)
ordering = ("-created_at", "-id")
date_hierarchy = "created_at"
list_select_related = ("user", "api_key")
+25
View File
@@ -10,6 +10,7 @@ from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import IntegrityError, OperationalError, connections, transaction
from django.test import TestCase, TransactionTestCase, override_settings
from django.urls import reverse
from django.utils import timezone
from apps.ai.models import AiModel, ModelAlias
@@ -49,6 +50,30 @@ from apps.users.models import ApiKey, UserWallet
TEST_ENCRYPTION_KEY = Fernet.generate_key().decode("ascii")
class BillingAdminTests(TestCase):
def setUp(self):
user_model = get_user_model()
self.admin_user = user_model.objects.create_superuser(
username="billing-admin",
email="billing-admin@example.com",
password="test-password",
)
self.client.force_login(self.admin_user)
def test_exchange_rate_changelist_renders_when_rates_exist(self):
ExchangeRate.objects.create(
currency="CNY",
points_per_unit=Decimal("10.0000"),
effective_from=timezone.now(),
is_active=True,
)
response = self.client.get(reverse("admin:billing_exchangerate_changelist"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "CNY")
class BillingCoreModelTests(TestCase):
def setUp(self):
suffix = uuid.uuid4().hex[:8]
+1
View File
@@ -18,6 +18,7 @@
- 用户端导航:顶部导航 active 状态已修复,`portal/base.html` 基于 `request.resolver_match.url_name` 高亮当前页面入口,并用 `aria-current="page"` 标记;「充值」不再在非充值页固定深色高亮。
- 测试:T-601 已验证:`.\init.ps1` 通过(Python 3.12.3,依赖已满足,`manage.py check` 0 issues,打印启动命令);`py -3.12 -m py_compile apps\ai\catalog.py apps\api\views.py apps\api\urls.py apps\api\tests.py apps\portal\views.py apps\portal\urls.py apps\portal\tests.py` 通过;`py -3.12 manage.py check` 通过;`py -3.12 manage.py makemigrations --check --dry-run` 无变化;`git diff --check` 通过;`py -3.12 manage.py test apps.api.tests.ModelsCatalogApiTests apps.portal.tests.PortalAccountFlowTests.test_models_page_requires_session_login apps.portal.tests.PortalAccountFlowTests.test_models_page_lists_public_aliases_prices_and_unpriced_state --keepdb --noinput --verbosity 2` 首次运行时 3 条 API 测试已通过,portal `setUpClass` 阶段因远程 MySQL 连接 43.128.3.240 超时中断;随后 `Test-NetConnection 43.128.3.240 -Port 3306` 显示 `TcpTestSucceeded=True`,单独重跑两条 portal 测试通过,2 tests OK。T-403 已验证:`./init.ps1` 开工前通过;`py -3.12 manage.py check` 通过;`py -3.12 manage.py makemigrations --check --dry-run` 无变化;`py -3.12 manage.py findstatic portal/vendor/bootstrap/bootstrap.min.css portal/vendor/qrcode/qrcode.js --verbosity 1` 找到两个本地 static 文件;`py -3.12 manage.py check --deploy` 在当前开发 `.env` 下仅报预期安全配置警告,临时注入生产型安全环境变量(含 HSTS includeSubDomains/preload)后通过,0 issues;`py -3.12 manage.py collectstatic --dry-run --noinput` 通过,预期收集 169 个 static 文件;`py -3.12 manage.py createcachetable --dry-run cmhub_cache` 通过,输出 MySQL cache 表 DDL;`py -3.12 -m compileall config` 通过;尝试 `py -3.12 manage.py test apps.api --noinput --keepdb --verbosity 2` 时 25 条用例已通过,`GenerateApiTests` 14 条因远程 MySQL 连接重置/事务中断被记 ERROR,随后 `Test-NetConnection 43.128.3.240 -Port 3306` 端口可达,单独重跑 `py -3.12 manage.py test apps.api.tests.GenerateApiTests --noinput --keepdb --verbosity 2` 通过,14 tests OK。测试/迁移阶段仍有 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的 `models.W036` 警告;本项目用户账本邮箱唯一性由 `user.email` 唯一约束承担。
- 最新验证:T-603 字段级中文化已验证 `py -3.12 -m py_compile apps\users\models.py apps\users\admin.py apps\billing\models.py apps\billing\admin.py apps\ai\models.py apps\ai\admin.py` 通过;`py -3.12 manage.py makemigrations` 仅生成 `users.0005` / `billing.0006` / `ai.0004` 三个 `AlterField` 迁移;`py -3.12 manage.py sqlmigrate users 0005`、`billing 0006`、`ai 0004` 均为 `(no-op)`;`py -3.12 manage.py migrate` 应用成功,仅出现既有 allauth MySQL `models.W036` 警告;`py -3.12 manage.py makemigrations --check --dry-run` 通过,No changes detected;`py -3.12 manage.py check` 通过;`git diff --check` 通过,仅 Windows CRLF 提示;`py -3.12 manage.py test --keepdb --noinput --verbosity 2` 跑 125 条,123 条通过,2 条失败,失败原因是工作区既有 `config/settings.py` 把 `ACCOUNT_EMAIL_VERIFICATION` 改为 `none`,导致 `test_signup_creates_unverified_user_wallet_with_zero_points_and_no_ledger` 收不到验证邮件、`test_unverified_email_cannot_establish_login_session` 未验证用户仍建立登录 session。
- 线上热修验证:`/admin/billing/exchangerate/` 500 根因是 `ExchangeRateAdmin.date_hierarchy` 触发 MySQL `CONVERT_TZ`,而生产 MySQL 未加载时区表,Django 抛 `ValueError: Database returned an invalid datetime value`;已移除 `apps.billing.admin` 中 4 个 DateTime `date_hierarchy`,保留 `list_filter` 日期筛选。验证 `py -3.12 -m py_compile apps\billing\admin.py apps\billing\tests.py`、`py -3.12 manage.py check`、`py -3.12 manage.py test apps.billing.tests.BillingAdminTests.test_exchange_rate_changelist_renders_when_rates_exist --keepdb --noinput --verbosity 2` 均通过。
- 数据:AI 上游调用与模型配置参考 `D:\chengma\cmbot`(`src/services/ai_text_service.py`、`ai_image_service.py`、`config/ai_models.json`);真实 `ai_models.json` 不提交,需通过 `import_ai_models` 命令加密导入
- 标准启动路径:Windows 用 `./init.ps1`;Unix/WSL 用 `./init.sh`
- 标准验证路径:Windows 用 `py -3.12 manage.py check` / `py -3.12 manage.py test`
+14
View File
@@ -1089,3 +1089,17 @@
- 失败用例:`test_signup_creates_unverified_user_wallet_with_zero_points_and_no_ledger`、`test_unverified_email_cannot_establish_login_session`。
- 失败原因:工作区既有 `config/settings.py` 未提交改动将 `ACCOUNT_EMAIL_VERIFICATION` 从 `mandatory` 改为 `none`,导致注册不发验证邮件、未验证用户也能建立登录 session;该文件不属于 T-603,本轮未纳入提交。
- 下一步:恢复 `ACCOUNT_EMAIL_VERIFICATION="mandatory"` 后,重跑上述失败用例和完整测试;全部通过后把 T-603 从 `BLOCKED` 改为 `DONE`。
## 2026-07-04 线上 admin ExchangeRate 500 热修
- 状态:DONE
- 现象:线上访问 `https://cm.833729.com/admin/billing/exchangerate/` 返回 500;未登录 curl 为 302,已登录 admin 页面复现 500。
- 根因:Django admin 的 `date_hierarchy` 会对 DateTime 字段做按日期层级聚合;MySQL 后端在 `USE_TZ=True` 且 `TIME_ZONE=Asia/Shanghai` 时需要 MySQL 时区表支持 `CONVERT_TZ`。生产 MySQL 当前未加载时区表,`ExchangeRateAdmin.date_hierarchy="effective_from"` 渲染时报 `ValueError: Database returned an invalid datetime value. Are time zone definitions for your database installed?`
- 变更:
- `apps/billing/admin.py`:移除 `PointsLedgerAdmin`、`ExchangeRateAdmin`、`RechargeOrderAdmin`、`CallRecordAdmin` 的 DateTime `date_hierarchy`,避免同类页面因 MySQL 时区表缺失 500;保留 `list_filter` 日期筛选。
- `apps/billing/tests.py`:新增 `BillingAdminTests.test_exchange_rate_changelist_renders_when_rates_exist`,覆盖有汇率数据时 admin 列表页必须 200。
- 验证:
- `py -3.12 -m py_compile apps\billing\admin.py apps\billing\tests.py`:通过。
- `py -3.12 manage.py check`:通过,0 issues。
- `py -3.12 manage.py test apps.billing.tests.BillingAdminTests.test_exchange_rate_changelist_renders_when_rates_exist --keepdb --noinput --verbosity 2`:通过,1 test OK;测试阶段仍有既有 allauth MySQL `models.W036` 警告。
- 下一步:提交并部署后,在 VPS 上重启 `cmhub-web.service`,用 Django test client 或浏览器确认 `/admin/billing/exchangerate/` 已恢复 200。