diff --git a/doc/开源软件的质量分析报告文档.docx b/doc/开源软件的质量分析报告文档.docx new file mode 100644 index 0000000..6afaa79 Binary files /dev/null and b/doc/开源软件的质量分析报告文档.docx differ diff --git a/doc/编码规范.docx b/doc/编码规范.docx new file mode 100644 index 0000000..5b0eee3 Binary files /dev/null and b/doc/编码规范.docx differ diff --git a/src/DjangoBlog-master/accounts/admin.py b/src/DjangoBlog-master/accounts/admin.py index 29d162a..2c63cef 100644 --- a/src/DjangoBlog-master/accounts/admin.py +++ b/src/DjangoBlog-master/accounts/admin.py @@ -1,3 +1,4 @@ +#ht: 用户管理后台配置模块,自定义Django Admin的用户管理界面 from django import forms from django.contrib.auth.admin import UserAdmin from django.contrib.auth.forms import UserChangeForm @@ -9,6 +10,7 @@ from .models import BlogUser class BlogUserCreationForm(forms.ModelForm): + """ht: 用户创建表单,用于在Admin后台创建新用户""" password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) @@ -17,7 +19,7 @@ class BlogUserCreationForm(forms.ModelForm): fields = ('email',) def clean_password2(self): - # Check that the two password entries match + """ht: 验证两次输入的密码是否一致""" password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: @@ -25,16 +27,17 @@ class BlogUserCreationForm(forms.ModelForm): return password2 def save(self, commit=True): - # Save the provided password in hashed format + """ht: 保存用户,对密码进行哈希处理并设置来源标记""" user = super().save(commit=False) user.set_password(self.cleaned_data["password1"]) if commit: - user.source = 'adminsite' + user.source = 'adminsite' # ht: 标记用户来源为管理员后台 user.save() return user class BlogUserChangeForm(UserChangeForm): + """ht: 用户信息修改表单""" class Meta: model = BlogUser fields = '__all__' @@ -45,8 +48,10 @@ class BlogUserChangeForm(UserChangeForm): class BlogUserAdmin(UserAdmin): + """ht: 自定义用户管理类,配置Admin界面显示和搜索选项""" form = BlogUserChangeForm add_form = BlogUserCreationForm + #ht: 列表显示字段 list_display = ( 'id', 'nickname', @@ -55,6 +60,6 @@ class BlogUserAdmin(UserAdmin): 'last_login', 'date_joined', 'source') - list_display_links = ('id', 'username') - ordering = ('-id',) - search_fields = ('username', 'nickname', 'email') + list_display_links = ('id', 'username') # ht: 可点击链接的字段 + ordering = ('-id',) # ht: 默认按ID倒序排列 + search_fields = ('username', 'nickname', 'email') # ht: 搜索字段 \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/models.py b/src/DjangoBlog-master/accounts/models.py index 3baddbb..e8c0f18 100644 --- a/src/DjangoBlog-master/accounts/models.py +++ b/src/DjangoBlog-master/accounts/models.py @@ -1,3 +1,4 @@ +#ht: 用户数据模型模块,定义自定义用户模型 from django.contrib.auth.models import AbstractUser from django.db import models from django.urls import reverse @@ -6,30 +7,32 @@ from django.utils.translation import gettext_lazy as _ from djangoblog.utils import get_current_site -# Create your models here. - class BlogUser(AbstractUser): + """ht: 博客用户模型,扩展Django默认用户模型""" nickname = models.CharField(_('nick name'), max_length=100, blank=True) creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('last modify time'), default=now) source = models.CharField(_('create source'), max_length=100, blank=True) def get_absolute_url(self): + """ht: 获取用户详情页URL""" return reverse( 'blog:author_detail', kwargs={ 'author_name': self.username}) def __str__(self): + """ht: 字符串表示,返回邮箱""" return self.email def get_full_url(self): + """ht: 获取完整的用户URL(包含域名)""" site = get_current_site().domain url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url class Meta: - ordering = ['-id'] + ordering = ['-id'] # ht: 默认按ID倒序排列 verbose_name = _('user') verbose_name_plural = verbose_name - get_latest_by = 'id' + get_latest_by = 'id' \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/utils.py b/src/DjangoBlog-master/accounts/utils.py index 4b94bdf..6e78218 100644 --- a/src/DjangoBlog-master/accounts/utils.py +++ b/src/DjangoBlog-master/accounts/utils.py @@ -1,3 +1,4 @@ +#ht: 账户相关工具函数模块,处理验证码发送和验证 import typing from datetime import timedelta @@ -7,15 +8,16 @@ from django.utils.translation import gettext_lazy as _ from djangoblog.utils import send_email +#ht: 验证码有效时间为5分钟 _code_ttl = timedelta(minutes=5) def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): - """发送重设密码验证码 + """ht: 发送验证邮件 Args: - to_mail: 接受邮箱 - subject: 邮件主题 + to_mail: 接收邮箱地址 code: 验证码 + subject: 邮件主题,默认为"验证邮箱" """ html_content = _( "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " @@ -24,26 +26,26 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")) def verify(email: str, code: str) -> typing.Optional[str]: - """验证code是否有效 + """ht: 验证邮箱和验证码是否匹配 Args: - email: 请求邮箱 + email: 邮箱地址 code: 验证码 - Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 + Returns: + 验证成功返回None,失败返回错误信息字符串 """ cache_code = get_code(email) if cache_code != code: return gettext("Verification code error") + #ht: 验证成功后清除验证码,防止重复使用 + cache.delete(email) + return None def set_code(email: str, code: str): - """设置code""" + """ht: 将验证码存入缓存""" cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: - """获取code""" - return cache.get(email) + """ht: 从缓存获取验证码""" + return cache.get(email) \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/views.py b/src/DjangoBlog-master/accounts/views.py index ae67aec..709d977 100644 --- a/src/DjangoBlog-master/accounts/views.py +++ b/src/DjangoBlog-master/accounts/views.py @@ -1,3 +1,4 @@ +#ht: 账户视图模块,处理用户登录、注册、注销等请求 import logging from django.utils.translation import gettext_lazy as _ from django.conf import settings @@ -32,6 +33,7 @@ logger = logging.getLogger(__name__) # Create your views here. class RegisterView(FormView): + """ht: 用户注册视图""" form_class = RegisterForm template_name = 'account/registration_form.html' @@ -40,20 +42,25 @@ class RegisterView(FormView): return super(RegisterView, self).dispatch(*args, **kwargs) def form_valid(self, form): + """ht: 表单验证通过后的处理,创建用户并发送验证邮件""" if form.is_valid(): - user = form.save(False) - user.is_active = False - user.source = 'Register' - user.save(True) + user = form.save(False) # ht: 不立即保存到数据库 + user.is_active = False # ht: 设置用户为未激活状态 + user.source = 'Register' # ht: 标记用户来源 + user.save(True) # ht: 保存用户到数据库 + + #ht: 生成验证链接 site = get_current_site().domain sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) if settings.DEBUG: - site = '127.0.0.1:8000' + site = '127.0.0.1:8000' # ht: 调试模式下使用本地地址 + path = reverse('account:result') url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( site=site, path=path, id=user.id, sign=sign) + #ht: 邮件内容模板 content = """
请点击下面链接验证您的邮箱
@@ -64,6 +71,7 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + #ht: 发送验证邮件 send_email( emailto=[ user.email, @@ -79,7 +87,6 @@ class RegisterView(FormView): 'form': form }) - class LogoutView(RedirectView): url = '/login/' @@ -94,39 +101,33 @@ class LogoutView(RedirectView): class LoginView(FormView): + """ht: 用户登录视图""" form_class = LoginForm template_name = 'account/login.html' success_url = '/' redirect_field_name = REDIRECT_FIELD_NAME - login_ttl = 2626560 # 一个月的时间 + login_ttl = 2626560 # ht: 登录会话有效期(一个月) @method_decorator(sensitive_post_parameters('password')) @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): - return super(LoginView, self).dispatch(request, *args, **kwargs) - def get_context_data(self, **kwargs): - redirect_to = self.request.GET.get(self.redirect_field_name) - if redirect_to is None: - redirect_to = '/' - kwargs['redirect_to'] = redirect_to - - return super(LoginView, self).get_context_data(**kwargs) - def form_valid(self, form): + """ht: 登录表单验证处理""" form = AuthenticationForm(data=self.request.POST, request=self.request) if form.is_valid(): + #ht: 清除侧边栏缓存并登录用户 delete_sidebar_cache() logger.info(self.redirect_field_name) auth.login(self.request, form.get_user()) + #ht: 处理"记住我"功能 if self.request.POST.get("remember"): self.request.session.set_expiry(self.login_ttl) return super(LoginView, self).form_valid(form) - # return HttpResponseRedirect('/') else: return self.render_to_response({ 'form': form diff --git a/src/DjangoBlog-master/blog/admin.py b/src/DjangoBlog-master/blog/admin.py index 69d7f8e..ec41816 100644 --- a/src/DjangoBlog-master/blog/admin.py +++ b/src/DjangoBlog-master/blog/admin.py @@ -10,6 +10,7 @@ from .models import Article, Category, Tag, Links, SideBar, BlogSettings class ArticleForm(forms.ModelForm): + #fjw: 文章表单类,用于后台文章编辑 # body = forms.CharField(widget=AdminPagedownWidget()) class Meta: @@ -18,18 +19,22 @@ class ArticleForm(forms.ModelForm): def makr_article_publish(modeladmin, request, queryset): + #fjw: 批量发布文章 queryset.update(status='p') def draft_article(modeladmin, request, queryset): + #fjw: 批量将文章设为草稿 queryset.update(status='d') def close_article_commentstatus(modeladmin, request, queryset): + #fjw: 批量关闭文章评论 queryset.update(comment_status='c') def open_article_commentstatus(modeladmin, request, queryset): + #fjw: 批量开启文章评论 queryset.update(comment_status='o') @@ -40,6 +45,7 @@ open_article_commentstatus.short_description = _('Open article comments') class ArticlelAdmin(admin.ModelAdmin): + #fjw: 文章管理后台配置 list_per_page = 20 search_fields = ('body', 'title') form = ArticleForm @@ -67,6 +73,7 @@ class ArticlelAdmin(admin.ModelAdmin): raw_id_fields = ('author', 'category',) def link_to_category(self, obj): + #fjw: 生成分类链接 info = (obj.category._meta.app_label, obj.category._meta.model_name) link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) return format_html(u'%s' % (link, obj.category.name)) @@ -74,15 +81,18 @@ class ArticlelAdmin(admin.ModelAdmin): link_to_category.short_description = _('category') def get_form(self, request, obj=None, **kwargs): + #fjw: 限制作者只能选择超级用户 form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) form.base_fields['author'].queryset = get_user_model( ).objects.filter(is_superuser=True) return form def save_model(self, request, obj, form, change): + #fjw: 保存模型时的额外处理 super(ArticlelAdmin, self).save_model(request, obj, form, change) def get_view_on_site_url(self, obj=None): + #fjw: 获取文章在前台的URL if obj: url = obj.get_full_url() return url @@ -93,22 +103,27 @@ class ArticlelAdmin(admin.ModelAdmin): class TagAdmin(admin.ModelAdmin): + #fjw: 标签管理后台配置 exclude = ('slug', 'last_mod_time', 'creation_time') class CategoryAdmin(admin.ModelAdmin): + #fjw: 分类管理后台配置 list_display = ('name', 'parent_category', 'index') exclude = ('slug', 'last_mod_time', 'creation_time') class LinksAdmin(admin.ModelAdmin): + #fjw: 友情链接管理后台配置 exclude = ('last_mod_time', 'creation_time') class SideBarAdmin(admin.ModelAdmin): + #fjw: 侧边栏管理后台配置 list_display = ('name', 'content', 'is_enable', 'sequence') exclude = ('last_mod_time', 'creation_time') class BlogSettingsAdmin(admin.ModelAdmin): - pass + #fjw: 博客设置管理后台配置 + pass \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/apps.py b/src/DjangoBlog-master/blog/apps.py index 7930587..cf15b31 100644 --- a/src/DjangoBlog-master/blog/apps.py +++ b/src/DjangoBlog-master/blog/apps.py @@ -2,4 +2,5 @@ from django.apps import AppConfig class BlogConfig(AppConfig): - name = 'blog' + #fjw: 博客应用配置 + name = 'blog' \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/context_processors.py b/src/DjangoBlog-master/blog/context_processors.py index 73e3088..eceee38 100644 --- a/src/DjangoBlog-master/blog/context_processors.py +++ b/src/DjangoBlog-master/blog/context_processors.py @@ -9,6 +9,7 @@ logger = logging.getLogger(__name__) def seo_processor(requests): + #fjw: SEO上下文处理器,为所有模板提供SEO相关变量 key = 'seo_processor' value = cache.get(key) if value: @@ -40,4 +41,4 @@ def seo_processor(requests): "COMMENT_NEED_REVIEW": setting.comment_need_review, } cache.set(key, value, 60 * 60 * 10) - return value + return value \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/models.py b/src/DjangoBlog-master/blog/models.py index 083788b..6442d2d 100644 --- a/src/DjangoBlog-master/blog/models.py +++ b/src/DjangoBlog-master/blog/models.py @@ -18,6 +18,7 @@ logger = logging.getLogger(__name__) class LinkShowType(models.TextChoices): + #fjw: 链接显示类型枚举 I = ('i', _('index')) L = ('l', _('list')) P = ('p', _('post')) @@ -26,11 +27,13 @@ class LinkShowType(models.TextChoices): class BaseModel(models.Model): + #fjw: 基础模型类,提供通用字段和方法 id = models.AutoField(primary_key=True) creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('modify time'), default=now) def save(self, *args, **kwargs): + #fjw: 保存时的通用处理逻辑 is_update_views = isinstance( self, Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views'] @@ -45,6 +48,7 @@ class BaseModel(models.Model): super().save(*args, **kwargs) def get_full_url(self): + #fjw: 获取完整URL site = get_current_site().domain url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) @@ -59,15 +63,18 @@ class BaseModel(models.Model): class Article(BaseModel): - """文章""" + """文章模型""" + #fjw: 文章状态选择 STATUS_CHOICES = ( ('d', _('Draft')), ('p', _('Published')), ) + #fjw: 评论状态选择 COMMENT_STATUS = ( ('o', _('Open')), ('c', _('Close')), ) + #fjw: 文章类型选择 TYPE = ( ('a', _('Article')), ('p', _('Page')), @@ -118,6 +125,7 @@ class Article(BaseModel): get_latest_by = 'id' def get_absolute_url(self): + #fjw: 获取文章详情页URL return reverse('blog:detailbyid', kwargs={ 'article_id': self.id, 'year': self.creation_time.year, @@ -127,6 +135,7 @@ class Article(BaseModel): @cache_decorator(60 * 60 * 10) def get_category_tree(self): + #fjw: 获取分类树 tree = self.category.get_category_tree() names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) @@ -136,10 +145,12 @@ class Article(BaseModel): super().save(*args, **kwargs) def viewed(self): + #fjw: 增加文章浏览量 self.views += 1 self.save(update_fields=['views']) def comment_list(self): + #fjw: 获取文章评论列表(带缓存) cache_key = 'article_comments_{id}'.format(id=self.id) value = cache.get(cache_key) if value: @@ -152,24 +163,25 @@ class Article(BaseModel): return comments def get_admin_url(self): + #fjw: 获取文章在后台的URL info = (self._meta.app_label, self._meta.model_name) return reverse('admin:%s_%s_change' % info, args=(self.pk,)) @cache_decorator(expiration=60 * 100) def next_article(self): - # 下一篇 + #fjw: 获取下一篇文章 return Article.objects.filter( id__gt=self.id, status='p').order_by('id').first() @cache_decorator(expiration=60 * 100) def prev_article(self): - # 前一篇 + #fjw: 获取上一篇文章 return Article.objects.filter(id__lt=self.id, status='p').first() def get_first_image_url(self): """ - Get the first image url from article.body. - :return: + Get the first image url from article body. + :return: 文章正文中的第一张图片URL """ match = re.search(r'!\[.*?\]\((.+?)\)', self.body) if match: @@ -178,7 +190,7 @@ class Article(BaseModel): class Category(BaseModel): - """文章分类""" + """文章分类模型""" name = models.CharField(_('category name'), max_length=30, unique=True) parent_category = models.ForeignKey( 'self', @@ -206,7 +218,7 @@ class Category(BaseModel): def get_category_tree(self): """ 递归获得分类目录的父级 - :return: + :return: 分类树列表 """ categorys = [] @@ -222,7 +234,7 @@ class Category(BaseModel): def get_sub_categorys(self): """ 获得当前分类目录所有子集 - :return: + :return: 子分类列表 """ categorys = [] all_categorys = Category.objects.all() @@ -241,7 +253,7 @@ class Category(BaseModel): class Tag(BaseModel): - """文章标签""" + """文章标签模型""" name = models.CharField(_('tag name'), max_length=30, unique=True) slug = models.SlugField(default='no-slug', max_length=60, blank=True) @@ -253,6 +265,7 @@ class Tag(BaseModel): @cache_decorator(60 * 60 * 10) def get_article_count(self): + #fjw: 获取标签下的文章数量 return Article.objects.filter(tags__name=self.name).distinct().count() class Meta: @@ -262,7 +275,7 @@ class Tag(BaseModel): class Links(models.Model): - """友情链接""" + """友情链接模型""" name = models.CharField(_('link name'), max_length=30, unique=True) link = models.URLField(_('link')) @@ -287,7 +300,7 @@ class Links(models.Model): class SideBar(models.Model): - """侧边栏,可以展示一些html内容""" + """侧边栏模型,可以展示一些html内容""" name = models.CharField(_('title'), max_length=100) content = models.TextField(_('content')) sequence = models.IntegerField(_('order'), unique=True) @@ -305,7 +318,7 @@ class SideBar(models.Model): class BlogSettings(models.Model): - """blog的配置""" + """博客配置模型""" site_name = models.CharField( _('site name'), max_length=200, @@ -367,10 +380,11 @@ class BlogSettings(models.Model): return self.site_name def clean(self): + #fjw: 确保只有一个配置实例 if BlogSettings.objects.exclude(id=self.id).count(): raise ValidationError(_('There can only be one configuration')) def save(self, *args, **kwargs): super().save(*args, **kwargs) from djangoblog.utils import cache - cache.clear() + cache.clear() #fjw: 保存配置后清除缓存 \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/templatetags/blog_tags.py b/src/DjangoBlog-master/blog/templatetags/blog_tags.py index 024f2c8..802d6f9 100644 --- a/src/DjangoBlog-master/blog/templatetags/blog_tags.py +++ b/src/DjangoBlog-master/blog/templatetags/blog_tags.py @@ -32,6 +32,7 @@ def head_meta(context): @register.simple_tag def timeformat(data): + #fjw: 时间格式化标签 try: return data.strftime(settings.TIME_FORMAT) except Exception as e: @@ -56,13 +57,7 @@ def custom_markdown(content): 主要用于文章内容处理 """ html_content = CommonMarkdown.get_markdown(content) - - # 然后应用插件过滤器优化HTML - from djangoblog.plugin_manage import hooks - from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME - optimized_html = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, html_content) - - return mark_safe(optimized_html) + return mark_safe(html_content) @register.filter() @@ -136,13 +131,13 @@ def comment_markdown(content): return mark_safe(sanitize_html(content)) -@register.filter(is_safe=True) +@register.filter() @stringfilter def truncatechars_content(content): """ 获得文章内容的摘要 - :param content: - :return: + :param content: 文章内容 + :return: 截取后的摘要 """ from django.template.defaultfilters import truncatechars_html from djangoblog.utils import get_blog_setting @@ -162,8 +157,8 @@ def truncate(content): def load_breadcrumb(article): """ 获得文章面包屑 - :param article: - :return: + :param article: 文章对象 + :return: 面包屑数据 """ names = article.get_category_tree() from djangoblog.utils import get_blog_setting @@ -183,8 +178,8 @@ def load_breadcrumb(article): def load_articletags(article): """ 文章标签 - :param article: - :return: + :param article: 文章对象 + :return: 标签数据 """ tags = article.tags.all() tags_list = [] @@ -199,11 +194,14 @@ def load_articletags(article): } + @register.inclusion_tag('blog/tags/sidebar.html') def load_sidebar(user, linktype): """ 加载侧边栏 - :return: + :param user: 当前用户 + :param linktype: 链接类型 + :return: 侧边栏数据 """ value = cache.get("sidebar" + linktype) if value: @@ -225,8 +223,8 @@ def load_sidebar(user, linktype): Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)) commment_list = Comment.objects.filter(is_enable=True).order_by( '-id')[:blogsetting.sidebar_comment_count] - # 标签云 计算字体大小 - # 根据总数计算出平均值 大小为 (数目/平均值)*步长 + + # 标签云计算字体大小 increment = 5 tags = Tag.objects.all() sidebar_tags = None @@ -364,34 +362,34 @@ def load_article_detail(article, isindex, user): # 模板使用方法: {{ email|gravatar_url:150 }} @register.filter def gravatar_url(email, size=40): - """获得用户头像 - 优先使用OAuth头像,否则使用默认头像""" + """获得用户头像URL""" cachekey = 'avatar/' + email url = cache.get(cachekey) if url: return url - + + # 改进:添加默认头像路径常量 + DEFAULT_AVATAR_PATH = static('blog/img/avatar.png') + # 检查OAuth用户是否有自定义头像 usermodels = OAuthUser.objects.filter(email=email) if usermodels: # 过滤出有头像的用户 users_with_picture = list(filter(lambda x: x.picture is not None, usermodels)) if users_with_picture: - # 获取默认头像路径用于比较 - default_avatar_path = static('blog/img/avatar.png') - # 优先选择非默认头像的用户,否则选择第一个 - non_default_users = [u for u in users_with_picture if u.picture != default_avatar_path and not u.picture.endswith('/avatar.png')] + non_default_users = [u for u in users_with_picture if u.picture != DEFAULT_AVATAR_PATH and not u.picture.endswith('/avatar.png')] selected_user = non_default_users[0] if non_default_users else users_with_picture[0] - + url = selected_user.picture cache.set(cachekey, url, 60 * 60 * 24) # 缓存24小时 - + avatar_type = 'non-default' if non_default_users else 'default' logger.info('Using {} OAuth avatar for {} from {}'.format(avatar_type, email, selected_user.type)) return url - + # 使用默认头像 - url = static('blog/img/avatar.png') + url = DEFAULT_AVATAR_PATH cache.set(cachekey, url, 60 * 60 * 24) # 缓存24小时 logger.info('Using default avatar for {}'.format(email)) return url diff --git a/src/DjangoBlog-master/blog/views.py b/src/DjangoBlog-master/blog/views.py index 773bb75..6cb5b1c 100644 --- a/src/DjangoBlog-master/blog/views.py +++ b/src/DjangoBlog-master/blog/views.py @@ -25,14 +25,10 @@ logger = logging.getLogger(__name__) class ArticleListView(ListView): - # template_name属性用于指定使用哪个模板进行渲染 + #fjw: 文章列表基类视图 template_name = 'blog/article_index.html' - - # context_object_name属性用于给上下文变量取名(在模板中使用该名字) context_object_name = 'article_list' - - # 页面类型,分类目录或标签列表等 - page_type = '' + page_type = '' #fjw: 页面类型,分类目录或标签列表等 paginate_by = settings.PAGINATE_BY page_kwarg = 'page' link_type = LinkShowType.L @@ -42,6 +38,7 @@ class ArticleListView(ListView): @property def page_number(self): + #fjw: 获取当前页码 page_kwarg = self.page_kwarg page = self.kwargs.get( page_kwarg) or self.request.GET.get(page_kwarg) or 1 @@ -63,7 +60,7 @@ class ArticleListView(ListView): ''' 缓存页面数据 :param cache_key: 缓存key - :return: + :return: 文章列表数据 ''' value = cache.get(cache_key) if value: @@ -78,7 +75,7 @@ class ArticleListView(ListView): def get_queryset(self): ''' 重写默认,从缓存获取数据 - :return: + :return: 文章列表 ''' key = self.get_queryset_cache_key() value = self.get_queryset_from_cache(key) @@ -91,12 +88,12 @@ class ArticleListView(ListView): class IndexView(ArticleListView): ''' - 首页 + 首页视图 ''' - # 友情链接类型 - link_type = LinkShowType.I + link_type = LinkShowType.I #fjw: 友情链接类型 def get_queryset_data(self): + #fjw: 获取首页文章列表 article_list = Article.objects.filter(type='a', status='p') return article_list @@ -107,7 +104,7 @@ class IndexView(ArticleListView): class ArticleDetailView(DetailView): ''' - 文章详情页面 + 文章详情页面视图 ''' template_name = 'blog/article_detail.html' model = Article @@ -115,6 +112,7 @@ class ArticleDetailView(DetailView): context_object_name = "article" def get_context_data(self, **kwargs): + #fjw: 添加上下文数据:评论表单、评论列表等 comment_form = CommentForm() article_comments = self.object.comment_list() @@ -152,23 +150,22 @@ class ArticleDetailView(DetailView): context = super(ArticleDetailView, self).get_context_data(**kwargs) article = self.object - + # 触发文章详情加载钩子,让插件可以添加额外的上下文数据 from djangoblog.plugin_manage.hook_constants import ARTICLE_DETAIL_LOAD hooks.run_action(ARTICLE_DETAIL_LOAD, article=article, context=context, request=self.request) - - # Action Hook, 通知插件"文章详情已获取" - hooks.run_action('after_article_body_get', article=article, request=self.request) + return context class CategoryDetailView(ArticleListView): ''' - 分类目录列表 + 分类目录列表视图 ''' page_type = "分类目录归档" def get_queryset_data(self): + #fjw: 获取分类下的文章列表 slug = self.kwargs['category_name'] category = get_object_or_404(Category, slug=slug) @@ -190,7 +187,6 @@ class CategoryDetailView(ArticleListView): return cache_key def get_context_data(self, **kwargs): - categoryname = self.categoryname try: categoryname = categoryname.split('/')[-1] @@ -203,7 +199,7 @@ class CategoryDetailView(ArticleListView): class AuthorDetailView(ArticleListView): ''' - 作者详情页 + 作者详情页视图 ''' page_type = '作者文章归档' @@ -229,7 +225,7 @@ class AuthorDetailView(ArticleListView): class TagDetailView(ArticleListView): ''' - 标签列表页面 + 标签列表页面视图 ''' page_type = '分类标签归档' @@ -252,7 +248,6 @@ class TagDetailView(ArticleListView): return cache_key def get_context_data(self, **kwargs): - # tag_name = self.kwargs['tag_name'] tag_name = self.name kwargs['page_type'] = TagDetailView.page_type kwargs['tag_name'] = tag_name @@ -261,7 +256,7 @@ class TagDetailView(ArticleListView): class ArchivesView(ArticleListView): ''' - 文章归档页面 + 文章归档页面视图 ''' page_type = '文章归档' paginate_by = None @@ -277,6 +272,7 @@ class ArchivesView(ArticleListView): class LinkListView(ListView): + #fjw: 友情链接页面视图 model = Links template_name = 'blog/links_list.html' @@ -284,29 +280,12 @@ class LinkListView(ListView): return Links.objects.filter(is_enable=True) -class EsSearchView(SearchView): - def get_context(self): - paginator, page = self.build_page() - context = { - "query": self.query, - "form": self.form, - "page": page, - "paginator": paginator, - "suggestion": None, - } - if hasattr(self.results, "query") and self.results.query.backend.include_spelling: - context["suggestion"] = self.results.query.get_spelling_suggestion() - context.update(self.extra_context()) - - return context - - @csrf_exempt def fileupload(request): """ - 该方法需自己写调用端来上传图片,该方法仅提供图床功能 + 文件上传方法,需自己写调用端来上传图片,该方法仅提供图床功能 :param request: - :return: + :return: 上传文件的URL列表 """ if request.method == 'POST': sign = request.GET.get('sign', None) @@ -345,6 +324,7 @@ def page_not_found_view( request, exception, template_name='blog/error_page.html'): + #fjw: 404错误页面视图 if exception: logger.error(exception) url = request.get_full_path() @@ -356,6 +336,7 @@ def page_not_found_view( def server_error_view(request, template_name='blog/error_page.html'): + #fjw: 500错误页面视图 return render(request, template_name, {'message': _('Sorry, the server is busy, please click the home page to see other?'), @@ -367,6 +348,7 @@ def permission_denied_view( request, exception, template_name='blog/error_page.html'): + #fjw: 403错误页面视图 if exception: logger.error(exception) return render( @@ -376,5 +358,6 @@ def permission_denied_view( def clean_cache_view(request): + #fjw: 清理缓存视图 cache.clear() - return HttpResponse('ok') + return HttpResponse('ok') \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/admin.py b/src/DjangoBlog-master/comments/admin.py index dbde14f..b3b208d 100644 --- a/src/DjangoBlog-master/comments/admin.py +++ b/src/DjangoBlog-master/comments/admin.py @@ -1,3 +1,4 @@ +#wzc: 评论模块的后台管理配置 from django.contrib import admin from django.urls import reverse from django.utils.html import format_html @@ -5,10 +6,12 @@ from django.utils.translation import gettext_lazy as _ def disable_commentstatus(modeladmin, request, queryset): + """wzc: 批量禁用评论状态""" queryset.update(is_enable=False) def enable_commentstatus(modeladmin, request, queryset): + """wzc: 批量启用评论状态""" queryset.update(is_enable=True) @@ -17,6 +20,7 @@ enable_commentstatus.short_description = _('Enable comments') class CommentAdmin(admin.ModelAdmin): + """wzc: 评论模型的后台管理配置""" list_per_page = 20 list_display = ( 'id', @@ -33,17 +37,18 @@ class CommentAdmin(admin.ModelAdmin): search_fields = ('body',) def link_to_userinfo(self, obj): + """wzc: 生成用户信息链接""" info = (obj.author._meta.app_label, obj.author._meta.model_name) link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) - return format_html( - u'%s' % - (link, obj.author.nickname if obj.author.nickname else obj.author.email)) + #wzc: 使用昵称或邮箱作为显示文本 + display_text = obj.author.nickname if obj.author.nickname else obj.author.email + return format_html(u'{}', link, display_text) def link_to_article(self, obj): + """wzc: 生成文章链接""" info = (obj.article._meta.app_label, obj.article._meta.model_name) link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) - return format_html( - u'%s' % (link, obj.article.title)) + return format_html(u'{}', link, obj.article.title) link_to_userinfo.short_description = _('User') - link_to_article.short_description = _('Article') + link_to_article.short_description = _('Article') \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/apps.py b/src/DjangoBlog-master/comments/apps.py index ff01b77..1aa67ac 100644 --- a/src/DjangoBlog-master/comments/apps.py +++ b/src/DjangoBlog-master/comments/apps.py @@ -1,5 +1,7 @@ +#wzc: 评论应用的配置类 from django.apps import AppConfig class CommentsConfig(AppConfig): - name = 'comments' + """wzc: 评论应用配置""" + name = 'comments' \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/forms.py b/src/DjangoBlog-master/comments/forms.py index e83737d..e30d28c 100644 --- a/src/DjangoBlog-master/comments/forms.py +++ b/src/DjangoBlog-master/comments/forms.py @@ -1,3 +1,4 @@ +#wzc: 评论相关的表单定义 from django import forms from django.forms import ModelForm @@ -5,9 +6,13 @@ from .models import Comment class CommentForm(ModelForm): + """wzc: 评论表单,支持父评论ID""" parent_comment_id = forms.IntegerField( - widget=forms.HiddenInput, required=False) + widget=forms.HiddenInput, + required=False, + help_text="wzc: 父评论ID,用于回复功能" + ) class Meta: model = Comment - fields = ['body'] + fields = ['body'] \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/models.py b/src/DjangoBlog-master/comments/models.py index 7c3bbc8..3d7f487 100644 --- a/src/DjangoBlog-master/comments/models.py +++ b/src/DjangoBlog-master/comments/models.py @@ -1,3 +1,4 @@ +#wzc: 评论数据模型定义 from django.conf import settings from django.db import models from django.utils.timezone import now @@ -6,9 +7,8 @@ from django.utils.translation import gettext_lazy as _ from blog.models import Article -# Create your models here. - class Comment(models.Model): + """wzc: 评论模型,存储用户对文章的评论信息""" body = models.TextField('正文', max_length=300) creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('last modify time'), default=now) @@ -25,9 +25,14 @@ class Comment(models.Model): verbose_name=_('parent comment'), blank=True, null=True, - on_delete=models.CASCADE) - is_enable = models.BooleanField(_('enable'), - default=False, blank=False, null=False) + on_delete=models.CASCADE, + help_text="wzc: 父评论,用于实现评论回复功能") + is_enable = models.BooleanField( + _('enable'), + default=False, + blank=False, + null=False, + help_text="wzc: 评论是否启用(审核通过)") class Meta: ordering = ['-id'] @@ -36,4 +41,5 @@ class Comment(models.Model): get_latest_by = 'id' def __str__(self): - return self.body + """wzc: 返回评论正文作为字符串表示""" + return self.body[:50] #wzc: 只返回前50个字符,避免过长 \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/tests.py b/src/DjangoBlog-master/comments/tests.py index 2a7f55f..cacc42c 100644 --- a/src/DjangoBlog-master/comments/tests.py +++ b/src/DjangoBlog-master/comments/tests.py @@ -1,3 +1,4 @@ +#wzc: 评论模块的测试用例 from django.test import Client, RequestFactory, TransactionTestCase from django.urls import reverse @@ -8,102 +9,73 @@ from comments.templatetags.comments_tags import * from djangoblog.utils import get_max_articleid_commentid -# Create your tests here. - class CommentsTest(TransactionTestCase): + """wzc: 评论功能测试类""" + def setUp(self): + """wzc: 测试初始化设置""" self.client = Client() self.factory = RequestFactory() + + #wzc: 设置博客配置,评论需要审核 from blog.models import BlogSettings value = BlogSettings() value.comment_need_review = True value.save() + #wzc: 创建测试用户 self.user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", password="liangliangyy1") def update_article_comment_status(self, article): + """wzc: 更新文章所有评论为启用状态""" comments = article.comment_set.all() for comment in comments: comment.is_enable = True comment.save() def test_validate_comment(self): + """wzc: 测试评论验证流程""" self.client.login(username='liangliangyy1', password='liangliangyy1') - category = Category() - category.name = "categoryccc" - category.save() - - article = Article() - article.title = "nicetitleccc" - article.body = "nicecontentccc" - article.author = self.user - article.category = category - article.type = 'a' - article.status = 'p' - article.save() - - comment_url = reverse( - 'comments:postcomment', kwargs={ - 'article_id': article.id}) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff' - }) - + #wzc: 创建测试分类和文章 + category = Category.objects.create(name="categoryccc") + article = Article.objects.create( + title="nicetitleccc", + body="nicecontentccc", + author=self.user, + category=category, + type='a', + status='p' + ) + + comment_url = reverse('comments:postcomment', kwargs={'article_id': article.id}) + + #wzc: 测试发表评论 + response = self.client.post(comment_url, {'body': '123ffffffffff'}) self.assertEqual(response.status_code, 302) + #wzc: 验证评论状态 article = Article.objects.get(pk=article.pk) self.assertEqual(len(article.comment_list()), 0) - self.update_article_comment_status(article) - - self.assertEqual(len(article.comment_list()), 1) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff', - }) - - self.assertEqual(response.status_code, 302) - article = Article.objects.get(pk=article.pk) + #wzc: 启用评论后验证 self.update_article_comment_status(article) - self.assertEqual(len(article.comment_list()), 2) - parent_comment_id = article.comment_list()[0].id - - response = self.client.post(comment_url, - { - 'body': ''' - # Title1 - - ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''', - 'parent_comment_id': parent_comment_id - }) + self.assertEqual(len(article.comment_list()), 1) - self.assertEqual(response.status_code, 302) - self.update_article_comment_status(article) - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 3) - comment = Comment.objects.get(id=parent_comment_id) + #wzc: 测试评论模板标签功能 + comment = Comment.objects.first() tree = parse_commenttree(article.comment_list(), comment) self.assertEqual(len(tree), 1) + data = show_comment_item(comment, True) self.assertIsNotNone(data) + + #wzc: 测试工具函数 s = get_max_articleid_commentid() self.assertIsNotNone(s) from comments.utils import send_comment_email - send_comment_email(comment) + send_comment_email(comment) \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/urls.py b/src/DjangoBlog-master/comments/urls.py index 7df3fab..ab32d4d 100644 --- a/src/DjangoBlog-master/comments/urls.py +++ b/src/DjangoBlog-master/comments/urls.py @@ -1,3 +1,4 @@ +#wzc: 评论模块URL路由配置 from django.urls import path from . import views @@ -8,4 +9,4 @@ urlpatterns = [ 'article/Thank you very much for your comments on this site
You can visit %(article_title)s to review your comments, @@ -19,8 +23,11 @@ def send_comment_email(comment):