1133 lines
39 KiB
Python
1133 lines
39 KiB
Python
from django.shortcuts import render, redirect, get_object_or_404
|
||
from django.http import HttpResponse, JsonResponse
|
||
from django.utils import timezone
|
||
from django.db import models
|
||
from django.db.models import Count, Q
|
||
from django.core.mail import send_mail, EmailMessage
|
||
from django.conf import settings
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
from django.contrib.auth import authenticate, login, logout
|
||
from django.contrib.auth.decorators import login_required
|
||
from django.contrib import messages
|
||
from datetime import timedelta, datetime
|
||
import os
|
||
import logging
|
||
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是否真正可用,检查依赖库
|
||
test_html = HTML(string="<html><body>Test</body></html>")
|
||
# 尝试生成PDF以验证依赖库是否可用
|
||
import tempfile
|
||
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=True) as tmp:
|
||
test_html.write_pdf(tmp.name)
|
||
WEASYPRINT_AVAILABLE = True
|
||
logger.info("WeasyPrint库可用,PDF功能正常")
|
||
except ImportError:
|
||
logger.warning("WeasyPrint库无法导入,PDF功能将不可用")
|
||
WEASYPRINT_AVAILABLE = False
|
||
except Exception as e:
|
||
logger.warning(f"WeasyPrint依赖库不可用: {str(e)},PDF功能将不可用")
|
||
WEASYPRINT_AVAILABLE = False
|
||
return WEASYPRINT_AVAILABLE
|
||
|
||
from .models import (
|
||
ReadingRecord,
|
||
InsightRecord,
|
||
Summary,
|
||
FamilyTask,
|
||
TodayPlan,
|
||
SystemConfig,
|
||
FamilyMember,
|
||
SummaryCategory,
|
||
PublicContentType,
|
||
PublicContent,
|
||
TempMessage
|
||
)
|
||
from .forms import (
|
||
ReadingRecordForm,
|
||
InsightRecordForm,
|
||
SummaryForm,
|
||
FamilyTaskForm,
|
||
TodayPlanForm,
|
||
SystemConfigForm,
|
||
PublicContentForm,
|
||
TempUploadForm,
|
||
TempMessageForm
|
||
)
|
||
|
||
# 首页视图
|
||
@login_required
|
||
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.exclude(status__name='completed')
|
||
# 过滤掉截止日期早于今天的事项(如果设置了截止日期)
|
||
pending_family_tasks = pending_family_tasks.filter(Q(deadline__gte=today) | Q(deadline__isnull=True))
|
||
|
||
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)
|
||
|
||
# 昨日记录视图
|
||
@login_required
|
||
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)
|
||
|
||
# 添加阅读记录
|
||
@login_required
|
||
def add_reading(request):
|
||
"""添加阅读记录"""
|
||
if request.method == 'POST':
|
||
form = ReadingRecordForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
reading = form.save(commit=False)
|
||
reading.date = timezone.now().date() - timedelta(days=1)
|
||
reading.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)
|
||
|
||
# 编辑阅读记录
|
||
@login_required
|
||
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)
|
||
|
||
# 删除阅读记录
|
||
@login_required
|
||
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)
|
||
|
||
# 添加感悟记录
|
||
@login_required
|
||
def add_insight(request):
|
||
"""添加感悟记录"""
|
||
family_members = FamilyMember.objects.all()
|
||
if request.method == 'POST':
|
||
form = InsightRecordForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
insight = form.save(commit=False)
|
||
insight.date = timezone.now().date() - timedelta(days=1)
|
||
insight.save()
|
||
logger.info("添加感悟记录")
|
||
return redirect('yesterday_records')
|
||
else:
|
||
form = InsightRecordForm()
|
||
|
||
context = {'form': form, 'family_members': family_members}
|
||
return render(request, 'core/add_insight.html', context)
|
||
|
||
# 编辑感悟记录
|
||
@login_required
|
||
def edit_insight(request, pk):
|
||
"""编辑感悟记录"""
|
||
insight = get_object_or_404(InsightRecord, pk=pk)
|
||
family_members = FamilyMember.objects.all()
|
||
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, 'family_members': family_members}
|
||
return render(request, 'core/edit_insight.html', context)
|
||
|
||
# 删除感悟记录
|
||
@login_required
|
||
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)
|
||
|
||
# 今日记录视图
|
||
@login_required
|
||
def today_records(request):
|
||
"""今日记录"""
|
||
logger.info("用户访问今日记录页面")
|
||
today = timezone.now().date()
|
||
|
||
# 获取今日阅读记录
|
||
reading_records = ReadingRecord.objects.filter(date=today)
|
||
|
||
# 获取今日感悟记录
|
||
insight_records = InsightRecord.objects.filter(date=today)
|
||
|
||
context = {
|
||
'today': today,
|
||
'reading_records': reading_records,
|
||
'insight_records': insight_records,
|
||
}
|
||
|
||
return render(request, 'core/today_records.html', context)
|
||
|
||
@login_required
|
||
# 添加今日阅读记录
|
||
def add_today_reading(request):
|
||
"""添加今日阅读记录"""
|
||
if request.method == 'POST':
|
||
form = ReadingRecordForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
reading = form.save(commit=False)
|
||
reading.date = timezone.now().date()
|
||
reading.save()
|
||
logger.info(f"添加今日阅读记录: {form.cleaned_data['title']}")
|
||
return redirect('today_records')
|
||
else:
|
||
form = ReadingRecordForm()
|
||
|
||
context = {'form': form}
|
||
return render(request, 'core/add_reading.html', context)
|
||
|
||
# 编辑今日阅读记录
|
||
@login_required
|
||
def edit_today_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('today_records')
|
||
else:
|
||
form = ReadingRecordForm(instance=reading)
|
||
|
||
context = {'form': form, 'reading': reading}
|
||
return render(request, 'core/edit_reading.html', context)
|
||
|
||
# 删除今日阅读记录
|
||
@login_required
|
||
def delete_today_reading(request, pk):
|
||
"""删除今日阅读记录"""
|
||
reading = get_object_or_404(ReadingRecord, pk=pk)
|
||
if request.method == 'POST':
|
||
reading.delete()
|
||
logger.info(f"删除今日阅读记录: {reading.title}")
|
||
return redirect('today_records')
|
||
|
||
context = {'reading': reading}
|
||
return render(request, 'core/delete_reading.html', context)
|
||
|
||
# 添加今日感悟记录
|
||
@login_required
|
||
def add_today_insight(request):
|
||
"""添加今日感悟记录"""
|
||
family_members = FamilyMember.objects.all()
|
||
if request.method == 'POST':
|
||
form = InsightRecordForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
insight = form.save(commit=False)
|
||
insight.date = timezone.now().date()
|
||
insight.save()
|
||
logger.info("添加今日感悟记录")
|
||
return redirect('today_records')
|
||
else:
|
||
form = InsightRecordForm()
|
||
|
||
context = {'form': form, 'family_members': family_members}
|
||
return render(request, 'core/add_insight.html', context)
|
||
|
||
# 编辑今日感悟记录
|
||
@login_required
|
||
def edit_today_insight(request, pk):
|
||
"""编辑今日感悟记录"""
|
||
insight = get_object_or_404(InsightRecord, pk=pk)
|
||
family_members = FamilyMember.objects.all()
|
||
if request.method == 'POST':
|
||
form = InsightRecordForm(request.POST, request.FILES, instance=insight)
|
||
if form.is_valid():
|
||
form.save()
|
||
logger.info("编辑今日感悟记录")
|
||
return redirect('today_records')
|
||
else:
|
||
form = InsightRecordForm(instance=insight)
|
||
|
||
context = {'form': form, 'insight': insight, 'family_members': family_members}
|
||
return render(request, 'core/edit_insight.html', context)
|
||
|
||
# 删除今日感悟记录
|
||
@login_required
|
||
def delete_today_insight(request, pk):
|
||
"""删除今日感悟记录"""
|
||
insight = get_object_or_404(InsightRecord, pk=pk)
|
||
if request.method == 'POST':
|
||
insight.delete()
|
||
logger.info("删除今日感悟记录")
|
||
return redirect('today_records')
|
||
|
||
context = {'insight': insight}
|
||
return render(request, 'core/delete_insight.html', context)
|
||
|
||
# 汇总记录视图
|
||
@login_required
|
||
def summaries(request):
|
||
"""汇总记录"""
|
||
logger.info("用户访问汇总记录页面")
|
||
today = timezone.now().date()
|
||
yesterday = today - timedelta(days=1)
|
||
|
||
summary_records = Summary.objects.filter(date=yesterday)
|
||
|
||
context = {
|
||
'yesterday': yesterday,
|
||
'summary_records': summary_records,
|
||
}
|
||
|
||
return render(request, 'core/summaries.html', context)
|
||
|
||
# 添加汇总记录
|
||
@login_required
|
||
def add_summary(request):
|
||
"""添加汇总记录"""
|
||
family_members = FamilyMember.objects.all()
|
||
categories = SummaryCategory.objects.all()
|
||
if request.method == 'POST':
|
||
form = SummaryForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
form.save()
|
||
logger.info(f"添加汇总记录: {form.cleaned_data['content'][:20]}...")
|
||
return redirect('summaries')
|
||
else:
|
||
form = SummaryForm()
|
||
|
||
context = {'form': form, 'family_members': family_members, 'categories': categories}
|
||
return render(request, 'core/add_summary.html', context)
|
||
|
||
# 编辑汇总记录
|
||
@login_required
|
||
def edit_summary(request, pk):
|
||
"""编辑汇总记录"""
|
||
summary = get_object_or_404(Summary, pk=pk)
|
||
family_members = FamilyMember.objects.all()
|
||
categories = SummaryCategory.objects.all()
|
||
if request.method == 'POST':
|
||
form = SummaryForm(request.POST, request.FILES, instance=summary)
|
||
if form.is_valid():
|
||
form.save()
|
||
logger.info(f"编辑汇总记录: {form.cleaned_data['content'][:20]}...")
|
||
return redirect('summaries')
|
||
else:
|
||
form = SummaryForm(instance=summary)
|
||
|
||
context = {'form': form, 'summary': summary, 'family_members': family_members, 'categories': categories}
|
||
return render(request, 'core/edit_summary.html', context)
|
||
|
||
# 删除汇总记录
|
||
@login_required
|
||
def delete_summary(request, pk):
|
||
"""删除汇总记录"""
|
||
summary = get_object_or_404(Summary, pk=pk)
|
||
if request.method == 'POST':
|
||
summary.delete()
|
||
logger.info(f"删除汇总记录: {summary.content[:20]}...")
|
||
return redirect('summaries')
|
||
|
||
context = {'summary': summary}
|
||
return render(request, 'core/delete_summary.html', context)
|
||
|
||
# 家庭事项视图
|
||
@login_required
|
||
def family_tasks(request):
|
||
"""家庭事项 - 显示未完成的事项,一个月内显示详情,超过一个月显示数量"""
|
||
logger.info("用户访问家庭事项页面")
|
||
|
||
today = timezone.now().date()
|
||
one_month_later = today + timedelta(days=30)
|
||
|
||
# 获取所有未完成的事项(排除已完成状态)
|
||
all_pending_tasks = FamilyTask.objects.exclude(status__name='completed')
|
||
|
||
# 一个月内到期的事项(显示详情)
|
||
# 包括:有截止日期且在一个月内,或者没有截止日期的事项
|
||
upcoming_tasks = all_pending_tasks.filter(
|
||
Q(deadline__isnull=True) | Q(deadline__lte=one_month_later)
|
||
)
|
||
|
||
# 超过一个月到期的事项(只显示数量)
|
||
future_tasks = all_pending_tasks.filter(deadline__gt=one_month_later)
|
||
future_tasks_count = future_tasks.count()
|
||
|
||
context = {
|
||
'upcoming_tasks': upcoming_tasks,
|
||
'future_tasks_count': future_tasks_count,
|
||
'total_pending_count': all_pending_tasks.count(),
|
||
'today': today,
|
||
}
|
||
|
||
return render(request, 'core/family_tasks.html', context)
|
||
|
||
# 添加家庭事项
|
||
@login_required
|
||
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)
|
||
|
||
# 编辑家庭事项
|
||
@login_required
|
||
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)
|
||
|
||
# 删除家庭事项
|
||
@login_required
|
||
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)
|
||
|
||
# 今日计划视图
|
||
@login_required
|
||
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)
|
||
|
||
# 添加今日计划
|
||
@login_required
|
||
def add_today_plan(request):
|
||
"""添加今日计划"""
|
||
family_members = FamilyMember.objects.all()
|
||
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, 'family_members': family_members}
|
||
return render(request, 'core/add_today_plan.html', context)
|
||
|
||
# 编辑今日计划
|
||
@login_required
|
||
def edit_today_plan(request, pk):
|
||
"""编辑今日计划"""
|
||
plan = get_object_or_404(TodayPlan, pk=pk)
|
||
family_members = FamilyMember.objects.all()
|
||
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, 'family_members': family_members}
|
||
return render(request, 'core/edit_today_plan.html', context)
|
||
|
||
# 删除今日计划
|
||
@login_required
|
||
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)
|
||
|
||
# 切换今日计划状态
|
||
@login_required
|
||
def toggle_today_plan(request, pk):
|
||
"""切换今日计划状态"""
|
||
plan = get_object_or_404(TodayPlan, pk=pk)
|
||
pending_status = Status.objects.get(name='pending')
|
||
completed_status = Status.objects.get(name='completed')
|
||
plan.status = completed_status if plan.status == pending_status else pending_status
|
||
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')
|
||
|
||
# 生成报告
|
||
@login_required
|
||
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)
|
||
yesterday_summary = Summary.objects.filter(date=yesterday)
|
||
|
||
# 获取今日计划
|
||
today_plan = TodayPlan.objects.filter(date=today)
|
||
|
||
# 获取家庭事项统计
|
||
family_task_stats = FamilyTask.objects.values('type').annotate(count=Count('id'))
|
||
|
||
# 获取最近7天的日期列表,用于生成历史PDF链接
|
||
historical_dates = []
|
||
for i in range(1, 8):
|
||
historical_date = today - timedelta(days=i)
|
||
historical_dates.append(historical_date)
|
||
|
||
# 检查是否有更早的PDF文件
|
||
has_earlier_pdfs = False
|
||
seven_days_ago = today - timedelta(days=7)
|
||
pdf_files = [f for f in os.listdir(settings.REPORTS_ROOT) if f.endswith('.pdf')]
|
||
for pdf_file in pdf_files:
|
||
try:
|
||
# 从文件名中提取日期
|
||
date_str = pdf_file.replace('report_', '').replace('.pdf', '')
|
||
file_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||
if file_date < seven_days_ago:
|
||
has_earlier_pdfs = True
|
||
break
|
||
except Exception:
|
||
continue
|
||
|
||
context = {
|
||
'today': today,
|
||
'yesterday': yesterday,
|
||
'yesterday_reading': yesterday_reading,
|
||
'yesterday_insight': yesterday_insight,
|
||
'yesterday_summary': yesterday_summary,
|
||
'today_plan': today_plan,
|
||
'family_task_stats': family_task_stats,
|
||
'historical_dates': historical_dates,
|
||
'has_earlier_pdfs': has_earlier_pdfs,
|
||
}
|
||
|
||
return render(request, 'core/report.html', context)
|
||
|
||
# 查看报告
|
||
@login_required
|
||
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)
|
||
yesterday_summary = Summary.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,
|
||
'yesterday_summary': yesterday_summary,
|
||
'today_plan': today_plan,
|
||
'family_task_stats': family_task_stats,
|
||
}
|
||
|
||
return render(request, 'core/report.html', context)
|
||
|
||
# 生成PDF报告
|
||
@login_required
|
||
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}")
|
||
|
||
try:
|
||
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)
|
||
yesterday_summary = Summary.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,
|
||
'yesterday_summary': yesterday_summary,
|
||
'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
|
||
except Exception as e:
|
||
logger.error(f"生成PDF报告失败: {str(e)}")
|
||
return HttpResponse(f"生成PDF报告失败: {str(e)}", status=500)
|
||
|
||
# 预览PDF报告
|
||
def preview_pdf_report(request, date):
|
||
"""预览PDF报告(HTML格式,样式与PDF一致)"""
|
||
logger.info(f"用户预览PDF报告: {date}")
|
||
|
||
try:
|
||
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)
|
||
yesterday_summary = Summary.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,
|
||
'yesterday_summary': yesterday_summary,
|
||
'today_plan': today_plan,
|
||
'family_task_stats': family_task_stats,
|
||
}
|
||
|
||
# 渲染HTML模板(与PDF使用相同的模板)
|
||
return render(request, 'core/report_pdf.html', context)
|
||
except Exception as e:
|
||
logger.error(f"预览PDF报告失败: {str(e)}")
|
||
return HttpResponse(f"预览PDF报告失败: {str(e)}", status=500)
|
||
|
||
# 系统配置
|
||
@login_required
|
||
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)
|
||
yesterday_summary = Summary.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,
|
||
'yesterday_summary': yesterday_summary,
|
||
'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配置一致
|
||
@login_required
|
||
def send_email(request):
|
||
"""发送邮件别名"""
|
||
return send_email_view(request)
|
||
|
||
# PDF文件列表
|
||
@login_required
|
||
def pdf_list(request):
|
||
"""显示服务器上已有的PDF文件列表"""
|
||
logger.info("用户访问PDF文件列表页面")
|
||
|
||
# 获取所有PDF文件
|
||
pdf_files = [f for f in os.listdir(settings.REPORTS_ROOT) if f.endswith('.pdf')]
|
||
|
||
# 解析文件名,提取日期,并按日期降序排序
|
||
pdf_info = []
|
||
for pdf_file in pdf_files:
|
||
try:
|
||
# 从文件名中提取日期
|
||
date_str = pdf_file.replace('report_', '').replace('.pdf', '')
|
||
file_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||
pdf_info.append({
|
||
'filename': pdf_file,
|
||
'date': file_date,
|
||
'date_str': date_str
|
||
})
|
||
except Exception as e:
|
||
logger.error(f"解析PDF文件名失败: {pdf_file}, 错误: {str(e)}")
|
||
continue
|
||
|
||
# 按日期降序排序
|
||
pdf_info.sort(key=lambda x: x['date'], reverse=True)
|
||
|
||
context = {
|
||
'pdf_files': pdf_info
|
||
}
|
||
|
||
return render(request, 'core/pdf_list.html', context)
|
||
|
||
def api_submit_summary(request):
|
||
"""API提交汇总记录 - 仅接受指定分类和发言人的记录"""
|
||
logger.info("API: 收到汇总记录提交请求")
|
||
|
||
if request.method != 'POST':
|
||
return JsonResponse({'success': False, 'message': '只支持POST请求'}, status=405)
|
||
|
||
try:
|
||
content = request.POST.get('content', '').strip()
|
||
|
||
if not content:
|
||
return JsonResponse({'success': False, 'message': '内容不能为空'}, status=400)
|
||
|
||
category_name = "定期"
|
||
speaker_name = "机器人"
|
||
|
||
try:
|
||
category = SummaryCategory.objects.get(name=category_name)
|
||
except SummaryCategory.DoesNotExist:
|
||
logger.error(f"API: 分类 '{category_name}' 不存在")
|
||
return JsonResponse({'success': False, 'message': f"分类 '{category_name}' 不存在"}, status=400)
|
||
|
||
try:
|
||
speaker = FamilyMember.objects.get(name=speaker_name)
|
||
except FamilyMember.DoesNotExist:
|
||
logger.error(f"API: 发言人 '{speaker_name}' 不存在")
|
||
return JsonResponse({'success': False, 'message': f"发言人 '{speaker_name}' 不存在"}, status=400)
|
||
|
||
# 获取来源,支持客户端提交
|
||
source = request.POST.get('source', '').strip()
|
||
if not source:
|
||
source = ""
|
||
|
||
# 如果没有提供来源,自动生成
|
||
if not source:
|
||
import socket
|
||
hostname = socket.gethostname()
|
||
local_ip = socket.gethostbyname(hostname)
|
||
source = f"{hostname} ({local_ip})"
|
||
|
||
summary = Summary.objects.create(
|
||
date=timezone.now().date(),
|
||
category=category,
|
||
speaker=speaker,
|
||
content=content,
|
||
source=source
|
||
)
|
||
|
||
logger.info(f"API: 汇总记录创建成功,ID={summary.id}")
|
||
return JsonResponse({
|
||
'success': True,
|
||
'message': '提交成功',
|
||
'id': summary.id
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"API: 提交汇总记录失败: {str(e)}")
|
||
return JsonResponse({'success': False, 'message': f"提交失败: {str(e)}"}, status=500)
|
||
|
||
@csrf_exempt
|
||
def api_temp_upload(request):
|
||
"""API临时文件上传"""
|
||
logger.info("API: 收到临时文件上传请求")
|
||
|
||
if request.method != 'POST':
|
||
return JsonResponse({'success': False, 'message': '只支持POST请求'}, status=405)
|
||
|
||
try:
|
||
title = request.POST.get('title', '').strip()
|
||
expire_type = request.POST.get('expire_type', '').strip()
|
||
file = request.FILES.get('file')
|
||
|
||
if not title:
|
||
return JsonResponse({'success': False, 'message': '标题不能为空'}, status=400)
|
||
|
||
if not file:
|
||
return JsonResponse({'success': False, 'message': '文件不能为空'}, status=400)
|
||
|
||
if expire_type not in ['expire_1h', 'expire_1d', 'expire_7d']:
|
||
return JsonResponse({'success': False, 'message': '无效的过期类型'}, status=400)
|
||
|
||
try:
|
||
public_content_type = PublicContentType.objects.get(name='临时文件')
|
||
except PublicContentType.DoesNotExist:
|
||
public_content_type = PublicContentType.objects.create(name='临时文件')
|
||
|
||
expire_delta_map = {
|
||
'expire_1h': timedelta(hours=1),
|
||
'expire_1d': timedelta(days=1),
|
||
'expire_7d': timedelta(days=7),
|
||
}
|
||
expire_at = timezone.now() + expire_delta_map[expire_type]
|
||
|
||
temp_content = PublicContent.objects.create(
|
||
type=public_content_type,
|
||
title=title,
|
||
file=file,
|
||
is_published=True,
|
||
is_temp_file=True,
|
||
expire_type=expire_type,
|
||
expire_at=expire_at,
|
||
)
|
||
|
||
file_url = request.build_absolute_uri(temp_content.file.url) if temp_content.file else None
|
||
file_size = temp_content.file.size if temp_content.file else 0
|
||
|
||
logger.info(f"API: 临时文件创建成功,ID={temp_content.id}, 过期时间={expire_at}")
|
||
return JsonResponse({
|
||
'success': True,
|
||
'message': '上传成功',
|
||
'id': temp_content.id,
|
||
'file_url': file_url,
|
||
'file_name': temp_content.file.name.split('/')[-1] if temp_content.file else None,
|
||
'file_size': file_size,
|
||
'expire_at': expire_at.isoformat(),
|
||
'expire_type': expire_type,
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"API: 临时文件上传失败: {str(e)}")
|
||
return JsonResponse({'success': False, 'message': f"上传失败: {str(e)}"}, status=500)
|
||
|
||
|
||
# 获取syslog日志记录器(用于fail2ban检测)
|
||
syslog_logger = logging.getLogger('django.security.login')
|
||
|
||
# 登录视图
|
||
def user_login(request):
|
||
"""用户登录"""
|
||
if request.user.is_authenticated:
|
||
logger.info(f"用户 {request.user.username} 已登录,重定向到首页")
|
||
return redirect('index')
|
||
|
||
if request.method == 'POST':
|
||
username = request.POST.get('username')
|
||
password = request.POST.get('password')
|
||
|
||
# 获取客户端IP地址
|
||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||
if x_forwarded_for:
|
||
client_ip = x_forwarded_for.split(',')[0].strip()
|
||
else:
|
||
client_ip = request.META.get('REMOTE_ADDR', 'unknown')
|
||
|
||
logger.info(f"用户登录尝试: {username}, IP: {client_ip}")
|
||
|
||
user = authenticate(request, username=username, password=password)
|
||
|
||
if user is not None:
|
||
login(request, user)
|
||
logger.info(f"用户 {username} 登录成功, IP: {client_ip}")
|
||
messages.success(request, '登录成功!')
|
||
return redirect('index')
|
||
else:
|
||
logger.warning(f"用户 {username} 登录失败: 用户名或密码错误, IP: {client_ip}")
|
||
# 记录到syslog供fail2ban检测
|
||
syslog_logger.warning(f"Authentication failure for username: {username} from IP: {client_ip}")
|
||
messages.error(request, '用户名或密码错误,请重新尝试。')
|
||
|
||
return render(request, 'core/login.html')
|
||
|
||
# 注销视图
|
||
def user_logout(request):
|
||
"""用户注销"""
|
||
if request.user.is_authenticated:
|
||
logger.info(f"用户 {request.user.username} 注销")
|
||
logout(request)
|
||
messages.success(request, '已成功注销!')
|
||
|
||
return redirect('login')
|
||
|
||
# 公开内容视图
|
||
def public_content(request):
|
||
"""公开内容页面 - 无需登录"""
|
||
logger.info("用户访问公开内容页面")
|
||
|
||
if request.method == 'POST':
|
||
form = TempMessageForm(request.POST)
|
||
if form.is_valid():
|
||
message = form.save(commit=False)
|
||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||
if x_forwarded_for:
|
||
message.ip_address = x_forwarded_for.split(',')[0].strip()
|
||
else:
|
||
message.ip_address = request.META.get('REMOTE_ADDR')
|
||
from datetime import timedelta
|
||
message.expire_at = timezone.now() + timedelta(minutes=10)
|
||
message.save()
|
||
logger.info(f"临时留言: {message.username or '匿名'} - {message.content[:20]}...")
|
||
return redirect('public_content')
|
||
else:
|
||
form = TempMessageForm()
|
||
|
||
public_contents = PublicContent.objects.filter(is_published=True)
|
||
|
||
content_by_type = {}
|
||
for content in public_contents:
|
||
type_name = content.type.name
|
||
if type_name not in content_by_type:
|
||
content_by_type[type_name] = []
|
||
content_by_type[type_name].append(content)
|
||
|
||
temp_upload_form = TempUploadForm()
|
||
temp_messages = TempMessage.objects.filter(expire_at__gt=timezone.now())
|
||
|
||
context = {
|
||
'content_by_type': content_by_type,
|
||
'temp_upload_form': temp_upload_form,
|
||
'temp_messages': temp_messages,
|
||
'temp_message_form': form,
|
||
}
|
||
|
||
return render(request, 'core/public_content.html', context)
|
||
|
||
# 添加公开内容
|
||
@login_required
|
||
def add_public_content(request):
|
||
"""添加公开内容"""
|
||
if request.method == 'POST':
|
||
form = PublicContentForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
form.save()
|
||
logger.info(f"添加公开内容: {form.cleaned_data['title']}")
|
||
return redirect('public_content')
|
||
else:
|
||
form = PublicContentForm()
|
||
|
||
context = {'form': form}
|
||
return render(request, 'core/add_public_content.html', context)
|
||
|
||
# 编辑公开内容
|
||
@login_required
|
||
def edit_public_content(request, pk):
|
||
"""编辑公开内容"""
|
||
content = get_object_or_404(PublicContent, pk=pk)
|
||
if request.method == 'POST':
|
||
form = PublicContentForm(request.POST, request.FILES, instance=content)
|
||
if form.is_valid():
|
||
form.save()
|
||
logger.info(f"编辑公开内容: {form.cleaned_data['title']}")
|
||
return redirect('public_content')
|
||
else:
|
||
form = PublicContentForm(instance=content)
|
||
|
||
context = {'form': form, 'content': content}
|
||
return render(request, 'core/edit_public_content.html', context)
|
||
|
||
# 删除公开内容
|
||
@login_required
|
||
def delete_public_content(request, pk):
|
||
"""删除公开内容"""
|
||
content = get_object_or_404(PublicContent, pk=pk)
|
||
if request.method == 'POST':
|
||
content.delete()
|
||
logger.info(f"删除公开内容: {content.title}")
|
||
return redirect('public_content')
|
||
|
||
context = {'content': content}
|
||
return render(request, 'core/delete_public_content.html', context)
|