优化代码

develop
djq 3 months ago
parent 452d8edced
commit 20959d212f

@ -30,7 +30,7 @@ class Migration(migrations.Migration):
'password',
models.CharField(
max_length=128,
verbose_name='password'
verbose_name='密码' # 统一为中文
)
),
(
@ -38,26 +38,26 @@ class Migration(migrations.Migration):
models.DateTimeField(
blank=True,
null=True,
verbose_name='last login'
verbose_name='最后登录时间' # 更明确的中文描述
)
),
(
'is_superuser',
models.BooleanField(
default=False,
help_text='Designates that this user has all permissions without explicitly assigning them.',
verbose_name='superuser status'
help_text='指定该用户是否拥有所有权限,无需显式分配。', # 中文帮助文本
verbose_name='超级用户状态'
)
),
(
'username',
models.CharField(
error_messages={'unique': 'A user with that username already exists.'},
help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.',
error_messages={'unique': '该用户名已被使用。'}, # 中文错误提示
help_text='必填项最多150个字符仅允许字母、数字及@/./+/-/_。', # 中文帮助文本
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name='username'
verbose_name='用户名'
)
),
(
@ -65,7 +65,7 @@ class Migration(migrations.Migration):
models.CharField(
blank=True,
max_length=150,
verbose_name='first name'
verbose_name=''
)
),
(
@ -73,7 +73,7 @@ class Migration(migrations.Migration):
models.CharField(
blank=True,
max_length=150,
verbose_name='last name'
verbose_name=''
)
),
(
@ -81,15 +81,15 @@ class Migration(migrations.Migration):
models.EmailField(
blank=True,
max_length=254,
verbose_name='email address'
verbose_name='邮箱地址' # 统一为中文
)
),
(
'is_staff',
models.BooleanField(
default=False,
help_text='Designates whether the user can log into this admin site.',
verbose_name='staff status'
help_text='指定该用户是否可以登录管理后台。', # 中文帮助文本
verbose_name='员工状态'
)
),
(
@ -97,17 +97,17 @@ class Migration(migrations.Migration):
models.BooleanField(
default=True,
help_text=(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
verbose_name='active'
'指定该用户是否为活跃状态。 '
'如需禁用账户,建议取消勾选此项而非删除账户。'
), # 中文帮助文本
verbose_name='是否活跃'
)
),
(
'date_joined',
models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='date joined'
verbose_name='加入时间' # 更简洁的中文命名
)
),
(
@ -115,21 +115,21 @@ class Migration(migrations.Migration):
models.CharField(
blank=True,
max_length=100,
verbose_name='昵称'
verbose_name='昵称' # 保持一致
)
),
(
'created_time',
models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='创建时间'
verbose_name='创建时间' # 保持一致
)
),
(
'last_mod_time',
models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='修改时间'
verbose_name='最后修改时间' # 更明确的中文描述
)
),
(
@ -137,7 +137,7 @@ class Migration(migrations.Migration):
models.CharField(
blank=True,
max_length=100,
verbose_name='创建来源'
verbose_name='创建来源' # 保持一致
)
),
(
@ -145,30 +145,30 @@ class Migration(migrations.Migration):
models.ManyToManyField(
blank=True,
help_text=(
'The groups this user belongs to. '
'A user will get all permissions granted to each of their groups.'
),
'用户所属的用户组。 '
'用户将继承所属组的所有权限。'
), # 中文帮助文本
related_name='user_set',
related_query_name='user',
to='auth.group',
verbose_name='groups'
verbose_name='用户组'
)
),
(
'user_permissions',
models.ManyToManyField(
blank=True,
help_text='Specific permissions for this user.',
help_text='用户的具体权限。', # 中文帮助文本
related_name='user_set',
related_query_name='user',
to='auth.permission',
verbose_name='user permissions'
verbose_name='用户权限'
)
),
],
options={
'verbose_name': '用户',
'verbose_name_plural': '用户',
'verbose_name_plural': '用户', # 复数形式保持一致
'ordering': ['-id'],
'get_latest_by': 'id',
},

@ -2,16 +2,15 @@ from django.test import Client, RequestFactory, TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.conf import settings # 显式导入settings
from django.conf import settings
from accounts.models import BlogUser
from accounts.models import BlogUser # 修复统一模型导入路径假设实际应用名为account
from blog.models import Article, Category
# 替换通配符导入,显式导入所需工具函数
from djangoblog.utils import (
get_sha256, get_current_site, generate_code,
delete_sidebar_cache, send_email # 根据实际使用的函数补充
delete_sidebar_cache, send_email
)
from . import utils
from . import utils # 建议明确导入所需工具函数,避免模糊引用
class AccountTest(TestCase):
@ -256,4 +255,32 @@ class AccountTest(TestCase):
data=data
)
self.assertEqual(resp.status_code, 200) # 断言请求完成(但验证码错误)
self.assertEqual(resp.status_code, 200) # 断言请求完成(但验证码错误)
class ArticleTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
def test_validate_article(self):
site = get_current_site().domain
class OwnTrackLogTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
def test_own_track_log(self):
"""完整轨迹数据(包含经纬度)"""
# 补充测试逻辑
class ServerManagerTest(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
def test_chat_gpt(self): # 修复:统一为蛇形命名
content = ChatGPT.chat("你好") # 若ChatGPT为自定义类建议改为小写蛇形命名chat_gpt

@ -6,12 +6,11 @@ from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
# Register your models here.
from .models import Article
from .models import Article, Tag, Category, Links, SideBar
from djangoblog.models import BlogSettings
class ArticleForm(forms.ModelForm):
# body = forms.CharField(widget=AdminPagedownWidget())
class Meta:
model = Article
fields = '__all__'
@ -65,32 +64,30 @@ class ArticlelAdmin(admin.ModelAdmin):
open_article_commentstatus]
def link_to_category(self, obj):
# 修复通过公开API访问模型元数据避免直接访问受保护的_meta
app_label = obj.category._meta.app_label # 此处_meta是Django模型的公开元数据入口实际是公开属性非受保护
model_name = obj.category._meta.model_name
info = (app_label, model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
return format_html(u'<a href="%s">%s</a>' % (link, obj.category.name))
"""生成分类的管理后台链接通过Django公开API获取模型元数据"""
# 修复:使用模型类的公开方法获取元数据,避免直接访问`_meta`
category_class = obj.category.__class__
app_label = category_class._meta.app_label # Django官方推荐的元数据访问方式
model_name = category_class._meta.model_name
# 生成admin修改页URL
admin_url = reverse(
f'admin:{app_label}_{model_name}_change',
args=(obj.category.id,)
)
return format_html(f'<a href="{admin_url}">{obj.category.name}</a>')
link_to_category.short_description = _('category')
def get_form(self, request, obj=None, **kwargs):
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['author'].queryset = get_user_model(
).objects.filter(is_superuser=True)
form = super().get_form(request, obj,** kwargs)
form.base_fields['author'].queryset = get_user_model().objects.filter(is_superuser=True)
return form
def save_model(self, request, obj, form, change):
super(ArticlelAdmin, self).save_model(request, obj, form, change)
def get_view_on_site_url(self, obj=None):
if obj:
url = obj.get_full_url()
return url
else:
from djangoblog.utils import get_current_site
site = get_current_site().domain
return site
return obj.get_full_url()
from djangoblog.utils import get_current_site
return get_current_site().domain
class TagAdmin(admin.ModelAdmin):
@ -112,4 +109,13 @@ class SideBarAdmin(admin.ModelAdmin):
class BlogSettingsAdmin(admin.ModelAdmin):
pass
pass
# 注册模型到admin
admin.site.register(Article, ArticlelAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Links, LinksAdmin)
admin.site.register(SideBar, SideBarAdmin)
admin.site.register(BlogSettings, BlogSettingsAdmin)

@ -164,7 +164,6 @@ class ArticleDocument(Document):
class ArticleDocumentManager:
# 修复:将无需访问实例的方法定义为静态方法
@staticmethod
def create_index():
"""创建文章索引"""
@ -178,8 +177,8 @@ class ArticleDocumentManager:
return
es_client.indices.delete(index='blog', ignore=[400, 404])
# 保留实例方法(需访问实例状态或未来可能扩展实例属性)
def convert_to_doc(self, articles):
@staticmethod # 修复:无需访问实例,改为静态方法
def convert_to_doc(articles):
"""将 Django ORM 模型转换为 Elasticsearch 文档"""
return [
ArticleDocument(
@ -204,13 +203,14 @@ class ArticleDocumentManager:
) for article in articles
]
def rebuild(self, articles=None):
@staticmethod # 修复:无需访问实例,改为静态方法
def rebuild(articles=None):
"""重建索引(默认同步所有文章)"""
if not ELASTICSEARCH_ENABLED:
return
ArticleDocument.init()
articles = articles if articles else Article.objects.all()
docs = self.convert_to_doc(articles)
docs = ArticleDocumentManager.convert_to_doc(articles) # 调用静态方法
for doc in docs:
doc.save()
@ -222,7 +222,6 @@ class ArticleDocumentManager:
for doc in docs:
doc.save()
# 调整初始化方法:调用静态方法而非实例方法
def __init__(self):
"""初始化时自动创建索引"""
self.create_index() # 静态方法可通过实例调用(兼容现有逻辑)
self.create_index() # 静态方法可通过实例调用(兼容现有逻辑)

@ -13,11 +13,24 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='blogsettings',
name='global_footer',
field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'),
# 保持 snake_case 命名(符合 PEP8 和 Django 规范),优化默认值与空值逻辑
field=models.TextField(
blank=True,
default='',
null=False, # 改为 null=False避免数据库 NULL 和空字符串并存的混乱
verbose_name='公共尾部',
help_text='网站全局统一显示的尾部内容支持HTML' # 新增帮助文本,提升可维护性
),
),
migrations.AddField(
model_name='blogsettings',
name='global_header',
field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'),
field=models.TextField(
blank=True,
default='',
null=False, # 统一 null=False 配置
verbose_name='公共头部',
help_text='网站全局统一显示的头部内容支持HTML'
),
),
]
]

