From eaee38d658d08184da462163879c51238fcaeeee Mon Sep 17 00:00:00 2001 From: xiaji Date: Sun, 4 Jan 2026 19:17:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=AE=B6=E5=BA=AD=E6=97=A5?= =?UTF-8?q?=E6=8A=A5=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .trae/documents/家庭日报系统设计与实现.md | 197 +++++++ core/__init__.py | 0 core/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 177 bytes core/__pycache__/admin.cpython-313.pyc | Bin 0 -> 221 bytes core/__pycache__/apps.cpython-313.pyc | Bin 0 -> 537 bytes core/__pycache__/forms.cpython-313.pyc | Bin 0 -> 5786 bytes core/__pycache__/models.cpython-313.pyc | Bin 0 -> 7860 bytes core/__pycache__/urls.cpython-313.pyc | Bin 0 -> 2414 bytes core/__pycache__/views.cpython-313.pyc | Bin 0 -> 22020 bytes core/admin.py | 3 + core/apps.py | 6 + core/forms.py | 84 +++ .../create_superuser.cpython-313.pyc | Bin 0 -> 1537 bytes core/management/commands/create_superuser.py | 18 + core/migrations/0001_initial.py | 104 ++++ core/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-313.pyc | Bin 0 -> 5459 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 188 bytes core/models.py | 131 +++++ core/tasks.py | 123 +++++ core/templates/core/add_family_task.html | 27 + core/templates/core/add_insight.html | 27 + core/templates/core/add_reading.html | 27 + core/templates/core/add_today_plan.html | 27 + core/templates/core/base.html | 130 +++++ core/templates/core/delete_family_task.html | 18 + core/templates/core/delete_insight.html | 18 + core/templates/core/delete_reading.html | 18 + core/templates/core/delete_today_plan.html | 18 + core/templates/core/edit_family_task.html | 27 + core/templates/core/edit_insight.html | 27 + core/templates/core/edit_reading.html | 27 + core/templates/core/edit_today_plan.html | 27 + core/templates/core/family_tasks.html | 61 +++ core/templates/core/index.html | 141 +++++ core/templates/core/report.html | 171 ++++++ core/templates/core/report_pdf.html | 203 +++++++ core/templates/core/system_settings.html | 95 ++++ core/templates/core/today_plan.html | 66 +++ core/templates/core/yesterday_records.html | 105 ++++ core/tests.py | 3 + core/urls.py | 40 ++ core/views.py | 503 ++++++++++++++++++ db.sqlite3 | Bin 0 -> 212992 bytes diary_family/__init__.py | 4 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 259 bytes .../__pycache__/celery.cpython-313.pyc | Bin 0 -> 1102 bytes .../__pycache__/settings.cpython-313.pyc | Bin 0 -> 3477 bytes diary_family/__pycache__/urls.cpython-313.pyc | Bin 0 -> 1533 bytes diary_family/__pycache__/wsgi.cpython-313.pyc | Bin 0 -> 683 bytes diary_family/asgi.py | 16 + diary_family/celery.py | 20 + diary_family/settings.py | 171 ++++++ diary_family/urls.py | 30 ++ diary_family/wsgi.py | 16 + logs/app.log | 5 + manage.py | 22 + 57 files changed, 2756 insertions(+) create mode 100644 .trae/documents/家庭日报系统设计与实现.md create mode 100644 core/__init__.py create mode 100644 core/__pycache__/__init__.cpython-313.pyc create mode 100644 core/__pycache__/admin.cpython-313.pyc create mode 100644 core/__pycache__/apps.cpython-313.pyc create mode 100644 core/__pycache__/forms.cpython-313.pyc create mode 100644 core/__pycache__/models.cpython-313.pyc create mode 100644 core/__pycache__/urls.cpython-313.pyc create mode 100644 core/__pycache__/views.cpython-313.pyc create mode 100644 core/admin.py create mode 100644 core/apps.py create mode 100644 core/forms.py create mode 100644 core/management/commands/__pycache__/create_superuser.cpython-313.pyc create mode 100644 core/management/commands/create_superuser.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/__init__.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-313.pyc create mode 100644 core/migrations/__pycache__/__init__.cpython-313.pyc create mode 100644 core/models.py create mode 100644 core/tasks.py create mode 100644 core/templates/core/add_family_task.html create mode 100644 core/templates/core/add_insight.html create mode 100644 core/templates/core/add_reading.html create mode 100644 core/templates/core/add_today_plan.html create mode 100644 core/templates/core/base.html create mode 100644 core/templates/core/delete_family_task.html create mode 100644 core/templates/core/delete_insight.html create mode 100644 core/templates/core/delete_reading.html create mode 100644 core/templates/core/delete_today_plan.html create mode 100644 core/templates/core/edit_family_task.html create mode 100644 core/templates/core/edit_insight.html create mode 100644 core/templates/core/edit_reading.html create mode 100644 core/templates/core/edit_today_plan.html create mode 100644 core/templates/core/family_tasks.html create mode 100644 core/templates/core/index.html create mode 100644 core/templates/core/report.html create mode 100644 core/templates/core/report_pdf.html create mode 100644 core/templates/core/system_settings.html create mode 100644 core/templates/core/today_plan.html create mode 100644 core/templates/core/yesterday_records.html create mode 100644 core/tests.py create mode 100644 core/urls.py create mode 100644 core/views.py create mode 100644 db.sqlite3 create mode 100644 diary_family/__init__.py create mode 100644 diary_family/__pycache__/__init__.cpython-313.pyc create mode 100644 diary_family/__pycache__/celery.cpython-313.pyc create mode 100644 diary_family/__pycache__/settings.cpython-313.pyc create mode 100644 diary_family/__pycache__/urls.cpython-313.pyc create mode 100644 diary_family/__pycache__/wsgi.cpython-313.pyc create mode 100644 diary_family/asgi.py create mode 100644 diary_family/celery.py create mode 100644 diary_family/settings.py create mode 100644 diary_family/urls.py create mode 100644 diary_family/wsgi.py create mode 100644 logs/app.log create mode 100644 manage.py diff --git a/.trae/documents/家庭日报系统设计与实现.md b/.trae/documents/家庭日报系统设计与实现.md new file mode 100644 index 0000000..8001d9a --- /dev/null +++ b/.trae/documents/家庭日报系统设计与实现.md @@ -0,0 +1,197 @@ +# 家庭日报系统设计与实现(Django 5.1.4) + +## 系统概述 + +一个轻量化的家庭日报系统,用于自动汇总昨日信息(阅读、收获),生成今日计划,注重生活化、个性化和低门槛使用。支持上传文件或视频作为附加内容,并可生成PDF报告定时发送至邮箱。 + +## 技术栈 + +- **Python 3.9+**:核心开发语言 +- **Django 5.1.4**:Web框架 +- **SQLite**:轻量化数据库 +- **loguru**:日志记录 +- **Bootstrap 5**:前端样式 +- **Chart.js**:数据可视化 +- **WeasyPrint**:PDF生成 +- **Celery**:定时任务 +- **Redis**:消息队列(用于Celery) +- **django-celery-beat**:定时任务管理 + +## 项目结构 + +``` +diary-family/ +├── diary_family/ # Django项目目录 +│ ├── settings.py # 项目配置 +│ ├── urls.py # 路由配置 +│ └── wsgi.py # WSGI配置 +├── core/ # 核心应用 +│ ├── migrations/ # 数据库迁移 +│ ├── models.py # 数据模型 +│ ├── views.py # 视图函数 +│ ├── forms.py # 表单定义 +│ ├── templates/ # 模板文件 +│ ├── static/ # 静态文件 +│ ├── tasks.py # Celery任务 +│ └── apps.py # 应用配置 +├── media/ # 媒体文件存储 +├── logs/ # 日志目录 +├── reports/ # PDF报告存储 +├── manage.py # Django管理脚本 +└── requirements.txt # 依赖文件 +``` + +## 核心功能模块 + +### 1. 数据模型设计 + +#### 1.1 阅读记录表 (ReadingRecord) +- 日期、类型(书籍/文章/视频)、标题、来源、进度、上传文件 + +#### 1.2 感悟记录表 (InsightRecord) +- 日期、内容、上传文件 + +#### 1.3 家庭事项表 (FamilyTask) +- 类型(采购/家务)、内容、优先级、状态、截止日期 + +#### 1.4 今日计划表 (TodayPlan) +- 日期、内容、优先级、类型、状态 + +#### 1.5 系统配置表 (SystemConfig) +- 邮箱设置(SMTP服务器、端口、用户名、密码) +- 报告发送时间 +- 收件人邮箱 + +### 2. 核心功能 + +#### 2.1 昨日记录 +- 阅读记录(支持多类型) +- 感悟记录 +- 支持文件/视频上传 + +#### 2.2 家庭事项管理 +- 事项添加、编辑、删除 +- 按类型、状态筛选 + +#### 2.3 今日计划生成 +- 基于昨日状态和待处理事项自动生成 +- 支持手动调整 +- 完成状态标记 + +#### 2.4 报告生成 +- 整合昨日阅读、感悟和今日计划 +- 可视化统计 +- 支持生成PDF格式 +- 支持查看历史报告 + +#### 2.5 定时邮件发送 +- 配置每日发送时间 +- 自动生成并发送PDF报告到指定邮箱 +- 支持手动触发发送 + +### 3. 文件上传功能 + +- 支持上传图片、文档、视频等多种格式 +- 自动生成访问链接 +- 支持预览和下载 + +## 实现步骤 + +### 1. 项目初始化 + +- 创建Django项目 +- 创建核心应用 +- 配置数据库和媒体文件路径 +- 安装依赖包 + +### 2. 数据模型实现 + +- 定义模型类 +- 生成并执行数据库迁移 +- 创建超级用户 + +### 3. 功能实现 + +- 实现昨日记录表单和视图 +- 实现家庭事项管理功能 +- 实现今日计划生成和管理 +- 实现报告生成功能 +- 实现PDF报告生成 +- 实现文件上传功能 +- 配置Celery和定时任务 +- 实现邮件发送功能 + +### 4. 模板设计 + +- 基础模板(导航栏、页脚) +- 昨日记录页面 +- 家庭事项页面 +- 今日计划页面 +- 报告页面 +- 系统配置页面 +- PDF报告模板 + +### 5. 日志配置 + +- 使用loguru配置日志 +- 记录关键操作和错误信息 + +## 运行方式 + +1. **安装依赖**: + ```bash + pip install -r requirements.txt + ``` + +2. **启动Redis服务**: + ```bash + redis-server + ``` + +3. **数据库迁移**: + ```bash + python manage.py migrate + ``` + +4. **启动Django开发服务器**: + ```bash + python manage.py runserver + ``` + +5. **启动Celery worker**: + ```bash + celery -A diary_family worker --loglevel=info + ``` + +6. **启动Celery beat**: + ```bash + celery -A diary_family beat --loglevel=info + ``` + +7. **访问系统**: + - 浏览器打开 http://localhost:8000 + - 支持移动端访问 + +## 系统特点 + +- **轻量化**:使用SQLite,部署简单 +- **低门槛**:界面简洁,易于使用 +- **生活化**:专注家庭场景 +- **个性化**:支持自定义记录和计划 +- **文件支持**:支持上传多种格式文件 +- **可视化**:提供数据统计图表 +- **响应式**:适配移动端和桌面端 +- **PDF报告**:支持生成PDF格式报告 +- **定时邮件**:自动发送报告到指定邮箱 + +## 预期效果 + +用户可以通过手机或电脑轻松完成: +1. 记录昨日阅读内容和感悟,可上传相关文件 +2. 管理家庭事项 +3. 生成并查看今日计划 +4. 查看自动生成的可视化报告,支持PDF下载 +5. 配置系统参数,设置邮件发送 +6. 每日自动收到包含PDF报告的邮件 + +系统设计简洁易用,适合家庭日常使用,注重生活化和个性化体验。 \ No newline at end of file diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/__pycache__/__init__.cpython-313.pyc b/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41b87f7196e15ae6d943a5c4720fbef30aa6291b GIT binary patch literal 177 zcmey&%ge<81OjeRnIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{AB=yA?NH|-?gche3 z6~|O$CT3;Exa237=BDPA6vsT-vFgdLUC*YqKiR$Q>5`o>PnYz+T(vYNB{Q+8Qa3Fz zH#4U)CON+-H6}hjGcU6wK3=b&@)n0pZhlH>PO4oIE6{9^1ByY6kIamWj77{q763DS BG=%^F literal 0 HcmV?d00001 diff --git a/core/__pycache__/admin.cpython-313.pyc b/core/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e83834b4760cf6be56502064d9ab696ea70e79be GIT binary patch literal 221 zcmey&%ge<81OjeRnNC3ZF^B^LOi;#W0U%>4Loh=yqc?*WV-ceQLpqZt^GlGlCgUyE z#FX63JU>mQTYM>5iFxVyddc~DB}JJ@Ma)12D;Yk6)P3;+63$jJp~b01#W59`iCLL3 zF8Rr&xv6<2#W7EIta`F**RyHuPj+v6x@2d}(814NTQkz*L}s7&ZZ8pUh!TP)z#xxt4T2Cws!O}=HITkjoOrq zUm!Ulj4=9+yrn>&l1Jn@Vcs%fKBq7J$_HK?gtd_?LR#M0$n#E?_H^8>cA*J+6iIvf0T%RWn8b+7F&Ydoqo)cC_2>XOT)w`?ZcY3Y%?d zl|*IGMgKRl9nO>uAyU~_LX=G*&|4&q-xcCRA(N?cPl%odV-uaKG(&kIQaxEDR!AI) z+GM6Pmk)$6R$AQ^(pu2Fg;hofXy8&~_n@0M2%R(ih7`t?@D7guzNr_H{DgZgnp6+_dunxWiAtz1p-8K9Z}Y)yv@F z)$IJ?Cd}RH+o501PLJZ6!F@&5?slt;^T^7^tLh5tu!yDm1#=(auTLrcMV{T%J-TqT K^p9Ygbo~d|rj;cC literal 0 HcmV?d00001 diff --git a/core/__pycache__/forms.cpython-313.pyc b/core/__pycache__/forms.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1058f08023e18a6b61dc94a471a4b55500dc7ac9 GIT binary patch literal 5786 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 Wo!ymb`=FiZe(UU8_ZU!V3j8m$IVoQN literal 0 HcmV?d00001 diff --git a/core/__pycache__/models.cpython-313.pyc b/core/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81e73a8b45702383c4988994bccfd8adfd5b0a40 GIT binary patch literal 7860 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 literal 0 HcmV?d00001 diff --git a/core/__pycache__/urls.cpython-313.pyc b/core/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c2bda53fdac95b60b4d6364244c142eed2e14c0 GIT binary patch literal 2414 zcmbW&%TF6e7y$4!Uh^_w48(wK-jCRTy)H>ql~AKVNCH$n)J09eEm~zK&L)0ocHP8Q zd+4d@DO_6lm;;q)PB~R+dg-m#ozto--Dq0(V(+Ms^^6M{ zG3))L)fss5o$+OwKk(Y31%&-uC%yD4)5=5}h>p{}wrFR<52A}BI+*AL(ajNEOawsm za6~r~K@fc$(ZfV9h!98gF>wLJ07rzF=m!zzhyf-BK}0zs%tQplFh@k07y>cM5yMQ3 zfEee9Q6|PfOmf6H6B8h&IbxEDDG)J^m}cT4h#8KEF%btb#}PA3%z{XA#2ga|5GjsG zG9iJO=ZF*)QV550@5Sd2DTsS4u`KKN4?T^B?@JXu(k#L9sYL}nrrkxkfNJekr0EDN za#gZ|?0N^IyCtk>wGc~57|DuS%t^AMNR}TWAUjhVJ&zXEqP|dmn3j;D>XOw#znCBY z7q6f^(vf6!H7?i~S0QRqQ*-xqS0Tq4cOeeq7UFnUA?N(rLYy(3_vC__uO@U^d#J%b zYczN4W-WNC)1IjSohcWpMNXMB^>y15<>o_t?v&SaJWuW+e!T?GGf~dVMagQn8#}a7 z3%EM$;<{*YE->f->ScF=ri0rwr0cM-;b8`}D!dGZ9Vb)s zYpAFsP(fDnFq5>gV>dpIM=ZbcKrZG=$qLSE7!Dx;H`Cyw0ZuCcx6wdP73`;ho*uZ9 z26{^101fmsz(E@5u8(_Zpu0Q1Km*;yaX$@ox5k4s&|MkR!w=Kl7Y{+O{7?eC>+l*E zwV#9|NC|(hL0DPHZfXc?*~hB2-yZz>^P9b2vTydD z9zOputEe)rCK@X#3-3S5(*Kq$SFP5?LP@FQQ5sLced(5>{SNW1*W>ZL7NW0(-gm8n zukA_Clg+2w-)+|)5%J=?W}1oaR?Pk+=}#J0ZV_?q53$u~8+&%eoVZLTF2DTHNzLwW znu#TnSo*C<#7$cz_A92eLZlUAeT#_O|9@nokdgY78J;8Ixff|7F57pD){!}Ui40#d z7B-0Zk)4~~_nEOYiKUIz&xm;2&c*hp%$a2}vuvz=PQ))7_ZzDF%z+pgi0ywx#6{bM z>#JroMWQL={hLI*W#=ZJA#?gFnZ9Z)XNdUe$!{!?*phMM4iUF_xyEk{?yj1Banctz zQrDcKhw2q`Bt=G2FTN(?sy)`8&qXq^Xsp~O;+@9W$SyKNvm`WY%&!pfIXs3?Aq|yfA@A literal 0 HcmV?d00001 diff --git a/core/__pycache__/views.cpython-313.pyc b/core/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..868d009098516e20cf947b609b50db5aae6c2ed3 GIT binary patch literal 22020 zcmeHPX>=RcbshkNeTa=4xQQS|N(RM63uP^qZ0!pvQ!+;I2E`0Sf)rvBq#rz)tCtoKumKe$|nk<5vCCd*5Id zfFMO#PI`KpC-GvIH*dD@-uvDA-kXmK3TzCN<9i>DZr;K$|Bfqi(PtJu+NWcfml&Sm zb?r=>j_BI-MBip01|M$Ix3g_VVr(-J6J0m7o7*hJLYLWgYnzSO=(4fh-o_EGt$-BJ zbyK^e%}Jbe+1&1Ga}#%)i@4~zrQJh3(6+U`u&sy`!8Xfd#k{SYAtk(>=h{o#yu{m9 zM#|dCNqJiZsc5Stl~Bvh7qnNkEg?(Vs!4U5kNDbZNKIQUsfB$GIC?4X1X;(sK-Tka zkjr=v$mM(?$UFEVkbb@xWCLFUas^)svXS?KY~ss6HuL2mTlfl)EBQ*0tN1FAtNA4$ z*YMRKTX`SIwR{c8JNa6W>-eQ0*YkBCH}qQk_1Eyx`VFFqL}J|$B3ekKJ4zy53DMmf zNpubz{sJs_4v@~)m92ig$n8la1_P1!;6N-M5xKVbKuliq>qJW;+8;SF5R1UFar?kf zECGjDmDFay#+o-bg$i?!`KmcqEaC#(Lve4Ug@P^d-Wgr8}I6;A#idG4&1f z_C`pTj0OB<;m-hnAH4|TB_;$?DqT*7Spw()EC4oQ3_NYaW19VSS<5v{$RrW3`ymH@@0ye9qDGCaE$Fo*@(Ha^BwXSLYFY&KLgJvzT>s1mBozCJvA?#%}x;rQ_b zBpOT1j$E2K`<Zxrr_Wve%FkxTzjk@zd8v(Eev?@Az|O7wLk9wTI}Ubky?^W8eOtHf+bP

