zh_第五周注释

pull/15/head
yyd 4 months ago
commit c02a58bf40

@ -1,3 +1,4 @@
<<<<<<< HEAD
from django import forms
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm
@ -57,3 +58,70 @@ class BlogUserAdmin(UserAdmin):
'source')
list_display_links = ('id', 'username')
ordering = ('-id',)
=======
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html # 用于安全地生成HTML内容
from django.utils.translation import gettext_lazy as _ # 用于国际化翻译
# 自定义批量操作:禁用评论状态
def disable_commentstatus(modeladmin, request, queryset):
queryset.update(is_enable=False) # 将选中的评论记录is_enable字段设为False
# 自定义批量操作:启用评论状态
def enable_commentstatus(modeladmin, request, queryset):
queryset.update(is_enable=True) # 将选中的评论记录is_enable字段设为True
# 为批量操作设置显示名称(支持国际化)
disable_commentstatus.short_description = _('Disable comments')
enable_commentstatus.short_description = _('Enable comments')
class CommentAdmin(admin.ModelAdmin):
list_per_page = 20 # 每页显示20条记录
list_display = (
'id',
'body', # 评论内容
'link_to_userinfo', # 自定义字段:链接到用户信息
'link_to_article', # 自定义字段:链接到文章
'is_enable', # 是否启用
'creation_time' # 创建时间
)
# 列表页中可点击跳转编辑页的字段
list_display_links = ('id', 'body', 'is_enable')
# 可筛选的字段(右侧过滤器)
list_filter = ('is_enable',)
# 编辑页排除的字段(不允许编辑,如自动生成的时间)
exclude = ('creation_time', 'last_modify_time')
# 注册自定义批量操作
actions = [disable_commentstatus, enable_commentstatus]
# 自定义列表字段:生成用户信息的编辑链接
def link_to_userinfo(self, obj):
# 获取用户模型的app标签和模型名称
info = (obj.author._meta.app_label, obj.author._meta.model_name)
# 生成用户编辑页的URL
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
# 返回带链接的HTML优先显示昵称无昵称则显示邮箱
return format_html(
u'<a href="%s">%s</a>' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
# 自定义列表字段:生成文章的编辑链接
def link_to_article(self, obj):
# 获取文章模型的app标签和模型名称
info = (obj.article._meta.app_label, obj.article._meta.model_name)
# 生成文章编辑页的URL
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
# 返回带链接的HTML显示文章标题
return format_html(
u'<a href="%s">%s</a>' % (link, obj.article.title))
# 自定义字段的显示名称(支持国际化)
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')
>>>>>>> zh_branch

@ -1,4 +1,5 @@
<<<<<<< HEAD
<<<<<<< HEAD
# 导入Django的AppConfig类用于配置Django应用的生命周期和元数据
from django.apps import AppConfig
@ -49,3 +50,12 @@ from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'
=======
from django.apps import AppConfig
class CommentsConfig(AppConfig):
# 配置应用的名称,对应项目中该应用的目录名)
# Django通过这个名称识别和管理该应用
name = 'comments'
>>>>>>> zh_branch

@ -1,79 +1,83 @@
{% load blog_tags %} // 加载自定义博客相关的自定义模板标签库
{% load cache %} // 加载缓存功能相关的模板标签库
{% load i18n %} // 加载国际化相关的模板标签库
{% load blog_tags %}
{% load cache %}
{% load i18n %}
<!-- 文章主体容器包含唯一ID和样式类 -->
<article id="post-{{ article.pk }} "
class="post-{{ article.pk }} post type-post status-publish format-standard hentry">// 加载国际化相关的模板标签库
class="post-{{ article.pk }} post type-post status-publish format-standard hentry">
<header class="entry-header"> <!-- 文章头部区域 -->
<header class="entry-header">// 文章头部区域,包含标题等信息
<h1 class="entry-title">// 文章标题元素
{% if isindex %} // 判断当前是否为索引页(文章列表页)
{% if article.article_order > 0 %} // 若为索引页判断文章是否有置顶顺序大于0表示置顶
<h1 class="entry-title"> <!-- 文章标题 -->
{% if isindex %} <!-- 判断是否为首页/列表页 -->
{% if article.article_order > 0 %} <!-- 若文章有置顶权重大于0 -->
<!-- 显示带"置顶"标识的标题链接 -->
<a href="{{ article.get_absolute_url }}"
rel="bookmark">【{% trans 'pin to top' %}】{{ article.title }}</a>// 显示带"置顶"标识的文章标题
rel="bookmark">【{% trans 'pin to top' %}】{{ article.title }}</a>
{% else %}
<!-- 普通标题链接 -->
<a href="{{ article.get_absolute_url }}"
rel="bookmark">{{ article.title }}</a>// 显示普通文章标题
rel="bookmark">{{ article.title }}</a>
{% endif %}
{% else %}
<!-- 非列表页直接显示标题(不添加链接) -->
{{ article.title }}
{% endif %}
</h1>
<div class="comments-link">
{% if article.comment_status == "o" and open_site_comment %}
<div class="comments-link"> <!-- 评论链接区域 -->
{% if article.comment_status == "o" and open_site_comment %} <!-- 若评论开启且网站允许评论 -->
<!-- 评论链接,指向文章详情页的评论区 -->
<a href="{{ article.get_absolute_url }}#comments" class="ds-thread-count" data-thread-key="3815"
rel="nofollow">// 链接添加nofollow属性告诉搜索引擎不要追踪此链接
<span class="leave-reply">// 评论数量显示容器
{% if article.comment_set and article.comment_set.count %}
{{ article.comment_set.count }} {% trans 'comments' %}// 检查是否有评论且评论数量存在
rel="nofollow">
<span class="leave-reply">
{% if article.comment_set and article.comment_set.count %} <!-- 若存在评论 -->
{{ article.comment_set.count }} {% trans 'comments' %} <!-- 显示评论数量 -->
{% else %}
{% trans 'comment' %}// 显示"comment"文本(国际化)
{% trans 'comment' %} <!-- 显示"评论"文本 -->
{% endif %}
</span>
</a>
{% endif %}
<div style="float:right">// 阅读量显示容器,设置右浮动
{{ article.views }} views// 显示文章阅读量和"views"文本
<!-- 右侧显示文章阅读量 -->
<div style="float:right">
{{ article.views }} views
</div>
</div><!-- .comments-link -->
<br/>
{% if article.type == 'a' %} // 检查文章类型是否为文章
{% if not isindex %} // 如果当前不是索引页(即文章详情页)
{% cache 36000 breadcrumb article.pk %}// 缓存面包屑导航有效期10小时36000秒以文章主键作为缓存键
{% load_breadcrumb article %}// 调用自定义标签加载文章的面包屑导航
{% if article.type == 'a' %} <!-- 若文章类型为普通文章(假设'a'代表文章) -->
{% if not isindex %} <!-- 非列表页时显示面包屑导航 -->
<!-- 缓存面包屑导航10小时36000秒以文章ID作为缓存键 -->
{% cache 36000 breadcrumb article.pk %}
{% load_breadcrumb article %} <!-- 调用自定义标签生成面包屑 -->
{% endcache %}
{% endif %}
{% endif %}
</header><!-- .entry-header -->
<div class="entry-content" itemprop="articleBody">
{% if isindex %}
{{ article.body|custom_markdown|escape|truncatechars_content }}// 显示经过markdown处理、转义并截断的文章内容
<div class="entry-content" itemprop="articleBody"> <!-- 文章内容区域,标记为文章主体 -->
{% if isindex %} <!-- 列表页显示 -->
<!-- 显示经过自定义markdown处理、转义并截断的内容 -->
{{ article.body|custom_markdown|escape|truncatechars_content }}
<!-- 阅读更多链接 -->
<p class='read-more'><a
href=' {{ article.get_absolute_url }}'>Read more</a></p>
{% else %}
{% if article.show_toc %}// 检查文章是否设置显示目录
{% get_markdown_toc article.body as toc %}// 调用自定义标签获取文章内容的markdown目录并赋值给toc变量
<b>{% trans 'toc' %}:</b>
{{ toc|safe }}// 安全地显示目录内容允许HTML渲染
<hr class="break_line"/>
{% else %} <!-- 详情页显示 -->
{% if article.show_toc %} <!-- 若文章设置显示目录 -->
<!-- 获取markdown内容中的目录 -->
{% get_markdown_toc article.body as toc %}
<b>{% trans 'toc' %}:</b> <!-- 显示"目录"标签 -->
{{ toc|safe }} <!-- 安全渲染目录HTML -->
<hr class="break_line"/> <!-- 分隔线 -->
{% endif %}
<div class="article">
{{ article.body|custom_markdown|escape }}// 显示经过markdown处理和转义的完整文章内容
<!-- 显示完整的经过markdown处理和转义的文章内容 -->
{{ article.body|custom_markdown|escape }}
</div>
{% endif %}
</div><!-- .entry-content -->
{% load_article_metas article user %}// 调用自定义标签加载文章的元数据(如作者、发布时间等),传入文章对象和用户对象
<!-- 加载文章元数据(如作者、发布时间、分类等),调用自定义标签 -->
{% load_article_metas article user %}
</article><!-- #post -->

@ -2,57 +2,62 @@
{% load blog_tags %}
<footer class="entry-meta">
{% trans 'posted in' %}
<footer class="entry-meta"> <!-- 文章元数据区域(底部信息栏) -->
{% trans 'posted in' %} <!-- 翻译为“发布于” -->
<!-- 文章所属分类链接 -->
<a href="{{ article.category.get_absolute_url }}" rel="category tag">{{ article.category.name }}</a>
<!-- 文章分类链接 -->
{% if article.type == 'a' %}
{% if article.tags.all %}
<!-- 如果是文章类型且有标签,则显示标签 -->
{% trans 'and tagged' %}
</a> <!-- 此处可能为多余闭合标签,需注意语法正确性 -->
{% if article.type == 'a' %} <!-- 若文章类型为普通文章(假设'a'代表文章) -->
{% if article.tags.all %} <!-- 若文章有关联标签 -->
{% trans 'and tagged' %} <!-- 翻译为“并标记为” -->
<!-- 循环输出所有标签 -->
{% for t in article.tags.all %}
<a href="{{ t.get_absolute_url }}" rel="tag">{{ t.name }}</a>
{% if t != article.tags.all.last %}
, <!-- 除最后一个标签外,其他标签后加逗号 -->
<a href="{{ t.get_absolute_url }}" rel="tag">{{ t.name }}</a> <!-- 标签链接 -->
{% if t != article.tags.all.last %} <!-- 除最后一个标签外,添加逗号分隔 -->
,
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
.{% trans 'by ' %}
<span class="by-author">
<span class="author vcard">
<a class="url fn n"
href="{{ article.author.get_absolute_url }}"
{% blocktranslate %}
title="View all articles published by {{ article.author.username }}"
{% endblocktranslate %}
rel="author">
<!-- 作者链接及信息 -->
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<!-- 使用schema.org定义作者信息 -->
<span itemprop="name">
<!-- 修正移除了重复的itemprop="publisher" -->
{{ article.author.username }}
</span>
</span>
</a> <!-- 修正闭合a标签 -->
.{% trans 'by ' %} <!-- 翻译为“作者:” -->
<span class="by-author"> <!-- 作者信息容器 -->
<span class="author vcard"> <!-- 符合hCard微格式的作者信息 -->
<!-- 作者主页链接 -->
<a class="url fn n" href="{{ article.author.get_absolute_url }}"
{% blocktranslate %} <!-- 国际化块,支持变量翻译 -->
title="View all articles published by {{ article.author.username }}" <!-- 鼠标悬停提示:查看该作者所有文章 -->
{% endblocktranslate %}
rel="author"> <!-- 标记为作者链接 -->
<!-- 符合Schema.org规范的作者信息 -->
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<span itemprop="name" itemprop="publisher"> <!-- 作者名称(同时作为发布者) -->
{{ article.author.username }} <!-- 显示作者用户名 -->
</span>
</span> <!-- 修正闭合by-author的span标签 -->
{% trans 'on' %}
<a href="{{ article.get_absolute_url }}"
title="{% datetimeformat article.pub_time %}"
itemprop="datePublished"
content="{% datetimeformat article.pub_time %}"
rel="bookmark">
<!-- 文章发布时间链接 -->
<time class="entry-date updated" datetime="{{ article.pub_time }}">
{% datetimeformat article.pub_time %}
</time>
{% if user.is_superuser %}
<a href="{{ article.get_admin_url }}">{% trans 'edit' %}</a>
<!-- 管理员编辑链接 -->
{% endif %}
</a> <!-- 修正闭合时间链接的a标签 -->
</footer><!-- .entry-meta -->
</span>
</a>
</span>
{% trans 'on' %} <!-- 翻译为“发布时间:” -->
<!-- 文章详情页链接(带发布时间信息) -->
<a href="{{ article.get_absolute_url }}"
title="{% datetimeformat article.pub_time %}" <!-- 鼠标悬停显示格式化的发布时间 -->
itemprop="datePublished" content="{% datetimeformat article.pub_time %}" <!-- Schema.org发布时间属性 -->
rel="bookmark"> <!-- 标记为永久链接 -->
<!-- 发布时间标签符合HTML5时间格式 -->
<time class="entry-date updated"
datetime="{{ article.pub_time }}"> <!-- datetime属性为机器可读格式 -->
{% datetimeformat article.pub_time %}</time> <!-- 显示格式化的发布时间(调用自定义标签) -->
{% if user.is_superuser %} <!-- 若当前用户是超级管理员 -->
<a href="{{ article.get_admin_url }}">{% trans 'edit' %}</a> <!-- 显示编辑链接(指向后台编辑页) -->
{% endif %}
</span>
</footer><!-- .entry-meta --> <!-- 元数据区域结束 -->

@ -1,21 +1,28 @@
{% load i18n %}
<nav id="nav-below" class="navigation" role="navigation"> // 定义文章导航的 nav 元素
<h3 class="assistive-text"> // 辅助性标题,用于无障碍访问等
{% trans 'article navigation' %} // 翻译显示“文章导航”
<!-- 文章导航区域,用于分页导航 -->
<nav id="nav-below" class="navigation" role="navigation">
<h3 class="assistive-text">
{% trans 'article navigation' %} <!-- 翻译为“文章导航”,供辅助设备识别 -->
</h3>
{% if page_obj.has_next and next_url %} // 判断是否有下一页且存在下一页 URL
<!-- 若存在下一页且有下一页URL显示“更早的文章”链接 -->
{% if page_obj.has_next and next_url %}
<div class="nav-previous">
<a href="{{ next_url }}"> // 链接到下一页 URL
<span class="meta-nav">&larr;</span> {% trans 'earlier articles' %} // 翻译显示“更早的文章”
<a href="{{ next_url }}">
<span class="meta-nav">&larr;</span> <!-- 左箭头图标 -->
{% trans 'earlier articles' %} <!-- 翻译为“更早的文章” -->
</a>
</div>
{% endif %}
{% if page_obj.has_previous and previous_url %} // 判断是否有上一页且存在上一页 URL
<!-- 若存在上一页且有上一页URL显示“更新的文章”链接 -->
{% if page_obj.has_previous and previous_url %}
<div class="nav-next">
<a href="{{ previous_url }}"> // 链接到上一页 URL
{% trans 'newer articles' %} // 翻译显示“更新的文章”
<span class="meta-nav"></span>
<a href="{{ previous_url }}">
{% trans 'newer articles' %} <!-- 翻译为“更新的文章” -->
<span class="meta-nav"></span> <!-- 右箭头图标 -->
</a>
</div>
{% endif %}
</nav> <!-- .navigation --> // 结束 nav 元素,注释说明是导航部分
</nav><!-- .navigation --> <!-- 导航区域结束 -->

@ -1,33 +1,20 @@
{% load i18n %} <!-- 加载国际化标签库,用于实现多语言支持 -->
<!-- 仅当存在标签数据时才渲染整个标签面板 -->
{% if article_tags_list %}
<!-- 标签面板容器使用Bootstrap的panel组件样式 -->
<div class="panel panel-default">
<!-- 面板标题栏 -->
<div class="panel-heading">
{% trans 'tags' %} <!-- 显示"标签"文本,支持多语言翻译 -->
{% load i18n %}
{% if article_tags_list %} <!-- 判断文章标签列表是否存在,存在则渲染标签面板 -->
<div class="panel panel-default"> <!-- 标签面板容器,使用默认样式的面板组件 -->
<div class="panel-heading"> <!-- 面板头部区域,显示标题 -->
{% trans 'tags' %} <!-- 翻译“tags”为对应语言如中文“标签”作为面板标题 -->
</div>
<div class="panel-body"> <!-- 面板内容区域,用于放置标签列表 -->
<!-- 面板内容区域 -->
<div class="panel-body">
<!-- 循环遍历标签列表数据
每个标签包含: 链接(url)、文章数量(count)、标签对象(tag)、样式颜色(color) -->
{% for url, count, tag, color in article_tags_list %}
<!-- 标签链接元素
- 使用label-{{ color }}动态应用不同颜色样式
- 添加margin样式使标签间保持适当间距
- title属性显示该标签下的文章数量支持复数形式翻译 -->
<a class="label label-{{ color }}"
style="display: inline-block; margin: 0 5px 5px 0;"
href="{{ url }}"
title="{% blocktranslate with tag_name=tag.name count=count %}
{{ count }} article tagged with {{ tag_name }}
{% endblocktranslate %}">
{% for url,count,tag,color in article_tags_list %} <!-- 循环遍历标签列表,获取每个标签的链接、数量、标签对象、颜色 -->
<!-- 标签链接使用label组件样式颜色由循环变量color控制 -->
<a class="label label-{{ color }}" style="display: inline-block;" href="{{ url }}"
title="{{ tag.name }}"> <!-- 鼠标悬停时显示标签名称 -->
{{ tag.name }} <!-- 显示标签名称 -->
<span class="badge">{{ count }}</span> <!-- 显示该标签关联的文章数量(徽章样式) -->
<span class="badge">{{ count }}</span> <!-- 显示该标签下的文章数量用badge组件样式包裹 -->
</a>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %} <!-- 标签循环结束 -->
</div> <!-- 面板内容区域结束 -->
</div> <!-- 标签面板容器结束 -->
{% endif %} <!-- 标签列表存在判断结束 -->