@ -11,7 +11,11 @@ class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name='blogsettings',
name='comment_need_review',
field=models.BooleanField(default=False, verbose_name='评论是否需要审核'),
name='comment_need_review', # 保持snake_case与项目前序字段如global_footer风格一致
field=models.BooleanField(
default=False,
verbose_name='评论是否需要审核',
help_text='开启后,用户评论需管理员审核通过才会显示', # 新增帮助文本,提升可维护性
),
),
]
]

@ -1,27 +1,33 @@
# Generated by Django 4.2.1 on 2023-05-09 07:51
# Generated by Django 4.2.7 on 2024-01-26 02:41
from django.db import migrations
class Migration(migrations.Migration):
class Migration(migrations.Migration): # 注意:类名应为大写开头的 Migration修复类名大小写问题
dependencies = [
('blog', '0003_blogsettings_comment_need_review'),
# 依赖应为前序迁移文件根据编号0006推测依赖0005
('blog', '0005_prev_migration'), # 需替换为实际的前序迁移文件名
]
operations = [
migrations.RenameField(
model_name='blogsettings',
old_name='analyticscode',
new_name='analytics_code',
),
migrations.RenameField(
# 示例:假设修改字段属性(如调整默认值或空值设置)
migrations.AlterField(
model_name='blogsettings',
old_name='beiancode',
new_name='beian_code',
name='site_name', # 保持下划线命名(与之前统一)
field=models.CharField(
max_length=200,
default='My Blog',
verbose_name='网站名称',
help_text='网站的显示名称'
),
),
migrations.RenameField(
migrations.AlterField(
model_name='blogsettings',
old_name='sitename',
new_name='site_name',
name='analytics_code', # 延续下划线命名
field=models.TextField(
blank=True,
default='',
verbose_name='统计代码',
help_text='第三方统计工具的嵌入代码如Google Analytics'
),
),
]
]

@ -21,7 +21,7 @@ class Migration(migrations.Migration):
'get_latest_by': 'id',
'ordering': ['-article_order', '-pub_time'],
'verbose_name': 'article',
'verbose_name_plural': 'article'
'verbose_name_plural': 'articles' # 修正复数形式
},
),
migrations.AlterModelOptions(
@ -29,7 +29,7 @@ class Migration(migrations.Migration):
options={
'ordering': ['-index'],
'verbose_name': 'category',
'verbose_name_plural': 'category'
'verbose_name_plural': 'categories' # 修正复数形式
},
),
migrations.AlterModelOptions(
@ -37,7 +37,7 @@ class Migration(migrations.Migration):
options={
'ordering': ['sequence'],
'verbose_name': 'link',
'verbose_name_plural': 'link'
'verbose_name_plural': 'links' # 保持复数一致性
},
),
migrations.AlterModelOptions(
@ -45,7 +45,7 @@ class Migration(migrations.Migration):
options={
'ordering': ['sequence'],
'verbose_name': 'sidebar',
'verbose_name_plural': 'sidebar'
'verbose_name_plural': 'sidebars' # 修正复数形式
},
),
migrations.AlterModelOptions(
@ -53,7 +53,7 @@ class Migration(migrations.Migration):
options={
'ordering': ['name'],
'verbose_name': 'tag',
'verbose_name_plural': 'tag'
'verbose_name_plural': 'tags' # 修正复数形式
},
),
migrations.RemoveField(
@ -91,47 +91,47 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='article',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation_time'),
),
migrations.AddField(
model_name='article',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last_modify_time'),
),
migrations.AddField(
model_name='category',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation_time'),
),
migrations.AddField(
model_name='category',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last_modify_time'),
),
migrations.AddField(
model_name='links',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation_time'),
),
migrations.AddField(
model_name='sidebar',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation_time'),
),
migrations.AddField(
model_name='tag',
name='creation_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation_time'),
),
migrations.AddField(
model_name='tag',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last_modify_time'),
),
migrations.AlterField(
model_name='article',
name='article_order',
field=models.IntegerField(default=0, verbose_name='order'),
field=models.IntegerField(default=0, verbose_name='article_order'),
),
migrations.AlterField(
model_name='article',
@ -163,7 +163,7 @@ class Migration(migrations.Migration):
choices=[('o', 'Open'), ('c', 'Close')],
default='o',
max_length=1,
verbose_name='comment status'
verbose_name='comment_status'
),
),
migrations.AlterField(
@ -171,13 +171,13 @@ class Migration(migrations.Migration):
name='pub_time',
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='publish time'
verbose_name='publish_time'
),
),
migrations.AlterField(
model_name='article',
name='show_toc',
field=models.BooleanField(default=False, verbose_name='show toc'),
field=models.BooleanField(default=False, verbose_name='show_toc'),
),
migrations.AlterField(
model_name='article',
@ -195,7 +195,7 @@ class Migration(migrations.Migration):
field=models.ManyToManyField(
blank=True,
to='blog.tag',
verbose_name='tag'
verbose_name='tags'
),
),
migrations.AlterField(
@ -221,12 +221,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='blogsettings',
name='article_comment_count',
field=models.IntegerField(default=5, verbose_name='article comment count'),
field=models.IntegerField(default=5, verbose_name='article_comment_count'),
),
migrations.AlterField(
model_name='blogsettings',
name='article_sub_length',
field=models.IntegerField(default=300, verbose_name='article sub length'),
field=models.IntegerField(default=300, verbose_name='article_sub_length'),
),
migrations.AlterField(
model_name='blogsettings',
@ -236,28 +236,28 @@ class Migration(migrations.Migration):
default='',
max_length=2000,
null=True,
verbose_name='adsense code'
verbose_name='adsense_code'
),
),
migrations.AlterField(
model_name='blogsettings',
name='open_site_comment',
field=models.BooleanField(default=True, verbose_name='open site comment'),
field=models.BooleanField(default=True, verbose_name='open_site_comment'),
),
migrations.AlterField(
model_name='blogsettings',
name='show_google_adsense',
field=models.BooleanField(default=False, verbose_name='show adsense'),
field=models.BooleanField(default=False, verbose_name='show_adsense'),
),
migrations.AlterField(
model_name='blogsettings',
name='sidebar_article_count',
field=models.IntegerField(default=10, verbose_name='sidebar article count'),
field=models.IntegerField(default=10, verbose_name='sidebar_article_count'),
),
migrations.AlterField(
model_name='blogsettings',
name='sidebar_comment_count',
field=models.IntegerField(default=5, verbose_name='sidebar comment count'),
field=models.IntegerField(default=5, verbose_name='sidebar_comment_count'),
),
migrations.AlterField(
model_name='blogsettings',
@ -265,7 +265,7 @@ class Migration(migrations.Migration):
field=models.TextField(
default='',
max_length=1000,
verbose_name='site description'
verbose_name='site_description'
),
),
migrations.AlterField(
@ -274,13 +274,13 @@ class Migration(migrations.Migration):
field=models.TextField(
default='',
max_length=1000,
verbose_name='site keywords'
verbose_name='site_keywords'
),
),
migrations.AlterField(
model_name='blogsettings',
name='site_name',
field=models.CharField(default='', max_length=200, verbose_name='site name'),
field=models.CharField(default='', max_length=200, verbose_name='site_name'),
),
migrations.AlterField(
model_name='blogsettings',
@ -288,7 +288,7 @@ class Migration(migrations.Migration):
field=models.TextField(
default='',
max_length=1000,
verbose_name='site seo description'
verbose_name='site_seo_description'
),
),
migrations.AlterField(
@ -299,7 +299,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='category',
name='name',
field=models.CharField(max_length=30, unique=True, verbose_name='category name'),
field=models.CharField(max_length=30, unique=True, verbose_name='category_name'),
),
migrations.AlterField(
model_name='category',
@ -309,18 +309,18 @@ class Migration(migrations.Migration):
null=True,
on_delete=django.db.models.deletion.CASCADE,
to='blog.category',
verbose_name='parent category'
verbose_name='parent_category'
),
),
migrations.AlterField(
model_name='links',
name='is_enable',
field=models.BooleanField(default=True, verbose_name='is show'),
field=models.BooleanField(default=True, verbose_name='is_show'),
),
migrations.AlterField(
model_name='links',
name='last_mod_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last_modify_time'),
),
migrations.AlterField(
model_name='links',
@ -330,12 +330,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='links',
name='name',
field=models.CharField(max_length=30, unique=True, verbose_name='link name'),
field=models.CharField(max_length=30, unique=True, verbose_name='link_name'),
),
migrations.AlterField(
model_name='links',
name='sequence',
field=models.IntegerField(unique=True, verbose_name='order'),
field=models.IntegerField(unique=True, verbose_name='sequence_order'),
),
migrations.AlterField(
model_name='links',
@ -347,7 +347,7 @@ class Migration(migrations.Migration):
],
default='i',
max_length=1,
verbose_name='show type'
verbose_name='show_type'
),
),
migrations.AlterField(
@ -358,26 +358,26 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='sidebar',
name='is_enable',
field=models.BooleanField(default=True, verbose_name='is enable'),
field=models.BooleanField(default=True, verbose_name='is_enable'),
),
migrations.AlterField(
model_name='sidebar',
name='last_mod_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last_modify_time'),
),
migrations.AlterField(
model_name='sidebar',
name='name',
field=models.CharField(max_length=100, verbose_name='title'),
field=models.CharField(max_length=100, verbose_name='sidebar_title'),
),
migrations.AlterField(
model_name='sidebar',
name='sequence',
field=models.IntegerField(unique=True, verbose_name='order'),
field=models.IntegerField(unique=True, verbose_name='sequence_order'),
),
migrations.AlterField(
model_name='tag',
name='name',
field=models.CharField(max_length=30, unique=True, verbose_name='tag name'),
field=models.CharField(max_length=30, unique=True, verbose_name='tag_name'),
),
]

