pull/19/head
djq 4 months ago
parent e861fe1603
commit 30cd084720

@ -18,6 +18,7 @@ logger = logging.getLogger(__name__)
class LinkShowType(models.TextChoices):
# 定义友情链接显示类型的枚举类,分别对应首页、列表页、文章页、所有页面、轮播
I = ('i', _('index'))
L = ('l', _('list'))
P = ('p', _('post'))
@ -26,98 +27,118 @@ class LinkShowType(models.TextChoices):
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):
"""
重写save方法处理slug字段如果模型有slug和title/name字段并调用父类save方法
同时处理仅更新views字段的特殊情况
"""
is_update_views = isinstance(
self,
Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
if is_update_views:
Article.objects.filter(pk=self.pk).update(views=self.views)
else:
# 如果模型有slug字段生成slug基于title或name字段
if 'slug' in self.__dict__:
slug = getattr(
self, 'title') if 'title' in self.__dict__ else getattr(
self, 'name')
setattr(self, 'slug', slugify(slug))
slug_source = getattr(self, 'title') if 'title' in self.__dict__ else getattr(self, 'name')
setattr(self, 'slug', slugify(slug_source))
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):
"""
抽象方法子类必须实现用于获取模型对象的绝对URL
"""
pass
class Article(BaseModel):
"""文章"""
"""
文章模型类存储文章的相关信息
"""
# 文章状态:草稿、已发布
STATUS_CHOICES = (
('d', _('Draft')),
('p', _('Published')),
)
# 评论状态:开启、关闭
COMMENT_STATUS = (
('o', _('Open')),
('c', _('Close')),
)
# 文章类型:文章、页面
TYPE = (
('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')) # 文章内容使用MDTextField支持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) # 文章分类外键关联Category模型
tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) # 文章标签多对多关联Tag模型
def body_to_string(self):
"""将文章内容转换为字符串返回"""
return self.body
def __str__(self):
"""自定义字符串表示,返回文章标题"""
return self.title
class Meta:
ordering = ['-article_order', '-pub_time']
ordering = ['-article_order', '-pub_time'] # 排序规则先按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,
@ -127,19 +148,26 @@ class Article(BaseModel):
@cache_decorator(60 * 60 * 10)
def get_category_tree(self):
"""
获取文章分类的树形结构包含当前分类及其所有父级分类并缓存
"""
tree = self.category.get_category_tree()
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
return names
def save(self, *args, **kwargs):
"""重写save方法调用父类save方法"""
super().save(*args, **kwargs)
def viewed(self):
"""文章被浏览时浏览量加1并保存"""
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:
@ -152,24 +180,25 @@ class Article(BaseModel):
return comments
def get_admin_url(self):
"""获取文章在admin后台的编辑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)
def next_article(self):
# 下一篇
"""获取下一篇文章id大于当前文章且已发布的第一篇并缓存"""
return Article.objects.filter(
id__gt=self.id, status='p').order_by('id').first()
@cache_decorator(expiration=60 * 100)
def prev_article(self):
# 前一篇
"""获取前一篇文章id小于当前文章且已发布的第一篇并缓存"""
return Article.objects.filter(id__lt=self.id, status='p').first()
def get_first_image_url(self):
"""
Get the first image url from article.body.
:return:
从文章内容中提取第一张图片的URL
通过正则表达式匹配markdown图片语法中的图片链接
"""
match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
if match:
@ -178,35 +207,38 @@ class Article(BaseModel):
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) # 分类的slug用于URL
index = models.IntegerField(default=0, verbose_name=_('index')) # 分类排序序号
class Meta:
ordering = ['-index']
ordering = ['-index'] # 按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})
def __str__(self):
"""自定义字符串表示,返回分类名称"""
return self.name
@cache_decorator(60 * 60 * 10)
def get_category_tree(self):
"""
递归获得分类目录的父级
:return:
递归获取分类的树形结构当前分类及其所有父级分类并缓存
"""
categorys = []
@ -221,8 +253,7 @@ class Category(BaseModel):
@cache_decorator(60 * 60 * 10)
def get_sub_categorys(self):
"""
获得当前分类目录所有子集
:return:
递归获取当前分类的所有子分类包括子分类的子分类等并缓存
"""
categorys = []
all_categorys = Category.objects.all()
@ -241,136 +272,156 @@ class Category(BaseModel):
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) # 标签的slug用于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)
def get_article_count(self):
"""获取该标签下的文章数量,并缓存"""
return Article.objects.filter(tags__name=self.name).distinct().count()
class Meta:
ordering = ['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')) # 链接URL
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) # 显示类型关联LinkShowType枚举
creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
last_mod_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
class Meta:
ordering = ['sequence']
ordering = ['sequence'] # 按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')) # 侧边栏内容HTML
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']
ordering = ['sequence'] # 按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='') # 公共头部HTML
global_footer = models.TextField("公共尾部", null=True, blank=True, default='') # 公共尾部HTML
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
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):
"""
重写save方法保存后清除缓存使配置变更立即生效
"""
super().save(*args, **kwargs)
from djangoblog.utils import cache
cache.clear()
cache.clear()
Loading…
Cancel
Save