Add models_annotated_yjy.py

main
piehnab5t 4 months ago
parent a1f1c1265d
commit bdfc4220b2

@ -0,0 +1,408 @@
import logging
import re
from abc import abstractmethod
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 _
from mdeditor.fields import MDTextField # Markdown编辑器字段
from uuslug import slugify # 智能slug生成支持中文转拼音
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')) # 幻灯片形式显示
class BaseModel(models.Model):
"""
抽象基模型 - 提供所有模型的通用字段和方法
采用抽象基类避免代码重复符合DRY原则
"""
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生成和浏览量更新优化
特殊处理当只更新views字段时使用update提高性能
"""
# 检查是否是文章视图更新操作 - 性能优化点
is_update_views = isinstance(
self,
Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
if is_update_views:
# 直接使用update语句更新浏览量避免触发其他信号和save逻辑
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')
# 使用uuslug智能生成支持中文转拼音
setattr(self, 'slug', slugify(slug))
super().save(*args, **kwargs)
def get_full_url(self):
"""生成包含域名的完整URL用于分享和SEO"""
site = get_current_site().domain
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
return url
class Meta:
abstract = True # 声明为抽象类,不会创建对应的数据库表
@abstractmethod
def get_absolute_url(self):
"""抽象方法 - 子类必须实现返回对象的相对URL"""
pass
class Article(BaseModel):
"""
文章模型 - 博客核心内容模型
包含文章的所有属性和业务逻辑
"""
# 状态选择项 - 使用元组保持Django传统也可用TextChoicesDjango3.0+
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')) # 正文使用Markdown编辑器
pub_time = models.DateTimeField(_('publish time'), default=now) # 发布时间
status = models.CharField(_('status'), max_length=1, choices=STATUS_CHOICES, 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) # 浏览量统计
author = models.ForeignKey(
settings.AUTH_USER_MODEL, # 关联Django用户模型
verbose_name=_('author'),
on_delete=models.CASCADE) # 用户删除时级联删除文章
article_order = models.IntegerField(_('order'), default=0) # 文章排序权重
show_toc = models.BooleanField(_('show toc'), default=False) # 是否显示文章目录
category = models.ForeignKey(
'Category',
verbose_name=_('category'),
on_delete=models.CASCADE) # 文章分类
tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) # 文章标签,多对多关系
def body_to_string(self):
"""将body内容转为字符串 - 可能用于全文搜索或预览"""
return self.body
def __str__(self):
"""字符串表示用于Admin后台显示"""
return self.title
class Meta:
"""模型元数据配置"""
ordering = ['-article_order', '-pub_time'] # 默认排序先按order倒序再按发布时间倒序
verbose_name = _('article') # 单数名称
verbose_name_plural = verbose_name # 复数名称
get_latest_by = 'id' # 指定获取最新记录的字段
def get_absolute_url(self):
"""
生成文章详情页URL
采用年月日+ID的URL结构有利于SEO和归档
"""
return reverse('blog:detailbyid', kwargs={
'article_id': self.id,
'year': self.creation_time.year,
'month': self.creation_time.month,
'day': self.creation_time.day
})
@cache_decorator(60 * 60 * 10) # 缓存10小时
def get_category_tree(self):
"""
获取分类树结构 - 用于面包屑导航
返回格式[(分类名, URL), ...]
"""
tree = self.category.get_category_tree()
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
return names
def viewed(self):
"""增加文章浏览量 - 使用update_fields优化性能"""
self.views += 1
self.save(update_fields=['views']) # 只更新views字段
def comment_list(self):
"""
获取文章评论列表 - 带缓存功能
缓存策略缓存100分钟减少数据库查询
"""
cache_key = 'article_comments_{id}'.format(id=self.id)
value = cache.get(cache_key)
if value:
logger.info('get article comments:{id}'.format(id=self.id))
return value
else:
# 获取已启用的评论按ID倒序新的在前
comments = self.comment_set.filter(is_enable=True).order_by('-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):
"""生成Django 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) # 缓存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) # 缓存100分钟
def prev_article(self):
"""
获取上一篇已发布文章
查询逻辑ID小于当前文章的最后一个已发布文章
注意这里用了first()但实际是获取ID最大的那个
"""
return Article.objects.filter(id__lt=self.id, status='p').first()
def get_first_image_url(self):
"""
从Markdown内容中提取第一张图片URL
用于文章列表的缩略图显示
正则匹配![alt text](image_url)
"""
match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
if match:
return match.group(1) # 返回第一个捕获组图片URL
return "" # 没有图片返回空字符串
class Category(BaseModel):
"""
文章分类模型 - 支持多级分类结构
通过parent_category自关联实现无限级分类
"""
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) # URL友好标识
index = models.IntegerField(default=0, verbose_name=_('index')) # 分类显示顺序
class Meta:
ordering = ['-index'] # 按index倒序排列数值越大越靠前
verbose_name = _('category')
verbose_name_plural = verbose_name
def get_absolute_url(self):
"""生成分类页面URL使用slug作为URL参数"""
return reverse(
'blog:category_detail', kwargs={
'category_name': self.slug})
def __str__(self):
return self.name
@cache_decorator(60 * 60 * 10) # 缓存10小时
def get_category_tree(self):
"""
递归获取分类的父级链 - 用于面包屑导航
返回从当前分类到根分类的列表
示例 [当前分类, 父分类, 祖父分类, ...]
"""
categorys = []
def parse(category):
"""递归解析分类层级"""
categorys.append(category)
if category.parent_category:
parse(category.parent_category) # 递归调用
parse(self) # 从当前分类开始
return categorys
@cache_decorator(60 * 60 * 10) # 缓存10小时
def get_sub_categorys(self):
"""
递归获取当前分类的所有子分类 - 用于分类导航
返回包含当前分类和所有子孙分类的列表
"""
categorys = []
all_categorys = Category.objects.all() # 获取所有分类
def parse(category):
"""递归解析子分类"""
if category not in categorys:
categorys.append(category)
# 查找直接子分类
childs = all_categorys.filter(parent_category=category)
for child in childs:
if child not in categorys:
categorys.append(child)
parse(child) # 递归查找子分类的子分类
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)
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) # 缓存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
class Links(models.Model):
"""
友情链接模型 - 管理外部链接
支持控制链接显示位置和顺序
"""
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) # 是否启用
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)
class Meta:
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')) # 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'] # 按sequence顺序排列
verbose_name = _('sidebar')
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class BlogSettings(models.Model):
"""
博客全局配置模型 - 单例模式
存储博客的所有可配置项整个系统应该只有一个配置实例
"""
# 站点基本信息
site_name = models.CharField(_('site name'), max_length=200, default='')
site_description = models.TextField(_('site description'), max_length=1000, default='') # 站点描述
site_seo_description = models.TextField(_('site seo description'), max_length=1000, default='') # SEO描述
site_keywords = models.TextField(_('site keywords'), max_length=1000, 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, default='') # 广告代码
# 评论系统设置
open_site_comment = models.BooleanField(_('open site comment'), default=True) # 是否开启全站评论
comment_need_review = models.BooleanField('评论是否需要审核', default=False) # 评论是否需要审核
# 自定义代码和备案信息
global_header = models.TextField("公共头部", default='') # 全局头部HTML代码
global_footer = models.TextField("公共尾部", default='') # 全局尾部HTML代码
beian_code = models.CharField('备案号', max_length=2000, default='') # ICP备案号
analytics_code = models.TextField("网站统计代码", max_length=1000, default='') # 网站统计代码Google Analytics等
show_gongan_code = models.BooleanField('是否显示公安备案号', default=False) # 是否显示公安备案
gongan_beiancode = models.TextField('公安备案号', max_length=2000, default='') # 公安备案号
class Meta:
verbose_name = _('Website configuration') # 在Admin中显示的名称
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() # 清空整个缓存系统
Loading…
Cancel
Save