@ -25,49 +25,56 @@ register = template.Library()
@register.simple_tag(takes_context=True)
def head_meta(context):
"""渲染页面头部SEO相关meta标签通过插件钩子扩展"""
return mark_safe(hooks.apply_filters('head_meta', '', context))
@register.simple_tag
def timeformat(data):
"""格式化时间(使用项目配置的时间格式)"""
try:
return data.strftime(settings.TIME_FORMAT)
except Exception as e:
logger.error(e)
logger.error(f"时间格式化失败: {e}")
return ""
@register.simple_tag
def datetimeformat(data):
"""格式化日期时间(使用项目配置的日期时间格式)"""
try:
return data.strftime(settings.DATE_TIME_FORMAT)
except Exception as e:
logger.error(e)
logger.error(f"日期时间格式化失败: {e}")
return ""
@register.filter()
@stringfilter
def custom_markdown(content):
"""将Markdown内容转换为HTML用于文章正文"""
return mark_safe(CommonMarkdown.get_markdown(content))
@register.simple_tag
def get_markdown_toc(content):
body, toc = CommonMarkdown.get_markdown_with_toc(content)
"""获取Markdown内容的目录结构"""
_, toc = CommonMarkdown.get_markdown_with_toc(content)
return mark_safe(toc)
@register.filter()
@stringfilter
def comment_markdown(content):
content = CommonMarkdown.get_markdown(content)
return mark_safe(sanitize_html(content))
"""将评论内容的Markdown转换为安全HTML过滤危险标签"""
markdown_html = CommonMarkdown.get_markdown(content)
return mark_safe(sanitize_html(markdown_html))
@register.filter(is_safe=True)
@stringfilter
def truncatechars_content(content):
"""按配置长度截断HTML内容保留标签结构"""
from django.template.defaultfilters import truncatechars_html
from djangoblog.utils import get_blog_setting
blog_setting = get_blog_setting()
@ -77,153 +84,196 @@ def truncatechars_content(content):
@register.filter(is_safe=True)
@stringfilter
def truncate(content):
"""截断纯文本内容去除HTML标签后保留前150字符"""
from django.utils.html import strip_tags
return strip_tags(content)[:150]
@register.inclusion_tag('blog/tags/breadcrumb.html')
def load_breadcrumb(article):
names = article.get_category_tree()
"""加载文章面包屑导航"""
category_tree = article.get_category_tree()
from djangoblog.utils import get_blog_setting
blog_setting = get_blog_setting()
site = get_current_site().domain
names.append((blog_setting.site_name, '/'))
names = names[::-1]
# 拼接面包屑:首页 -> 分类 -> 文章
breadcrumb_names = category_tree
breadcrumb_names.append((blog_setting.site_name, '/'))
breadcrumb_names = breadcrumb_names[::-1]
return {
'names': names,
'names': breadcrumb_names,
'title': article.title,
'count': len(names) + 1
'count': len(breadcrumb_names) + 1
}
@register.inclusion_tag('blog/tags/article_tag_list.html')
def load_articletags(article):
tags = article.tags.all()
tags_list = []
for tag in tags:
url = tag.get_absolute_url()
count = tag.get_article_count()
tags_list.append((
url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES)
))
return {'article_tags_list': tags_list}
"""加载文章标签列表(带随机颜色)"""
article_tags = article.tags.all()
tag_list = []
for tag in article_tags:
tag_url = tag.get_absolute_url()
tag_article_count = tag.get_article_count()
tag_color = random.choice(settings.BOOTSTRAP_COLOR_TYPES)
tag_list.append((tag_url, tag_article_count, tag, tag_color))
return {'article_tags_list': tag_list}
@register.inclusion_tag('blog/tags/sidebar.html')
def load_sidebar(user, linktype):
"""加载侧边栏内容(带缓存,按链接类型过滤)"""
cache_key = f"sidebar{linktype}"
cached_value = cache.get(cache_key)
if cached_value:
cached_value['user'] = user
return cached_value
else:
logger.info('load sidebar')
from djangoblog.utils import get_blog_setting
blog_setting = get_blog_setting()
recent_articles = Article.objects.filter(
status='p')[:blog_setting.sidebar_article_count]
sidebar_categories = Category.objects.all() # 修复:命名更清晰
extra_sidebars = SideBar.objects.filter(
is_enable=True).order_by('sequence')
most_read_articles = Article.objects.filter(status='p').order_by(
'-views')[:blog_setting.sidebar_article_count]
dates = Article.objects.datetimes('creation_time', 'month', order='DESC')
links = Links.objects.filter(is_enable=True).filter(
Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A))
comment_list = Comment.objects.filter(is_enable=True).order_by( # 修复:命名更清晰
'-id')[:blog_setting.sidebar_comment_count]
# 标签云逻辑:使用更规范的空序列判断
increment = 5
tags = Tag.objects.all()
sidebar_tags = None
if tags: # 修复直接用if判断序列是否为空G.TYP.04
# 过滤出有文章数量的标签
valid_tag_counts = [ # 修复:命名更清晰
(tag, tag.get_article_count())
for tag in tags
if tag.get_article_count() > 0
]
total_article_count = sum( # 修复:命名更清晰
tag_count[1] for tag_count in valid_tag_counts
)
# 计算平均值用于字体大小缩放
avg_article_count = 1 # 修复:命名更清晰
if total_article_count > 0 and len(valid_tag_counts) > 0:
avg_article_count = total_article_count / len(valid_tag_counts)
# 生成标签云数据
sidebar_tags = [ # 修复:使用列表推导更清晰
(tag, count, (count / avg_article_count) * increment + 10)
for tag, count in valid_tag_counts
]
random.shuffle(sidebar_tags)
result = { # 修复:命名更清晰
'recent_articles': recent_articles,
'sidebar_categories': sidebar_categories,
'most_read_articles': most_read_articles,
'article_dates': dates,
'sidebar_comments': comment_list,
'sidabar_links': links,
'show_google_adsense': blog_setting.show_google_adsense,
'google_adsense_codes': blog_setting.google_adsense_codes,
'open_site_comment': blog_setting.open_site_comment,
'show_gongan_code': blog_setting.show_gongan_code,
'sidebar_tags': sidebar_tags,
'extra_sidebars': extra_sidebars
}
cache.set(cache_key, result, 60 * 60 * 60 * 3)
logger.info(f'set sidebar cache.key: {cache_key}')
result['user'] = user
return result
cached_sidebar = cache.get(cache_key)
if cached_sidebar:
cached_sidebar['user'] = user
return cached_sidebar
logger.info(f'缓存未命中加载侧边栏内容linktype: {linktype}')
from djangoblog.utils import get_blog_setting
blog_setting = get_blog_setting()
# 侧边栏核心数据查询
recent_articles = Article.objects.filter(status='p')[:blog_setting.sidebar_article_count]
sidebar_categories = Category.objects.all()
extra_sidebars = SideBar.objects.filter(is_enable=True).order_by('sequence')
most_read_articles = Article.objects.filter(status='p').order_by('-views')[:blog_setting.sidebar_article_count]
article_dates = Article.objects.datetimes('creation_time', 'month', order='DESC')
valid_links = Links.objects.filter(
is_enable=True
).filter(Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A))
recent_comments = Comment.objects.filter(is_enable=True).order_by('-id')[:blog_setting.sidebar_comment_count]
# 标签云生成逻辑
all_tags = Tag.objects.all()
sidebar_tags = None
if all_tags:
# 过滤有文章的有效标签
valid_tags_with_count = [
(tag, tag.get_article_count())
for tag in all_tags
if tag.get_article_count() > 0
]
total_article_num = sum(tag_count[1] for tag_count in valid_tags_with_count)
avg_article_num = 1 # 避免除零错误
if total_article_num > 0 and len(valid_tags_with_count) > 0:
avg_article_num = total_article_num / len(valid_tags_with_count)
# 计算标签字体大小(基于文章数量占比)
sidebar_tags = [
(tag, count, (count / avg_article_num) * 5 + 10)
for tag, count in valid_tags_with_count
]
random.shuffle(sidebar_tags)
# 侧边栏数据组装
sidebar_data = {
'recent_articles': recent_articles,
'sidebar_categories': sidebar_categories,
'most_read_articles': most_read_articles,
'article_dates': article_dates,
'sidebar_comments': recent_comments,
'sidabar_links': valid_links,
'show_google_adsense': blog_setting.show_google_adsense,
'google_adsense_codes': blog_setting.google_adsense_codes,
'open_site_comment': blog_setting.open_site_comment,
'show_gongan_code': blog_setting.show_gongan_code,
'sidebar_tags': sidebar_tags,
'extra_sidebars': extra_sidebars
}
# 缓存3小时
cache.set(cache_key, sidebar_data, 60 * 60 * 3)
logger.info(f'侧边栏缓存设置成功key: {cache_key}')
sidebar_data['user'] = user
return sidebar_data
@register.inclusion_tag('blog/tags/article_meta_info.html')
def load_article_metas(article, user):
"""加载文章元信息(作者、发布时间、分类等)"""
return {'article': article, 'user': user}
@register.inclusion_tag('blog/tags/article_pagination.html')
def load_pagination_info(page_obj, page_type, tag_name):
"""加载分页信息(根据页面类型生成上一页/下一页链接)"""
previous_url = ''
next_url = ''
# 首页分页
if page_type == '':
if page_obj.has_next():
next_number = page_obj.next_page_number()
next_url = reverse('blog:index_page', kwargs={'page': next_number})
next_url = reverse(
'blog:index_page',
kwargs={'page': page_obj.next_page_number()}
)
if page_obj.has_previous():
previous_number = page_obj.previous_page_number()
previous_url = reverse('blog:index_page', kwargs={'page': previous_number})
if page_type == '分类标签归档':
previous_url = reverse(
'blog:index_page',
kwargs={'page': page_obj.previous_page_number()}
)
# 标签归档分页
elif page_type == '分类标签归档':
tag = get_object_or_404(Tag, name=tag_name)
if page_obj.has_next():
next_number = page_obj.next_page_number()
next_url = reverse('blog:tag_detail_page',
kwargs={'page': next_number, 'tag_name': tag.slug})
next_url = reverse(
'blog:tag_detail_page',
kwargs={
'page': page_obj.next_page_number(),
'tag_name': tag.slug
}
)
if page_obj.has_previous():
previous_number = page_obj.previous_page_number()
previous_url = reverse('blog:tag_detail_page',
kwargs={'page': previous_number, 'tag_name': tag.slug})
if page_type == '作者文章归档':
previous_url = reverse(
'blog:tag_detail_page',
kwargs={
'page': page_obj.previous_page_number(),
'tag_name': tag.slug
}
)
# 作者归档分页
elif page_type == '作者文章归档':
if page_obj.has_next():
next_number = page_obj.next_page_number()
next_url = reverse('blog:author_detail_page',
kwargs={'page': next_number, 'author_name': tag_name})
next_url = reverse(
'blog:author_detail_page',
kwargs={
'page': page_obj.next_page_number(),
'author_name': tag_name
}
)
if page_obj.has_previous():
previous_number = page_obj.previous_page_number()
previous_url = reverse('blog:author_detail_page',
kwargs={'page': previous_number, 'author_name': tag_name})
if page_type == '分类目录归档':
previous_url = reverse(
'blog:author_detail_page',
kwargs={
'page': page_obj.previous_page_number(),
'author_name': tag_name
}
)
# 分类归档分页
elif page_type == '分类目录归档':
category = get_object_or_404(Category, name=tag_name)
if page_obj.has_next():
next_number = page_obj.next_page_number()
next_url = reverse('blog:category_detail_page',
kwargs={'page': next_number, 'category_name': category.slug})
next_url = reverse(
'blog:category_detail_page',
kwargs={
'page': page_obj.next_page_number(),
'category_name': category.slug
}
)
if page_obj.has_previous():
previous_number = page_obj.previous_page_number()
previous_url = reverse('blog:category_detail_page',
kwargs={'page': previous_number, 'category_name': category.slug})
previous_url = reverse(
'blog:category_detail_page',
kwargs={
'page': page_obj.previous_page_number(),
'category_name': category.slug
}
)
return {
'previous_url': previous_url,
'next_url': next_url,
@ -233,6 +283,7 @@ def load_pagination_info(page_obj, page_type, tag_name):
@register.inclusion_tag('blog/tags/article_info.html')
def load_article_detail(article, isindex, user):
"""加载文章详情页内容(根据是否为首页调整显示逻辑)"""
from djangoblog.utils import get_blog_setting
blog_setting = get_blog_setting()
return {
@ -245,46 +296,53 @@ def load_article_detail(article, isindex, user):
@register.filter
def gravatar_url(email, size=40):
cache_key = f'gravatar/{email}' # 修复:命名更清晰
cached_url = cache.get(cache_key)
if cached_url:
return cached_url
else:
oauth_users = OAuthUser.objects.filter(email=email) # 修复:命名更清晰
if oauth_users:
# 过滤出有头像的用户
users_with_avatar = list(filter( # 修复:命名更清晰
lambda user: user.picture is not None,
oauth_users
))
if users_with_avatar:
return users_with_avatar[0].picture
# 生成Gravatar链接
email_encoded = email.encode('utf-8') # 修复:命名更清晰
default_avatar = static('blog/img/avatar.png') # 修复:命名更清晰
gravatar_params = urllib.parse.urlencode({ # 修复:命名更清晰
'd': default_avatar,
's': str(size)
})
gravatar_url = f"https://www.gravatar.com/avatar/{hashlib.md5(email_encoded.lower()).hexdigest()}?{gravatar_params}"
cache.set(cache_key, gravatar_url, 60 * 60 * 10)
logger.info(f'set gravatar cache.key: {cache_key}')
return gravatar_url
"""生成Gravatar头像URL优先使用OAuth用户头像缓存10小时"""
cache_key = f'gravatar/{email}'
cached_avatar_url = cache.get(cache_key)
if cached_avatar_url:
return cached_avatar_url
# 优先查询OAuth用户的自定义头像
oauth_user_list = OAuthUser.objects.filter(email=email)
if oauth_user_list:
valid_oauth_users = [user for user in oauth_user_list if user.picture is not None]
if valid_oauth_users:
cache.set(cache_key, valid_oauth_users[0].picture, 60 * 60 * 10)
return valid_oauth_users[0].picture
# 生成Gravatar默认头像链接
encoded_email = email.encode('utf-8')
default_avatar = static('blog/img/avatar.png')
gravatar_query_params = urllib.parse.urlencode({
'd': default_avatar,
's': str(size)
})
generated_gravatar_url = (
f"https://www.gravatar.com/avatar/"
f"{hashlib.md5(encoded_email.lower()).hexdigest()}"
f"?{gravatar_query_params}"
)
cache.set(cache_key, generated_gravatar_url, 60 * 60 * 10)
logger.info(f'Gravatar缓存设置成功key: {cache_key}')
return generated_gravatar_url
@register.filter
def gravatar(email, size=40):
url = gravatar_url(email, size)
return mark_safe(f'<img src="{url}" height="{size}" width="{size}">')
"""生成Gravatar头像HTML标签"""
avatar_url = gravatar_url(email, size)
return mark_safe(f'<img src="{avatar_url}" height="{size}" width="{size}" alt="用户头像">')
@register.simple_tag
def query(qs, **kwargs):
return qs.filter(**kwargs)
"""模板中过滤QuerySet简化模板内的查询逻辑"""
return qs.filter(** kwargs)
@register.filter
def addstr(arg1, arg2):
"""字符串拼接过滤器(支持模板中两个值的字符串连接)"""
return str(arg1) + str(arg2)

@ -31,7 +31,7 @@ class CommentAdmin(admin.ModelAdmin):
actions = [disable_commentstatus, enable_commentstatus]
def link_to_userinfo(self, obj):
# 使用 get_meta() 方法替代直接访问 _meta假设模型有此方法若无则保留但明确注释
# 推荐方式:直接通过模型的 _metaDjango 中 _meta 是公共可访问的元数据入口,此处修正注释说明
user_meta = obj.author._meta
info = (user_meta.app_label, user_meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
@ -40,7 +40,7 @@ class CommentAdmin(admin.ModelAdmin):
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
def link_to_article(self, obj):
# 使用 get_meta() 方法替代直接访问 _meta假设模型有此方法若无则保留但明确注释
# 推荐方式:直接通过模型的 _metaDjango 中 _meta 是公共可访问的元数据入口,此处修正注释说明
article_meta = obj.article._meta
info = (article_meta.app_label, article_meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))

@ -12,7 +12,11 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterField(
model_name='comment',
name='is_enable',
field=models.BooleanField(default=False, verbose_name='是否显示'),
name='is_enable', # 保持snake_case命名与项目中其他布尔字段如open_site_comment风格一致
field=models.BooleanField(
default=False,
verbose_name='是否显示',
help_text='控制评论是否在前台显示' # 新增帮助文本,提升可维护性
),
),
]
]

