@ -1,42 +1,55 @@
# 导入日志模块
import logging
# 导入正则表达式模块
import re
# 导入抽象方法装饰器
from abc import abstractmethod
# 导入Django配置和模型相关模块
from django . conf import settings
from django . core . exceptions import ValidationError
from django . db import models
from django . urls import reverse
from django . utils . timezone import now
from django . utils . translation import gettext_lazy as _
# 导入Markdown编辑器字段
from mdeditor . fields import MDTextField
# 导入slug生成工具
from uuslug import slugify
# 导入自定义工具函数
from djangoblog . utils import cache_decorator , cache
from djangoblog . utils import get_current_site
# 获取日志记录器
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 ' ]
if is_update_views :
# 如果是视图更新,直接更新数据库
Article . objects . filter ( pk = self . pk ) . update ( views = self . views )
else :
# 否则正常保存, 并处理slug字段
if ' slug ' in self . __dict__ :
slug = getattr (
self , ' title ' ) if ' title ' in self . __dict__ else getattr (
@ -45,65 +58,68 @@ class BaseModel(models.Model):
super ( ) . save ( * args , * * kwargs )
def get_full_url ( self ) :
# 获取完整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 # 标记为抽象基类
@abstractmethod
def get_absolute_url ( self ) :
# 抽象方法,子类必须实现
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
@ -112,12 +128,13 @@ class Article(BaseModel):
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,8 +142,9 @@ 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 ) )
@ -136,10 +154,12 @@ class Article(BaseModel):
super ( ) . save ( * args , * * kwargs )
def viewed ( self ) :
# 增加浏览量
self . views + = 1
self . save ( update_fields = [ ' views ' ] )
def comment_list ( self ) :
# 获取评论列表,带缓存
cache_key = ' article_comments_ {id} ' . format ( id = self . id )
value = cache . get ( cache_key )
if value :
@ -147,23 +167,24 @@ class Article(BaseModel):
return value
else :
comments = self . comment_set . filter ( is_enable = True ) . order_by ( ' -id ' )
cache . set ( cache_key , comments , 60 * 100 )
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 ) :
# 获取管理后台URL
info = ( self . _meta . app_label , self . _meta . model_name )
return reverse ( ' admin: %s _ %s _change ' % info , args = ( self . pk , ) )
@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 ( )
@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 ( )
def get_first_image_url ( self ) :
@ -171,30 +192,33 @@ class Article(BaseModel):
Get the first image url from article . body .
: return :
"""
# 从文章正文中提取第一张图片URL
match = re . search ( r ' ! \ [.*? \ ] \ ((.+?) \ ) ' , self . body )
if match :
return match . group ( 1 )
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 } )
@ -202,7 +226,7 @@ class Category(BaseModel):
def __str__ ( self ) :
return self . name
@cache_decorator ( 60 * 60 * 10 )
@cache_decorator ( 60 * 60 * 10 ) # 缓存10小时
def get_category_tree ( self ) :
"""
递归获得分类目录的父级
@ -218,7 +242,7 @@ class Category(BaseModel):
parse ( self )
return categorys
@cache_decorator ( 60 * 60 * 10 )
@cache_decorator ( 60 * 60 * 10 ) # 缓存10小时
def get_sub_categorys ( self ) :
"""
获得当前分类目录所有子集
@ -240,70 +264,76 @@ class Category(BaseModel):
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 ) :
# 获取标签绝对URL
return reverse ( ' blog:tag_detail ' , kwargs = { ' tag_name ' : self . slug } )
@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 ( )
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 )
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 (
@ -311,66 +341,67 @@ class BlogSettings(models.Model):
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 ' ) )
def save ( self , * args , * * kwargs ) :
super ( ) . save ( * args , * * kwargs )
from djangoblog . utils import cache
cache . clear ( )
cache . clear ( ) # 保存配置后清除缓存