@ -1,33 +1,25 @@
<!-- 面包屑导航列表使用Schema.org规范标记提升SEO和结构化数据识别 -->
<ul itemscope itemtype="https://schema.org/BreadcrumbList" class="breadcrumb">
<!--
面包屑导航容器使用schema.org的BreadcrumbList类型定义结构化数据
class="breadcrumb"通常用于Bootstrap样式的面包屑导航
-->
{% for name,url in names %}
<!-- 循环输出面包屑导航的每一级(除最后一级) -->
{% for name,url in names %} <!-- 循环遍历面包屑导航的每一级(除最后一级) -->
<!-- 每一级导航项符合Schema.org的ListItem类型 -->
<li itemprop="itemListElement" itemscope
itemtype="https://schema.org/ListItem">
<!-- 每一项使用schema.org的ListItem类型定义 -->
<!-- 导航项链接itemprop="item"标记链接地址 -->
<a href="{{ url }}" itemprop="item" >
<!-- 链接地址itemprop="item"关联到具体资源 -->
<span itemprop="name">{{ name }}</span>
<!-- 显示的名称itemprop="name"定义列表项名称 -->
</a>
<span itemprop="name">{{ name }}</span></a> <!-- 导航项名称itemprop="name"标记 -->
<!-- 标记当前导航项在列表中的位置从1开始 -->
<meta itemprop="position" content="{{ forloop.counter }}"/>
<!-- 元数据当前项在面包屑中的位置序号forloop.counter获取循环次数 -->
</li>
{% endfor %}
{% endfor %} <!-- 导航项循环结束 -->
<!-- 面包屑最后一级(当前页面),通常显示为激活状态 -->
<!-- 面包屑最后一级(当前页面),添加active类表示激活状态 -->
<li class="active" itemprop="itemListElement" itemscope
itemtype="https://schema.org/ListItem">
<span itemprop="name">{{ title }}</span>
<!-- 当前页面标题,无链接 -->
<span itemprop="name">{{ title }}</span> <!-- 当前页面名称,无链接 -->
<!-- 标记最后一级在列表中的位置由count变量指定 -->
<meta itemprop="position" content="{{ count }}"/>
<!-- 元数据:当前项在面包屑中的位置(总计数) -->
</li>
</ul>
</ul> <!-- 面包屑导航列表结束 -->