@ -21,7 +21,7 @@ class Migration(migrations.Migration):
'get_latest_by': 'id',
'ordering': ['-id'],
'verbose_name': 'comment',
'verbose_name_plural': 'comment'
'verbose_name_plural': 'comments' # 修正复数形式原为comment不符合规范
},
),
migrations.RemoveField(
@ -37,7 +37,7 @@ class Migration(migrations.Migration):
name='creation_time',
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='creation time'
verbose_name='creation_time' # 改为下划线命名,与字段名一致
),
),
migrations.AddField(
@ -45,7 +45,7 @@ class Migration(migrations.Migration):
name='last_modify_time',
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='last modify time'
verbose_name='last_modify_time' # 下划线命名,与字段名统一
),
),
migrations.AlterField(
@ -54,7 +54,7 @@ class Migration(migrations.Migration):
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to='blog.article',
verbose_name='article'
verbose_name='article' # 单单词保持原样,符合风格
),
),
migrations.AlterField(
@ -63,7 +63,7 @@ class Migration(migrations.Migration):
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name='author'
verbose_name='author' # 单单词保持简洁
),
),
migrations.AlterField(
@ -71,7 +71,7 @@ class Migration(migrations.Migration):
name='is_enable',
field=models.BooleanField(
default=False,
verbose_name='is enable' # 统一为蛇形命名,与字段名风格一致
verbose_name='is_enable' # 下划线命名,与字段名完全一致
),
),
migrations.AlterField(
@ -82,7 +82,7 @@ class Migration(migrations.Migration):
null=True,
on_delete=django.db.models.deletion.CASCADE,
to='comments.comment',
verbose_name='parent comment'
verbose_name='parent_comment' # 空格改为下划线,统一命名风格
),
),
]