^-g+`jQpFdiZCL&u`w zFGLUR80Z@6kHix3LzgGMd-?6RXJ3Bi@;g78dE+OCX5Kh+?YnOt>W+rV@ur?|f3)xT zp{@ZEIrMlmax~sNcw8)v#yiyu=?p&(Z$EsvFG9R<9=wS70T9E?$0o*7nKUo`P;a#Q zKj4;6EnO~fzK<;o=M&iKNqaTO|NPj@xGFwk^j7~g=Q-1HxXnnj`>~Iu(^6)DBt#3cBSj3{%UjmG@fJt@4t* zFQyCXLUws8ujdULbWNZSF@1=Ww+0;YlKLA!EajA-o@Y1cVuldnkd|9Kk_{Ez@{u}v zPGhL#R*&S3ya}$>6x0WGK|_!Y8iOX@+-!=OLuGPXLGS`W^ICjAQ0f@JbqJsIweP%q z?fl;k;cbF`&z}78)r-Hl_Kl0uW`akfx&x@<(r+Rnng_u)0vut3DV_)?hT@>n*5eU? z8-a?VF@Ycy-O83uBH>rNDJo%Dw9(3)g9zE8Ov9a$UUnt`((w~cRL_rcA;!+XJ+lr2kpo08t9 zw6`_sZJqE7-d4f8Ltu-iz4i0#fulSXX?JbXU7L3MlWzamkl^+U?zO{hu&Z)ex}qsr z(Uh)eO;)r{>=i0ng^FDQ>;2HglvGc-OQx!t@qdYL%Gdh40<+UJy!&Gh!+Fjw7b+UZ zdQiMwQqf!>L00xdSp2Wzkvc&bY}Dk^a>q9Ty$o%N#1ax zg6fP`0ck}^f9zC$$ZBjB;Y`+`$W|ZHirdh#N_s=J#YFDJ4XUQxg{zxTtOF58dm^cZ zq#0QjfL;LE3{`ex-RD4Pv}ENZyJ~S-Qd*ZTS&=MRkuF)CELlBa6-rhMCEJGg1K#J` zCBeO-VXC6xcV?ENoMR-Eub)_+vTqaEZDa#nP=1u}q9_kC0jQB%c-_Va876#`VL}