@ -1,4 +1,5 @@
from django import forms
<<<<<<< HEAD
from django.contrib.auth import get_user_model, password_validation
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.exceptions import ValidationError
@ -115,3 +116,19 @@ class ForgetPasswordCodeForm(forms.Form):
email = forms.EmailField(
label=_('Email'),
)
=======
from django.forms import ModelForm
from .models import Comment
class CommentForm(ModelForm): # 定义评论表单类继承自ModelForm
# 添加父评论ID字段用于实现评论回复功能
# 使用HiddenInput控件隐藏显示且非必填顶级评论无需父ID
parent_comment_id = forms.IntegerField(
widget=forms.HiddenInput, required=False)
class Meta: # Meta类用于配置表单与模型的关联信息
model = Comment # 指定表单对应的模型为Comment
fields = ['body'] # 表单需要包含的模型字段这里只包含评论内容body
>>>>>>> zh_branch

@ -1,3 +1,4 @@
<<<<<<< HEAD
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
@ -33,3 +34,43 @@ class BlogUser(AbstractUser):
verbose_name = _('user')
verbose_name_plural = verbose_name
get_latest_by = 'id'
=======
from django.conf import settings
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from blog.models import Article
# 评论模型,存储用户对文章的评论及评论间的嵌套关系
class Comment(models.Model):
body = models.TextField('正文', max_length=300) # 评论内容限制最大长度300字符
creation_time = models.DateTimeField(_('creation time'), default=now) # 评论创建时间,默认当前时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now) # 评论最后修改时间,默认当前时间
author = models.ForeignKey(
settings.AUTH_USER_MODEL, # 关联Django内置用户模型便于扩展用户系统
verbose_name=_('author'),
on_delete=models.CASCADE) # 级联删除:用户删除时,其评论也会被删除
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE) # 级联删除:文章删除时,其下所有评论也会被删除
parent_comment = models.ForeignKey(
'self', # 自关联,实现评论嵌套回复功能
verbose_name=_('parent comment'),
blank=True,
null=True, # 允许为空,表示该评论是顶级评论(不是回复)
on_delete=models.CASCADE) # 级联删除:父评论删除时,其所有子评论也会被删除
is_enable = models.BooleanField(_('enable'),
default=False, blank=False, null=False) # 评论是否启用(可用于审核功能)
class Meta:
ordering = ['-id'] # 默认按ID降序排列最新评论显示在前面
verbose_name = _('comment')
verbose_name_plural = verbose_name
get_latest_by = 'id' # 指定通过id字段获取最新记录
def __str__(self):
return self.body
>>>>>>> zh_branch