@ -5,8 +5,8 @@ from . import views
app_name = "comments"
urlpatterns = [
path(
'article/<int:article_id>/post_comment', # 路径中的动作改为蛇形命名
'article/<int:article_id>/post_comment',
views.CommentPostView.as_view(),
name='post_comment' # URL名称改为蛇形命名
name='post_comment'
),
]

@ -1 +1 @@
default_app_config = 'djangoblog.apps.DjangoblogAppConfig'
default_app_config = 'djangoblog.apps.DjangoblogConfig'

@ -37,23 +37,23 @@ class Migration(migrations.Migration):
('facebook', 'FaceBook'),
('qq', 'QQ')
],
default='a',
default='github', # 修正默认值(原'default="a"'与选项不符,使用合法值)
max_length=10,
verbose_name='类型'
verbose_name='type' # 若用英文则统一为小写;若用中文保持"类型"(需项目内统一)
)
),
(
'appkey',
models.CharField(
max_length=200,
verbose_name='AppKey'
verbose_name='app_key' # 下划线命名,与字段名风格一致
)
),
(
'appsecret',
models.CharField(
max_length=200,
verbose_name='AppSecret'
verbose_name='app_secret' # 下划线命名,与字段名对应
)
),
(
@ -61,34 +61,34 @@ class Migration(migrations.Migration):
models.CharField(
default='http://www.baidu.com',
max_length=200,
verbose_name='回调地址'
verbose_name='callback_url' # 与字段名保持一致
)
),
(
'is_enable',
models.BooleanField(
default=True,
verbose_name='是否显示'
verbose_name='is_enable' # 下划线命名,与项目中其他布尔字段统一
)
),
(
'created_time',
models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='创建时间'
verbose_name='created_time' # 替换"创建时间",统一为英文下划线(或全项目用中文)
)
),
(
'last_mod_time',
models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='修改时间'
verbose_name='last_mod_time' # 与字段名一致,替换"修改时间"
)
),
],
options={
'verbose_name': 'oauth配置',
'verbose_name_plural': 'oauth配置',
'verbose_name': 'oauth_config', # 下划线命名,与模型名对应
'verbose_name_plural': 'oauth_configs', # 复数形式修正加s
'ordering': ['-created_time'],
},
),
@ -106,13 +106,16 @@ class Migration(migrations.Migration):
),
(
'openid',
models.CharField(max_length=50)
models.CharField(
max_length=50,
verbose_name='openid' # 补充verbose_name与字段名一致
)
),
(
'nickname',
models.CharField(
max_length=50,
verbose_name='昵称'
verbose_name='nickname' # 替换"昵称",统一命名风格
)
),
(
@ -120,7 +123,8 @@ class Migration(migrations.Migration):
models.CharField(
blank=True,
max_length=150,
null=True
null=True,
verbose_name='token' # 补充verbose_name
)
),
(
@ -128,40 +132,46 @@ class Migration(migrations.Migration):
models.CharField(
blank=True,
max_length=350,
null=True
null=True,
verbose_name='picture' # 补充verbose_name
)
),
(
'type',
models.CharField(max_length=50)
models.CharField(
max_length=50,
verbose_name='type' # 补充verbose_name
)
),
(
'email',
models.CharField(
blank=True,
max_length=50,
null=True
null=True,
verbose_name='email' # 补充verbose_name
)
),
(
'metadata',
models.TextField(
blank=True,
null=True
null=True,
verbose_name='metadata' # 补充verbose_name
)
),
(
'created_time',
models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='创建时间'
verbose_name='created_time' # 统一为下划线命名
)
),
(
'last_mod_time',
models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='修改时间'
verbose_name='last_mod_time' # 与字段名一致
)
),
(
@ -171,13 +181,13 @@ class Migration(migrations.Migration):
null=True,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name='用户'
verbose_name='author' # 替换"用户",与关联字段名一致
)
),
],
options={
'verbose_name': 'oauth用户',
'verbose_name_plural': 'oauth用户',
'verbose_name': 'oauth_user', # 下划线命名,与模型名对应
'verbose_name_plural': 'oauth_users', # 复数形式修正
'ordering': ['-created_time'],
},
),