;-`hBGPEd=d-}?f|m%m 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 literal 0 HcmV?d00001 diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/core/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/core/apps.py b/core/apps.py new file mode 100644 index 0000000..8115ae6 --- /dev/null +++ b/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..f150251 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,84 @@ +from django import forms +from django.utils import timezone +from .models import ( + ReadingRecord, + InsightRecord, + FamilyTask, + TodayPlan, + SystemConfig +) + +class ReadingRecordForm(forms.ModelForm): + """阅读记录表单""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # 设置日期字段默认值为当日 + self.fields['date'].initial = timezone.now().date() + + class Meta: + model = ReadingRecord + fields = ['date', 'type', 'title', 'source', 'progress', 'file'] + widgets = { + 'date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control', 'readonly': 'readonly'}), + 'type': forms.Select(attrs={'class': 'form-select'}), + 'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入标题'}), + 'source': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入来源'}), + 'progress': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入进度'}), + 'file': forms.FileInput(attrs={'class': 'form-control'}), + } + +class InsightRecordForm(forms.ModelForm): + """感悟记录表单""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # 设置日期字段默认值为当日 + self.fields['date'].initial = timezone.now().date() + + class Meta: + model = InsightRecord + fields = ['date', 'content', 'file'] + widgets = { + 'date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control', 'readonly': 'readonly'}), + 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'placeholder': '请输入今日感悟'}), + 'file': forms.FileInput(attrs={'class': 'form-control'}), + } + +class FamilyTaskForm(forms.ModelForm): + """家庭事项表单""" + class Meta: + model = FamilyTask + fields = ['type', 'content', 'priority', 'status', 'deadline'] + widgets = { + 'type': forms.Select(attrs={'class': 'form-select'}), + 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': '请输入事项内容'}), + 'priority': forms.Select(attrs={'class': 'form-select'}), + 'status': forms.Select(attrs={'class': 'form-select'}), + 'deadline': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), + } + +class TodayPlanForm(forms.ModelForm): + """今日计划表单""" + class Meta: + model = TodayPlan + fields = ['date', 'content', 'priority', 'type', 'status'] + widgets = { + 'date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), + 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': '请输入计划内容'}), + 'priority': forms.Select(attrs={'class': 'form-select'}), + 'type': forms.Select(attrs={'class': 'form-select'}), + 'status': forms.Select(attrs={'class': 'form-select'}), + } + +class SystemConfigForm(forms.ModelForm): + """系统配置表单""" + class Meta: + model = SystemConfig + fields = ['smtp_server', 'smtp_port', 'smtp_username', 'smtp_password', 'send_time', 'recipient_email'] + widgets = { + 'smtp_server': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入SMTP服务器'}), + 'smtp_port': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '请输入SMTP端口'}), + 'smtp_username': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入SMTP用户名'}), + 'smtp_password': forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': '请输入SMTP密码'}), + 'send_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), + 'recipient_email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': '请输入收件人邮箱'}), + } \ No newline at end of file diff --git a/core/management/commands/__pycache__/create_superuser.cpython-313.pyc b/core/management/commands/__pycache__/create_superuser.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70864a0dbd887af8ddb9f6c5fac7a085a3c04948 GIT binary patch literal 1537 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 Qu(dd7E&iS&GgZXD0JKxA8UO$Q literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/__init__.cpython-313.pyc b/core/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bed5233f7e424b99df8ef334202bacf40d44c33 GIT binary patch literal 188 zcmey&%ge<81OjeRnIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{AB=sc%NH|-?gche3 z6~|O$CT3;Exa237=BDPA6vsT-vFgdLUC*YqKiR$Q>5`o>PnYz+T(vYNB{Q+8Qa3Fz zH#4U)CON+-H6}MRy(qCHGe565CO$qhFS8^*Uaz3?7Kcr4eoARhs$CH)&>E0iib0Hz M%#4hTMa)1J0P&VNDF6Tf literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py new file mode 100644 index 0000000..c825936 --- /dev/null +++ b/core/models.py @@ -0,0 +1,131 @@ +from django.db import models +from django.utils import timezone + +# 阅读记录类型 +READING_TYPE_CHOICES = [ + ('book', '书籍'), + ('article', '文章'), + ('video', '视频'), +] + +# 家庭事项类型 +FAMILY_TASK_TYPE_CHOICES = [ + ('purchase', '采购'), + ('housework', '家务'), +] + +# 优先级 +PRIORITY_CHOICES = [ + ('low', '低'), + ('medium', '中'), + ('high', '高'), +] + +# 状态 +STATUS_CHOICES = [ + ('pending', '待处理'), + ('completed', '已完成'), +] + +# 今日计划类型 +PLAN_TYPE_CHOICES = [ + ('reading', '阅读'), + ('insight', '感悟'), + ('family', '家庭事项'), + ('other', '其他'), +] + +class ReadingRecord(models.Model): + """阅读记录表""" + date = models.DateField(default=timezone.now, verbose_name="日期") + type = models.CharField(max_length=20, choices=READING_TYPE_CHOICES, verbose_name="类型") + title = models.CharField(max_length=200, verbose_name="标题") + source = models.CharField(max_length=200, blank=True, null=True, verbose_name="来源") + progress = models.CharField(max_length=100, blank=True, null=True, verbose_name="进度") + file = models.FileField(upload_to='reading_files/', blank=True, null=True, verbose_name="上传文件") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") + updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") + + class Meta: + verbose_name = "阅读记录" + verbose_name_plural = "阅读记录" + ordering = ['-date', '-created_at'] + + def __str__(self): + return f"{self.title} ({self.get_type_display()})" + +class InsightRecord(models.Model): + """感悟记录表""" + date = models.DateField(default=timezone.now, verbose_name="日期") + content = models.TextField(verbose_name="内容") + file = models.FileField(upload_to='insight_files/', blank=True, null=True, verbose_name="上传文件") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") + updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") + + class Meta: + verbose_name = "感悟记录" + verbose_name_plural = "感悟记录" + ordering = ['-date', '-created_at'] + + def __str__(self): + return f"感悟记录 - {self.date}" + +class FamilyTask(models.Model): + """家庭事项表""" + type = models.CharField(max_length=20, choices=FAMILY_TASK_TYPE_CHOICES, verbose_name="类型") + content = models.TextField(verbose_name="内容") + priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default='medium', verbose_name="优先级") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="状态") + deadline = models.DateField(blank=True, null=True, verbose_name="截止日期") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") + updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") + + class Meta: + verbose_name = "家庭事项" + verbose_name_plural = "家庭事项" + ordering = ['-priority', '-deadline', '-created_at'] + + def __str__(self): + return f"{self.get_type_display()} - {self.content[:20]}..." + +class TodayPlan(models.Model): + """今日计划表""" + date = models.DateField(default=timezone.now, verbose_name="日期") + content = models.TextField(verbose_name="内容") + priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default='medium', verbose_name="优先级") + type = models.CharField(max_length=20, choices=PLAN_TYPE_CHOICES, default='other', verbose_name="类型") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="状态") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") + updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") + + class Meta: + verbose_name = "今日计划" + verbose_name_plural = "今日计划" + ordering = ['-priority', '-created_at'] + + def __str__(self): + return f"{self.date} - {self.content[:20]}..." + +class SystemConfig(models.Model): + """系统配置表""" + smtp_server = models.CharField(max_length=100, blank=True, null=True, verbose_name="SMTP服务器") + smtp_port = models.IntegerField(default=587, verbose_name="SMTP端口") + smtp_username = models.CharField(max_length=100, blank=True, null=True, verbose_name="SMTP用户名") + smtp_password = models.CharField(max_length=100, blank=True, null=True, verbose_name="SMTP密码") + send_time = models.TimeField(default='08:00', verbose_name="每日发送时间") + recipient_email = models.EmailField(blank=True, null=True, verbose_name="收件人邮箱") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") + updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") + + class Meta: + verbose_name = "系统配置" + verbose_name_plural = "系统配置" + + def __str__(self): + return "系统配置" + + @classmethod + def get_config(cls): + """获取系统配置,单例模式""" + config, created = cls.objects.get_or_create(pk=1) + return config diff --git a/core/tasks.py b/core/tasks.py new file mode 100644 index 0000000..dac66d0 --- /dev/null +++ b/core/tasks.py @@ -0,0 +1,123 @@ +from celery import shared_task +from django.utils import timezone +from datetime import timedelta +from django.core.mail import EmailMessage +from django.conf import settings +from django.shortcuts import render +import os +from loguru import logger + +# WeasyPrint可用性标记,初始为None +WEASYPRINT_AVAILABLE = None + +# 获取WeasyPrint可用性 +def is_weasyprint_available(): + """检查WeasyPrint是否可用""" + global WEASYPRINT_AVAILABLE + if WEASYPRINT_AVAILABLE is None: + try: + from weasyprint import HTML + WEASYPRINT_AVAILABLE = True + except ImportError: + logger.warning("WeasyPrint库无法导入,PDF功能将不可用") + WEASYPRINT_AVAILABLE = False + return WEASYPRINT_AVAILABLE + +from .models import ( + ReadingRecord, + InsightRecord, + FamilyTask, + TodayPlan, + SystemConfig +) + +@shared_task +def send_daily_report(): + """发送每日报告""" + logger.info("开始执行每日报告发送任务") + + # 检查WeasyPrint是否可用 + if not is_weasyprint_available(): + logger.error("WeasyPrint库不可用,无法生成PDF报告") + return False + + today = timezone.now().date() + today_str = today.strftime('%Y-%m-%d') + + # 获取系统配置 + config = SystemConfig.get_config() + + # 检查邮件配置是否完整 + if not all([config.smtp_server, config.smtp_username, config.smtp_password, config.recipient_email]): + logger.error("邮件配置不完整,无法发送邮件") + return False + + # 生成报告数据 + report_date = today + yesterday = report_date - timedelta(days=1) + + # 获取昨日记录 + yesterday_reading = ReadingRecord.objects.filter(date=yesterday) + yesterday_insight = InsightRecord.objects.filter(date=yesterday) + + # 获取今日计划 + today_plan = TodayPlan.objects.filter(date=report_date) + + # 获取家庭事项统计 + from django.db.models import Count + family_task_stats = FamilyTask.objects.values('type').annotate(count=Count('id')) + + # 准备上下文 + context = { + 'today': report_date, + 'yesterday': yesterday, + 'yesterday_reading': yesterday_reading, + 'yesterday_insight': yesterday_insight, + 'today_plan': today_plan, + 'family_task_stats': family_task_stats, + } + + # 渲染HTML模板 + html_string = render(None, 'core/report_pdf.html', context).content.decode('utf-8') + + # 生成PDF报告 + pdf_file = f"report_{today_str}.pdf" + pdf_path = os.path.join(settings.REPORTS_ROOT, pdf_file) + + # 确保报告目录存在 + os.makedirs(settings.REPORTS_ROOT, exist_ok=True) + + # 生成PDF - 动态导入WeasyPrint + try: + from weasyprint import HTML + HTML(string=html_string).write_pdf(pdf_path) + logger.info(f"PDF报告生成成功: {pdf_path}") + except Exception as e: + logger.error(f"PDF报告生成失败: {str(e)}") + return False + + # 发送邮件 + subject = f"家庭日报 - {today_str}" + message = f"这是您的家庭日报,日期:{today_str}" + from_email = config.smtp_username + recipient_list = [config.recipient_email] + + try: + email = EmailMessage( + subject=subject, + body=message, + from_email=from_email, + to=recipient_list, + ) + + # 添加附件 + with open(pdf_path, 'rb') as f: + email.attach(pdf_file, f.read(), 'application/pdf') + + # 发送邮件 + email.send() + logger.info(f"邮件发送成功,收件人:{config.recipient_email}") + return True + except Exception as e: + logger.error(f"邮件发送失败:{str(e)}") + return False \ No newline at end of file diff --git a/core/templates/core/add_family_task.html b/core/templates/core/add_family_task.html new file mode 100644 index 0000000..148e9d5 --- /dev/null +++ b/core/templates/core/add_family_task.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} +

添加家庭事项

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+ + 取消 +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/add_insight.html b/core/templates/core/add_insight.html new file mode 100644 index 0000000..0ef2af3 --- /dev/null +++ b/core/templates/core/add_insight.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} +

添加感悟记录

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+ + 取消 +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/add_reading.html b/core/templates/core/add_reading.html new file mode 100644 index 0000000..d9b6f05 --- /dev/null +++ b/core/templates/core/add_reading.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} +