@ -1,223 +1,184 @@
{% load blog_tags %} <!-- 加载博客项目自定义的模板标签如处理Markdown的custom_markdown -->
{% load i18n %} <!-- 加载Django多语言工具用于后续文本的多语言切换 -->
<!-- 侧边栏容器class和role用于样式控制与无障碍访问表明该区域是辅助内容 -->
{% load blog_tags %} <!-- 加载自定义博客相关模板标签 -->
{% load i18n %} <!-- 加载国际化标签,支持多语言翻译 -->
<!-- 网站侧边栏容器role="complementary"标识为辅助内容区域 -->
<div id="secondary" class="widget-area" role="complementary">
<!-- 搜索框组件id和class用于定位与样式表明这是搜索类小部件 -->
<!-- 搜索框组件 -->
<aside id="search-2" class="widget widget_search">
<!-- 搜索表单,提交方式为GET数据提交到/search路径role="search"增强无障碍 -->
<!-- 搜索表单,请求方式为GET提交到/search路径 -->
<form role="search" method="get" id="searchform" class="searchform" action="/search">
<div>
<!-- 搜索框标签screen-reader-text类用于仅让屏幕阅读器识别不显示在页面 -->
<!-- 供屏幕阅读器识别的搜索标签,视觉上隐藏 -->
<label class="screen-reader-text" for="s">{% trans 'search' %}</label>
<!-- 搜索关键词输入框name="q"供后端获取关键词,初始值为空 -->
<!-- 搜索输入框name为"q"用于后端接收搜索关键词 -->
<input type="text" value="" name="q" id="q"/>
<!-- 搜索提交按钮id用于样式或JS绑定点击事件 -->
<!-- 搜索提交按钮 -->
<input type="submit" id="searchsubmit" />
</div>
</form>
</aside>
<!-- 条件判断若存在自定义侧边栏数据extra_sidebars则渲染该组件 -->
<!-- 额外侧边栏内容(若存在) -->
{% if extra_sidebars %}
<!-- 循环遍历所有自定义侧边栏,逐个渲染 -->
{% for sidebar in extra_sidebars %}
<!-- 自定义侧边栏容器class表明这是文本/HTML类小部件 -->
{% for sidebar in extra_sidebars %} <!-- 循环遍历所有额外侧边栏 -->
<!-- 自定义HTML侧边栏组件 -->
<aside class="widget_text widget widget_custom_html">
<!-- 自定义侧边栏标题,显示用户设置的侧边栏名称 -->
<p class="widget-title">{{ sidebar.name }}</p>
<!-- 自定义侧边栏内容容器,用于包裹处理后的内容 -->
<p class="widget-title">{{ sidebar.name }}</p> <!-- 侧边栏标题 -->
<div class="textwidget custom-html-widget">
<!-- 将侧边栏内容用custom_markdown转为HTML|safe允许渲染HTML代码需确保内容安全 -->
<!-- 渲染侧边栏内容经过自定义markdown处理并允许安全HTML -->
{{ sidebar.content|custom_markdown|safe }}
</div>
</aside>
{% endfor %}
{% endif %}
<!-- 条件判断若存在热门文章数据most_read_articles则渲染该组件 -->
<!-- 热门阅读文章(若存在) -->
{% if most_read_articles %}
<!-- 热门文章组件容器id和class用于定位与样式 -->
<aside id="views-4" class="widget widget_views">
<!-- 热门文章标题固定显示“Views”可改为多语言标签 -->
<p class="widget-title">Views</p>
<!-- 热门文章列表容器 -->
<p class="widget-title">Views</p> <!-- 组件标题(阅读量) -->
<ul>
<!-- 循环遍历热门文章数据,逐个渲染文章项 -->
{% for a in most_read_articles %}
{% for a in most_read_articles %} <!-- 循环遍历热门文章 -->
<li>
<!-- 文章标题链接调用文章模型的get_absolute_url生成详情页地址title属性为文章标题鼠标悬浮显示 -->
<!-- 文章链接,标题为文章标题 -->
<a href="{{ a.get_absolute_url }}" title="{{ a.title }}">
{{ a.title }} <!-- 显示文章标题 -->
</a> - {{ a.views }} views <!-- 显示文章阅读量后缀“views”为固定文本 -->
{{ a.title }}
</a> - {{ a.views }} views <!-- 显示文章阅读量 -->
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
<!-- 条件判断若存在分类数据sidebar_categorys则渲染该组件 -->
<!-- 文章分类侧边栏(若存在) -->
{% if sidebar_categorys %}
<!-- 分类组件容器id和class用于定位与样式 -->
<aside id="su_siloed_terms-2" class="widget widget_su_siloed_terms">
<!-- 分类标题,用{% trans 'category' %}实现多语言中文显示“分类”英文显示“Category” -->
<p class="widget-title">{% trans 'category' %}</p>
<!-- 分类列表容器 -->
<p class="widget-title">{% trans 'category' %}</p> <!-- 分类标题(多语言) -->
<ul>
<!-- 循环遍历分类数据,逐个渲染分类项 -->
{% for c in sidebar_categorys %}
<!-- 分类项cat-item类用于样式每个分类项是一个链接 -->
{% for c in sidebar_categorys %} <!-- 循环遍历分类 -->
<li class="cat-item cat-item-184">
<!-- 分类名称链接调用分类模型的get_absolute_url生成分类页地址 -->
<!-- 分类链接,指向分类详情页 -->
<a href={{ c.get_absolute_url }}>{{ c.name }}</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
<!-- 条件判断若存在评论数据sidebar_comments且开启网站评论open_site_comment则渲染该组件 -->
<!-- 最新评论(若存在且网站允许评论) -->
{% if sidebar_comments and open_site_comment %}
<!-- 最新评论组件容器id和class用于定位与样式 -->
<aside id="ds-recent-comments-4" class="widget ds-widget-recent-comments">
<!-- 最新评论标题,用{% trans 'recent comments' %}实现多语言中文“最新评论”英文“Recent Comments” -->
<p class="widget-title">{% trans 'recent comments' %}</p>
<!-- 最新评论列表容器id用于定位 -->
<p class="widget-title">{% trans 'recent comments' %}</p> <!-- 最新评论标题 -->
<ul id="recentcomments">
<!-- 循环遍历评论数据,逐个渲染评论项 -->
{% for c in sidebar_comments %}
{% for c in sidebar_comments %} <!-- 循环遍历最新评论 -->
<li class="recentcomments">
<!-- 评论作者名称,显示评论用户的用户名 -->
<span class="comment-author-link">{{ c.author.username }}</span>
<!-- 评论归属文本,用{% trans 'published on' %}实现多语言中文“发表于”英文“published on” -->
{% trans 'published on' %}《
<!-- 评论所属文章链接,生成文章详情页地址并通过#comment-{{ c.pk }}锚点定位到该评论 -->
<span class="comment-author-link">{{ c.author.username }}</span> <!-- 评论作者 -->
{% trans 'published on' %}《 <!-- 翻译为“发表于” -->
<!-- 链接到评论所在的文章,锚点定位到具体评论 -->
<a href="{{ c.article.get_absolute_url }}#comment-{{ c.pk }}">{{ c.article.title }}</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
<!-- 条件判断若存在最新文章数据recent_articles则渲染该组件 -->
<!-- 最新文章(若存在) -->
{% if recent_articles %}
<!-- 最新文章组件容器id和class用于定位与样式 -->
<aside id="recent-posts-2" class="widget widget_recent_entries">
<!-- 最新文章标题,用{% trans 'recent articles' %}实现多语言中文“最新文章”英文“Recent Articles” -->
<p class="widget-title">{% trans 'recent articles' %}</p>
<!-- 最新文章列表容器 -->
<p class="widget-title">{% trans 'recent articles' %}</p> <!-- 最新文章标题 -->
<ul>
<!-- 循环遍历最新文章数据,逐个渲染文章项 -->
{% for a in recent_articles %}
{% for a in recent_articles %} <!-- 循环遍历最新文章 -->
<li>
<!-- 文章标题链接生成详情页地址title属性为文章标题鼠标悬浮显示 -->
<!-- 文章链接,标题为文章标题 -->
<a href="{{ a.get_absolute_url }}" title="{{ a.title }}">
{{ a.title }} <!-- 显示文章标题 -->
{{ a.title }}
</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
<!-- 条件判断若存在书签链接数据sidabar_links注意此处变量名可能为笔误应为sidebar_links则渲染该组件 -->
<!-- 收藏链接(若存在) -->
{% if sidabar_links %}
<!-- 书签链接组件容器id和class用于定位与样式 -->
<aside id="linkcat-0" class="widget widget_links">
<!-- 书签链接标题,用{% trans 'bookmark' %}实现多语言中文“书签”英文“Bookmark” -->
<p class="widget-title">{% trans 'bookmark' %}</p>
<!-- 书签链接列表容器class用于样式 -->
<p class="widget-title">{% trans 'bookmark' %}</p> <!-- 收藏标题 -->
<ul class='xoxo blogroll'>
<!-- 循环遍历书签链接数据,逐个渲染链接项 -->
{% for l in sidabar_links %}
{% for l in sidabar_links %} <!-- 循环遍历收藏链接 -->
<li>
<!-- 书签链接target="_blank"表示在新窗口打开title属性为书签名称鼠标悬浮显示 -->
<!-- 收藏链接,新窗口打开,标题为链接名称 -->
<a href="{{ l.link }}" target="_blank" title="{{ l.name }}">{{ l.name }}</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
<!-- 条件判断若开启Google广告show_google_adsense则渲染该组件 -->
<!-- Google广告若开启 -->
{% if show_google_adsense %}
<!-- Google广告组件容器id和class用于定位与样式 -->
<aside id="text-2" class="widget widget_text">
<!-- 广告标题固定显示“Google AdSense” -->
<p class="widget-title">Google AdSense</p>
<!-- 广告内容容器 -->
<p class="widget-title">Google AdSense</p> <!-- 广告标题 -->
<div class="textwidget">
<!-- 渲染Google广告代码|safe允许执行广告的HTML/JS代码需确保代码来源安全 -->
<!-- 渲染Google广告代码允许安全HTML -->
{{ google_adsense_codes|safe }}
</div>
</aside>
{% endif %}
<!-- 条件判断若存在标签数据sidebar_tags则渲染该组件 -->
<!-- 标签云(若存在) -->
{% if sidebar_tags %}
<!-- 标签云组件容器id和class用于定位与样式 -->
<aside id="tag_cloud-2" class="widget widget_tag_cloud">
<!-- 标签云标题,用{% trans 'Tag Cloud' %}实现多语言中文“标签云”英文“Tag Cloud” -->
<p class="widget-title">{% trans 'Tag Cloud' %}</p>
<!-- 标签云容器,用于包裹所有标签 -->
<p class="widget-title">{% trans 'Tag Cloud' %}</p> <!-- 标签云标题 -->
<div class="tagcloud">
<!-- 循环遍历标签数据tag=标签对象count=标签关联文章数size=标签字体大小) -->
{% for tag,count,size in sidebar_tags %}
<!-- 标签链接生成标签页地址class用于样式title属性显示标签关联文章数 -->
{% for tag,count,size in sidebar_tags %} <!-- 循环遍历标签,获取标签、数量、字体大小 -->
<!-- 标签链接字体大小由size变量控制标题显示标签下文章数量 -->
<a href="{{ tag.get_absolute_url }}"
class="tag-link-{{ tag.id }} tag-link-position-{{ tag.id }}"
style="font-size: {{ size }}pt;" <!-- 动态设置标签字体大小,关联文章越多字体越大 -->
title="{{ count }}个话题"> {{ tag.name }} <!-- 显示标签名称title属性文本可改为多语言 -->
style="font-size: {{ size }}pt;" title="{{ count }}个话题"> {{ tag.name }}
</a>
{% endfor %}
</div>
</aside>
{% endif %}
<!-- GitHub仓库推广组件容器id和class用于定位与样式id="text-2"与广告组件重复建议修改为唯一id -->
<!-- 网站源码Star/Fork提示 -->
<aside id="text-2" class="widget widget_text">
<!-- 推广标题,用{% trans %}实现多语言中文“欢迎Star或Fork本网站源码”英文对应翻译 -->
<p class="widget-title">{% trans 'Welcome to star or fork the source code of this site' %}</p>
<!-- 推广内容容器 -->
<p class="widget-title">{% trans 'Welcome to star or fork the source code of this site' %}</p> <!-- 多语言提示文本 -->
<div class="textwidget">
<p>
<!-- GitHub Star按钮链接指向项目仓库rel="nofollow"避免搜索引擎追踪 -->
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow">
<!-- 加载GitHub Star数量图标实时显示项目Star数 -->
<img src="https://resource.lylinux.net/img.shields.io/github/stars/liangliangyy/djangoblog.svg?style=social&amp;label=Star" alt="GitHub stars">
</a>
<!-- GitHub Fork按钮链接指向项目仓库 -->
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow">
<!-- 加载GitHub Fork数量图标实时显示项目Fork数 -->
<img src="https://resource.lylinux.net/img.shields.io/github/forks/liangliangyy/djangoblog.svg?style=social&amp;label=Fork" alt="GitHub forks">
</a>
<!-- GitHub Star按钮图片链接 -->
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
src="https://resource.lylinux.net/img.shields.io/github/stars/liangliangyy/djangoblog.svg?style=social&amp;label=Star"
alt="GitHub stars"></a>
<!-- GitHub Fork按钮图片链接 -->
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
src="https://resource.lylinux.net/img.shields.io/github/forks/liangliangyy/djangoblog.svg?style=social&amp;label=Fork"
alt="GitHub forks"></a>
</p>
</div>
</aside>
<!-- 功能链接组件容器id和class用于定位与样式 -->
<!-- 功能链接侧边栏 -->
<aside id="meta-3" class="widget widget_meta">
<!-- 功能链接标题,用{% trans 'Function' %}实现多语言中文“功能”英文“Function” -->
<p class="widget-title">{% trans 'Function' %}</p>
<!-- 功能链接列表容器 -->
<p class="widget-title">{% trans 'Function' %}</p> <!-- 功能标题 -->
<ul>
<!-- 管理后台链接,固定指向/admin/路径rel="nofollow"避免搜索引擎追踪 -->
<!-- 后台管理系统链接 -->
<li><a href="/admin/" rel="nofollow">{% trans 'management site' %}</a></li>
<!-- 条件判断若用户已登录user.is_authenticated显示“退出”链接 -->
{% if user.is_authenticated %}
<li>
<!-- 退出链接,通过{% url "account:logout" %}生成退出接口地址 -->
<a href="{% url "account:logout" %}" rel="nofollow">{% trans 'logout' %}</a>
</li>
<!-- 若用户未登录,显示“登录”链接 -->
{% if user.is_authenticated %} <!-- 若用户已登录 -->
<!-- 登出链接 -->
<li><a href="{% url "account:logout" %}" rel="nofollow">{% trans 'logout' %}</a></li>
{% else %}
<li>
<!-- 登录链接,通过{% url "account:login" %}生成登录页面地址 -->
<a href="{% url "account:login" %}" rel="nofollow">{% trans 'login' %}</a>
</li>
<!-- 登录链接 -->
<li><a href="{% url "account:login" %}" rel="nofollow">{% trans 'login' %}</a></li>
{% endif %}
<!-- 条件判断若用户是超级管理员user.is_superuser显示“轨迹记录”链接 -->
{% if user.is_superuser %}
<li>
<!-- 轨迹记录链接,通过{% url 'owntracks:show_dates' %}生成对应页面地址target="_blank"新窗口打开 -->
<a href="{% url 'owntracks:show_dates' %}" target="_blank">{% trans 'Track record' %}</a>
</li>
{% if user.is_superuser %} <!-- 若用户是超级管理员 -->
<!-- 轨迹记录链接(新窗口打开) -->
<li><a href="{% url 'owntracks:show_dates' %}" target="_blank">{% trans 'Track record' %}</a></li>
{% endif %}
<!-- GitBook链接固定指向http://gitbook.lylinux.net新窗口打开 -->
<!-- GitBook链接新窗口打开 -->
<li><a href="http://gitbook.lylinux.net" target="_blank" rel="nofollow">GitBook</a></li>
</ul>
</aside>
<!-- 回到顶部按钮,id="rocket"用于JS绑定点击事件class="show"表示初始显示状态 -->
<!-- 回到顶部按钮,初始显示,鼠标悬停提示文本 -->
<div id="rocket" class="show" title="{% trans 'Click me to return to the top' %}"></div>
</div><!-- #secondary --> <!-- 侧边栏容器结束注释 -->
</div><!-- #secondary --> <!-- 侧边栏容器结束 -->