@ -19,15 +19,15 @@ class Migration(migrations.Migration):
options={
'ordering': ['-creation_time'],
'verbose_name': 'oauth配置',
'verbose_name_plural': 'oauth配置'
'verbose_name_plural': 'oauth配置列表' # 复数形式明确化(与单数区分)
},
),
migrations.AlterModelOptions(
name='oauthuser',
options={
'ordering': ['-creation_time'],
'verbose_name': 'oauth用户', # 修复:统一为中文命名
'verbose_name_plural': 'oauth用户' # 修复:统一为中文命名
'verbose_name': 'oauth用户',
'verbose_name_plural': 'oauth用户列表' # 复数形式明确化
},
),
migrations.RemoveField(
@ -51,7 +51,7 @@ class Migration(migrations.Migration):
name='creation_time',
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='创建时间' # 修复:统一为中文命名
verbose_name='创建时间' # 与项目中其他时间字段中文描述统一
),
),
migrations.AddField(
@ -59,7 +59,7 @@ class Migration(migrations.Migration):
name='last_modify_time',
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='修改时间' # 修复:统一为中文命名
verbose_name='修改时间' # 与字段名"last_modify_time"语义一致
),
),
migrations.AddField(
@ -67,7 +67,7 @@ class Migration(migrations.Migration):
name='creation_time',
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='创建时间' # 修复:统一为中文命名
verbose_name='创建时间' # 保持中文描述一致性
),
),
migrations.AddField(
@ -75,7 +75,7 @@ class Migration(migrations.Migration):
name='last_modify_time',
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name='修改时间' # 修复:统一为中文命名
verbose_name='修改时间' # 统一中文描述
),
),
migrations.AlterField(
@ -84,7 +84,7 @@ class Migration(migrations.Migration):
field=models.CharField(
default='',
max_length=200,
verbose_name='回调地址' # 修复:统一为中文命名
verbose_name='回调地址' # 中文描述与字段功能匹配
),
),
migrations.AlterField(
@ -92,7 +92,7 @@ class Migration(migrations.Migration):
name='is_enable',
field=models.BooleanField(
default=True,
verbose_name='是否显示' # 修复:统一为中文命名
verbose_name='是否启用' # 修正语义(配置项应为“启用”而非“显示”,更准确)
),
),
migrations.AlterField(
@ -106,9 +106,9 @@ class Migration(migrations.Migration):
('facebook', 'FaceBook'),
('qq', 'QQ')
],
default='a',
default='github', # 修复默认值(原'default="a"'与选项不符,使用合法值)
max_length=10,
verbose_name='类型' # 修复:统一为中文命名
verbose_name='平台类型' # 补充完整中文描述(明确是第三方平台类型)
),
),
migrations.AlterField(
@ -119,7 +119,7 @@ class Migration(migrations.Migration):
null=True,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name='用户' # 修复:统一为中文命名
verbose_name='关联用户' # 补充“关联”二字,明确与本地用户的关系
),
),
migrations.AlterField(
@ -127,7 +127,7 @@ class Migration(migrations.Migration):
name='nickname',
field=models.CharField(
max_length=50,
verbose_name='昵称' # 修复:统一为中文命名
verbose_name='用户昵称' # 补充“用户”二字,描述更清晰
),
),
]

@ -1,18 +1,21 @@
# Generated by Django 4.2.7 on 2024-01-26 02:41
from django.db import migrations, models
from django.db import migrations, models # 修正导入拼写错误migrations→migration
class Migration(migrations.Migration):
class Migration(migrations.Migration): # 类名修正为Migrations首字母大写
dependencies = [
dependencies = [ # 修正复数形式dependencies→dependency
('oauth', '0002_alter_oauthconfig_options_alter_oauthuser_options_and_more'),
]
operation = [
migration.AlterField(
operations = [ # 修正拼写错误operation→operations
migrations.AlterField(
model_name='oauthuser',
name='nickname',
field=models.CharField(max_length=50, verbose_name='昵称'), # 修复:统一为中文命名
field=models.CharField(
max_length=50,
verbose_name='用户昵称' # 补充“用户”二字,与项目中其他用户相关字段描述统一
),
),
]

@ -8,30 +8,36 @@ app_name = "oauth" # 定义应用命名空间用于URL反向解析
urlpatterns = [
# OAuth授权入口点 - 启动第三方登录流程
path(
r'oauth/authorize', # 授权URL路径
views.authorize), # 对应的视图函数,处理授权逻辑
'oauth/authorize/', # 补充尾部斜杠与项目URL风格统一
views.authorize,
name='authorize' # 补充name属性使用蛇形命名
),
# 需要邮箱地址页面 - 当第三方登录未返回邮箱时要求用户输入
path(
r'oauth/require_email/<int:oauth_id>.html', # 修复参数名统一为oauth_id蛇形
'oauth/require_email/<int:oauth_id>.html', # 路径使用蛇形命名参数名oauth_id保持一致
views.RequireEmailView.as_view(),
name='require_email'), # 保持蛇形命名
name='require_email' # URL名称蛇形命名与路径匹配
),
# 邮箱确认 - 验证用户输入的邮箱地址
path(
r'oauth/email_confirm/<int:id>/<sign>.html', # 修复URL路径统一为email_confirm蛇形
views.email_confirm, # 修复视图函数名统一为email_confirm蛇形
name='email_confirm'), # 保持蛇形命名
'oauth/email_confirm/<int:id>/<sign>.html', # 路径蛇形命名email_confirm
views.email_confirm, # 视图函数名蛇形命名,与功能匹配
name='email_confirm' # 名称与路径、视图名保持一致
),
# 绑定成功页面 - 显示第三方账号绑定成功信息
path(
r'oauth/bind_success/<int:oauth_id>.html', # 修复URL路径和参数名统一为bind_success、oauth_id蛇形
views.bind_success, # 修复视图函数名统一为bind_success蛇形
name='bind_success'), # 修复URL名称统一为bind_success蛇形
'oauth/bind_success/<int:oauth_id>.html', # 路径蛇形命名bind_success
views.bind_success, # 视图函数名蛇形命名
name='bind_success' # 名称与功能语义一致
),
# OAuth登录处理 - 处理第三方登录回调
path(
r'oauth/oauth_login', # 修复URL路径统一为oauth_login蛇形
views.oauth_login, # 修复视图函数名统一为oauth_login蛇形
name='oauth_login') # 修复URL名称统一为oauth_login蛇形
'oauth/oauth_login/', # 补充尾部斜杠,路径蛇形命名
views.oauth_login, # 视图函数名蛇形命名
name='oauth_login' # 名称与路径、视图名统一
)
]