添加阅读记录

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+ + 取消 +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/add_today_plan.html b/core/templates/core/add_today_plan.html new file mode 100644 index 0000000..2e67c71 --- /dev/null +++ b/core/templates/core/add_today_plan.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} +

添加今日计划

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+ + 取消 +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/base.html b/core/templates/core/base.html new file mode 100644 index 0000000..7808514 --- /dev/null +++ b/core/templates/core/base.html @@ -0,0 +1,130 @@ + + + + + + 家庭日报系统 + + + + + + + + + + + +
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + + {% block content %} + {% endblock %} +
+ + +
+
+

© 2024 家庭日报系统 - 专注于家庭生活的轻量级日报系统

+
+
+ + + + + + + \ No newline at end of file diff --git a/core/templates/core/delete_family_task.html b/core/templates/core/delete_family_task.html new file mode 100644 index 0000000..94be971 --- /dev/null +++ b/core/templates/core/delete_family_task.html @@ -0,0 +1,18 @@ +{% extends 'core/base.html' %} + +{% block content %} +

删除家庭事项

+ +
+

您确定要删除这条家庭事项吗?

+

{{ task.content }} ({{ task.get_type_display }})

+

此操作不可恢复。

+
+ +
+ {% csrf_token %} + + + 取消 +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/delete_insight.html b/core/templates/core/delete_insight.html new file mode 100644 index 0000000..d4bc14b --- /dev/null +++ b/core/templates/core/delete_insight.html @@ -0,0 +1,18 @@ +{% extends 'core/base.html' %} + +{% block content %} +

删除感悟记录

+ +
+

您确定要删除这条感悟记录吗?

+

{{ insight.content|truncatechars:50 }}

+

此操作不可恢复。

