From 5d2ebbb8ea0f8308e28365acabdb9449bc22b617 Mon Sep 17 00:00:00 2001 From: unknown <18888073323@139.com> Date: Mon, 3 Nov 2025 14:22:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=A3=E7=A0=81=E7=B2=BE=E8=AF=BB?= =?UTF-8?q?=E4=B8=8E=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/admin.py | 6 ++++++ accounts/forms.py | 6 ++++++ accounts/models.py | 20 ++++++++++++++++--- accounts/urls.py | 5 +++++ accounts/views.py | 33 +++++++++++++++++++++++++++---- blog/admin.py | 23 +++++++++++++++++++++- blog/apps.py | 4 +++- blog/index.py | 13 +++++++++---- blog/models.py | 41 +++++++++++++++++++++++++++++++++++++-- blog/urls.py | 5 ++++- blog/views.py | 21 ++++++++++++++++++-- comments/admin.py | 22 ++++++++++++++++++++- comments/forms.py | 23 +++++++++++++++++----- comments/models.py | 22 ++++++++++++++++----- comments/urls.py | 27 ++++++++++++++++++++++++-- comments/views.py | 21 ++++++++++++++++---- mysite/urls.py | 19 ++---------------- templates/base.html | 13 ++++++------- templates/blog/index.html | 2 +- 19 files changed, 266 insertions(+), 60 deletions(-) diff --git a/accounts/admin.py b/accounts/admin.py index c0394f2..3134105 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -2,14 +2,20 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import CustomUser +# HJJ: 使用装饰器注册CustomUser模型到Django管理后台 @admin.register(CustomUser) class CustomUserAdmin(UserAdmin): + # HJJ: 指定管理的模型为CustomUser model = CustomUser + # HJJ: 定义管理后台列表页显示的字段 list_display = ['username', 'email', 'is_staff', 'date_joined'] + # HJJ: 定义管理后台可用的过滤器 list_filter = ['is_staff', 'is_active', 'date_joined'] + # HJJ: 在原有字段集基础上添加扩展信息字段集 fieldsets = UserAdmin.fieldsets + ( ('扩展信息', {'fields': ('bio', 'avatar', 'location', 'website')}), ) + # HJJ: 在添加用户表单中添加扩展信息字段集 add_fieldsets = UserAdmin.add_fieldsets + ( ('扩展信息', {'fields': ('bio', 'avatar', 'location', 'website')}), ) \ No newline at end of file diff --git a/accounts/forms.py b/accounts/forms.py index 206a2e0..92a2be8 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -2,12 +2,18 @@ from django import forms from django.contrib.auth.forms import UserCreationForm, UserChangeForm from .models import CustomUser +# HJJ: 自定义用户创建表单,继承Django内置的UserCreationForm class CustomUserCreationForm(UserCreationForm): class Meta: + # HJJ: 指定表单对应的模型为CustomUser model = CustomUser + # HJJ: 定义表单中包含的字段:用户名、邮箱、个人简介、头像、位置、网站 fields = ('username', 'email', 'bio', 'avatar', 'location', 'website') +# HJJ: 自定义用户信息修改表单,继承Django内置的UserChangeForm class CustomUserChangeForm(UserChangeForm): class Meta: + # HJJ: 指定表单对应的模型为CustomUser model = CustomUser + # HJJ: 定义表单中包含的字段:用户名、邮箱、个人简介、头像、位置、网站 fields = ('username', 'email', 'bio', 'avatar', 'location', 'website') \ No newline at end of file diff --git a/accounts/models.py b/accounts/models.py index 3cbe743..c31125e 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,16 +1,30 @@ from django.db import models from django.contrib.auth.models import AbstractUser + class CustomUser(AbstractUser): - """自定义用户模型""" + """自定义用户模型 + 继承Django内置的AbstractUser类,扩展用户基本信息 + """ + + # MYT:个人简介字段,最大长度500字符,可为空 bio = models.TextField(max_length=500, blank=True, verbose_name="个人简介") + + # MYT:头像字段,图片将上传到avatars/年/月/目录,可为空 avatar = models.ImageField(upload_to='avatars/%Y/%m/', blank=True, null=True, verbose_name="头像") + + # MYT:所在地字段,最大长度100字符,可为空 location = models.CharField(max_length=100, blank=True, verbose_name="所在地") + + # MYT:个人网站字段,URL格式,可为空 website = models.URLField(blank=True, verbose_name="个人网站") - + class Meta: + # MYT: 在Django admin中显示的单数名称 verbose_name = "用户" + # MYT:在Django admin中显示的复数名称 verbose_name_plural = "用户" - + def __str__(self): + """字符串表示方法,返回用户名用于显示""" return self.username \ No newline at end of file diff --git a/accounts/urls.py b/accounts/urls.py index 8623d50..b6541ca 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -2,9 +2,14 @@ from django.urls import path from django.contrib.auth import views as auth_views from . import views +# HJJ: 定义用户认证相关的URL路由配置 urlpatterns = [ + # HJJ: 用户注册页面路由,指向views.register视图函数 path('register/', views.register, name='register'), + # HJJ: 用户登录页面路由,指向views.user_login视图函数 path('login/', views.user_login, name='login'), + # HJJ: 用户登出路由,使用Django内置的LogoutView视图 path('logout/', auth_views.LogoutView.as_view(), name='logout'), + # HJJ: 用户个人资料页面路由,指向views.profile视图函数 path('profile/', views.profile, name='profile'), ] \ No newline at end of file diff --git a/accounts/views.py b/accounts/views.py index 004031e..096543c 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -5,33 +5,58 @@ from django.contrib.auth.decorators import login_required from .forms import CustomUserCreationForm def register(request): - """用户注册视图""" + """用户注册视图 + 处理用户注册流程,支持GET和POST请求 + GET:显示空注册表单 + POST:处理表单提交,验证并创建用户 + """ if request.method == 'POST': + # 实例化表单,包含POST数据和上传的文件(如头像) form = CustomUserCreationForm(request.POST, request.FILES) if form.is_valid(): + # 表单验证通过,保存用户到数据库 user = form.save() + # 注册成功后自动登录用户 login(request, user) + # 重定向到首页 return redirect('index') else: + # MYT:GET请求,创建空表单实例 form = CustomUserCreationForm() + # MYT:渲染注册模板,传递表单对象 return render(request, 'accounts/register.html', {'form': form}) def user_login(request): - """用户登录视图""" + """用户登录视图 + 处理用户登录认证流程 + GET:显示登录表单 + POST:验证用户凭证并登录 + """ if request.method == 'POST': + # MYT:实例化认证表单,包含请求和POST数据 form = AuthenticationForm(request, data=request.POST) if form.is_valid(): + # MYT:从清洗后的表单数据获取用户名和密码 username = form.cleaned_data.get('username') password = form.cleaned_data.get('password') + # MYT:认证用户凭证 user = authenticate(username=username, password=password) if user is not None: + # MYT:认证成功,登录用户 login(request, user) + # MYT:重定向到首页 return redirect('index') else: + # MYT:GET请求,创建空认证表单 form = AuthenticationForm() + # MYT:渲染登录模板,传递表单对象 return render(request, 'accounts/login.html', {'form': form}) -@login_required +@login_required # MYT:装饰器确保只有登录用户才能访问此视图 def profile(request): - """用户个人资料页面""" + """用户个人资料页面 + 显示当前登录用户的个人信息 + 需要用户登录后才能访问 + """ + # MYT:直接将请求中的用户对象传递给模板 return render(request, 'accounts/profile.html', {'user': request.user}) \ No newline at end of file diff --git a/blog/admin.py b/blog/admin.py index e104928..95e9e7a 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -1,38 +1,59 @@ from django.contrib import admin from .models import Category, PrimaryTag, SecondaryTag, Post +# HJH:使用装饰器注册Category模型到Django管理后台 @admin.register(Category) class CategoryAdmin(admin.ModelAdmin): + # HJH:定义在管理后台列表页面显示的字段 list_display = ['name', 'icon', 'order', 'description'] + # HJH:定义可以在列表页面直接编辑的字段,无需进入编辑页面 list_editable = ['order', 'icon'] + # HJH:定义可以通过搜索框搜索的字段 search_fields = ['name'] +# HJH:使用装饰器注册PrimaryTag模型到Django管理后台 @admin.register(PrimaryTag) class PrimaryTagAdmin(admin.ModelAdmin): + # HJH:定义在管理后台列表页面显示的字段 list_display = ['name', 'color'] + # HJH:定义可以通过搜索框搜索的字段 search_fields = ['name'] +# HJH:使用装饰器注册SecondaryTag模型到Django管理后台 @admin.register(SecondaryTag) class SecondaryTagAdmin(admin.ModelAdmin): + # HJH:定义在管理后台列表页面显示的字段 list_display = ['name', 'tag_type', 'parent_tag'] + # HJH:定义在列表页面右侧的过滤器,可以按这些字段快速筛选 list_filter = ['tag_type', 'parent_tag'] + # HJH:定义可以通过搜索框搜索的字段 search_fields = ['name'] +# HJH:使用装饰器注册Post模型到Django管理后台 @admin.register(Post) class PostAdmin(admin.ModelAdmin): + # HJH:定义在管理后台列表页面显示的字段 list_display = ['title', 'category', 'author', 'status', 'views', 'created_time'] + # HJH:定义在列表页面右侧的过滤器,可以按这些字段快速筛选 list_filter = ['category', 'status', 'created_time'] + # HJH:定义可以通过搜索框搜索的字段,支持标题和内容搜索 search_fields = ['title', 'content'] + # HJH:使用水平选择器显示多对多字段,方便用户选择多个标签 filter_horizontal = ['primary_tags', 'secondary_tags'] + # HJH:定义在编辑页面中只读的字段,用户不能修改这些字段 readonly_fields = ['views', 'created_time', 'updated_time'] + # HJH:定义字段分组显示,使编辑页面更加清晰有序 fieldsets = ( + # HJH:基本信息分组,包含文章的核心信息 ('基本信息', { 'fields': ('title', 'content', 'excerpt', 'author', 'category', 'featured_image') }), + # HJH:标签管理分组,包含一级和二级标签选择 ('标签管理', { 'fields': ('primary_tags', 'secondary_tags') }), + # HJH:状态信息分组,包含文章状态和统计信息 ('状态信息', { 'fields': ('status', 'views', 'created_time', 'updated_time') }), - ) + ) \ No newline at end of file diff --git a/blog/apps.py b/blog/apps.py index 94788a5..5dc812d 100644 --- a/blog/apps.py +++ b/blog/apps.py @@ -2,5 +2,7 @@ from django.apps import AppConfig class BlogConfig(AppConfig): + # HJH:设置默认主键字段类型为BigAutoField,支持更大的自增ID default_auto_field = 'django.db.models.BigAutoField' - name = 'blog' + # HJH:定义应用的名称,需要与settings.py中INSTALLED_APPS的配置一致 + name = 'blog' \ No newline at end of file diff --git a/blog/index.py b/blog/index.py index f6d38fa..d4879d1 100644 --- a/blog/index.py +++ b/blog/index.py @@ -3,25 +3,27 @@ from django.shortcuts import render from .models import Post, Category def index(request): - # 获取所有文章或根据需求筛选 + # LXC: 获取所有文章,按创建时间倒序排列,取前10篇 post_list = Post.objects.all().order_by('-created_time')[:10] - # 分类映射信息 + # LXC: 定义分类信息字典 category_info = { 'name': '非遗传承', 'label': '', 'desc': '探索南京非物质文化遗产的独特魅力与传承故事' } + # LXC: 构建上下文数据 context = { 'post_list': post_list, 'category_info': category_info, 'current_path': request.path } + # LXC: 渲染首页模板并返回响应 return render(request, 'blog/index.html', context) def category_view(request, category_id): - # 分类映射信息 + # LXC: 定义分类映射字典,包含每个分类的详细信息 category_map = { 1: {'name': '传统工艺', 'label': '巧夺天工·工艺', 'desc': '探索南京传统手工艺的精湛技艺与匠心传承'}, 2: {'name': '表演艺术', 'label': '梨园雅韵·表演', 'desc': '感受南京传统表演艺术的独特韵味与舞台魅力'}, @@ -30,15 +32,18 @@ def category_view(request, category_id): 5: {'name': '传承人物', 'label': '匠心传承·人物', 'desc': '认识南京非物质文化遗产的传承人与守护者'}, } + # LXC: 根据分类ID获取对应的分类信息,如果不存在则使用默认值 category_info = category_map.get(category_id, {'name': '非遗传承', 'label': '', 'desc': '探索南京非物质文化遗产的独特魅力'}) - # 根据分类ID获取对应的文章 + # LXC: 根据分类ID获取对应的文章,按创建时间倒序排列 posts = Post.objects.filter(category_id=category_id).order_by('-created_time') + # LXC: 构建上下文数据 context = { 'post_list': posts, 'category_info': category_info, 'current_path': request.path, 'current_category_id': category_id } + # LXC: 渲染分类页面模板并返回响应 return render(request, 'blog/index.html', context) \ No newline at end of file diff --git a/blog/models.py b/blog/models.py index 1550430..ef4efb9 100644 --- a/blog/models.py +++ b/blog/models.py @@ -3,33 +3,47 @@ from django.conf import settings # 新增导入 class Category(models.Model): """非遗分类""" + # HJH:分类名称字段,最大长度100字符,必须唯一,用于后台显示名称 name = models.CharField(max_length=100, unique=True, verbose_name="分类名称") + # HJH:分类描述字段,Text类型允许长文本,blank=True表示可为空 description = models.TextField(blank=True, verbose_name="分类描述") + # HJH:分类图标字段,存储图标字符或表情,默认使用🏮表情 icon = models.CharField(max_length=50, default="🏮", verbose_name="分类图标") + # HJH:显示顺序字段,整数类型,用于控制分类在前端的显示顺序 order = models.IntegerField(default=0, verbose_name="显示顺序") class Meta: + # HJH:设置模型在Django管理后台的单数显示名称 verbose_name = "非遗分类" + # HJH:设置模型在Django管理后台的复数显示名称 verbose_name_plural = "非遗分类" + # HJH:默认按order字段升序排列,数字小的排在前面 ordering = ['order'] def __str__(self): + # HJH:定义对象的字符串表示形式,在管理后台和shell中显示分类名称 return self.name class PrimaryTag(models.Model): """一级标签""" + # HJH:一级标签名称字段,最大长度100字符,必须唯一 name = models.CharField(max_length=100, unique=True, verbose_name="标签名称") + # HJH:标签颜色字段,存储十六进制颜色值,默认值为棕色#8b4513 color = models.CharField(max_length=7, default="#8b4513", verbose_name="标签颜色") class Meta: + # HJH:设置模型在Django管理后台的单数显示名称 verbose_name = "一级标签" + # HJH:设置模型在Django管理后台的复数显示名称 verbose_name_plural = "一级标签" def __str__(self): + # HJH:定义对象的字符串表示形式,返回标签名称 return self.name class SecondaryTag(models.Model): """二级标签""" + # HJH:定义二级标签的类型选择项,每个元组包含(数据库值, 显示名称) TAG_TYPE_CHOICES = [ ('geo', '地理空间'), ('theme', '主题维度'), @@ -38,41 +52,64 @@ class SecondaryTag(models.Model): ('time', '时间节庆'), ] + # HJH:二级标签名称字段,最大长度100字符 name = models.CharField(max_length=100, verbose_name="标签名称") + # HJH:标签类型字段,使用预定义的选择项,max_length=20足够存储所有选项 tag_type = models.CharField(max_length=20, choices=TAG_TYPE_CHOICES, verbose_name="标签类型") + # HJH:外键关联到一级标签,on_delete=CASCADE表示父标签删除时子标签也删除 parent_tag = models.ForeignKey(PrimaryTag, on_delete=models.CASCADE, null=True, blank=True, verbose_name="父级标签") class Meta: + # HJH:设置模型在Django管理后台的单数显示名称 verbose_name = "二级标签" + # HJH:设置模型在Django管理后台的复数显示名称 verbose_name_plural = "二级标签" def __str__(self): + # HJH:定义对象的字符串表示形式,返回标签名称和类型显示名称 return f"{self.name} ({self.get_tag_type_display()})" class Post(models.Model): """非遗文章""" + # HJH:定义文章状态的选择项,用于status字段 STATUS_CHOICES = [ ('draft', '草稿'), ('published', '已发布'), ] + # HJH:文章标题字段,最大长度200字符 title = models.CharField(max_length=200, verbose_name="文章标题") + # HJH:文章内容字段,Text类型支持长文本内容 content = models.TextField(verbose_name="文章内容") + # HJH:文章摘要字段,最大长度300字符,blank=True表示可为空 excerpt = models.TextField(max_length=300, blank=True, verbose_name="文章摘要") - author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="作者") # 修改这行 + # HJH:外键关联到用户模型,使用Django设置中的AUTH_USER_MODEL确保兼容性 + author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="作者") + # HJH:外键关联到分类模型,文章必须属于一个分类 category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name="分类") + # HJH:多对多关联到一级标签,blank=True表示可以为空标签集合 primary_tags = models.ManyToManyField(PrimaryTag, blank=True, verbose_name="一级标签") + # HJH:多对多关联到二级标签,blank=True表示可以为空标签集合 secondary_tags = models.ManyToManyField(SecondaryTag, blank=True, verbose_name="二级标签") + # HJH:特色图片字段,图片上传到posts/年/月/目录,可为空 featured_image = models.ImageField(upload_to='posts/%Y/%m/', blank=True, null=True, verbose_name="特色图片") + # HJH:阅读量字段,PositiveIntegerField确保非负整数,默认值为0 views = models.PositiveIntegerField(default=0, verbose_name="阅读量") + # HJH:文章状态字段,使用选择项,默认状态为'published'已发布 status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='published', verbose_name="状态") + # HJH:创建时间字段,auto_now_add=True在对象创建时自动设置当前时间 created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") + # HJH:更新时间字段,auto_now=True在对象保存时自动更新为当前时间 updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") class Meta: + # HJH:设置模型在Django管理后台的单数显示名称 verbose_name = "非遗文章" + # HJH:设置模型在Django管理后台的复数显示名称 verbose_name_plural = "非遗文章" + # HJH:默认按创建时间降序排列,最新的文章显示在最前面 ordering = ['-created_time'] def __str__(self): - return self.title + # HJH:定义对象的字符串表示形式,返回文章标题 + return self.title \ No newline at end of file diff --git a/blog/urls.py b/blog/urls.py index d9efe4c..6dd4492 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -1,9 +1,12 @@ -# blog/urls.py from django.urls import path from . import views +# LXC: 定义URL模式列表 urlpatterns = [ + # LXC: 首页URL,对应index视图函数 path('', views.index, name='index'), + # LXC: 分类页面URL,包含分类ID参数,对应category_view视图函数 path('category//', views.category_view, name='category'), + # LXC: 文章详情页URL,包含文章ID参数,对应detail视图函数 path('post//', views.detail, name='detail'), ] \ No newline at end of file diff --git a/blog/views.py b/blog/views.py index 9f862a9..f181d0c 100644 --- a/blog/views.py +++ b/blog/views.py @@ -4,9 +4,12 @@ from .models import Post, Category def index(request): """首页视图""" + # LXC: 获取已发布的文章,按创建时间倒序排列,取前10篇 post_list = Post.objects.filter(status='published').order_by('-created_time')[:10] + # LXC: 获取已发布的文章,按浏览量倒序排列,取前5篇作为热门文章 hot_posts = Post.objects.filter(status='published').order_by('-views')[:5] + # LXC: 构建上下文数据,包含文章列表、热门文章、分类信息等 context = { 'post_list': post_list, 'hot_posts': hot_posts, @@ -18,13 +21,17 @@ def index(request): 'current_path': request.path, 'current_category_id': None } + # LXC: 渲染首页模板并返回响应 return render(request, 'blog/index.html', context) def category_view(request, category_id): """分类页面视图""" + # LXC: 根据分类ID获取分类对象,如果不存在则返回404 category = get_object_or_404(Category, id=category_id) + # LXC: 获取热门文章列表 hot_posts = Post.objects.filter(status='published').order_by('-views')[:5] + # LXC: 定义分类映射字典,包含每个分类的标签和描述 category_map = { 1: {'label': '巧夺天工·工艺', 'desc': '探索南京传统手工艺的精湛技艺与匠心传承'}, 2: {'label': '梨园雅韵·表演', 'desc': '感受南京传统表演艺术的独特韵味与舞台魅力'}, @@ -33,13 +40,16 @@ def category_view(request, category_id): 5: {'label': '匠心传承·人物', 'desc': '认识南京非物质文化遗产的传承人与守护者'}, } + # LXC: 根据分类ID获取对应的分类信息,如果不存在则使用默认值 category_info = category_map.get(category_id, { 'label': category.name, 'desc': f'探索南京{category.name}的独特魅力' }) + # LXC: 获取该分类下已发布的文章,按创建时间倒序排列 posts = Post.objects.filter(category=category, status='published').order_by('-created_time') + # LXC: 构建上下文数据 context = { 'post_list': posts, 'hot_posts': hot_posts, @@ -52,24 +62,31 @@ def category_view(request, category_id): 'current_category_id': category_id } + # LXC: 渲染分类页面模板并返回响应 return render(request, 'blog/index.html', context) def detail(request, post_id): """文章详情页""" + # LXC: 根据文章ID获取已发布的文章对象,如果不存在则返回404 post = get_object_or_404(Post, id=post_id, status='published') + # LXC: 增加文章浏览量并保存到数据库 post.views += 1 post.save() - # 使用新的 related_name 查询 + # LXC: 导入评论模型和表单 from comments.models import Comment from comments.forms import CommentForm - comments = post.comments.all().order_by('-created_time') # 使用 comments 而不是 comment_set + # LXC: 获取该文章的所有评论,按创建时间倒序排列 + comments = post.comments.all().order_by('-created_time') + # LXC: 创建评论表单实例 comment_form = CommentForm() + # LXC: 构建上下文数据 context = { 'post': post, 'comments': comments, 'comment_form': comment_form, } + # LXC: 渲染文章详情页模板并返回响应 return render(request, 'blog/detail.html', context) \ No newline at end of file diff --git a/comments/admin.py b/comments/admin.py index e503dcc..713e48e 100644 --- a/comments/admin.py +++ b/comments/admin.py @@ -1,10 +1,30 @@ -# comments/admin.py +#zy:comments/admin.py +#zy: 评论(Comment)模型的管理后台配置文件 +#zy: 该文件用于配置Django管理后台中评论模型的显示和行为 + +#zy: 导入Django管理后台模块,用于注册模型和管理配置 from django.contrib import admin + +#zy: 从当前包(models模块)导入Comment模型 from .models import Comment +#zy: 使用装饰器注册Comment模型到管理后台,并指定使用CommentAdmin作为管理类 @admin.register(Comment) +#zy: 定义评论模型的管理配置类,继承自admin.ModelAdmin class CommentAdmin(admin.ModelAdmin): + #zy: 设置在管理后台列表页面显示的字段列 + # 将显示:作者(author)、关联文章(post)、创建时间(created_time) list_display = ['author', 'post', 'created_time'] + + # zy:设置右侧过滤侧边栏的过滤条件 + #zy: 允许用户按照创建时间(created_time)对评论进行筛选 list_filter = ['created_time'] + + # zy:设置搜索字段,在列表页顶部显示搜索框 + # zy:支持按作者用户名(author__username)和评论内容(content)进行搜索 + # author__username表示通过外键关系搜索作者的用户名字段 search_fields = ['author__username', 'content'] + + # zy:设置只读字段,在编辑页面中这些字段将显示为不可编辑状态 + # zy:创建时间(created_time)通常应该设为只读,防止用户修改 readonly_fields = ['created_time'] \ No newline at end of file diff --git a/comments/forms.py b/comments/forms.py index 978b930..ec9aab4 100644 --- a/comments/forms.py +++ b/comments/forms.py @@ -1,18 +1,31 @@ -# comments/forms.py +# zy:comments/forms.py +#zy:评论(Comment)模型的表单定义文件 +#zy: 该文件定义了用于创建和编辑评论的表单类 + +#zy: 导入Django表单模块,提供表单相关的基类和功能 from django import forms + +#zy: 从当前包(models模块)导入Comment模型 from .models import Comment +#zy: 定义评论表单类,继承自ModelForm,自动根据模型字段生成表单 class CommentForm(forms.ModelForm): + # zy:定义表单的元数据类,用于配置表单与模型的关联和行为 class Meta: + # zy:指定表单关联的模型为Comment model = Comment + #zy: 指定表单中包含的字段,这里只包含评论内容字段 fields = ['content'] + #zy: 配置字段的小部件(Widget)属性,控制表单字段的HTML渲染 widgets = { + # 为content字段配置Textarea文本域小部件 'content': forms.Textarea(attrs={ - 'rows': 4, - 'placeholder': '请输入您的评论...', - 'class': 'comment-textarea' + 'rows': 4, # 设置文本域行数为4行 + 'placeholder': '请输入您的评论...', # 设置占位符文本 + 'class': 'comment-textarea' # 设置CSS类名,用于样式控制 }) } + # zy:配置字段的标签显示 labels = { - 'content': '' + 'content': '' # zy:将content字段的标签设为空,不显示标签文本 } \ No newline at end of file diff --git a/comments/models.py b/comments/models.py index 58e53b3..70cace7 100644 --- a/comments/models.py +++ b/comments/models.py @@ -1,27 +1,39 @@ # comments/models.py +# DZQ: 评论模块的数据模型定义文件 + from django.db import models from django.conf import settings +# DZQ: 评论数据模型类 - 用于存储博客文章的评论信息 class Comment(models.Model): + # DZQ: 外键关联到博客文章,CASCADE表示文章删除时评论也删除 post = models.ForeignKey( 'blog.Post', on_delete=models.CASCADE, verbose_name="所属文章", - related_name="comments" # 添加 related_name + related_name="comments" #DZQ: 通过related_name可从文章对象反向查询评论 ) + + # DZQ: 外键关联到用户模型,CASCADE表示用户删除时评论也删除 author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="评论者", - related_name="comments" # 添加 related_name + related_name="comments" # DZQ: 通过related_name可从用户对象反向查询评论 ) + + # DZQ: 评论内容字段,使用TextField支持长文本 content = models.TextField(verbose_name="评论内容") + + # DZQ: 评论时间字段,auto_now_add=True表示创建时自动设置当前时间 created_time = models.DateTimeField(auto_now_add=True, verbose_name="评论时间") + # DZQ: 模型元数据配置类 class Meta: - verbose_name = "评论" - verbose_name_plural = "评论" - ordering = ['-created_time'] + verbose_name = "评论" # DZQ: 单数形式的显示名称 + verbose_name_plural = "评论" # DZQ: 复数形式的显示名称 + ordering = ['-created_time'] #DZQ: 默认按评论时间倒序排列 + #DZQ: 对象字符串表示方法,用于在admin等界面显示 def __str__(self): return f'{self.author.username} 评论了《{self.post.title}》' \ No newline at end of file diff --git a/comments/urls.py b/comments/urls.py index 3da70a5..58c59d8 100644 --- a/comments/urls.py +++ b/comments/urls.py @@ -1,9 +1,32 @@ -# comments/urls.py +# zy:comments/urls.py +# zy:评论(Comment)应用的URL路由配置文件 +# zy:该文件定义了评论功能的所有URL模式和对应的视图处理 + +#zy: 导入Django的URL路由配置模块,path函数用于定义URL模式 from django.urls import path + +#zy: 从当前包(views模块)导入所有视图函数或类 from . import views +# zy:定义应用的命名空间,用于URL反向解析时区分不同应用的相同名称URL +#zy: 当在模板中使用{% url 'comments:add_comment' post_id=1 %}时会指向此应用的add_comment URL app_name = 'comments' +# zy:定义URL模式列表,Django会按顺序匹配这些模式 +# zy:每个path()函数定义一个URL模式及其对应的视图 urlpatterns = [ - path('post//comment/', views.add_comment, name='add_comment'), + # zy:定义添加评论的URL模式 + path( + # URL路径模式,使用尖括号定义路径参数: + # - 'post/' 固定文本部分 + # - '' 路径转换器,匹配整数并将其作为post_id参数传递给视图 + # - '/comment/' 固定文本部分 + 'post//comment/', + + # zy:对应的视图函数,处理添加评论的请求 + views.add_comment, + + # zy:URL模式的名称,用于在模板和代码中进行反向解析 + name='add_comment' + ), ] \ No newline at end of file diff --git a/comments/views.py b/comments/views.py index f6806e1..d38de86 100644 --- a/comments/views.py +++ b/comments/views.py @@ -1,4 +1,6 @@ # comments/views.py +# DZQ: 评论模块的视图函数文件,处理评论相关的HTTP请求 + from django.shortcuts import get_object_or_404, redirect from django.contrib.auth.decorators import login_required from django.contrib import messages @@ -6,20 +8,31 @@ from .models import Comment from .forms import CommentForm from blog.models import Post -@login_required +# DZQ: 添加评论视图函数 - 处理用户提交评论的请求 +@login_required # DZQ: 登录要求装饰器,确保只有登录用户才能发表评论 def add_comment(request, post_id): """添加评论""" + # DZQ: 获取指定ID且状态为已发布的文章,如果不存在则返回404错误 post = get_object_or_404(Post, id=post_id, status='published') + # DZQ: 只处理POST请求,GET请求直接重定向 if request.method == 'POST': + # DZQ: 实例化评论表单,传入POST数据 form = CommentForm(request.POST) + + # DZQ: 表单验证通过的处理逻辑 if form.is_valid(): + # DZQ: commit=False表示先不保存到数据库,允许设置额外字段 comment = form.save(commit=False) - comment.post = post - comment.author = request.user - comment.save() + comment.post = post # DZQ: 设置评论关联的文章 + comment.author = request.user # DZQ: 设置评论作者为当前登录用户 + comment.save() # DZQ: 将评论保存到数据库 + + # DZQ: 添加成功消息,将在下次请求时显示给用户 messages.success(request, '评论发表成功!') else: + # DZQ: 表单验证失败,添加错误消息 messages.error(request, '评论发表失败,请检查输入内容。') + # DZQ: 重定向回文章详情页面,无论评论成功与否都返回文章页面 return redirect('blog:detail', post_id=post_id) \ No newline at end of file diff --git a/mysite/urls.py b/mysite/urls.py index 043358c..b9dd716 100644 --- a/mysite/urls.py +++ b/mysite/urls.py @@ -1,19 +1,4 @@ -""" -URL configuration for mysite project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/5.2/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')) -""" +# mysite/urls.py from django.contrib import admin from django.urls import include, path @@ -22,4 +7,4 @@ urlpatterns = [ path('', include('blog.urls')), path('accounts/', include('accounts.urls')), path('comments/', include('comments.urls', namespace='comments')), -] +] \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index eaab914..29c0a31 100644 --- a/templates/base.html +++ b/templates/base.html @@ -537,13 +537,12 @@ diff --git a/templates/blog/index.html b/templates/blog/index.html index ae44311..8279478 100644 --- a/templates/blog/index.html +++ b/templates/blog/index.html @@ -9,7 +9,7 @@