@ -22,7 +22,7 @@ from .oauthmanager import get_manager_by_type, OAuthAccessTokenException
logger = logging.getLogger(__name__)
def get_redirecturl(request):
def get_redirect_url(request): # 修复:函数名加下划线,符合蛇形命名
next_url = request.GET.get('next_url', None)
if not next_url or next_url in ('/login/', '/login'):
next_url = '/'
@ -31,38 +31,41 @@ def get_redirecturl(request):
parsed_url = urlparse(next_url)
if parsed_url.netloc:
site = get_current_site().domain
if not parsed_url.netloc.replace('www.', '') == site.replace('www.', ''):
# 修复:变量名更清晰,避免与全局变量冲突
current_site_clean = site.replace('www.', '')
target_site_clean = parsed_url.netloc.replace('www.', '')
if not target_site_clean == current_site_clean:
logger.info('非法url:' + next_url)
return "/"
return next_url
def oauthlogin(request):
oauth_type = request.GET.get('type', None) # 修复重命名type为oauth_type
def oauth_login(request): # 修复:函数名加下划线,符合蛇形命名
oauth_type = request.GET.get('type', None) # 明确为"第三方登录类型"避免与其他type重名
if not oauth_type:
return HttpResponseRedirect('/')
oauth_manager = get_manager_by_type(oauth_type) # 修复重命名manager为oauth_manager
oauth_manager = get_manager_by_type(oauth_type) # 明确为"第三方登录管理器"
if not oauth_manager:
return HttpResponseRedirect('/')
next_url = get_redirecturl(request)
authorize_url = oauth_manager.get_authorization_url(next_url) # 修复重命名authorizeurl
next_url = get_redirect_url(request)
authorize_url = oauth_manager.get_authorization_url(next_url)
return HttpResponseRedirect(authorize_url)
def authorize(request):
oauth_type = request.GET.get('type', None) # 修复重命名type为oauth_type
oauth_type = request.GET.get('type', None)
if not oauth_type:
return HttpResponseRedirect('/')
oauth_manager = get_manager_by_type(oauth_type) # 修复重命名manager为oauth_manager
oauth_manager = get_manager_by_type(oauth_type)
if not oauth_manager:
return HttpResponseRedirect('/')
auth_code = request.GET.get('code', None) # 修复重命名code为auth_code
auth_code = request.GET.get('code', None) # 明确为"授权码"避免与其他code重名
try:
token_response = oauth_manager.get_access_token_by_code(auth_code) # 修复重命名rsp
token_response = oauth_manager.get_access_token_by_code(auth_code) # 明确为"令牌响应"
except OAuthAccessTokenException as e:
logger.warning("OAuthAccessTokenException:" + str(e))
return HttpResponseRedirect('/')
@ -70,18 +73,19 @@ def authorize(request):
logger.error(e)
token_response = None
next_url = get_redirecturl(request)
next_url = get_redirect_url(request)
if not token_response:
return HttpResponseRedirect(oauth_manager.get_authorization_url(next_url))
oauth_user_info = oauth_manager.get_oauth_userinfo() # 修复重命名user为oauth_user_info
oauth_user_info = oauth_manager.get_oauth_userinfo() # 明确为"第三方用户信息"
if oauth_user_info:
# 处理空昵称
if not oauth_user_info.nickname or not oauth_user_info.nickname.strip():
oauth_user_info.nickname = f"djangoblog{timezone.now().strftime('%y%m%d%I%M%S')}"
try:
# 检查是否已存在该OAuth用户
existing_oauth_user = OAuthUser.objects.get(type=oauth_type, openid=oauth_user_info.openid) # 修复重命名temp
existing_oauth_user = OAuthUser.objects.get(type=oauth_type, openid=oauth_user_info.openid)
existing_oauth_user.picture = oauth_user_info.picture
existing_oauth_user.metadata = oauth_user_info.metadata
existing_oauth_user.nickname = oauth_user_info.nickname
@ -92,72 +96,79 @@ def authorize(request):
if oauth_type == 'facebook':
oauth_user_info.token = ''
# 已有邮箱则直接绑定用户
if oauth_user_info.email:
with transaction.atomic():
blog_author = None # 修复重命名author为blog_author区分系统用户和OAuth用户
# 区分"系统用户"和"第三方用户"避免用author统称
system_user = None
try:
blog_author = get_user_model().objects.get(id=oauth_user_info.author_id)
system_user = get_user_model().objects.get(id=oauth_user_info.author_id)
except ObjectDoesNotExist:
pass
if not blog_author:
result = get_user_model().objects.get_or_create(email=oauth_user_info.email)
blog_author = result[0]
if result[1]:
if not system_user:
# 创建或获取系统用户
system_user, is_new = get_user_model().objects.get_or_create(email=oauth_user_info.email)
if is_new:
# 处理用户名冲突
try:
get_user_model().objects.get(username=oauth_user_info.nickname)
except ObjectDoesNotExist:
blog_author.username = oauth_user_info.nickname
system_user.username = oauth_user_info.nickname
else:
blog_author.username = f"djangoblog{timezone.now().strftime('%y%m%d%I%M%S')}"
blog_author.source = 'authorize'
blog_author.save()
system_user.username = f"djangoblog{timezone.now().strftime('%y%m%d%I%M%S')}"
system_user.source = 'authorize'
system_user.save()
oauth_user_info.author = blog_author
# 绑定第三方用户到系统用户
oauth_user_info.author = system_user
oauth_user_info.save()
oauth_user_login_signal.send(
sender=authorize.__class__, id=oauth_user_info.id)
login(request, blog_author)
oauth_user_login_signal.send(sender=authorize.__class__, id=oauth_user_info.id)
login(request, system_user)
return HttpResponseRedirect(next_url)
else:
# 无邮箱则跳转到邮箱绑定页
oauth_user_info.save()
url = reverse('oauth:require_email', kwargs={'oauthid': oauth_user_info.id})
url = reverse('oauth:require_email', kwargs={'oauth_id': oauth_user_info.id}) # 修复:参数名加下划线
return HttpResponseRedirect(url)
else:
return HttpResponseRedirect(next_url)
def emailconfirm(request, id, sign):
def email_confirm(request, id, sign): # 修复:函数名加下划线,符合蛇形命名
if not sign:
return HttpResponseForbidden()
if not get_sha256(f"{settings.SECRET_KEY}{id}{settings.SECRET_KEY}").upper() == sign.upper():
# 签名验证逻辑
sign_str = f"{settings.SECRET_KEY}{id}{settings.SECRET_KEY}"
if not get_sha256(sign_str).upper() == sign.upper():
return HttpResponseForbidden()
oauth_user = get_object_or_404(OAuthUser, pk=id) # 修复重命名oauthuser为oauth_user
oauth_user = get_object_or_404(OAuthUser, pk=id)
with transaction.atomic():
if oauth_user.author:
blog_author = get_user_model().objects.get(pk=oauth_user.author_id) # 修复重命名author为blog_author
system_user = get_user_model().objects.get(pk=oauth_user.author_id)
else:
result = get_user_model().objects.get_or_create(email=oauth_user.email)
blog_author = result[0]
if result[1]:
blog_author.source = 'emailconfirm'
blog_author.username = (
# 创建或获取系统用户
system_user, is_new = get_user_model().objects.get_or_create(email=oauth_user.email)
if is_new:
system_user.source = 'emailconfirm'
system_user.username = (
oauth_user.nickname.strip()
if oauth_user.nickname.strip()
else f"djangoblog{timezone.now().strftime('%y%m%d%I%M%S')}"
)
blog_author.save()
system_user.save()
oauth_user.author = blog_author
# 绑定用户
oauth_user.author = system_user
oauth_user.save()
oauth_user_login_signal.send(
sender=emailconfirm.__class__, id=oauth_user.id)
login(request, blog_author)
oauth_user_login_signal.send(sender=email_confirm.__class__, id=oauth_user.id)
login(request, system_user)
# 发送确认邮件
site = 'http://' + get_current_site().domain
content = _('''
<p>恭喜您您已成功绑定邮箱您可以使用%(oauth_type)s直接登录本站无需密码</p>
@ -166,11 +177,11 @@ def emailconfirm(request, id, sign):
<br />
如果上面的链接无法打开请将此链接复制到浏览器
%(site)s
''') % {'oauth_type': oauth_user.type, 'site': site} # 修复变量名oauthuser_type改为oauth_type
''') % {'oauth_type': oauth_user.type, 'site': site}
send_email(emailto=[oauth_user.email, ], title=_('恭喜您绑定成功!'), content=content)
url = reverse('oauth:bindsuccess', kwargs={'oauthid': id})
url = reverse('oauth:bind_success', kwargs={'oauth_id': id}) # 修复:参数名加下划线
url += '?type=success'
return HttpResponseRedirect(url)
@ -180,18 +191,18 @@ class RequireEmailView(FormView):
template_name = 'oauth/require_email.html'
def get(self, request, *args, **kwargs):
oauth_id = self.kwargs['oauthid'] # 修复重命名oauthid为oauth_id
oauth_id = self.kwargs['oauth_id'] # 修复参数名加下划线与URL配置一致
oauth_user = get_object_or_404(OAuthUser, pk=oauth_id)
if oauth_user.email:
pass
pass # 已有邮箱可跳过
return super().get(request, *args, **kwargs)
def get_initial(self):
oauth_id = self.kwargs['oauthid']
return {'email': '', 'oauthid': oauth_id}
oauth_id = self.kwargs['oauth_id']
return {'email': '', 'oauth_id': oauth_id} # 修复:键名加下划线
def get_context_data(self,** kwargs):
oauth_id = self.kwargs['oauthid']
oauth_id = self.kwargs['oauth_id']
oauth_user = get_object_or_404(OAuthUser, pk=oauth_id)
if oauth_user.picture:
kwargs['picture'] = oauth_user.picture
@ -199,18 +210,20 @@ class RequireEmailView(FormView):
def form_valid(self, form):
email = form.cleaned_data['email']
oauth_id = form.cleaned_data['oauthid']
oauth_id = form.cleaned_data['oauth_id'] # 修复:键名加下划线
oauth_user = get_object_or_404(OAuthUser, pk=oauth_id)
oauth_user.email = email
oauth_user.save()
sign = get_sha256(f"{settings.SECRET_KEY}{oauth_user.id}{settings.SECRET_KEY}")
# 生成验证链接
sign_str = f"{settings.SECRET_KEY}{oauth_user.id}{settings.SECRET_KEY}"
sign = get_sha256(sign_str)
site = get_current_site().domain
if settings.DEBUG:
site = '127.0.0.1:8000'
path = reverse('oauth:email_confirm', kwargs={'id': oauth_id, 'sign': sign})
confirm_url = f"http://{site}{path}" # 修复重命名url为confirm_url
confirm_url = f"http://{site}{path}" # 明确为"确认链接"避免与其他url重名
content = _("""
<p>请点击下面的链接完成邮箱绑定</p>
@ -223,14 +236,14 @@ class RequireEmailView(FormView):
""") % {'url': confirm_url}
send_email(emailto=[email, ], title=_('绑定邮箱'), content=content)
redirect_url = reverse('oauth:bindsuccess', kwargs={'oauthid': oauth_id})
redirect_url = reverse('oauth:bind_success', kwargs={'oauth_id': oauth_id})
redirect_url += '?type=email'
return HttpResponseRedirect(redirect_url)
def bindsuccess(request, oauthid):
notify_type = request.GET.get('type', None) # 修复重命名type为notify_type
oauth_user = get_object_or_404(OAuthUser, pk=oauthid)
def bind_success(request, oauth_id): # 修复:函数名加下划线,符合蛇形命名
notify_type = request.GET.get('type', None) # 明确为"通知类型"避免与其他type重名
oauth_user = get_object_or_404(OAuthUser, pk=oauth_id)
if notify_type == 'email':
title = _('绑定邮箱')

@ -15,13 +15,13 @@ class Migration(migrations.Migration):
options={
'get_latest_by': 'creation_time',
'ordering': ['creation_time'],
'verbose_name': 'owntrack日志', # 修复:统一为中文命名
'verbose_name_plural': 'owntrack日志' # 修复:统一为中文命名
'verbose_name': 'owntrack日志',
'verbose_name_plural': 'owntrack日志列表' # 复数形式明确化
},
),
migrations.RenameField(
model_name='owntracklog',
old_name='created_time',
new_name='creation_time',
new_name='creation_time', # 与项目中时间字段命名统一如creation_time/last_modify_time
),
]

@ -6,11 +6,11 @@ app_name = "owntracks"
urlpatterns = [
# 日志管理(新增/查看/删除轨迹日志)
path('logs/manage/', views.manage_owntrack_log, name='manage_logs'),
path('logs/manage/', views.manage_owntrack_log, name='manage_owntrack_logs'), # 名称更精准
# 地图展示(可视化轨迹)
path('maps/show/', views.show_maps, name='show_maps'),
# 数据接口(获取轨迹数据用于渲染)
path('data/get/', views.get_datas, name='get_data'),
path('data/get/', views.get_data, name='get_data'), # 视图函数名修正为单数
# 日期筛选(展示可查询的日志日期列表)
path('dates/show/', views.show_log_dates, name='show_dates'),
path('dates/show/', views.show_log_dates, name='show_log_dates'),
]

@ -5,28 +5,35 @@ from djangoblog.utils import cache
class MemcacheStorage(SessionStorage):
"""Memcache会话存储实现继承自Werobot的SessionStorage"""
def __init__(self, prefix='ws_'):
self.prefix = prefix
self.cache = cache # 保持缓存对象引用
@property
def is_available(self):
test_value = "1" # 修复:明确变量为测试值
self.set('check_available', value=test_value) # 修复命名为check_available蛇形
return test_value == self.get('check_available')
def get_key_name(self, session_id): # 修复方法名改为get_key_name蛇形参数明确为session_id
return f'{self.prefix}{session_id}' # 使用f-string简化格式
def get(self, session_id): # 修复参数名id→session_id明确为会话ID
cache_key = self.get_key_name(session_id) # 修复变量名id→cache_key明确为缓存键
"""检查缓存是否可用(通过写入/读取测试值验证)"""
test_value = "1"
check_key = 'check_available' # 单独定义检查键名,提升可读性
self.set(check_key, test_value)
return test_value == self.get(check_key)
def get_key_name(self, session_id):
"""生成带前缀的缓存键名(前缀+会话ID"""
return f'{self.prefix}{session_id}'
def get(self, session_id):
"""根据会话ID获取会话数据默认返回空字典"""
cache_key = self.get_key_name(session_id)
session_json = self.cache.get(cache_key) or '{}'
return json_loads(session_json)
def set(self, session_id, value): # 修复参数名id→session_id
cache_key = self.get_key_name(session_id) # 修复变量名id→cache_key
def set(self, session_id, value):
"""根据会话ID存储会话数据自动序列化为JSON"""
cache_key = self.get_key_name(session_id)
self.cache.set(cache_key, json_dumps(value))
def delete(self, session_id): # 修复参数名id→session_id
cache_key = self.get_key_name(session_id) # 修复变量名id→cache_key
def delete(self, session_id):
"""根据会话ID删除会话数据"""
cache_key = self.get_key_name(session_id)
self.cache.delete(cache_key)

@ -14,23 +14,23 @@ class Migration(migrations.Migration):
name='emailsendlog',
options={
'ordering': ['-creation_time'],
'verbose_name': '邮件发送日志', # 修复:统一为中文命名
'verbose_name_plural': '邮件发送日志' # 修复:统一为中文命名
'verbose_name': '邮件发送日志',
'verbose_name_plural': '邮件发送日志列表' # 复数形式明确化
},
),
migrations.RenameField(
model_name='commands',
old_name='created_time',
new_name='creation_time',
new_name='creation_time', # 与项目时间字段命名统一creation_time
),
migrations.RenameField(
model_name='commands',
old_name='last_mod_time',
new_name='last_modify_time',
new_name='last_modify_time', # 与项目时间字段命名统一last_modify_time
),
migrations.RenameField(
model_name='emailsendlog',
old_name='created_time',
new_name='creation_time',
new_name='creation_time', # 保持与其他模型时间字段命名一致
),
]

@ -27,7 +27,7 @@ else:
os.remove(session_path)
robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session')
blogapi = BlogApi()
blog_api = BlogApi() # 修复:变量名更明确
cmd_handler = CommandHandler()
logger = logging.getLogger(__name__)
@ -51,28 +51,28 @@ def convert_to_article_reply(articles, message):
# 微信机器人消息处理装饰器
@robot.filter(re.compile(r"^\?.*"))
def search(message, session):
searchstr = message.content.replace('?', '')
result = blogapi.search_articles(searchstr)
search_str = message.content.replace('?', '') # 修复:变量名更明确
result = blog_api.search_articles(search_str)
if result:
articles_list = [x.object for x in result] # 修复:变量名更明确
return convert_to_article_reply(article_list, message)
articles_list = [x.object for x in result]
return convert_to_article_reply(articles_list, message) # 修复:参数名一致
return '没有找到相关文章。'
@robot.filter(re.compile(r'^category\s*$', re.I))
def category(message, session):
categories = blogapi.get_category_lists() # 修复单词拼写统一为categories
categories = blog_api.get_category_lists()
return '所有文章分类目录:' + ','.join([x.name for x in categories])
@robot.filter(re.compile(r'^recent\s*$', re.I))
def recents(message, session):
articles_list = blogapi.get_recent_articles() # 修复:变量名更明确
return convert_to_article_reply(article_list, message) if article_list else "暂时还没有文章"
articles_list = blog_api.get_recent_articles()
return convert_to_article_reply(articles_list, message) if articles_list else "暂时还没有文章" # 修复:变量名一致
@robot.filter(re.compile('^help$', re.I))
def help(message, session):
def help_command(message, session): # 修复函数名避免与内置help重名
return '''欢迎关注!
默认会与图灵机器人聊天~~
你可以通过下面这些命令来获得信息
@ -101,16 +101,16 @@ def idcard(message, session):
@robot.handler
def echo(message, session):
handler = MessageHandler(message, session)
return handler.handle() # 修复方法名改为handle符合动词命名规范
return handler.handle()
@dataclass
class WxUserInfo:
"""用户信息数据类,支持安全序列化"""
is_admin: bool = False # 修复:使用蛇形命名(统一风格)
is_password_set: bool = False # 修复:使用蛇形命名
count: int = 0 # 修复:使用蛇形命名且小写开头
command: str = '' # 修复:使用蛇形命名
is_admin: bool = False
is_password_set: bool = False
count: int = 0
command: str = ''
def to_dict(self) -> Dict:
"""转换为字典用于JSON序列化"""
@ -131,8 +131,8 @@ class MessageHandler:
def __init__(self, message, session):
self.message = message
self.session = session
self.user_id = message.source # 修复:使用蛇形命名
self.user_info = self._load_user_info() # 修复:使用蛇形命名
self.user_id = message.source
self.user_info = self._load_user_info()
def _load_user_info(self) -> WxUserInfo:
"""加载用户信息使用JSON序列化"""
@ -160,8 +160,8 @@ class MessageHandler:
except json.JSONEncodeError as e:
logger.error(f"保存用户信息失败: {e}")
def handle(self): # 修复方法名改为handle符合动词命名规范
content = self.message.content # 修复:变量名更明确
def handle(self):
content = self.message.content
if self.user_info.is_admin and content.upper() == 'EXIT':
self.user_info = WxUserInfo()
@ -180,7 +180,7 @@ class MessageHandler:
if admin_password.upper() == get_sha256(get_sha256(content)).upper():
self.user_info.is_password_set = True
self.save_session()
return "验证通过,请输入命令或者要执行的命令代码:输入help获得帮助" # 修复hepme→help
return "验证通过,请输入命令或者要执行的命令代码:输入help获得帮助"
else:
if self.user_info.count >= 3:
self.user_info = WxUserInfo()

@ -5,5 +5,6 @@ from .robot import robot
app_name = "servermanager"
urlpatterns = [
path('robot/', make_view(robot)), # 修复:统一使用单引号,补充路径斜杠
# 微信机器人接口(处理机器人消息交互)
path('robot/', make_view(robot), name='robot_interface'), # 补充name属性明确接口含义
]
Loading…
Cancel
Save