+
+ +
+ {% csrf_token %} + + + 取消 +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/delete_reading.html b/core/templates/core/delete_reading.html new file mode 100644 index 0000000..f49ec0a --- /dev/null +++ b/core/templates/core/delete_reading.html @@ -0,0 +1,18 @@ +{% extends 'core/base.html' %} + +{% block content %} +

删除阅读记录

+ +
+

您确定要删除这条阅读记录吗?

+

{{ reading.title }} ({{ reading.get_type_display }})

+

此操作不可恢复。

+
+ +
+ {% csrf_token %} + + + 取消 +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/delete_today_plan.html b/core/templates/core/delete_today_plan.html new file mode 100644 index 0000000..06499e6 --- /dev/null +++ b/core/templates/core/delete_today_plan.html @@ -0,0 +1,18 @@ +{% extends 'core/base.html' %} + +{% block content %} +

删除今日计划

+ +
+

您确定要删除这条今日计划吗?

+

{{ plan.content }} ({{ plan.get_type_display }})

+

此操作不可恢复。

+
+ +
+ {% csrf_token %} + + + 取消 +
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/edit_family_task.html b/core/templates/core/edit_family_task.html new file mode 100644 index 0000000..a1cabd4 --- /dev/null +++ b/core/templates/core/edit_family_task.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} +

编辑家庭事项

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+ + 取消 +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/edit_insight.html b/core/templates/core/edit_insight.html new file mode 100644 index 0000000..691d06c --- /dev/null +++ b/core/templates/core/edit_insight.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} +

编辑感悟记录

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+ + 取消 +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/edit_reading.html b/core/templates/core/edit_reading.html new file mode 100644 index 0000000..d35f871 --- /dev/null +++ b/core/templates/core/edit_reading.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} +

编辑阅读记录

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+ + 取消 +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/edit_today_plan.html b/core/templates/core/edit_today_plan.html new file mode 100644 index 0000000..1d70214 --- /dev/null +++ b/core/templates/core/edit_today_plan.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} +

编辑今日计划

+ +
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+ + 取消 +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/family_tasks.html b/core/templates/core/family_tasks.html new file mode 100644 index 0000000..607750c --- /dev/null +++ b/core/templates/core/family_tasks.html @@ -0,0 +1,61 @@ +{% extends 'core/base.html' %} + +{% block content %} +

家庭事项

+ + + + +
+
+
家庭事项列表
+
+
+ {% if tasks %} + + + + + + + + + + + + + {% for task in tasks %} + + + + + + + + + {% endfor %} + +
类型内容优先级状态截止日期操作
{{ task.get_type_display }}{{ task.content }} + + {{ task.get_priority_display }} + + + + {{ task.get_status_display }} + + {{ task.deadline|default:"-" }} + + + + + + +
+ {% else %} +

还没有家庭事项,点击上方按钮添加

