Compare commits
59 Commits
yyb_branch
...
zjy_branch
@ -0,0 +1 @@
|
||||
基于Python的Hello World
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,41 +0,0 @@
|
||||
#yyb 导入必要的模块
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
|
||||
#yyb 获取日志记录器
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SpiderNotify():
|
||||
"""#yyb 搜索引擎爬虫通知类"""
|
||||
|
||||
@staticmethod
|
||||
def baidu_notify(urls):
|
||||
"""
|
||||
#yyb 向百度站长平台提交链接,通知百度爬虫抓取更新内容
|
||||
|
||||
Args:
|
||||
urls: 需要通知的URL列表
|
||||
"""
|
||||
try:
|
||||
#yyb 将URL列表转换为换行分隔的字符串格式
|
||||
data = '\n'.join(urls)
|
||||
#yyb 向百度站长平台API提交URL数据
|
||||
result = requests.post(settings.BAIDU_NOTIFY_URL, data=data)
|
||||
#yyb 记录API返回结果
|
||||
logger.info(result.text)
|
||||
except Exception as e:
|
||||
#yyb 记录通知过程中的错误
|
||||
logger.error(e)
|
||||
|
||||
@staticmethod
|
||||
def notify(url):
|
||||
"""
|
||||
#yyb 通用的爬虫通知方法(目前仅支持百度)
|
||||
|
||||
Args:
|
||||
url: 需要通知的URL
|
||||
"""
|
||||
SpiderNotify.baidu_notify(url)
|
||||
@ -1,44 +0,0 @@
|
||||
#yyb 导入Django测试框架
|
||||
from django.test import TestCase
|
||||
|
||||
#yyb 导入工具函数
|
||||
from djangoblog.utils import *
|
||||
|
||||
|
||||
class DjangoBlogTest(TestCase):
|
||||
"""#yyb DjangoBlog应用测试类"""
|
||||
|
||||
def setUp(self):
|
||||
"""#yyb 测试初始化方法"""
|
||||
#yyb 可以在这里设置测试数据,当前为空
|
||||
pass
|
||||
|
||||
def test_utils(self):
|
||||
"""#yyb 测试工具函数"""
|
||||
#yyb 测试SHA256加密函数
|
||||
md5 = get_sha256('test')
|
||||
self.assertIsNotNone(md5) #yyb 断言加密结果不为空
|
||||
|
||||
#yyb 测试Markdown转换函数
|
||||
c = CommonMarkdown.get_markdown('''
|
||||
# Title1
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
[url](https://www.lylinux.net/)
|
||||
|
||||
[ddd](http://www.baidu.com)
|
||||
|
||||
|
||||
''')
|
||||
self.assertIsNotNone(c) #yyb 断言Markdown转换结果不为空
|
||||
|
||||
#yyb 测试字典转URL参数字符串函数
|
||||
d = {
|
||||
'd': 'key1',
|
||||
'd2': 'key2'
|
||||
}
|
||||
data = parse_dict_to_url(d)
|
||||
self.assertIsNotNone(data) #yyb 断言转换结果不为空
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,13 @@
|
||||
#zjy 评论模块的应用配置
|
||||
#zjy 定义评论应用的配置信息
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CommentsConfig(AppConfig):
|
||||
#zjy 指定该 App 在项目中的名称(即所在目录名)
|
||||
name = 'comments'
|
||||
|
||||
#zjy (可选)可以在这里做初始化操作,如引入 signals
|
||||
# def ready(self):
|
||||
# import comments.signals
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,131 @@
|
||||
#zjy 评论模块的测试用例
|
||||
#zjy 测试评论的提交、审核、显示和邮件通知等功能
|
||||
|
||||
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 djangoblog.utils import get_max_articleid_commentid
|
||||
|
||||
|
||||
class CommentsTest(TransactionTestCase):
|
||||
#zjy 评论相关功能测试类
|
||||
#zjy 使用 TransactionTestCase 允许测试包含事务的数据库操作
|
||||
|
||||
def setUp(self):
|
||||
#zjy 测试初始化工作:
|
||||
#zjy - 创建请求客户端
|
||||
#zjy - 配置博客系统为"评论需要审核"
|
||||
#zjy - 创建一个超级管理员用户(用于登录发表评论)
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
from blog.models import BlogSettings
|
||||
value = BlogSettings()
|
||||
value.comment_need_review = True #zjy 开启评论审核,提交的评论默认不显示
|
||||
value.save()
|
||||
|
||||
#zjy 创建可登录的超级管理员用户
|
||||
self.user = BlogUser.objects.create_superuser(
|
||||
email="liangliangyy1@gmail.com",
|
||||
username="liangliangyy1",
|
||||
password="liangliangyy1"
|
||||
)
|
||||
|
||||
def update_article_comment_status(self, article):
|
||||
#zjy 将文章下所有评论改为 is_enable=True
|
||||
#zjy 模拟管理员审核通过评论(使评论显示)
|
||||
comments = article.comment_set.all()
|
||||
for comment in comments:
|
||||
comment.is_enable = True
|
||||
comment.save()
|
||||
|
||||
def test_validate_comment(self):
|
||||
#zjy 测试评论提交、审核、评论树解析等功能流程
|
||||
#zjy 登录用户
|
||||
self.client.login(username='liangliangyy1', password='liangliangyy1')
|
||||
|
||||
#zjy 创建分类
|
||||
category = Category()
|
||||
category.name = "categoryccc"
|
||||
category.save()
|
||||
|
||||
#zjy 创建文章
|
||||
article = Article()
|
||||
article.title = "nicetitleccc"
|
||||
article.body = "nicecontentccc"
|
||||
article.author = self.user
|
||||
article.category = category
|
||||
article.type = 'a'
|
||||
article.status = 'p'
|
||||
article.save()
|
||||
|
||||
#zjy 文章评论提交 URL
|
||||
comment_url = reverse('comments:postcomment', kwargs={'article_id': article.id})
|
||||
|
||||
#zjy 用户提交第一条评论
|
||||
response = self.client.post(comment_url, {'body': '123ffffffffff'})
|
||||
self.assertEqual(response.status_code, 302) #zjy 正常应重定向(提交成功)
|
||||
|
||||
#zjy 因为评论需要审核,未批准前评论数为 0
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.assertEqual(len(article.comment_list()), 0)
|
||||
|
||||
#zjy 模拟管理员审核评论
|
||||
self.update_article_comment_status(article)
|
||||
self.assertEqual(len(article.comment_list()), 1)
|
||||
|
||||
#zjy 提交第二条评论
|
||||
response = self.client.post(comment_url, {'body': '123ffffffffff'})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.update_article_comment_status(article)
|
||||
self.assertEqual(len(article.comment_list()), 2)
|
||||
|
||||
#zjy 回复第一条评论(测试 parent_comment 功能)
|
||||
parent_comment_id = article.comment_list()[0].id
|
||||
|
||||
response = self.client.post(comment_url, {
|
||||
'body': '''
|
||||
# Title1
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
[url](https://www.lylinux.net/)
|
||||
|
||||
[ddd](http://www.baidu.com)
|
||||
''',
|
||||
'parent_comment_id': parent_comment_id
|
||||
})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
#zjy 通过审核
|
||||
self.update_article_comment_status(article)
|
||||
|
||||
#zjy 再次获取文章
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.assertEqual(len(article.comment_list()), 3)
|
||||
|
||||
#zjy 测试评论树解析
|
||||
comment = Comment.objects.get(id=parent_comment_id)
|
||||
tree = parse_commenttree(article.comment_list(), comment)
|
||||
self.assertEqual(len(tree), 1) #zjy 第一条评论应当有 1 个子评论
|
||||
|
||||
#zjy 渲染评论组件标签是否正常返回
|
||||
data = show_comment_item(comment, True)
|
||||
self.assertIsNotNone(data)
|
||||
|
||||
#zjy 测试工具函数获取最大文章/评论 id
|
||||
s = get_max_articleid_commentid()
|
||||
self.assertIsNotNone(s)
|
||||
|
||||
#zjy 测试发送评论邮件通知(若配置邮件服务则会成功)
|
||||
from comments.utils import send_comment_email
|
||||
send_comment_email(comment)
|
||||
send_comment_email(comment)
|
||||
@ -0,0 +1,42 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic import View
|
||||
|
||||
from blog.models import Article
|
||||
from .models import Comment
|
||||
from .forms import CommentForm
|
||||
|
||||
|
||||
class CommentPostView(LoginRequiredMixin, View):
|
||||
"""
|
||||
负责处理评论提交
|
||||
"""
|
||||
|
||||
def post(self, request, article_id):
|
||||
# 获取目标文章
|
||||
article = get_object_or_404(Article, pk=article_id)
|
||||
|
||||
form = CommentForm(request.POST)
|
||||
if form.is_valid():
|
||||
# 获取评论内容
|
||||
body = form.cleaned_data['body'].strip()
|
||||
parent_id = form.cleaned_data.get('parent_comment_id')
|
||||
|
||||
comment = Comment()
|
||||
comment.article = article
|
||||
comment.author = request.user
|
||||
comment.body = body
|
||||
|
||||
# 判断是否是子评论(回复)
|
||||
if parent_id:
|
||||
try:
|
||||
parent_comment = Comment.objects.get(id=parent_id)
|
||||
comment.parent_comment = parent_comment
|
||||
except Comment.DoesNotExist:
|
||||
pass
|
||||
|
||||
comment.save()
|
||||
|
||||
# 评论成功后返回文章页面
|
||||
return HttpResponseRedirect(article.get_absolute_url())
|
||||
@ -0,0 +1,44 @@
|
||||
#zjy 评论模块的视图处理
|
||||
#zjy 处理评论的提交和显示逻辑
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic import View
|
||||
|
||||
from blog.models import Article
|
||||
from .models import Comment
|
||||
from .forms import CommentForm
|
||||
|
||||
|
||||
class CommentPostView(LoginRequiredMixin, View):
|
||||
#zjy 负责处理评论提交
|
||||
#zjy 使用类视图方式处理POST请求
|
||||
|
||||
def post(self, request, article_id):
|
||||
#zjy 获取目标文章
|
||||
article = get_object_or_404(Article, pk=article_id)
|
||||
|
||||
form = CommentForm(request.POST)
|
||||
if form.is_valid():
|
||||
#zjy 获取评论内容
|
||||
body = form.cleaned_data['body'].strip()
|
||||
parent_id = form.cleaned_data.get('parent_comment_id')
|
||||
|
||||
comment = Comment()
|
||||
comment.article = article
|
||||
comment.author = request.user
|
||||
comment.body = body
|
||||
|
||||
#zjy 判断是否是子评论(回复)
|
||||
if parent_id:
|
||||
try:
|
||||
parent_comment = Comment.objects.get(id=parent_id)
|
||||
comment.parent_comment = parent_comment
|
||||
except Comment.DoesNotExist:
|
||||
pass
|
||||
|
||||
comment.save()
|
||||
|
||||
#zjy 评论成功后返回文章页面
|
||||
return HttpResponseRedirect(article.get_absolute_url())
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
}
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -1,41 +0,0 @@
|
||||
#yyb 导入必要的模块
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
|
||||
#yyb 获取日志记录器
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SpiderNotify():
|
||||
"""#yyb 搜索引擎爬虫通知类"""
|
||||
|
||||
@staticmethod
|
||||
def baidu_notify(urls):
|
||||
"""
|
||||
#yyb 向百度站长平台提交链接,通知百度爬虫抓取更新内容
|
||||
|
||||
Args:
|
||||
urls: 需要通知的URL列表
|
||||
"""
|
||||
try:
|
||||
#yyb 将URL列表转换为换行分隔的字符串格式
|
||||
data = '\n'.join(urls)
|
||||
#yyb 向百度站长平台API提交URL数据
|
||||
result = requests.post(settings.BAIDU_NOTIFY_URL, data=data)
|
||||
#yyb 记录API返回结果
|
||||
logger.info(result.text)
|
||||
except Exception as e:
|
||||
#yyb 记录通知过程中的错误
|
||||
logger.error(e)
|
||||
|
||||
@staticmethod
|
||||
def notify(url):
|
||||
"""
|
||||
#yyb 通用的爬虫通知方法(目前仅支持百度)
|
||||
|
||||
Args:
|
||||
url: 需要通知的URL
|
||||
"""
|
||||
SpiderNotify.baidu_notify(url)
|
||||
@ -1,44 +0,0 @@
|
||||
#yyb 导入Django测试框架
|
||||
from django.test import TestCase
|
||||
|
||||
#yyb 导入工具函数
|
||||
from djangoblog.utils import *
|
||||
|
||||
|
||||
class DjangoBlogTest(TestCase):
|
||||
"""#yyb DjangoBlog应用测试类"""
|
||||
|
||||
def setUp(self):
|
||||
"""#yyb 测试初始化方法"""
|
||||
#yyb 可以在这里设置测试数据,当前为空
|
||||
pass
|
||||
|
||||
def test_utils(self):
|
||||
"""#yyb 测试工具函数"""
|
||||
#yyb 测试SHA256加密函数
|
||||
md5 = get_sha256('test')
|
||||
self.assertIsNotNone(md5) #yyb 断言加密结果不为空
|
||||
|
||||
#yyb 测试Markdown转换函数
|
||||
c = CommonMarkdown.get_markdown('''
|
||||
# Title1
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
[url](https://www.lylinux.net/)
|
||||
|
||||
[ddd](http://www.baidu.com)
|
||||
|
||||
|
||||
''')
|
||||
self.assertIsNotNone(c) #yyb 断言Markdown转换结果不为空
|
||||
|
||||
#yyb 测试字典转URL参数字符串函数
|
||||
d = {
|
||||
'd': 'key1',
|
||||
'd2': 'key2'
|
||||
}
|
||||
data = parse_dict_to_url(d)
|
||||
self.assertIsNotNone(data) #yyb 断言转换结果不为空
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue