commit eaee38d658d08184da462163879c51238fcaeeee Author: xiaji Date: Sun Jan 4 19:17:33 2026 +0800 一个家庭日报系统 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 0000000..41b87f7 Binary files /dev/null and b/core/__pycache__/__init__.cpython-313.pyc differ diff --git a/core/__pycache__/admin.cpython-313.pyc b/core/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..e83834b Binary files /dev/null and b/core/__pycache__/admin.cpython-313.pyc differ diff --git a/core/__pycache__/apps.cpython-313.pyc b/core/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..64e1f81 Binary files /dev/null and b/core/__pycache__/apps.cpython-313.pyc differ diff --git a/core/__pycache__/forms.cpython-313.pyc b/core/__pycache__/forms.cpython-313.pyc new file mode 100644 index 0000000..1058f08 Binary files /dev/null and b/core/__pycache__/forms.cpython-313.pyc differ diff --git a/core/__pycache__/models.cpython-313.pyc b/core/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..81e73a8 Binary files /dev/null and b/core/__pycache__/models.cpython-313.pyc differ diff --git a/core/__pycache__/urls.cpython-313.pyc b/core/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000..8c2bda5 Binary files /dev/null and b/core/__pycache__/urls.cpython-313.pyc differ diff --git a/core/__pycache__/views.cpython-313.pyc b/core/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000..868d009 Binary files /dev/null and b/core/__pycache__/views.cpython-313.pyc differ 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 0000000..70864a0 Binary files /dev/null and b/core/management/commands/__pycache__/create_superuser.cpython-313.pyc differ diff --git a/core/management/commands/create_superuser.py b/core/management/commands/create_superuser.py new file mode 100644 index 0000000..297314b --- /dev/null +++ b/core/management/commands/create_superuser.py @@ -0,0 +1,18 @@ +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand + +class Command(BaseCommand): + """非交互式创建超级用户""" + help = 'Create a superuser non-interactively' + + def handle(self, *args, **options): + User = get_user_model() + if not User.objects.filter(username='admin').exists(): + User.objects.create_superuser( + username='admin', + email='admin@example.com', + password='admin123' + ) + self.stdout.write(self.style.SUCCESS('Superuser created successfully!')) + else: + self.stdout.write(self.style.WARNING('Superuser already exists.')) \ No newline at end of file diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..c7ac3cc --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,104 @@ +# Generated by Django 5.1.4 on 2026-01-04 11:06 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='FamilyTask', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(choices=[('purchase', '采购'), ('housework', '家务')], max_length=20, verbose_name='类型')), + ('content', models.TextField(verbose_name='内容')), + ('priority', models.CharField(choices=[('low', '低'), ('medium', '中'), ('high', '高')], default='medium', max_length=10, verbose_name='优先级')), + ('status', models.CharField(choices=[('pending', '待处理'), ('completed', '已完成')], default='pending', max_length=20, 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='更新时间')), + ], + options={ + 'verbose_name': '家庭事项', + 'verbose_name_plural': '家庭事项', + 'ordering': ['-priority', '-deadline', '-created_at'], + }, + ), + migrations.CreateModel( + name='InsightRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField(default=django.utils.timezone.now, verbose_name='日期')), + ('content', models.TextField(verbose_name='内容')), + ('file', models.FileField(blank=True, null=True, upload_to='insight_files/', verbose_name='上传文件')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ], + options={ + 'verbose_name': '感悟记录', + 'verbose_name_plural': '感悟记录', + 'ordering': ['-date', '-created_at'], + }, + ), + migrations.CreateModel( + name='ReadingRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField(default=django.utils.timezone.now, verbose_name='日期')), + ('type', models.CharField(choices=[('book', '书籍'), ('article', '文章'), ('video', '视频')], max_length=20, verbose_name='类型')), + ('title', models.CharField(max_length=200, verbose_name='标题')), + ('source', models.CharField(blank=True, max_length=200, null=True, verbose_name='来源')), + ('progress', models.CharField(blank=True, max_length=100, null=True, verbose_name='进度')), + ('file', models.FileField(blank=True, null=True, upload_to='reading_files/', verbose_name='上传文件')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ], + options={ + 'verbose_name': '阅读记录', + 'verbose_name_plural': '阅读记录', + 'ordering': ['-date', '-created_at'], + }, + ), + migrations.CreateModel( + name='SystemConfig', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('smtp_server', models.CharField(blank=True, max_length=100, null=True, verbose_name='SMTP服务器')), + ('smtp_port', models.IntegerField(default=587, verbose_name='SMTP端口')), + ('smtp_username', models.CharField(blank=True, max_length=100, null=True, verbose_name='SMTP用户名')), + ('smtp_password', models.CharField(blank=True, max_length=100, null=True, verbose_name='SMTP密码')), + ('send_time', models.TimeField(default='08:00', verbose_name='每日发送时间')), + ('recipient_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='收件人邮箱')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ], + options={ + 'verbose_name': '系统配置', + 'verbose_name_plural': '系统配置', + }, + ), + migrations.CreateModel( + name='TodayPlan', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField(default=django.utils.timezone.now, verbose_name='日期')), + ('content', models.TextField(verbose_name='内容')), + ('priority', models.CharField(choices=[('low', '低'), ('medium', '中'), ('high', '高')], default='medium', max_length=10, verbose_name='优先级')), + ('type', models.CharField(choices=[('reading', '阅读'), ('insight', '感悟'), ('family', '家庭事项'), ('other', '其他')], default='other', max_length=20, verbose_name='类型')), + ('status', models.CharField(choices=[('pending', '待处理'), ('completed', '已完成')], default='pending', max_length=20, verbose_name='状态')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')), + ], + options={ + 'verbose_name': '今日计划', + 'verbose_name_plural': '今日计划', + 'ordering': ['-priority', '-created_at'], + }, + ), + ] diff --git a/core/migrations/__init__.py b/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/migrations/__pycache__/0001_initial.cpython-313.pyc b/core/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..f09bb60 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/core/migrations/__pycache__/__init__.cpython-313.pyc b/core/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..3bed523 Binary files /dev/null and b/core/migrations/__pycache__/__init__.cpython-313.pyc differ 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 %} +
+ + + + + + + + + + \ 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 %} +

