diff --git a/.idea/Django.iml b/.idea/Django.iml
index f571432..07abf20 100644
--- a/.idea/Django.iml
+++ b/.idea/Django.iml
@@ -5,4 +5,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index db8786c..060d2c5 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,4 @@
-
-
-
\ No newline at end of file
diff --git a/doc/26组软件数据模型设计说明书.docx b/doc/26组软件数据模型设计说明书.docx
deleted file mode 100644
index 66ccd68..0000000
Binary files a/doc/26组软件数据模型设计说明书.docx and /dev/null differ
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/blog/context_processors.py b/src/DjangoBlog-master(1)/DjangoBlog-master/blog/context_processors.py
index cc2a5dc..4a62f34 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/blog/context_processors.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/blog/context_processors.py
@@ -1,44 +1,88 @@
+# 导入日志模块,用于记录系统运行时的信息和错误
import logging
+# 导入Django的时间工具,用于获取当前时间
from django.utils import timezone
+# 导入自定义工具:缓存工具和获取博客设置的方法
from djangoblog.utils import cache, get_blog_setting
+# 导入当前应用的模型:分类和文章模型
from .models import Category, Article
+# 创建日志记录器,用于记录当前模块的日志信息
logger = logging.getLogger(__name__)
def seo_processor(requests):
+ """
+ 自定义上下文处理器:用于在所有模板中全局共享SEO相关的配置和数据
+ 上下文处理器会在每次请求时被调用,返回的字典会自动注入到所有模板中
+
+ Args:
+ requests: HttpRequest对象,包含当前请求的信息(如协议、主机等)
+
+ Returns:
+ dict: 包含网站配置、导航数据等的字典,供模板全局使用
+ """
+ # 定义缓存键,用于标识当前处理器的缓存数据
key = 'seo_processor'
+ # 尝试从缓存中获取数据,减少数据库查询和计算开销
value = cache.get(key)
+
+ # 如果缓存中存在数据,直接返回缓存内容
if value:
return value
else:
+ # 缓存未命中时,记录日志并重新计算数据
logger.info('set processor cache.')
+ # 获取博客的全局设置(从数据库或其他配置源)
setting = get_blog_setting()
+
+ # 构建需要返回给模板的数据集
value = {
+ # 网站名称(用于页面标题等)
'SITE_NAME': setting.site_name,
+ # 是否显示谷歌广告
'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,
+ # 谷歌广告代码
'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes,
+ # 网站SEO描述(用于meta标签)
'SITE_SEO_DESCRIPTION': setting.site_seo_description,
+ # 网站描述(用于页面展示)
'SITE_DESCRIPTION': setting.site_description,
+ # 网站关键词(用于meta标签,提升SEO)
'SITE_KEYWORDS': setting.site_keywords,
+ # 网站基础URL(协议+域名,如https://example.com/)
'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',
+ # 文章摘要长度(用于列表页显示)
'ARTICLE_SUB_LENGTH': setting.article_sub_length,
+ # 导航栏显示的分类列表(从数据库查询所有分类)
'nav_category_list': Category.objects.all(),
+ # 导航栏显示的页面列表(筛选类型为"页面"且状态为"已发布"的文章)
'nav_pages': Article.objects.filter(
- type='p',
- status='p'),
+ type='p', # 'p'表示页面(page),区别于普通文章(article)
+ status='p'), # 'p'表示已发布(published)
+ # 是否开启网站评论功能
'OPEN_SITE_COMMENT': setting.open_site_comment,
+ # 网站备案号
'BEIAN_CODE': setting.beian_code,
+ # 网站统计代码(如百度统计、Google Analytics)
'ANALYTICS_CODE': setting.analytics_code,
+ # 公安备案号
"BEIAN_CODE_GONGAN": setting.gongan_beiancode,
+ # 是否显示公安备案号
"SHOW_GONGAN_CODE": setting.show_gongan_code,
+ # 当前年份(用于页脚版权信息等)
"CURRENT_YEAR": timezone.now().year,
+ # 全局页头代码(如自定义CSS、JS)
"GLOBAL_HEADER": setting.global_header,
+ # 全局页脚代码
"GLOBAL_FOOTER": setting.global_footer,
+ # 评论是否需要审核后才显示
"COMMENT_NEED_REVIEW": setting.comment_need_review,
}
- cache.set(key, value, 60 * 60 * 10)
- return value
+ # 将数据存入缓存,有效期为10小时(60秒*60分*10小时)
+ # 减少重复计算和数据库查询,提升性能
+ cache.set(key, value, 60 * 60 * 10)
+ return value
\ No newline at end of file
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/blog/search_indexes.py b/src/DjangoBlog-master(1)/DjangoBlog-master/blog/search_indexes.py
index 8ebb26a..7f1dfac 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/blog/search_indexes.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/blog/search_indexes.py
@@ -1,26 +1,13 @@
-# 从haystack框架导入索引相关的模块,用于实现全文搜索功能
from haystack import indexes
-# 导入当前项目中blog应用的Article模型,该模型对应需要被索引的文章数据
from blog.models import Article
-# 定义ArticleIndex类,继承自SearchIndex和Indexable,用于配置Article模型的搜索索引
-# SearchIndex:提供索引的核心功能,定义了如何从模型中提取数据构建索引
-# Indexable:标识该类可被索引,要求实现get_model方法来指定关联的模型
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
- # 定义一个text字段作为文档的主要索引字段(document=True表示这是主要搜索字段)
- # use_template=True表示使用模板来定义该字段需要索引的内容(通常在templates/search/indexes/blog/article_text.txt中配置)
- # 该字段会聚合模型中需要被搜索的字段(如标题、正文等),作为全文搜索的基础
text = indexes.CharField(document=True, use_template=True)
- # 实现Indexable接口的方法,返回当前索引关联的模型类
- # 作用:告诉haystack该索引对应的数据来自哪个模型
def get_model(self):
return Article
- # 定义需要被索引的查询集(即哪些数据会被纳入搜索范围)
- # using参数用于指定搜索引擎(多引擎场景下使用),默认None表示使用默认引擎
- # 这里返回状态为'p'(假设表示"已发布")的文章,确保只有已发布的内容可被搜索
def index_queryset(self, using=None):
- return self.get_model().objects.filter(status='p')
\ No newline at end of file
+ return self.get_model().objects.filter(status='p')
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/blog/urls.py b/src/DjangoBlog-master(1)/DjangoBlog-master/blog/urls.py
index e1c78e4..adf2703 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/blog/urls.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/blog/urls.py
@@ -1,108 +1,62 @@
-# 导入 Django 内置的路径配置工具和缓存装饰器
from django.urls import path
from django.views.decorators.cache import cache_page
-# 导入当前应用(blog)的视图模块,用于关联路由与视图逻辑
from . import views
-# 定义应用命名空间(namespace),用于在模板或反向解析时区分不同应用的路由
-# 例如:在模板中使用 {% url 'blog:index' %} 生成首页链接
app_name = "blog"
-
-# 路由配置列表,每个 path 对应一个 URL 规则与视图的映射
urlpatterns = [
- # 首页路由:匹配根路径(网站域名/)
path(
- r'', # URL 路径表达式,空字符串表示根路径
- views.IndexView.as_view(), # 关联的视图类(IndexView),通过 as_view() 转换为可调用视图
- name='index' # 路由名称,用于反向解析(如 reverse('blog:index'))
- ),
-
- # 分页首页路由:匹配带页码的首页(如 /page/2/)
+ r'',
+ views.IndexView.as_view(),
+ name='index'),
path(
- r'page//', # 是路径参数,int 表示接收整数类型,page 是参数名
- views.IndexView.as_view(), # 复用首页视图类,视图中会通过 page 参数处理分页
- name='index_page'
- ),
-
- # 文章详情页路由:按日期和文章ID匹配(如 /article/2023/10/20/100.html)
+ r'page//',
+ views.IndexView.as_view(),
+ name='index_page'),
path(
r'article////.html',
- # 路径参数:year(年)、month(月)、day(日)、article_id(文章ID),均为整数
- views.ArticleDetailView.as_view(), # 文章详情视图类,处理文章展示逻辑
- name='detailbyid'
- ),
-
- # 分类详情页路由:按分类名匹配(如 /category/tech.html)
+ views.ArticleDetailView.as_view(),
+ name='detailbyid'),
path(
r'category/.html',
- # :slug 类型表示接收字母、数字、下划线和连字符组成的字符串(适合URL友好的名称)
- views.CategoryDetailView.as_view(), # 分类详情视图类,展示该分类下的文章
- name='category_detail'
- ),
-
- # 分类详情分页路由:带页码的分类页(如 /category/tech/2.html)
+ views.CategoryDetailView.as_view(),
+ name='category_detail'),
path(
r'category//.html',
- views.CategoryDetailView.as_view(), # 复用分类视图类,通过 page 参数分页
- name='category_detail_page'
- ),
-
- # 作者详情页路由:按作者名匹配(如 /author/alice.html)
+ views.CategoryDetailView.as_view(),
+ name='category_detail_page'),
path(
r'author/.html',
- # :未指定类型,默认接收字符串(除特殊字符外)
- views.AuthorDetailView.as_view(), # 作者详情视图类,展示该作者的文章
- name='author_detail'
- ),
-
- # 作者详情分页路由:带页码的作者页(如 /author/alice/2.html)
+ views.AuthorDetailView.as_view(),
+ name='author_detail'),
path(
r'author//.html',
- views.AuthorDetailView.as_view(), # 复用作者视图类,通过 page 参数分页
- name='author_detail_page'
- ),
-
- # 标签详情页路由:按标签名匹配(如 /tag/python.html)
+ views.AuthorDetailView.as_view(),
+ name='author_detail_page'),
path(
r'tag/.html',
- views.TagDetailView.as_view(), # 标签详情视图类,展示该标签下的文章
- name='tag_detail'
- ),
-
- # 标签详情分页路由:带页码的标签页(如 /tag/python/2.html)
+ views.TagDetailView.as_view(),
+ name='tag_detail'),
path(
r'tag//.html',
- views.TagDetailView.as_view(), # 复用标签视图类,通过 page 参数分页
- name='tag_detail_page'
- ),
-
- # 归档页路由:匹配 /archives.html
+ views.TagDetailView.as_view(),
+ name='tag_detail_page'),
path(
'archives.html',
- # 缓存装饰器:cache_page(60*60) 表示缓存该页面1小时(60秒*60),减轻服务器压力
- cache_page(60 * 60)(views.ArchivesView.as_view()),
- name='archives' # 归档视图,通常展示按日期分组的文章列表
- ),
-
- # 友情链接页路由:匹配 /links.html
+ cache_page(
+ 60 * 60)(
+ views.ArchivesView.as_view()),
+ name='archives'),
path(
'links.html',
- views.LinkListView.as_view(), # 友情链接视图类,展示网站链接列表
- name='links'
- ),
-
- # 文件上传路由:匹配 /upload
+ views.LinkListView.as_view(),
+ name='links'),
path(
r'upload',
- views.fileupload, # 关联函数视图(非类视图),处理文件上传逻辑
- name='upload'
- ),
-
- # 缓存清理路由:匹配 /clean
+ views.fileupload,
+ name='upload'),
path(
r'clean',
- views.clean_cache_view, # 关联缓存清理视图,用于手动触发缓存清理
- name='clean'
- ),
-]
\ No newline at end of file
+ views.clean_cache_view,
+ name='clean'),
+]
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/comments/models.py b/src/DjangoBlog-master(1)/DjangoBlog-master/comments/models.py
index 0d052b1..7c3bbc8 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/comments/models.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/comments/models.py
@@ -36,4 +36,4 @@ class Comment(models.Model):
get_latest_by = 'id'
def __str__(self):
- return self.body
\ No newline at end of file
+ return self.body
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/djangoblog/tests.py b/src/DjangoBlog-master(1)/DjangoBlog-master/djangoblog/tests.py
index 01237d9..c2e16a7 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/djangoblog/tests.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/djangoblog/tests.py
@@ -1,15 +1,19 @@
from django.test import TestCase
from djangoblog.utils import *
-
-
+# 定义测试类,继承自TestCase,用于测试Django博客项目中的工具类/函数
class DjangoBlogTest(TestCase):
+ # 本测试用例无需前置初始化操作,故保持空实现
def setUp(self):
pass
+ # 1. 测试SHA256加密工具函数get_sha256
+ # 对字符串'test'进行SHA256加密,获取加密结果
def test_utils(self):
md5 = get_sha256('test')
self.assertIsNotNone(md5)
+ # 2. 测试Markdown解析工具类CommonMarkdown
+ # 调用get_markdown方法,解析一段包含多种元素的Markdown文本
c = CommonMarkdown.get_markdown('''
# Title1
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/admin.py b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/admin.py
index d3ffcd6..57eab5f 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/admin.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/admin.py
@@ -5,93 +5,50 @@ from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
-# 获取当前模块的日志记录器
logger = logging.getLogger(__name__)
class OAuthUserAdmin(admin.ModelAdmin):
- """
- OAuth用户管理后台配置类
- 用于自定义Django Admin中OAuth用户模型的显示和行为
- """
-
- # 搜索字段配置,支持按昵称和邮箱搜索
search_fields = ('nickname', 'email')
-
- # 每页显示的项目数量
list_per_page = 20
-
- # 列表页显示的字段
list_display = (
'id',
'nickname',
- 'link_to_usermodel', # 自定义方法:链接到用户模型
- 'show_user_image', # 自定义方法:显示用户头像
- 'type', # OAuth类型
+ 'link_to_usermodel',
+ 'show_user_image',
+ 'type',
'email',
)
-
- # 可点击进入编辑页的字段
list_display_links = ('id', 'nickname')
-
- # 右侧筛选器配置
list_filter = ('author', 'type',)
-
- # 只读字段列表,开始为空
readonly_fields = []
def get_readonly_fields(self, request, obj=None):
- """
- 动态获取只读字段
- 将所有模型字段和多对多字段设为只读
- """
return list(self.readonly_fields) + \
- [field.name for field in obj._meta.fields] + \
- [field.name for field in obj._meta.many_to_many]
+ [field.name for field in obj._meta.fields] + \
+ [field.name for field in obj._meta.many_to_many]
def has_add_permission(self, request):
- """
- 禁用添加权限
- OAuth用户应该通过OAuth流程自动创建,而不是手动添加
- """
return False
def link_to_usermodel(self, obj):
- """
- 自定义方法:生成指向关联用户模型的链接
- """
if obj.author:
- # 获取用户模型的app和模型名称信息
info = (obj.author._meta.app_label, obj.author._meta.model_name)
- # 生成用户编辑页面的URL
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
- # 返回HTML格式的链接
return format_html(
u'%s' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
def show_user_image(self, obj):
- """
- 自定义方法:在管理后台显示用户头像
- """
- img = obj.picture # 获取头像图片URL
+ img = obj.picture
return format_html(
u'
' %
(img))
- # 设置自定义方法在管理后台的显示名称
link_to_usermodel.short_description = '用户'
show_user_image.short_description = '用户头像'
class OAuthConfigAdmin(admin.ModelAdmin):
- """
- OAuth配置管理后台配置类
- 用于管理不同OAuth服务的配置信息
- """
-
- # 列表页显示的字段
list_display = ('type', 'appkey', 'appsecret', 'is_enable')
-
- # 右侧筛选器配置
list_filter = ('type',)
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/apps.py b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/apps.py
index 42788ab..17fcea2 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/apps.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/apps.py
@@ -2,14 +2,4 @@ from django.apps import AppConfig
class OauthConfig(AppConfig):
- """
- OAuth应用配置类
- 用于配置Django中OAuth应用的元数据和启动行为
-
- 这个类继承自Django的AppConfig基类,用于定义
- OAuth应用在Django项目中的配置信息
- """
-
- # 指定应用的Python路径,Django使用这个名称来识别应用
- # 这应该与应用目录的名称保持相同
- name = 'oauth'
\ No newline at end of file
+ name = 'oauth'
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/forms.py b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/forms.py
index 0db6327..0e4ede3 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/forms.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/forms.py
@@ -3,32 +3,10 @@ from django.forms import widgets
class RequireEmailForm(forms.Form):
- """
- 邮箱验证表单类
- 用于在OAuth登录过程中要求用户提供邮箱地址
- 通常在第三方OAuth服务没有返回邮箱信息时使用
- """
-
- # 邮箱字段,标签显示为'电子邮箱'
email = forms.EmailField(label='电子邮箱', required=True)
-
- # OAuth用户ID隐藏字段,用于关联OAuth用户记录
oauthid = forms.IntegerField(widget=forms.HiddenInput, required=False)
def __init__(self, *args, **kwargs):
- """
- 初始化方法,自定义表单字段的显示属性
-
- Args:
- *args: 位置参数
- **kwargs: 关键字参数
- """
- # 调用父类的初始化方法
super(RequireEmailForm, self).__init__(*args, **kwargs)
-
- # 自定义邮箱字段的输入控件,添加占位符和CSS类
self.fields['email'].widget = widgets.EmailInput(
- attrs={
- 'placeholder': "email", # 输入框内的提示文字
- "class": "form-control" # Bootstrap样式类
- })
\ No newline at end of file
+ attrs={'placeholder': "email", "class": "form-control"})
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/models.py b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/models.py
index b4b570d..be838ed 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/models.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/models.py
@@ -7,113 +7,61 @@ from django.utils.translation import gettext_lazy as _
class OAuthUser(models.Model):
- """
- OAuth用户模型
- 用于存储通过第三方OAuth服务登录的用户信息
- """
-
- # 关联到系统的本地用户,允许为空
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
blank=True,
null=True,
- on_delete=models.CASCADE) # 删除用户时级联删除OAuth记录
-
- # OAuth服务提供的用户唯一标识
+ on_delete=models.CASCADE)
openid = models.CharField(max_length=50)
-
- # 用户在第三方平台的昵称
nickname = models.CharField(max_length=50, verbose_name=_('nick name'))
-
- # OAuth访问令牌,用于后续API调用
token = models.CharField(max_length=150, null=True, blank=True)
-
- # 用户在第三方平台的头像URL
picture = models.CharField(max_length=350, blank=True, null=True)
-
- # OAuth服务类型(如github、weibo等)
type = models.CharField(blank=False, null=False, max_length=50)
-
- # 用户在第三方平台的邮箱
email = models.CharField(max_length=50, null=True, blank=True)
-
- # 存储额外的OAuth返回数据(JSON格式)
metadata = models.TextField(null=True, blank=True)
-
- # 记录创建时间
creation_time = models.DateTimeField(_('creation time'), default=now)
-
- # 最后修改时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
def __str__(self):
- """返回对象的字符串表示,显示用户昵称"""
return self.nickname
class Meta:
- """模型元数据配置"""
- verbose_name = _('oauth user') # 单数名称
- verbose_name_plural = verbose_name # 复数名称
- ordering = ['-creation_time'] # 按创建时间降序排列
+ verbose_name = _('oauth user')
+ verbose_name_plural = verbose_name
+ ordering = ['-creation_time']
class OAuthConfig(models.Model):
- """
- OAuth服务配置模型
- 用于存储不同OAuth服务的应用配置信息
- """
-
- # OAuth服务类型选择项
TYPE = (
- ('weibo', _('weibo')), # 微博
- ('google', _('google')), # 谷歌
- ('github', 'GitHub'), # GitHub
- ('facebook', 'FaceBook'), # Facebook
- ('qq', 'QQ'), # QQ
+ ('weibo', _('weibo')),
+ ('google', _('google')),
+ ('github', 'GitHub'),
+ ('facebook', 'FaceBook'),
+ ('qq', 'QQ'),
)
-
- # OAuth服务类型,从预定义选项中选择
type = models.CharField(_('type'), max_length=10, choices=TYPE, default='a')
-
- # OAuth应用的AppKey/Client ID
appkey = models.CharField(max_length=200, verbose_name='AppKey')
-
- # OAuth应用的AppSecret/Client Secret
appsecret = models.CharField(max_length=200, verbose_name='AppSecret')
-
- # OAuth回调URL
callback_url = models.CharField(
max_length=200,
verbose_name=_('callback url'),
blank=False,
default='')
-
- # 是否启用该OAuth服务
is_enable = models.BooleanField(
_('is enable'), default=True, blank=False, null=False)
-
- # 配置创建时间
creation_time = models.DateTimeField(_('creation time'), default=now)
-
- # 配置最后修改时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
def clean(self):
- """
- 数据验证方法
- 确保同类型的OAuth配置只能有一个
- """
if OAuthConfig.objects.filter(
type=self.type).exclude(id=self.id).count():
raise ValidationError(_(self.type + _('already exists')))
def __str__(self):
- """返回对象的字符串表示,显示OAuth服务类型"""
return self.type
class Meta:
- """模型元数据配置"""
- verbose_name = 'oauth配置' # 单数名称(中文)
- verbose_name_plural = verbose_name # 复数名称
- ordering = ['-creation_time'] # 按创建时间降序排列
\ No newline at end of file
+ verbose_name = 'oauth配置'
+ verbose_name_plural = verbose_name
+ ordering = ['-creation_time']
diff --git a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/tests.py b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/tests.py
index 8198f84..bb23b9b 100644
--- a/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/tests.py
+++ b/src/DjangoBlog-master(1)/DjangoBlog-master/oauth/tests.py
@@ -13,81 +13,43 @@ from oauth.oauthmanager import BaseOauthManager
# Create your tests here.
class OAuthConfigTest(TestCase):
- """
- OAuth配置基础测试类
- 测试OAuth登录流程的基本功能
- """
-
def setUp(self):
- """
- 测试初始化方法,在每个测试方法执行前运行
- """
- self.client = Client() # Django测试客户端,用于模拟HTTP请求
- self.factory = RequestFactory() # 用于创建请求对象的工厂
+ self.client = Client()
+ self.factory = RequestFactory()
def test_oauth_login_test(self):
- """
- 测试OAuth登录流程
- 验证微博OAuth登录的跳转和授权流程
- """
- # 创建微博OAuth配置
c = OAuthConfig()
c.type = 'weibo'
c.appkey = 'appkey'
c.appsecret = 'appsecret'
c.save()
- # 测试OAuth登录请求,应该重定向到微博授权页面
response = self.client.get('/oauth/oauthlogin?type=weibo')
- self.assertEqual(response.status_code, 302) # 验证重定向状态码
- self.assertTrue("api.weibo.com" in response.url) # 验证跳转到微博授权页面
+ self.assertEqual(response.status_code, 302)
+ self.assertTrue("api.weibo.com" in response.url)
- # 测试授权回调处理,应该重定向到首页
response = self.client.get('/oauth/authorize?type=weibo&code=code')
- self.assertEqual(response.status_code, 302) # 验证重定向状态码
- self.assertEqual(response.url, '/') # 验证跳转到首页
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.url, '/')
class OauthLoginTest(TestCase):
- """
- OAuth登录详细测试类
- 测试各种OAuth服务提供商的登录流程
- """
-
def setUp(self) -> None:
- """
- 测试初始化方法
- """
- self.client = Client() # Django测试客户端
- self.factory = RequestFactory() # 请求工厂
- self.apps = self.init_apps() # 初始化所有OAuth应用配置
+ self.client = Client()
+ self.factory = RequestFactory()
+ self.apps = self.init_apps()
def init_apps(self):
- """
- 初始化所有支持的OAuth应用配置
- 为每种OAuth服务创建测试配置
- """
- # 获取所有OAuth管理器的子类实例
applications = [p() for p in BaseOauthManager.__subclasses__()]
for application in applications:
- # 为每个OAuth服务创建配置
c = OAuthConfig()
- c.type = application.ICON_NAME.lower() # 服务类型(小写)
- c.appkey = 'appkey' # 测试用的AppKey
- c.appsecret = 'appsecret' # 测试用的AppSecret
+ c.type = application.ICON_NAME.lower()
+ c.appkey = 'appkey'
+ c.appsecret = 'appsecret'
c.save()
return applications
def get_app_by_type(self, type):
- """
- 根据类型获取对应的OAuth应用实例
-
- Args:
- type: OAuth服务类型
-
- Returns:
- 对应的OAuth管理器实例
- """
for app in self.apps:
if app.ICON_NAME.lower() == type:
return app
@@ -95,117 +57,73 @@ class OauthLoginTest(TestCase):
@patch("oauth.oauthmanager.WBOauthManager.do_post")
@patch("oauth.oauthmanager.WBOauthManager.do_get")
def test_weibo_login(self, mock_do_get, mock_do_post):
- """
- 测试微博OAuth登录流程
- 使用mock模拟API调用
- """
weibo_app = self.get_app_by_type('weibo')
- assert weibo_app # 确保获取到微博应用实例
-
- # 获取授权URL
+ assert weibo_app
url = weibo_app.get_authorization_url()
-
- # 设置mock返回值 - 获取access token
mock_do_post.return_value = json.dumps({"access_token": "access_token",
"uid": "uid"
})
- # 设置mock返回值 - 获取用户信息
mock_do_get.return_value = json.dumps({
"avatar_large": "avatar_large",
"screen_name": "screen_name",
"id": "id",
"email": "email",
})
-
- # 执行获取access token的操作
userinfo = weibo_app.get_access_token_by_code('code')
-
- # 验证返回的用户信息
self.assertEqual(userinfo.token, 'access_token')
self.assertEqual(userinfo.openid, 'id')
@patch("oauth.oauthmanager.GoogleOauthManager.do_post")
@patch("oauth.oauthmanager.GoogleOauthManager.do_get")
def test_google_login(self, mock_do_get, mock_do_post):
- """
- 测试Google OAuth登录流程
- """
google_app = self.get_app_by_type('google')
assert google_app
-
url = google_app.get_authorization_url()
-
- # 模拟Google OAuth的token响应
mock_do_post.return_value = json.dumps({
"access_token": "access_token",
"id_token": "id_token",
})
-
- # 模拟Google用户信息响应
mock_do_get.return_value = json.dumps({
"picture": "picture",
"name": "name",
- "sub": "sub", # Google的用户ID字段
+ "sub": "sub",
"email": "email",
})
-
token = google_app.get_access_token_by_code('code')
userinfo = google_app.get_oauth_userinfo()
-
- # 验证用户信息
self.assertEqual(userinfo.token, 'access_token')
- self.assertEqual(userinfo.openid, 'sub') # Google使用sub作为用户ID
+ self.assertEqual(userinfo.openid, 'sub')
@patch("oauth.oauthmanager.GitHubOauthManager.do_post")
@patch("oauth.oauthmanager.GitHubOauthManager.do_get")
def test_github_login(self, mock_do_get, mock_do_post):
- """
- 测试GitHub OAuth登录流程
- """
github_app = self.get_app_by_type('github')
assert github_app
-
url = github_app.get_authorization_url()
- # 验证GitHub授权URL包含必要信息
self.assertTrue("github.com" in url)
self.assertTrue("client_id" in url)
-
- # 模拟GitHub的token响应(字符串格式)
mock_do_post.return_value = "access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=repo%2Cgist&token_type=bearer"
-
- # 模拟GitHub用户信息响应
mock_do_get.return_value = json.dumps({
"avatar_url": "avatar_url",
"name": "name",
"id": "id",
"email": "email",
})
-
token = github_app.get_access_token_by_code('code')
userinfo = github_app.get_oauth_userinfo()
-
- # 验证用户信息
self.assertEqual(userinfo.token, 'gho_16C7e42F292c6912E7710c838347Ae178B4a')
self.assertEqual(userinfo.openid, 'id')
@patch("oauth.oauthmanager.FaceBookOauthManager.do_post")
@patch("oauth.oauthmanager.FaceBookOauthManager.do_get")
def test_facebook_login(self, mock_do_get, mock_do_post):
- """
- 测试Facebook OAuth登录流程
- """
facebook_app = self.get_app_by_type('facebook')
assert facebook_app
-
url = facebook_app.get_authorization_url()
- self.assertTrue("facebook.com" in url) # 验证Facebook授权URL
-
- # 模拟Facebook token响应
+ self.assertTrue("facebook.com" in url)
mock_do_post.return_value = json.dumps({
"access_token": "access_token",
})
-
- # 模拟Facebook用户信息响应(嵌套结构)
mock_do_get.return_value = json.dumps({
"name": "name",
"id": "id",
@@ -216,16 +134,14 @@ class OauthLoginTest(TestCase):
}
}
})
-
token = facebook_app.get_access_token_by_code('code')
userinfo = facebook_app.get_oauth_userinfo()
-
self.assertEqual(userinfo.token, 'access_token')
@patch("oauth.oauthmanager.QQOauthManager.do_get", side_effect=[
- 'access_token=access_token&expires_in=3600', # 第一次调用:获取token
- 'callback({"client_id":"appid","openid":"openid"} );', # 第二次调用:获取openid
- json.dumps({ # 第三次调用:获取用户信息
+ 'access_token=access_token&expires_in=3600',
+ 'callback({"client_id":"appid","openid":"openid"} );',
+ json.dumps({
"nickname": "nickname",
"email": "email",
"figureurl": "figureurl",
@@ -233,33 +149,21 @@ class OauthLoginTest(TestCase):
})
])
def test_qq_login(self, mock_do_get):
- """
- 测试QQ OAuth登录流程
- 使用side_effect模拟多次不同的API响应
- """
qq_app = self.get_app_by_type('qq')
assert qq_app
-
url = qq_app.get_authorization_url()
- self.assertTrue("qq.com" in url) # 验证QQ授权URL
-
+ self.assertTrue("qq.com" in url)
token = qq_app.get_access_token_by_code('code')
userinfo = qq_app.get_oauth_userinfo()
-
self.assertEqual(userinfo.token, 'access_token')
@patch("oauth.oauthmanager.WBOauthManager.do_post")
@patch("oauth.oauthmanager.WBOauthManager.do_get")
def test_weibo_authoriz_login_with_email(self, mock_do_get, mock_do_post):
- """
- 测试包含邮箱的微博授权登录完整流程
- 验证用户认证和会话管理
- """
- # 模拟获取access token
+
mock_do_post.return_value = json.dumps({"access_token": "access_token",
"uid": "uid"
})
- # 模拟用户信息(包含邮箱)
mock_user_info = {
"avatar_large": "avatar_large",
"screen_name": "screen_name1",
@@ -268,31 +172,25 @@ class OauthLoginTest(TestCase):
}
mock_do_get.return_value = json.dumps(mock_user_info)
- # 测试登录跳转
response = self.client.get('/oauth/oauthlogin?type=weibo')
self.assertEqual(response.status_code, 302)
self.assertTrue("api.weibo.com" in response.url)
- # 测试授权回调
response = self.client.get('/oauth/authorize?type=weibo&code=code')
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/')
- # 验证用户认证状态
user = auth.get_user(self.client)
assert user.is_authenticated
self.assertTrue(user.is_authenticated)
self.assertEqual(user.username, mock_user_info['screen_name'])
self.assertEqual(user.email, mock_user_info['email'])
-
- # 登出后再次测试
self.client.logout()
response = self.client.get('/oauth/authorize?type=weibo&code=code')
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/')
- # 再次验证用户认证状态
user = auth.get_user(self.client)
assert user.is_authenticated
self.assertTrue(user.is_authenticated)
@@ -302,15 +200,10 @@ class OauthLoginTest(TestCase):
@patch("oauth.oauthmanager.WBOauthManager.do_post")
@patch("oauth.oauthmanager.WBOauthManager.do_get")
def test_weibo_authoriz_login_without_email(self, mock_do_get, mock_do_post):
- """
- 测试不包含邮箱的微博授权登录流程
- 验证邮箱补充流程
- """
- # 模拟获取access token
+
mock_do_post.return_value = json.dumps({"access_token": "access_token",
"uid": "uid"
})
- # 模拟用户信息(不包含邮箱)
mock_user_info = {
"avatar_large": "avatar_large",
"screen_name": "screen_name1",
@@ -318,34 +211,28 @@ class OauthLoginTest(TestCase):
}
mock_do_get.return_value = json.dumps(mock_user_info)
- # 测试登录跳转
response = self.client.get('/oauth/oauthlogin?type=weibo')
self.assertEqual(response.status_code, 302)
self.assertTrue("api.weibo.com" in response.url)
- # 测试授权回调 - 应该重定向到邮箱补充页面
response = self.client.get('/oauth/authorize?type=weibo&code=code')
+
self.assertEqual(response.status_code, 302)
- # 解析OAuth用户ID
oauth_user_id = int(response.url.split('/')[-1].split('.')[0])
self.assertEqual(response.url, f'/oauth/requireemail/{oauth_user_id}.html')
- # 提交邮箱信息
response = self.client.post(response.url, {'email': 'test@gmail.com', 'oauthid': oauth_user_id})
- self.assertEqual(response.status_code, 302)
- # 生成邮箱验证签名
+ self.assertEqual(response.status_code, 302)
sign = get_sha256(settings.SECRET_KEY +
str(oauth_user_id) + settings.SECRET_KEY)
- # 验证绑定成功URL
url = reverse('oauth:bindsuccess', kwargs={
'oauthid': oauth_user_id,
})
self.assertEqual(response.url, f'{url}?type=email')
- # 验证邮箱确认流程
path = reverse('oauth:email_confirm', kwargs={
'id': oauth_user_id,
'sign': sign
@@ -353,12 +240,10 @@ class OauthLoginTest(TestCase):
response = self.client.get(path)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, f'/oauth/bindsuccess/{oauth_user_id}.html?type=success')
-
- # 验证最终用户状态
user = auth.get_user(self.client)
from oauth.models import OAuthUser
oauth_user = OAuthUser.objects.get(author=user)
self.assertTrue(user.is_authenticated)
self.assertEqual(user.username, mock_user_info['screen_name'])
self.assertEqual(user.email, 'test@gmail.com')
- self.assertEqual(oauth_user.pk, oauth_user_id)
\ No newline at end of file
+ self.assertEqual(oauth_user.pk, oauth_user_id)