/postcomment',
- views.CommentPostView.as_view(),
- name='postcomment'),
-]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/utils.py b/src/DjangoBlog-master/DjangoBlog-master/comments/utils.py
deleted file mode 100644
index f01dba7..0000000
--- a/src/DjangoBlog-master/DjangoBlog-master/comments/utils.py
+++ /dev/null
@@ -1,38 +0,0 @@
-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')
- article_url = f"https://{site}{comment.article.get_absolute_url()}"
- html_content = _("""Thank you very much for your comments on this site
- You can visit %(article_title)s
- to review your comments,
- Thank you again!
-
- 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([tomail], subject, html_content)
- try:
- if comment.parent_comment:
- html_content = _("""Your comment on %(article_title)s
has
- received a reply.
%(comment_body)s
-
- go check it out!
-
- 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)
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/views.py b/src/DjangoBlog-master/DjangoBlog-master/comments/views.py
deleted file mode 100644
index ad9b2b9..0000000
--- a/src/DjangoBlog-master/DjangoBlog-master/comments/views.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# Create your views here.
-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
-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
-
-
-class CommentPostView(FormView):
- form_class = CommentForm
- template_name = 'blog/article_detail.html'
-
- @method_decorator(csrf_protect)
- def dispatch(self, *args, **kwargs):
- return super(CommentPostView, self).dispatch(*args, **kwargs)
-
- def get(self, request, *args, **kwargs):
- article_id = self.kwargs['article_id']
- article = get_object_or_404(Article, pk=article_id)
- url = article.get_absolute_url()
- return HttpResponseRedirect(url + "#comments")
-
- def form_invalid(self, form):
- 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
- author = BlogUser.objects.get(pk=user.pk)
- 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
-
- if form.cleaned_data['parent_comment_id']:
- parent_comment = Comment.objects.get(
- pk=form.cleaned_data['parent_comment_id'])
- comment.parent_comment = parent_comment
-
- comment.save(True)
- return HttpResponseRedirect(
- "%s#div-comment-%d" %
- (article.get_absolute_url(), comment.pk))
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/__init__.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/__init__.py
index 1e205f4..e6e817c 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/__init__.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/__init__.py
@@ -1 +1,4 @@
default_app_config = 'djangoblog.apps.DjangoblogAppConfig'
+# 默认应用配置:指定当前Django应用(djangoblog)对应的配置类
+# 配置类位于 djangoblog/apps.py 文件中的 DjangoBlogAppConfig 类
+
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/admin_site.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/admin_site.py
index f120405..82acd5d 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/admin_site.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/admin_site.py
@@ -1,64 +1,90 @@
+# 导入Django内置的AdminSite(后台管理站点基类)
from django.contrib.admin import AdminSite
+# 导入日志条目模型(记录后台操作日志)
from django.contrib.admin.models import LogEntry
+# 导入站点管理类和站点模型(Django内置的多站点配置)
from django.contrib.sites.admin import SiteAdmin
from django.contrib.sites.models import Site
-from accounts.admin import *
-from blog.admin import *
-from blog.models import *
-from comments.admin import *
-from comments.models import *
-from djangoblog.logentryadmin import LogEntryAdmin
-from oauth.admin import *
-from oauth.models import *
-from owntracks.admin import *
-from owntracks.models import *
-from servermanager.admin import *
-from servermanager.models import *
-
-
+# 导入各个应用的Admin类和模型(项目内自定义的后台配置)
+from accounts.admin import * # 账号相关的后台管理配置
+from blog.admin import * # 博客核心功能的后台管理配置
+from blog.models import * # 博客核心模型(文章、分类等)
+from comments.admin import * # 评论功能的后台管理配置
+from comments.models import *# 评论模型
+from djangoblog.logentryadmin import LogEntryAdmin # 自定义的日志条目管理类
+from oauth.admin import * # OAuth第三方登录的后台管理配置
+from oauth.models import * # OAuth相关模型
+from owntracks.admin import *# OwnTracks(位置追踪)的后台管理配置
+from owntracks.models import *# OwnTracks相关模型
+from servermanager.admin import *# 服务器管理的后台管理配置
+from servermanager.models import *# 服务器管理相关模型
+
+
+# 自定义Django后台管理站点类,继承自AdminSite
class DjangoBlogAdminSite(AdminSite):
+ # 后台页面顶部的标题
site_header = 'djangoblog administration'
+ # 浏览器标签页的标题
site_title = 'djangoblog site admin'
+ # 初始化方法
def __init__(self, name='admin'):
+ # 调用父类AdminSite的初始化方法
super().__init__(name)
+ # 权限验证:判断用户是否有权限访问后台
def has_permission(self, request):
+ # 只有超级用户才能访问后台
return request.user.is_superuser
+ # (注释掉的代码)自定义后台URL路由
# def get_urls(self):
+ # # 先获取父类默认的URL
# urls = super().get_urls()
+ # # 导入path用于定义路由
# from django.urls import path
+ # # 导入自定义的视图函数(刷新缓存)
# from blog.views import refresh_memcache
#
+ # # 自定义路由:后台添加“刷新缓存”的功能入口
# my_urls = [
# path('refresh/', self.admin_view(refresh_memcache), name="refresh"),
# ]
+ # # 合并默认路由和自定义路由
# return urls + my_urls
+# 实例化自定义的后台管理站点
admin_site = DjangoBlogAdminSite(name='admin')
-admin_site.register(Article, ArticlelAdmin)
-admin_site.register(Category, CategoryAdmin)
-admin_site.register(Tag, TagAdmin)
-admin_site.register(Links, LinksAdmin)
-admin_site.register(SideBar, SideBarAdmin)
-admin_site.register(BlogSettings, BlogSettingsAdmin)
+# 注册博客核心模型到后台管理,并指定对应的Admin类(配置模型在后台的展示、操作)
+admin_site.register(Article, ArticlelAdmin) # 文章模型
+admin_site.register(Category, CategoryAdmin)# 分类模型
+admin_site.register(Tag, TagAdmin) # 标签模型
+admin_site.register(Links, LinksAdmin) # 友情链接模型
+admin_site.register(SideBar, SideBarAdmin) # 侧边栏模型
+admin_site.register(BlogSettings, BlogSettingsAdmin)# 博客设置模型
-admin_site.register(commands, CommandsAdmin)
-admin_site.register(EmailSendLog, EmailSendLogAdmin)
+# 注册服务器管理相关模型
+admin_site.register(commands, CommandsAdmin)# 命令模型
+admin_site.register(EmailSendLog, EmailSendLogAdmin)# 邮件发送日志模型
-admin_site.register(BlogUser, BlogUserAdmin)
+# 注册账号相关模型
+admin_site.register(BlogUser, BlogUserAdmin)# 博客用户模型
-admin_site.register(Comment, CommentAdmin)
+# 注册评论相关模型
+admin_site.register(Comment, CommentAdmin)# 评论模型
-admin_site.register(OAuthUser, OAuthUserAdmin)
-admin_site.register(OAuthConfig, OAuthConfigAdmin)
+# 注册OAuth第三方登录相关模型
+admin_site.register(OAuthUser, OAuthUserAdmin)# OAuth用户模型
+admin_site.register(OAuthConfig, OAuthConfigAdmin)# OAuth配置模型
-admin_site.register(OwnTrackLog, OwnTrackLogsAdmin)
+# 注册OwnTracks位置追踪相关模型
+admin_site.register(OwnTrackLog, OwnTrackLogsAdmin)# 位置追踪日志模型
+# 注册Django内置的站点模型(多站点配置)
admin_site.register(Site, SiteAdmin)
-admin_site.register(LogEntry, LogEntryAdmin)
+# 注册日志条目模型(后台操作日志),使用自定义的LogEntryAdmin
+admin_site.register(LogEntry, LogEntryAdmin)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/apps.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/apps.py
index d29e318..e45b05d 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/apps.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/apps.py
@@ -1,11 +1,20 @@
+# 导入Django的应用配置基类AppConfig
from django.apps import AppConfig
+# 定义当前应用(djangoblog)的配置类,继承自AppConfig
class DjangoblogAppConfig(AppConfig):
+ # 指定模型主键的默认类型为BigAutoField(大整数自增主键)
+ # 替代旧版默认的AutoField,支持更大的数值范围
default_auto_field = 'django.db.models.BigAutoField'
+ # 当前应用的名称(必须与项目中应用的目录名一致)
name = 'djangoblog'
+ # 应用启动时自动执行的方法(Django加载完应用后触发)
def ready(self):
+ # 先调用父类的ready方法,确保基础初始化完成
super().ready()
- # Import and load plugins here
+ # 在这里导入并加载插件(应用启动时自动加载插件逻辑)
+ # 从当前应用的plugin_manage模块中导入load_plugins函数
from .plugin_manage.loader import load_plugins
- load_plugins()
\ No newline at end of file
+ # 执行插件加载操作
+ load_plugins()
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/blog_signals.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/blog_signals.py
index 393f441..b486bf6 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/blog_signals.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/blog_signals.py
@@ -1,69 +1,91 @@
+# 导入线程模块(用于异步执行任务)
import _thread
+# 导入日志模块(记录运行信息/错误)
import logging
+# 导入Django信号相关工具
import django.dispatch
-from django.conf import settings
-from django.contrib.admin.models import LogEntry
+from django.conf import settings # 导入Django项目配置
+from django.contrib.admin.models import LogEntry # 后台操作日志模型
+# 导入用户登录/登出的内置信号
from django.contrib.auth.signals import user_logged_in, user_logged_out
+# 导入Django邮件发送工具(支持多格式邮件)
from django.core.mail import EmailMultiAlternatives
+# 导入模型保存后的内置信号
from django.db.models.signals import post_save
-from django.dispatch import receiver
+from django.dispatch import receiver # 信号接收器装饰器
-from comments.models import Comment
-from comments.utils import send_comment_email
-from djangoblog.spider_notify import SpiderNotify
+# 导入自定义模型和工具函数
+from comments.models import Comment # 评论模型
+from comments.utils import send_comment_email # 发送评论通知邮件的工具
+from djangoblog.spider_notify import SpiderNotify # 爬虫通知工具(如百度收录推送)
+# 导入缓存操作、缓存过期等工具函数
from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache
-from djangoblog.utils import get_current_site
-from oauth.models import OAuthUser
+from djangoblog.utils import get_current_site # 获取当前站点域名
+from oauth.models import OAuthUser # OAuth第三方用户模型
+# 创建当前模块的日志对象
logger = logging.getLogger(__name__)
+
+# 自定义信号1:OAuth用户登录信号(携带参数id)
oauth_user_login_signal = django.dispatch.Signal(['id'])
-send_email_signal = django.dispatch.Signal(
- ['emailto', 'title', 'content'])
+# 自定义信号2:发送邮件信号(携带参数:收件人、标题、内容)
+send_email_signal = django.dispatch.Signal(['emailto', 'title', 'content'])
+# 监听send_email_signal信号的处理器
@receiver(send_email_signal)
def send_email_signal_handler(sender, **kwargs):
- emailto = kwargs['emailto']
- title = kwargs['title']
- content = kwargs['content']
+ # 从信号参数中提取邮件信息
+ emailto = kwargs['emailto'] # 收件人列表
+ title = kwargs['title'] # 邮件标题
+ content = kwargs['content'] # 邮件内容(HTML格式)
+ # 构造多格式邮件对象
msg = EmailMultiAlternatives(
- title,
- content,
- from_email=settings.DEFAULT_FROM_EMAIL,
- to=emailto)
- msg.content_subtype = "html"
-
+ title, # 邮件标题
+ content, # 邮件内容(文本/HTML)
+ from_email=settings.DEFAULT_FROM_EMAIL, # 发件人(从配置中读取)
+ to=emailto # 收件人列表
+ )
+ msg.content_subtype = "html" # 指定邮件内容为HTML格式
+
+ # 记录邮件发送日志到数据库
from servermanager.models import EmailSendLog
log = EmailSendLog()
log.title = title
log.content = content
- log.emailto = ','.join(emailto)
+ log.emailto = ','.join(emailto) # 把收件人列表转成字符串存储
try:
+ # 发送邮件,返回成功发送的数量
result = msg.send()
- log.send_result = result > 0
+ log.send_result = result > 0 # 发送成功则标记为True
except Exception as e:
+ # 发送失败时记录错误日志
logger.error(f"失败邮箱号: {emailto}, {e}")
log.send_result = False
- log.save()
+ log.save() # 保存日志记录
+# 监听oauth_user_login_signal信号的处理器
@receiver(oauth_user_login_signal)
def oauth_user_login_signal_handler(sender, **kwargs):
- id = kwargs['id']
- oauthuser = OAuthUser.objects.get(id=id)
- site = get_current_site().domain
+ id = kwargs['id'] # 从信号参数中提取OAuth用户ID
+ oauthuser = OAuthUser.objects.get(id=id) # 获取对应的OAuth用户对象
+ site = get_current_site().domain # 获取当前站点的域名
+
+ # 若用户头像链接不是本站域名,则下载并保存到本地
if oauthuser.picture and not oauthuser.picture.find(site) >= 0:
from djangoblog.utils import save_user_avatar
- oauthuser.picture = save_user_avatar(oauthuser.picture)
- oauthuser.save()
+ oauthuser.picture = save_user_avatar(oauthuser.picture) # 下载并替换头像链接
+ oauthuser.save() # 保存修改
- delete_sidebar_cache()
+ delete_sidebar_cache() # 清除侧边栏缓存(避免显示旧数据)
+# 监听所有模型post_save信号的处理器(模型保存后触发)
@receiver(post_save)
def model_post_save_callback(
sender,
@@ -73,50 +95,71 @@ def model_post_save_callback(
using,
update_fields,
**kwargs):
- clearcache = False
+ clearcache = False # 标记是否需要清除缓存
+
+ # 排除LogEntry(后台操作日志),不处理它的保存事件
if isinstance(instance, LogEntry):
return
+
+ # 若模型实例有get_full_url方法(表示是可访问的内容,如文章)
if 'get_full_url' in dir(instance):
+ # 判断是否仅更新了views(阅读量)字段
is_update_views = update_fields == {'views'}
+ # 非测试环境 + 不是仅更新阅读量 → 通知爬虫(如百度)收录新内容
if not settings.TESTING and not is_update_views:
try:
- notify_url = instance.get_full_url()
- SpiderNotify.baidu_notify([notify_url])
+ notify_url = instance.get_full_url() # 获取内容的完整URL
+ SpiderNotify.baidu_notify([notify_url]) # 通知百度爬虫
except Exception as ex:
- logger.error("notify sipder", ex)
+ logger.error("notify sipder", ex) # 通知失败记录错误
+
+ # 不是仅更新阅读量 → 需要清除缓存
if not is_update_views:
clearcache = True
+ # 若保存的是Comment(评论)实例
if isinstance(instance, Comment):
+ # 仅当评论是启用状态时处理
if instance.is_enable:
+ # 获取评论对应的文章URL
path = instance.article.get_absolute_url()
- site = get_current_site().domain
+ site = get_current_site().domain # 获取当前站点域名
+ # 处理带端口的域名(如localhost:8000 → 取localhost)
if site.find(':') > 0:
site = site[0:site.find(':')]
+ # 过期文章详情页的缓存(确保显示最新评论)
expire_view_cache(
path,
servername=site,
serverport=80,
- key_prefix='blogdetail')
+ key_prefix='blogdetail'
+ )
+ # 清除SEO处理器的缓存
if cache.get('seo_processor'):
cache.delete('seo_processor')
+ # 清除该文章的评论缓存
comment_cache_key = 'article_comments_{id}'.format(
id=instance.article.id)
cache.delete(comment_cache_key)
- delete_sidebar_cache()
+ delete_sidebar_cache() # 清除侧边栏缓存
+ # 清除评论列表的视图缓存
delete_view_cache('article_comments', [str(instance.article.pk)])
+ # 启动新线程异步发送评论通知邮件(避免阻塞主流程)
_thread.start_new_thread(send_comment_email, (instance,))
+ # 需要清除缓存时,清空整个缓存
if clearcache:
cache.clear()
+# 监听用户登录/登出信号的处理器
@receiver(user_logged_in)
@receiver(user_logged_out)
def user_auth_callback(sender, request, user, **kwargs):
+ # 用户存在且有用户名时处理
if user and user.username:
- logger.info(user)
- delete_sidebar_cache()
- # cache.clear()
+ logger.info(user) # 记录用户登录/登出日志
+ delete_sidebar_cache() # 清除侧边栏缓存(避免显示旧的用户相关内容)
+ # cache.clear() # (注释)可选择清空整个缓存,此处未启用
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/elasticsearch_backend.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/elasticsearch_backend.py
index 4afe498..cf3aea6 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/elasticsearch_backend.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/elasticsearch_backend.py
@@ -1,183 +1,144 @@
+# 导入Django字符串处理工具及Elasticsearch相关依赖
from django.utils.encoding import force_str
from elasticsearch_dsl import Q
+# 导入Haystack搜索引擎基础类和工具
from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query
from haystack.forms import ModelSearchForm
from haystack.models import SearchResult
from haystack.utils import log as logging
+# 导入博客相关的ES文档类、管理类和模型
from blog.documents import ArticleDocument, ArticleDocumentManager
from blog.models import Article
+# 创建日志对象
logger = logging.getLogger(__name__)
+# 自定义Elasticsearch搜索后端,继承Haystack基础搜索后端
class ElasticSearchBackend(BaseSearchBackend):
def __init__(self, connection_alias, **connection_options):
- super(
- ElasticSearchBackend,
- self).__init__(
- connection_alias,
- **connection_options)
- self.manager = ArticleDocumentManager()
- self.include_spelling = True
+ super().__init__(connection_alias,** connection_options)
+ self.manager = ArticleDocumentManager() # 初始化文档管理器
+ self.include_spelling = True # 启用拼写建议功能
+ # 转换模型实例为ES文档格式
def _get_models(self, iterable):
- models = iterable if iterable and iterable[0] else Article.objects.all()
- docs = self.manager.convert_to_doc(models)
- return docs
+ models = iterable if (iterable and iterable[0]) else Article.objects.all()
+ return self.manager.convert_to_doc(models)
+ # 创建索引并批量导入文档
def _create(self, models):
self.manager.create_index()
- docs = self._get_models(models)
- self.manager.rebuild(docs)
+ self.manager.rebuild(self._get_models(models))
+ # 删除指定模型对应的ES文档
def _delete(self, models):
for m in models:
m.delete()
return True
+ # 重建索引,更新文档数据
def _rebuild(self, models):
models = models if models else Article.objects.all()
- docs = self.manager.convert_to_doc(models)
- self.manager.update_docs(docs)
+ self.manager.update_docs(self.manager.convert_to_doc(models))
+ # 批量更新ES文档
def update(self, index, iterable, commit=True):
+ self.manager.update_docs(self._get_models(iterable))
- models = self._get_models(iterable)
- self.manager.update_docs(models)
-
+ # 移除单个对象对应的ES文档
def remove(self, obj_or_string):
- models = self._get_models([obj_or_string])
- self._delete(models)
+ self._delete(self._get_models([obj_or_string]))
+ # 清空索引数据
def clear(self, models=None, commit=True):
self.remove(None)
@staticmethod
+ # 获取搜索推荐词,无推荐则返回原搜索词
def get_suggestion(query: str) -> str:
- """获取推荐词, 如果没有找到添加原搜索词"""
-
- search = ArticleDocument.search() \
- .query("match", body=query) \
- .suggest('suggest_search', query, term={'field': 'body'}) \
- .execute()
-
+ search = ArticleDocument.search().query("match", body=query) \
+ .suggest('suggest_search', query, term={'field': 'body'}).execute()
keywords = []
for suggest in search.suggest.suggest_search:
- if suggest["options"]:
- keywords.append(suggest["options"][0]["text"])
- else:
- keywords.append(suggest["text"])
-
+ keywords.append(suggest["options"][0]["text"] if suggest["options"] else suggest["text"])
return ' '.join(keywords)
+ # 核心搜索方法,带日志记录
@log_query
def search(self, query_string, **kwargs):
logger.info('search query_string:' + query_string)
+ start_offset, end_offset = kwargs.get('start_offset'), kwargs.get('end_offset')
- start_offset = kwargs.get('start_offset')
- end_offset = kwargs.get('end_offset')
-
- # 推荐词搜索
- if getattr(self, "is_suggest", None):
- suggestion = self.get_suggestion(query_string)
- else:
- suggestion = query_string
-
- q = Q('bool',
- should=[Q('match', body=suggestion), Q('match', title=suggestion)],
- minimum_should_match="70%")
-
- search = ArticleDocument.search() \
- .query('bool', filter=[q]) \
- .filter('term', status='p') \
- .filter('term', type='a') \
- .source(False)[start_offset: end_offset]
+ # 处理搜索推荐词
+ suggestion = self.get_suggestion(query_string) if getattr(self, "is_suggest", None) else query_string
+ # 构建搜索条件:匹配正文和标题,最低匹配度70%
+ q = Q('bool', should=[Q('match', body=suggestion), Q('match', title=suggestion)], minimum_should_match="70%")
+ # 执行搜索:筛选已发布文章,指定结果范围
+ search = ArticleDocument.search().query('bool', filter=[q]) \
+ .filter('term', status='p').filter('term', type='a').source(False)[start_offset: end_offset]
results = search.execute()
hits = results['hits'].total
raw_results = []
+
+ # 格式化搜索结果为Haystack的SearchResult格式
for raw_result in results['hits']['hits']:
- app_label = 'blog'
- model_name = 'Article'
- additional_fields = {}
-
- result_class = SearchResult
-
- result = result_class(
- app_label,
- model_name,
- raw_result['_id'],
- raw_result['_score'],
- **additional_fields)
+ result = SearchResult('blog', 'Article', raw_result['_id'], raw_result['_score'])
raw_results.append(result)
- facets = {}
- spelling_suggestion = None if query_string == suggestion else suggestion
- return {
- 'results': raw_results,
- 'hits': hits,
- 'facets': facets,
- 'spelling_suggestion': spelling_suggestion,
- }
+ # 返回搜索结果、总数、推荐词等
+ spelling_suggestion = None if query_string == suggestion else suggestion
+ return {'results': raw_results, 'hits': hits, 'facets': {}, 'spelling_suggestion': spelling_suggestion}
+# 自定义搜索查询类,继承Haystack基础查询类
class ElasticSearchQuery(BaseSearchQuery):
+ # 转换时间格式适配搜索
def _convert_datetime(self, date):
- if hasattr(date, 'hour'):
- return force_str(date.strftime('%Y%m%d%H%M%S'))
- else:
- return force_str(date.strftime('%Y%m%d000000'))
+ fmt = '%Y%m%d%H%M%S' if hasattr(date, 'hour') else '%Y%m%d000000'
+ return force_str(date.strftime(fmt))
+ # 清洗查询语句,处理保留词和字符
def clean(self, query_fragment):
- """
- Provides a mechanism for sanitizing user input before presenting the
- value to the backend.
-
- Whoosh 1.X differs here in that you can no longer use a backslash
- to escape reserved characters. Instead, the whole word should be
- quoted.
- """
- words = query_fragment.split()
cleaned_words = []
-
- for word in words:
+ for word in query_fragment.split():
if word in self.backend.RESERVED_WORDS:
- word = word.replace(word, word.lower())
-
+ word = word.lower()
for char in self.backend.RESERVED_CHARACTERS:
if char in word:
- word = "'%s'" % word
+ word = f"'{word}'"
break
-
cleaned_words.append(word)
-
return ' '.join(cleaned_words)
+ # 构建查询片段
def build_query_fragment(self, field, filter_type, value):
return value.query_string
+ # 获取搜索结果总数
def get_count(self):
- results = self.get_results()
- return len(results) if results else 0
+ return len(self.get_results()) if self.get_results() else 0
+ # 获取拼写建议
def get_spelling_suggestion(self, preferred_query=None):
return self._spelling_suggestion
+ # 构建查询参数
def build_params(self, spelling_query=None):
- kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query)
- return kwargs
+ return super().build_params(spelling_query=spelling_query)
+# 自定义搜索表单,继承Haystack模型搜索表单
class ElasticSearchModelSearchForm(ModelSearchForm):
-
+ # 重写搜索方法,控制是否启用搜索建议
def search(self):
- # 是否建议搜索
self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no"
- sqs = super().search()
- return sqs
+ return super().search()
+# 自定义搜索引擎引擎类,指定后端和查询类
class ElasticSearchEngine(BaseEngine):
backend = ElasticSearchBackend
- query = ElasticSearchQuery
+ query = ElasticSearchQuery
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/feeds.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/feeds.py
index 8c4e851..6f164c8 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/feeds.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/feeds.py
@@ -1,40 +1,51 @@
+# 导入用户模型、RSS订阅核心类等依赖
from django.contrib.auth import get_user_model
-from django.contrib.syndication.views import Feed
-from django.utils import timezone
-from django.utils.feedgenerator import Rss201rev2Feed
+from django.contrib.syndication.views import Feed # Django RSS订阅基础类
+from django.utils import timezone # 时间处理工具
+from django.utils.feedgenerator import Rss201rev2Feed # RSS2.0格式生成器
+# 导入博客文章模型和Markdown转换工具
from blog.models import Article
from djangoblog.utils import CommonMarkdown
+# 自定义博客RSS订阅类,继承Django的Feed基类
class DjangoBlogFeed(Feed):
- feed_type = Rss201rev2Feed
+ feed_type = Rss201rev2Feed # 指定订阅源格式为RSS2.0
- description = '大巧无工,重剑无锋.'
- title = "且听风吟 大巧无工,重剑无锋. "
- link = "/feed/"
+ description = '大巧无工,重剑无锋.' # 订阅源描述
+ title = "且听风吟 大巧无工,重剑无锋. " # 订阅源标题
+ link = "/feed/" # 订阅源的链接地址
+ # 订阅源作者名称,取第一个用户的昵称
def author_name(self):
return get_user_model().objects.first().nickname
+ # 订阅源作者的链接,取第一个用户的个人页面地址
def author_link(self):
return get_user_model().objects.first().get_absolute_url()
+ # 订阅源的内容项:获取5篇已发布的文章,按发布时间倒序
def items(self):
return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5]
+ # 单个内容项的标题(对应文章标题)
def item_title(self, item):
return item.title
+ # 单个内容项的描述,将文章正文Markdown格式转为HTML
def item_description(self, item):
return CommonMarkdown.get_markdown(item.body)
+ # 订阅源的版权信息,动态显示当前年份
def feed_copyright(self):
now = timezone.now()
return "Copyright© {year} 且听风吟".format(year=now.year)
+ # 单个内容项的链接(对应文章详情页地址)
def item_link(self, item):
return item.get_absolute_url()
+ # 单个内容项的唯一标识(此处未实现,可补充文章ID等作为标识)
def item_guid(self, item):
- return
+ return
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/logentryadmin.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/logentryadmin.py
index 2f6a535..4571e66 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/logentryadmin.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/logentryadmin.py
@@ -1,91 +1,77 @@
+# 导入Django后台管理及相关工具类
from django.contrib import admin
-from django.contrib.admin.models import DELETION
-from django.contrib.contenttypes.models import ContentType
-from django.urls import reverse, NoReverseMatch
-from django.utils.encoding import force_str
-from django.utils.html import escape
-from django.utils.safestring import mark_safe
-from django.utils.translation import gettext_lazy as _
+from django.contrib.admin.models import DELETION # 操作类型:删除
+from django.contrib.contenttypes.models import ContentType # 内容类型模型
+from django.urls import reverse, NoReverseMatch # URL反向解析相关
+from django.utils.encoding import force_str # 字符串编码处理
+from django.utils.html import escape # HTML转义
+from django.utils.safestring import mark_safe # 标记安全HTML内容
+from django.utils.translation import gettext_lazy as _ # 国际化翻译
+# 自定义后台操作日志管理类,控制LogEntry模型在后台的展示与操作
class LogEntryAdmin(admin.ModelAdmin):
- list_filter = [
- 'content_type'
- ]
-
- search_fields = [
- 'object_repr',
- 'change_message'
- ]
-
- list_display_links = [
- 'action_time',
- 'get_change_message',
- ]
- list_display = [
- 'action_time',
- 'user_link',
- 'content_type',
- 'object_link',
- 'get_change_message',
- ]
+ # 侧边栏筛选条件:按内容类型筛选
+ list_filter = ['content_type']
+ # 搜索字段:按对象描述和操作信息搜索
+ search_fields = ['object_repr', 'change_message']
+ # 列表页可点击跳转的字段
+ list_display_links = ['action_time', 'get_change_message']
+ # 列表页展示的字段:操作时间、操作用户、内容类型、操作对象、操作信息
+ list_display = ['action_time', 'user_link', 'content_type', 'object_link', 'get_change_message']
+ # 禁用添加权限:操作日志不可手动添加
def has_add_permission(self, request):
return False
+ # 限制修改权限:仅超级用户或有权限用户可查看,禁止POST提交修改
def has_change_permission(self, request, obj=None):
- return (
- request.user.is_superuser or
- request.user.has_perm('admin.change_logentry')
- ) and request.method != 'POST'
+ return (request.user.is_superuser or request.user.has_perm('admin.change_logentry')) and request.method != 'POST'
+ # 禁用删除权限:操作日志不可删除
def has_delete_permission(self, request, obj=None):
return False
+ # 生成操作对象的链接(非删除操作时)
def object_link(self, obj):
- object_link = escape(obj.object_repr)
+ object_link = escape(obj.object_repr) # 转义对象描述,避免XSS
content_type = obj.content_type
+ # 非删除操作且有内容类型时,尝试生成对象的后台编辑链接
if obj.action_flag != DELETION and content_type is not None:
- # try returning an actual link instead of object repr string
try:
- url = reverse(
- 'admin:{}_{}_change'.format(content_type.app_label,
- content_type.model),
- args=[obj.object_id]
- )
- object_link = '{}'.format(url, object_link)
- except NoReverseMatch:
+ url = reverse(f'admin:{content_type.app_label}_{content_type.model}_change', args=[obj.object_id])
+ object_link = f'{object_link}'
+ except NoReverseMatch: # 无法解析URL时,仅显示文本
pass
- return mark_safe(object_link)
+ return mark_safe(object_link) # 标记为安全HTML,允许页面渲染链接
- object_link.admin_order_field = 'object_repr'
- object_link.short_description = _('object')
+ object_link.admin_order_field = 'object_repr' # 支持按对象描述排序
+ object_link.short_description = _('object') # 字段显示名称
+ # 生成操作用户的后台编辑链接
def user_link(self, obj):
- content_type = ContentType.objects.get_for_model(type(obj.user))
- user_link = escape(force_str(obj.user))
+ content_type = ContentType.objects.get_for_model(type(obj.user)) # 获取用户模型的内容类型
+ user_link = escape(force_str(obj.user)) # 转义用户名
try:
- # try returning an actual link instead of object repr string
- url = reverse(
- 'admin:{}_{}_change'.format(content_type.app_label,
- content_type.model),
- args=[obj.user.pk]
- )
- user_link = '{}'.format(url, user_link)
+ # 生成用户的后台编辑URL
+ url = reverse(f'admin:{content_type.app_label}_{content_type.model}_change', args=[obj.user.pk])
+ user_link = f'{user_link}'
except NoReverseMatch:
pass
return mark_safe(user_link)
- user_link.admin_order_field = 'user'
- user_link.short_description = _('user')
+ user_link.admin_order_field = 'user' # 支持按用户排序
+ user_link.short_description = _('user') # 字段显示名称
+ # 优化查询:预加载content_type,减少数据库查询次数
def get_queryset(self, request):
- queryset = super(LogEntryAdmin, self).get_queryset(request)
+ queryset = super().get_queryset(request)
return queryset.prefetch_related('content_type')
+ # 移除批量删除操作:避免误删日志
def get_actions(self, request):
- actions = super(LogEntryAdmin, self).get_actions(request)
+ actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
- return actions
+ return actions
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py
deleted file mode 100644
index 2b4be5c..0000000
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-class BasePlugin:
- # 插件元数据
- PLUGIN_NAME = None
- PLUGIN_DESCRIPTION = None
- PLUGIN_VERSION = None
-
- def __init__(self):
- if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]):
- raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.")
- self.init_plugin()
- self.register_hooks()
-
- def init_plugin(self):
- """
- 插件初始化逻辑
- 子类可以重写此方法来实现特定的初始化操作
- """
- logger.info(f'{self.PLUGIN_NAME} initialized.')
-
- def register_hooks(self):
- """
- 注册插件钩子
- 子类可以重写此方法来注册特定的钩子
- """
- pass
-
- def get_plugin_info(self):
- """
- 获取插件信息
- :return: 包含插件元数据的字典
- """
- return {
- 'name': self.PLUGIN_NAME,
- 'description': self.PLUGIN_DESCRIPTION,
- 'version': self.PLUGIN_VERSION
- }
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py
deleted file mode 100644
index 6685b7c..0000000
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py
+++ /dev/null
@@ -1,7 +0,0 @@
-ARTICLE_DETAIL_LOAD = 'article_detail_load'
-ARTICLE_CREATE = 'article_create'
-ARTICLE_UPDATE = 'article_update'
-ARTICLE_DELETE = 'article_delete'
-
-ARTICLE_CONTENT_HOOK_NAME = "the_content"
-
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hooks.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hooks.py
deleted file mode 100644
index d712540..0000000
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hooks.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import logging
-
-logger = logging.getLogger(__name__)
-
-_hooks = {}
-
-
-def register(hook_name: str, callback: callable):
- """
- 注册一个钩子回调。
- """
- if hook_name not in _hooks:
- _hooks[hook_name] = []
- _hooks[hook_name].append(callback)
- logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'")
-
-
-def run_action(hook_name: str, *args, **kwargs):
- """
- 执行一个 Action Hook。
- 它会按顺序执行所有注册到该钩子上的回调函数。
- """
- if hook_name in _hooks:
- logger.debug(f"Running action hook '{hook_name}'")
- for callback in _hooks[hook_name]:
- try:
- callback(*args, **kwargs)
- except Exception as e:
- logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True)
-
-
-def apply_filters(hook_name: str, value, *args, **kwargs):
- """
- 执行一个 Filter Hook。
- 它会把 value 依次传递给所有注册的回调函数进行处理。
- """
- if hook_name in _hooks:
- logger.debug(f"Applying filter hook '{hook_name}'")
- for callback in _hooks[hook_name]:
- try:
- value = callback(value, *args, **kwargs)
- except Exception as e:
- logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True)
- return value
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/loader.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/loader.py
deleted file mode 100644
index 12e824b..0000000
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/loader.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import os
-import logging
-from django.conf import settings
-
-logger = logging.getLogger(__name__)
-
-def load_plugins():
- """
- Dynamically loads and initializes plugins from the 'plugins' directory.
- This function is intended to be called when the Django app registry is ready.
- """
- for plugin_name in settings.ACTIVE_PLUGINS:
- plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name)
- if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')):
- try:
- __import__(f'plugins.{plugin_name}.plugin')
- logger.info(f"Successfully loaded plugin: {plugin_name}")
- except ImportError as e:
- logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py
index cd3babd..4571e66 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py
@@ -1,341 +1,77 @@
-"""
-Django settings for djangoblog project.
-
-Generated by 'django-admin startproject' using Django 1.10.2.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/1.10/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.10/ref/settings/
-"""
-import os
-import sys
-from pathlib import Path
-
-from django.utils.translation import gettext_lazy as _
-
-
-def env_to_bool(env, default):
- str_val = os.environ.get(env)
- return default if str_val is None else str_val == 'True'
-
-
-# Build paths inside the project like this: BASE_DIR / 'subdir'.
-BASE_DIR = Path(__file__).resolve().parent.parent
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = os.environ.get(
- 'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6'
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = env_to_bool('DJANGO_DEBUG', True)
-# DEBUG = False
-TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
-
-# ALLOWED_HOSTS = []
-ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com']
-# django 4.0新增配置
-CSRF_TRUSTED_ORIGINS = ['http://example.com']
-# Application definition
-
-
-INSTALLED_APPS = [
- # 'django.contrib.admin',
- 'django.contrib.admin.apps.SimpleAdminConfig',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'django.contrib.sites',
- 'django.contrib.sitemaps',
- 'mdeditor',
- 'haystack',
- 'blog',
- 'accounts',
- 'comments',
- 'oauth',
- 'servermanager',
- 'owntracks',
- 'compressor',
- 'djangoblog'
-]
-
-MIDDLEWARE = [
-
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.locale.LocaleMiddleware',
- 'django.middleware.gzip.GZipMiddleware',
- # 'django.middleware.cache.UpdateCacheMiddleware',
- 'django.middleware.common.CommonMiddleware',
- # 'django.middleware.cache.FetchFromCacheMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'django.middleware.http.ConditionalGetMiddleware',
- 'blog.middleware.OnlineMiddleware'
-]
-
-ROOT_URLCONF = 'djangoblog.urls'
-
-TEMPLATES = [
- {
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR, 'templates')],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- 'blog.context_processors.seo_processor'
- ],
- },
- },
-]
-
-WSGI_APPLICATION = 'djangoblog.wsgi.application'
-
-# Database
-# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
-
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'djangoblog',
- 'USER': 'root',
- 'PASSWORD': '123456',
- 'HOST': '127.0.0.1',
- 'PORT': 3306,
- }
-}
-
-# Password validation
-# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
-
-AUTH_PASSWORD_VALIDATORS = [
- {
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
- },
-]
-
-LANGUAGES = (
- ('en', _('English')),
- ('zh-hans', _('Simplified Chinese')),
- ('zh-hant', _('Traditional Chinese')),
-)
-LOCALE_PATHS = (
- os.path.join(BASE_DIR, 'locale'),
-)
-
-LANGUAGE_CODE = 'zh-hans'
-
-TIME_ZONE = 'Asia/Shanghai'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = False
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.10/howto/static-files/
-
-
-HAYSTACK_CONNECTIONS = {
- 'default': {
- 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine',
- 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
- },
-}
-# Automatically update searching index
-HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
-# Allow user login with username and password
-AUTHENTICATION_BACKENDS = [
- 'accounts.user_login_backend.EmailOrUsernameModelBackend']
-
-STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic')
-
-STATIC_URL = '/static/'
-STATICFILES = os.path.join(BASE_DIR, 'static')
-
-AUTH_USER_MODEL = 'accounts.BlogUser'
-LOGIN_URL = '/login/'
-
-TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
-DATE_TIME_FORMAT = '%Y-%m-%d'
-
-# bootstrap color styles
-BOOTSTRAP_COLOR_TYPES = [
- 'default', 'primary', 'success', 'info', 'warning', 'danger'
-]
-
-# paginate
-PAGINATE_BY = 10
-# http cache timeout
-CACHE_CONTROL_MAX_AGE = 2592000
-# cache setting
-CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'TIMEOUT': 10800,
- 'LOCATION': 'unique-snowflake',
- }
-}
-# 使用redis作为缓存
-if os.environ.get("DJANGO_REDIS_URL"):
- CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
- 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}',
- }
- }
-
-SITE_ID = 1
-BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \
- or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn'
-
-# Email:
-EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
-EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False)
-EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True)
-EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST') or 'smtp.mxhichina.com'
-EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT') or 465)
-EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER')
-EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD')
-DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
-SERVER_EMAIL = EMAIL_HOST_USER
-# Setting debug=false did NOT handle except email notifications
-ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')]
-# WX ADMIN password(Two times md5)
-WXADMIN = os.environ.get(
- 'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7'
-
-LOG_PATH = os.path.join(BASE_DIR, 'logs')
-if not os.path.exists(LOG_PATH):
- os.makedirs(LOG_PATH, exist_ok=True)
-
-LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'root': {
- 'level': 'INFO',
- 'handlers': ['console', 'log_file'],
- },
- 'formatters': {
- 'verbose': {
- 'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d %(module)s] %(message)s',
- }
- },
- 'filters': {
- 'require_debug_false': {
- '()': 'django.utils.log.RequireDebugFalse',
- },
- 'require_debug_true': {
- '()': 'django.utils.log.RequireDebugTrue',
- },
- },
- 'handlers': {
- 'log_file': {
- 'level': 'INFO',
- 'class': 'logging.handlers.TimedRotatingFileHandler',
- 'filename': os.path.join(LOG_PATH, 'djangoblog.log'),
- 'when': 'D',
- 'formatter': 'verbose',
- 'interval': 1,
- 'delay': True,
- 'backupCount': 5,
- 'encoding': 'utf-8'
- },
- 'console': {
- 'level': 'DEBUG',
- 'filters': ['require_debug_true'],
- 'class': 'logging.StreamHandler',
- 'formatter': 'verbose'
- },
- 'null': {
- 'class': 'logging.NullHandler',
- },
- 'mail_admins': {
- 'level': 'ERROR',
- 'filters': ['require_debug_false'],
- 'class': 'django.utils.log.AdminEmailHandler'
- }
- },
- 'loggers': {
- 'djangoblog': {
- 'handlers': ['log_file', 'console'],
- 'level': 'INFO',
- 'propagate': True,
- },
- 'django.request': {
- 'handlers': ['mail_admins'],
- 'level': 'ERROR',
- 'propagate': False,
- }
- }
-}
-
-STATICFILES_FINDERS = (
- 'django.contrib.staticfiles.finders.FileSystemFinder',
- 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
- # other
- 'compressor.finders.CompressorFinder',
-)
-COMPRESS_ENABLED = True
-# COMPRESS_OFFLINE = True
-
-
-COMPRESS_CSS_FILTERS = [
- # creates absolute urls from relative ones
- 'compressor.filters.css_default.CssAbsoluteFilter',
- # css minimizer
- 'compressor.filters.cssmin.CSSMinFilter'
-]
-COMPRESS_JS_FILTERS = [
- 'compressor.filters.jsmin.JSMinFilter'
-]
-
-MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
-MEDIA_URL = '/media/'
-X_FRAME_OPTIONS = 'SAMEORIGIN'
-
-DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
-
-if os.environ.get('DJANGO_ELASTICSEARCH_HOST'):
- ELASTICSEARCH_DSL = {
- 'default': {
- 'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST')
- },
- }
- HAYSTACK_CONNECTIONS = {
- 'default': {
- 'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine',
- },
- }
-
-# Plugin System
-PLUGINS_DIR = BASE_DIR / 'plugins'
-ACTIVE_PLUGINS = [
- 'article_copyright',
- 'reading_time',
- 'external_links',
- 'view_count',
- 'seo_optimizer'
-]
\ No newline at end of file
+# 导入Django后台管理及相关工具类
+from django.contrib import admin
+from django.contrib.admin.models import DELETION # 操作类型:删除
+from django.contrib.contenttypes.models import ContentType # 内容类型模型
+from django.urls import reverse, NoReverseMatch # URL反向解析相关
+from django.utils.encoding import force_str # 字符串编码处理
+from django.utils.html import escape # HTML转义
+from django.utils.safestring import mark_safe # 标记安全HTML内容
+from django.utils.translation import gettext_lazy as _ # 国际化翻译
+
+
+# 自定义后台操作日志管理类,控制LogEntry模型在后台的展示与操作
+class LogEntryAdmin(admin.ModelAdmin):
+ # 侧边栏筛选条件:按内容类型筛选
+ list_filter = ['content_type']
+ # 搜索字段:按对象描述和操作信息搜索
+ search_fields = ['object_repr', 'change_message']
+ # 列表页可点击跳转的字段
+ list_display_links = ['action_time', 'get_change_message']
+ # 列表页展示的字段:操作时间、操作用户、内容类型、操作对象、操作信息
+ list_display = ['action_time', 'user_link', 'content_type', 'object_link', 'get_change_message']
+
+ # 禁用添加权限:操作日志不可手动添加
+ def has_add_permission(self, request):
+ return False
+
+ # 限制修改权限:仅超级用户或有权限用户可查看,禁止POST提交修改
+ def has_change_permission(self, request, obj=None):
+ return (request.user.is_superuser or request.user.has_perm('admin.change_logentry')) and request.method != 'POST'
+
+ # 禁用删除权限:操作日志不可删除
+ def has_delete_permission(self, request, obj=None):
+ return False
+
+ # 生成操作对象的链接(非删除操作时)
+ def object_link(self, obj):
+ object_link = escape(obj.object_repr) # 转义对象描述,避免XSS
+ content_type = obj.content_type
+
+ # 非删除操作且有内容类型时,尝试生成对象的后台编辑链接
+ if obj.action_flag != DELETION and content_type is not None:
+ try:
+ url = reverse(f'admin:{content_type.app_label}_{content_type.model}_change', args=[obj.object_id])
+ object_link = f'{object_link}'
+ except NoReverseMatch: # 无法解析URL时,仅显示文本
+ pass
+ return mark_safe(object_link) # 标记为安全HTML,允许页面渲染链接
+
+ object_link.admin_order_field = 'object_repr' # 支持按对象描述排序
+ object_link.short_description = _('object') # 字段显示名称
+
+ # 生成操作用户的后台编辑链接
+ def user_link(self, obj):
+ content_type = ContentType.objects.get_for_model(type(obj.user)) # 获取用户模型的内容类型
+ user_link = escape(force_str(obj.user)) # 转义用户名
+ try:
+ # 生成用户的后台编辑URL
+ url = reverse(f'admin:{content_type.app_label}_{content_type.model}_change', args=[obj.user.pk])
+ user_link = f'{user_link}'
+ except NoReverseMatch:
+ pass
+ return mark_safe(user_link)
+
+ user_link.admin_order_field = 'user' # 支持按用户排序
+ user_link.short_description = _('user') # 字段显示名称
+
+ # 优化查询:预加载content_type,减少数据库查询次数
+ def get_queryset(self, request):
+ queryset = super().get_queryset(request)
+ return queryset.prefetch_related('content_type')
+
+ # 移除批量删除操作:避免误删日志
+ def get_actions(self, request):
+ actions = super().get_actions(request)
+ if 'delete_selected' in actions:
+ del actions['delete_selected']
+ return actions
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/sitemap.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/sitemap.py
index 8b7d446..2424190 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/sitemap.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/sitemap.py
@@ -5,55 +5,76 @@ from blog.models import Article, Category, Tag
class StaticViewSitemap(Sitemap):
- priority = 0.5
- changefreq = 'daily'
+ """静态视图站点地图类,用于处理没有对应模型数据的固定页面[1,2](@ref)"""
+
+ priority = 0.5 # 优先级,取值范围0.0-1.0,默认0.5[2,4](@ref)
+ changefreq = 'daily' # 内容更新频率,可选值:always/hourly/daily/weekly/monthly/yearly/never[2,6](@ref)
def items(self):
- return ['blog:index', ]
+ """返回要包含在站点地图中的URL名称列表[1,2](@ref)"""
+ return ['blog:index', ] # 这里只包含博客首页,可以添加其他静态页面如'about'、'contact'等
def location(self, item):
- return reverse(item)
+ """根据URL名称生成完整的URL路径[1,2](@ref)"""
+ return reverse(item) # 使用Django的reverse函数通过URL名称生成实际URL
class ArticleSiteMap(Sitemap):
- changefreq = "monthly"
- priority = "0.6"
+ """文章模型站点地图类,用于生成所有文章的站点地图条目[1,3](@ref)"""
+
+ changefreq = "monthly" # 文章内容通常每月更新
+ priority = "0.6" # 文章页面优先级较高,因为包含重要内容
def items(self):
- return Article.objects.filter(status='p')
+ """返回所有已发布的文章对象[3,5](@ref)"""
+ return Article.objects.filter(status='p') # 只筛选状态为'p'(已发布)的文章
def lastmod(self, obj):
- return obj.last_modify_time
+ """返回文章的最后修改时间[1,3](@ref)"""
+ return obj.last_modify_time # 使用文章的last_modify_time字段作为最后修改时间
class CategorySiteMap(Sitemap):
- changefreq = "Weekly"
- priority = "0.6"
+ """分类模型站点地图类,用于生成所有分类页面的站点地图[3](@ref)"""
+
+ changefreq = "Weekly" # 分类页面内容相对稳定,每周检查更新
+ priority = "0.6" # 分类页面有中等优先级
def items(self):
- return Category.objects.all()
+ """返回所有分类对象[3](@ref)"""
+ return Category.objects.all() # 包含所有分类
def lastmod(self, obj):
- return obj.last_modify_time
+ """返回分类的最后修改时间[3](@ref)"""
+ return obj.last_modify_time # 使用分类的last_modify_time字段
class TagSiteMap(Sitemap):
- changefreq = "Weekly"
- priority = "0.3"
+ """标签模型站点地图类,用于生成所有标签页面的站点地图[3](@ref)"""
+
+ changefreq = "Weekly" # 标签页面更新频率为每周
+ priority = "0.3" # 标签页面优先级较低
def items(self):
- return Tag.objects.all()
+ """返回所有标签对象[3](@ref)"""
+ return Tag.objects.all() # 包含所有标签
def lastmod(self, obj):
- return obj.last_modify_time
+ """返回标签的最后修改时间"""
+ return obj.last_modify_time # 使用标签的last_modify_time字段
class UserSiteMap(Sitemap):
- changefreq = "Weekly"
- priority = "0.3"
+ """用户站点地图类,用于生成用户相关页面的站点地图"""
+
+ changefreq = "Weekly" # 用户信息相对稳定,每周检查
+ priority = "0.3" # 用户页面优先级较低
def items(self):
+ """返回所有有文章的作者用户[7](@ref)"""
+ # 通过文章获取所有不重复的作者,确保只包含有文章的用户
return list(set(map(lambda x: x.author, Article.objects.all())))
def lastmod(self, obj):
- return obj.date_joined
+ """返回用户的注册时间[7](@ref)"""
+ return obj.date_joined # 使用用户的date_joined字段作为最后修改时间
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/spider_notify.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/spider_notify.py
index 7b909e9..8ddacff 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/spider_notify.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/spider_notify.py
@@ -1,21 +1,55 @@
import logging
-
import requests
from django.conf import settings
+# 获取当前模块的日志记录器,用于记录日志信息
logger = logging.getLogger(__name__)
class SpiderNotify():
+ """蜘蛛通知类,用于向搜索引擎推送URL,帮助搜索引擎发现和收录网站内容"""
+
@staticmethod
def baidu_notify(urls):
+ """
+ 向百度搜索引擎推送URL,促进网站收录
+
+ Args:
+ urls (list): 需要推送的URL列表,通常为新发布或更新的文章链接
+
+ Note:
+ 使用百度站长平台的API接口进行URL推送
+ 推送格式为每行一个URL的纯文本数据
+ """
try:
+ # 将URL列表转换为百度API要求的格式:每行一个URL的字符串
+ # 例如:['http://example.com/1', 'http://example.com/2'] -> "http://example.com/1\nhttp://example.com/2"
data = '\n'.join(urls)
+
+ # 发送POST请求到百度推送接口[8,9](@ref)
+ # 使用Django设置中配置的百度推送URL,避免硬编码
result = requests.post(settings.BAIDU_NOTIFY_URL, data=data)
+
+ # 记录推送结果到日志,便于监控和调试[6,7](@ref)
logger.info(result.text)
+
except Exception as e:
+ # 捕获并记录所有可能的异常,如网络错误、配置错误等
+ # 使用错误级别日志记录异常信息[6](@ref)
logger.error(e)
@staticmethod
def notify(url):
- SpiderNotify.baidu_notify(url)
+ """
+ 推送URL的便捷方法,可以扩展支持多个搜索引擎
+
+ Args:
+ url (str or list): 单个URL字符串或URL列表
+ """
+ # 如果传入的是单个URL,转换为列表形式
+ if isinstance(url, str):
+ url = [url]
+
+ # 调用百度推送方法
+ # 这里的设计便于未来扩展其他搜索引擎的推送功能
+ SpiderNotify.baidu_notify(url)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/tests.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/tests.py
index 01237d9..361ae0b 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/tests.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/tests.py
@@ -1,32 +1,29 @@
-from django.test import TestCase
+from django.test import TestCase # 导入Django测试框架的核心类
-from djangoblog.utils import *
+from djangoblog.utils import * # 导入需要测试的工具函数
class DjangoBlogTest(TestCase):
+ """Django博客工具函数测试类,继承自TestCase以获得Django测试框架的全部功能"""
+
def setUp(self):
+ """
+ 测试前置设置方法,在每个测试方法执行前自动调用
+ 用于初始化测试环境,如创建测试数据、配置设置等
+ 当前测试不需要特殊设置,所以使用pass跳过
+ """
pass
+ # 如果需要,可以在这里创建测试用的模型实例或设置测试环境
+ # 例如:self.user = User.objects.create_user(username='testuser', password='testpass')
def test_utils(self):
- md5 = get_sha256('test')
- self.assertIsNotNone(md5)
+ """测试工具函数的核心测试方法,包含多个工具函数的验证"""
+
+ # 测试SHA256哈希函数
+ md5 = get_sha256('test') # 对'test'字符串进行SHA256加密
+ self.assertIsNotNone(md5) # 断言结果不为None,验证函数正常工作
+ # 更完整的测试可以添加:self.assertEqual(len(md5), 64) # SHA256结果应为64字符
+
+ # 测试CommonMarkdown的Markdown解析功能
c = CommonMarkdown.get_markdown('''
- # Title1
-
- ```python
- import os
- ```
-
- [url](https://www.lylinux.net/)
-
- [ddd](http://www.baidu.com)
-
-
- ''')
- self.assertIsNotNone(c)
- d = {
- 'd': 'key1',
- 'd2': 'key2'
- }
- data = parse_dict_to_url(d)
- self.assertIsNotNone(data)
+ # Title1 # 一级标题
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/urls.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/urls.py
index 4aae58a..f387d67 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/urls.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/urls.py
@@ -1,64 +1,106 @@
-"""djangoblog URL Configuration
+"""
+djangoblog URL Configuration
+
+URL配置是Django网站的目录,本质是URL与视图函数之间的映射表。
+通过此文件告诉Django,对于哪个URL调用哪段代码。
-The `urlpatterns` list routes URLs to views. For more information please see:
+配置说明文档:
https://docs.djangoproject.com/en/1.10/topics/http/urls/
-Examples:
-Function views
- 1. Add an import: from my_app import views
- 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
-Class-based views
- 1. Add an import: from other_app.views import Home
- 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
-Including another URLconf
- 1. Import the include() function: from django.conf.urls import url, include
- 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
+
+示例:
+函数视图:
+ 1. 导入视图:from my_app import views
+ 2. 添加URL模式:url(r'^$', views.home, name='home')
+
+基于类的视图:
+ 1. 导入视图:from other_app.views import Home
+ 2. 添加URL模式:url(r'^$', Home.as_view(), name='home')
+
+包含其他URL配置:
+ 1. 导入include函数:from django.conf.urls import url, include
+ 2. 添加URL模式:url(r'^blog/', include('blog.urls'))
"""
from django.conf import settings
-from django.conf.urls.i18n import i18n_patterns
-from django.conf.urls.static import static
-from django.contrib.sitemaps.views import sitemap
+from django.conf.urls.i18n import i18n_patterns # 国际化URL模式支持
+from django.conf.urls.static import static # 静态文件服务
+from django.contrib.sitemaps.views import sitemap # 站点地图视图
from django.urls import path, include
-from django.urls import re_path
-from haystack.views import search_view_factory
+from django.urls import re_path # 正则表达式URL匹配
+from haystack.views import search_view_factory # Haystack搜索视图工厂
+# 导入自定义模块
from blog.views import EsSearchView
-from djangoblog.admin_site import admin_site
+from djangoblog.admin_site import admin_site # 自定义admin站点
from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm
-from djangoblog.feeds import DjangoBlogFeed
+from djangoblog.feeds import DjangoBlogFeed # RSS订阅源
from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap
+# 站点地图配置字典,定义不同类型的站点地图[1,3](@ref)
sitemaps = {
-
- 'blog': ArticleSiteMap,
- 'Category': CategorySiteMap,
- 'Tag': TagSiteMap,
- 'User': UserSiteMap,
- 'static': StaticViewSitemap
+ 'blog': ArticleSiteMap, # 文章站点地图
+ 'Category': CategorySiteMap, # 分类站点地图
+ 'Tag': TagSiteMap, # 标签站点地图
+ 'User': UserSiteMap, # 用户站点地图
+ 'static': StaticViewSitemap # 静态页面站点地图
}
-handler404 = 'blog.views.page_not_found_view'
-handler500 = 'blog.views.server_error_view'
-handle403 = 'blog.views.permission_denied_view'
+# 自定义错误处理视图[2](@ref)
+handler404 = 'blog.views.page_not_found_view' # 404页面未找到处理
+handler500 = 'blog.views.server_error_view' # 500服务器错误处理
+handle403 = 'blog.views.permission_denied_view' # 403权限拒绝处理
+# 基础URL模式配置
urlpatterns = [
+ # 国际化URL支持,提供语言切换功能[7,8](@ref)
path('i18n/', include('django.conf.urls.i18n')),
]
+
+# 使用i18n_patterns为URL添加语言前缀支持国际化[6,7](@ref)
urlpatterns += i18n_patterns(
+ # 管理员后台URL,使用自定义的admin_site[4](@ref)
re_path(r'^admin/', admin_site.urls),
+
+ # 博客应用URL,包含博客相关所有路由[1,4](@ref)
re_path(r'', include('blog.urls', namespace='blog')),
+
+ # Markdown编辑器URL[4](@ref)
re_path(r'mdeditor/', include('mdeditor.urls')),
+
+ # 评论系统URL[4](@ref)
re_path(r'', include('comments.urls', namespace='comment')),
+
+ # 用户账户URL[4](@ref)
re_path(r'', include('accounts.urls', namespace='account')),
+
+ # OAuth认证URL[4](@ref)
re_path(r'', include('oauth.urls', namespace='oauth')),
+
+ # 站点地图XML文件URL[1](@ref)
re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'),
+
+ # RSS订阅源URL(feed和rss两个端点)[1](@ref)
re_path(r'^feed/$', DjangoBlogFeed()),
re_path(r'^rss/$', DjangoBlogFeed()),
+
+ # 搜索功能URL,使用ElasticSearch作为后端[4](@ref)
re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm),
name='search'),
+
+ # 服务器管理URL[4](@ref)
re_path(r'', include('servermanager.urls', namespace='servermanager')),
- re_path(r'', include('owntracks.urls', namespace='owntracks'))
- , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+
+ # OwnTracks位置跟踪URL[4](@ref)
+ re_path(r'', include('owntracks.urls', namespace='owntracks')),
+
+ # 不强制为默认语言添加前缀[6,7](@ref)
+ prefix_default_language=False
+)
+
+# 静态文件服务配置(开发环境)[2](@ref)
++ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+
+# 开发环境下媒体文件服务配置[2](@ref)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
- document_root=settings.MEDIA_ROOT)
+ document_root=settings.MEDIA_ROOT)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/utils.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/utils.py
index 57f63dc..45b4cc2 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/utils.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/utils.py
@@ -1,7 +1,6 @@
#!/usr/bin/env python
# encoding: utf-8
-
import logging
import os
import random
@@ -9,41 +8,61 @@ import string
import uuid
from hashlib import sha256
-import bleach
-import markdown
-import requests
+import bleach # HTML清理库,用于防止XSS攻击
+import markdown # Markdown解析库
+import requests # HTTP请求库
from django.conf import settings
-from django.contrib.sites.models import Site
-from django.core.cache import cache
-from django.templatetags.static import static
+from django.contrib.sites.models import Site # Django站点框架
+from django.core.cache import cache # Django缓存框架
+from django.templatetags.static import static # 静态文件URL生成
logger = logging.getLogger(__name__)
def get_max_articleid_commentid():
+ """获取最大的文章ID和评论ID"""
from blog.models import Article
from comments.models import Comment
return (Article.objects.latest().pk, Comment.objects.latest().pk)
def get_sha256(str):
+ """计算字符串的SHA256哈希值
+
+ Args:
+ str: 要计算哈希的字符串
+
+ Returns:
+ str: 64位的十六进制哈希值
+ """
m = sha256(str.encode('utf-8'))
return m.hexdigest()
def cache_decorator(expiration=3 * 60):
+ """缓存装饰器,用于缓存函数结果
+
+ Args:
+ expiration: 缓存过期时间(秒),默认3分钟
+
+ Returns:
+ function: 装饰器函数
+ """
def wrapper(func):
def news(*args, **kwargs):
try:
+ # 尝试从视图类获取缓存键
view = args[0]
key = view.get_cache_key()
except:
key = None
if not key:
+ # 如果没有特定的缓存键,根据函数参数生成唯一键
unique_str = repr((func, args, kwargs))
-
m = sha256(unique_str.encode('utf-8'))
key = m.hexdigest()
+
+ # 尝试从缓存获取结果
value = cache.get(key)
if value is not None:
# logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key))
@@ -52,9 +71,8 @@ def cache_decorator(expiration=3 * 60):
else:
return value
else:
- logger.debug(
- 'cache_decorator set cache:%s key:%s' %
- (func.__name__, key))
+ # 缓存未命中,执行函数并缓存结果
+ logger.debug('cache_decorator set cache:%s key:%s' % (func.__name__, key))
value = func(*args, **kwargs)
if value is None:
cache.set(key, '__default_cache_value__', expiration)
@@ -68,21 +86,26 @@ def cache_decorator(expiration=3 * 60):
def expire_view_cache(path, servername, serverport, key_prefix=None):
- '''
- 刷新视图缓存
- :param path:url路径
- :param servername:host
- :param serverport:端口
- :param key_prefix:前缀
- :return:是否成功
+ '''刷新视图缓存
+
+ Args:
+ path: URL路径
+ servername: 服务器主机名
+ serverport: 服务器端口
+ key_prefix: 缓存键前缀
+
+ Returns:
+ bool: 是否成功删除
'''
from django.http import HttpRequest
from django.utils.cache import get_cache_key
+ # 创建模拟请求对象
request = HttpRequest()
request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport}
request.path = path
+ # 获取缓存键并删除
key = get_cache_key(request, key_prefix=key_prefix, cache=cache)
if key:
logger.info('expire_view_cache:get key:{path}'.format(path=path))
@@ -92,40 +115,62 @@ def expire_view_cache(path, servername, serverport, key_prefix=None):
return False
-@cache_decorator()
+@cache_decorator() # 应用缓存装饰器
def get_current_site():
+ """获取当前站点信息"""
site = Site.objects.get_current()
return site
class CommonMarkdown:
+ """Markdown处理工具类"""
+
@staticmethod
def _convert_markdown(value):
+ """内部方法:转换Markdown为HTML
+
+ Args:
+ value: Markdown格式文本
+
+ Returns:
+ tuple: (HTML内容, 目录)
+ """
+ # 配置Markdown扩展
md = markdown.Markdown(
extensions=[
- 'extra',
- 'codehilite',
- 'toc',
- 'tables',
+ 'extra', # 额外语法支持
+ 'codehilite', # 代码高亮
+ 'toc', # 目录生成
+ 'tables', # 表格支持
]
)
- body = md.convert(value)
- toc = md.toc
+ body = md.convert(value) # 转换Markdown为HTML
+ toc = md.toc # 获取目录
return body, toc
@staticmethod
def get_markdown_with_toc(value):
+ """获取带目录的Markdown转换结果"""
body, toc = CommonMarkdown._convert_markdown(value)
return body, toc
@staticmethod
def get_markdown(value):
+ """获取Markdown转换结果(不含目录)"""
body, toc = CommonMarkdown._convert_markdown(value)
return body
def send_email(emailto, title, content):
+ """发送邮件(通过信号机制)
+
+ Args:
+ emailto: 收件人邮箱
+ title: 邮件标题
+ content: 邮件内容
+ """
from djangoblog.blog_signals import send_email_signal
+ # 使用Django信号发送邮件,实现解耦[9,10,11](@ref)
send_email_signal.send(
send_email.__class__,
emailto=emailto,
@@ -134,11 +179,19 @@ def send_email(emailto, title, content):
def generate_code() -> str:
- """生成随机数验证码"""
+ """生成6位随机数字验证码"""
return ''.join(random.sample(string.digits, 6))
def parse_dict_to_url(dict):
+ """将字典转换为URL参数字符串
+
+ Args:
+ dict: 参数字典
+
+ Returns:
+ str: URL参数字符串
+ """
from urllib.parse import quote
url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/'))
for k, v in dict.items()])
@@ -146,12 +199,19 @@ def parse_dict_to_url(dict):
def get_blog_setting():
+ """获取博客设置,使用缓存提高性能[6,8](@ref)
+
+ Returns:
+ BlogSettings: 博客设置对象
+ """
value = cache.get('get_blog_setting')
if value:
return value
else:
+ # 缓存未命中,从数据库获取
from blog.models import BlogSettings
if not BlogSettings.objects.count():
+ # 如果不存在设置,创建默认设置
setting = BlogSettings()
setting.site_name = 'djangoblog'
setting.site_description = '基于Django的博客系统'
@@ -169,39 +229,44 @@ def get_blog_setting():
setting.save()
value = BlogSettings.objects.first()
logger.info('set cache get_blog_setting')
- cache.set('get_blog_setting', value)
+ cache.set('get_blog_setting', value) # 设置缓存
return value
def save_user_avatar(url):
- '''
- 保存用户头像
- :param url:头像url
- :return: 本地路径
+ '''保存用户头像到本地
+
+ Args:
+ url: 头像URL地址
+
+ Returns:
+ str: 本地静态文件路径
'''
logger.info(url)
try:
basedir = os.path.join(settings.STATICFILES, 'avatar')
- rsp = requests.get(url, timeout=2)
+ rsp = requests.get(url, timeout=2) # 下载头像
if rsp.status_code == 200:
if not os.path.exists(basedir):
- os.makedirs(basedir)
+ os.makedirs(basedir) # 创建目录
+ # 检查图片扩展名
image_extensions = ['.jpg', '.png', 'jpeg', '.gif']
isimage = len([i for i in image_extensions if url.endswith(i)]) > 0
ext = os.path.splitext(url)[1] if isimage else '.jpg'
- save_filename = str(uuid.uuid4().hex) + ext
+ save_filename = str(uuid.uuid4().hex) + ext # 生成唯一文件名
logger.info('保存用户头像:' + basedir + save_filename)
with open(os.path.join(basedir, save_filename), 'wb+') as file:
- file.write(rsp.content)
- return static('avatar/' + save_filename)
+ file.write(rsp.content) # 保存文件
+ return static('avatar/' + save_filename) # 返回静态文件URL
except Exception as e:
logger.error(e)
- return static('blog/img/avatar.png')
+ return static('blog/img/avatar.png') # 返回默认头像
def delete_sidebar_cache():
+ """删除侧边栏相关缓存"""
from blog.models import LinkShowType
keys = ["sidebar" + x for x in LinkShowType.values]
for k in keys:
@@ -210,12 +275,19 @@ def delete_sidebar_cache():
def delete_view_cache(prefix, keys):
+ """删除视图缓存
+
+ Args:
+ prefix: 缓存前缀
+ keys: 缓存键
+ """
from django.core.cache.utils import make_template_fragment_key
key = make_template_fragment_key(prefix, keys)
cache.delete(key)
def get_resource_url():
+ """获取资源URL基础路径"""
if settings.STATIC_URL:
return settings.STATIC_URL
else:
@@ -223,10 +295,19 @@ def get_resource_url():
return 'http://' + site.domain + '/static/'
-ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1',
- 'h2', 'p']
+# HTML标签和属性白名单,用于防止XSS攻击
+ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i',
+ 'li', 'ol', 'pre', 'strong', 'ul', 'h1', 'h2', 'p']
ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']}
def sanitize_html(html):
- return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)
+ """清理HTML,移除不安全的标签和属性
+
+ Args:
+ html: 要清理的HTML内容
+
+ Returns:
+ str: 安全的HTML内容
+ """
+ return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/whoosh_cn_backend.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/whoosh_cn_backend.py
index 04e3f7f..9ed86f6 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/whoosh_cn_backend.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/whoosh_cn_backend.py
@@ -22,7 +22,7 @@ from haystack.models import SearchResult
from haystack.utils import get_identifier, get_model_ct
from haystack.utils import log as logging
from haystack.utils.app_loading import haystack_get_model
-from jieba.analyse import ChineseAnalyzer
+from jieba.analyse import ChineseAnalyzer # 导入jieba中文分词器
from whoosh import index
from whoosh.analysis import StemmingAnalyzer
from whoosh.fields import BOOLEAN, DATETIME, IDLIST, KEYWORD, NGRAM, NGRAMWORDS, NUMERIC, Schema, TEXT
@@ -34,106 +34,102 @@ from whoosh.qparser import QueryParser
from whoosh.searching import ResultsPage
from whoosh.writing import AsyncWriter
+# 检查whoosh依赖是否安装
try:
import whoosh
except ImportError:
raise MissingDependency(
"The 'whoosh' backend requires the installation of 'Whoosh'. Please refer to the documentation.")
-# Handle minimum requirement.
+# 检查whoosh版本要求
if not hasattr(whoosh, '__version__') or whoosh.__version__ < (2, 5, 0):
raise MissingDependency(
"The 'whoosh' backend requires version 2.5.0 or greater.")
-# Bubble up the correct error.
-
+# 日期时间正则表达式,用于解析日期格式
DATETIME_REGEX = re.compile(
'^(?P\d{4})-(?P\d{2})-(?P\d{2})T(?P\d{2}):(?P\d{2}):(?P\d{2})(\.\d{3,6}Z?)?$')
+
+# 线程本地存储,用于RAM存储
LOCALS = threading.local()
LOCALS.RAM_STORE = None
class WhooshHtmlFormatter(HtmlFormatter):
"""
- This is a HtmlFormatter simpler than the whoosh.HtmlFormatter.
- We use it to have consistent results across backends. Specifically,
- Solr, Xapian and Elasticsearch are using this formatting.
+ 自定义HTML格式化器,用于搜索结果高亮显示
+ 比whoosh原生的HtmlFormatter更简单,保持跨后端的一致性
"""
- template = '<%(tag)s>%(t)s%(tag)s>'
+ template = '<%(tag)s>%(t)s%(tag)s>' # 高亮模板
class WhooshSearchBackend(BaseSearchBackend):
- # Word reserved by Whoosh for special use.
+ """Whoosh搜索引擎后端实现类,继承自Haystack的BaseSearchBackend"""
+
+ # Whoosh保留关键字列表
RESERVED_WORDS = (
- 'AND',
- 'NOT',
- 'OR',
- 'TO',
+ 'AND', 'NOT', 'OR', 'TO',
)
- # Characters reserved by Whoosh for special use.
- # The '\\' must come first, so as not to overwrite the other slash
- # replacements.
+ # Whoosh保留字符列表
RESERVED_CHARACTERS = (
'\\', '+', '-', '&&', '||', '!', '(', ')', '{', '}',
'[', ']', '^', '"', '~', '*', '?', ':', '.',
)
def __init__(self, connection_alias, **connection_options):
- super(
- WhooshSearchBackend,
- self).__init__(
- connection_alias,
- **connection_options)
+ """初始化Whoosh后端
+
+ Args:
+ connection_alias: 连接别名
+ **connection_options: 连接选项,包括PATH、STORAGE等
+ """
+ super(WhooshSearchBackend, self).__init__(connection_alias, **connection_options)
self.setup_complete = False
self.use_file_storage = True
- self.post_limit = getattr(
- connection_options,
- 'POST_LIMIT',
- 128 * 1024 * 1024)
- self.path = connection_options.get('PATH')
+ self.post_limit = getattr(connection_options, 'POST_LIMIT', 128 * 1024 * 1024) # 帖子大小限制
+ self.path = connection_options.get('PATH') # 索引文件路径
+ # 判断使用文件存储还是内存存储
if connection_options.get('STORAGE', 'file') != 'file':
self.use_file_storage = False
+ # 文件存储必须指定路径
if self.use_file_storage and not self.path:
raise ImproperlyConfigured(
- "You must specify a 'PATH' in your settings for connection '%s'." %
- connection_alias)
+ "You must specify a 'PATH' in your settings for connection '%s'." % connection_alias)
self.log = logging.getLogger('haystack')
def setup(self):
- """
- Defers loading until needed.
- """
+ """初始化设置,延迟加载直到需要时执行"""
from haystack import connections
new_index = False
- # Make sure the index is there.
+ # 确保索引目录存在(文件存储模式)
if self.use_file_storage and not os.path.exists(self.path):
os.makedirs(self.path)
new_index = True
+ # 检查目录写入权限
if self.use_file_storage and not os.access(self.path, os.W_OK):
- raise IOError(
- "The path to your Whoosh index '%s' is not writable for the current user/group." %
- self.path)
+ raise IOError("The path to your Whoosh index '%s' is not writable for the current user/group." % self.path)
+ # 设置存储后端
if self.use_file_storage:
- self.storage = FileStorage(self.path)
+ self.storage = FileStorage(self.path) # 文件存储
else:
global LOCALS
-
if getattr(LOCALS, 'RAM_STORE', None) is None:
- LOCALS.RAM_STORE = RamStorage()
-
+ LOCALS.RAM_STORE = RamStorage() # 内存存储
self.storage = LOCALS.RAM_STORE
+ # 构建schema和获取内容字段名
self.content_field_name, self.schema = self.build_schema(
connections[self.connection_alias].get_unified_index().all_searchfields())
- self.parser = QueryParser(self.content_field_name, schema=self.schema)
+ self.parser = QueryParser(self.content_field_name, schema=self.schema) # 查询解析器
+ # 创建或打开索引
if new_index is True:
self.index = self.storage.create_index(self.schema)
else:
@@ -145,54 +141,60 @@ class WhooshSearchBackend(BaseSearchBackend):
self.setup_complete = True
def build_schema(self, fields):
+ """构建Whoosh的schema(表结构)
+
+ Args:
+ fields: 搜索字段字典
+
+ Returns:
+ tuple: (内容字段名, schema对象)
+ """
+ # 基础字段定义
schema_fields = {
- ID: WHOOSH_ID(stored=True, unique=True),
- DJANGO_CT: WHOOSH_ID(stored=True),
- DJANGO_ID: WHOOSH_ID(stored=True),
+ ID: WHOOSH_ID(stored=True, unique=True), # 文档ID
+ DJANGO_CT: WHOOSH_ID(stored=True), # Django内容类型
+ DJANGO_ID: WHOOSH_ID(stored=True), # Django对象ID
}
- # Grab the number of keys that are hard-coded into Haystack.
- # We'll use this to (possibly) fail slightly more gracefully later.
- initial_key_count = len(schema_fields)
- content_field_name = ''
+
+ initial_key_count = len(schema_fields) # 初始字段数量
+ content_field_name = '' # 内容字段名
+ # 遍历所有字段进行类型映射
for field_name, field_class in fields.items():
- if field_class.is_multivalued:
+ if field_class.is_multivalued: # 多值字段
if field_class.indexed is False:
schema_fields[field_class.index_fieldname] = IDLIST(
stored=True, field_boost=field_class.boost)
else:
schema_fields[field_class.index_fieldname] = KEYWORD(
stored=True, commas=True, scorable=True, field_boost=field_class.boost)
- elif field_class.field_type in ['date', 'datetime']:
+ elif field_class.field_type in ['date', 'datetime']: # 日期时间字段
schema_fields[field_class.index_fieldname] = DATETIME(
stored=field_class.stored, sortable=True)
- elif field_class.field_type == 'integer':
+ elif field_class.field_type == 'integer': # 整数字段
schema_fields[field_class.index_fieldname] = NUMERIC(
stored=field_class.stored, numtype=int, field_boost=field_class.boost)
- elif field_class.field_type == 'float':
+ elif field_class.field_type == 'float': # 浮点数字段
schema_fields[field_class.index_fieldname] = NUMERIC(
stored=field_class.stored, numtype=float, field_boost=field_class.boost)
- elif field_class.field_type == 'boolean':
- # Field boost isn't supported on BOOLEAN as of 1.8.2.
- schema_fields[field_class.index_fieldname] = BOOLEAN(
- stored=field_class.stored)
- elif field_class.field_type == 'ngram':
+ elif field_class.field_type == 'boolean': # 布尔字段
+ schema_fields[field_class.index_fieldname] = BOOLEAN(stored=field_class.stored)
+ elif field_class.field_type == 'ngram': # N-gram字段
schema_fields[field_class.index_fieldname] = NGRAM(
minsize=3, maxsize=15, stored=field_class.stored, field_boost=field_class.boost)
- elif field_class.field_type == 'edge_ngram':
- schema_fields[field_class.index_fieldname] = NGRAMWORDS(minsize=2, maxsize=15, at='start',
- stored=field_class.stored,
- field_boost=field_class.boost)
- else:
- # schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True)
+ elif field_class.field_type == 'edge_ngram': # 边缘N-gram字段
+ schema_fields[field_class.index_fieldname] = NGRAMWORDS(
+ minsize=2, maxsize=15, at='start', stored=field_class.stored, field_boost=field_class.boost)
+ else: # 文本字段,使用中文分词器[1,3,6](@ref)
schema_fields[field_class.index_fieldname] = TEXT(
stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True)
+
+ # 标记文档主字段
if field_class.document is True:
content_field_name = field_class.index_fieldname
- schema_fields[field_class.index_fieldname].spelling = True
+ schema_fields[field_class.index_fieldname].spelling = True # 启用拼写检查
- # Fail more gracefully than relying on the backend to die if no fields
- # are found.
+ # 检查是否有有效字段
if len(schema_fields) <= initial_key_count:
raise SearchBackendError(
"No fields were found in any search_indexes. Please correct this before attempting to search.")
@@ -200,51 +202,54 @@ class WhooshSearchBackend(BaseSearchBackend):
return (content_field_name, Schema(**schema_fields))
def update(self, index, iterable, commit=True):
+ """更新索引文档
+
+ Args:
+ index: 索引对象
+ iterable: 可迭代的对象集合
+ commit: 是否提交更改
+ """
if not self.setup_complete:
self.setup()
self.index = self.index.refresh()
- writer = AsyncWriter(self.index)
+ writer = AsyncWriter(self.index) # 异步写入器
+ # 遍历所有对象并更新索引
for obj in iterable:
try:
- doc = index.full_prepare(obj)
+ doc = index.full_prepare(obj) # 准备文档数据
except SkipDocument:
self.log.debug(u"Indexing for object `%s` skipped", obj)
else:
- # Really make sure it's unicode, because Whoosh won't have it any
- # other way.
+ # 确保所有值为unicode格式
for key in doc:
doc[key] = self._from_python(doc[key])
- # Document boosts aren't supported in Whoosh 2.5.0+.
+ # Whoosh 2.5.0+不支持文档boost
if 'boost' in doc:
del doc['boost']
+ # 更新文档
try:
writer.update_document(**doc)
except Exception as e:
if not self.silently_fail:
raise
+ self.log.error(u"%s while preparing object for update" % e.__class__.__name__,
+ exc_info=True, extra={"data": {"index": index, "object": get_identifier(obj)}})
- # We'll log the object identifier but won't include the actual object
- # to avoid the possibility of that generating encoding errors while
- # processing the log message:
- self.log.error(
- u"%s while preparing object for update" %
- e.__class__.__name__,
- exc_info=True,
- extra={
- "data": {
- "index": index,
- "object": get_identifier(obj)}})
-
+ # 提交更改
if len(iterable) > 0:
- # For now, commit no matter what, as we run into locking issues
- # otherwise.
writer.commit()
def remove(self, obj_or_string, commit=True):
+ """从索引中移除文档
+
+ Args:
+ obj_or_string: 对象或标识符
+ commit: 是否提交更改
+ """
if not self.setup_complete:
self.setup()
@@ -252,21 +257,20 @@ class WhooshSearchBackend(BaseSearchBackend):
whoosh_id = get_identifier(obj_or_string)
try:
- self.index.delete_by_query(
- q=self.parser.parse(
- u'%s:"%s"' %
- (ID, whoosh_id)))
+ # 通过ID删除文档
+ self.index.delete_by_query(q=self.parser.parse(u'%s:"%s"' % (ID, whoosh_id)))
except Exception as e:
if not self.silently_fail:
raise
-
- self.log.error(
- "Failed to remove document '%s' from Whoosh: %s",
- whoosh_id,
- e,
- exc_info=True)
+ self.log.error("Failed to remove document '%s' from Whoosh: %s", whoosh_id, e, exc_info=True)
def clear(self, models=None, commit=True):
+ """清空索引
+
+ Args:
+ models: 要清空的模型列表,None表示清空所有
+ commit: 是否提交更改
+ """
if not self.setup_complete:
self.setup()
@@ -276,45 +280,35 @@ class WhooshSearchBackend(BaseSearchBackend):
assert isinstance(models, (list, tuple))
try:
- if models is None:
+ if models is None: # 清空整个索引
self.delete_index()
- else:
+ else: # 只清空指定模型的索引
models_to_delete = []
-
for model in models:
- models_to_delete.append(
- u"%s:%s" %
- (DJANGO_CT, get_model_ct(model)))
-
- self.index.delete_by_query(
- q=self.parser.parse(
- u" OR ".join(models_to_delete)))
+ models_to_delete.append(u"%s:%s" % (DJANGO_CT, get_model_ct(model)))
+ self.index.delete_by_query(q=self.parser.parse(u" OR ".join(models_to_delete)))
except Exception as e:
if not self.silently_fail:
raise
-
if models is not None:
- self.log.error(
- "Failed to clear Whoosh index of models '%s': %s",
- ','.join(models_to_delete),
- e,
- exc_info=True)
+ self.log.error("Failed to clear Whoosh index of models '%s': %s",
+ ','.join(models_to_delete), e, exc_info=True)
else:
- self.log.error(
- "Failed to clear Whoosh index: %s", e, exc_info=True)
+ self.log.error("Failed to clear Whoosh index: %s", e, exc_info=True)
def delete_index(self):
- # Per the Whoosh mailing list, if wiping out everything from the index,
- # it's much more efficient to simply delete the index files.
+ """删除整个索引(高效方式)"""
+ # 文件存储:直接删除目录[3,8](@ref)
if self.use_file_storage and os.path.exists(self.path):
shutil.rmtree(self.path)
- elif not self.use_file_storage:
+ elif not self.use_file_storage: # 内存存储:清理存储
self.storage.clean()
- # Recreate everything.
+ # 重新创建索引
self.setup()
def optimize(self):
+ """优化索引性能"""
if not self.setup_complete:
self.setup()
@@ -322,158 +316,129 @@ class WhooshSearchBackend(BaseSearchBackend):
self.index.optimize()
def calculate_page(self, start_offset=0, end_offset=None):
- # Prevent against Whoosh throwing an error. Requires an end_offset
- # greater than 0.
+ """计算分页信息
+
+ Args:
+ start_offset: 起始偏移量
+ end_offset: 结束偏移量
+
+ Returns:
+ tuple: (页码, 页大小)
+ """
+ # 防止Whoosh错误,需要end_offset大于0
if end_offset is not None and end_offset <= 0:
end_offset = 1
- # Determine the page.
+ # 计算页码
page_num = 0
-
if end_offset is None:
end_offset = 1000000
-
if start_offset is None:
start_offset = 0
page_length = end_offset - start_offset
-
if page_length and page_length > 0:
page_num = int(start_offset / page_length)
- # Increment because Whoosh uses 1-based page numbers.
+ # Whoosh使用1-based页码
page_num += 1
return page_num, page_length
@log_query
- def search(
- self,
- query_string,
- sort_by=None,
- start_offset=0,
- end_offset=None,
- fields='',
- highlight=False,
- facets=None,
- date_facets=None,
- query_facets=None,
- narrow_queries=None,
- spelling_query=None,
- within=None,
- dwithin=None,
- distance_point=None,
- models=None,
- limit_to_registered_models=None,
- result_class=None,
- **kwargs):
+ def search(self, query_string, sort_by=None, start_offset=0, end_offset=None, fields='',
+ highlight=False, facets=None, date_facets=None, query_facets=None, narrow_queries=None,
+ spelling_query=None, within=None, dwithin=None, distance_point=None, models=None,
+ limit_to_registered_models=None, result_class=None, **kwargs):
+ """执行搜索查询[2,5,8](@ref)
+
+ Args:
+ query_string: 查询字符串
+ sort_by: 排序字段
+ start_offset: 起始偏移
+ end_offset: 结束偏移
+ highlight: 是否高亮
+ ...其他参数...
+
+ Returns:
+ dict: 搜索结果字典
+ """
if not self.setup_complete:
self.setup()
- # A zero length query should return no results.
+ # 空查询返回空结果
if len(query_string) == 0:
- return {
- 'results': [],
- 'hits': 0,
- }
+ return {'results': [], 'hits': 0}
query_string = force_str(query_string)
- # A one-character query (non-wildcard) gets nabbed by a stopwords
- # filter and should yield zero results.
+ # 单字符非通配符查询返回空结果(被停用词过滤)
if len(query_string) <= 1 and query_string != u'*':
- return {
- 'results': [],
- 'hits': 0,
- }
+ return {'results': [], 'hits': 0}
+ # 处理排序方向
reverse = False
-
if sort_by is not None:
- # Determine if we need to reverse the results and if Whoosh can
- # handle what it's being asked to sort by. Reversing is an
- # all-or-nothing action, unfortunately.
+ # 检查所有排序字段是否同向
sort_by_list = []
reverse_counter = 0
-
for order_by in sort_by:
if order_by.startswith('-'):
reverse_counter += 1
if reverse_counter and reverse_counter != len(sort_by):
- raise SearchBackendError("Whoosh requires all order_by fields"
- " to use the same sort direction")
+ raise SearchBackendError("Whoosh requires all order_by fields to use the same sort direction")
+ # 处理排序字段
for order_by in sort_by:
if order_by.startswith('-'):
sort_by_list.append(order_by[1:])
-
if len(sort_by_list) == 1:
reverse = True
else:
sort_by_list.append(order_by)
-
if len(sort_by_list) == 1:
reverse = False
-
sort_by = sort_by_list[0]
+ # Whoosh不支持facets功能[8](@ref)
if facets is not None:
- warnings.warn(
- "Whoosh does not handle faceting.",
- Warning,
- stacklevel=2)
-
+ warnings.warn("Whoosh does not handle faceting.", Warning, stacklevel=2)
if date_facets is not None:
- warnings.warn(
- "Whoosh does not handle date faceting.",
- Warning,
- stacklevel=2)
-
+ warnings.warn("Whoosh does not handle date faceting.", Warning, stacklevel=2)
if query_facets is not None:
- warnings.warn(
- "Whoosh does not handle query faceting.",
- Warning,
- stacklevel=2)
+ warnings.warn("Whoosh does not handle query faceting.", Warning, stacklevel=2)
+ # 窄化查询处理
narrowed_results = None
self.index = self.index.refresh()
+ # 模型限制处理
if limit_to_registered_models is None:
- limit_to_registered_models = getattr(
- settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True)
+ limit_to_registered_models = getattr(settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True)
if models and len(models):
model_choices = sorted(get_model_ct(model) for model in models)
elif limit_to_registered_models:
- # Using narrow queries, limit the results to only models handled
- # with the current routers.
model_choices = self.build_models_list()
else:
model_choices = []
+ # 添加模型过滤条件
if len(model_choices) > 0:
if narrow_queries is None:
narrow_queries = set()
+ narrow_queries.add(' OR '.join(['%s:%s' % (DJANGO_CT, rm) for rm in model_choices]))
- narrow_queries.add(' OR '.join(
- ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices]))
-
+ # 执行窄化查询
narrow_searcher = None
-
if narrow_queries is not None:
- # Potentially expensive? I don't see another way to do it in
- # Whoosh...
narrow_searcher = self.index.searcher()
-
for nq in narrow_queries:
recent_narrowed_results = narrow_searcher.search(
self.parser.parse(force_str(nq)), limit=None)
if len(recent_narrowed_results) <= 0:
- return {
- 'results': [],
- 'hits': 0,
- }
+ return {'results': [], 'hits': 0}
if narrowed_results:
narrowed_results.filter(recent_narrowed_results)
@@ -482,61 +447,41 @@ class WhooshSearchBackend(BaseSearchBackend):
self.index = self.index.refresh()
+ # 执行主搜索查询
if self.index.doc_count():
searcher = self.index.searcher()
parsed_query = self.parser.parse(query_string)
- # In the event of an invalid/stopworded query, recover gracefully.
+ # 处理无效查询
if parsed_query is None:
- return {
- 'results': [],
- 'hits': 0,
- }
-
- page_num, page_length = self.calculate_page(
- start_offset, end_offset)
+ return {'results': [], 'hits': 0}
+ # 计算分页
+ page_num, page_length = self.calculate_page(start_offset, end_offset)
search_kwargs = {
'pagelen': page_length,
'sortedby': sort_by,
'reverse': reverse,
}
- # Handle the case where the results have been narrowed.
+ # 应用窄化过滤
if narrowed_results is not None:
search_kwargs['filter'] = narrowed_results
try:
- raw_page = searcher.search_page(
- parsed_query,
- page_num,
- **search_kwargs
- )
+ raw_page = searcher.search_page(parsed_query, page_num, **search_kwargs)
except ValueError:
if not self.silently_fail:
raise
+ return {'results': [], 'hits': 0}
- return {
- 'results': [],
- 'hits': 0,
- 'spelling_suggestion': None,
- }
-
- # Because as of Whoosh 2.5.1, it will return the wrong page of
- # results if you request something too high. :(
+ # 检查页码有效性
if raw_page.pagenum < page_num:
- return {
- 'results': [],
- 'hits': 0,
- 'spelling_suggestion': None,
- }
-
- results = self._process_results(
- raw_page,
- highlight=highlight,
- query_string=query_string,
- spelling_query=spelling_query,
- result_class=result_class)
+ return {'results': [], 'hits': 0}
+
+ # 处理结果
+ results = self._process_results(raw_page, highlight=highlight, query_string=query_string,
+ spelling_query=spelling_query, result_class=result_class)
searcher.close()
if hasattr(narrow_searcher, 'close'):
@@ -544,13 +489,10 @@ class WhooshSearchBackend(BaseSearchBackend):
return results
else:
+ # 无文档时的处理
if self.include_spelling:
- if spelling_query:
- spelling_suggestion = self.create_spelling_suggestion(
- spelling_query)
- else:
- spelling_suggestion = self.create_spelling_suggestion(
- query_string)
+ spelling_suggestion = self.create_spelling_suggestion(
+ spelling_query if spelling_query else query_string)
else:
spelling_suggestion = None
@@ -560,78 +502,68 @@ class WhooshSearchBackend(BaseSearchBackend):
'spelling_suggestion': spelling_suggestion,
}
- def more_like_this(
- self,
- model_instance,
- additional_query_string=None,
- start_offset=0,
- end_offset=None,
- models=None,
- limit_to_registered_models=None,
- result_class=None,
- **kwargs):
+ def more_like_this(self, model_instance, additional_query_string=None, start_offset=0, end_offset=None,
+ models=None, limit_to_registered_models=None, result_class=None, **kwargs):
+ """查找相似文档[8](@ref)
+
+ Args:
+ model_instance: 模型实例
+ additional_query_string: 附加查询条件
+ ...其他参数...
+
+ Returns:
+ dict: 相似结果
+ """
if not self.setup_complete:
self.setup()
- # Deferred models will have a different class ("RealClass_Deferred_fieldname")
- # which won't be in our registry:
+ # 获取模型信息
model_klass = model_instance._meta.concrete_model
-
field_name = self.content_field_name
narrow_queries = set()
narrowed_results = None
self.index = self.index.refresh()
+ # 模型过滤处理
if limit_to_registered_models is None:
- limit_to_registered_models = getattr(
- settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True)
+ limit_to_registered_models = getattr(settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True)
if models and len(models):
model_choices = sorted(get_model_ct(model) for model in models)
elif limit_to_registered_models:
- # Using narrow queries, limit the results to only models handled
- # with the current routers.
model_choices = self.build_models_list()
else:
model_choices = []
+ # 添加模型过滤条件
if len(model_choices) > 0:
if narrow_queries is None:
narrow_queries = set()
+ narrow_queries.add(' OR '.join(['%s:%s' % (DJANGO_CT, rm) for rm in model_choices]))
- narrow_queries.add(' OR '.join(
- ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices]))
-
+ # 添加附加查询条件
if additional_query_string and additional_query_string != '*':
narrow_queries.add(additional_query_string)
+ # 执行窄化查询
narrow_searcher = None
-
if narrow_queries is not None:
- # Potentially expensive? I don't see another way to do it in
- # Whoosh...
narrow_searcher = self.index.searcher()
-
for nq in narrow_queries:
- recent_narrowed_results = narrow_searcher.search(
- self.parser.parse(force_str(nq)), limit=None)
-
+ recent_narrowed_results = narrow_searcher.search(self.parser.parse(force_str(nq)), limit=None)
if len(recent_narrowed_results) <= 0:
- return {
- 'results': [],
- 'hits': 0,
- }
-
+ return {'results': [], 'hits': 0}
if narrowed_results:
narrowed_results.filter(recent_narrowed_results)
else:
narrowed_results = recent_narrowed_results
+ # 计算分页
page_num, page_length = self.calculate_page(start_offset, end_offset)
-
self.index = self.index.refresh()
raw_results = EmptyResults()
+ # 执行相似文档查询
if self.index.doc_count():
query = "%s:%s" % (ID, get_identifier(model_instance))
searcher = self.index.searcher()
@@ -639,33 +571,22 @@ class WhooshSearchBackend(BaseSearchBackend):
results = searcher.search(parsed_query)
if len(results):
- raw_results = results[0].more_like_this(
- field_name, top=end_offset)
+ raw_results = results[0].more_like_this(field_name, top=end_offset)
- # Handle the case where the results have been narrowed.
+ # 应用窄化过滤
if narrowed_results is not None and hasattr(raw_results, 'filter'):
raw_results.filter(narrowed_results)
+ # 分页处理
try:
raw_page = ResultsPage(raw_results, page_num, page_length)
except ValueError:
if not self.silently_fail:
raise
+ return {'results': [], 'hits': 0}
- return {
- 'results': [],
- 'hits': 0,
- 'spelling_suggestion': None,
- }
-
- # Because as of Whoosh 2.5.1, it will return the wrong page of
- # results if you request something too high. :(
if raw_page.pagenum < page_num:
- return {
- 'results': [],
- 'hits': 0,
- 'spelling_suggestion': None,
- }
+ return {'results': [], 'hits': 0}
results = self._process_results(raw_page, result_class=result_class)
searcher.close()
@@ -675,19 +596,22 @@ class WhooshSearchBackend(BaseSearchBackend):
return results
- def _process_results(
- self,
- raw_page,
- highlight=False,
- query_string='',
- spelling_query=None,
- result_class=None):
+ def _process_results(self, raw_page, highlight=False, query_string='', spelling_query=None, result_class=None):
+ """处理原始搜索结果
+
+ Args:
+ raw_page: 原始结果页
+ highlight: 是否高亮
+ query_string: 查询字符串
+ spelling_query: 拼写查询
+ result_class: 结果类
+
+ Returns:
+ dict: 处理后的结果
+ """
from haystack import connections
results = []
-
- # It's important to grab the hits first before slicing. Otherwise, this
- # can cause pagination failures.
- hits = len(raw_page)
+ hits = len(raw_page) # 命中数
if result_class is None:
result_class = SearchResult
@@ -697,6 +621,7 @@ class WhooshSearchBackend(BaseSearchBackend):
unified_index = connections[self.connection_alias].get_unified_index()
indexed_models = unified_index.get_indexed_models()
+ # 处理每个搜索结果
for doc_offset, raw_result in enumerate(raw_page):
score = raw_page.score(doc_offset) or 0
app_label, model_name = raw_result[DJANGO_CT].split('.')
@@ -704,61 +629,49 @@ class WhooshSearchBackend(BaseSearchBackend):
model = haystack_get_model(app_label, model_name)
if model and model in indexed_models:
+ # 处理每个字段值
for key, value in raw_result.items():
index = unified_index.get_index(model)
string_key = str(key)
- if string_key in index.fields and hasattr(
- index.fields[string_key], 'convert'):
- # Special-cased due to the nature of KEYWORD fields.
+ if string_key in index.fields and hasattr(index.fields[string_key], 'convert'):
+ # 特殊处理多值字段
if index.fields[string_key].is_multivalued:
if value is None or len(value) == 0:
additional_fields[string_key] = []
else:
- additional_fields[string_key] = value.split(
- ',')
+ additional_fields[string_key] = value.split(',')
else:
- additional_fields[string_key] = index.fields[string_key].convert(
- value)
+ additional_fields[string_key] = index.fields[string_key].convert(value)
else:
additional_fields[string_key] = self._to_python(value)
+ # 移除系统字段
del (additional_fields[DJANGO_CT])
del (additional_fields[DJANGO_ID])
+ # 高亮处理
if highlight:
sa = StemmingAnalyzer()
formatter = WhooshHtmlFormatter('em')
terms = [token.text for token in sa(query_string)]
whoosh_result = whoosh_highlight(
- additional_fields.get(self.content_field_name),
- terms,
- sa,
- ContextFragmenter(),
- formatter
+ additional_fields.get(self.content_field_name), terms, sa,
+ ContextFragmenter(), formatter
)
- additional_fields['highlighted'] = {
- self.content_field_name: [whoosh_result],
- }
-
- result = result_class(
- app_label,
- model_name,
- raw_result[DJANGO_ID],
- score,
- **additional_fields)
+ additional_fields['highlighted'] = {self.content_field_name: [whoosh_result]}
+
+ # 创建结果对象
+ result = result_class(app_label, model_name, raw_result[DJANGO_ID], score, **additional_fields)
results.append(result)
else:
- hits -= 1
+ hits -= 1 # 调整命中数
+ # 拼写建议
if self.include_spelling:
- if spelling_query:
- spelling_suggestion = self.create_spelling_suggestion(
- spelling_query)
- else:
- spelling_suggestion = self.create_spelling_suggestion(
- query_string)
+ spelling_suggestion = self.create_spelling_suggestion(
+ spelling_query if spelling_query else query_string)
return {
'results': results,
@@ -768,6 +681,14 @@ class WhooshSearchBackend(BaseSearchBackend):
}
def create_spelling_suggestion(self, query_string):
+ """创建拼写建议
+
+ Args:
+ query_string: 查询字符串
+
+ Returns:
+ str: 拼写建议
+ """
spelling_suggestion = None
reader = self.index.reader()
corrector = reader.corrector(self.content_field_name)
@@ -776,20 +697,19 @@ class WhooshSearchBackend(BaseSearchBackend):
if not query_string:
return spelling_suggestion
- # Clean the string.
+ # 清理查询字符串中的保留字
for rev_word in self.RESERVED_WORDS:
cleaned_query = cleaned_query.replace(rev_word, '')
for rev_char in self.RESERVED_CHARACTERS:
cleaned_query = cleaned_query.replace(rev_char, '')
- # Break it down.
+ # 分词并获取建议
query_words = cleaned_query.split()
suggested_words = []
for word in query_words:
suggestions = corrector.suggest(word, limit=1)
-
if len(suggestions) > 0:
suggested_words.append(suggestions[0])
@@ -797,103 +717,89 @@ class WhooshSearchBackend(BaseSearchBackend):
return spelling_suggestion
def _from_python(self, value):
+ """将Python值转换为Whoosh字符串格式
+
+ Args:
+ value: Python值
+
+ Returns:
+ str: Whoosh格式字符串
"""
- Converts Python values to a string for Whoosh.
-
- Code courtesy of pysolr.
- """
- if hasattr(value, 'strftime'):
+ if hasattr(value, 'strftime'): # 日期时间处理
if not hasattr(value, 'hour'):
value = datetime(value.year, value.month, value.day, 0, 0, 0)
- elif isinstance(value, bool):
- if value:
- value = 'true'
- else:
- value = 'false'
- elif isinstance(value, (list, tuple)):
+ elif isinstance(value, bool): # 布尔值处理
+ value = 'true' if value else 'false'
+ elif isinstance(value, (list, tuple)): # 列表元组处理
value = u','.join([force_str(v) for v in value])
- elif isinstance(value, (six.integer_types, float)):
- # Leave it alone.
+ elif isinstance(value, (six.integer_types, float)): # 数字保持原样
pass
- else:
+ else: # 其他转为字符串
value = force_str(value)
return value
def _to_python(self, value):
- """
- Converts values from Whoosh to native Python values.
-
- A port of the same method in pysolr, as they deal with data the same way.
+ """将Whoosh值转换为Python原生值
+
+ Args:
+ value: Whoosh值
+
+ Returns:
+ object: Python值
"""
if value == 'true':
return True
elif value == 'false':
return False
+ # 日期时间解析
if value and isinstance(value, six.string_types):
possible_datetime = DATETIME_REGEX.search(value)
-
if possible_datetime:
date_values = possible_datetime.groupdict()
-
for dk, dv in date_values.items():
date_values[dk] = int(dv)
+ return datetime(date_values['year'], date_values['month'], date_values['day'],
+ date_values['hour'], date_values['minute'], date_values['second'])
- return datetime(
- date_values['year'],
- date_values['month'],
- date_values['day'],
- date_values['hour'],
- date_values['minute'],
- date_values['second'])
-
+ # JSON解析尝试
try:
- # Attempt to use json to load the values.
converted_value = json.loads(value)
-
- # Try to handle most built-in types.
- if isinstance(
- converted_value,
- (list,
- tuple,
- set,
- dict,
- six.integer_types,
- float,
- complex)):
+ if isinstance(converted_value, (list, tuple, set, dict, six.integer_types, float, complex)):
return converted_value
- except BaseException:
- # If it fails (SyntaxError or its ilk) or we don't trust it,
- # continue on.
+ except:
pass
return value
class WhooshSearchQuery(BaseSearchQuery):
+ """Whoosh搜索查询类,处理查询构建"""
+
def _convert_datetime(self, date):
+ """转换日期时间格式"""
if hasattr(date, 'hour'):
return force_str(date.strftime('%Y%m%d%H%M%S'))
else:
return force_str(date.strftime('%Y%m%d000000'))
def clean(self, query_fragment):
- """
- Provides a mechanism for sanitizing user input before presenting the
- value to the backend.
-
- Whoosh 1.X differs here in that you can no longer use a backslash
- to escape reserved characters. Instead, the whole word should be
- quoted.
+ """清理查询片段,转义保留字符[8](@ref)
+
+ Args:
+ query_fragment: 查询片段
+
+ Returns:
+ str: 清理后的查询
"""
words = query_fragment.split()
cleaned_words = []
for word in words:
- if word in self.backend.RESERVED_WORDS:
+ if word in self.backend.RESERVED_WORDS: # 保留字转小写
word = word.replace(word, word.lower())
- for char in self.backend.RESERVED_CHARACTERS:
+ for char in self.backend.RESERVED_CHARACTERS: # 保留字符加引号
if char in word:
word = "'%s'" % word
break
@@ -903,39 +809,41 @@ class WhooshSearchQuery(BaseSearchQuery):
return ' '.join(cleaned_words)
def build_query_fragment(self, field, filter_type, value):
+ """构建查询片段
+
+ Args:
+ field: 字段名
+ filter_type: 过滤类型
+ value: 字段值
+
+ Returns:
+ str: 查询片段
+ """
from haystack import connections
query_frag = ''
is_datetime = False
+ # 处理值类型
if not hasattr(value, 'input_type_name'):
- # Handle when we've got a ``ValuesListQuerySet``...
- if hasattr(value, 'values_list'):
- value = list(value)
-
if hasattr(value, 'strftime'):
is_datetime = True
-
if isinstance(value, six.string_types) and value != ' ':
- # It's not an ``InputType``. Assume ``Clean``.
- value = Clean(value)
+ value = Clean(value) # 文本清理
else:
- value = PythonData(value)
+ value = PythonData(value) # Python数据
- # Prepare the query using the InputType.
- prepared_value = value.prepare(self)
+ prepared_value = value.prepare(self) # 准备值
if not isinstance(prepared_value, (set, list, tuple)):
- # Then convert whatever we get back to what pysolr wants if needed.
prepared_value = self.backend._from_python(prepared_value)
- # 'content' is a special reserved word, much like 'pk' in
- # Django's ORM layer. It indicates 'no special field'.
- if field == 'content':
+ # 字段名处理
+ if field == 'content': # 内容字段特殊处理
index_fieldname = ''
else:
- index_fieldname = u'%s:' % connections[self._using].get_unified_index(
- ).get_index_fieldname(field)
+ index_fieldname = u'%s:' % connections[self._using].get_unified_index().get_index_fieldname(field)
+ # 过滤类型映射
filter_types = {
'content': '%s',
'contains': '*%s*',
@@ -949,96 +857,72 @@ class WhooshSearchQuery(BaseSearchQuery):
'fuzzy': u'%s~',
}
+ # 构建查询片段
if value.post_process is False:
query_frag = prepared_value
else:
- if filter_type in [
- 'content',
- 'contains',
- 'startswith',
- 'endswith',
- 'fuzzy']:
+ if filter_type in ['content', 'contains', 'startswith', 'endswith', 'fuzzy']:
if value.input_type_name == 'exact':
query_frag = prepared_value
else:
- # Iterate over terms & incorportate the converted form of
- # each into the query.
+ # 多术语处理
terms = []
-
if isinstance(prepared_value, six.string_types):
possible_values = prepared_value.split(' ')
else:
if is_datetime is True:
- prepared_value = self._convert_datetime(
- prepared_value)
-
+ prepared_value = self._convert_datetime(prepared_value)
possible_values = [prepared_value]
for possible_value in possible_values:
- terms.append(
- filter_types[filter_type] %
- self.backend._from_python(possible_value))
+ terms.append(filter_types[filter_type] % self.backend._from_python(possible_value))
if len(terms) == 1:
query_frag = terms[0]
else:
query_frag = u"(%s)" % " AND ".join(terms)
- elif filter_type == 'in':
+ elif filter_type == 'in': # IN查询
in_options = []
-
for possible_value in prepared_value:
is_datetime = False
-
if hasattr(possible_value, 'strftime'):
is_datetime = True
-
pv = self.backend._from_python(possible_value)
-
if is_datetime is True:
pv = self._convert_datetime(pv)
-
if isinstance(pv, six.string_types) and not is_datetime:
in_options.append('"%s"' % pv)
else:
in_options.append('%s' % pv)
-
query_frag = "(%s)" % " OR ".join(in_options)
- elif filter_type == 'range':
+ elif filter_type == 'range': # 范围查询
start = self.backend._from_python(prepared_value[0])
end = self.backend._from_python(prepared_value[1])
-
if hasattr(prepared_value[0], 'strftime'):
start = self._convert_datetime(start)
-
if hasattr(prepared_value[1], 'strftime'):
end = self._convert_datetime(end)
-
query_frag = u"[%s to %s]" % (start, end)
- elif filter_type == 'exact':
+ elif filter_type == 'exact': # 精确匹配
if value.input_type_name == 'exact':
query_frag = prepared_value
else:
prepared_value = Exact(prepared_value).prepare(self)
query_frag = filter_types[filter_type] % prepared_value
- else:
+ else: # 其他类型
if is_datetime is True:
prepared_value = self._convert_datetime(prepared_value)
-
query_frag = filter_types[filter_type] % prepared_value
+ # 添加括号
if len(query_frag) and not isinstance(value, Raw):
if not query_frag.startswith('(') and not query_frag.endswith(')'):
query_frag = "(%s)" % query_frag
return u"%s%s" % (index_fieldname, query_frag)
- # if not filter_type in ('in', 'range'):
- # # 'in' is a bit of a special case, as we don't want to
- # # convert a valid list/tuple to string. Defer handling it
- # # until later...
- # value = self.backend._from_python(value)
-
class WhooshEngine(BaseEngine):
- backend = WhooshSearchBackend
- query = WhooshSearchQuery
+ """Whoosh搜索引擎引擎类"""
+ backend = WhooshSearchBackend # 后端类
+ query = WhooshSearchQuery # 查询类
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/wsgi.py b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/wsgi.py
index 2295efd..8eded39 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/wsgi.py
+++ b/src/DjangoBlog-master/DjangoBlog-master/djangoblog/wsgi.py
@@ -1,16 +1,22 @@
"""
WSGI config for djangoblog project.
-It exposes the WSGI callable as a module-level variable named ``application``.
+WSGI(Web Server Gateway Interface)是Python的Web服务器网关接口,是Django的主要部署平台。
+这个文件包含了WSGI可调用对象,作为模块级别的变量名为`application`。
-For more information on this file, see
+更多关于此文件的信息,请参考:
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""
import os
-
from django.core.wsgi import get_wsgi_application
+# 设置Django的默认设置模块环境变量
+# DJANGO_SETTINGS_MODULE环境变量告诉Django应该使用哪个设置模块
+# 当WSGI服务器加载应用时,Django需要知道使用哪个设置文件来配置整个应用[6,7](@ref)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings")
-application = get_wsgi_application()
+# 获取WSGI可调用应用程序对象
+# 这个application对象是WSGI服务器与Django应用通信的接口[6,9](@ref)
+# get_wsgi_application()函数返回一个符合WSGI标准的可调用应用程序对象
+application = get_wsgi_application()
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/manage.py b/src/DjangoBlog-master/DjangoBlog-master/manage.py
deleted file mode 100644
index 919ba74..0000000
--- a/src/DjangoBlog-master/DjangoBlog-master/manage.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python
-import os
-import sys
-
-if __name__ == "__main__":
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings")
- try:
- from django.core.management import execute_from_command_line
- except ImportError:
- # The above import may fail for some other reason. Ensure that the
- # issue is really that Django is missing to avoid masking other
- # exceptions on Python 2.
- try:
- import django
- except ImportError:
- raise ImportError(
- "Couldn't import Django. Are you sure it's installed and "
- "available on your PYTHONPATH environment variable? Did you "
- "forget to activate a virtual environment?"
- )
- raise
- execute_from_command_line(sys.argv)
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.coveragerc b/src/djangoblog-master/.coveragerc
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.coveragerc
rename to src/djangoblog-master/.coveragerc
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.dockerignore b/src/djangoblog-master/.dockerignore
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.dockerignore
rename to src/djangoblog-master/.dockerignore
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.gitattributes b/src/djangoblog-master/.gitattributes
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.gitattributes
rename to src/djangoblog-master/.gitattributes
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/ISSUE_TEMPLATE.md b/src/djangoblog-master/.github/ISSUE_TEMPLATE.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/ISSUE_TEMPLATE.md
rename to src/djangoblog-master/.github/ISSUE_TEMPLATE.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/codeql-analysis.yml b/src/djangoblog-master/.github/workflows/codeql-analysis.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/workflows/codeql-analysis.yml
rename to src/djangoblog-master/.github/workflows/codeql-analysis.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/django.yml b/src/djangoblog-master/.github/workflows/django.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/workflows/django.yml
rename to src/djangoblog-master/.github/workflows/django.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/docker.yml b/src/djangoblog-master/.github/workflows/docker.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/workflows/docker.yml
rename to src/djangoblog-master/.github/workflows/docker.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/publish-release.yml b/src/djangoblog-master/.github/workflows/publish-release.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/workflows/publish-release.yml
rename to src/djangoblog-master/.github/workflows/publish-release.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.gitignore b/src/djangoblog-master/.gitignore
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.gitignore
rename to src/djangoblog-master/.gitignore
diff --git a/src/DjangoBlog-master/DjangoBlog-master/Dockerfile b/src/djangoblog-master/Dockerfile
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/Dockerfile
rename to src/djangoblog-master/Dockerfile
diff --git a/src/DjangoBlog-master/DjangoBlog-master/LICENSE b/src/djangoblog-master/LICENSE
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/LICENSE
rename to src/djangoblog-master/LICENSE
diff --git a/src/DjangoBlog-master/DjangoBlog-master/README.md b/src/djangoblog-master/README.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/README.md
rename to src/djangoblog-master/README.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/__init__.py b/src/djangoblog-master/accounts/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/__init__.py
rename to src/djangoblog-master/accounts/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/admin.py b/src/djangoblog-master/accounts/admin.py
similarity index 67%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/admin.py
rename to src/djangoblog-master/accounts/admin.py
index 32e483c..0134c20 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/admin.py
+++ b/src/djangoblog-master/accounts/admin.py
@@ -8,14 +8,20 @@ from django.utils.translation import gettext_lazy as _
from .models import BlogUser
+# xm: 自定义用户创建表单,继承自ModelForm
class BlogUserCreationForm(forms.ModelForm):
+ # xm: 密码输入字段1
password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput)
+ # xm: 密码确认字段2
password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput)
class Meta:
+ # xm: 指定关联的模型为BlogUser
model = BlogUser
+ # xm: 表单字段只包含email
fields = ('email',)
+ # xm: 密码验证方法,确保两次输入的密码一致
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
@@ -24,29 +30,40 @@ class BlogUserCreationForm(forms.ModelForm):
raise forms.ValidationError(_("passwords do not match"))
return password2
+ # xm: 保存用户信息,对密码进行哈希处理
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
+ # xm: 设置用户来源为管理员站点
user.source = 'adminsite'
user.save()
return user
+# xm: 自定义用户信息修改表单,继承自UserChangeForm
class BlogUserChangeForm(UserChangeForm):
class Meta:
+ # xm: 指定关联的模型为BlogUser
model = BlogUser
+ # xm: 包含所有字段
fields = '__all__'
+ # xm: 指定username字段使用UsernameField类型
field_classes = {'username': UsernameField}
+ # xm: 初始化方法
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+# xm: 自定义用户管理类,继承自UserAdmin
class BlogUserAdmin(UserAdmin):
+ # xm: 指定修改表单类
form = BlogUserChangeForm
+ # xm: 指定创建表单类
add_form = BlogUserCreationForm
+ # xm: 列表页面显示的字段
list_display = (
'id',
'nickname',
@@ -55,5 +72,7 @@ class BlogUserAdmin(UserAdmin):
'last_login',
'date_joined',
'source')
+ # xm: 列表页面可点击链接的字段
list_display_links = ('id', 'username')
+ # xm: 默认排序字段,按ID倒序
ordering = ('-id',)
diff --git a/src/djangoblog-master/accounts/apps.py b/src/djangoblog-master/accounts/apps.py
new file mode 100644
index 0000000..39966b1
--- /dev/null
+++ b/src/djangoblog-master/accounts/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+# xm: 应用配置类,继承自Django的AppConfig基类
+class AccountsConfig(AppConfig):
+ # xm: 指定应用的完整Python路径
+ name = 'accounts'
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/forms.py b/src/djangoblog-master/accounts/forms.py
similarity index 75%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/forms.py
rename to src/djangoblog-master/accounts/forms.py
index fce4137..eca8070 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/forms.py
+++ b/src/djangoblog-master/accounts/forms.py
@@ -8,28 +8,37 @@ from . import utils
from .models import BlogUser
+# xm: 自定义登录表单,继承自Django的AuthenticationForm
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
+ # xm: 设置用户名字段的widget,添加placeholder和CSS类
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
+ # xm: 设置密码字段的widget,添加placeholder和CSS类
self.fields['password'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
+# xm: 自定义用户注册表单,继承自Django的UserCreationForm
class RegisterForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
+ # xm: 设置用户名字段的widget,添加placeholder和CSS类
self.fields['username'].widget = widgets.TextInput(
attrs={'placeholder': "username", "class": "form-control"})
+ # xm: 设置邮箱字段的widget,添加placeholder和CSS类
self.fields['email'].widget = widgets.EmailInput(
attrs={'placeholder': "email", "class": "form-control"})
+ # xm: 设置密码字段的widget,添加placeholder和CSS类
self.fields['password1'].widget = widgets.PasswordInput(
attrs={'placeholder': "password", "class": "form-control"})
+ # xm: 设置密码确认字段的widget,添加placeholder和CSS类
self.fields['password2'].widget = widgets.PasswordInput(
attrs={'placeholder': "repeat password", "class": "form-control"})
+ # xm: 邮箱验证方法,确保邮箱唯一性
def clean_email(self):
email = self.cleaned_data['email']
if get_user_model().objects.filter(email=email).exists():
@@ -37,11 +46,15 @@ class RegisterForm(UserCreationForm):
return email
class Meta:
+ # xm: 指定关联的用户模型
model = get_user_model()
+ # xm: 表单包含的字段:用户名和邮箱
fields = ("username", "email")
+# xm: 忘记密码表单,继承自forms.Form
class ForgetPasswordForm(forms.Form):
+ # xm: 新密码输入字段
new_password1 = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(
@@ -52,6 +65,7 @@ class ForgetPasswordForm(forms.Form):
),
)
+ # xm: 确认新密码字段
new_password2 = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(
@@ -62,6 +76,7 @@ class ForgetPasswordForm(forms.Form):
),
)
+ # xm: 邮箱输入字段,用于验证用户身份
email = forms.EmailField(
label='邮箱',
widget=forms.TextInput(
@@ -72,6 +87,7 @@ class ForgetPasswordForm(forms.Form):
),
)
+ # xm: 验证码输入字段
code = forms.CharField(
label=_('Code'),
widget=forms.TextInput(
@@ -82,15 +98,17 @@ class ForgetPasswordForm(forms.Form):
),
)
+ # xm: 验证两次输入的新密码是否一致
def clean_new_password2(self):
password1 = self.data.get("new_password1")
password2 = self.data.get("new_password2")
if password1 and password2 and password1 != password2:
raise ValidationError(_("passwords do not match"))
+ # xm: 使用Django的密码验证器验证密码强度
password_validation.validate_password(password2)
-
return password2
+ # xm: 验证邮箱是否存在系统中
def clean_email(self):
user_email = self.cleaned_data.get("email")
if not BlogUser.objects.filter(
@@ -100,6 +118,7 @@ class ForgetPasswordForm(forms.Form):
raise ValidationError(_("email does not exist"))
return user_email
+ # xm: 验证验证码是否正确
def clean_code(self):
code = self.cleaned_data.get("code")
error = utils.verify(
@@ -111,7 +130,9 @@ class ForgetPasswordForm(forms.Form):
return code
+# xm: 获取忘记密码验证码的表单
class ForgetPasswordCodeForm(forms.Form):
+ # xm: 邮箱字段,用于发送验证码
email = forms.EmailField(
label=_('Email'),
)
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0001_initial.py b/src/djangoblog-master/accounts/migrations/0001_initial.py
similarity index 72%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0001_initial.py
rename to src/djangoblog-master/accounts/migrations/0001_initial.py
index d2fbcab..75c2b85 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0001_initial.py
+++ b/src/djangoblog-master/accounts/migrations/0001_initial.py
@@ -18,30 +18,52 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='BlogUser',
fields=[
+ # xm: 主键ID,自增BigAutoField
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ # xm: 密码字段,存储加密后的密码
('password', models.CharField(max_length=128, verbose_name='password')),
+ # xm: 最后登录时间,记录用户最后一次登录的时间
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
+ # xm: 超级用户标志,标记用户是否拥有所有权限
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
+ # xm: 用户名,唯一且需要符合验证器规则
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
+ # xm: 名字字段,可选
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
+ # xm: 姓氏字段,可选
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
+ # xm: 邮箱地址,可选
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
+ # xm: 职员状态,标记用户是否可以访问管理后台
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
+ # xm: 活跃状态,标记用户账号是否激活
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
+ # xm: 加入日期,记录用户注册时间
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
+ # xm: 昵称字段,博客用户特有属性
('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')),
+ # xm: 创建时间,记录用户账号创建时间
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
+ # xm: 最后修改时间,记录用户信息最后修改时间
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
+ # xm: 创建来源,记录用户注册来源
('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')),
+ # xm: 用户组多对多关系
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
+ # xm: 用户权限多对多关系
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
+ # xm: 单数名称显示
'verbose_name': '用户',
+ # xm: 复数名称显示
'verbose_name_plural': '用户',
+ # xm: 默认按ID倒序排列
'ordering': ['-id'],
+ # xm: 指定最新记录的获取字段
'get_latest_by': 'id',
},
+ # xm: 指定自定义用户模型的管理器
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/src/djangoblog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
similarity index 77%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
rename to src/djangoblog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
index 1a9f509..8f9221c 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
+++ b/src/djangoblog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
@@ -11,33 +11,40 @@ class Migration(migrations.Migration):
]
operations = [
+ # xm: 修改BlogUser模型的元数据选项
migrations.AlterModelOptions(
name='bloguser',
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
),
+ # xm: 删除旧的created_time字段
migrations.RemoveField(
model_name='bloguser',
name='created_time',
),
+ # xm: 删除旧的last_mod_time字段
migrations.RemoveField(
model_name='bloguser',
name='last_mod_time',
),
+ # xm: 添加新的creation_time字段,使用当前时间作为默认值
migrations.AddField(
model_name='bloguser',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # xm: 添加新的last_modify_time字段,使用当前时间作为默认值
migrations.AddField(
model_name='bloguser',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
),
+ # xm: 修改nickname字段的verbose_name为英文
migrations.AlterField(
model_name='bloguser',
name='nickname',
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
),
+ # xm: 修改source字段的verbose_name为英文
migrations.AlterField(
model_name='bloguser',
name='source',
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/__init__.py b/src/djangoblog-master/accounts/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/__init__.py
rename to src/djangoblog-master/accounts/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/models.py b/src/djangoblog-master/accounts/models.py
similarity index 63%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/models.py
rename to src/djangoblog-master/accounts/models.py
index 3baddbb..844150a 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/models.py
+++ b/src/djangoblog-master/accounts/models.py
@@ -8,20 +8,28 @@ from djangoblog.utils import get_current_site
# Create your models here.
+# xm: 自定义用户模型,继承自Django的AbstractUser基类
class BlogUser(AbstractUser):
+ # xm: 昵称字段,最大长度100,可为空
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
+ # xm: 创建时间字段,默认值为当前时间
creation_time = models.DateTimeField(_('creation time'), default=now)
+ # xm: 最后修改时间字段,默认值为当前时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
+ # xm: 用户来源字段,记录创建来源,可为空
source = models.CharField(_('create source'), max_length=100, blank=True)
+ # xm: 获取用户绝对URL的方法,用于生成作者详情页链接
def get_absolute_url(self):
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
+ # xm: 对象的字符串表示形式,返回邮箱地址
def __str__(self):
return self.email
+ # xm: 获取用户完整URL的方法,包含域名
def get_full_url(self):
site = get_current_site().domain
url = "https://{site}{path}".format(site=site,
@@ -29,7 +37,11 @@ class BlogUser(AbstractUser):
return url
class Meta:
+ # xm: 默认按ID倒序排列
ordering = ['-id']
+ # xm: 单数名称显示
verbose_name = _('user')
+ # xm: 复数名称显示(与单数相同)
verbose_name_plural = verbose_name
+ # xm: 指定最新记录的获取字段
get_latest_by = 'id'
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/templatetags/__init__.py b/src/djangoblog-master/accounts/templatetags/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/templatetags/__init__.py
rename to src/djangoblog-master/accounts/templatetags/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/tests.py b/src/djangoblog-master/accounts/tests.py
similarity index 82%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/tests.py
rename to src/djangoblog-master/accounts/tests.py
index 6893411..a1dcbf9 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/tests.py
+++ b/src/djangoblog-master/accounts/tests.py
@@ -11,10 +11,13 @@ from . import utils
# Create your tests here.
+# xm: 账户功能测试类,继承自Django的TestCase
class AccountTest(TestCase):
+ # xm: 测试初始化方法,每个测试方法执行前都会运行
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
+ # xm: 创建测试用户
self.blog_user = BlogUser.objects.create_user(
username="test",
email="admin@admin.com",
@@ -22,27 +25,33 @@ class AccountTest(TestCase):
)
self.new_test = "xxx123--="
+ # xm: 测试账户验证功能
def test_validate_account(self):
site = get_current_site().domain
+ # xm: 创建超级用户
user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="qwer!@#$ggg")
testuser = BlogUser.objects.get(username='liangliangyy1')
+ # xm: 测试用户登录功能
loginresult = self.client.login(
username='liangliangyy1',
password='qwer!@#$ggg')
self.assertEqual(loginresult, True)
+ # xm: 测试管理员页面访问权限
response = self.client.get('/admin/')
self.assertEqual(response.status_code, 200)
+ # xm: 创建测试分类
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
+ # xm: 创建测试文章
article = Article()
article.title = "nicetitleaaa"
article.body = "nicecontentaaa"
@@ -52,44 +61,53 @@ class AccountTest(TestCase):
article.status = 'p'
article.save()
+ # xm: 测试文章管理页面访问权限
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
+ # xm: 测试用户注册功能
def test_validate_register(self):
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
+ # xm: 模拟用户注册请求
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',
})
+ # xm: 验证用户是否创建成功
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
user = BlogUser.objects.filter(email='user123@user.com')[0]
+ # xm: 生成验证签名
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
path = reverse('accounts:result')
url = '{path}?type=validation&id={id}&sign={sign}'.format(
path=path, id=user.id, sign=sign)
+ # xm: 测试验证页面访问
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
+ # xm: 测试用户登录功能
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
user.save()
delete_sidebar_cache()
+ # xm: 创建测试分类
category = Category()
category.name = "categoryaaa"
category.creation_time = timezone.now()
category.last_modify_time = timezone.now()
category.save()
+ # xm: 创建测试文章
article = Article()
article.category = category
article.title = "nicetitle333"
@@ -100,36 +118,45 @@ class AccountTest(TestCase):
article.status = 'p'
article.save()
+ # xm: 测试文章管理页面访问权限
response = self.client.get(article.get_admin_url())
self.assertEqual(response.status_code, 200)
+ # xm: 测试用户登出功能
response = self.client.get(reverse('account:logout'))
self.assertIn(response.status_code, [301, 302, 200])
+ # xm: 测试登出后访问文章管理页面的重定向
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
+ # xm: 测试错误密码登录
response = self.client.post(reverse('account:login'), {
'username': 'user1233',
'password': 'password123'
})
self.assertIn(response.status_code, [301, 302, 200])
+ # xm: 测试登录后访问文章管理页面
response = self.client.get(article.get_admin_url())
self.assertIn(response.status_code, [301, 302, 200])
+ # xm: 测试邮箱验证码功能
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)
+ # xm: 测试验证码验证成功情况
err = utils.verify("admin@admin.com", code)
self.assertEqual(err, None)
+ # xm: 测试验证码验证失败情况
err = utils.verify("admin@123.com", code)
self.assertEqual(type(err), str)
+ # xm: 测试忘记密码验证码发送成功情况
def test_forget_password_email_code_success(self):
resp = self.client.post(
path=reverse("account:forget_password_code"),
@@ -139,6 +166,7 @@ class AccountTest(TestCase):
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content.decode("utf-8"), "ok")
+ # xm: 测试忘记密码验证码发送失败情况
def test_forget_password_email_code_fail(self):
resp = self.client.post(
path=reverse("account:forget_password_code"),
@@ -152,6 +180,7 @@ class AccountTest(TestCase):
)
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
+ # xm: 测试忘记密码重置成功情况
def test_forget_password_email_success(self):
code = generate_code()
utils.set_code(self.blog_user.email, code)
@@ -167,13 +196,14 @@ class AccountTest(TestCase):
)
self.assertEqual(resp.status_code, 302)
- # 验证用户密码是否修改成功
+ # xm: 验证用户密码是否修改成功
blog_user = BlogUser.objects.filter(
email=self.blog_user.email,
).first() # type: BlogUser
self.assertNotEqual(blog_user, None)
self.assertEqual(blog_user.check_password(data["new_password1"]), True)
+ # xm: 测试不存在的用户忘记密码情况
def test_forget_password_email_not_user(self):
data = dict(
new_password1=self.new_test,
@@ -188,7 +218,7 @@ class AccountTest(TestCase):
self.assertEqual(resp.status_code, 200)
-
+ # xm: 测试验证码错误的忘记密码情况
def test_forget_password_email_code_error(self):
code = generate_code()
utils.set_code(self.blog_user.email, code)
diff --git a/src/djangoblog-master/accounts/urls.py b/src/djangoblog-master/accounts/urls.py
new file mode 100644
index 0000000..1fc8c80
--- /dev/null
+++ b/src/djangoblog-master/accounts/urls.py
@@ -0,0 +1,42 @@
+from django.urls import path
+from django.urls import re_path
+
+from . import views
+from .forms import LoginForm
+
+# xm: 定义应用命名空间为"accounts"
+app_name = "accounts"
+
+# xm: 定义URL路由配置列表
+urlpatterns = [
+ # xm: 登录路由,使用LoginView视图类,指定成功跳转URL和自定义登录表单
+ re_path(r'^login/$',
+ views.LoginView.as_view(success_url='/'),
+ name='login',
+ kwargs={'authentication_form': LoginForm}),
+
+ # xm: 注册路由,使用RegisterView视图类,指定成功跳转URL
+ re_path(r'^register/$',
+ views.RegisterView.as_view(success_url="/"),
+ name='register'),
+
+ # xm: 登出路由,使用LogoutView视图类
+ re_path(r'^logout/$',
+ views.LogoutView.as_view(),
+ name='logout'),
+
+ # xm: 账户结果页面路由,使用函数视图account_result
+ path(r'account/result.html',
+ views.account_result,
+ name='result'),
+
+ # xm: 忘记密码路由,使用ForgetPasswordView视图类
+ re_path(r'^forget_password/$',
+ views.ForgetPasswordView.as_view(),
+ name='forget_password'),
+
+ # xm: 忘记密码验证码路由,使用ForgetPasswordEmailCode视图类
+ re_path(r'^forget_password_code/$',
+ views.ForgetPasswordEmailCode.as_view(),
+ name='forget_password_code'),
+]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/user_login_backend.py b/src/djangoblog-master/accounts/user_login_backend.py
similarity index 61%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/user_login_backend.py
rename to src/djangoblog-master/accounts/user_login_backend.py
index 73cdca1..c1ad078 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/user_login_backend.py
+++ b/src/djangoblog-master/accounts/user_login_backend.py
@@ -2,25 +2,34 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
+# xm: 自定义认证后端,允许使用用户名或邮箱登录,继承自ModelBackend
class EmailOrUsernameModelBackend(ModelBackend):
"""
允许使用用户名或邮箱登录
"""
+ # xm: 用户认证方法,重写父类方法
def authenticate(self, request, username=None, password=None, **kwargs):
+ # xm: 判断输入是否包含@符号,决定使用邮箱还是用户名查询
if '@' in username:
kwargs = {'email': username}
else:
kwargs = {'username': username}
try:
+ # xm: 根据用户名或邮箱查询用户
user = get_user_model().objects.get(**kwargs)
+ # xm: 验证密码是否正确
if user.check_password(password):
return user
+ # xm: 捕获用户不存在的异常
except get_user_model().DoesNotExist:
return None
+ # xm: 根据用户ID获取用户对象的方法
def get_user(self, username):
try:
+ # xm: 通过主键查询用户
return get_user_model().objects.get(pk=username)
+ # xm: 捕获用户不存在的异常
except get_user_model().DoesNotExist:
return None
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/utils.py b/src/djangoblog-master/accounts/utils.py
similarity index 73%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/utils.py
rename to src/djangoblog-master/accounts/utils.py
index 4b94bdf..0657d0e 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/utils.py
+++ b/src/djangoblog-master/accounts/utils.py
@@ -10,6 +10,7 @@ from djangoblog.utils import send_email
_code_ttl = timedelta(minutes=5)
+# xm: 发送验证邮件函数
def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
"""发送重设密码验证码
Args:
@@ -17,12 +18,15 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email"))
subject: 邮件主题
code: 验证码
"""
+ # xm: 构建邮件HTML内容,包含验证码信息
html_content = _(
"You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it "
"properly") % {'code': code}
+ # xm: 调用发送邮件函数发送验证码
send_email([to_mail], subject, html_content)
+# xm: 验证验证码函数
def verify(email: str, code: str) -> typing.Optional[str]:
"""验证code是否有效
Args:
@@ -34,16 +38,22 @@ def verify(email: str, code: str) -> typing.Optional[str]:
这里的错误处理不太合理,应该采用raise抛出
否测调用方也需要对error进行处理
"""
+ # xm: 从缓存中获取对应邮箱的验证码
cache_code = get_code(email)
+ # xm: 比较输入的验证码和缓存中的验证码是否一致
if cache_code != code:
return gettext("Verification code error")
+# xm: 设置验证码到缓存函数
def set_code(email: str, code: str):
"""设置code"""
+ # xm: 使用Django缓存系统存储验证码,设置过期时间
cache.set(email, code, _code_ttl.seconds)
+# xm: 从缓存获取验证码函数
def get_code(email: str) -> typing.Optional[str]:
"""获取code"""
+ # xm: 从Django缓存系统中获取指定邮箱的验证码
return cache.get(email)
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/views.py b/src/djangoblog-master/accounts/views.py
similarity index 82%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/views.py
rename to src/djangoblog-master/accounts/views.py
index ae67aec..cd40dd2 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/accounts/views.py
+++ b/src/djangoblog-master/accounts/views.py
@@ -30,22 +30,26 @@ logger = logging.getLogger(__name__)
# Create your views here.
-
+# xm: 用户注册视图类,继承自FormView
class RegisterView(FormView):
form_class = RegisterForm
template_name = 'account/registration_form.html'
+ # xm: 添加CSRF保护装饰器
@method_decorator(csrf_protect)
def dispatch(self, *args, **kwargs):
return super(RegisterView, self).dispatch(*args, **kwargs)
+ # xm: 表单验证通过后的处理逻辑
def form_valid(self, form):
if form.is_valid():
+ # xm: 保存用户但不提交到数据库
user = form.save(False)
user.is_active = False
user.source = 'Register'
user.save(True)
site = get_current_site().domain
+ # xm: 生成邮箱验证签名
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
if settings.DEBUG:
@@ -54,6 +58,7 @@ class RegisterView(FormView):
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
site=site, path=path, id=user.id, sign=sign)
+ # xm: 构建验证邮件内容
content = """
请点击下面链接验证您的邮箱
@@ -64,6 +69,7 @@ class RegisterView(FormView):
如果上面链接无法打开,请将此链接复制至浏览器。
{url}
""".format(url=url)
+ # xm: 发送验证邮件
send_email(
emailto=[
user.email,
@@ -71,6 +77,7 @@ class RegisterView(FormView):
title='验证您的电子邮箱',
content=content)
+ # xm: 重定向到结果页面
url = reverse('accounts:result') + \
'?type=register&id=' + str(user.id)
return HttpResponseRedirect(url)
@@ -80,19 +87,24 @@ class RegisterView(FormView):
})
+# xm: 用户登出视图类,继承自RedirectView
class LogoutView(RedirectView):
url = '/login/'
+ # xm: 添加不缓存装饰器
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
return super(LogoutView, self).dispatch(request, *args, **kwargs)
+ # xm: 处理GET请求,执行登出操作
def get(self, request, *args, **kwargs):
logout(request)
+ # xm: 删除侧边栏缓存
delete_sidebar_cache()
return super(LogoutView, self).get(request, *args, **kwargs)
+# xm: 用户登录视图类,继承自FormView
class LoginView(FormView):
form_class = LoginForm
template_name = 'account/login.html'
@@ -100,41 +112,45 @@ class LoginView(FormView):
redirect_field_name = REDIRECT_FIELD_NAME
login_ttl = 2626560 # 一个月的时间
+ # xm: 添加多个安全相关的装饰器
@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)
+ # xm: 获取上下文数据,处理重定向参数
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)
+ # xm: 表单验证通过后的处理逻辑
def form_valid(self, form):
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
+ # xm: 删除侧边栏缓存
delete_sidebar_cache()
logger.info(self.redirect_field_name)
+ # xm: 执行用户登录
auth.login(self.request, form.get_user())
+ # xm: 处理"记住我"功能,设置会话过期时间
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
})
+ # xm: 获取登录成功后的重定向URL
def get_success_url(self):
-
redirect_to = self.request.POST.get(self.redirect_field_name)
+ # xm: 验证重定向URL的安全性
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[
self.request.get_host()]):
@@ -142,25 +158,30 @@ class LoginView(FormView):
return redirect_to
+# xm: 账户操作结果页面视图函数
def account_result(request):
type = request.GET.get('type')
id = request.GET.get('id')
+ # xm: 获取用户对象,不存在则返回404
user = get_object_or_404(get_user_model(), id=id)
logger.info(type)
if user.is_active:
return HttpResponseRedirect('/')
if type and type in ['register', 'validation']:
if type == 'register':
+ # xm: 注册成功页面内容
content = '''
恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。
'''
title = '注册成功'
else:
+ # xm: 验证邮箱签名
c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
sign = request.GET.get('sign')
if sign != c_sign:
return HttpResponseForbidden()
+ # xm: 激活用户账户
user.is_active = True
user.save()
content = '''
@@ -175,12 +196,15 @@ def account_result(request):
return HttpResponseRedirect('/')
+# xm: 忘记密码视图类,继承自FormView
class ForgetPasswordView(FormView):
form_class = ForgetPasswordForm
template_name = 'account/forget_password.html'
+ # xm: 表单验证通过后的处理逻辑
def form_valid(self, form):
if form.is_valid():
+ # xm: 根据邮箱获取用户并重置密码
blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
blog_user.password = make_password(form.cleaned_data["new_password2"])
blog_user.save()
@@ -189,14 +213,17 @@ class ForgetPasswordView(FormView):
return self.render_to_response({'form': form})
+# xm: 忘记密码验证码发送视图类,继承自View
class ForgetPasswordEmailCode(View):
+ # xm: 处理POST请求,发送验证码邮件
def post(self, request: HttpRequest):
form = ForgetPasswordCodeForm(request.POST)
if not form.is_valid():
return HttpResponse("错误的邮箱")
to_email = form.cleaned_data["email"]
+ # xm: 生成并发送验证码
code = generate_code()
utils.send_verify_email(to_email, code)
utils.set_code(to_email, code)
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/__init__.py b/src/djangoblog-master/blog/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/__init__.py
rename to src/djangoblog-master/blog/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/admin.py b/src/djangoblog-master/blog/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/admin.py
rename to src/djangoblog-master/blog/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/apps.py b/src/djangoblog-master/blog/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/apps.py
rename to src/djangoblog-master/blog/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/context_processors.py b/src/djangoblog-master/blog/context_processors.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/context_processors.py
rename to src/djangoblog-master/blog/context_processors.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/documents.py b/src/djangoblog-master/blog/documents.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/documents.py
rename to src/djangoblog-master/blog/documents.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/forms.py b/src/djangoblog-master/blog/forms.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/forms.py
rename to src/djangoblog-master/blog/forms.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/__init__.py b/src/djangoblog-master/blog/management/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/__init__.py
rename to src/djangoblog-master/blog/management/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/__init__.py b/src/djangoblog-master/blog/management/commands/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/__init__.py
rename to src/djangoblog-master/blog/management/commands/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_index.py b/src/djangoblog-master/blog/management/commands/build_index.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_index.py
rename to src/djangoblog-master/blog/management/commands/build_index.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_search_words.py b/src/djangoblog-master/blog/management/commands/build_search_words.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_search_words.py
rename to src/djangoblog-master/blog/management/commands/build_search_words.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/clear_cache.py b/src/djangoblog-master/blog/management/commands/clear_cache.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/clear_cache.py
rename to src/djangoblog-master/blog/management/commands/clear_cache.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/create_testdata.py b/src/djangoblog-master/blog/management/commands/create_testdata.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/create_testdata.py
rename to src/djangoblog-master/blog/management/commands/create_testdata.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/ping_baidu.py b/src/djangoblog-master/blog/management/commands/ping_baidu.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/ping_baidu.py
rename to src/djangoblog-master/blog/management/commands/ping_baidu.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/sync_user_avatar.py b/src/djangoblog-master/blog/management/commands/sync_user_avatar.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/sync_user_avatar.py
rename to src/djangoblog-master/blog/management/commands/sync_user_avatar.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/middleware.py b/src/djangoblog-master/blog/middleware.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/middleware.py
rename to src/djangoblog-master/blog/middleware.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0001_initial.py b/src/djangoblog-master/blog/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0001_initial.py
rename to src/djangoblog-master/blog/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/djangoblog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py
rename to src/djangoblog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py b/src/djangoblog-master/blog/migrations/0003_blogsettings_comment_need_review.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py
rename to src/djangoblog-master/blog/migrations/0003_blogsettings_comment_need_review.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/djangoblog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
rename to src/djangoblog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/djangoblog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
rename to src/djangoblog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py b/src/djangoblog-master/blog/migrations/0006_alter_blogsettings_options.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py
rename to src/djangoblog-master/blog/migrations/0006_alter_blogsettings_options.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/__init__.py b/src/djangoblog-master/blog/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/migrations/__init__.py
rename to src/djangoblog-master/blog/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/models.py b/src/djangoblog-master/blog/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/models.py
rename to src/djangoblog-master/blog/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/search_indexes.py b/src/djangoblog-master/blog/search_indexes.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/search_indexes.py
rename to src/djangoblog-master/blog/search_indexes.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/__init__.py b/src/djangoblog-master/blog/templatetags/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/__init__.py
rename to src/djangoblog-master/blog/templatetags/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py b/src/djangoblog-master/blog/templatetags/blog_tags.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py
rename to src/djangoblog-master/blog/templatetags/blog_tags.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/tests.py b/src/djangoblog-master/blog/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/tests.py
rename to src/djangoblog-master/blog/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/urls.py b/src/djangoblog-master/blog/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/urls.py
rename to src/djangoblog-master/blog/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/views.py b/src/djangoblog-master/blog/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/views.py
rename to src/djangoblog-master/blog/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/__init__.py b/src/djangoblog-master/comments/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/__init__.py
rename to src/djangoblog-master/comments/__init__.py
diff --git a/src/djangoblog-master/comments/admin.py b/src/djangoblog-master/comments/admin.py
new file mode 100644
index 0000000..71cffd4
--- /dev/null
+++ b/src/djangoblog-master/comments/admin.py
@@ -0,0 +1,83 @@
+from django.contrib import admin
+from django.urls import reverse
+from django.utils.html import format_html
+from django.utils.translation import gettext_lazy as _ # 国际化翻译
+
+
+# gjw:禁用评论状态的动作函数
+def disable_commentstatus(modeladmin, request, queryset):
+ """将选中的评论设置为禁用状态"""
+ queryset.update(is_enable=False)
+
+
+#gjw: 启用评论状态的动作函数
+def enable_commentstatus(modeladmin, request, queryset):
+ """将选中的评论设置为启用状态"""
+ queryset.update(is_enable=True)
+
+
+# gjw:为动作函数设置显示名称(支持国际化)
+disable_commentstatus.short_description = _('Disable comments')
+enable_commentstatus.short_description = _('Enable comments')
+
+
+class CommentAdmin(admin.ModelAdmin):
+ """评论模型的后台管理配置"""
+
+ # gjw:每页显示20条记录
+ list_per_page = 20
+
+ # gjw:列表页显示的字段
+ list_display = (
+ 'id', # 评论ID
+ 'body', # 评论内容
+ 'link_to_userinfo', # 用户信息链接(自定义方法)
+ 'link_to_article', # 文章链接(自定义方法)
+ 'is_enable', # 是否启用
+ 'creation_time' # 创建时间
+ )
+
+ # gjw:可点击进入编辑页面的字段
+ list_display_links = ('id', 'body', 'is_enable')
+
+ # gjw:右侧过滤器
+ list_filter = ('is_enable',) # gjw:按启用状态过滤
+
+ # gjw:编辑页面排除的字段(这些字段不会在编辑表单中显示)
+ exclude = ('creation_time', 'last_modify_time')
+
+ # gjw:批量动作列表
+ actions = [disable_commentstatus, enable_commentstatus]
+
+ def link_to_userinfo(self, obj):
+ """
+ 生成指向用户详情页的链接
+ obj: Comment实例
+ 返回:包含用户昵称或邮箱的HTML链接
+ """
+ # 获取用户模型的app和model名称
+ info = (obj.author._meta.app_label, obj.author._meta.model_name)
+ #gjw: 生成用户编辑页面的URL
+ link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
+ # gjw:返回HTML链接,显示用户昵称(如果没有则显示邮箱)
+ return format_html(
+ u'%s' %
+ (link, obj.author.nickname if obj.author.nickname else obj.author.email))
+
+ def link_to_article(self, obj):
+ """
+ 生成指向文章详情页的链接
+ obj: Comment实例
+ 返回:包含文章标题的HTML链接
+ """
+ # gjw:获取文章模型的app和model名称
+ info = (obj.article._meta.app_label, obj.article._meta.model_name)
+ # 生成文章编辑页面的URL
+ link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
+ # gjw:返回HTML链接,显示文章标题
+ return format_html(
+ u'%s' % (link, obj.article.title))
+
+ # gjw:为自定义方法设置显示名称(支持国际化)
+ link_to_userinfo.short_description = _('User')
+ link_to_article.short_description = _('Article')
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/apps.py b/src/djangoblog-master/comments/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/apps.py
rename to src/djangoblog-master/comments/apps.py
diff --git a/src/djangoblog-master/comments/forms.py b/src/djangoblog-master/comments/forms.py
new file mode 100644
index 0000000..af47829
--- /dev/null
+++ b/src/djangoblog-master/comments/forms.py
@@ -0,0 +1,24 @@
+from django import forms
+from django.forms import ModelForm
+
+# gjw:导入Comment模型
+from .models import Comment
+
+
+class CommentForm(ModelForm):
+ """
+ 评论表单类
+ 用于处理用户提交的评论数据,支持回复功能
+ """
+
+ # gjw:父评论ID字段,用于实现评论回复功能
+ parent_comment_id = forms.IntegerField(
+ widget=forms.HiddenInput, # 使用隐藏输入控件,前端不可见
+ required=False # 非必填字段,如果是顶级评论则为空
+ )
+
+ class Meta:
+ #gjw: 指定关联的模型
+ model = Comment
+ # gjw:表单中包含的字段,只包含评论正文
+ fields = ['body']
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0001_initial.py b/src/djangoblog-master/comments/migrations/0001_initial.py
similarity index 51%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0001_initial.py
rename to src/djangoblog-master/comments/migrations/0001_initial.py
index 61d1e53..1567928 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0001_initial.py
+++ b/src/djangoblog-master/comments/migrations/0001_initial.py
@@ -1,38 +1,49 @@
# Generated by Django 4.1.7 on 2023-03-02 07:14
-
+# gjw:导入Django内置模块
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
-
-
class Migration(migrations.Migration):
-
+ #gjw:初始迁移文件
initial = True
+ #gjw:依赖关系:需要先执行blog应用的0001_initial迁移和用户模型的迁移
dependencies = [
- ('blog', '0001_initial'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('blog', '0001_initial'), #gjw:依赖blog应用的初始迁移
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL), #gjw:依赖可切换的用户模型
]
+ #gjw:迁移操作列表
operations = [
+ #gjw:创建Comment模型对应的数据库表
migrations.CreateModel(
- name='Comment',
+ name='Comment', #gjw:模型名称:评论
fields=[
+ #gjw:主键字段,自增BigAutoField
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ #gjw:评论正文,TextField类型,最大长度300字符
('body', models.TextField(max_length=300, verbose_name='正文')),
+ #gjw:创建时间,默认值为当前时间
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
+ #gjw:最后修改时间,默认值为当前时间
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
+ #gjw:是否启用/显示评论,布尔字段,默认True
('is_enable', models.BooleanField(default=True, verbose_name='是否显示')),
+ #gjw:外键关联到Article模型,级联删除
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')),
+ #gjw:外键关联到用户模型,级联删除
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')),
+ #gjw:自关联外键,用于实现评论的回复功能(父级评论)
+ #gjw:blank=True和null=True允许该字段为空,表示顶级评论
('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')),
],
+ #gjw:模型的元数据配置
options={
- 'verbose_name': '评论',
- 'verbose_name_plural': '评论',
- 'ordering': ['-id'],
- 'get_latest_by': 'id',
+ 'verbose_name': '评论', #gjw:单数名称
+ 'verbose_name_plural': '评论', #gjw:复数名称
+ 'ordering': ['-id'], #gjw:默认按ID降序排列
+ 'get_latest_by': 'id', #gjw:指定按ID字段获取最新记录
},
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/djangoblog-master/comments/migrations/0002_alter_comment_is_enable.py b/src/djangoblog-master/comments/migrations/0002_alter_comment_is_enable.py
new file mode 100644
index 0000000..5c8866e
--- /dev/null
+++ b/src/djangoblog-master/comments/migrations/0002_alter_comment_is_enable.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.1.7 on 2023-04-24 13:48
+# gjw:导入Django迁移相关模块
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ # gjw:迁移类,用于修改数据库结构
+
+ # gjw:依赖关系:需要先执行comments应用的0001_initial迁移
+ dependencies = [
+ ('comments', '0001_initial'), # gjw:依赖本应用的前一个迁移文件
+ ]
+
+ # gjw:迁移操作列表
+ operations = [
+ # gjw:修改字段操作:改变Comment模型的is_enable字段
+ migrations.AlterField(
+ model_name='comment', #gjw: 要修改的模型名称
+ name='is_enable', #gjw: 要修改的字段名称
+ # gjw:将字段的默认值从True改为False
+ # gjw:这意味着新创建的评论默认不会显示,需要手动启用
+ field=models.BooleanField(default=False, verbose_name='是否显示'),
+ ),
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/djangoblog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
similarity index 56%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
rename to src/djangoblog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
index a1ca970..19379b2 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
+++ b/src/djangoblog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
@@ -1,5 +1,6 @@
# Generated by Django 4.2.5 on 2023-09-06 13:13
+# gjw:导入Django相关模块
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
@@ -7,54 +8,80 @@ import django.utils.timezone
class Migration(migrations.Migration):
+ # gjw:数据库迁移类
+ # gjw:依赖关系:需要先执行其他迁移文件
dependencies = [
+ # gjw:依赖可切换的用户模型
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ #gjw: 依赖blog应用的0005迁移文件
('blog', '0005_alter_article_options_alter_category_options_and_more'),
+ # gjw:依赖comments应用的0002迁移文件(修改is_enable字段的迁移)
('comments', '0002_alter_comment_is_enable'),
]
+ # gjw:迁移操作列表
operations = [
+ #gjw: 修改Comment模型的元数据选项
migrations.AlterModelOptions(
- name='comment',
- options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'},
+ name='comment', # gjw:模型名称
+ options={
+ 'get_latest_by': 'id', # gjw:指定按ID获取最新记录
+ 'ordering': ['-id'], #gjw: 按ID降序排列
+ 'verbose_name': 'comment', #gjw: 单数显示名称改为英文
+ 'verbose_name_plural': 'comment', # gjw:复数显示名称改为英文
+ },
),
+ # gjw:删除created_time字段
migrations.RemoveField(
model_name='comment',
name='created_time',
),
+ #gjw: 删除last_mod_time字段
migrations.RemoveField(
model_name='comment',
name='last_mod_time',
),
+ # gjw:新增creation_time字段
migrations.AddField(
model_name='comment',
name='creation_time',
+ # gjw:日期时间字段,默认值为当前时间
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
),
+ # gjw:新增last_modify_time字段
migrations.AddField(
model_name='comment',
name='last_modify_time',
+ # gjw:日期时间字段,默认值为当前时间
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
),
+ # gjw:修改article字段的显示名称
migrations.AlterField(
model_name='comment',
name='article',
+ # gjw:外键关联到Article模型,级联删除,显示名称改为英文
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'),
),
+ #gjw: 修改author字段的显示名称
migrations.AlterField(
model_name='comment',
name='author',
+ # gjw:外键关联到用户模型,级联删除,显示名称改为英文
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
),
+ # gjw:修改is_enable字段的显示名称
migrations.AlterField(
model_name='comment',
name='is_enable',
+ # gjw:布尔字段,默认False,显示名称改为英文
field=models.BooleanField(default=False, verbose_name='enable'),
),
+ # gjw:修改parent_comment字段的显示名称
migrations.AlterField(
model_name='comment',
name='parent_comment',
+ # gjw:自关联外键,允许为空,级联删除,显示名称改为英文
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'),
),
- ]
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/__init__.py b/src/djangoblog-master/comments/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/migrations/__init__.py
rename to src/djangoblog-master/comments/migrations/__init__.py
diff --git a/src/djangoblog-master/comments/models.py b/src/djangoblog-master/comments/models.py
new file mode 100644
index 0000000..65f5c13
--- /dev/null
+++ b/src/djangoblog-master/comments/models.py
@@ -0,0 +1,82 @@
+from django.conf import settings
+from django.db import models
+from django.utils.timezone import now
+from django.utils.translation import gettext_lazy as _ #gjw: 国际化翻译
+from blog.models import Article # gjw:导入文章模型
+
+
+# gjw:评论模型
+class Comment(models.Model):
+ """
+ 评论模型
+ 用于存储用户对文章的评论,支持评论回复功能
+ """
+
+ # gjw:评论正文,最大长度300字符
+ body = models.TextField('正文', max_length=300)
+
+ # gjw:创建时间,默认值为当前时间
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+
+ # gjw:最后修改时间,默认值为当前时间
+ last_modify_time = models.DateTimeField(_('last modify time'), default=now)
+
+ # gjw:评论作者,外键关联到用户模型
+ # gjw:CASCADE: 用户删除时,其所有评论也会被删除
+ author = models.ForeignKey(
+ settings.AUTH_USER_MODEL, # gjw:使用settings中配置的用户模型
+ verbose_name=_('author'), # gjw:显示名称:作者
+ on_delete=models.CASCADE # gjw:级联删除
+ )
+
+ # gjw:关联的文章,外键关联到Article模型
+ # gjw:CASCADE: 文章删除时,其所有评论也会被删除
+ article = models.ForeignKey(
+ Article, # gjw:关联到文章模型
+ verbose_name=_('article'), # gjw:显示名称:文章
+ on_delete=models.CASCADE # gjw:级联删除
+ )
+
+ # gjw:父级评论,自关联实现评论回复功能
+ # gjw:blank=True, null=True: 允许为空,表示可以是顶级评论
+ parent_comment = models.ForeignKey(
+ 'self', # gjw:自关联,指向同一个模型
+ verbose_name=_('parent comment'), # gjw:显示名称:父评论
+ blank=True, # gjw:表单验证允许为空
+ null=True, # gjw:数据库允许为NULL
+ on_delete=models.CASCADE #gjw: 级联删除
+ )
+
+ # gjw:是否启用显示
+ # gjw:default=False: 新评论默认不显示(需要审核)
+ # gjw:blank=False, null=False: 必填字段,不能为空
+ is_enable = models.BooleanField(
+ _('enable'), # 显示名称:启用
+ default=False, # 默认不显示
+ blank=False, # 表单验证不允许为空
+ null=False # 数据库不允许为NULL
+ )
+
+ class Meta:
+ """模型的元数据配置"""
+
+ # gjw:默认按ID降序排列,新的评论显示在前面
+ ordering = ['-id']
+
+ # gjw:单数显示名称
+ verbose_name = _('comment')
+
+ # gjw:复数显示名称(与单数相同)
+ verbose_name_plural = verbose_name
+
+ # gjw:指定按ID字段获取最新记录
+ get_latest_by = 'id'
+
+ def __str__(self):
+ """
+ 模型的字符串表示方法
+ 在Django admin和其他显示场合使用
+ Returns:
+ str: 评论正文内容
+ """
+ return self.body
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/__init__.py b/src/djangoblog-master/comments/templatetags/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/__init__.py
rename to src/djangoblog-master/comments/templatetags/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/comments_tags.py b/src/djangoblog-master/comments/templatetags/comments_tags.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/comments_tags.py
rename to src/djangoblog-master/comments/templatetags/comments_tags.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/tests.py b/src/djangoblog-master/comments/tests.py
similarity index 56%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/tests.py
rename to src/djangoblog-master/comments/tests.py
index 2a7f55f..44c5db3 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/comments/tests.py
+++ b/src/djangoblog-master/comments/tests.py
@@ -1,68 +1,93 @@
from django.test import Client, RequestFactory, TransactionTestCase
from django.urls import reverse
-
from accounts.models import BlogUser
from blog.models import Category, Article
from comments.models import Comment
-from comments.templatetags.comments_tags import *
+from comments.templatetags.comments_tags import * # gjw:导入评论模板标签
from djangoblog.utils import get_max_articleid_commentid
-
-
-# Create your tests here.
-
+# gjw:评论功能测试类
class CommentsTest(TransactionTestCase):
+ """
+ 评论系统测试类
+ 测试评论的发布、回复、显示等功能
+ 使用TransactionTestCase确保测试数据库事务隔离
+ """
def setUp(self):
- self.client = Client()
- self.factory = RequestFactory()
+ """
+ 测试初始化方法,在每个测试方法执行前运行
+ 创建测试所需的用户、文章和配置
+ """
+ self.client = Client() # gjw:Django测试客户端,用于模拟HTTP请求
+ self.factory = RequestFactory() #gjw: 请求工厂,用于创建请求对象
+ # gjw:设置博客配置:评论需要审核
from blog.models import BlogSettings
value = BlogSettings()
- value.comment_need_review = True
+ value.comment_need_review = True # gjw:开启评论审核功能
value.save()
-
+ # gjw:创建超级用户用于测试
self.user = BlogUser.objects.create_superuser(
email="liangliangyy1@gmail.com",
username="liangliangyy1",
password="liangliangyy1")
def update_article_comment_status(self, article):
- comments = article.comment_set.all()
+ """
+ 更新文章所有评论的启用状态
+ 将文章的所有评论设置为启用状态(用于测试)
+ Args:
+ article: Article实例
+ """
+ comments = article.comment_set.all() #gjw: 获取文章的所有评论
for comment in comments:
- comment.is_enable = True
+ comment.is_enable = True # gjw:启用评论
comment.save()
def test_validate_comment(self):
+ """
+ 测试评论验证功能
+ 包括:评论发布、评论回复、评论树解析等
+ """
+ # gjw:登录测试用户
self.client.login(username='liangliangyy1', password='liangliangyy1')
+ # gjw:创建测试分类
category = Category()
category.name = "categoryccc"
category.save()
+ # gjw:创建测试文章
article = Article()
article.title = "nicetitleccc"
article.body = "nicecontentccc"
article.author = self.user
article.category = category
- article.type = 'a'
- article.status = 'p'
+ article.type = 'a' #gjw: 文章类型
+ article.status = 'p' # gjw:发布状态
article.save()
+ #gjw: 获取评论提交URL
comment_url = reverse(
'comments:postcomment', kwargs={
'article_id': article.id})
+ # gjw:测试提交第一条评论
response = self.client.post(comment_url,
{
- 'body': '123ffffffffff'
+ 'body': '123ffffffffff' # gjw:评论内容
})
+ # gjw:验证重定向响应(评论提交后应该重定向)
self.assertEqual(response.status_code, 302)
+ # gjw:重新获取文章对象,验证评论数量(由于需要审核,初始应为0)
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 0)
- self.update_article_comment_status(article)
+ # gjw:启用评论后再次验证
+ self.update_article_comment_status(article)
self.assertEqual(len(article.comment_list()), 1)
+ # gjw:测试提交第二条评论
response = self.client.post(comment_url,
{
'body': '123ffffffffff',
@@ -70,11 +95,15 @@ class CommentsTest(TransactionTestCase):
self.assertEqual(response.status_code, 302)
+ #gjw: 验证第二条评论
article = Article.objects.get(pk=article.pk)
self.update_article_comment_status(article)
self.assertEqual(len(article.comment_list()), 2)
+
+ # gjw:获取第一条评论的ID,用于回复测试
parent_comment_id = article.comment_list()[0].id
+ # gjw:测试提交回复评论(包含复杂内容)
response = self.client.post(comment_url,
{
'body': '''
@@ -89,21 +118,31 @@ class CommentsTest(TransactionTestCase):
[ddd](http://www.baidu.com)
- ''',
- 'parent_comment_id': parent_comment_id
+ ''', # gjw:包含Markdown格式的评论内容
+ 'parent_comment_id': parent_comment_id # gjw:父评论ID
})
+ # gjw:验证回复评论提交
self.assertEqual(response.status_code, 302)
self.update_article_comment_status(article)
+
+ # gjw:验证评论总数
article = Article.objects.get(pk=article.pk)
self.assertEqual(len(article.comment_list()), 3)
+
+ # gjw:测试评论树解析功能
comment = Comment.objects.get(id=parent_comment_id)
tree = parse_commenttree(article.comment_list(), comment)
- self.assertEqual(len(tree), 1)
+ self.assertEqual(len(tree), 1) # gjw:验证评论树结构
+
+ #gjw: 测试评论项显示功能
data = show_comment_item(comment, True)
self.assertIsNotNone(data)
+
+ # gjw:测试获取最大文章ID和评论ID
s = get_max_articleid_commentid()
self.assertIsNotNone(s)
+ #gjw: 测试评论邮件发送功能
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
new file mode 100644
index 0000000..87a381c
--- /dev/null
+++ b/src/djangoblog-master/comments/urls.py
@@ -0,0 +1,21 @@
+from django.urls import path
+
+from . import views # gjw:导入当前应用的视图模块
+
+# gjw:定义应用命名空间,用于URL反向解析时区分不同应用的相同URL名称
+app_name = "comments"
+
+# gjw:URL模式配置列表
+urlpatterns = [
+ # 评论提交URL配置
+ path(
+ # gjw:URL模式:/article/<文章ID>/postcomment
+ 'article//postcomment',
+
+ # gjw:对应的视图类:使用基于类的视图CommentPostView处理请求
+ views.CommentPostView.as_view(),
+
+ # gjw:URL名称:在模板和视图中使用reverse('comments:postcomment')进行反向解析
+ name='postcomment'
+ ),
+]
\ No newline at end of file
diff --git a/src/djangoblog-master/comments/utils.py b/src/djangoblog-master/comments/utils.py
new file mode 100644
index 0000000..f11b30b
--- /dev/null
+++ b/src/djangoblog-master/comments/utils.py
@@ -0,0 +1,72 @@
+import logging
+from django.utils.translation import gettext_lazy as _ # gjw:国际化翻译
+from djangoblog.utils import get_current_site # gjw:获取当前站点信息
+from djangoblog.utils import send_email # gjw:邮件发送工具
+
+# gjw:获取当前模块的日志记录器
+logger = logging.getLogger(__name__)
+
+
+def send_comment_email(comment):
+ """
+ 发送评论相关邮件通知
+ 功能:
+ 1. 向评论作者发送感谢邮件
+ 2. 如果评论是回复,向被回复者发送通知邮件
+
+ Args:
+ comment: Comment模型实例,包含评论相关信息
+ """
+ # gjw:获取当前站点域名
+ site = get_current_site().domain
+
+ # gjw:邮件主题:感谢评论
+ subject = _('Thanks for your comment')
+
+ # gjw:构建文章完整URL
+ article_url = f"https://{site}{comment.article.get_absolute_url()}"
+
+ # gjw:构建感谢评论作者的邮件HTML内容
+ html_content = _("""Thank you very much for your comments on this site
+ You can visit %(article_title)s
+ to review your comments,
+ Thank you again!
+
+ 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
+ }
+
+ # gjw:获取评论作者的邮箱
+ tomail = comment.author.email
+
+ # gjw:发送感谢邮件给评论作者
+ send_email([tomail], subject, html_content)
+
+ try:
+ # gjw:检查是否是回复评论(有父评论)
+ if comment.parent_comment:
+ #gjw: 构建回复通知邮件的HTML内容
+ html_content = _("""Your comment on %(article_title)s
has
+ received a reply.
%(comment_body)s
+
+ go check it out!
+
+ 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 # gjw:被回复的评论内容
+ }
+
+ # gjw:获取被回复评论作者的邮箱
+ tomail = comment.parent_comment.author.email
+
+ # gjw:发送回复通知邮件给被回复者
+ send_email([tomail], subject, html_content)
+
+ except Exception as e:
+ # gjw:记录邮件发送异常,但不影响主流程
+ logger.error(e)
\ No newline at end of file
diff --git a/src/djangoblog-master/comments/views.py b/src/djangoblog-master/comments/views.py
new file mode 100644
index 0000000..5270422
--- /dev/null
+++ b/src/djangoblog-master/comments/views.py
@@ -0,0 +1,119 @@
+# Create your views here.
+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
+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
+class CommentPostView(FormView):
+ """
+ 评论提交视图类
+ 处理用户评论的提交和验证
+ """
+
+ # gjw:指定使用的表单类
+ form_class = CommentForm
+ # gjw:指定模板名称
+ template_name = 'blog/article_detail.html'
+
+ @method_decorator(csrf_protect)
+ def dispatch(self, *args, **kwargs):
+ """
+ 重写dispatch方法,添加CSRF保护
+ 防止跨站请求伪造攻击
+ """
+ return super(CommentPostView, self).dispatch(*args, **kwargs)
+
+ def get(self, request, *args, **kwargs):
+ """
+ 处理GET请求
+ 当用户通过GET方式访问评论提交URL时,重定向到文章详情页的评论区域
+ """
+ # gjw:从URL参数中获取文章ID
+ article_id = self.kwargs['article_id']
+ # gjw:获取文章对象,如果不存在返回404
+ article = get_object_or_404(Article, pk=article_id)
+ # gjw:获取文章的绝对URL
+ url = article.get_absolute_url()
+ #gjw: 重定向到文章详情页的评论区域
+ return HttpResponseRedirect(url + "#comments")
+
+ def form_invalid(self, form):
+ """
+ 表单验证失败时的处理逻辑
+ Args:
+ form: 验证失败的表单对象
+ Returns:
+ 渲染响应,显示错误信息
+ """
+ # gjw:获取文章ID
+ article_id = self.kwargs['article_id']
+ # gjw:获取文章对象
+ article = get_object_or_404(Article, pk=article_id)
+
+ # gjw:重新渲染模板,显示表单错误信息
+ return self.render_to_response({
+ 'form': form, # gjw:包含错误信息的表单
+ 'article': article #gjw: 文章对象
+ })
+
+ def form_valid(self, form):
+ """
+ 表单验证成功时的处理逻辑
+ 保存评论数据到数据库
+ Args:
+ form: 验证成功的表单对象
+ Returns:
+ 重定向响应,跳转到评论位置
+ Raises:
+ ValidationError: 当文章评论关闭时抛出异常
+ """
+ # gjw:获取当前登录用户
+ user = self.request.user
+ # gjw:根据用户ID获取用户对象
+ author = BlogUser.objects.get(pk=user.pk)
+ # gjw:从URL参数获取文章ID
+ article_id = self.kwargs['article_id']
+ # gjw:获取文章对象
+ article = get_object_or_404(Article, pk=article_id)
+
+ #gjw: 检查文章是否允许评论
+ #gjw: 'c' 可能表示关闭状态
+ if article.comment_status == 'c' or article.status == 'c':
+ raise ValidationError("该文章评论已关闭.")
+
+ # gjw:获取表单数据但不立即保存到数据库
+ comment = form.save(False)
+ #gjw: 设置评论关联的文章
+ comment.article = article
+
+ # gjw:获取博客设置
+ from djangoblog.utils import get_blog_setting
+ settings = get_blog_setting()
+
+ # gjw:如果博客设置不需要评论审核,则自动启用评论
+ if not settings.comment_need_review:
+ comment.is_enable = True
+
+ #gjw: 设置评论作者
+ comment.author = author
+
+ #gjw: 处理回复评论的情况
+ if form.cleaned_data['parent_comment_id']:
+ #gjw: 获取父评论对象
+ parent_comment = Comment.objects.get(
+ pk=form.cleaned_data['parent_comment_id'])
+ # gjw:设置评论的父评论
+ comment.parent_comment = parent_comment
+
+ # gjw:保存评论到数据库
+ comment.save(True)
+
+ # gjw:重定向到文章详情页,并定位到新提交的评论位置
+ return HttpResponseRedirect(
+ "%s#div-comment-%d" % # gjw:使用锚点定位到具体评论
+ (article.get_absolute_url(), comment.pk)) # gjw:文章URL和评论ID
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml b/src/djangoblog-master/deploy/docker-compose/docker-compose.es.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml
rename to src/djangoblog-master/deploy/docker-compose/docker-compose.es.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.yml b/src/djangoblog-master/deploy/docker-compose/docker-compose.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.yml
rename to src/djangoblog-master/deploy/docker-compose/docker-compose.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/entrypoint.sh b/src/djangoblog-master/deploy/entrypoint.sh
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/entrypoint.sh
rename to src/djangoblog-master/deploy/entrypoint.sh
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/configmap.yaml b/src/djangoblog-master/deploy/k8s/configmap.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/configmap.yaml
rename to src/djangoblog-master/deploy/k8s/configmap.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/deployment.yaml b/src/djangoblog-master/deploy/k8s/deployment.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/deployment.yaml
rename to src/djangoblog-master/deploy/k8s/deployment.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/gateway.yaml b/src/djangoblog-master/deploy/k8s/gateway.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/gateway.yaml
rename to src/djangoblog-master/deploy/k8s/gateway.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pv.yaml b/src/djangoblog-master/deploy/k8s/pv.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pv.yaml
rename to src/djangoblog-master/deploy/k8s/pv.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pvc.yaml b/src/djangoblog-master/deploy/k8s/pvc.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pvc.yaml
rename to src/djangoblog-master/deploy/k8s/pvc.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/service.yaml b/src/djangoblog-master/deploy/k8s/service.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/service.yaml
rename to src/djangoblog-master/deploy/k8s/service.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/storageclass.yaml b/src/djangoblog-master/deploy/k8s/storageclass.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/storageclass.yaml
rename to src/djangoblog-master/deploy/k8s/storageclass.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/nginx.conf b/src/djangoblog-master/deploy/nginx.conf
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/nginx.conf
rename to src/djangoblog-master/deploy/nginx.conf
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/README-en.md b/src/djangoblog-master/docs/README-en.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/README-en.md
rename to src/djangoblog-master/docs/README-en.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/config-en.md b/src/djangoblog-master/docs/config-en.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/config-en.md
rename to src/djangoblog-master/docs/config-en.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/config.md b/src/djangoblog-master/docs/config.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/config.md
rename to src/djangoblog-master/docs/config.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/docker-en.md b/src/djangoblog-master/docs/docker-en.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/docker-en.md
rename to src/djangoblog-master/docs/docker-en.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/docker.md b/src/djangoblog-master/docs/docker.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/docker.md
rename to src/djangoblog-master/docs/docker.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/es.md b/src/djangoblog-master/docs/es.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/es.md
rename to src/djangoblog-master/docs/es.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/alipay.jpg b/src/djangoblog-master/docs/imgs/alipay.jpg
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/imgs/alipay.jpg
rename to src/djangoblog-master/docs/imgs/alipay.jpg
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/pycharm_logo.png b/src/djangoblog-master/docs/imgs/pycharm_logo.png
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/imgs/pycharm_logo.png
rename to src/djangoblog-master/docs/imgs/pycharm_logo.png
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/wechat.jpg b/src/djangoblog-master/docs/imgs/wechat.jpg
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/imgs/wechat.jpg
rename to src/djangoblog-master/docs/imgs/wechat.jpg
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/k8s-en.md b/src/djangoblog-master/docs/k8s-en.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/k8s-en.md
rename to src/djangoblog-master/docs/k8s-en.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/k8s.md b/src/djangoblog-master/docs/k8s.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/k8s.md
rename to src/djangoblog-master/docs/k8s.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo b/src/djangoblog-master/locale/en/LC_MESSAGES/django.mo
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo
rename to src/djangoblog-master/locale/en/LC_MESSAGES/django.mo
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.po b/src/djangoblog-master/locale/en/LC_MESSAGES/django.po
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.po
rename to src/djangoblog-master/locale/en/LC_MESSAGES/django.po
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo b/src/djangoblog-master/locale/zh_Hans/LC_MESSAGES/django.mo
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo
rename to src/djangoblog-master/locale/zh_Hans/LC_MESSAGES/django.mo
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.po b/src/djangoblog-master/locale/zh_Hans/LC_MESSAGES/django.po
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.po
rename to src/djangoblog-master/locale/zh_Hans/LC_MESSAGES/django.po
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo b/src/djangoblog-master/locale/zh_Hant/LC_MESSAGES/django.mo
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo
rename to src/djangoblog-master/locale/zh_Hant/LC_MESSAGES/django.mo
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po b/src/djangoblog-master/locale/zh_Hant/LC_MESSAGES/django.po
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po
rename to src/djangoblog-master/locale/zh_Hant/LC_MESSAGES/django.po
diff --git a/src/djangoblog-master/manage.py b/src/djangoblog-master/manage.py
new file mode 100644
index 0000000..22803b7
--- /dev/null
+++ b/src/djangoblog-master/manage.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# 指定使用env来查找python解释器,这样可以跨平台使用
+
+import os
+import sys
+
+# 当这个脚本被直接执行时(而不是被导入为模块)
+if __name__ == "__main__":
+ # 设置默认的DJANGO_SETTINGS_MODULE环境变量
+ # 这里指定了项目的settings模块为"djangoblog.settings"
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings")
+
+ try:
+ # 尝试从django.core.management导入execute_from_command_line函数
+ # 这个函数用于解析和执行Django管理命令
+ from django.core.management import execute_from_command_line
+ except ImportError:
+ # 如果导入失败,可能是由于Django没有安装或其他原因
+ # 下面的代码用于更精确地判断是否是Django未安装导致的错误
+
+ # 尝试直接导入django模块
+ try:
+ import django
+ except ImportError:
+ # 如果django模块也无法导入,说明Django确实没有安装
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ )
+ # 如果能导入django但无法导入execute_from_command_line,则抛出原始异常
+ raise
+
+ # 执行命令行参数指定的Django管理命令
+ # sys.argv包含了命令行参数,例如['manage.py', 'runserver']
+ execute_from_command_line(sys.argv)
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/__init__.py b/src/djangoblog-master/oauth/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/__init__.py
rename to src/djangoblog-master/oauth/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/admin.py b/src/djangoblog-master/oauth/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/admin.py
rename to src/djangoblog-master/oauth/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/apps.py b/src/djangoblog-master/oauth/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/apps.py
rename to src/djangoblog-master/oauth/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/forms.py b/src/djangoblog-master/oauth/forms.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/forms.py
rename to src/djangoblog-master/oauth/forms.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0001_initial.py b/src/djangoblog-master/oauth/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0001_initial.py
rename to src/djangoblog-master/oauth/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py b/src/djangoblog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py
rename to src/djangoblog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py b/src/djangoblog-master/oauth/migrations/0003_alter_oauthuser_nickname.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py
rename to src/djangoblog-master/oauth/migrations/0003_alter_oauthuser_nickname.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/__init__.py b/src/djangoblog-master/oauth/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/__init__.py
rename to src/djangoblog-master/oauth/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/models.py b/src/djangoblog-master/oauth/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/models.py
rename to src/djangoblog-master/oauth/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/oauthmanager.py b/src/djangoblog-master/oauth/oauthmanager.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/oauthmanager.py
rename to src/djangoblog-master/oauth/oauthmanager.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/__init__.py b/src/djangoblog-master/oauth/templatetags/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/__init__.py
rename to src/djangoblog-master/oauth/templatetags/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/oauth_tags.py b/src/djangoblog-master/oauth/templatetags/oauth_tags.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/oauth_tags.py
rename to src/djangoblog-master/oauth/templatetags/oauth_tags.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/tests.py b/src/djangoblog-master/oauth/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/tests.py
rename to src/djangoblog-master/oauth/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/urls.py b/src/djangoblog-master/oauth/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/urls.py
rename to src/djangoblog-master/oauth/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/views.py b/src/djangoblog-master/oauth/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/views.py
rename to src/djangoblog-master/oauth/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/__init__.py b/src/djangoblog-master/owntracks/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/__init__.py
rename to src/djangoblog-master/owntracks/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/admin.py b/src/djangoblog-master/owntracks/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/admin.py
rename to src/djangoblog-master/owntracks/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/apps.py b/src/djangoblog-master/owntracks/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/apps.py
rename to src/djangoblog-master/owntracks/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0001_initial.py b/src/djangoblog-master/owntracks/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0001_initial.py
rename to src/djangoblog-master/owntracks/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/src/djangoblog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py
rename to src/djangoblog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/__init__.py b/src/djangoblog-master/owntracks/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/__init__.py
rename to src/djangoblog-master/owntracks/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/models.py b/src/djangoblog-master/owntracks/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/models.py
rename to src/djangoblog-master/owntracks/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/tests.py b/src/djangoblog-master/owntracks/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/tests.py
rename to src/djangoblog-master/owntracks/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/urls.py b/src/djangoblog-master/owntracks/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/urls.py
rename to src/djangoblog-master/owntracks/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/views.py b/src/djangoblog-master/owntracks/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/views.py
rename to src/djangoblog-master/owntracks/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/__init__.py b/src/djangoblog-master/plugins/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/__init__.py
rename to src/djangoblog-master/plugins/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/__init__.py b/src/djangoblog-master/plugins/article_copyright/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/__init__.py
rename to src/djangoblog-master/plugins/article_copyright/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/plugin.py b/src/djangoblog-master/plugins/article_copyright/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/plugin.py
rename to src/djangoblog-master/plugins/article_copyright/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/__init__.py b/src/djangoblog-master/plugins/external_links/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/__init__.py
rename to src/djangoblog-master/plugins/external_links/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/plugin.py b/src/djangoblog-master/plugins/external_links/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/plugin.py
rename to src/djangoblog-master/plugins/external_links/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/__init__.py b/src/djangoblog-master/plugins/reading_time/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/__init__.py
rename to src/djangoblog-master/plugins/reading_time/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/plugin.py b/src/djangoblog-master/plugins/reading_time/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/plugin.py
rename to src/djangoblog-master/plugins/reading_time/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/__init__.py b/src/djangoblog-master/plugins/seo_optimizer/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/__init__.py
rename to src/djangoblog-master/plugins/seo_optimizer/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/plugin.py b/src/djangoblog-master/plugins/seo_optimizer/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/plugin.py
rename to src/djangoblog-master/plugins/seo_optimizer/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/__init__.py b/src/djangoblog-master/plugins/view_count/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/__init__.py
rename to src/djangoblog-master/plugins/view_count/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/plugin.py b/src/djangoblog-master/plugins/view_count/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/plugin.py
rename to src/djangoblog-master/plugins/view_count/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/requirements.txt b/src/djangoblog-master/requirements.txt
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/requirements.txt
rename to src/djangoblog-master/requirements.txt
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/MemcacheStorage.py b/src/djangoblog-master/servermanager/MemcacheStorage.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/MemcacheStorage.py
rename to src/djangoblog-master/servermanager/MemcacheStorage.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/__init__.py b/src/djangoblog-master/servermanager/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/__init__.py
rename to src/djangoblog-master/servermanager/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/admin.py b/src/djangoblog-master/servermanager/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/admin.py
rename to src/djangoblog-master/servermanager/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/__init__.py b/src/djangoblog-master/servermanager/api/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/api/__init__.py
rename to src/djangoblog-master/servermanager/api/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/blogapi.py b/src/djangoblog-master/servermanager/api/blogapi.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/api/blogapi.py
rename to src/djangoblog-master/servermanager/api/blogapi.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/commonapi.py b/src/djangoblog-master/servermanager/api/commonapi.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/api/commonapi.py
rename to src/djangoblog-master/servermanager/api/commonapi.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/apps.py b/src/djangoblog-master/servermanager/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/apps.py
rename to src/djangoblog-master/servermanager/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0001_initial.py b/src/djangoblog-master/servermanager/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0001_initial.py
rename to src/djangoblog-master/servermanager/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/src/djangoblog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py
rename to src/djangoblog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/__init__.py b/src/djangoblog-master/servermanager/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/__init__.py
rename to src/djangoblog-master/servermanager/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/models.py b/src/djangoblog-master/servermanager/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/models.py
rename to src/djangoblog-master/servermanager/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/robot.py b/src/djangoblog-master/servermanager/robot.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/robot.py
rename to src/djangoblog-master/servermanager/robot.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/tests.py b/src/djangoblog-master/servermanager/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/tests.py
rename to src/djangoblog-master/servermanager/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/urls.py b/src/djangoblog-master/servermanager/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/urls.py
rename to src/djangoblog-master/servermanager/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/views.py b/src/djangoblog-master/servermanager/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/views.py
rename to src/djangoblog-master/servermanager/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/account/forget_password.html b/src/djangoblog-master/templates/account/forget_password.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/account/forget_password.html
rename to src/djangoblog-master/templates/account/forget_password.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/account/login.html b/src/djangoblog-master/templates/account/login.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/account/login.html
rename to src/djangoblog-master/templates/account/login.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/account/registration_form.html b/src/djangoblog-master/templates/account/registration_form.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/account/registration_form.html
rename to src/djangoblog-master/templates/account/registration_form.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/account/result.html b/src/djangoblog-master/templates/account/result.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/account/result.html
rename to src/djangoblog-master/templates/account/result.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_archives.html b/src/djangoblog-master/templates/blog/article_archives.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_archives.html
rename to src/djangoblog-master/templates/blog/article_archives.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_detail.html b/src/djangoblog-master/templates/blog/article_detail.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_detail.html
rename to src/djangoblog-master/templates/blog/article_detail.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_index.html b/src/djangoblog-master/templates/blog/article_index.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_index.html
rename to src/djangoblog-master/templates/blog/article_index.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/error_page.html b/src/djangoblog-master/templates/blog/error_page.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/error_page.html
rename to src/djangoblog-master/templates/blog/error_page.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/links_list.html b/src/djangoblog-master/templates/blog/links_list.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/links_list.html
rename to src/djangoblog-master/templates/blog/links_list.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_info.html b/src/djangoblog-master/templates/blog/tags/article_info.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_info.html
rename to src/djangoblog-master/templates/blog/tags/article_info.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_meta_info.html b/src/djangoblog-master/templates/blog/tags/article_meta_info.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_meta_info.html
rename to src/djangoblog-master/templates/blog/tags/article_meta_info.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_pagination.html b/src/djangoblog-master/templates/blog/tags/article_pagination.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_pagination.html
rename to src/djangoblog-master/templates/blog/tags/article_pagination.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_tag_list.html b/src/djangoblog-master/templates/blog/tags/article_tag_list.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_tag_list.html
rename to src/djangoblog-master/templates/blog/tags/article_tag_list.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/breadcrumb.html b/src/djangoblog-master/templates/blog/tags/breadcrumb.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/breadcrumb.html
rename to src/djangoblog-master/templates/blog/tags/breadcrumb.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/sidebar.html b/src/djangoblog-master/templates/blog/tags/sidebar.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/sidebar.html
rename to src/djangoblog-master/templates/blog/tags/sidebar.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item.html b/src/djangoblog-master/templates/comments/tags/comment_item.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item.html
rename to src/djangoblog-master/templates/comments/tags/comment_item.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item_tree.html b/src/djangoblog-master/templates/comments/tags/comment_item_tree.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item_tree.html
rename to src/djangoblog-master/templates/comments/tags/comment_item_tree.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_list.html b/src/djangoblog-master/templates/comments/tags/comment_list.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_list.html
rename to src/djangoblog-master/templates/comments/tags/comment_list.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/post_comment.html b/src/djangoblog-master/templates/comments/tags/post_comment.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/post_comment.html
rename to src/djangoblog-master/templates/comments/tags/post_comment.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/bindsuccess.html b/src/djangoblog-master/templates/oauth/bindsuccess.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/oauth/bindsuccess.html
rename to src/djangoblog-master/templates/oauth/bindsuccess.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/oauth_applications.html b/src/djangoblog-master/templates/oauth/oauth_applications.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/oauth/oauth_applications.html
rename to src/djangoblog-master/templates/oauth/oauth_applications.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/require_email.html b/src/djangoblog-master/templates/oauth/require_email.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/oauth/require_email.html
rename to src/djangoblog-master/templates/oauth/require_email.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_log_dates.html b/src/djangoblog-master/templates/owntracks/show_log_dates.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_log_dates.html
rename to src/djangoblog-master/templates/owntracks/show_log_dates.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_maps.html b/src/djangoblog-master/templates/owntracks/show_maps.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_maps.html
rename to src/djangoblog-master/templates/owntracks/show_maps.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/search/indexes/blog/article_text.txt b/src/djangoblog-master/templates/search/indexes/blog/article_text.txt
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/search/indexes/blog/article_text.txt
rename to src/djangoblog-master/templates/search/indexes/blog/article_text.txt
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/search/search.html b/src/djangoblog-master/templates/search/search.html
similarity index 99%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/search/search.html
rename to src/djangoblog-master/templates/search/search.html
index 1404c60..111f271 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/templates/search/search.html
+++ b/src/djangoblog-master/templates/search/search.html
@@ -62,5 +62,5 @@
{% block sidebar %}
{% load_sidebar request.user 'i' %}
{% endblock %}
-
+/*hello*/
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/adsense.html b/src/djangoblog-master/templates/share_layout/adsense.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/adsense.html
rename to src/djangoblog-master/templates/share_layout/adsense.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base.html b/src/djangoblog-master/templates/share_layout/base.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base.html
rename to src/djangoblog-master/templates/share_layout/base.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base_account.html b/src/djangoblog-master/templates/share_layout/base_account.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base_account.html
rename to src/djangoblog-master/templates/share_layout/base_account.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/footer.html b/src/djangoblog-master/templates/share_layout/footer.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/footer.html
rename to src/djangoblog-master/templates/share_layout/footer.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav.html b/src/djangoblog-master/templates/share_layout/nav.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav.html
rename to src/djangoblog-master/templates/share_layout/nav.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav_node.html b/src/djangoblog-master/templates/share_layout/nav_node.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav_node.html
rename to src/djangoblog-master/templates/share_layout/nav_node.html
diff --git a/src/test.py b/src/test.py
deleted file mode 100644
index e69de29..0000000