diff --git a/core/admin.py b/core/admin.py index da9e09b..fbd798c 100644 --- a/core/admin.py +++ b/core/admin.py @@ -13,6 +13,8 @@ from .models import ( FamilyTask, TodayPlan, SystemConfig, + PublicContentType, + PublicContent, ) @@ -96,3 +98,17 @@ class TodayPlanAdmin(admin.ModelAdmin): @admin.register(SystemConfig) class SystemConfigAdmin(admin.ModelAdmin): list_display = ('smtp_server', 'smtp_port', 'smtp_username', 'recipient_email', 'send_time', 'created_at') + + +@admin.register(PublicContentType) +class PublicContentTypeAdmin(admin.ModelAdmin): + list_display = ('name', 'created_at', 'updated_at') + search_fields = ('name',) + + +@admin.register(PublicContent) +class PublicContentAdmin(admin.ModelAdmin): + list_display = ('title', 'type', 'is_published', 'sort_order', 'created_at') + list_filter = ('type', 'is_published') + search_fields = ('title', 'content') + ordering = ('sort_order', '-created_at') diff --git a/core/forms.py b/core/forms.py index 66e9639..66f0914 100644 --- a/core/forms.py +++ b/core/forms.py @@ -8,7 +8,8 @@ from .models import ( FamilyTask, TodayPlan, SystemConfig, - FamilyMember + FamilyMember, + PublicContent ) class ReadingRecordForm(forms.ModelForm): @@ -109,4 +110,19 @@ class SystemConfigForm(forms.ModelForm): if not re.match(email_pattern, recipient_email): logger.warning(f"收件人邮箱格式不正确: {recipient_email}") raise forms.ValidationError("请输入有效的邮箱地址,格式如:example@domain.com") - return recipient_email \ No newline at end of file + return recipient_email + + +class PublicContentForm(forms.ModelForm): + """公开内容表单""" + class Meta: + model = PublicContent + fields = ['type', 'title', 'content', 'file', 'url', 'sort_order', 'is_published'] + widgets = { + 'type': forms.Select(attrs={'class': 'form-select'}), + 'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入标题'}), + 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'placeholder': '请输入内容'}), + 'file': forms.FileInput(attrs={'class': 'form-control'}), + 'url': forms.URLInput(attrs={'class': 'form-control', 'placeholder': '请输入链接地址'}), + 'sort_order': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '请输入排序值'}), + } \ No newline at end of file diff --git a/core/models.py b/core/models.py index 914b283..3c8d24d 100644 --- a/core/models.py +++ b/core/models.py @@ -216,3 +216,37 @@ class SystemConfig(models.Model): """获取系统配置,单例模式""" config, created = cls.objects.get_or_create(pk=1) return config + +class PublicContentType(models.Model): + """公开内容类型""" + name = models.CharField(max_length=20, unique=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 = ['name'] + + def __str__(self): + return self.name + +class PublicContent(models.Model): + """公开内容表""" + type = models.ForeignKey(PublicContentType, on_delete=models.CASCADE, verbose_name="类型") + title = models.CharField(max_length=200, verbose_name="标题") + content = models.TextField(blank=True, null=True, verbose_name="内容") + file = models.FileField(upload_to='public_files/', blank=True, null=True, verbose_name="上传文件") + url = models.URLField(blank=True, null=True, verbose_name="链接地址") + sort_order = models.IntegerField(default=0, verbose_name="排序") + is_published = models.BooleanField(default=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 = ['sort_order', '-created_at'] + + def __str__(self): + return f"{self.type.name} - {self.title}" diff --git a/core/templates/core/add_public_content.html b/core/templates/core/add_public_content.html new file mode 100644 index 0000000..88729d2 --- /dev/null +++ b/core/templates/core/add_public_content.html @@ -0,0 +1,76 @@ +{% extends 'core/base.html' %} + +{% block content %} + +
+

+ 添加公开内容 +

+ + 返回 + +
+ +
+
+
+
+
+ 填写公开内容信息 +
+
+
+
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
+ {{ error }} +
+ {% endfor %} +
+ {% endfor %} + +
+ + + 取消 + +
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/base.html b/core/templates/core/base.html index 16625a7..5a156ba 100644 --- a/core/templates/core/base.html +++ b/core/templates/core/base.html @@ -449,6 +449,12 @@ 首页 + + {% if user.is_authenticated %} + {% endif %} @@ -538,7 +547,7 @@

- 代码最后更新:{% git_last_commit_time %} + 更新时间:{% git_last_commit_time %}

diff --git a/core/templates/core/delete_public_content.html b/core/templates/core/delete_public_content.html new file mode 100644 index 0000000..9c9337c --- /dev/null +++ b/core/templates/core/delete_public_content.html @@ -0,0 +1,48 @@ +{% extends 'core/base.html' %} + +{% block content %} + +
+

+ 删除公开内容 +

+
+ +
+
+
+
+ +
确认删除
+
+
+

您确定要删除以下公开内容吗?

+
+
+ {{ content.get_type_display }} +
+
{{ content.title }}
+ {% if content.content %} +

{{ content.content|truncatechars:100 }}

+ {% endif %} +
+

+ + 此操作不可撤销! +

+
+ {% csrf_token %} +
+ + + 取消 + +
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/edit_public_content.html b/core/templates/core/edit_public_content.html new file mode 100644 index 0000000..ec53e89 --- /dev/null +++ b/core/templates/core/edit_public_content.html @@ -0,0 +1,76 @@ +{% extends 'core/base.html' %} + +{% block content %} + +
+

+ 编辑公开内容 +

+ + 返回 + +
+ +
+
+
+
+
+ 修改公开内容信息 +
+
+
+
+ {% csrf_token %} + + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
+ {{ error }} +
+ {% endfor %} +
+ {% endfor %} + +
+ + + 取消 + +
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 5c294a8..93d063a 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,6 +1,7 @@ {% extends 'core/base.html' %} {% block content %} +{% if user.is_authenticated %}
@@ -372,4 +373,21 @@ setCurrentYear(); }); +{% else %} + +
+
+
+
+ +

