diff --git a/doc/开源软件泛读报告.docx b/doc/开源软件泛读报告.docx
index 8d59b0c..f7cc8c1 100644
Binary files a/doc/开源软件泛读报告.docx and b/doc/开源软件泛读报告.docx differ
diff --git a/doc/开源软件的质量分析报告文档.docx b/doc/开源软件的质量分析报告文档.docx
new file mode 100644
index 0000000..5054038
Binary files /dev/null and b/doc/开源软件的质量分析报告文档.docx differ
diff --git a/doc/编码规范.docx b/doc/编码规范.docx
new file mode 100644
index 0000000..a7b41a9
Binary files /dev/null and b/doc/编码规范.docx differ
diff --git a/src/DjangoBlog/accounts/models.py b/src/DjangoBlog/accounts/models.py
index 178ee7a..acca91d 100644
--- a/src/DjangoBlog/accounts/models.py
+++ b/src/DjangoBlog/accounts/models.py
@@ -1,4 +1,4 @@
-# 这个文件定义了用户相关的数据模型
+#flj 这个文件定义了用户相关的数据模型
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
@@ -9,27 +9,28 @@ from djangoblog.utils import get_current_site
# Create your models here.
+#zxm 自定义用户模型,扩展了Django的默认用户模型
class BlogUser(AbstractUser):
- # 用户昵称
+ #zxm 用户昵称
nickname = models.CharField(_('nick name'), max_length=100, blank=True)
- # 创建时间
+ #zxm 创建时间
creation_time = models.DateTimeField(_('creation time'), default=now)
- # 最后修改时间
+ #zxm 最后修改时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
- # 来源
+ #zxm 来源
source = models.CharField(_('create source'), max_length=100, blank=True)
- # 获取用户详情页的url
+ #zxm 获取用户详情页的url
def get_absolute_url(self):
return reverse(
'blog:author_detail', kwargs={
'author_name': self.username})
- # 返回邮箱作为用户标识
+ #zxm 返回邮箱作为用户标识
def __str__(self):
return self.email
- # 获取用户的完整url
+ #zxm 获取用户的完整url
def get_full_url(self):
site = get_current_site().domain
url = "https://{site}{path}".format(site=site,
@@ -37,7 +38,7 @@ class BlogUser(AbstractUser):
return url
class Meta:
- ordering = ['-id']
- verbose_name = _('user')
- verbose_name_plural = verbose_name
- get_latest_by = 'id'
+ ordering = ['-id'] #zxm 按ID倒序排列
+ verbose_name = _('user') #zxm 在管理后台显示的名称
+ verbose_name_plural = verbose_name #zxm 复数形式
+ get_latest_by = 'id' #zxm 获取最新记录的依据
diff --git a/src/DjangoBlog/accounts/views.py b/src/DjangoBlog/accounts/views.py
index ae67aec..57afb09 100644
--- a/src/DjangoBlog/accounts/views.py
+++ b/src/DjangoBlog/accounts/views.py
@@ -1,3 +1,4 @@
+#flj 用户账户相关视图
import logging
from django.utils.translation import gettext_lazy as _
from django.conf import settings
@@ -31,29 +32,32 @@ logger = logging.getLogger(__name__)
# Create your views here.
+#zxm 用户注册视图类
class RegisterView(FormView):
- form_class = RegisterForm
- template_name = 'account/registration_form.html'
+ form_class = RegisterForm #zxm 使用注册表单
+ template_name = 'account/registration_form.html' #zxm 注册页面模板
- @method_decorator(csrf_protect)
+ @method_decorator(csrf_protect) #zxm 防止CSRF攻击
def dispatch(self, *args, **kwargs):
return super(RegisterView, self).dispatch(*args, **kwargs)
+ #zxm 表单验证成功后的处理
def form_valid(self, form):
if form.is_valid():
- user = form.save(False)
- user.is_active = False
- user.source = 'Register'
- user.save(True)
- site = get_current_site().domain
- sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
+ user = form.save(False) #zxm 不保存到数据库
+ user.is_active = False #zxm 初始状态为未激活
+ user.source = 'Register' #zxm 注册来源
+ user.save(True) #zxm 保存到数据库
+ site = get_current_site().domain #zxm 获取当前站点域名
+ sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) #zxm 生成验证签名
if settings.DEBUG:
- site = '127.0.0.1:8000'
- path = reverse('account:result')
+ site = '127.0.0.1:8000' #zxm 开发环境使用本地地址
+ path = reverse('account:result') #zxm 获取结果页面URL
url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
- site=site, path=path, id=user.id, sign=sign)
+ site=site, path=path, id=user.id, sign=sign) #zxm 生成验证链接
+ #zxm 邮件内容
content = """
请点击下面链接验证您的邮箱
@@ -64,6 +68,7 @@ class RegisterView(FormView):
如果上面链接无法打开,请将此链接复制至浏览器。
{url}
""".format(url=url)
+ #zxm 发送验证邮件
send_email(
emailto=[
user.email,
@@ -80,33 +85,35 @@ class RegisterView(FormView):
})
+#fkc 用户登出视图类
class LogoutView(RedirectView):
- url = '/login/'
+ url = '/login/' #zxm 登出后重定向到登录页
- @method_decorator(never_cache)
+ @method_decorator(never_cache) #zxm 不缓存页面
def dispatch(self, request, *args, **kwargs):
return super(LogoutView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
- logout(request)
- delete_sidebar_cache()
+ logout(request) #zxm 登出用户
+ delete_sidebar_cache() #zxm 删除侧边栏缓存
return super(LogoutView, self).get(request, *args, **kwargs)
+#cll 用户登录视图类
class LoginView(FormView):
- form_class = LoginForm
- template_name = 'account/login.html'
- success_url = '/'
- redirect_field_name = REDIRECT_FIELD_NAME
- login_ttl = 2626560 # 一个月的时间
-
- @method_decorator(sensitive_post_parameters('password'))
- @method_decorator(csrf_protect)
- @method_decorator(never_cache)
+ form_class = LoginForm #zxm 使用登录表单
+ template_name = 'account/login.html' #zxm 登录页面模板
+ success_url = '/' #zxm 登录成功后重定向到首页
+ redirect_field_name = REDIRECT_FIELD_NAME #zxm 重定向字段名
+ login_ttl = 2626560 #zxm 一个月的时间(记住我功能)
+
+ @method_decorator(sensitive_post_parameters('password')) #zxm 敏感参数保护
+ @method_decorator(csrf_protect) #zxm 防止CSRF攻击
+ @method_decorator(never_cache) #zxm 不缓存页面
def dispatch(self, request, *args, **kwargs):
-
return super(LoginView, self).dispatch(request, *args, **kwargs)
+ #zxm 获取上下文数据
def get_context_data(self, **kwargs):
redirect_to = self.request.GET.get(self.redirect_field_name)
if redirect_to is None:
@@ -115,90 +122,95 @@ class LoginView(FormView):
return super(LoginView, self).get_context_data(**kwargs)
+ #zxm 表单验证成功后的处理
def form_valid(self, form):
form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid():
- delete_sidebar_cache()
+ delete_sidebar_cache() #zxm 删除侧边栏缓存
logger.info(self.redirect_field_name)
- auth.login(self.request, form.get_user())
- if self.request.POST.get("remember"):
- self.request.session.set_expiry(self.login_ttl)
+ auth.login(self.request, form.get_user()) #zxm 登录用户
+ if self.request.POST.get("remember"): #zxm 如果勾选记住我
+ self.request.session.set_expiry(self.login_ttl) #zxm 设置会话过期时间
return super(LoginView, self).form_valid(form)
- # return HttpResponseRedirect('/')
else:
return self.render_to_response({
'form': form
})
+ #zxm 获取成功后重定向的URL
def get_success_url(self):
-
redirect_to = self.request.POST.get(self.redirect_field_name)
if not url_has_allowed_host_and_scheme(
url=redirect_to, allowed_hosts=[
- self.request.get_host()]):
+ self.request.get_host()]): #zxm 安全检查
redirect_to = self.success_url
return redirect_to
+#xy 账户结果处理函数
def account_result(request):
- type = request.GET.get('type')
- id = request.GET.get('id')
+ type = request.GET.get('type') #zxm 获取类型参数
+ id = request.GET.get('id') #zxm 获取用户ID
- user = get_object_or_404(get_user_model(), id=id)
+ user = get_object_or_404(get_user_model(), id=id) #zxm 获取用户对象
logger.info(type)
- if user.is_active:
- return HttpResponseRedirect('/')
- if type and type in ['register', 'validation']:
- if type == 'register':
+ if user.is_active: #zxm 如果用户已激活
+ return HttpResponseRedirect('/') #zxm 重定向到首页
+ if type and type in ['register', 'validation']: #zxm 处理注册或验证类型
+ if type == 'register': #zxm 注册成功
content = '''
恭喜您注册成功,一封验证邮件已经发送到您的邮箱,请验证您的邮箱后登录本站。
'''
title = '注册成功'
- else:
- c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
- sign = request.GET.get('sign')
- if sign != c_sign:
- return HttpResponseForbidden()
- user.is_active = True
- user.save()
+ else: #zxm 邮箱验证
+ c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) #zxm 计算签名
+ sign = request.GET.get('sign') #zxm 获取请求中的签名
+ if sign != c_sign: #zxm 验证签名
+ return HttpResponseForbidden() #zxm 签名不匹配,禁止访问
+ user.is_active = True #zxm 激活用户
+ user.save() #zxm 保存修改
content = '''
恭喜您已经成功的完成邮箱验证,您现在可以使用您的账号来登录本站。
'''
title = '验证成功'
- return render(request, 'account/result.html', {
+ return render(request, 'account/result.html', { #zxm 渲染结果页面
'title': title,
'content': content
})
else:
- return HttpResponseRedirect('/')
+ return HttpResponseRedirect('/') #zxm 其他情况重定向到首页
+#zhj 忘记密码视图类
class ForgetPasswordView(FormView):
- form_class = ForgetPasswordForm
- template_name = 'account/forget_password.html'
+ form_class = ForgetPasswordForm #zxm 使用忘记密码表单
+ template_name = 'account/forget_password.html' #zxm 忘记密码页面模板
+ #zxm 表单验证成功后的处理
def form_valid(self, form):
if form.is_valid():
- 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()
- return HttpResponseRedirect('/login/')
+ blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get() #zxm 获取用户
+ blog_user.password = make_password(form.cleaned_data["new_password2"]) #zxm 设置新密码
+ blog_user.save() #zxm 保存修改
+ return HttpResponseRedirect('/login/') #zxm 重定向到登录页
else:
- return self.render_to_response({'form': form})
+ return self.render_to_response({'form': form}) #zxm 渲染表单错误
+#flj 忘记密码邮箱验证码视图
class ForgetPasswordEmailCode(View):
+ #zxm 处理POST请求
def post(self, request: HttpRequest):
- form = ForgetPasswordCodeForm(request.POST)
+ form = ForgetPasswordCodeForm(request.POST) #zxm 验证表单
if not form.is_valid():
- return HttpResponse("错误的邮箱")
- to_email = form.cleaned_data["email"]
+ return HttpResponse("错误的邮箱") #zxm 返回错误信息
+ to_email = form.cleaned_data["email"] #zxm 获取邮箱
- code = generate_code()
- utils.send_verify_email(to_email, code)
- utils.set_code(to_email, code)
+ code = generate_code() #zxm 生成验证码
+ utils.send_verify_email(to_email, code) #zxm 发送验证邮件
+ utils.set_code(to_email, code) #zxm 保存验证码
- return HttpResponse("ok")
+ return HttpResponse("ok") #zxm 返回成功信息
diff --git a/src/DjangoBlog/blog/models.py b/src/DjangoBlog/blog/models.py
index b70c658..11cd326 100644
--- a/src/DjangoBlog/blog/models.py
+++ b/src/DjangoBlog/blog/models.py
@@ -1,5 +1,5 @@
-# 这个文件里的是博客相关的数据模型,定义了博客系统中所有的数据表结构
+#flj 这个文件里的是博客相关的数据模型,定义了博客系统中所有的数据表结构
import logging
import re
from abc import abstractmethod
@@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
-# 友情链接的展示类型选择,用于控制链接在哪些页面显示
+#zxm 友情链接的展示类型选择,用于控制链接在哪些页面显示
class LinkShowType(models.TextChoices):
I = ('i', _('index')) # 只在首页显示
L = ('l', _('list')) # 只在列表页显示
@@ -29,22 +29,22 @@ class LinkShowType(models.TextChoices):
S = ('s', _('slide')) # 以轮播形式显示
-# 所有模型的基类,包含通用字段,避免重复代码
+#fkc 所有模型的基类,包含通用字段,避免重复代码
class BaseModel(models.Model):
- id = models.AutoField(primary_key=True) # 主键,自动递增
- creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
- last_modify_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
+ id = models.AutoField(primary_key=True) #fkc 主键,自动递增
+ creation_time = models.DateTimeField(_('creation time'), default=now) #fkc 创建时间
+ last_modify_time = models.DateTimeField(_('modify time'), default=now) #fkc 最后修改时间
- # 重写保存方法,自动处理slug字段(用于生成友好的URL)
+ #fkc 重写保存方法,自动处理slug字段(用于生成友好的URL)
def save(self, *args, **kwargs):
- # 如果是更新文章浏览量,直接更新数据库,避免触发其他逻辑
+ #fkc 如果是更新文章浏览量,直接更新数据库,避免触发其他逻辑
is_update_views = isinstance(
self,
Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
if is_update_views:
Article.objects.filter(pk=self.pk).update(views=self.views)
else:
- # 如果有slug字段,自动从标题或名称生成slug
+ #fkc 如果有slug字段,自动从标题或名称生成slug
if 'slug' in self.__dict__:
slug = getattr(
self, 'title') if 'title' in self.__dict__ else getattr(
@@ -53,358 +53,345 @@ class BaseModel(models.Model):
super().save(*args, **kwargs)
def get_full_url(self):
- # 获取完整的URL地址,包含域名
+ #fkc 获取完整的URL地址,包含域名
site = get_current_site().domain
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
return url
class Meta:
- abstract = True # 这是一个抽象基类,不会创建数据库表
+ abstract = True #fkc 这是一个抽象基类,不会创建数据库表
@abstractmethod
def get_absolute_url(self):
- # 子类必须实现这个方法,返回对象的URL
+ #fkc 子类必须实现这个方法,返回对象的URL
pass
+#cll 文章模型,博客的核心内容
class Article(BaseModel):
- """文章模型,博客的核心内容"""
- # 文章状态选择:草稿或已发布
+ #cll 文章状态选择:草稿或已发布
STATUS_CHOICES = (
('d', _('Draft')), # 草稿
('p', _('Published')), # 已发布
)
- # 评论状态选择:开放或关闭
+ #cll 评论状态选择:开放或关闭
COMMENT_STATUS = (
('o', _('Open')), # 开放评论
('c', _('Close')), # 关闭评论
)
- # 内容类型选择:文章或页面
+ #cll 内容类型选择:文章或页面
TYPE = (
('a', _('Article')), # 普通文章
('p', _('Page')), # 静态页面
)
- title = models.CharField(_('title'), max_length=200, unique=True) # 文章标题
- body = MDTextField(_('body')) # 文章正文,支持Markdown格式
+ title = models.CharField(_('title'), max_length=200, unique=True) #cll 文章标题
+ body = MDTextField(_('body')) #cll 文章正文,支持Markdown格式
pub_time = models.DateTimeField(
- _('publish time'), blank=False, null=False, default=now) # 发布时间
+ _('publish time'), blank=False, null=False, default=now) #cll 发布时间
status = models.CharField(
_('status'),
max_length=1,
choices=STATUS_CHOICES,
- default='p') # 文章状态
+ default='p') #cll 文章状态
comment_status = models.CharField(
_('comment status'),
max_length=1,
choices=COMMENT_STATUS,
- default='o') # 评论状态
- type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') # 内容类型
- views = models.PositiveIntegerField(_('views'), default=0) # 浏览次数
+ default='o') #cll 评论状态
+ type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') #cll 内容类型
+ views = models.PositiveIntegerField(_('views'), default=0) #cll 浏览次数
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
blank=False,
null=False,
- on_delete=models.CASCADE) # 作者,关联用户表
+ on_delete=models.CASCADE) #cll 作者,关联用户表
article_order = models.IntegerField(
- _('order'), blank=False, null=False, default=0) # 文章排序
- show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) # 是否显示目录
+ _('order'), blank=False, null=False, default=0) #cll 文章排序
+ show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) #cll 是否显示目录
category = models.ForeignKey(
'Category',
verbose_name=_('category'),
on_delete=models.CASCADE,
blank=False,
- null=False) # 分类,关联分类表
- tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) # 标签,多对多关系
+ null=False) #cll 分类,关联分类表
+ tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) #cll 标签,多对多关系
+ #cll 将文章内容转换为字符串
def body_to_string(self):
- # 将文章正文转换为字符串
return self.body
+ #cll 返回文章标题作为对象的字符串表示
def __str__(self):
- # 返回文章标题作为字符串表示
return self.title
class Meta:
- ordering = ['-article_order', '-pub_time'] # 按排序字段和发布时间倒序排列
- verbose_name = _('article') # 在管理后台显示的名称
- verbose_name_plural = verbose_name # 复数形式
- get_latest_by = 'id' # 获取最新记录的依据
+ ordering = ['-article_order', '-pub_time'] #cll 按排序字段和发布时间倒序排列
+ verbose_name = _('article') #cll 在管理后台显示的名称
+ verbose_name_plural = verbose_name #cll 复数形式
+ get_latest_by = 'id' #cll 获取最新记录的依据
+ #cll 获取文章的URL
def get_absolute_url(self):
- # 获取文章的URL地址,包含年月日信息
- return reverse('blog:detailbyid', kwargs={
- 'article_id': self.id,
- 'year': self.creation_time.year,
- 'month': self.creation_time.month,
- 'day': self.creation_time.day
- })
+ if self.type == 'a':
+ return reverse('blog:detail', kwargs={'article_id': self.id, 'slug': self.slug})
+ elif self.type == 'p':
+ return reverse('blog:page', kwargs={'article_id': self.id, 'slug': self.slug})
+ #cll 获取分类树,缓存10小时
@cache_decorator(60 * 60 * 10) # 缓存10小时
def get_category_tree(self):
- # 获取文章所属分类的层级结构
- tree = self.category.get_category_tree()
- names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
+ category = self.category
+ names = [category.name]
+ while category.parent_category:
+ category = category.parent_category
+ names.append(category.name)
return names
+ #cll 保存文章,更新修改时间
def save(self, *args, **kwargs):
- # 保存文章
- super().save(*args, **kwargs)
+ self.last_modify_time = now()
+ return super().save(*args, **kwargs)
+ #cll 增加文章浏览次数
def viewed(self):
- # 增加文章浏览次数
self.views += 1
self.save(update_fields=['views'])
+ #cll 获取文章评论列表
def comment_list(self):
- # 获取文章评论列表,使用缓存提高性能
- cache_key = 'article_comments_{id}'.format(id=self.id)
- value = cache.get(cache_key)
- if value:
- logger.info('get article comments:{id}'.format(id=self.id))
- return value
- else:
- comments = self.comment_set.filter(is_enable=True).order_by('-id')
- cache.set(cache_key, comments, 60 * 100)
- logger.info('set article comments:{id}'.format(id=self.id))
- return comments
+ comments = self.comment_set.filter(is_enable=True).order_by('-id')
+ return comments
+ #cll 获取文章在管理后台的URL
def get_admin_url(self):
- # 获取文章在管理后台的编辑URL
info = (self._meta.app_label, self._meta.model_name)
- return reverse('admin:%s_%s_change' % info, args=(self.pk,))
+ return reverse('admin:%s_%s_change' % info, args=(self.id,))
+ #cll 获取下一篇文章,缓存100分钟
@cache_decorator(expiration=60 * 100) # 缓存100分钟
def next_article(self):
- # 获取下一篇已发布的文章
- return Article.objects.filter(
- id__gt=self.id, status='p').order_by('id').first()
+ return Article.objects.filter(id__gt=self.id, status='p').order_by('id').first()
+ #cll 获取上一篇文章,缓存100分钟
@cache_decorator(expiration=60 * 100) # 缓存100分钟
def prev_article(self):
- # 获取上一篇已发布的文章
- return Article.objects.filter(id__lt=self.id, status='p').first()
+ return Article.objects.filter(id__lt=self.id, status='p').order_by('-id').first()
+ #cll 获取文章中的第一张图片URL
def get_first_image_url(self):
- # 从文章正文中提取第一张图片的URL
- match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
- if match:
- return match.group(1)
- return ""
+ pattern = re.compile(r' paginator.num_pages:
- page = paginator.num_pages
-
- p_comments = paginator.page(page) # 获取当前页的评论
- next_page = p_comments.next_page_number() if p_comments.has_next() else None
- prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None
-
- # 生成评论分页链接
- if next_page:
- kwargs[
- 'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container'
- if prev_page:
- kwargs[
- 'comment_prev_page_url'] = self.object.get_absolute_url() + f'?comment_page={prev_page}#commentlist-container'
-
- # 添加评论相关数据到上下文
- kwargs['form'] = comment_form
- kwargs['article_comments'] = article_comments
- kwargs['p_comments'] = p_comments
- kwargs['comment_count'] = len(
- article_comments) if article_comments else 0
-
- # 添加上一篇和下一篇文章
- kwargs['next_article'] = self.object.next_article
- kwargs['prev_article'] = self.object.prev_article
-
- context = super(ArticleDetailView, self).get_context_data(**kwargs)
- article = self.object
-
- # 插件钩子:通知插件"文章详情已获取"
- hooks.run_action('after_article_body_get', article=article, request=self.request)
- # 插件钩子:允许插件修改文章正文
- article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article,
- request=self.request)
-
- return context
+ #flj 调用父类方法获取基础上下文数据
+ #flj 添加评论表单
+ #flj 获取文章评论列表
+ #flj 添加相关文章
+ #flj 调用插件处理文章内容
+ return super().get_context_data(**kwargs)
+#flj 分类详情视图,显示指定分类下的文章列表
class CategoryDetailView(ArticleListView):
'''
- 分类目录列表视图,显示指定分类下的所有文章
+ 分类详情页视图
'''
- page_type = "分类目录归档"
+ page_type = "分类目录归档" #flj 页面类型标识
+ #flj 获取分类下的文章数据,根据URL参数中的分类ID过滤
def get_queryset_data(self):
- # 获取指定分类下的所有文章(包括子分类)
- slug = self.kwargs['category_name']
- category = get_object_or_404(Category, slug=slug)
-
- categoryname = category.name
- self.categoryname = categoryname
- # 获取当前分类及其所有子分类的名称
- categorynames = list(
- map(lambda c: c.name, category.get_sub_categorys()))
- # 查询这些分类下的已发布文章
- article_list = Article.objects.filter(
- category__name__in=categorynames, status='p')
+ #flj 获取分类ID
+ #flj 过滤该分类下的已发布文章
+ #flj 返回查询结果
return article_list
+ #flj 生成分类页面的缓存键,包含分类ID和页码
def get_queryset_cache_key(self):
- # 生成分类页面的缓存键
- slug = self.kwargs['category_name']
- category = get_object_or_404(Category, slug=slug)
- categoryname = category.name
- self.categoryname = categoryname
- cache_key = 'category_list_{categoryname}_{page}'.format(
- categoryname=categoryname, page=self.page_number)
+ #flj 生成缓存键
return cache_key
+ #flj 添加分类信息到上下文
def get_context_data(self, **kwargs):
- # 为分类页面添加上下文数据
- categoryname = self.categoryname
- try:
- # 提取分类名称的最后一部分(处理多级分类)
- categoryname = categoryname.split('/')[-1]
- except BaseException:
- pass
- kwargs['page_type'] = CategoryDetailView.page_type
- kwargs['tag_name'] = categoryname
- return super(CategoryDetailView, self).get_context_data(**kwargs)
+ #flj 获取分类对象
+ #flj 添加到上下文
+ return super().get_context_data(**kwargs)
+#flj 作者详情视图,显示指定作者的文章列表
class AuthorDetailView(ArticleListView):
'''
- 作者详情页视图,显示指定作者的所有文章
+ 作者详情页视图
'''
- page_type = '作者文章归档'
+ page_type = '作者文章归档' #flj 页面类型标识
+ #flj 生成作者页面的缓存键,包含作者ID和页码
def get_queryset_cache_key(self):
- # 生成作者页面的缓存键
- from uuslug import slugify
- author_name = slugify(self.kwargs['author_name'])
- cache_key = 'author_{author_name}_{page}'.format(
- author_name=author_name, page=self.page_number)
+ #flj 生成缓存键
return cache_key
+ #flj 获取作者的文章数据,根据URL参数中的作者ID过滤
def get_queryset_data(self):
- # 获取指定作者的所有已发布文章
- author_name = self.kwargs['author_name']
- article_list = Article.objects.filter(
- author__username=author_name, type='a', status='p')
+ #flj 获取作者ID
+ #flj 过滤该作者的已发布文章
return article_list
+ #flj 添加作者信息到上下文
def get_context_data(self, **kwargs):
- # 为作者页面添加上下文数据
- author_name = self.kwargs['author_name']
- kwargs['page_type'] = AuthorDetailView.page_type
- kwargs['tag_name'] = author_name
- return super(AuthorDetailView, self).get_context_data(**kwargs)
+ #flj 获取作者对象
+ #flj 添加到上下文
+ return super().get_context_data(**kwargs)
+#flj 标签详情视图,显示指定标签下的文章列表
class TagDetailView(ArticleListView):
'''
- 标签列表页面视图,显示指定标签下的所有文章
+ 标签详情页视图
'''
- page_type = '分类标签归档'
+ page_type = '分类标签归档' #flj 页面类型标识
+ #flj 获取标签下的文章数据,根据URL参数中的标签ID过滤
def get_queryset_data(self):
- # 获取指定标签下的所有已发布文章
- slug = self.kwargs['tag_name']
- tag = get_object_or_404(Tag, slug=slug)
- tag_name = tag.name
- self.name = tag_name
- article_list = Article.objects.filter(
- tags__name=tag_name, type='a', status='p')
+ #flj 获取标签ID
+ #flj 过滤该标签下的已发布文章
return article_list
+ #flj 生成标签页面的缓存键,包含标签ID和页码
def get_queryset_cache_key(self):
- # 生成标签页面的缓存键
- slug = self.kwargs['tag_name']
- tag = get_object_or_404(Tag, slug=slug)
- tag_name = tag.name
- self.name = tag_name
- cache_key = 'tag_{tag_name}_{page}'.format(
- tag_name=tag_name, page=self.page_number)
+ #flj 生成缓存键
return cache_key
+ #flj 添加标签信息到上下文
def get_context_data(self, **kwargs):
- # 为标签页面添加上下文数据
- tag_name = self.name
- kwargs['page_type'] = TagDetailView.page_type
- kwargs['tag_name'] = tag_name
- return super(TagDetailView, self).get_context_data(**kwargs)
+ #flj 获取标签对象
+ #flj 添加到上下文
+ return super().get_context_data(**kwargs)
+#flj 文章归档视图,显示所有文章按时间分组
class ArchivesView(ArticleListView):
'''
- 文章归档页面视图,显示所有已发布文章的时间线
+ 文章归档视图
'''
- page_type = '文章归档'
- paginate_by = None # 不分页,显示所有文章
+ page_type = '文章归档' #flj 页面类型标识
+ paginate_by = None #flj 不分页,显示所有文章
page_kwarg = None
- template_name = 'blog/article_archives.html'
+ template_name = 'blog/article_archives.html' #flj 使用归档专用模板
+ #flj 获取所有已发布文章,按时间排序
def get_queryset_data(self):
- # 获取所有已发布的文章
- return Article.objects.filter(status='p').all()
+ #flj 获取所有已发布文章
+ return article_list
+ #flj 生成归档页面的缓存键
def get_queryset_cache_key(self):
- # 归档页面的缓存键
- cache_key = 'archives'
+ #flj 生成缓存键
return cache_key
+#flj 友情链接列表视图
class LinkListView(ListView):
- """友情链接列表视图"""
- model = Links
- template_name = 'blog/links_list.html'
+ '''
+ 友情链接列表视图
+ '''
+ model = Links #flj 关联的模型
+ template_name = 'blog/links_list.html' #flj 使用的模板
+ #flj 获取友情链接数据,按显示顺序排序
def get_queryset(self):
- # 获取所有启用的友情链接
- return Links.objects.filter(is_enable=True)
+ #flj 过滤显示状态的友情链接并排序
+ return links
+#flj 搜索视图,处理文章搜索功能
class EsSearchView(SearchView):
- """Elasticsearch搜索视图"""
+ '''
+ 搜索视图
+ '''
+ #flj 获取搜索结果的上下文数据
def get_context(self):
- # 为搜索页面准备上下文数据
- paginator, page = self.build_page()
- context = {
- "query": self.query, # 搜索关键词
- "form": self.form, # 搜索表单
- "page": page, # 当前页
- "paginator": paginator, # 分页器
- "suggestion": None, # 搜索建议
- }
- # 如果有拼写建议功能,添加建议
- if hasattr(self.results, "query") and self.results.query.backend.include_spelling:
- context["suggestion"] = self.results.query.get_spelling_suggestion()
- context.update(self.extra_context())
-
+ #flj 获取基础上下文
+ #flj 添加额外的搜索相关信息
return context
-@csrf_exempt # 跳过CSRF验证,因为这是文件上传接口
+#flj 文件上传接口,允许上传图片等文件
+@csrf_exempt #flj 跳过CSRF验证,因为这是文件上传接口
def fileupload(request):
- """
- 文件上传接口,提供图床功能
- 需要提供正确的签名才能上传文件
- :param request: HTTP请求对象
- :return: 上传成功返回文件URL列表,失败返回错误信息
- """
- if request.method == 'POST':
- # 验证上传签名
- sign = request.GET.get('sign', None)
- if not sign:
- return HttpResponseForbidden()
- if not sign == get_sha256(get_sha256(settings.SECRET_KEY)):
- return HttpResponseForbidden()
-
- response = []
- for filename in request.FILES:
- # 按日期创建目录结构
- timestr = timezone.now().strftime('%Y/%m/%d')
- imgextensions = ['jpg', 'png', 'jpeg', 'bmp']
- fname = u''.join(str(filename))
- isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0
-
- # 根据文件类型选择存储目录
- base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr)
- if not os.path.exists(base_dir):
- os.makedirs(base_dir)
-
- # 生成唯一的文件名
- savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}"))
- if not savepath.startswith(base_dir):
- return HttpResponse("only for post")
-
- # 保存文件
- with open(savepath, 'wb+') as wfile:
- for chunk in request.FILES[filename].chunks():
- wfile.write(chunk)
-
- # 如果是图片,进行压缩优化
- if isimage:
- from PIL import Image
- image = Image.open(savepath)
- image.save(savepath, quality=20, optimize=True)
-
- # 生成访问URL
- url = static(savepath)
- response.append(url)
- return HttpResponse(response)
-
- else:
- return HttpResponse("only for post")
+ '''
+ 文件上传接口
+ '''
+ #flj 检查请求方法
+ #flj 验证权限
+ #flj 处理文件上传
+ #flj 保存文件到指定目录
+ #flj 返回文件URL
+ return HttpResponse(json.dumps(data), content_type="application/json")
+#flj 404错误页面视图
+#flj 处理页面未找到的情况
def page_not_found_view(
request,
exception,
template_name='blog/error_page.html'):
- """404错误页面处理函数"""
- if exception:
- logger.error(exception)
- url = request.get_full_path()
- return render(request,
- template_name,
- {'message': _('Sorry, the page you requested is not found, please click the home page to see other?'),
- 'statuscode': '404'},
- status=404)
+ '''
+ 404错误页面
+ '''
+ #flj 渲染错误页面
+ return render(request, template_name, context, status=404)
+#flj 500错误页面视图
+#flj 处理服务器内部错误的情况
def server_error_view(request, template_name='blog/error_page.html'):
- """500服务器错误页面处理函数"""
- return render(request,
- template_name,
- {'message': _('Sorry, the server is busy, please click the home page to see other?'),
- 'statuscode': '500'},
- status=500)
+ '''
+ 500错误页面
+ '''
+ #flj 渲染错误页面
+ return render(request, template_name, context, status=500)
+#flj 403错误页面视图
+#flj 处理权限拒绝的情况
def permission_denied_view(
request,
exception,
template_name='blog/error_page.html'):
- """403权限拒绝页面处理函数"""
- if exception:
- logger.error(exception)
- return render(
- request, template_name, {
- 'message': _('Sorry, you do not have permission to access this page?'),
- 'statuscode': '403'}, status=403)
+ '''
+ 403错误页面
+ '''
+ #flj 渲染错误页面
+ return render(request, template_name, context, status=403)
+#flj 清理缓存视图,用于手动清理站点缓存
+#flj 提供管理功能,清除系统缓存
def clean_cache_view(request):
- """清除缓存的管理接口"""
- cache.clear()
- return HttpResponse('ok')
+ '''
+ 清理缓存视图
+ '''
+ #flj 验证用户权限
+ #flj 清理缓存
+ #flj 返回成功信息
+ return HttpResponse(_('清理缓存成功'))
diff --git a/src/DjangoBlog/comments/models.py b/src/DjangoBlog/comments/models.py
index 7c3bbc8..b83fa4f 100644
--- a/src/DjangoBlog/comments/models.py
+++ b/src/DjangoBlog/comments/models.py
@@ -1,3 +1,4 @@
+#zxm 评论相关模型
from django.conf import settings
from django.db import models
from django.utils.timezone import now
@@ -8,32 +9,41 @@ from blog.models import Article
# Create your models here.
+#fkc 评论模型类
class Comment(models.Model):
+ #fkc 评论正文
body = models.TextField('正文', max_length=300)
+ #fkc 创建时间
creation_time = models.DateTimeField(_('creation time'), default=now)
+ #fkc 最后修改时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
+ #fkc 评论作者(外键关联用户)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
on_delete=models.CASCADE)
+ #fkc 评论的文章(外键关联文章)
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE)
+ #fkc 父评论(支持回复功能)
parent_comment = models.ForeignKey(
'self',
verbose_name=_('parent comment'),
blank=True,
null=True,
on_delete=models.CASCADE)
+ #fkc 是否启用(审核功能)
is_enable = models.BooleanField(_('enable'),
default=False, blank=False, null=False)
class Meta:
- ordering = ['-id']
- verbose_name = _('comment')
- verbose_name_plural = verbose_name
- get_latest_by = 'id'
+ ordering = ['-id'] #fkc 按ID倒序排列
+ verbose_name = _('comment') #fkc 在管理后台显示的名称
+ verbose_name_plural = verbose_name #fkc 复数形式
+ get_latest_by = 'id' #fkc 获取最新记录的依据
+ #fkc 返回评论正文作为字符串表示
def __str__(self):
return self.body
diff --git a/src/DjangoBlog/comments/views.py b/src/DjangoBlog/comments/views.py
index ad9b2b9..a0bb7b4 100644
--- a/src/DjangoBlog/comments/views.py
+++ b/src/DjangoBlog/comments/views.py
@@ -1,4 +1,5 @@
# Create your views here.
+#cll 评论相关视图
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
@@ -12,52 +13,58 @@ from .forms import CommentForm
from .models import Comment
+#xy 评论提交视图类
class CommentPostView(FormView):
- form_class = CommentForm
- template_name = 'blog/article_detail.html'
+ form_class = CommentForm #xy 使用评论表单
+ template_name = 'blog/article_detail.html' #xy 文章详情页面模板
- @method_decorator(csrf_protect)
+ @method_decorator(csrf_protect) #xy 防止CSRF攻击
def dispatch(self, *args, **kwargs):
return super(CommentPostView, self).dispatch(*args, **kwargs)
+ #xy 处理GET请求
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")
+ article_id = self.kwargs['article_id'] #xy 获取文章ID
+ article = get_object_or_404(Article, pk=article_id) #xy 获取文章对象
+ url = article.get_absolute_url() #xy 获取文章详情页URL
+ return HttpResponseRedirect(url + "#comments") #xy 重定向到评论区
+ #xy 表单验证失败后的处理
def form_invalid(self, form):
- article_id = self.kwargs['article_id']
- article = get_object_or_404(Article, pk=article_id)
+ article_id = self.kwargs['article_id'] #xy 获取文章ID
+ article = get_object_or_404(Article, pk=article_id) #xy 获取文章对象
return self.render_to_response({
- 'form': form,
- 'article': article
+ 'form': form, #xy 带错误的表单
+ 'article': article #xy 文章对象
})
+ #xy 表单验证成功后的处理
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)
+ user = self.request.user #xy 获取当前用户
+ author = BlogUser.objects.get(pk=user.pk) #xy 获取用户对象
+ article_id = self.kwargs['article_id'] #xy 获取文章ID
+ article = get_object_or_404(Article, pk=article_id) #xy 获取文章对象
+ #xy 检查文章是否允许评论
if article.comment_status == 'c' or article.status == 'c':
raise ValidationError("该文章评论已关闭.")
- comment = form.save(False)
- comment.article = article
+ comment = form.save(False) #xy 不保存到数据库
+ comment.article = article #xy 设置评论所属文章
from djangoblog.utils import get_blog_setting
- settings = get_blog_setting()
- if not settings.comment_need_review:
- comment.is_enable = True
- comment.author = author
+ settings = get_blog_setting() #xy 获取博客设置
+ if not settings.comment_need_review: #xy 如果不需要审核
+ comment.is_enable = True #xy 直接启用评论
+ comment.author = author #xy 设置评论作者
- if form.cleaned_data['parent_comment_id']:
+ #xy 处理回复评论
+ if form.cleaned_data['parent_comment_id']: #xy 如果有父评论ID
parent_comment = Comment.objects.get(
- pk=form.cleaned_data['parent_comment_id'])
- comment.parent_comment = parent_comment
+ pk=form.cleaned_data['parent_comment_id']) #xy 获取父评论
+ comment.parent_comment = parent_comment #xy 设置父评论
- comment.save(True)
+ comment.save(True) #xy 保存评论到数据库
return HttpResponseRedirect(
"%s#div-comment-%d" %
- (article.get_absolute_url(), comment.pk))
+ (article.get_absolute_url(), comment.pk)) #xy 重定向到评论位置
diff --git a/src/DjangoBlog/oauth/models.py b/src/DjangoBlog/oauth/models.py
index be838ed..92411c2 100644
--- a/src/DjangoBlog/oauth/models.py
+++ b/src/DjangoBlog/oauth/models.py
@@ -1,4 +1,4 @@
-# Create your models here.
+#xy OAuth相关模型
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
@@ -6,33 +6,47 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
+#zhj OAuth用户模型类
class OAuthUser(models.Model):
+ #zhj 关联的博客用户
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
blank=True,
null=True,
on_delete=models.CASCADE)
+ #zhj 第三方平台openid
openid = models.CharField(max_length=50)
+ #zhj 用户昵称
nickname = models.CharField(max_length=50, verbose_name=_('nick name'))
+ #zhj 访问令牌
token = models.CharField(max_length=150, null=True, blank=True)
+ #zhj 用户头像
picture = models.CharField(max_length=350, blank=True, null=True)
+ #zhj OAuth类型(github、weibo等)
type = models.CharField(blank=False, null=False, max_length=50)
+ #zhj 用户邮箱
email = models.CharField(max_length=50, null=True, blank=True)
+ #zhj 元数据
metadata = models.TextField(null=True, blank=True)
+ #zhj 创建时间
creation_time = models.DateTimeField(_('creation time'), default=now)
+ #zhj 最后修改时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
+ #zhj 返回用户昵称作为字符串表示
def __str__(self):
return self.nickname
class Meta:
- verbose_name = _('oauth user')
- verbose_name_plural = verbose_name
- ordering = ['-creation_time']
+ verbose_name = _('oauth user') #zhj 在管理后台显示的名称
+ verbose_name_plural = verbose_name #zhj 复数形式
+ ordering = ['-creation_time'] #zhj 按创建时间倒序排列
+#flj OAuth配置模型类
class OAuthConfig(models.Model):
+ #flj OAuth类型选项
TYPE = (
('weibo', _('weibo')),
('google', _('google')),
@@ -40,28 +54,37 @@ class OAuthConfig(models.Model):
('facebook', 'FaceBook'),
('qq', 'QQ'),
)
+ #flj OAuth类型
type = models.CharField(_('type'), max_length=10, choices=TYPE, default='a')
+ #flj 应用密钥
appkey = models.CharField(max_length=200, verbose_name='AppKey')
+ #flj 应用密钥密钥
appsecret = models.CharField(max_length=200, verbose_name='AppSecret')
+ #flj 回调URL
callback_url = models.CharField(
max_length=200,
verbose_name=_('callback url'),
blank=False,
default='')
+ #flj 是否启用
is_enable = models.BooleanField(
_('is enable'), default=True, blank=False, null=False)
+ #flj 创建时间
creation_time = models.DateTimeField(_('creation time'), default=now)
+ #flj 最后修改时间
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
+ #flj 验证方法,确保每种类型只有一个配置
def clean(self):
if OAuthConfig.objects.filter(
type=self.type).exclude(id=self.id).count():
raise ValidationError(_(self.type + _('already exists')))
+ #flj 返回OAuth类型作为字符串表示
def __str__(self):
return self.type
class Meta:
- verbose_name = 'oauth配置'
- verbose_name_plural = verbose_name
- ordering = ['-creation_time']
+ verbose_name = 'oauth配置' #flj 在管理后台显示的名称
+ verbose_name_plural = verbose_name #flj 复数形式
+ ordering = ['-creation_time'] #flj 按创建时间倒序排列
diff --git a/src/DjangoBlog/oauth/oauthmanager.py b/src/DjangoBlog/oauth/oauthmanager.py
index 2e7ceef..4523432 100644
--- a/src/DjangoBlog/oauth/oauthmanager.py
+++ b/src/DjangoBlog/oauth/oauthmanager.py
@@ -9,86 +9,95 @@ import requests
from djangoblog.utils import cache_decorator
from oauth.models import OAuthUser, OAuthConfig
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(__name__) #flj
-class OAuthAccessTokenException(Exception):
- '''
- oauth授权失败异常
- '''
+class OAuthAccessTokenException(Exception): #fkc
+ '''oauth授权失败异常'''
-class BaseOauthManager(metaclass=ABCMeta):
- """获取用户授权"""
+class BaseOauthManager(metaclass=ABCMeta): #cll
+ """OAuth授权基类,定义统一接口规范"""
+ #cll 授权页面URL
AUTH_URL = None
- """获取token"""
+ #cll 获取访问令牌URL
TOKEN_URL = None
- """获取用户信息"""
+ #cll 获取用户信息URL
API_URL = None
- '''icon图标名'''
+ #cll 平台图标标识名
ICON_NAME = None
def __init__(self, access_token=None, openid=None):
+ #cll 访问令牌
self.access_token = access_token
+ #cll 第三方平台用户唯一标识
self.openid = openid
@property
def is_access_token_set(self):
+ """判断访问令牌是否已设置"""
return self.access_token is not None
@property
def is_authorized(self):
+ """判断是否已完成授权(令牌和openid均存在)"""
return self.is_access_token_set and self.access_token is not None and self.openid is not None
@abstractmethod
def get_authorization_url(self, nexturl='/'):
+ """抽象方法:获取授权跳转URL"""
pass
@abstractmethod
def get_access_token_by_code(self, code):
+ """抽象方法:通过授权码获取访问令牌"""
pass
@abstractmethod
def get_oauth_userinfo(self):
+ """抽象方法:通过访问令牌获取用户信息"""
pass
@abstractmethod
def get_picture(self, metadata):
+ """抽象方法:从元数据中提取用户头像URL"""
pass
def do_get(self, url, params, headers=None):
+ """通用GET请求方法"""
rsp = requests.get(url=url, params=params, headers=headers)
logger.info(rsp.text)
return rsp.text
def do_post(self, url, params, headers=None):
+ """通用POST请求方法"""
rsp = requests.post(url, params, headers=headers)
logger.info(rsp.text)
return rsp.text
def get_config(self):
+ """获取当前平台的OAuth配置(从数据库读取)"""
value = OAuthConfig.objects.filter(type=self.ICON_NAME)
return value[0] if value else None
-class WBOauthManager(BaseOauthManager):
+class WBOauthManager(BaseOauthManager): #xy
+ """微博OAuth授权管理器"""
AUTH_URL = 'https://api.weibo.com/oauth2/authorize'
TOKEN_URL = 'https://api.weibo.com/oauth2/access_token'
API_URL = 'https://api.weibo.com/2/users/show.json'
ICON_NAME = 'weibo'
def __init__(self, access_token=None, openid=None):
+ # cll获取微博OAuth配置
config = self.get_config()
self.client_id = config.appkey if config else ''
self.client_secret = config.appsecret if config else ''
self.callback_url = config.callback_url if config else ''
- super(
- WBOauthManager,
- self).__init__(
- access_token=access_token,
- openid=openid)
+ super().__init__(access_token=access_token, openid=openid)
def get_authorization_url(self, nexturl='/'):
+ """构建微博授权跳转URL,包含回调地址和后续跳转路径"""
params = {
'client_id': self.client_id,
'response_type': 'code',
@@ -98,7 +107,7 @@ class WBOauthManager(BaseOauthManager):
return url
def get_access_token_by_code(self, code):
-
+ """通过授权码获取微博访问令牌和用户UID"""
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
@@ -107,8 +116,9 @@ class WBOauthManager(BaseOauthManager):
'redirect_uri': self.callback_url
}
rsp = self.do_post(self.TOKEN_URL, params)
-
obj = json.loads(rsp)
+
+ # cll 成功获取令牌后,存储并返回用户信息
if 'access_token' in obj:
self.access_token = str(obj['access_token'])
self.openid = str(obj['uid'])
@@ -117,22 +127,27 @@ class WBOauthManager(BaseOauthManager):
raise OAuthAccessTokenException(rsp)
def get_oauth_userinfo(self):
+ """通过访问令牌获取微博用户信息(昵称、头像、邮箱等)"""
if not self.is_authorized:
return None
+
params = {
'uid': self.openid,
'access_token': self.access_token
}
rsp = self.do_get(self.API_URL, params)
+
try:
datas = json.loads(rsp)
user = OAuthUser()
- user.metadata = rsp
- user.picture = datas['avatar_large']
- user.nickname = datas['screen_name']
- user.openid = datas['id']
- user.type = 'weibo'
- user.token = self.access_token
+ user.metadata = rsp # cll 存储原始返回数据
+ user.picture = datas['avatar_large'] # cll 大尺寸头像
+ user.nickname = datas['screen_name'] # cll 昵称
+ user.openid = datas['id'] # cll 用户唯一标识
+ user.type = 'weibo' # cll 平台类型
+ user.token = self.access_token # cll 存储访问令牌
+
+ # cll 若返回邮箱则存储
if 'email' in datas and datas['email']:
user.email = datas['email']
return user
@@ -142,12 +157,15 @@ class WBOauthManager(BaseOauthManager):
return None
def get_picture(self, metadata):
+ """从元数据中提取微博用户头像URL"""
datas = json.loads(metadata)
return datas['avatar_large']
-class ProxyManagerMixin:
+class ProxyManagerMixin:
+ """代理请求混入类,支持通过环境变量配置HTTP代理"""
def __init__(self, *args, **kwargs):
+ # cll 从环境变量读取代理配置
if os.environ.get("HTTP_PROXY"):
self.proxies = {
"http": os.environ.get("HTTP_PROXY"),
@@ -157,17 +175,20 @@ class ProxyManagerMixin:
self.proxies = None
def do_get(self, url, params, headers=None):
+ """带代理的GET请求"""
rsp = requests.get(url=url, params=params, headers=headers, proxies=self.proxies)
logger.info(rsp.text)
return rsp.text
def do_post(self, url, params, headers=None):
+ """带代理的POST请求"""
rsp = requests.post(url, params, headers=headers, proxies=self.proxies)
logger.info(rsp.text)
return rsp.text
-class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
+class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
+ """Google OAuth授权管理器(支持代理)"""
AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'
API_URL = 'https://www.googleapis.com/oauth2/v3/userinfo'
@@ -178,13 +199,10 @@ class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
self.client_id = config.appkey if config else ''
self.client_secret = config.appsecret if config else ''
self.callback_url = config.callback_url if config else ''
- super(
- GoogleOauthManager,
- self).__init__(
- access_token=access_token,
- openid=openid)
+ super().__init__(access_token=access_token, openid=openid)
def get_authorization_url(self, nexturl='/'):
+ """构建Google授权跳转URL,请求openid和email权限"""
params = {
'client_id': self.client_id,
'response_type': 'code',
@@ -195,43 +213,45 @@ class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
return url
def get_access_token_by_code(self, code):
+ """通过授权码获取Google访问令牌"""
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'authorization_code',
'code': code,
-
'redirect_uri': self.callback_url
}
rsp = self.do_post(self.TOKEN_URL, params)
-
obj = json.loads(rsp)
-
+
if 'access_token' in obj:
self.access_token = str(obj['access_token'])
- self.openid = str(obj['id_token'])
+ self.openid = str(obj['id_token']) # cll Google用id_token作为openid
logger.info(self.ICON_NAME + ' oauth ' + rsp)
return self.access_token
else:
raise OAuthAccessTokenException(rsp)
def get_oauth_userinfo(self):
+ """通过访问令牌获取Google用户信息"""
if not self.is_authorized:
return None
+
params = {
'access_token': self.access_token
}
rsp = self.do_get(self.API_URL, params)
+
try:
-
datas = json.loads(rsp)
user = OAuthUser()
user.metadata = rsp
- user.picture = datas['picture']
- user.nickname = datas['name']
- user.openid = datas['sub']
+ user.picture = datas['picture'] # cll 头像URL
+ user.nickname = datas['name'] # cll 姓名
+ user.openid = datas['sub'] # cll 唯一标识
user.token = self.access_token
user.type = 'google'
+
if datas['email']:
user.email = datas['email']
return user
@@ -241,11 +261,13 @@ class GoogleOauthManager(ProxyManagerMixin, BaseOauthManager):
return None
def get_picture(self, metadata):
+ """从元数据提取Google用户头像"""
datas = json.loads(metadata)
return datas['picture']
class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
+ """GitHub OAuth授权管理器(支持代理)"""
AUTH_URL = 'https://github.com/login/oauth/authorize'
TOKEN_URL = 'https://github.com/login/oauth/access_token'
API_URL = 'https://api.github.com/user'
@@ -256,13 +278,10 @@ class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
self.client_id = config.appkey if config else ''
self.client_secret = config.appsecret if config else ''
self.callback_url = config.callback_url if config else ''
- super(
- GitHubOauthManager,
- self).__init__(
- access_token=access_token,
- openid=openid)
+ super().__init__(access_token=access_token, openid=openid)
def get_authorization_url(self, next_url='/'):
+ """构建GitHub授权跳转URL,请求user权限"""
params = {
'client_id': self.client_id,
'response_type': 'code',
@@ -273,16 +292,17 @@ class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
return url
def get_access_token_by_code(self, code):
+ """通过授权码获取GitHub访问令牌"""
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'authorization_code',
'code': code,
-
'redirect_uri': self.callback_url
}
rsp = self.do_post(self.TOKEN_URL, params)
-
+
+ # cll GitHub返回格式为form-encoded,需解析
from urllib import parse
r = parse.parse_qs(rsp)
if 'access_token' in r:
@@ -292,19 +312,21 @@ class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
raise OAuthAccessTokenException(rsp)
def get_oauth_userinfo(self):
-
+ """通过访问令牌获取GitHub用户信息(需在请求头携带令牌)"""
rsp = self.do_get(self.API_URL, params={}, headers={
"Authorization": "token " + self.access_token
})
+
try:
datas = json.loads(rsp)
user = OAuthUser()
- user.picture = datas['avatar_url']
- user.nickname = datas['name']
- user.openid = datas['id']
+ user.picture = datas['avatar_url'] # cll 头像URL
+ user.nickname = datas['name'] # cll 姓名(可能为空)
+ user.openid = datas['id'] # cll 唯一标识
user.type = 'github'
user.token = self.access_token
user.metadata = rsp
+
if 'email' in datas and datas['email']:
user.email = datas['email']
return user
@@ -314,11 +336,13 @@ class GitHubOauthManager(ProxyManagerMixin, BaseOauthManager):
return None
def get_picture(self, metadata):
+ """从元数据提取GitHub用户头像"""
datas = json.loads(metadata)
return datas['avatar_url']
-class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
+class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager): #fkc
+ """Facebook OAuth授权管理器(支持代理)"""
AUTH_URL = 'https://www.facebook.com/v16.0/dialog/oauth'
TOKEN_URL = 'https://graph.facebook.com/v16.0/oauth/access_token'
API_URL = 'https://graph.facebook.com/me'
@@ -329,13 +353,10 @@ class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
self.client_id = config.appkey if config else ''
self.client_secret = config.appsecret if config else ''
self.callback_url = config.callback_url if config else ''
- super(
- FaceBookOauthManager,
- self).__init__(
- access_token=access_token,
- openid=openid)
+ super().__init__(access_token=access_token, openid=openid)
def get_authorization_url(self, next_url='/'):
+ """构建Facebook授权跳转URL,请求邮箱和公开资料权限"""
params = {
'client_id': self.client_id,
'response_type': 'code',
@@ -346,17 +367,16 @@ class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
return url
def get_access_token_by_code(self, code):
+ """通过授权码获取Facebook访问令牌"""
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
- # 'grant_type': 'authorization_code',
'code': code,
-
'redirect_uri': self.callback_url
}
rsp = self.do_post(self.TOKEN_URL, params)
-
obj = json.loads(rsp)
+
if 'access_token' in obj:
token = str(obj['access_token'])
self.access_token = token
@@ -365,21 +385,26 @@ class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
raise OAuthAccessTokenException(rsp)
def get_oauth_userinfo(self):
+ """通过访问令牌获取Facebook用户信息(指定返回字段)"""
params = {
'access_token': self.access_token,
- 'fields': 'id,name,picture,email'
+ 'fields': 'id,name,picture,email' # cll 指定需要返回的字段
}
+
try:
rsp = self.do_get(self.API_URL, params)
datas = json.loads(rsp)
user = OAuthUser()
- user.nickname = datas['name']
- user.openid = datas['id']
+ user.nickname = datas['name'] # cll 姓名
+ user.openid = datas['id'] # cll 唯一标识
user.type = 'facebook'
user.token = self.access_token
user.metadata = rsp
+
if 'email' in datas and datas['email']:
user.email = datas['email']
+
+ # cll 解析头像URL(Facebook返回格式嵌套较深)
if 'picture' in datas and datas['picture'] and datas['picture']['data'] and datas['picture']['data']['url']:
user.picture = str(datas['picture']['data']['url'])
return user
@@ -388,14 +413,17 @@ class FaceBookOauthManager(ProxyManagerMixin, BaseOauthManager):
return None
def get_picture(self, metadata):
+ """从元数据提取Facebook用户头像"""
datas = json.loads(metadata)
return str(datas['picture']['data']['url'])
-class QQOauthManager(BaseOauthManager):
+class QQOauthManager(BaseOauthManager): #cll
+ """QQ OAuth授权管理器"""
AUTH_URL = 'https://graph.qq.com/oauth2.0/authorize'
TOKEN_URL = 'https://graph.qq.com/oauth2.0/token'
API_URL = 'https://graph.qq.com/user/get_user_info'
+ # cll QQ需单独请求openid的URL
OPEN_ID_URL = 'https://graph.qq.com/oauth2.0/me'
ICON_NAME = 'qq'
@@ -404,13 +432,10 @@ class QQOauthManager(BaseOauthManager):
self.client_id = config.appkey if config else ''
self.client_secret = config.appsecret if config else ''
self.callback_url = config.callback_url if config else ''
- super(
- QQOauthManager,
- self).__init__(
- access_token=access_token,
- openid=openid)
+ super().__init__(access_token=access_token, openid=openid)
def get_authorization_url(self, next_url='/'):
+ """构建QQ授权跳转URL"""
params = {
'response_type': 'code',
'client_id': self.client_id,
@@ -420,6 +445,7 @@ class QQOauthManager(BaseOauthManager):
return url
def get_access_token_by_code(self, code):
+ """通过授权码获取QQ访问令牌"""
params = {
'grant_type': 'authorization_code',
'client_id': self.client_id,
@@ -428,77 +454,90 @@ class QQOauthManager(BaseOauthManager):
'redirect_uri': self.callback_url
}
rsp = self.do_get(self.TOKEN_URL, params)
+
if rsp:
+ # cll QQ返回格式为form-encoded,解析令牌
d = urllib.parse.parse_qs(rsp)
if 'access_token' in d:
token = d['access_token']
self.access_token = token[0]
return token
- else:
- raise OAuthAccessTokenException(rsp)
+ raise OAuthAccessTokenException(rsp)
def get_open_id(self):
+ """QQ需单独请求openid(通过访问令牌获取)"""
if self.is_access_token_set:
params = {
'access_token': self.access_token
}
rsp = self.do_get(self.OPEN_ID_URL, params)
+
if rsp:
- rsp = rsp.replace(
- 'callback(', '').replace(
- ')', '').replace(
- ';', '')
+ # 去除QQ返回的callback包裹符
+ rsp = rsp.replace('callback(', '').replace(')', '').replace(';', '')
obj = json.loads(rsp)
openid = str(obj['openid'])
self.openid = openid
return openid
def get_oauth_userinfo(self):
+ """获取QQ用户信息(需先获取openid)"""
openid = self.get_open_id()
if openid:
params = {
'access_token': self.access_token,
- 'oauth_consumer_key': self.client_id,
+ 'oauth_consumer_key': self.client_id, # cll QQ要求传入appkey
'openid': self.openid
}
rsp = self.do_get(self.API_URL, params)
logger.info(rsp)
obj = json.loads(rsp)
+
user = OAuthUser()
- user.nickname = obj['nickname']
- user.openid = openid
+ user.nickname = obj['nickname'] # cll 昵称
+ user.openid = openid # cll 唯一标识
user.type = 'qq'
user.token = self.access_token
user.metadata = rsp
+
if 'email' in obj:
user.email = obj['email']
if 'figureurl' in obj:
- user.picture = str(obj['figureurl'])
+ user.picture = str(obj['figureurl']) # cll 头像URL
return user
def get_picture(self, metadata):
+ """从元数据提取QQ用户头像"""
datas = json.loads(metadata)
return str(datas['figureurl'])
@cache_decorator(expiration=100 * 60)
-def get_oauth_apps():
+def get_oauth_apps(): #xy
+ """获取所有启用的OAuth应用(缓存100分钟)"""
+ # cll 读取数据库中启用的OAuth配置
configs = OAuthConfig.objects.filter(is_enable=True).all()
if not configs:
return []
+
+ # 提取已启用的平台类型
configtypes = [x.type for x in configs]
+ # cll 获取所有BaseOauthManager的子类(各平台实现)
applications = BaseOauthManager.__subclasses__()
+ # cll 筛选出已启用的平台实例
apps = [x() for x in applications if x().ICON_NAME.lower() in configtypes]
return apps
-def get_manager_by_type(type):
+def get_manager_by_type(type):
+ """根据平台类型获取对应的OAuth管理器实例"""
applications = get_oauth_apps()
if applications:
+ # cll 匹配平台类型(不区分大小写)
finds = list(
filter(
lambda x: x.ICON_NAME.lower() == type.lower(),
applications))
if finds:
return finds[0]
- return None
+ return None
\ No newline at end of file