一个家庭日报系统
This commit is contained in:
197
.trae/documents/家庭日报系统设计与实现.md
Normal file
197
.trae/documents/家庭日报系统设计与实现.md
Normal file
@@ -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报告的邮件
|
||||
|
||||
系统设计简洁易用,适合家庭日常使用,注重生活化和个性化体验。
|
||||
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
BIN
core/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
core/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/admin.cpython-313.pyc
Normal file
BIN
core/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/apps.cpython-313.pyc
Normal file
BIN
core/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/forms.cpython-313.pyc
Normal file
BIN
core/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/models.cpython-313.pyc
Normal file
BIN
core/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/urls.cpython-313.pyc
Normal file
BIN
core/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/views.cpython-313.pyc
Normal file
BIN
core/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
3
core/admin.py
Normal file
3
core/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
core/apps.py
Normal file
6
core/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'core'
|
||||
84
core/forms.py
Normal file
84
core/forms.py
Normal file
@@ -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': '请输入收件人邮箱'}),
|
||||
}
|
||||
Binary file not shown.
18
core/management/commands/create_superuser.py
Normal file
18
core/management/commands/create_superuser.py
Normal file
@@ -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.'))
|
||||
104
core/migrations/0001_initial.py
Normal file
104
core/migrations/0001_initial.py
Normal file
@@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
core/migrations/__init__.py
Normal file
0
core/migrations/__init__.py
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
Binary file not shown.
BIN
core/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
core/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
131
core/models.py
Normal file
131
core/models.py
Normal file
@@ -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
|
||||
123
core/tasks.py
Normal file
123
core/tasks.py
Normal file
@@ -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
|
||||
27
core/templates/core/add_family_task.html
Normal file
27
core/templates/core/add_family_task.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>添加家庭事项</h2>
|
||||
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{% url 'family_tasks' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
27
core/templates/core/add_insight.html
Normal file
27
core/templates/core/add_insight.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>添加感悟记录</h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{% url 'yesterday_records' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
27
core/templates/core/add_reading.html
Normal file
27
core/templates/core/add_reading.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>添加阅读记录</h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{% url 'yesterday_records' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
27
core/templates/core/add_today_plan.html
Normal file
27
core/templates/core/add_today_plan.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>添加今日计划</h2>
|
||||
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{% url 'today_plan' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
130
core/templates/core/base.html
Normal file
130
core/templates/core/base.html
Normal file
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>家庭日报系统</title>
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Chart.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
/* 自定义样式 */
|
||||
.nav-item.active .nav-link {
|
||||
font-weight: bold;
|
||||
color: #0d6efd !important;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.file-link {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{% url 'index' %}">家庭日报系统</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'index' %}active{% endif %}" href="{% url 'index' %}">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'yesterday_records' or 'add_reading' in request.path or 'edit_reading' in request.path or 'add_insight' in request.path or 'edit_insight' in request.path %}active{% endif %}" href="{% url 'yesterday_records' %}">昨日记录</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'family_tasks' or 'add_family_task' in request.path or 'edit_family_task' in request.path %}active{% endif %}" href="{% url 'family_tasks' %}">家庭事项</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'today_plan' or 'add_today_plan' in request.path or 'edit_today_plan' in request.path %}active{% endif %}" href="{% url 'today_plan' %}">今日计划</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'generate_report' or 'view_report' in request.path or 'generate_pdf_report' in request.path %}active{% endif %}" href="{% url 'generate_report' %}">报告生成</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'system_settings' %}active{% endif %}" href="{% url 'system_settings' %}">系统配置</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<main class="container mt-4">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-light text-center text-lg-start mt-5 py-3">
|
||||
<div class="container">
|
||||
<p class="text-muted">© 2024 家庭日报系统 - 专注于家庭生活的轻量级日报系统</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap 5 JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- 自定义JS -->
|
||||
<script>
|
||||
// 切换计划状态
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const toggleButtons = document.querySelectorAll('.toggle-status-btn');
|
||||
toggleButtons.forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const url = this.getAttribute('href');
|
||||
const statusBadge = this.closest('tr').querySelector('.status-badge');
|
||||
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'completed') {
|
||||
statusBadge.className = 'badge bg-success status-badge';
|
||||
statusBadge.textContent = '已完成';
|
||||
this.innerHTML = '<i class="bi bi-check2-square"></i> 已完成';
|
||||
} else {
|
||||
statusBadge.className = 'badge bg-warning status-badge';
|
||||
statusBadge.textContent = '待处理';
|
||||
this.innerHTML = '<i class="bi bi-square"></i> 标记完成';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
18
core/templates/core/delete_family_task.html
Normal file
18
core/templates/core/delete_family_task.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>删除家庭事项</h2>
|
||||
|
||||
<div class="alert alert-danger mt-4">
|
||||
<p>您确定要删除这条家庭事项吗?</p>
|
||||
<p><strong>{{ task.content }}</strong> ({{ task.get_type_display }})</p>
|
||||
<p>此操作不可恢复。</p>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<button type="submit" class="btn btn-danger">确认删除</button>
|
||||
<a href="{% url 'family_tasks' %}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
18
core/templates/core/delete_insight.html
Normal file
18
core/templates/core/delete_insight.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>删除感悟记录</h2>
|
||||
|
||||
<div class="alert alert-danger mt-4">
|
||||
<p>您确定要删除这条感悟记录吗?</p>
|
||||
<p><strong>{{ insight.content|truncatechars:50 }}</strong></p>
|
||||
<p>此操作不可恢复。</p>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<button type="submit" class="btn btn-danger">确认删除</button>
|
||||
<a href="{% url 'yesterday_records' %}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
18
core/templates/core/delete_reading.html
Normal file
18
core/templates/core/delete_reading.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>删除阅读记录</h2>
|
||||
|
||||
<div class="alert alert-danger mt-4">
|
||||
<p>您确定要删除这条阅读记录吗?</p>
|
||||
<p><strong>{{ reading.title }}</strong> ({{ reading.get_type_display }})</p>
|
||||
<p>此操作不可恢复。</p>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<button type="submit" class="btn btn-danger">确认删除</button>
|
||||
<a href="{% url 'yesterday_records' %}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
18
core/templates/core/delete_today_plan.html
Normal file
18
core/templates/core/delete_today_plan.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>删除今日计划</h2>
|
||||
|
||||
<div class="alert alert-danger mt-4">
|
||||
<p>您确定要删除这条今日计划吗?</p>
|
||||
<p><strong>{{ plan.content }}</strong> ({{ plan.get_type_display }})</p>
|
||||
<p>此操作不可恢复。</p>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<button type="submit" class="btn btn-danger">确认删除</button>
|
||||
<a href="{% url 'today_plan' %}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
27
core/templates/core/edit_family_task.html
Normal file
27
core/templates/core/edit_family_task.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>编辑家庭事项</h2>
|
||||
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{% url 'family_tasks' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
27
core/templates/core/edit_insight.html
Normal file
27
core/templates/core/edit_insight.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>编辑感悟记录</h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{% url 'yesterday_records' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
27
core/templates/core/edit_reading.html
Normal file
27
core/templates/core/edit_reading.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>编辑阅读记录</h2>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{% url 'yesterday_records' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
27
core/templates/core/edit_today_plan.html
Normal file
27
core/templates/core/edit_today_plan.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>编辑今日计划</h2>
|
||||
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<a href="{% url 'today_plan' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
61
core/templates/core/family_tasks.html
Normal file
61
core/templates/core/family_tasks.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>家庭事项</h2>
|
||||
|
||||
<!-- 添加事项按钮 -->
|
||||
<div class="mb-4">
|
||||
<a href="{% url 'add_family_task' %}" class="btn btn-primary">添加家庭事项</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">家庭事项列表</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if tasks %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>内容</th>
|
||||
<th>优先级</th>
|
||||
<th>状态</th>
|
||||
<th>截止日期</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for task in tasks %}
|
||||
<tr>
|
||||
<td>{{ task.get_type_display }}</td>
|
||||
<td>{{ task.content }}</td>
|
||||
<td>
|
||||
<span class="badge {% if task.priority == 'high' %}bg-danger{% elif task.priority == 'medium' %}bg-warning{% else %}bg-info{% endif %}">
|
||||
{{ task.get_priority_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {% if task.status == 'completed' %}bg-success{% else %}bg-warning{% endif %}">
|
||||
{{ task.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ task.deadline|default:"-" }}</td>
|
||||
<td>
|
||||
<a href="{% url 'edit_family_task' task.id %}" class="btn btn-sm btn-warning" title="编辑">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_family_task' task.id %}" class="btn btn-sm btn-danger" title="删除">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">还没有家庭事项,点击上方按钮添加</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
141
core/templates/core/index.html
Normal file
141
core/templates/core/index.html
Normal file
@@ -0,0 +1,141 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<!-- 今日概览 -->
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">今日概览</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>日期:{{ today }}</h6>
|
||||
<div class="mt-4">
|
||||
<h5>今日计划</h5>
|
||||
{% if today_plan %}
|
||||
<ul class="list-group">
|
||||
{% for plan in today_plan %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>{{ plan.content }}</strong>
|
||||
<small class="text-muted"> - {{ plan.get_type_display }} - {{ plan.get_priority_display }}</small>
|
||||
</div>
|
||||
<span class="badge {% if plan.status == 'completed' %}bg-success{% else %}bg-warning{% endif %} status-badge">
|
||||
{{ plan.get_status_display }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-muted">今天还没有计划,快去添加吧!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待处理事项 -->
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5 class="card-title mb-0">待处理事项</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if pending_family_tasks %}
|
||||
<ul class="list-group">
|
||||
{% for task in pending_family_tasks %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>{{ task.content }}</strong>
|
||||
<small class="text-muted"> - {{ task.get_type_display }}</small>
|
||||
</div>
|
||||
<span class="badge bg-info status-badge">{{ task.get_priority_display }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-muted">没有待处理的家庭事项</p>
|
||||
{% endif %}
|
||||
<a href="{% url 'family_tasks' %}" class="btn btn-sm btn-outline-warning mt-3">查看所有家庭事项</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快捷操作 -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="card-title mb-0">快捷操作</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="btn-group-vertical w-100">
|
||||
<a href="{% url 'yesterday_records' %}" class="btn btn-outline-primary mb-2">记录昨日信息</a>
|
||||
<a href="{% url 'today_plan' %}" class="btn btn-outline-primary mb-2">管理今日计划</a>
|
||||
<a href="{% url 'generate_report' %}" class="btn btn-outline-primary mb-2">查看今日报告</a>
|
||||
<a href="{% url 'send_email' %}" class="btn btn-outline-success">发送今日邮件</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 昨日记录 -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h5 class="card-title mb-0">昨日记录 ({{ yesterday }})</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<!-- 昨日阅读 -->
|
||||
<div class="col-md-6">
|
||||
<h5>阅读记录</h5>
|
||||
{% if yesterday_reading %}
|
||||
<ul class="list-group">
|
||||
{% for reading in yesterday_reading %}
|
||||
<li class="list-group-item">
|
||||
<strong>{{ reading.title }}</strong> ({{ reading.get_type_display }})
|
||||
{% if reading.source %}
|
||||
<small class="text-muted"> - {{ reading.source }}</small>
|
||||
{% endif %}
|
||||
{% if reading.progress %}
|
||||
<div class="mt-1"><small>进度:{{ reading.progress }}</small></div>
|
||||
{% endif %}
|
||||
{% if reading.file %}
|
||||
<div class="mt-1">
|
||||
<a href="{{ reading.file.url }}" class="file-link" target="_blank"><i class="bi bi-file-earmark"></i> 查看附件</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-muted">昨日没有阅读记录</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 昨日感悟 -->
|
||||
<div class="col-md-6">
|
||||
<h5>感悟记录</h5>
|
||||
{% if yesterday_insight %}
|
||||
<ul class="list-group">
|
||||
{% for insight in yesterday_insight %}
|
||||
<li class="list-group-item">
|
||||
{{ insight.content }}
|
||||
{% if insight.file %}
|
||||
<div class="mt-1">
|
||||
<a href="{{ insight.file.url }}" class="file-link" target="_blank"><i class="bi bi-file-earmark"></i> 查看附件</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-muted">昨日没有感悟记录</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
171
core/templates/core/report.html
Normal file
171
core/templates/core/report.html
Normal file
@@ -0,0 +1,171 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>家庭日报报告</h2>
|
||||
|
||||
<div class="mb-4">
|
||||
<a href="{% url 'generate_pdf_report' today|date:'Y-m-d' %}" class="btn btn-primary">
|
||||
<i class="bi bi-file-pdf"></i> 生成PDF报告
|
||||
</a>
|
||||
<a href="{% url 'send_email' %}" class="btn btn-success">
|
||||
<i class="bi bi-envelope"></i> 发送邮件
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">报告概览</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>报告日期:{{ today }}</h6>
|
||||
<h6>昨日日期:{{ yesterday }}</h6>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="chart-container" style="height: 200px;">
|
||||
<canvas id="familyTaskChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- 昨日阅读记录 -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">昨日阅读记录</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if yesterday_reading %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>标题</th>
|
||||
<th>来源</th>
|
||||
<th>进度</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for reading in yesterday_reading %}
|
||||
<tr>
|
||||
<td>{{ reading.get_type_display }}</td>
|
||||
<td>{{ reading.title }}</td>
|
||||
<td>{{ reading.source|default:"-" }}</td>
|
||||
<td>{{ reading.progress|default:"-" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">昨日没有阅读记录</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 昨日感悟记录 -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="card-title mb-0">昨日感悟记录</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if yesterday_insight %}
|
||||
<ul class="list-group">
|
||||
{% for insight in yesterday_insight %}
|
||||
<li class="list-group-item">{{ insight.content }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-muted">昨日没有感悟记录</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5 class="card-title mb-0">今日计划</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if today_plan %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>内容</th>
|
||||
<th>优先级</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for plan in today_plan %}
|
||||
<tr>
|
||||
<td>{{ plan.get_type_display }}</td>
|
||||
<td>{{ plan.content }}</td>
|
||||
<td>
|
||||
<span class="badge {% if plan.priority == 'high' %}bg-danger{% elif plan.priority == 'medium' %}bg-warning{% else %}bg-info{% endif %}">
|
||||
{{ plan.get_priority_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {% if plan.status == 'completed' %}bg-success{% else %}bg-warning{% endif %}">
|
||||
{{ plan.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">今天还没有计划</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 家庭事项统计图表
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const ctx = document.getElementById('familyTaskChart').getContext('2d');
|
||||
const familyTaskStats = {% if family_task_stats %}{{ family_task_stats|safe }}{% else %}[]{% endif %};
|
||||
|
||||
const labels = familyTaskStats.map(stat => {
|
||||
return stat.type === 'purchase' ? '采购' : '家务';
|
||||
});
|
||||
const data = familyTaskStats.map(stat => stat.count);
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
data: data,
|
||||
backgroundColor: [
|
||||
'#0d6efd',
|
||||
'#198754'
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: '家庭事项统计'
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
203
core/templates/core/report_pdf.html
Normal file
203
core/templates/core/report_pdf.html
Normal file
@@ -0,0 +1,203 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>家庭日报 - {{ today }}</title>
|
||||
<style>
|
||||
/* PDF样式 */
|
||||
body {
|
||||
font-family: SimHei, Arial, sans-serif;
|
||||
margin: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #000;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #555;
|
||||
font-size: 18px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
border-left: 4px solid #007bff;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>家庭日报</h1>
|
||||
|
||||
<div class="summary">
|
||||
<h3>报告概览</h3>
|
||||
<p>报告日期:{{ today }}</p>
|
||||
<p>昨日日期:{{ yesterday }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 昨日阅读记录 -->
|
||||
<div class="section">
|
||||
<h2>昨日阅读记录</h2>
|
||||
{% if yesterday_reading %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>标题</th>
|
||||
<th>来源</th>
|
||||
<th>进度</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for reading in yesterday_reading %}
|
||||
<tr>
|
||||
<td>{{ reading.get_type_display }}</td>
|
||||
<td>{{ reading.title }}</td>
|
||||
<td>{{ reading.source|default:"-" }}</td>
|
||||
<td>{{ reading.progress|default:"-" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="no-data">昨日没有阅读记录</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 昨日感悟记录 -->
|
||||
<div class="section">
|
||||
<h2>昨日感悟记录</h2>
|
||||
{% if yesterday_insight %}
|
||||
<ul>
|
||||
{% for insight in yesterday_insight %}
|
||||
<li>{{ insight.content }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="no-data">昨日没有感悟记录</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 今日计划 -->
|
||||
<div class="section">
|
||||
<h2>今日计划</h2>
|
||||
{% if today_plan %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>内容</th>
|
||||
<th>优先级</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for plan in today_plan %}
|
||||
<tr>
|
||||
<td>{{ plan.get_type_display }}</td>
|
||||
<td>{{ plan.content }}</td>
|
||||
<td>{{ plan.get_priority_display }}</td>
|
||||
<td>{{ plan.get_status_display }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="no-data">今天还没有计划</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 家庭事项统计 -->
|
||||
<div class="section">
|
||||
<h2>家庭事项统计</h2>
|
||||
{% if family_task_stats %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>数量</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for stat in family_task_stats %}
|
||||
<tr>
|
||||
<td>{{ stat.type|yesno:"采购,家务" }}</td>
|
||||
<td>{{ stat.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="no-data">没有家庭事项统计数据</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>此报告由家庭日报系统自动生成</p>
|
||||
<p>生成时间:{{ today|date:"Y年m月d日 H:i:s" }}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
95
core/templates/core/system_settings.html
Normal file
95
core/templates/core/system_settings.html
Normal file
@@ -0,0 +1,95 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>系统配置</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">系统配置设置</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>邮件配置</h6>
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.smtp_server.id_for_label }}" class="form-label">{{ form.smtp_server.label }}</label>
|
||||
{{ form.smtp_server }}
|
||||
{% if form.smtp_server.help_text %}
|
||||
<div class="form-text">{{ form.smtp_server.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in form.smtp_server.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.smtp_port.id_for_label }}" class="form-label">{{ form.smtp_port.label }}</label>
|
||||
{{ form.smtp_port }}
|
||||
{% if form.smtp_port.help_text %}
|
||||
<div class="form-text">{{ form.smtp_port.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in form.smtp_port.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.smtp_username.id_for_label }}" class="form-label">{{ form.smtp_username.label }}</label>
|
||||
{{ form.smtp_username }}
|
||||
{% if form.smtp_username.help_text %}
|
||||
<div class="form-text">{{ form.smtp_username.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in form.smtp_username.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.smtp_password.id_for_label }}" class="form-label">{{ form.smtp_password.label }}</label>
|
||||
{{ form.smtp_password }}
|
||||
{% if form.smtp_password.help_text %}
|
||||
<div class="form-text">{{ form.smtp_password.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in form.smtp_password.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h6>发送配置</h6>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.send_time.id_for_label }}" class="form-label">{{ form.send_time.label }}</label>
|
||||
{{ form.send_time }}
|
||||
{% if form.send_time.help_text %}
|
||||
<div class="form-text">{{ form.send_time.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in form.send_time.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.recipient_email.id_for_label }}" class="form-label">{{ form.recipient_email.label }}</label>
|
||||
{{ form.recipient_email }}
|
||||
{% if form.recipient_email.help_text %}
|
||||
<div class="form-text">{{ form.recipient_email.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in form.recipient_email.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">保存配置</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
66
core/templates/core/today_plan.html
Normal file
66
core/templates/core/today_plan.html
Normal file
@@ -0,0 +1,66 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>今日计划</h2>
|
||||
|
||||
<!-- 添加计划按钮 -->
|
||||
<div class="mb-4">
|
||||
<a href="{% url 'add_today_plan' %}" class="btn btn-primary">添加今日计划</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">今日计划列表</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if plans %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>内容</th>
|
||||
<th>优先级</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for plan in plans %}
|
||||
<tr>
|
||||
<td>{{ plan.get_type_display }}</td>
|
||||
<td>{{ plan.content }}</td>
|
||||
<td>
|
||||
<span class="badge {% if plan.priority == 'high' %}bg-danger{% elif plan.priority == 'medium' %}bg-warning{% else %}bg-info{% endif %}">
|
||||
{{ plan.get_priority_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {% if plan.status == 'completed' %}bg-success{% else %}bg-warning{% endif %} status-badge">
|
||||
{{ plan.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'toggle_today_plan' plan.id %}" class="btn btn-sm btn-success toggle-status-btn">
|
||||
{% if plan.status == 'completed' %}
|
||||
<i class="bi bi-check2-square"></i> 已完成
|
||||
{% else %}
|
||||
<i class="bi bi-square"></i> 标记完成
|
||||
{% endif %}
|
||||
</a>
|
||||
<a href="{% url 'edit_today_plan' plan.id %}" class="btn btn-sm btn-warning" title="编辑">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_today_plan' plan.id %}" class="btn btn-sm btn-danger" title="删除">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">今天还没有计划,快去添加吧!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
105
core/templates/core/yesterday_records.html
Normal file
105
core/templates/core/yesterday_records.html
Normal file
@@ -0,0 +1,105 @@
|
||||
{% extends 'core/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>昨日记录 ({{ yesterday }})</h2>
|
||||
|
||||
<!-- 添加记录按钮 -->
|
||||
<div class="mb-4">
|
||||
<a href="{% url 'add_reading' %}" class="btn btn-primary">添加阅读记录</a>
|
||||
<a href="{% url 'add_insight' %}" class="btn btn-success">添加感悟记录</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- 阅读记录 -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">阅读记录</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if reading_records %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>标题</th>
|
||||
<th>来源</th>
|
||||
<th>进度</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for reading in reading_records %}
|
||||
<tr>
|
||||
<td>{{ reading.get_type_display }}</td>
|
||||
<td>{{ reading.title }}</td>
|
||||
<td>{{ reading.source|default:"-" }}</td>
|
||||
<td>{{ reading.progress|default:"-" }}</td>
|
||||
<td>
|
||||
{% if reading.file %}
|
||||
<a href="{{ reading.file.url }}" class="btn btn-sm btn-info" title="查看附件" target="_blank">
|
||||
<i class="bi bi-file-earmark"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'edit_reading' reading.id %}" class="btn btn-sm btn-warning" title="编辑">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_reading' reading.id %}" class="btn btn-sm btn-danger" title="删除">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">昨日没有阅读记录,点击上方按钮添加</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 感悟记录 -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="card-title mb-0">感悟记录</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if insight_records %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>内容</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for insight in insight_records %}
|
||||
<tr>
|
||||
<td>{{ insight.content }}</td>
|
||||
<td>
|
||||
{% if insight.file %}
|
||||
<a href="{{ insight.file.url }}" class="btn btn-sm btn-info" title="查看附件" target="_blank">
|
||||
<i class="bi bi-file-earmark"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'edit_insight' insight.id %}" class="btn btn-sm btn-warning" title="编辑">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_insight' insight.id %}" class="btn btn-sm btn-danger" title="删除">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">昨日没有感悟记录,点击上方按钮添加</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
3
core/tests.py
Normal file
3
core/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
40
core/urls.py
Normal file
40
core/urls.py
Normal file
@@ -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/<int:pk>/edit/', views.edit_reading, name='edit_reading'),
|
||||
path('yesterday/reading/<int:pk>/delete/', views.delete_reading, name='delete_reading'),
|
||||
path('yesterday/insight/add/', views.add_insight, name='add_insight'),
|
||||
path('yesterday/insight/<int:pk>/edit/', views.edit_insight, name='edit_insight'),
|
||||
path('yesterday/insight/<int:pk>/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/<int:pk>/edit/', views.edit_family_task, name='edit_family_task'),
|
||||
path('family-tasks/<int:pk>/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/<int:pk>/edit/', views.edit_today_plan, name='edit_today_plan'),
|
||||
path('today-plan/<int:pk>/delete/', views.delete_today_plan, name='delete_today_plan'),
|
||||
path('today-plan/<int:pk>/toggle/', views.toggle_today_plan, name='toggle_today_plan'),
|
||||
|
||||
# 报告生成
|
||||
path('report/', views.generate_report, name='generate_report'),
|
||||
path('report/<str:date>/', views.view_report, name='view_report'),
|
||||
path('report/<str:date>/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'),
|
||||
]
|
||||
503
core/views.py
Normal file
503
core/views.py
Normal file
@@ -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)
|
||||
BIN
db.sqlite3
Normal file
BIN
db.sqlite3
Normal file
Binary file not shown.
4
diary_family/__init__.py
Normal file
4
diary_family/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# 确保Celery应用在Django启动时被加载
|
||||
from .celery import app as celery_app
|
||||
|
||||
__all__ = ('celery_app',)
|
||||
BIN
diary_family/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
diary_family/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
diary_family/__pycache__/celery.cpython-313.pyc
Normal file
BIN
diary_family/__pycache__/celery.cpython-313.pyc
Normal file
Binary file not shown.
BIN
diary_family/__pycache__/settings.cpython-313.pyc
Normal file
BIN
diary_family/__pycache__/settings.cpython-313.pyc
Normal file
Binary file not shown.
BIN
diary_family/__pycache__/urls.cpython-313.pyc
Normal file
BIN
diary_family/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
diary_family/__pycache__/wsgi.cpython-313.pyc
Normal file
BIN
diary_family/__pycache__/wsgi.cpython-313.pyc
Normal file
Binary file not shown.
16
diary_family/asgi.py
Normal file
16
diary_family/asgi.py
Normal file
@@ -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()
|
||||
20
diary_family/celery.py
Normal file
20
diary_family/celery.py
Normal file
@@ -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}')
|
||||
171
diary_family/settings.py
Normal file
171
diary_family/settings.py
Normal file
@@ -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']
|
||||
30
diary_family/urls.py
Normal file
30
diary_family/urls.py
Normal file
@@ -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)
|
||||
16
diary_family/wsgi.py
Normal file
16
diary_family/wsgi.py
Normal file
@@ -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()
|
||||
5
logs/app.log
Normal file
5
logs/app.log
Normal file
@@ -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 - 用户访问昨日记录页面
|
||||
22
manage.py
Normal file
22
manage.py
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user