欢迎使用家庭日报系统

+

请先登录以使用系统功能

+ + 登录系统 + +
+
+
+
+{% endif %} {% endblock %} diff --git a/core/templates/core/public_content.html b/core/templates/core/public_content.html new file mode 100644 index 0000000..a974d36 --- /dev/null +++ b/core/templates/core/public_content.html @@ -0,0 +1,80 @@ +{% extends 'core/base.html' %} + +{% block content %} + +
+

+ 公开内容 +

+ {% if user.is_authenticated %} +
+ + 添加内容 + +
+ {% endif %} +
+ +{% if content_by_type %} + {% for type_name, contents in content_by_type.items %} +
+
+
+ {{ type_name }} +
+ {{ contents|length }} 项 +
+
+
+ {% for content in contents %} +
+
+
+
+ {% if content.url %} + + {{ content.title }} + + {% else %} + {{ content.title }} + {% endif %} +
+ {% if content.content %} +

{{ content.content|truncatechars:200 }}

+ {% endif %} + {% if content.file %} + + 下载文件 + + {% endif %} +
+ {% if user.is_authenticated %} + + {% endif %} +
+
+ {% endfor %} +
+
+
+ {% endfor %} +{% else %} +
+ +
暂无公开内容
+

请稍后再来查看

+ {% if user.is_authenticated %} + + 添加内容 + + {% endif %} +
+{% endif %} +{% endblock %} diff --git a/core/urls.py b/core/urls.py index a7e193c..4eb95bf 100644 --- a/core/urls.py +++ b/core/urls.py @@ -68,4 +68,10 @@ urlpatterns = [ # 历史记录查询 path('history/', history_views.history_records, name='history_records'), path('history/pdf/', history_views.history_pdf, name='history_pdf'), + + # 公开内容 + path('public/', views.public_content, name='public_content'), + path('public/add/', views.add_public_content, name='add_public_content'), + path('public//edit/', views.edit_public_content, name='edit_public_content'), + path('public//delete/', views.delete_public_content, name='delete_public_content'), ] \ No newline at end of file diff --git a/core/views.py b/core/views.py index 8c72316..0bc17e3 100644 --- a/core/views.py +++ b/core/views.py @@ -2,7 +2,7 @@ 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 +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 @@ -48,7 +48,9 @@ from .models import ( TodayPlan, SystemConfig, FamilyMember, - SummaryCategory + SummaryCategory, + PublicContentType, + PublicContent ) from .forms import ( ReadingRecordForm, @@ -56,7 +58,8 @@ from .forms import ( SummaryForm, FamilyTaskForm, TodayPlanForm, - SystemConfigForm + SystemConfigForm, + PublicContentForm ) # 首页视图 @@ -75,8 +78,10 @@ def index(request): 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, @@ -400,30 +405,30 @@ def delete_summary(request, pk): 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( - models.Q(deadline__isnull=True) | models.Q(deadline__lte=one_month_later) + 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) # 添加家庭事项 @@ -969,3 +974,70 @@ def user_logout(request): messages.success(request, '已成功注销!') return redirect('login') + +# 公开内容视图 +def public_content(request): + """公开内容页面 - 无需登录""" + logger.info("用户访问公开内容页面") + # 获取所有已发布的公开内容 + 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) + + context = { + 'content_by_type': content_by_type, + } + + 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)