@ -1,4 +1,5 @@
<<<<<<< HEAD
<<<<<<< HEAD
from django.test import TestCase
from djangoblog.utils import *
@ -90,20 +91,118 @@ class AccountTest(TestCase):
self.assertEqual(response.status_code, 200)
def test_validate_register(self):
=======
from django.test import Client, RequestFactory, TestCase
from django.urls import reverse
# 导入时区处理模块,用于处理时间相关数据
from django.utils import timezone
# 导入国际化翻译函数,用于多语言文本
from django.utils.translation import gettext_lazy as _
# 导入用户模型,用于创建测试用户数据
from accounts.models import BlogUser
# 导入文章、分类模型,用于创建测试内容数据
from blog.models import Article, Category
# 导入项目工具函数,用于测试通用功能
from djangoblog.utils import *
# 导入当前应用accounts的工具函数用于测试账号相关工具功能
from . import utils
# 定义账号功能测试类继承TestCase基础测试用例类
class AccountTest(TestCase):
# 测试前初始化方法,每个测试方法执行前自动运行
def setUp(self):
# 初始化测试客户端用于模拟用户发起HTTP请求
self.client = Client()
# 初始化请求工厂,用于构造自定义请求对象
self.factory = RequestFactory()
# 创建普通测试用户,存入测试数据库
self.blog_user = BlogUser.objects.create_user(
username="test", # 用户名
email="admin@admin.com", # 邮箱
password="12345678" # 密码
)
# 定义测试用的新密码字符串,用于后续密码修改测试
self.new_test = "xxx123--="
# 测试账号验证功能(登录、管理员权限、文章管理)
def test_validate_account(self):
# 获取当前站点域名(用于测试环境下的域名相关逻辑)
site = get_current_site().domain
# 创建超级用户,用于测试管理员权限
user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com", # 超级用户邮箱
username="liangliangyy1", # 超级用户名
password="qwer!@#$ggg") # 超级用户密码
# 从数据库中查询刚创建的超级用户,用于后续验证
testuser = BlogUser.objects.get(username='liangliangyy1')
# 模拟超级用户登录,返回登录结果(布尔值)
loginresult = self.client.login(
username='liangliangyy1', # 登录用户名
password='qwer!@#$ggg') # 登录密码
# 断言登录结果应为True登录成功
self.assertEqual(loginresult, True)
# 模拟超级用户访问管理员后台首页
response = self.client.get('/admin/')
# 断言响应状态码应为200访问成功
self.assertEqual(response.status_code, 200)
# 创建测试分类,用于后续文章关联
category = Category()
category.name = "categoryaaa" # 分类名称
category.creation_time = timezone.now() # 分类创建时间(当前时间)
category.last_modify_time = timezone.now() # 分类最后修改时间(当前时间)
category.save() # 保存分类到测试数据库
# 创建测试文章,关联上述分类和超级用户
article = Article()
article.title = "nicetitleaaa" # 文章标题
article.body = "nicecontentaaa" # 文章内容
article.author = user # 文章作者(超级用户)
article.category = category # 文章所属分类
article.type = 'a' # 文章类型(假设'a'代表普通文章)
article.status = 'p' # 文章状态(假设'p'代表已发布)
article.save() # 保存文章到测试数据库
# 模拟访问该文章的管理员编辑页通过文章模型的自定义方法获取URL
response = self.client.get(article.get_admin_url())
# 断言响应状态码应为200管理员有权限访问访问成功
self.assertEqual(response.status_code, 200)
# 测试账号注册功能(注册、邮箱验证、登录、权限提升、文章管理、登出)
def test_validate_register(self):
# 断言:数据库中初始不存在邮箱为'user123@user.com'的用户计数为0
>>>>>>> zh_branch
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
<<<<<<< HEAD
response = self.client.post(reverse('account:register'), {
'username': 'user1233',
'email': 'user123@user.com',
'password1': 'password123!q@wE#R$T',
'password2': 'password123!q@wE#R$T',
})
=======
# 模拟POST请求提交注册表单访问注册接口
response = self.client.post(reverse('account:register'), {
'username': 'user1233', # 注册用户名
'email': 'user123@user.com', # 注册邮箱
'password1': 'password123!q@wE#R$T', # 注册密码
'password2': 'password123!q@wE#R$T', # 密码确认(与密码一致)
})
# 断言注册后数据库中应存在该邮箱用户计数为1
>>>>>>> zh_branch
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
<<<<<<< HEAD
user = BlogUser.objects.filter(email='user123@user.com')[0]
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
path = reverse('accounts:result')
@ -215,11 +314,174 @@ class AccountTest(TestCase):
email="123@123.com",
code="123456",
)
=======
# 从数据库中查询刚注册的用户
user = BlogUser.objects.filter(email='user123@user.com')[0]
# 生成用户邮箱验证的签名双重SHA256加密结合密钥和用户ID
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
# 反向解析验证结果页的URL
path = reverse('accounts:result')
# 拼接完整的邮箱验证URL包含用户ID和签名
url = '{path}?type=validation&id={id}&sign={sign}'.format(
path=path, id=user.id, sign=sign)
# 模拟访问邮箱验证URL完成验证
response = self.client.get(url)
# 断言验证页面访问成功状态码200
self.assertEqual(response.status_code, 200)
# 模拟刚注册的用户登录
self.client.login(username='user1233', password='password123!q@wE#R$T')
# 重新查询该用户,准备提升权限
user = BlogUser.objects.filter(email='user123@user.com')[0]
user.is_superuser = True # 设置为超级用户
user.is_staff = True # 设置为管理员有权访问admin后台
user.save() # 保存权限修改
# 调用工具函数删除侧边栏缓存(避免缓存影响测试结果)
delete_sidebar_cache()
# 创建测试分类(用于后续文章关联)
category = Category()
category.name = "categoryaaa" # 分类名称
category.creation_time = timezone.now() # 创建时间
category.last_modify_time = timezone.now() # 最后修改时间
category.save() # 保存分类
# 创建测试文章(关联上述分类和提升权限后的用户)
article = Article()
article.category = category # 所属分类
article.title = "nicetitle333" # 文章标题
article.body = "nicecontentttt" # 文章内容
article.author = user # 文章作者(提升权限后的用户)
article.type = 'a' # 文章类型
article.status = 'p' # 文章状态(已发布)
article.save() # 保存文章
# 模拟访问该文章的管理员编辑页
response = self.client.get(article.get_admin_url())
# 断言访问成功状态码200因用户已提升为管理员
self.assertEqual(response.status_code, 200)
# 模拟用户登出(访问登出接口)
response = self.client.get(reverse('account:logout'))
# 断言:登出响应状态码在[301,302,200]内(重定向或成功)
self.assertIn(response.status_code, [301, 302, 200])
# 登出后再次访问文章管理员编辑页(应无权限)
response = self.client.get(article.get_admin_url())
# 断言:响应状态码在[301,302,200]内(可能重定向到登录页)
self.assertIn(response.status_code, [301, 302, 200])
# 模拟使用错误密码登录(密码不匹配)
response = self.client.post(reverse('account:login'), {
'username': 'user1233', # 正确用户名
'password': 'password123' # 错误密码
})
# 断言:登录响应状态码在[301,302,200]内(登录失败可能重定向或返回表单)
self.assertIn(response.status_code, [301, 302, 200])
# 错误登录后访问文章管理员编辑页(仍无权限)
response = self.client.get(article.get_admin_url())
# 断言:响应状态码在[301,302,200]内(可能重定向到登录页)
self.assertIn(response.status_code, [301, 302, 200])
# 测试邮箱验证码的生成、存储、发送和验证功能
def test_verify_email_code(self):
# 定义测试邮箱地址
to_email = "admin@admin.com"
# 生成随机邮箱验证码(调用工具函数)
code = generate_code()
# 存储验证码(关联邮箱和验证码,用于后续验证)
utils.set_code(to_email, code)
# 发送验证邮件(调用工具函数,将验证码发送到测试邮箱)
utils.send_verify_email(to_email, code)
# 验证:使用正确邮箱和正确验证码
err = utils.verify("admin@admin.com", code)
# 断言验证无错误返回None
self.assertEqual(err, None)
# 验证:使用错误邮箱和正确验证码
err = utils.verify("admin@123.com", code)
# 断言:验证错误,错误类型为字符串(返回错误信息)
self.assertEqual(type(err), str)
# 测试“忘记密码-发送验证码”功能的成功场景
def test_forget_password_email_code_success(self):
# 模拟POST请求提交邮箱访问“发送忘记密码验证码”接口
resp = self.client.post(
path=reverse("account:forget_password_code"), # 反向解析接口URL
data=dict(email="admin@admin.com") # 提交已存在的测试邮箱
)
# 断言响应状态码为200请求处理成功
self.assertEqual(resp.status_code, 200)
# 断言:响应内容为"ok"(表示验证码发送成功)
self.assertEqual(resp.content.decode("utf-8"), "ok")
# 测试“忘记密码-发送验证码”功能的失败场景
def test_forget_password_email_code_fail(self):
# 模拟POST请求不提交邮箱空数据
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict() # 空数据
)
# 断言:响应内容为“错误的邮箱”(无邮箱参数,请求失败)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
# 模拟POST请求提交格式错误的邮箱无效邮箱
resp = self.client.post(
path=reverse("account:forget_password_code"),
data=dict(email="admin@com") # 格式错误的邮箱
)
# 断言:响应内容为“错误的邮箱”(邮箱格式无效,请求失败)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
# 测试“忘记密码-重置密码”功能的成功场景
def test_forget_password_email_success(self):
# 生成随机验证码
code = generate_code()
# 存储验证码(关联测试用户的邮箱)
utils.set_code(self.blog_user.email, code)
# 构造重置密码的请求数据
data = dict(
new_password1=self.new_test, # 新密码
new_password2=self.new_test, # 新密码确认(与新密码一致)
email=self.blog_user.email, # 测试用户邮箱
code=code, # 正确的验证码
)
# 模拟POST请求提交重置密码数据访问重置密码接口
resp = self.client.post(
path=reverse("account:forget_password"), # 反向解析接口URL
data=data
)
# 断言响应状态码为302重置成功重定向到登录页或结果页
self.assertEqual(resp.status_code, 302)
# 验证:数据库中用户密码是否已更新
blog_user = BlogUser.objects.filter(
email=self.blog_user.email, # 按邮箱查询测试用户
).first() # 获取查询结果的第一个(唯一用户)
# 断言:查询到用户(用户存在)
self.assertNotEqual(blog_user, None)
# 断言用户密码与新密码匹配check_password方法验证哈希密码
self.assertEqual(blog_user.check_password(data["new_password1"]), True)
# 测试“忘记密码-重置密码”功能:邮箱不存在的失败场景
def test_forget_password_email_not_user(self):
# 构造重置密码请求数据(使用不存在的邮箱)
data = dict(
new_password1=self.new_test, # 新密码
new_password2=self.new_test, # 新密码确认
email="123@123.com", # 不存在的邮箱
code="123456", # 任意验证码
)
# 模拟POST请求提交数据访问重置密码接口
>>>>>>> zh_branch
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
<<<<<<< HEAD
self.assertEqual(resp.status_code, 200)
@ -232,11 +494,35 @@ class AccountTest(TestCase):
email=self.blog_user.email,
code="111111",
)
=======
# 断言响应状态码为200请求处理完成但重置失败返回表单页
self.assertEqual(resp.status_code, 200)
# 测试“忘记密码-重置密码”功能:验证码错误的失败场景
def test_forget_password_email_code_error(self):
# 生成正确的验证码并存储(关联测试用户邮箱)
code = generate_code()
utils.set_code(self.blog_user.email, code)
# 构造重置密码请求数据(使用错误的验证码)
data = dict(
new_password1=self.new_test, # 新密码
new_password2=self.new_test, # 新密码确认
email=self.blog_user.email, # 正确的测试用户邮箱
code="111111", # 错误的验证码
)
# 模拟POST请求提交数据访问重置密码接口
>>>>>>> zh_branch
resp = self.client.post(
path=reverse("account:forget_password"),
data=data
)
<<<<<<< HEAD
self.assertEqual(resp.status_code, 200)
=======
# 断言响应状态码为200请求处理完成但验证码错误返回表单页
self.assertEqual(resp.status_code, 200)
>>>>>>> zh_branch