家庭日报报告

+ +
+ + 生成PDF报告 + + + 发送邮件 + +
+ +
+
+
报告概览
+
+
+
+
+
报告日期:{{ 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 %} + + {% 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 0000000..d2cd0d2 Binary files /dev/null and b/db.sqlite3 differ diff --git a/diary_family/__init__.py b/diary_family/__init__.py new file mode 100644 index 0000000..007b5dc --- /dev/null +++ b/diary_family/__init__.py @@ -0,0 +1,4 @@ +# 确保Celery应用在Django启动时被加载 +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/diary_family/__pycache__/__init__.cpython-313.pyc b/diary_family/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..ebe61df Binary files /dev/null and b/diary_family/__pycache__/__init__.cpython-313.pyc differ diff --git a/diary_family/__pycache__/celery.cpython-313.pyc b/diary_family/__pycache__/celery.cpython-313.pyc new file mode 100644 index 0000000..e7196fa Binary files /dev/null and b/diary_family/__pycache__/celery.cpython-313.pyc differ diff --git a/diary_family/__pycache__/settings.cpython-313.pyc b/diary_family/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000..8f1c533 Binary files /dev/null and b/diary_family/__pycache__/settings.cpython-313.pyc differ diff --git a/diary_family/__pycache__/urls.cpython-313.pyc b/diary_family/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000..cb6a42e Binary files /dev/null and b/diary_family/__pycache__/urls.cpython-313.pyc differ diff --git a/diary_family/__pycache__/wsgi.cpython-313.pyc b/diary_family/__pycache__/wsgi.cpython-313.pyc new file mode 100644 index 0000000..6347f45 Binary files /dev/null and b/diary_family/__pycache__/wsgi.cpython-313.pyc differ diff --git a/diary_family/asgi.py b/diary_family/asgi.py new file mode 100644 index 0000000..ca50003 --- /dev/null +++ b/diary_family/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for diary_family project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_family.settings') + +application = get_asgi_application() diff --git a/diary_family/celery.py b/diary_family/celery.py new file mode 100644 index 0000000..a5d54c7 --- /dev/null +++ b/diary_family/celery.py @@ -0,0 +1,20 @@ +import os +from celery import Celery +from django.conf import settings + +# 设置默认的Django设置模块 +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_family.settings') + +# 创建Celery应用实例 +app = Celery('diary_family') + +# 从Django设置中读取配置 +app.config_from_object('django.conf:settings', namespace='CELERY') + +# 自动发现任务 +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) + +@app.task(bind=True) +def debug_task(self): + """调试任务""" + print(f'Request: {self.request!r}') \ No newline at end of file diff --git a/diary_family/settings.py b/diary_family/settings.py new file mode 100644 index 0000000..77e5d05 --- /dev/null +++ b/diary_family/settings.py @@ -0,0 +1,171 @@ +""" +Django settings for diary_family project. + +Generated by 'django-admin startproject' using Django 5.1.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-x^5b(t-qcpahyz+l^@3)lg_1d5@ks@jk*bqi042i7sle#vtmt(' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'core', + 'django_celery_beat', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'diary_family.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'diary_family.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Media files configuration +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR / 'media' + +# Reports files configuration +REPORTS_URL = '/reports/' +REPORTS_ROOT = BASE_DIR / 'reports' + +# Celery configuration +CELERY_BROKER_URL = 'redis://localhost:6379/0' +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' +CELERY_TIMEZONE = 'Asia/Shanghai' +CELERY_ENABLE_UTC = True + +# Celery Beat configuration +CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' + +# Logging configuration +import os +from loguru import logger + +LOG_DIR = BASE_DIR / 'logs' +LOG_DIR.mkdir(exist_ok=True) + +# Configure loguru +logger.add( + LOG_DIR / 'app.log', + rotation='1 day', + retention='7 days', + compression='zip', + level='INFO' +) + +# Internationalization +LANGUAGE_CODE = 'zh-Hans' +TIME_ZONE = 'Asia/Shanghai' + +# Allow all hosts for development +ALLOWED_HOSTS = ['*'] + +# Template directories +TEMPLATES[0]['DIRS'] = [BASE_DIR / 'templates'] + +# Static files configuration +STATICFILES_DIRS = [BASE_DIR / 'static'] diff --git a/diary_family/urls.py b/diary_family/urls.py new file mode 100644 index 0000000..bcb769a --- /dev/null +++ b/diary_family/urls.py @@ -0,0 +1,30 @@ +""" +URL configuration for diary_family project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('core.urls')), +] + +# Static files for development +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static(settings.REPORTS_URL, document_root=settings.REPORTS_ROOT) diff --git a/diary_family/wsgi.py b/diary_family/wsgi.py new file mode 100644 index 0000000..d855767 --- /dev/null +++ b/diary_family/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for diary_family project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_family.settings') + +application = get_wsgi_application() diff --git a/logs/app.log b/logs/app.log new file mode 100644 index 0000000..3800ad8 --- /dev/null +++ b/logs/app.log @@ -0,0 +1,5 @@ +2026-01-04 19:09:19.202 | INFO | core.views:index:45 - 用户访问首页 +2026-01-04 19:09:30.615 | INFO | core.views:generate_report:307 - 用户访问报告生成页面 +2026-01-04 19:09:41.595 | INFO | core.views:system_settings:411 - 用户访问系统配置页面 +2026-01-04 19:10:41.211 | INFO | core.views:index:45 - 用户访问首页 +2026-01-04 19:11:02.642 | INFO | core.views:yesterday_records:73 - 用户访问昨日记录页面 diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..d5fe57a --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_family.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main()