From 009d45c0535ac6c09d78f01e6c556b4b2807c204 Mon Sep 17 00:00:00 2001 From: root <000a7936-c5cd-44ad-ad47-6a2f33b973ae-xhicfann_dreamgery@noreply.codeup.aliyun.com> Date: Wed, 26 Aug 2026 15:54:23 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=9C=A8=E4=B8=8D=E5=90=8C=20ORM=20=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=20datetime=20=E7=B1=BB=E5=9E=8B=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../callbacks/login_c.py | 14 ++++++------ .../models/_peewee/logs.py | 3 ++- .../models/_sqlalchemy/logs.py | 16 ++------------ .../models/_sqlmodel/logs.py | 16 ++------------ .../magic-dash-pro/callbacks/login_c.py | 14 ++++++------ .../magic-dash-pro/models/_peewee/logs.py | 3 ++- .../magic-dash-pro/models/_sqlalchemy/logs.py | 16 ++------------ .../magic-dash-pro/models/_sqlmodel/logs.py | 16 ++------------ tests/test_magic_dash_pro_orm_engines.py | 22 ++++++++++++------- 9 files changed, 40 insertions(+), 80 deletions(-) diff --git a/magic_dash/templates/magic-dash-pro-fastapi/callbacks/login_c.py b/magic_dash/templates/magic-dash-pro-fastapi/callbacks/login_c.py index f61aba8..12d1b04 100644 --- a/magic_dash/templates/magic-dash-pro-fastapi/callbacks/login_c.py +++ b/magic_dash/templates/magic-dash-pro-fastapi/callbacks/login_c.py @@ -164,7 +164,7 @@ def handle_login( browser=browser_info, os=os_info, status="用户不存在", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) return [ @@ -199,7 +199,7 @@ def handle_login( browser=browser_info, os=os_info, status="密码错误", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) return [ @@ -221,7 +221,7 @@ def handle_login( browser=browser_info, os=os_info, status="登录成功", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) return [None] * 4 @@ -744,7 +744,7 @@ def handle_login_user_email_submit(nClicks, email, verification_code): ), os="{} {}".format(user_agent.os.family, user_agent.os.version_string), status=log_status, - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) # 提前终止当前无输出回调 return @@ -767,7 +767,7 @@ def handle_login_user_email_submit(nClicks, email, verification_code): ), os="{} {}".format(user_agent.os.family, user_agent.os.version_string), status="邮箱登录成功", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) @@ -841,7 +841,7 @@ def reject_login( browser=browser_info, os=os_info, status=log_status, - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) try: @@ -927,5 +927,5 @@ def reject_login( browser=browser_info, os=os_info, status="OTP登录成功", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) diff --git a/magic_dash/templates/magic-dash-pro-fastapi/models/_peewee/logs.py b/magic_dash/templates/magic-dash-pro-fastapi/models/_peewee/logs.py index 5ea3fb6..9201b2e 100644 --- a/magic_dash/templates/magic-dash-pro-fastapi/models/_peewee/logs.py +++ b/magic_dash/templates/magic-dash-pro-fastapi/models/_peewee/logs.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import List, Literal from peewee import AutoField, CharField, DateTimeField @@ -82,7 +83,7 @@ def add_log( browser: str, os: str, status: str, - login_datetime: str, + login_datetime: datetime, ): """添加日志记录""" diff --git a/magic_dash/templates/magic-dash-pro-fastapi/models/_sqlalchemy/logs.py b/magic_dash/templates/magic-dash-pro-fastapi/models/_sqlalchemy/logs.py index e0d6025..4c08d2d 100644 --- a/magic_dash/templates/magic-dash-pro-fastapi/models/_sqlalchemy/logs.py +++ b/magic_dash/templates/magic-dash-pro-fastapi/models/_sqlalchemy/logs.py @@ -61,16 +61,9 @@ def add_log( browser: str, os: str, status: str, - login_datetime: str, + login_datetime: datetime, ): with session_scope() as session: - # 应用回调中传入的是格式化后的字符串,这里统一转换为datetime入库 - if isinstance(login_datetime, str): - login_datetime = datetime.strptime( - login_datetime, - "%Y-%m-%d %H:%M:%S", - ) - session.add( cls( user_name=user_name, @@ -108,12 +101,7 @@ def columns(cls): @classmethod def to_dict(cls, record): - result = object_to_dict(record, cls.columns()) - if isinstance(result["login_datetime"], datetime): - result["login_datetime"] = result["login_datetime"].strftime( - "%Y-%m-%d %H:%M:%S" - ) - return result + return object_to_dict(record, cls.columns()) # 保持Peewee旧实现的导入时建表行为,避免日志页首次访问时报表不存在 diff --git a/magic_dash/templates/magic-dash-pro-fastapi/models/_sqlmodel/logs.py b/magic_dash/templates/magic-dash-pro-fastapi/models/_sqlmodel/logs.py index 1371f90..ebe5be7 100644 --- a/magic_dash/templates/magic-dash-pro-fastapi/models/_sqlmodel/logs.py +++ b/magic_dash/templates/magic-dash-pro-fastapi/models/_sqlmodel/logs.py @@ -73,16 +73,9 @@ def add_log( browser: str, os: str, status: str, - login_datetime: str, + login_datetime: datetime, ): with session_scope() as session: - # 应用回调中传入的是格式化后的字符串,这里统一转换为datetime入库 - if isinstance(login_datetime, str): - login_datetime = datetime.strptime( - login_datetime, - "%Y-%m-%d %H:%M:%S", - ) - session.add( cls( user_name=user_name, @@ -120,12 +113,7 @@ def columns(cls): @classmethod def to_dict(cls, record): - result = object_to_dict(record, cls.columns()) - if isinstance(result["login_datetime"], datetime): - result["login_datetime"] = result["login_datetime"].strftime( - "%Y-%m-%d %H:%M:%S" - ) - return result + return object_to_dict(record, cls.columns()) # 保持Peewee旧实现的导入时建表行为,避免日志页首次访问时报表不存在 diff --git a/magic_dash/templates/magic-dash-pro/callbacks/login_c.py b/magic_dash/templates/magic-dash-pro/callbacks/login_c.py index f3b8a21..42851a2 100644 --- a/magic_dash/templates/magic-dash-pro/callbacks/login_c.py +++ b/magic_dash/templates/magic-dash-pro/callbacks/login_c.py @@ -169,7 +169,7 @@ def handle_login( browser=browser_info, os=os_info, status="用户不存在", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) return [ @@ -204,7 +204,7 @@ def handle_login( browser=browser_info, os=os_info, status="密码错误", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) return [ @@ -226,7 +226,7 @@ def handle_login( browser=browser_info, os=os_info, status="登录成功", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) return [None] * 4 @@ -749,7 +749,7 @@ def handle_login_user_email_submit(nClicks, email, verification_code): ), os="{} {}".format(user_agent.os.family, user_agent.os.version_string), status=log_status, - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) # 提前终止当前无输出回调 return @@ -772,7 +772,7 @@ def handle_login_user_email_submit(nClicks, email, verification_code): ), os="{} {}".format(user_agent.os.family, user_agent.os.version_string), status="邮箱登录成功", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) @@ -846,7 +846,7 @@ def reject_login( browser=browser_info, os=os_info, status=log_status, - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) try: @@ -932,5 +932,5 @@ def reject_login( browser=browser_info, os=os_info, status="OTP登录成功", - login_datetime=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + login_datetime=datetime.now(), ) diff --git a/magic_dash/templates/magic-dash-pro/models/_peewee/logs.py b/magic_dash/templates/magic-dash-pro/models/_peewee/logs.py index 5ea3fb6..9201b2e 100644 --- a/magic_dash/templates/magic-dash-pro/models/_peewee/logs.py +++ b/magic_dash/templates/magic-dash-pro/models/_peewee/logs.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import List, Literal from peewee import AutoField, CharField, DateTimeField @@ -82,7 +83,7 @@ def add_log( browser: str, os: str, status: str, - login_datetime: str, + login_datetime: datetime, ): """添加日志记录""" diff --git a/magic_dash/templates/magic-dash-pro/models/_sqlalchemy/logs.py b/magic_dash/templates/magic-dash-pro/models/_sqlalchemy/logs.py index e0d6025..4c08d2d 100644 --- a/magic_dash/templates/magic-dash-pro/models/_sqlalchemy/logs.py +++ b/magic_dash/templates/magic-dash-pro/models/_sqlalchemy/logs.py @@ -61,16 +61,9 @@ def add_log( browser: str, os: str, status: str, - login_datetime: str, + login_datetime: datetime, ): with session_scope() as session: - # 应用回调中传入的是格式化后的字符串,这里统一转换为datetime入库 - if isinstance(login_datetime, str): - login_datetime = datetime.strptime( - login_datetime, - "%Y-%m-%d %H:%M:%S", - ) - session.add( cls( user_name=user_name, @@ -108,12 +101,7 @@ def columns(cls): @classmethod def to_dict(cls, record): - result = object_to_dict(record, cls.columns()) - if isinstance(result["login_datetime"], datetime): - result["login_datetime"] = result["login_datetime"].strftime( - "%Y-%m-%d %H:%M:%S" - ) - return result + return object_to_dict(record, cls.columns()) # 保持Peewee旧实现的导入时建表行为,避免日志页首次访问时报表不存在 diff --git a/magic_dash/templates/magic-dash-pro/models/_sqlmodel/logs.py b/magic_dash/templates/magic-dash-pro/models/_sqlmodel/logs.py index 1371f90..ebe5be7 100644 --- a/magic_dash/templates/magic-dash-pro/models/_sqlmodel/logs.py +++ b/magic_dash/templates/magic-dash-pro/models/_sqlmodel/logs.py @@ -73,16 +73,9 @@ def add_log( browser: str, os: str, status: str, - login_datetime: str, + login_datetime: datetime, ): with session_scope() as session: - # 应用回调中传入的是格式化后的字符串,这里统一转换为datetime入库 - if isinstance(login_datetime, str): - login_datetime = datetime.strptime( - login_datetime, - "%Y-%m-%d %H:%M:%S", - ) - session.add( cls( user_name=user_name, @@ -120,12 +113,7 @@ def columns(cls): @classmethod def to_dict(cls, record): - result = object_to_dict(record, cls.columns()) - if isinstance(result["login_datetime"], datetime): - result["login_datetime"] = result["login_datetime"].strftime( - "%Y-%m-%d %H:%M:%S" - ) - return result + return object_to_dict(record, cls.columns()) # 保持Peewee旧实现的导入时建表行为,避免日志页首次访问时报表不存在 diff --git a/tests/test_magic_dash_pro_orm_engines.py b/tests/test_magic_dash_pro_orm_engines.py index 8664f86..f39b0b3 100644 --- a/tests/test_magic_dash_pro_orm_engines.py +++ b/tests/test_magic_dash_pro_orm_engines.py @@ -1,13 +1,12 @@ import importlib import sys +from datetime import datetime from pathlib import Path import pytest -TEMPLATE_ROOT = ( - Path(__file__).resolve().parents[1] / "magic_dash" / "templates" / "magic-dash-pro" -) +TEMPLATES_ROOT = Path(__file__).resolve().parents[1] / "magic_dash" / "templates" def clear_template_modules(): @@ -18,15 +17,20 @@ def clear_template_modules(): sys.modules.pop(module_name) -@pytest.mark.parametrize("orm_engine", ["sqlalchemy", "sqlmodel"]) -def test_magic_dash_pro_alternative_engine_keeps_model_api_contract( +@pytest.mark.parametrize( + "template_name", + ["magic-dash-pro", "magic-dash-pro-fastapi"], +) +@pytest.mark.parametrize("orm_engine", ["peewee", "sqlalchemy", "sqlmodel"]) +def test_magic_dash_pro_engine_keeps_model_api_contract( tmp_path, monkeypatch, + template_name, orm_engine, ): clear_template_modules() monkeypatch.chdir(tmp_path) - monkeypatch.syspath_prepend(str(TEMPLATE_ROOT)) + monkeypatch.syspath_prepend(str(TEMPLATES_ROOT / template_name)) engine_package = f"models._{orm_engine}" models = importlib.import_module(engine_package) @@ -73,6 +77,7 @@ def test_magic_dash_pro_alternative_engine_keeps_model_api_contract( Users.get_all_users(with_department_name=True)[0]["department_name"] == "研发部" ) + login_datetime = datetime(2026, 7, 6, 12, 0, 0) LoginLogs.add_log( "admin", "user-1", @@ -80,11 +85,12 @@ def test_magic_dash_pro_alternative_engine_keeps_model_api_contract( "Chrome", "Windows", "登录成功", - "2026-07-06 12:00:00", + login_datetime, ) assert LoginLogs.get_count() == 1 assert LoginLogs.get_logs()[0]["user_name"] == "admin" - assert LoginLogs.get_logs()[0]["login_datetime"] == "2026-07-06 12:00:00" + assert isinstance(LoginLogs.get_logs()[0]["login_datetime"], datetime) + assert LoginLogs.get_logs()[0]["login_datetime"] == login_datetime verification, remaining_seconds, previous_verification = ( EmailVerifications.issue_verification("admin@example.com", 60) From b041525b81aba629be67ab2b94ae84dffe2dcbbd Mon Sep 17 00:00:00 2001 From: CNFeffery Date: Sun, 30 Aug 2026 21:24:53 +0800 Subject: [PATCH 2/5] test: isolate ORM contract checks and run full suite --- .github/workflows/ci.yml | 4 +- tests/_magic_dash_pro_orm_contract.py | 96 ++++++++++++++++++++++ tests/test_magic_dash_pro_orm_engines.py | 100 ++++------------------- 3 files changed, 114 insertions(+), 86 deletions(-) create mode 100644 tests/_magic_dash_pro_orm_contract.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab08ef6..105f4f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,8 +27,8 @@ jobs: python -m pip install --upgrade pip pip install -e ".[test]" - - name: Run CLI tests with pytest + - name: Run test suite with pytest env: PYTHONIOENCODING: utf-8 run: | - python -m pytest tests/test_cli.py -v + python -m pytest -v diff --git a/tests/_magic_dash_pro_orm_contract.py b/tests/_magic_dash_pro_orm_contract.py new file mode 100644 index 0000000..c312e46 --- /dev/null +++ b/tests/_magic_dash_pro_orm_contract.py @@ -0,0 +1,96 @@ +"""Run one magic-dash-pro template/ORM contract check in an isolated process.""" + +import importlib +import os +import sys +from datetime import datetime +from pathlib import Path + + +def check_model_api_contract(template_root: Path, orm_engine: str, workdir: Path): + os.chdir(workdir) + sys.path.insert(0, str(template_root)) + + engine_package = f"models._{orm_engine}" + models = importlib.import_module(engine_package) + Users = importlib.import_module(f"{engine_package}.users").Users + Departments = importlib.import_module(f"{engine_package}.departments").Departments + LoginLogs = importlib.import_module(f"{engine_package}.logs").LoginLogs + EmailVerifications = importlib.import_module( + f"{engine_package}.email_verifications" + ).EmailVerifications + OtpCredentials = importlib.import_module( + f"{engine_package}.otp_credentials" + ).OtpCredentials + UserPermissionGroups = importlib.import_module( + f"{engine_package}.user_permission_groups" + ).UserPermissionGroups + + try: + models.create_tables( + [ + Users, + Departments, + LoginLogs, + EmailVerifications, + OtpCredentials, + UserPermissionGroups, + ] + ) + + Departments.add_department("dept-1", "研发部") + assert Departments.get_department("dept-1").department_name == "研发部" + assert Departments.get_all_departments()[0]["department_id"] == "dept-1" + + assert UserPermissionGroups.get_all_permission_groups() == [] + Users.add_user( + "user-1", + "admin", + "password-hash", + user_email="admin@example.com", + department_id="dept-1", + user_role="normal", + ) + assert Users.get_user("user-1").user_name == "admin" + assert Users.get_user_by_email("admin@example.com").user_id == "user-1" + assert ( + Users.get_all_users(with_department_name=True)[0]["department_name"] + == "研发部" + ) + + login_datetime = datetime(2026, 7, 6, 12, 0, 0) + LoginLogs.add_log( + "admin", + "user-1", + "127.0.0.1", + "Chrome", + "Windows", + "登录成功", + login_datetime, + ) + assert LoginLogs.get_count() == 1 + login_log = LoginLogs.get_logs()[0] + assert login_log["user_name"] == "admin" + assert isinstance(login_log["login_datetime"], datetime) + assert login_log["login_datetime"] == login_datetime + + verification, remaining_seconds, previous_verification = ( + EmailVerifications.issue_verification("admin@example.com", 60) + ) + assert verification.verification_code.isdigit() + assert remaining_seconds == 0 + assert previous_verification is None + + credential = OtpCredentials.enable_credential("user-1", "secret") + assert credential.is_enabled + assert OtpCredentials.has_enabled_otp("user-1") + finally: + models.db.close() + + +if __name__ == "__main__": + check_model_api_contract( + template_root=Path(sys.argv[1]).resolve(), + orm_engine=sys.argv[2], + workdir=Path(sys.argv[3]).resolve(), + ) diff --git a/tests/test_magic_dash_pro_orm_engines.py b/tests/test_magic_dash_pro_orm_engines.py index f39b0b3..9404814 100644 --- a/tests/test_magic_dash_pro_orm_engines.py +++ b/tests/test_magic_dash_pro_orm_engines.py @@ -1,20 +1,12 @@ -import importlib +import subprocess import sys -from datetime import datetime from pathlib import Path import pytest TEMPLATES_ROOT = Path(__file__).resolve().parents[1] / "magic_dash" / "templates" - - -def clear_template_modules(): - for module_name in list(sys.modules): - if module_name == "models" or module_name.startswith("models."): - sys.modules.pop(module_name) - elif module_name == "configs" or module_name.startswith("configs."): - sys.modules.pop(module_name) +CONTRACT_RUNNER = Path(__file__).with_name("_magic_dash_pro_orm_contract.py") @pytest.mark.parametrize( @@ -24,84 +16,24 @@ def clear_template_modules(): @pytest.mark.parametrize("orm_engine", ["peewee", "sqlalchemy", "sqlmodel"]) def test_magic_dash_pro_engine_keeps_model_api_contract( tmp_path, - monkeypatch, template_name, orm_engine, ): - clear_template_modules() - monkeypatch.chdir(tmp_path) - monkeypatch.syspath_prepend(str(TEMPLATES_ROOT / template_name)) - - engine_package = f"models._{orm_engine}" - models = importlib.import_module(engine_package) - Users = importlib.import_module(f"{engine_package}.users").Users - Departments = importlib.import_module(f"{engine_package}.departments").Departments - LoginLogs = importlib.import_module(f"{engine_package}.logs").LoginLogs - EmailVerifications = importlib.import_module( - f"{engine_package}.email_verifications" - ).EmailVerifications - OtpCredentials = importlib.import_module( - f"{engine_package}.otp_credentials" - ).OtpCredentials - UserPermissionGroups = importlib.import_module( - f"{engine_package}.user_permission_groups" - ).UserPermissionGroups - - models.create_tables( + result = subprocess.run( [ - Users, - Departments, - LoginLogs, - EmailVerifications, - OtpCredentials, - UserPermissionGroups, - ] - ) - - Departments.add_department("dept-1", "研发部") - assert Departments.get_department("dept-1").department_name == "研发部" - assert Departments.get_all_departments()[0]["department_id"] == "dept-1" - - assert UserPermissionGroups.get_all_permission_groups() == [] - Users.add_user( - "user-1", - "admin", - "password-hash", - user_email="admin@example.com", - department_id="dept-1", - user_role="normal", - ) - assert Users.get_user("user-1").user_name == "admin" - assert Users.get_user_by_email("admin@example.com").user_id == "user-1" - assert ( - Users.get_all_users(with_department_name=True)[0]["department_name"] == "研发部" - ) - - login_datetime = datetime(2026, 7, 6, 12, 0, 0) - LoginLogs.add_log( - "admin", - "user-1", - "127.0.0.1", - "Chrome", - "Windows", - "登录成功", - login_datetime, + sys.executable, + str(CONTRACT_RUNNER), + str(TEMPLATES_ROOT / template_name), + orm_engine, + str(tmp_path), + ], + capture_output=True, + text=True, + check=False, ) - assert LoginLogs.get_count() == 1 - assert LoginLogs.get_logs()[0]["user_name"] == "admin" - assert isinstance(LoginLogs.get_logs()[0]["login_datetime"], datetime) - assert LoginLogs.get_logs()[0]["login_datetime"] == login_datetime - verification, remaining_seconds, previous_verification = ( - EmailVerifications.issue_verification("admin@example.com", 60) + assert result.returncode == 0, ( + f"{template_name}/{orm_engine} contract check failed\n" + f"stdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}" ) - assert verification.verification_code.isdigit() - assert remaining_seconds == 0 - assert previous_verification is None - - credential = OtpCredentials.enable_credential("user-1", "secret") - assert credential.is_enabled - assert OtpCredentials.has_enabled_otp("user-1") - - models.db.close() - clear_template_modules() From 757115d2c4fae0459be175741d620a27227e241a Mon Sep 17 00:00:00 2001 From: CNFeffery Date: Sun, 30 Aug 2026 21:24:53 +0800 Subject: [PATCH 3/5] docs: clarify login log datetime contract --- ...\254\241\345\274\200\345\217\221\346\214\207\345\215\227.md" | 2 ++ 1 file changed, 2 insertions(+) diff --git "a/docs/magic-dash-pro/\344\272\214\346\254\241\345\274\200\345\217\221\346\214\207\345\215\227.md" "b/docs/magic-dash-pro/\344\272\214\346\254\241\345\274\200\345\217\221\346\214\207\345\215\227.md" index 26f3b91..1eb3615 100644 --- "a/docs/magic-dash-pro/\344\272\214\346\254\241\345\274\200\345\217\221\346\214\207\345\215\227.md" +++ "b/docs/magic-dash-pro/\344\272\214\346\254\241\345\274\200\345\217\221\346\214\207\345\215\227.md" @@ -157,6 +157,8 @@ roles = { 切换数据库类型时,先修改`DatabaseConfig.database_type`和对应连接配置,再确认目标数据库已创建。 +内置模型会在`Peewee`、`SQLAlchemy`和`SQLModel`实现之间保持一致的上层类型契约。以登录日志为例,`LoginLogs.add_log()`的`login_datetime`参数应传入原生`datetime`对象,`LoginLogs.get_logs()`返回记录中的`login_datetime`也始终为`datetime`对象。日期时间的字符串格式化应留在页面展示、文件导出或接口序列化层处理,不应放在模型层,以免不同ORM返回不同类型。 + ## 登录与安全能力 常见安全增强点: From 3400038430ed38512c989ae1f3f7ba4eb478c604 Mon Sep 17 00:00:00 2001 From: CNFeffery Date: Sun, 30 Aug 2026 21:29:17 +0800 Subject: [PATCH 4/5] test: install dependencies for the full suite --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 2f8b0af..a3e9f35 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,10 @@ def get_version(): "peewee>=4.0.0", "SQLAlchemy>=2.0.0", "sqlmodel>=0.0.27", + "cryptography", + "dash[fastapi]>=4.2.0,<5.0.0", + "fastapi-login", + "user_agents", ], }, entry_points={ From ea6b6b7eb8fca9f9d071159f8ecc45d529a73728 Mon Sep 17 00:00:00 2001 From: CNFeffery Date: Sun, 30 Aug 2026 21:32:26 +0800 Subject: [PATCH 5/5] test: add HTTP client for FastAPI checks --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a3e9f35..722a8af 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ def get_version(): "cryptography", "dash[fastapi]>=4.2.0,<5.0.0", "fastapi-login", + "httpx", "user_agents", ], },