+ {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html new file mode 100644 index 0000000..8aaea55 --- /dev/null +++ b/core/templates/core/index.html @@ -0,0 +1,141 @@ +{% extends 'core/base.html' %} + +{% block content %} +
+ +
+
+
+
今日概览
+
+
+
日期:{{ today }}
+
+
今日计划
+ {% if today_plan %} +
    + {% for plan in today_plan %} +
  • +
    + {{ plan.content }} + - {{ plan.get_type_display }} - {{ plan.get_priority_display }} +
    + + {{ plan.get_status_display }} + +
  • + {% endfor %} +
+ {% else %} +

今天还没有计划,快去添加吧!

+ {% endif %} +
+
+
+
+ + +
+
+
+
待处理事项
+
+
+ {% if pending_family_tasks %} +
    + {% for task in pending_family_tasks %} +
  • +
    + {{ task.content }} + - {{ task.get_type_display }} +
    + {{ task.get_priority_display }} +
  • + {% endfor %} +
+ {% else %} +

没有待处理的家庭事项

+ {% endif %} + 查看所有家庭事项 +
+
+ + + +
+
+ + +
+
+
+
+
昨日记录 ({{ yesterday }})
+
+
+
+ +
+
阅读记录
+ {% if yesterday_reading %} +
    + {% for reading in yesterday_reading %} +
  • + {{ reading.title }} ({{ reading.get_type_display }}) + {% if reading.source %} + - {{ reading.source }} + {% endif %} + {% if reading.progress %} +
    进度:{{ reading.progress }}
    + {% endif %} + {% if reading.file %} + + {% endif %} +
  • + {% endfor %} +
+ {% else %} +

昨日没有阅读记录

+ {% endif %} +
+ + +
+
感悟记录
+ {% if yesterday_insight %} +
    + {% for insight in yesterday_insight %} +
  • + {{ insight.content }} + {% if insight.file %} + + {% endif %} +
  • + {% endfor %} +
+ {% else %} +

昨日没有感悟记录

+ {% endif %} +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/report.html b/core/templates/core/report.html new file mode 100644 index 0000000..15f82e8 --- /dev/null +++ b/core/templates/core/report.html @@ -0,0 +1,171 @@ +{% extends 'core/base.html' %} + +{% block content %} +

家庭日报报告

+ + + +
+
+
报告概览
+
+
+
+
+
报告日期:{{ today }}
+
昨日日期:{{ yesterday }}
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
昨日阅读记录
+
+
+ {% if yesterday_reading %} + + + + + + + + + + + {% for reading in yesterday_reading %} + + + + + + + {% endfor %} + +
类型标题来源进度
{{ reading.get_type_display }}{{ reading.title }}{{ reading.source|default:"-" }}{{ reading.progress|default:"-" }}
+ {% else %} +

昨日没有阅读记录

+ {% endif %} +
+
+
+ + +
+
+
+
昨日感悟记录
+
+
+ {% if yesterday_insight %} +
    + {% for insight in yesterday_insight %} +
  • {{ insight.content }}
  • + {% endfor %} +
+ {% else %} +

昨日没有感悟记录

+ {% endif %} +
+
+
+
+ +
+
+
今日计划
+
+
+ {% if today_plan %} + + + + + + + + + + + {% for plan in today_plan %} + + + + + + + {% endfor %} + +
类型内容优先级状态
{{ plan.get_type_display }}{{ plan.content }} + + {{ plan.get_priority_display }} + + + + {{ plan.get_status_display }} + +
+ {% else %} +

今天还没有计划

+ {% endif %} +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/report_pdf.html b/core/templates/core/report_pdf.html new file mode 100644 index 0000000..b54ca94 --- /dev/null +++ b/core/templates/core/report_pdf.html @@ -0,0 +1,203 @@ + + + + + 家庭日报 - {{ today }} + + + +

家庭日报

+ +
+

报告概览

+

报告日期:{{ today }}

+

昨日日期:{{ yesterday }}

+
+ + +
+

昨日阅读记录

+ {% if yesterday_reading %} + + + + + + + + + + + {% for reading in yesterday_reading %} + + + + + + + {% endfor %} + +
类型标题来源进度
{{ reading.get_type_display }}{{ reading.title }}{{ reading.source|default:"-" }}{{ reading.progress|default:"-" }}
+ {% else %} +
昨日没有阅读记录
+ {% endif %} +
+ + +
+

昨日感悟记录

+ {% if yesterday_insight %} +
    + {% for insight in yesterday_insight %} +
  • {{ insight.content }}
  • + {% endfor %} +
+ {% else %} +
昨日没有感悟记录
+ {% endif %} +
+ + +
+

今日计划

+ {% if today_plan %} + + + + + + + + + + + {% for plan in today_plan %} + + + + + + + {% endfor %} + +
类型内容优先级状态
{{ plan.get_type_display }}{{ plan.content }}{{ plan.get_priority_display }}{{ plan.get_status_display }}
+ {% else %} +
今天还没有计划
+ {% endif %} +
+ + +
+

家庭事项统计

+ {% if family_task_stats %} + + + + + + + + + {% for stat in family_task_stats %} + + + + + {% endfor %} + +
类型数量
{{ stat.type|yesno:"采购,家务" }}{{ stat.count }}
+ {% else %} +
没有家庭事项统计数据
+ {% endif %} +
+ + + + \ No newline at end of file diff --git a/core/templates/core/system_settings.html b/core/templates/core/system_settings.html new file mode 100644 index 0000000..5a884f4 --- /dev/null +++ b/core/templates/core/system_settings.html @@ -0,0 +1,95 @@ +{% extends 'core/base.html' %} + +{% block content %} +

系统配置

+ +
+
+
系统配置设置
+
+
+
+ {% csrf_token %} + +
+
+
邮件配置
+
+ + {{ form.smtp_server }} + {% if form.smtp_server.help_text %} +
{{ form.smtp_server.help_text }}
+ {% endif %} + {% for error in form.smtp_server.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ + {{ form.smtp_port }} + {% if form.smtp_port.help_text %} +
{{ form.smtp_port.help_text }}
+ {% endif %} + {% for error in form.smtp_port.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ + {{ form.smtp_username }} + {% if form.smtp_username.help_text %} +
{{ form.smtp_username.help_text }}
+ {% endif %} + {% for error in form.smtp_username.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ + {{ form.smtp_password }} + {% if form.smtp_password.help_text %} +
{{ form.smtp_password.help_text }}
+ {% endif %} + {% for error in form.smtp_password.errors %} +
{{ error }}
+ {% endfor %} +
+
+ +
+
发送配置
+ +
+ + {{ form.send_time }} + {% if form.send_time.help_text %} +
{{ form.send_time.help_text }}
+ {% endif %} + {% for error in form.send_time.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ + {{ form.recipient_email }} + {% if form.recipient_email.help_text %} +
{{ form.recipient_email.help_text }}
+ {% endif %} + {% for error in form.recipient_email.errors %} +
{{ error }}
+ {% endfor %} +
+
+
+ +
+ +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/today_plan.html b/core/templates/core/today_plan.html new file mode 100644 index 0000000..284420c --- /dev/null +++ b/core/templates/core/today_plan.html @@ -0,0 +1,66 @@ +{% extends 'core/base.html' %} + +{% block content %} +

今日计划

+ + + + +
+
+
今日计划列表
+
+
+ {% if plans %} + + + + + + + + + + + + {% for plan in plans %} + + + + + + + + {% endfor %} + +
类型内容优先级状态操作
{{ plan.get_type_display }}{{ plan.content }} + + {{ plan.get_priority_display }} + + + + {{ plan.get_status_display }} + + + + {% if plan.status == 'completed' %} + 已完成 + {% else %} + 标记完成 + {% endif %} + + + + + + + +
+ {% else %} +

今天还没有计划,快去添加吧!

+ {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/yesterday_records.html b/core/templates/core/yesterday_records.html new file mode 100644 index 0000000..e67d873 --- /dev/null +++ b/core/templates/core/yesterday_records.html @@ -0,0 +1,105 @@ +{% extends 'core/base.html' %} + +{% block content %} +

昨日记录 ({{ yesterday }})

+ + + + +
+ +
+
+
+
阅读记录
+
+
+ {% if reading_records %} + + + + + + + + + + + + {% for reading in reading_records %} + + + + + + + + {% endfor %} + +
类型标题来源进度操作
{{ reading.get_type_display }}{{ reading.title }}{{ reading.source|default:"-" }}{{ reading.progress|default:"-" }} + {% if reading.file %} + + + + {% endif %} + + + + + + +
+ {% else %} +

昨日没有阅读记录,点击上方按钮添加

+ {% endif %} +
+
+
+ + +
+
+
+
感悟记录
+
+
+ {% if insight_records %} + + + + + + + + + {% for insight in insight_records %} + + + + + {% endfor %} + +
内容操作
{{ insight.content }} + {% if insight.file %} + + + + {% endif %} + + + + + + +
+ {% else %} +

昨日没有感悟记录,点击上方按钮添加

+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/tests.py b/core/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..a5939a4 --- /dev/null +++ b/core/urls.py @@ -0,0 +1,40 @@ +from django.urls import path +from . import views + +urlpatterns = [ + # 首页 + path('', views.index, name='index'), + + # 昨日记录 + path('yesterday/', views.yesterday_records, name='yesterday_records'), + path('yesterday/reading/add/', views.add_reading, name='add_reading'), + path('yesterday/reading//edit/', views.edit_reading, name='edit_reading'), + path('yesterday/reading//delete/', views.delete_reading, name='delete_reading'), + path('yesterday/insight/add/', views.add_insight, name='add_insight'), + path('yesterday/insight//edit/', views.edit_insight, name='edit_insight'), + path('yesterday/insight//delete/', views.delete_insight, name='delete_insight'), + + # 家庭事项 + path('family-tasks/', views.family_tasks, name='family_tasks'), + path('family-tasks/add/', views.add_family_task, name='add_family_task'), + path('family-tasks//edit/', views.edit_family_task, name='edit_family_task'), + path('family-tasks//delete/', views.delete_family_task, name='delete_family_task'), + + # 今日计划 + path('today-plan/', views.today_plan, name='today_plan'), + path('today-plan/add/', views.add_today_plan, name='add_today_plan'), + path('today-plan//edit/', views.edit_today_plan, name='edit_today_plan'), + path('today-plan//delete/', views.delete_today_plan, name='delete_today_plan'), + path('today-plan//toggle/', views.toggle_today_plan, name='toggle_today_plan'), + + # 报告生成 + path('report/', views.generate_report, name='generate_report'), + path('report//', views.view_report, name='view_report'), + path('report//pdf/', views.generate_pdf_report, name='generate_pdf_report'), + + # 系统配置 + path('settings/', views.system_settings, name='system_settings'), + + # 手动发送邮件 + path('send-email/', views.send_email, name='send_email'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py new file mode 100644 index 0000000..05fa366 --- /dev/null +++ b/core/views.py @@ -0,0 +1,503 @@ +from django.shortcuts import render, redirect, get_object_or_404 +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.db.models import Count +from django.core.mail import send_mail, EmailMessage +from django.conf import settings +from datetime import timedelta, datetime +import os +from loguru import logger + +# WeasyPrint可用性标记,初始为None +WEASYPRINT_AVAILABLE = None + +# 获取WeasyPrint可用性 +def is_weasyprint_available(): + """检查WeasyPrint是否可用""" + global WEASYPRINT_AVAILABLE + if WEASYPRINT_AVAILABLE is None: + try: + from weasyprint import HTML + WEASYPRINT_AVAILABLE = True + except ImportError: + logger.warning("WeasyPrint库无法导入,PDF功能将不可用") + WEASYPRINT_AVAILABLE = False + return WEASYPRINT_AVAILABLE + +from .models import ( + ReadingRecord, + InsightRecord, + FamilyTask, + TodayPlan, + SystemConfig +) +from .forms import ( + ReadingRecordForm, + InsightRecordForm, + FamilyTaskForm, + TodayPlanForm, + SystemConfigForm +) + +# 首页视图 +def index(request): + """首页""" + logger.info("用户访问首页") + today = timezone.now().date() + yesterday = today - timedelta(days=1) + + # 获取昨日记录 + yesterday_reading = ReadingRecord.objects.filter(date=yesterday) + yesterday_insight = InsightRecord.objects.filter(date=yesterday) + + # 获取今日计划 + today_plan = TodayPlan.objects.filter(date=today) + + # 获取待处理的家庭事项 + pending_family_tasks = FamilyTask.objects.filter(status='pending') + + context = { + 'yesterday': yesterday, + 'today': today, + 'yesterday_reading': yesterday_reading, + 'yesterday_insight': yesterday_insight, + 'today_plan': today_plan, + 'pending_family_tasks': pending_family_tasks, + } + + return render(request, 'core/index.html', context) + +# 昨日记录视图 +def yesterday_records(request): + """昨日记录""" + logger.info("用户访问昨日记录页面") + today = timezone.now().date() + yesterday = today - timedelta(days=1) + + # 获取昨日阅读记录 + reading_records = ReadingRecord.objects.filter(date=yesterday) + + # 获取昨日感悟记录 + insight_records = InsightRecord.objects.filter(date=yesterday) + + context = { + 'yesterday': yesterday, + 'reading_records': reading_records, + 'insight_records': insight_records, + } + + return render(request, 'core/yesterday_records.html', context) + +# 添加阅读记录 +def add_reading(request): + """添加阅读记录""" + if request.method == 'POST': + form = ReadingRecordForm(request.POST, request.FILES) + if form.is_valid(): + form.save() + logger.info(f"添加阅读记录: {form.cleaned_data['title']}") + return redirect('yesterday_records') + else: + form = ReadingRecordForm() + + context = {'form': form} + return render(request, 'core/add_reading.html', context) + +# 编辑阅读记录 +def edit_reading(request, pk): + """编辑阅读记录""" + reading = get_object_or_404(ReadingRecord, pk=pk) + if request.method == 'POST': + form = ReadingRecordForm(request.POST, request.FILES, instance=reading) + if form.is_valid(): + form.save() + logger.info(f"编辑阅读记录: {form.cleaned_data['title']}") + return redirect('yesterday_records') + else: + form = ReadingRecordForm(instance=reading) + + context = {'form': form, 'reading': reading} + return render(request, 'core/edit_reading.html', context) + +# 删除阅读记录 +def delete_reading(request, pk): + """删除阅读记录""" + reading = get_object_or_404(ReadingRecord, pk=pk) + if request.method == 'POST': + reading.delete() + logger.info(f"删除阅读记录: {reading.title}") + return redirect('yesterday_records') + + context = {'reading': reading} + return render(request, 'core/delete_reading.html', context) + +# 添加感悟记录 +def add_insight(request): + """添加感悟记录""" + if request.method == 'POST': + form = InsightRecordForm(request.POST, request.FILES) + if form.is_valid(): + form.save() + logger.info("添加感悟记录") + return redirect('yesterday_records') + else: + form = InsightRecordForm() + + context = {'form': form} + return render(request, 'core/add_insight.html', context) + +# 编辑感悟记录 +def edit_insight(request, pk): + """编辑感悟记录""" + insight = get_object_or_404(InsightRecord, pk=pk) + if request.method == 'POST': + form = InsightRecordForm(request.POST, request.FILES, instance=insight) + if form.is_valid(): + form.save() + logger.info("编辑感悟记录") + return redirect('yesterday_records') + else: + form = InsightRecordForm(instance=insight) + + context = {'form': form, 'insight': insight} + return render(request, 'core/edit_insight.html', context) + +# 删除感悟记录 +def delete_insight(request, pk): + """删除感悟记录""" + insight = get_object_or_404(InsightRecord, pk=pk) + if request.method == 'POST': + insight.delete() + logger.info("删除感悟记录") + return redirect('yesterday_records') + + context = {'insight': insight} + return render(request, 'core/delete_insight.html', context) + +# 家庭事项视图 +def family_tasks(request): + """家庭事项""" + logger.info("用户访问家庭事项页面") + tasks = FamilyTask.objects.all() + + context = { + 'tasks': tasks, + } + + return render(request, 'core/family_tasks.html', context) + +# 添加家庭事项 +def add_family_task(request): + """添加家庭事项""" + if request.method == 'POST': + form = FamilyTaskForm(request.POST) + if form.is_valid(): + form.save() + logger.info(f"添加家庭事项: {form.cleaned_data['content'][:20]}...") + return redirect('family_tasks') + else: + form = FamilyTaskForm() + + context = {'form': form} + return render(request, 'core/add_family_task.html', context) + +# 编辑家庭事项 +def edit_family_task(request, pk): + """编辑家庭事项""" + task = get_object_or_404(FamilyTask, pk=pk) + if request.method == 'POST': + form = FamilyTaskForm(request.POST, instance=task) + if form.is_valid(): + form.save() + logger.info(f"编辑家庭事项: {form.cleaned_data['content'][:20]}...") + return redirect('family_tasks') + else: + form = FamilyTaskForm(instance=task) + + context = {'form': form, 'task': task} + return render(request, 'core/edit_family_task.html', context) + +# 删除家庭事项 +def delete_family_task(request, pk): + """删除家庭事项""" + task = get_object_or_404(FamilyTask, pk=pk) + if request.method == 'POST': + task.delete() + logger.info(f"删除家庭事项: {task.content[:20]}...") + return redirect('family_tasks') + + context = {'task': task} + return render(request, 'core/delete_family_task.html', context) + +# 今日计划视图 +def today_plan(request): + """今日计划""" + logger.info("用户访问今日计划页面") + today = timezone.now().date() + + # 获取今日计划 + plans = TodayPlan.objects.filter(date=today) + + context = { + 'today': today, + 'plans': plans, + } + + return render(request, 'core/today_plan.html', context) + +# 添加今日计划 +def add_today_plan(request): + """添加今日计划""" + if request.method == 'POST': + form = TodayPlanForm(request.POST) + if form.is_valid(): + form.save() + logger.info(f"添加今日计划: {form.cleaned_data['content'][:20]}...") + return redirect('today_plan') + else: + form = TodayPlanForm() + + context = {'form': form} + return render(request, 'core/add_today_plan.html', context) + +# 编辑今日计划 +def edit_today_plan(request, pk): + """编辑今日计划""" + plan = get_object_or_404(TodayPlan, pk=pk) + if request.method == 'POST': + form = TodayPlanForm(request.POST, instance=plan) + if form.is_valid(): + form.save() + logger.info(f"编辑今日计划: {form.cleaned_data['content'][:20]}...") + return redirect('today_plan') + else: + form = TodayPlanForm(instance=plan) + + context = {'form': form, 'plan': plan} + return render(request, 'core/edit_today_plan.html', context) + +# 删除今日计划 +def delete_today_plan(request, pk): + """删除今日计划""" + plan = get_object_or_404(TodayPlan, pk=pk) + if request.method == 'POST': + plan.delete() + logger.info(f"删除今日计划: {plan.content[:20]}...") + return redirect('today_plan') + + context = {'plan': plan} + return render(request, 'core/delete_today_plan.html', context) + +# 切换今日计划状态 +def toggle_today_plan(request, pk): + """切换今日计划状态""" + plan = get_object_or_404(TodayPlan, pk=pk) + plan.status = 'completed' if plan.status == 'pending' else 'pending' + plan.save() + logger.info(f"切换今日计划状态: {plan.content[:20]}... -> {plan.get_status_display()}") + + if request.headers.get('x-requested-with') == 'XMLHttpRequest': + return JsonResponse({'status': plan.status, 'status_text': plan.get_status_display()}) + + return redirect('today_plan') + +# 生成报告 +def generate_report(request): + """生成报告""" + logger.info("用户访问报告生成页面") + today = timezone.now().date() + yesterday = today - timedelta(days=1) + + # 获取昨日记录 + yesterday_reading = ReadingRecord.objects.filter(date=yesterday) + yesterday_insight = InsightRecord.objects.filter(date=yesterday) + + # 获取今日计划 + today_plan = TodayPlan.objects.filter(date=today) + + # 获取家庭事项统计 + family_task_stats = FamilyTask.objects.values('type').annotate(count=Count('id')) + + context = { + 'today': today, + 'yesterday': yesterday, + 'yesterday_reading': yesterday_reading, + 'yesterday_insight': yesterday_insight, + 'today_plan': today_plan, + 'family_task_stats': family_task_stats, + } + + return render(request, 'core/report.html', context) + +# 查看报告 +def view_report(request, date): + """查看指定日期的报告""" + logger.info(f"用户查看报告: {date}") + report_date = datetime.strptime(date, '%Y-%m-%d').date() + yesterday = report_date - timedelta(days=1) + + # 获取指定日期的记录 + yesterday_reading = ReadingRecord.objects.filter(date=yesterday) + yesterday_insight = InsightRecord.objects.filter(date=yesterday) + today_plan = TodayPlan.objects.filter(date=report_date) + + # 获取家庭事项统计 + family_task_stats = FamilyTask.objects.values('type').annotate(count=Count('id')) + + context = { + 'today': report_date, + 'yesterday': yesterday, + 'yesterday_reading': yesterday_reading, + 'yesterday_insight': yesterday_insight, + 'today_plan': today_plan, + 'family_task_stats': family_task_stats, + } + + return render(request, 'core/report.html', context) + +# 生成PDF报告 +def generate_pdf_report(request, date): + """生成PDF报告""" + if not is_weasyprint_available(): + logger.error("WeasyPrint库不可用,无法生成PDF报告") + return HttpResponse("PDF功能不可用,请检查WeasyPrint库是否正确安装", status=500) + + logger.info(f"用户生成PDF报告: {date}") + report_date = datetime.strptime(date, '%Y-%m-%d').date() + yesterday = report_date - timedelta(days=1) + + # 获取指定日期的记录 + yesterday_reading = ReadingRecord.objects.filter(date=yesterday) + yesterday_insight = InsightRecord.objects.filter(date=yesterday) + today_plan = TodayPlan.objects.filter(date=report_date) + + # 获取家庭事项统计 + family_task_stats = FamilyTask.objects.values('type').annotate(count=Count('id')) + + context = { + 'today': report_date, + 'yesterday': yesterday, + 'yesterday_reading': yesterday_reading, + 'yesterday_insight': yesterday_insight, + 'today_plan': today_plan, + 'family_task_stats': family_task_stats, + } + + # 渲染HTML模板 + html_string = render(request, 'core/report_pdf.html', context).content.decode('utf-8') + + # 生成PDF + pdf_file = f"report_{date}.pdf" + pdf_path = os.path.join(settings.REPORTS_ROOT, pdf_file) + + # 确保报告目录存在 + os.makedirs(settings.REPORTS_ROOT, exist_ok=True) + + # 动态导入WeasyPrint + from weasyprint import HTML + HTML(string=html_string).write_pdf(pdf_path) + + logger.info(f"PDF报告生成成功: {pdf_path}") + + # 返回PDF文件 + with open(pdf_path, 'rb') as f: + response = HttpResponse(f.read(), content_type='application/pdf') + response['Content-Disposition'] = f'attachment; filename="{pdf_file}"' + return response + +# 系统配置 +def system_settings(request): + """系统配置""" + logger.info("用户访问系统配置页面") + config = SystemConfig.get_config() + + if request.method == 'POST': + form = SystemConfigForm(request.POST, instance=config) + if form.is_valid(): + form.save() + logger.info("系统配置更新成功") + return redirect('system_settings') + else: + form = SystemConfigForm(instance=config) + + context = {'form': form} + return render(request, 'core/system_settings.html', context) + +# 发送邮件 +def send_email_view(request): + """手动发送邮件""" + if not is_weasyprint_available(): + logger.error("WeasyPrint库不可用,无法生成PDF报告") + return HttpResponse("PDF功能不可用,无法发送邮件", status=500) + + logger.info("用户手动发送邮件") + today = timezone.now().date() + today_str = today.strftime('%Y-%m-%d') + + # 获取系统配置 + config = SystemConfig.get_config() + + if not all([config.smtp_server, config.smtp_username, config.smtp_password, config.recipient_email]): + logger.error("邮件配置不完整") + return redirect('system_settings') + + # 生成PDF报告 + report_date = today + yesterday = report_date - timedelta(days=1) + + yesterday_reading = ReadingRecord.objects.filter(date=yesterday) + yesterday_insight = InsightRecord.objects.filter(date=yesterday) + today_plan = TodayPlan.objects.filter(date=report_date) + family_task_stats = FamilyTask.objects.values('type').annotate(count=Count('id')) + + context = { + 'today': report_date, + 'yesterday': yesterday, + 'yesterday_reading': yesterday_reading, + 'yesterday_insight': yesterday_insight, + 'today_plan': today_plan, + 'family_task_stats': family_task_stats, + } + + # 渲染HTML模板 + html_string = render(request, 'core/report_pdf.html', context).content.decode('utf-8') + + # 生成PDF + pdf_file = f"report_{today_str}.pdf" + pdf_path = os.path.join(settings.REPORTS_ROOT, pdf_file) + os.makedirs(settings.REPORTS_ROOT, exist_ok=True) + + # 动态导入WeasyPrint + from weasyprint import HTML + HTML(string=html_string).write_pdf(pdf_path) + + # 发送邮件 + subject = f"家庭日报 - {today_str}" + message = f"这是您的家庭日报,日期:{today_str}" + from_email = config.smtp_username + recipient_list = [config.recipient_email] + + email = EmailMessage( + subject=subject, + body=message, + from_email=from_email, + to=recipient_list, + ) + + # 添加附件 + with open(pdf_path, 'rb') as f: + email.attach(pdf_file, f.read(), 'application/pdf') + + # 发送邮件 + try: + email.send() + logger.info(f"邮件发送成功,收件人:{config.recipient_email}") + return redirect('index') + except Exception as e: + logger.error(f"邮件发送失败:{str(e)}") + return redirect('system_settings') + +# 别名,保持URL配置一致 +def send_email(request): + """发送邮件别名""" + return send_email_view(request) diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..d2cd0d2c5dd356cf645904e64ea61a4bba798436 GIT binary patch literal 212992 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 P8B`x|Nj9<tj*@@H`%-o-j&m|K}0YTzX zC6niil3auU%J(`wgH1{AVQ@gG-5F}{1YiSJCaN$;+c_|8TbJ-N$3g0Vswz3(?q0kixNLC zad9uV#tftS4Rb16vI|RlA;?9WEn{znkeTr zk}QnK-!$BUm@OAFIgezjfXFYdA=;`2E&(1#s%^vq(UZ-)M7@>&WkzSxWJcn zRgkotx*#I6AkARgbs-PVVdl2#b`BL)p)8BT=qh#)LfQfrM+`+#KY?oxT-pQSBRim& z75B1x7X<1cPzy$Dx5swC_^};0e5;SY+pkm^62tC-U>yW&-F=&(8iDsZto82L4!HY^ bVpl@Tp;f&as!?6XG)>)VGJp=4$gJxxIBEt8 literal 0 HcmV?d00001 diff --git a/diary_family/__pycache__/settings.cpython-313.pyc b/diary_family/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f1c533a4ccc53467cb95b2351f949f0bee67ad6 GIT binary patch literal 3477 zcmb6b&uQ%}lb8rO?$D$DRZe#}0lDbdj~AvHfBX8GFo| zF_1ZVdjYA3a%sh}hi#?qA5d+#=iX!WfHhL3NM$SS%|LtaH)9*T0jYFkzxTf1Ki>C! zGp|{eq2T%B=3S*cMN$8>fzjh19$tS-Q`B!ML=g(n$hAe^bP*Rvm0ahj(!?6j?57YI z?Bna&5GXh{#wo!69vtza3F1R;;uk0q5GdriMH2>gFYH0&BOwG7WcsJ*R9vB#us#lK`D@pgHsrIR*dI=qxz{G9Mw1oQ0gsZ*hc!{oL04&2!`& z_{mio_|6q>zDtF?E})+}ahyks=mJWhrK=u}LKnf~v*7W^ z5OV-*EZaN+U1AaKv9 zrBrlbX_TEM%H>+R>D};L>t8J|RKeh1PAuU|^2HEFfFc70#Jes6!Nd|z3){E2c! zSMkCFqhVYqCj4e>poXt#O@k<9-bt!?;yAVq7Ec{R+9J4V7zZt^o3qD=I@Wba^Sguw z5ab$2%pI37;9@HFb?BD60@r0l2OLrbs~D=HjAbL?Gna>Q4W(LD@k5zl-pPPs9Pr3a z1st~D(9Fxn%?usm$K=rfHzt*M*9g==Lu>LWXZs(lI@#k>u-sPg!!gz?$9$0?t>b&d=;15 zHFN0$I0?QF4P}@YhrDlU*iP$vG~4MmFCAlklgjr9jA#kB##0a(GU- zEU%c`%;ZGNol9kl&Ype*U4Fww0#RXg9LBSu^Ty<$v*^@`Hxi8T$BE!SxSSkZU(EQ7Z3h@ZjchTel{5 ziz(9w?*V8W0j|NRL>@BP(5hI~`E{iRjiqfUSgo2qp@HF|1k2;>Wt8Exq`n9xd$Yoi_ zXoSka^!BP+t%ix^hFu5Pj~~G+L(=w5ACwCZSktr0RpkTId&S-$K2vE~?o4hY4|V7# znjNUtv;qX%WHy_>Bc`Prc~nGJIFmy~Kmh{!1zF5?CY{cTcajCs3KjDCqO@Dcrt-NBD^L`- zce2T%h^)vRw3)H_vzb)VZY?X2P8O54S!BhNyTu#QP7lt6P{+Q^96 zv=!JE)0w10W(G&wSu0o&ck+cIatNUj;E<+LVpc4CA*~nkTVlasm>!}EBC^$w=Evj^ zZTsiAHxd zyT>p7`seSz{!8imuYdEyH-9KOGi`bBJvIP_A7`b}2N2&nu>7Avt3l0wPDHR_e}3zK zgS9_F)AUOhL%aVIr|6R}y;Laj)c@0j-{JFUt2K@n@fPBa1IR6A|F?I6QMyVDd0>w?E+mIllw! zj03mEfx-ZoW>32gsd)c9H5Ko$N1>@s;3zoR@gITH-hPM*&w`^2HM4d|MIjV+rW5F~ z)6aZe_N)`z|$*r!H^?NXOyK(+-R_Fwt%zqVna=S~#06%@^*`sc3@$kfj LE)^juxX%9qCiH2p literal 0 HcmV?d00001 diff --git a/diary_family/__pycache__/urls.cpython-313.pyc b/diary_family/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb6a42e619f99d3312bd0daeb12db792e7c90fa1 GIT binary patch literal 1533 zcmb7ETW=dh6dtd4eOcQ?N)c2MLen5*FLHJ%QV>{es?sDhHjrRDLV}gt&3bHaI$NQPfJcq(-VPQ3(q<-ALDED%TaNgloBxsb^_6 zl$A!Vo~L~Hqam>v!HkfaieU^mAF-NYbbXuY-EGV3`$;<6 zcMakhi~2=_`yiBB6i!Bd3?#QRShH;Pj zE_Qc1)@T$fBbuTA8?nErKol@lA%sG)gG#wvjq4itE~#SAa>?bgkjt8SZD=vJumy#6 zr%?Zmh0h0s3L<*QIH7YLu>KDl;jde)BS0$JKNzif_Hf*FygoD`%qL0{+nn@gEo=-1 zDzH7mmsB*|i(6TX+7)ewE*hSYjD4$NQ~opCt)bsHpn*xIvQUJ$Wld9`ileHf0>$dO zj;Be1EIJ-!V{fqJ7~vd>VIXH1hm&OA%7orVfNte&IAkFjm`iYw#xC)Ahx$JMK${J+ zoet5CXecHNVfq1`051CDLzu4B z%uPlpGjBT9wqq{G6*BW^|H0A2hrfTjd-U^7$(>cggj;;~`n1;)N1#7yAmP<8&&OdM3@Ji+e^OY0N(&TUNOjoXdCjrwFx-ya+Rym@aP-}k;ZZ$=9XI*^v%cuC$U0Q^dm zspTfY=s*UqfCU(^6ieM!m?BSYTiw!_27#^Y&QBpX=WgL9o<>&Ax>ZrLOyaa9P??n2 zoUfMWj=$?KtXdtqG-?qawkU%hL9FkzP)LG4j2Z18^a8Rf&d)^ z7$Oc445`-*@N$5AIDkFG$hZ`t5PPuML~$Gt7YRb6W>fNQ$W|d`7?MbK4aWsYBS}s; zY>@yzfE;7JEkw-MOw*$-H@pstd^*+S($K__Sus}4Ha!&7^l%)|euyJs9&(?U`gJ;M zqpT$Nd@P*w&Y6*sELOK3H)=bMwZ6AkZ`3U3*-mx;>H2YL_6ZD*g&>j7%lRblI8uk> zBr4^}olK44${~!Dn`yFJlE+bxFd8Mg1bVoIx`9XvGi~32Q7Y@-bLpO}+4~$bZXXfU zA$FCz-Bdoe2Olm5mzTd^pAJ5~AD(}-hv#S47jNycu;pwwSu$CaoeD7Gelq_sTf-w( zl>G940lp&LA5Bq|ziM8|jqU