@ -18,106 +18,114 @@ logger = logging.getLogger(__name__)
class LinkShowType ( models . TextChoices ) :
I = ( ' i ' , _ ( ' index ' ) )
L = ( ' l ' , _ ( ' list ' ) )
P = ( ' p ' , _ ( ' post ' ) )
A = ( ' a ' , _ ( ' all ' ) )
S = ( ' s ' , _ ( ' slide ' ) )
# 链接显示类型选择
I = ( ' i ' , _ ( ' index ' ) ) # 首页显示
L = ( ' l ' , _ ( ' list ' ) ) # 列表页显示
P = ( ' p ' , _ ( ' post ' ) ) # 文章页面显示
A = ( ' a ' , _ ( ' all ' ) ) # 全站显示
S = ( ' s ' , _ ( ' slide ' ) ) # 幻灯片显示
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 ) # 自增主键
creation_time = models . DateTimeField ( _ ( ' creation time ' ) , default = now ) # 创建时间
last_modify_time = models . DateTimeField ( _ ( ' modify time ' ) , default = now ) # 最后修改时间
def save ( self , * args , * * kwargs ) :
# 重写保存方法,处理特殊逻辑
is_update_views = isinstance (
self ,
Article ) and ' update_fields ' in kwargs and kwargs [ ' update_fields ' ] == [ ' views ' ]
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 )
Article . objects . filter ( pk = self . pk ) . update ( views = self . views ) # 直接更新浏览量,避免触发其他逻辑
else :
if ' slug ' in self . __dict__ :
if ' slug ' in self . __dict__ : # 如果有slug字段
slug = getattr (
self , ' title ' ) if ' title ' in self . __dict__ else getattr (
self , ' name ' )
setattr ( self , ' slug ' , slugify ( slug ) )
super ( ) . save ( * args , * * kwargs )
self , ' name ' ) # 根据title或name生成slug
setattr ( self , ' slug ' , slugify ( slug ) ) # 设置slug值
super ( ) . save ( * args , * * kwargs ) # 调用父类保存方法
def get_full_url ( self ) :
site = get_current_site ( ) . domain
# 获取完整URL
site = get_current_site ( ) . domain # 获取当前站点域名
url = " https:// {site} {path} " . format ( site = site ,
path = self . get_absolute_url ( ) )
path = self . get_absolute_url ( ) ) # 构建完整URL
return url
class Meta :
abstract = True
abstract = True # 抽象基类,不会创建数据库表
@abstractmethod
def get_absolute_url ( self ) :
# 抽象方法, 子类必须实现获取绝对URL的方法
pass
class Article ( BaseModel ) :
""" 文章 """
""" 文章 模型 """
STATUS_CHOICES = (
( ' d ' , _ ( ' Draft ' ) ) ,
( ' p ' , _ ( ' Published ' ) ) ,
( ' d ' , _ ( ' Draft ' ) ) , # 草稿状态
( ' p ' , _ ( ' Published ' ) ) , # 已发布状态
)
COMMENT_STATUS = (
( ' o ' , _ ( ' Open ' ) ) ,
( ' c ' , _ ( ' Close ' ) ) ,
( ' o ' , _ ( ' Open ' ) ) , # 评论开启
( ' c ' , _ ( ' Close ' ) ) , # 评论关闭
)
TYPE = (
( ' a ' , _ ( ' Article ' ) ) ,
( ' p ' , _ ( ' Page ' ) ) ,
( ' a ' , _ ( ' Article ' ) ) , # 文章类型
( ' p ' , _ ( ' Page ' ) ) , # 页面类型
)
title = models . CharField ( _ ( ' title ' ) , max_length = 200 , unique = True )
body = MDTextField ( _ ( ' body ' ) )
title = models . CharField ( _ ( ' title ' ) , max_length = 200 , unique = True ) # 文章标题
body = MDTextField ( _ ( ' body ' ) ) # 文章正文, 使用Markdown编辑器
pub_time = models . DateTimeField (
_ ( ' publish time ' ) , blank = False , null = False , default = now )
_ ( ' publish time ' ) , blank = False , null = False , default = now ) # 发布时间
status = models . CharField (
_ ( ' status ' ) ,
max_length = 1 ,
choices = STATUS_CHOICES ,
default = ' p ' )
default = ' p ' ) # 文章状态
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 ' ) # 评论状态
type = models . CharField ( _ ( ' type ' ) , max_length = 1 , choices = TYPE , default = ' a ' ) # 文章类型
views = models . PositiveIntegerField ( _ ( ' views ' ) , default = 0 ) # 浏览量
author = models . ForeignKey (
settings . AUTH_USER_MODEL ,
verbose_name = _ ( ' author ' ) ,
blank = False ,
null = False ,
on_delete = models . CASCADE )
on_delete = models . CASCADE ) # 作者外键
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 ) # 文章排序
show_toc = models . BooleanField ( _ ( ' show toc ' ) , blank = False , null = False , default = False ) # 是否显示目录
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 ) # 分类外键
tags = models . ManyToManyField ( ' Tag ' , verbose_name = _ ( ' tag ' ) , blank = True ) # 标签多对多关系
def body_to_string ( self ) :
# 返回正文字符串
return self . body
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 ' ] # 默认排序:按文章排序倒序,发布时间倒序
verbose_name = _ ( ' article ' ) # 单数显示名称
verbose_name_plural = verbose_name # 复数显示名称
get_latest_by = ' id ' # 指定获取最新记录的字段
def get_absolute_url ( self ) :
# 获取文章绝对URL
return reverse ( ' blog:detailbyid ' , kwargs = {
' article_id ' : self . id ,
' year ' : self . creation_time . year ,
@ -125,252 +133,269 @@ class Article(BaseModel):
' day ' : self . creation_time . day
} )
@cache_decorator ( 60 * 60 * 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 ) )
# 获取分类树
tree = self . category . get_category_tree ( ) # 获取分类树
names = list ( map ( lambda c : ( c . name , c . get_absolute_url ( ) ) , tree ) ) # 转换为名称和URL的元组列表
return names
def save ( self , * args , * * kwargs ) :
# 保存文章
super ( ) . save ( * args , * * kwargs )
def viewed ( self ) :
# 增加浏览量
self . views + = 1
self . save ( update_fields = [ ' views ' ] )
self . save ( update_fields = [ ' views ' ] ) # 只更新views字段
def comment_list ( self ) :
cache_key = ' article_comments_ {id} ' . format ( id = self . id )
value = cache . get ( cache_key )
# 获取评论列表(带缓存)
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 ) )
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 ) )
comments = self . comment_set . filter ( is_enable = True ) . order_by ( ' -id ' ) # 获取有效评论并按ID倒序
cache . set ( cache_key , comments , 60 * 100 ) # 设置缓存, 有效期100分钟
logger . info ( ' set article comments: {id} ' . format ( id = self . id ) ) # 记录缓存设置日志
return comments
def get_admin_url ( self ) :
info = ( self . _meta . app_label , self . _meta . model_name )
return reverse ( ' admin: %s _ %s _change ' % info , args = ( self . pk , ) )
# 获取管理后台URL
info = ( self . _meta . app_label , self . _meta . model_name ) # 获取应用和模型信息
return reverse ( ' admin: %s _ %s _change ' % info , args = ( self . pk , ) ) # 生成管理后台编辑URL
@cache_decorator ( expiration = 60 * 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 ( )
id__gt = self . id , status = ' p ' ) . order_by ( ' id ' ) . first ( ) # 获取ID大于当前文章的第一篇已发布文章
@cache_decorator ( expiration = 60 * 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 ' ) . first ( ) # 获取ID小于当前文章的第一篇已发布文章
def get_first_image_url ( self ) :
"""
Get the first image url from article . body .
从文章正文中获取第一张图片URL
: return :
"""
match = re . search ( r ' ! \ [.*? \ ] \ ((.+?) \ ) ' , self . body )
match = re . search ( r ' ! \ [.*? \ ] \ ((.+?) \ ) ' , self . body ) # 使用正则表达式匹配Markdown图片语法
if match :
return match . group ( 1 )
return " "
return match . group ( 1 ) # 返回图片URL
return " " # 没有图片返回空字符串
class Category ( BaseModel ) :
""" 文章分类 """
name = models . CharField ( _ ( ' category name ' ) , max_length = 30 , unique = True )
""" 文章分类 模型 """
name = models . CharField ( _ ( ' category name ' ) , max_length = 30 , unique = True ) # 分类名称
parent_category = models . ForeignKey (
' self ' ,
verbose_name = _ ( ' parent category ' ) ,
blank = True ,
null = True ,
on_delete = models . CASCADE )
slug = models . SlugField ( default = ' no-slug ' , max_length = 60 , blank = True )
index = models . IntegerField ( default = 0 , verbose_name = _ ( ' index ' ) )
on_delete = models . CASCADE ) # 父级分类,自关联
slug = models . SlugField ( default = ' no-slug ' , max_length = 60 , blank = True ) # URL友好标识
index = models . IntegerField ( default = 0 , verbose_name = _ ( ' index ' ) ) # 排序索引
class Meta :
ordering = [ ' -index ' ]
verbose_name = _ ( ' category ' )
verbose_name_plural = verbose_name
ordering = [ ' -index ' ] # 默认按索引倒序排列
verbose_name = _ ( ' category ' ) # 单数显示名称
verbose_name_plural = verbose_name # 复数显示名称
def get_absolute_url ( self ) :
# 获取分类绝对URL
return reverse (
' blog:category_detail ' , kwargs = {
' category_name ' : self . slug } )
' category_name ' : self . slug } ) # 使用slug作为URL参数
def __str__ ( self ) :
# 对象字符串表示
return self . name
@cache_decorator ( 60 * 60 * 10 )
@cache_decorator ( 60 * 60 * 10 ) # 缓存10小时
def get_category_tree ( self ) :
"""
递归获得分类目录的父级
: return :
"""
categorys = [ ]
categorys = [ ] # 存储分类树
def parse ( category ) :
categorys . append ( category )
if category . parent_category :
parse ( category . parent_category )
# 递归解析分类树
categorys . append ( category ) # 添加当前分类
if category . parent_category : # 如果有父级分类
parse ( category . parent_category ) # 递归解析父级分类
parse ( self )
parse ( self ) # 从当前分类开始解析
return categorys
@cache_decorator ( 60 * 60 * 10 )
@cache_decorator ( 60 * 60 * 10 ) # 缓存10小时
def get_sub_categorys ( self ) :
"""
获得当前分类目录所有子集
: return :
"""
categorys = [ ]
all_categorys = Category . objects . all ( )
categorys = [ ] # 存储子分类
all_categorys = Category . objects . all ( ) # 获取所有分类
def parse ( category ) :
# 递归解析子分类
if category not in categorys :
categorys . append ( category )
childs = all_categorys . filter ( parent_category = category )
categorys . append ( category ) # 添加当前分类
childs = all_categorys . filter ( parent_category = category ) # 获取直接子分类
for child in childs :
if category not in categorys :
categorys . append ( child )
parse ( child )
categorys . append ( child ) # 添加子分类
parse ( child ) # 递归解析子分类的子分类
parse ( self )
parse ( self ) # 从当前分类开始解析
return categorys
class Tag ( BaseModel ) :
""" 文章标签 """
name = models . CharField ( _ ( ' tag name ' ) , max_length = 30 , unique = True )
slug = models . SlugField ( default = ' no-slug ' , max_length = 60 , blank = True )
""" 文章标签 模型 """
name = models . CharField ( _ ( ' tag name ' ) , max_length = 30 , unique = True ) # 标签名称
slug = models . SlugField ( default = ' no-slug ' , max_length = 60 , blank = True ) # URL友好标识
def __str__ ( self ) :
# 对象字符串表示
return self . name
def get_absolute_url ( self ) :
return reverse ( ' blog:tag_detail ' , kwargs = { ' tag_name ' : self . slug } )
# 获取标签绝对URL
return reverse ( ' blog:tag_detail ' , kwargs = { ' tag_name ' : self . slug } ) # 使用slug作为URL参数
@cache_decorator ( 60 * 60 * 10 )
@cache_decorator ( 60 * 60 * 10 ) # 缓存10小时
def get_article_count ( self ) :
return Article . objects . filter ( tags__name = self . name ) . distinct ( ) . count ( )
# 获取标签下的文章数量
return Article . objects . filter ( tags__name = self . name ) . distinct ( ) . count ( ) # 统计不重复的文章数量
class Meta :
ordering = [ ' name ' ]
verbose_name = _ ( ' tag ' )
verbose_name_plural = verbose_name
ordering = [ ' name ' ] # 默认按名称升序排列
verbose_name = _ ( ' tag ' ) # 单数显示名称
verbose_name_plural = verbose_name # 复数显示名称
class Links ( models . Model ) :
""" 友情链接 """
""" 友情链接 模型 """
name = models . CharField ( _ ( ' link name ' ) , max_length = 30 , unique = True )
link = models . URLField ( _ ( ' link ' ) )
sequence = models . IntegerField ( _ ( ' order ' ) , unique = True )
name = models . CharField ( _ ( ' link name ' ) , max_length = 30 , unique = True ) # 链接名称
link = models . URLField ( _ ( ' link ' ) ) # 链接地址
sequence = models . IntegerField ( _ ( ' order ' ) , unique = True ) # 排序序号
is_enable = models . BooleanField (
_ ( ' is show ' ) , default = True , blank = False , null = False )
_ ( ' is show ' ) , default = True , blank = False , null = False ) # 是否显示
show_type = models . CharField (
_ ( ' show type ' ) ,
max_length = 1 ,
choices = LinkShowType . choices ,
default = LinkShowType . I )
creation_time = models . DateTimeField ( _ ( ' creation time ' ) , default = now )
last_mod_time = models . DateTimeField ( _ ( ' modify time ' ) , default = now )
default = LinkShowType . I ) # 显示类型
creation_time = models . DateTimeField ( _ ( ' creation time ' ) , default = now ) # 创建时间
last_mod_time = models . DateTimeField ( _ ( ' modify time ' ) , default = now ) # 最后修改时间
class Meta :
ordering = [ ' sequence ' ]
verbose_name = _ ( ' link ' )
verbose_name_plural = verbose_name
ordering = [ ' sequence ' ] # 默认按序号升序排列
verbose_name = _ ( ' link ' ) # 单数显示名称
verbose_name_plural = verbose_name # 复数显示名称
def __str__ ( self ) :
# 对象字符串表示
return self . name
class SideBar ( models . Model ) :
""" 侧边栏 , 可以展示一些html内容"""
name = models . CharField ( _ ( ' title ' ) , max_length = 100 )
content = models . TextField ( _ ( ' content ' ) )
sequence = models . IntegerField ( _ ( ' order ' ) , unique = True )
is_enable = models . BooleanField ( _ ( ' is enable ' ) , default = True )
creation_time = models . DateTimeField ( _ ( ' creation time ' ) , default = now )
last_mod_time = models . DateTimeField ( _ ( ' modify time ' ) , default = now )
""" 侧边栏 模型, 可以展示一些html内容"""
name = models . CharField ( _ ( ' title ' ) , max_length = 100 ) # 侧边栏标题
content = models . TextField ( _ ( ' content ' ) ) # 侧边栏内容
sequence = models . IntegerField ( _ ( ' order ' ) , unique = True ) # 排序序号
is_enable = models . BooleanField ( _ ( ' is enable ' ) , default = True ) # 是否启用
creation_time = models . DateTimeField ( _ ( ' creation time ' ) , default = now ) # 创建时间
last_mod_time = models . DateTimeField ( _ ( ' modify time ' ) , default = now ) # 最后修改时间
class Meta :
ordering = [ ' sequence ' ]
verbose_name = _ ( ' sidebar ' )
verbose_name_plural = verbose_name
ordering = [ ' sequence ' ] # 默认按序号升序排列
verbose_name = _ ( ' sidebar ' ) # 单数显示名称
verbose_name_plural = verbose_name # 复数显示名称
def __str__ ( self ) :
# 对象字符串表示
return self . name
class BlogSettings ( models . Model ) :
""" blog的配置 """
""" 博客配置模型 """
site_name = models . CharField (
_ ( ' site name ' ) ,
max_length = 200 ,
null = False ,
blank = False ,
default = ' ' )
default = ' ' ) # 网站名称
site_description = models . TextField (
_ ( ' site description ' ) ,
max_length = 1000 ,
null = False ,
blank = False ,
default = ' ' )
default = ' ' ) # 网站描述
site_seo_description = models . TextField (
_ ( ' site seo description ' ) , max_length = 1000 , null = False , blank = False , default = ' ' )
_ ( ' site seo description ' ) , max_length = 1000 , null = False , blank = False , default = ' ' ) # 网站SEO描述
site_keywords = models . TextField (
_ ( ' site keywords ' ) ,
max_length = 1000 ,
null = False ,
blank = False ,
default = ' ' )
article_sub_length = models . IntegerField ( _ ( ' article sub length ' ) , default = 300 )
sidebar_article_count = models . IntegerField ( _ ( ' sidebar article count ' ) , default = 10 )
sidebar_comment_count = models . IntegerField ( _ ( ' sidebar comment count ' ) , default = 5 )
article_comment_count = models . IntegerField ( _ ( ' article comment count ' ) , default = 5 )
show_google_adsense = models . BooleanField ( _ ( ' show adsense ' ) , default = False )
default = ' ' ) # 网站关键词
article_sub_length = models . IntegerField ( _ ( ' article sub length ' ) , default = 300 ) # 文章摘要长度
sidebar_article_count = models . IntegerField ( _ ( ' sidebar article count ' ) , default = 10 ) # 侧边栏文章数量
sidebar_comment_count = models . IntegerField ( _ ( ' sidebar comment count ' ) , default = 5 ) # 侧边栏评论数量
article_comment_count = models . IntegerField ( _ ( ' article comment count ' ) , default = 5 ) # 文章页面评论数量
show_google_adsense = models . BooleanField ( _ ( ' show adsense ' ) , default = False ) # 是否显示Google广告
google_adsense_codes = models . TextField (
_ ( ' adsense code ' ) , max_length = 2000 , null = True , blank = True , default = ' ' )
open_site_comment = models . BooleanField ( _ ( ' open site comment ' ) , default = True )
global_header = models . TextField ( " 公共头部 " , null = True , blank = True , default = ' ' )
global_footer = models . TextField ( " 公共尾部 " , null = True , blank = True , default = ' ' )
_ ( ' adsense code ' ) , max_length = 2000 , null = True , blank = True , default = ' ' ) # Google广告代码
open_site_comment = models . BooleanField ( _ ( ' open site comment ' ) , default = True ) # 是否开启网站评论
global_header = models . TextField ( " 公共头部 " , null = True , blank = True , default = ' ' ) # 全局头部内容
global_footer = models . TextField ( " 公共尾部 " , null = True , blank = True , default = ' ' ) # 全局尾部内容
beian_code = models . CharField (
' 备案号 ' ,
max_length = 2000 ,
null = True ,
blank = True ,
default = ' ' )
default = ' ' ) # 备案号
analytics_code = models . TextField (
" 网站统计代码 " ,
max_length = 1000 ,
null = False ,
blank = False ,
default = ' ' )
default = ' ' ) # 网站统计代码
show_gongan_code = models . BooleanField (
' 是否显示公安备案号 ' , default = False , null = False )
' 是否显示公安备案号 ' , default = False , null = False ) # 是否显示公安备案号
gongan_beiancode = models . TextField (
' 公安备案号 ' ,
max_length = 2000 ,
null = True ,
blank = True ,
default = ' ' )
default = ' ' ) # 公安备案号
comment_need_review = models . BooleanField (
' 评论是否需要审核 ' , default = False , null = False )
' 评论是否需要审核 ' , default = False , null = False ) # 评论是否需要审核
class Meta :
verbose_name = _ ( ' Website configuration ' )
verbose_name_plural = verbose_name
verbose_name = _ ( ' Website configuration ' ) # 单数显示名称
verbose_name_plural = verbose_name # 复数显示名称
def __str__ ( self ) :
# 对象字符串表示
return self . site_name
def clean ( self ) :
# 数据清理验证,确保只能有一个配置实例
if BlogSettings . objects . exclude ( id = self . id ) . count ( ) :
raise ValidationError ( _ ( ' There can only be one configuration ' ) )
raise ValidationError ( _ ( ' There can only be one configuration ' ) ) # 只能有一个配置
def save ( self , * args , * * kwargs ) :
# 保存配置,同时清除缓存
super ( ) . save ( * args , * * kwargs )
from djangoblog . utils import cache
cache . clear ( )
cache . clear ( ) # 清除所有缓存