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="Test") # 尝试生成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)