@ -1,4 +1,5 @@
<<<<<<< HEAD
<<<<<<< HEAD
"""djangoblog URL Configuration
项目URL路由总配置文件定义所有URL与视图/应用的映射关系
核心作用是将用户访问的URL地址分发到对应的应用或视图函数处理
@ -127,3 +128,22 @@ urlpatterns = [re_path(r'^login/$',
name='forget_password_code'),
]
>>>>>>> sh_branch
=======
from django.urls import path
# 导入当前应用comments的views模块用于关联视图函数/类
from . import views
# 定义当前应用的命名空间为"comments"避免URL名称冲突
app_name = "comments"
# 定义URL路由列表存储URL规则与视图的映射关系
urlpatterns = [
path(
'article/<int:article_id>/postcomment', # URL路径包含文章ID整数类型的动态路径
views.CommentPostView.as_view(), # 关联的视图类调用CommentPostView的as_view()方法生成视图函数
name='postcomment'), # 给该URL命名为"postcomment",用于反向解析
]
>>>>>>> zh_branch

@ -1,4 +1,5 @@
<<<<<<< HEAD
<<<<<<< HEAD
#!/usr/bin/env python
# encoding: utf-8
@ -404,3 +405,64 @@ def get_code(email: str) -> typing.Optional[str]:
"""获取code"""
return cache.get(email)
>>>>>>> sh_branch
=======
# 导入logging模块用于记录日志如异常信息
import logging
# 导入国际化翻译函数,用于生成多语言邮件内容
from django.utils.translation import gettext_lazy as _
# 导入项目工具函数,获取当前站点域名
from djangoblog.utils import get_current_site
# 导入项目工具函数,用于发送邮件
from djangoblog.utils import send_email
# 创建当前模块的日志记录器,用于记录该函数的运行日志
logger = logging.getLogger(__name__)
# 定义发送评论相关邮件的函数,接收评论对象作为参数
def send_comment_email(comment):
# 获取当前站点的域名(用于拼接文章链接)
site = get_current_site().domain
# 定义邮件主题(多语言翻译,如中文为“感谢您的评论”)
subject = _('Thanks for your comment')
# 拼接评论所属文章的完整URLHTTPS协议 + 域名 + 文章相对路径)
article_url = f"https://{site}{comment.article.get_absolute_url()}"
# 定义邮件HTML内容多语言模板包含感谢语、文章链接、链接提示
# 使用字符串格式化,替换{article_url}和{article_title}为实际值
html_content = _("""<p>Thank you very much for your comments on this site</p>
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
to review your comments,
Thank you again!
<br />
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
tomail = comment.author.email
# 调用send_email函数发送邮件收件人列表、主题、HTML内容
send_email([tomail], subject, html_content)
# 尝试给父评论作者发送“评论被回复”的邮件(若当前评论是回复)
try:
# 判断当前评论是否有父评论(即是否为回复)
if comment.parent_comment:
# 定义回复通知的HTML邮件内容多语言模板
# 包含父评论所属文章链接、父评论内容、查看提示
html_content = _("""Your comment on <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has
received a reply. <br/> %(comment_body)s
<br/>
go check it out!
<br/>
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s
""") % {'article_url': article_url, 'article_title': comment.article.title,
'comment_body': comment.parent_comment.body}
# 获取父评论作者的邮箱(回复通知的收件人)
tomail = comment.parent_comment.author.email
# 发送回复通知邮件
send_email([tomail], subject, html_content)
# 捕获所有异常,避免发送失败影响主流程
except Exception as e:
logger.error(e)
>>>>>>> zh_branch

@ -1,3 +1,4 @@
<<<<<<< HEAD
import logging
from django.utils.translation import gettext_lazy as _
from django.conf import settings
@ -202,3 +203,103 @@ class ForgetPasswordEmailCode(View):
utils.set_code(to_email, code)
return HttpResponse("ok")
=======
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
# 导入方法装饰器工具,用于装饰类中的方法
from django.utils.decorators import method_decorator
# 导入CSRF保护装饰器防止跨站请求伪造
from django.views.decorators.csrf import csrf_protect
# 导入表单视图基类,用于处理表单提交逻辑
from django.views.generic.edit import FormView
# 导入用户模型,用于获取评论作者信息
from accounts.models import BlogUser
# 导入文章模型,用于关联评论所属文章
from blog.models import Article
# 导入评论表单类,用于处理评论提交数据
from .forms import CommentForm
# 导入评论模型,用于创建和保存评论
from .models import Comment
# 定义评论提交视图类继承自FormView表单处理基类
class CommentPostView(FormView):
form_class = CommentForm # 指定使用的表单类为CommentForm
template_name = 'blog/article_detail.html' # 指定表单验证失败时渲染的模板
# 使用CSRF保护装饰器装饰dispatch方法确保表单提交安全
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
# 调用父类的dispatch方法处理请求分发
return super(CommentPostView, self).dispatch(*args, **kwargs)
# 处理GET请求重定向到文章详情页的评论区
def get(self, request, *args, **kwargs):
# 从URL参数中获取文章ID
article_id = self.kwargs['article_id']
# 获取对应的文章对象不存在则返回404
article = get_object_or_404(Article, pk=article_id)
# 获取文章详情页的URL
url = article.get_absolute_url()
# 重定向到文章详情页的评论区(通过锚点#comments定位
return HttpResponseRedirect(url + "#comments")
# 处理表单验证失败的逻辑
def form_invalid(self, form):
# 从URL参数中获取文章ID
article_id = self.kwargs['article_id']
# 获取对应的文章对象
article = get_object_or_404(Article, pk=article_id)
# 渲染文章详情页模板,传递错误的表单和文章对象(用于显示错误信息)
return self.render_to_response({
'form': form,
'article': article
})
# 处理表单验证成功后的逻辑
def form_valid(self, form):
"""提交的数据验证合法后的逻辑"""
# 获取当前登录用户
user = self.request.user
# 根据用户ID获取对应的用户对象评论作者
author = BlogUser.objects.get(pk=user.pk)
# 从URL参数中获取文章ID
article_id = self.kwargs['article_id']
# 获取对应的文章对象
article = get_object_or_404(Article, pk=article_id)
# 检查文章是否允许评论:若文章评论状态为关闭或文章状态为草稿,则抛出验证错误
if article.comment_status == 'c' or article.status == 'c':
raise ValidationError("该文章评论已关闭.")
# 保存表单数据但不提交到数据库(获取评论对象)
comment = form.save(False)
# 关联评论到对应的文章
comment.article = article
# 导入工具函数,获取博客设置
from djangoblog.utils import get_blog_setting
settings = get_blog_setting()
# 若博客设置为评论无需审核,则直接启用评论
if not settings.comment_need_review:
comment.is_enable = True
# 设置评论的作者
comment.author = author
# 处理回复功能若存在父评论ID则关联到父评论
if form.cleaned_data['parent_comment_id']:
# 根据父评论ID获取父评论对象
parent_comment = Comment.objects.get(
pk=form.cleaned_data['parent_comment_id'])
# 设置当前评论的父评论
comment.parent_comment = parent_comment
# 保存评论到数据库(执行真正的保存操作)
comment.save(True)
# 重定向到文章详情页的当前评论位置(通过锚点#div-comment-{评论ID}定位)
return HttpResponseRedirect(
"%s#div-comment-%d" %
(article.get_absolute_url(), comment.pk))
>>>>>>> zh_branch

Loading…
Cancel
Save