|
|
|
@ -5,6 +5,8 @@ from abc import abstractmethod
|
|
|
|
from django.conf import settings
|
|
|
|
from django.conf import settings
|
|
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from django.db import models
|
|
|
|
from django.db import models
|
|
|
|
|
|
|
|
from django.dispatch import receiver
|
|
|
|
|
|
|
|
from django.db.models.signals import post_save
|
|
|
|
from django.urls import reverse
|
|
|
|
from django.urls import reverse
|
|
|
|
from django.utils.timezone import now
|
|
|
|
from django.utils.timezone import now
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
@ -177,39 +179,64 @@ class Article(BaseModel):
|
|
|
|
return ""
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#zbw 模块功能:定义文章分类模型Category,继承自BaseModel,用于存储博客系统中的文章分类信息,
|
|
|
|
|
|
|
|
#zbw 支持分类的层级结构(父分类与子分类),提供获取分类绝对URL、分类树(含父级)及子分类的方法,
|
|
|
|
|
|
|
|
#zbw 优化了缓存策略、添加循环引用检测并移除冗余代码,提升了模型的健壮性和性能
|
|
|
|
class Category(BaseModel):
|
|
|
|
class Category(BaseModel):
|
|
|
|
"""文章分类"""
|
|
|
|
"""文章分类"""
|
|
|
|
|
|
|
|
#zbw 分类名称,字符类型,最大长度30,唯一(不允许重名),使用翻译函数支持多语言
|
|
|
|
name = models.CharField(_('category name'), max_length=30, unique=True)
|
|
|
|
name = models.CharField(_('category name'), max_length=30, unique=True)
|
|
|
|
|
|
|
|
#zbw 父分类,自关联外键,允许为空(表示顶级分类),级联删除(父分类删除时子分类也删除)
|
|
|
|
parent_category = models.ForeignKey(
|
|
|
|
parent_category = models.ForeignKey(
|
|
|
|
'self',
|
|
|
|
'self',
|
|
|
|
verbose_name=_('parent category'),
|
|
|
|
verbose_name=_('parent category'),
|
|
|
|
blank=True,
|
|
|
|
blank=True,
|
|
|
|
null=True,
|
|
|
|
null=True,
|
|
|
|
on_delete=models.CASCADE)
|
|
|
|
on_delete=models.CASCADE)
|
|
|
|
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
|
|
|
|
#zbw URL别名(用于生成友好URL),默认值'no-slug',设置唯一约束,避免URL冲突
|
|
|
|
|
|
|
|
slug = models.SlugField(default='no-slug', max_length=60, blank=True, unique=True)
|
|
|
|
|
|
|
|
#zbw 排序索引,数值越小越靠前,默认0,用于控制分类展示顺序
|
|
|
|
index = models.IntegerField(default=0, verbose_name=_('index'))
|
|
|
|
index = models.IntegerField(default=0, verbose_name=_('index'))
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
class Meta:
|
|
|
|
|
|
|
|
#zbw 按index降序排列(数值小的在前),若index相同则按默认主键排序
|
|
|
|
ordering = ['-index']
|
|
|
|
ordering = ['-index']
|
|
|
|
verbose_name = _('category')
|
|
|
|
verbose_name = _('category')
|
|
|
|
verbose_name_plural = verbose_name
|
|
|
|
verbose_name_plural = verbose_name # 复数形式与单数相同
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clean(self):
|
|
|
|
|
|
|
|
#zbw 验证逻辑:检测分类是否形成循环引用(如A的父级是B,B的父级是A)
|
|
|
|
|
|
|
|
current = self
|
|
|
|
|
|
|
|
while current.parent_category:
|
|
|
|
|
|
|
|
if current.parent_category == self:
|
|
|
|
|
|
|
|
raise ValidationError(_("Category cannot form a circular reference"))
|
|
|
|
|
|
|
|
current = current.parent_category
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
|
|
|
|
#zbw 保存前先执行验证逻辑,确保数据合法性
|
|
|
|
|
|
|
|
self.full_clean()
|
|
|
|
|
|
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#zbw 方法功能:获取分类详情页的绝对URL
|
|
|
|
|
|
|
|
#zbw 返回值:反向解析blog应用的category_detail视图,参数为当前分类的slug
|
|
|
|
def get_absolute_url(self):
|
|
|
|
def get_absolute_url(self):
|
|
|
|
return reverse(
|
|
|
|
return reverse(
|
|
|
|
'blog:category_detail', kwargs={
|
|
|
|
'blog:category_detail', kwargs={
|
|
|
|
'category_name': self.slug})
|
|
|
|
'category_name': self.slug})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#zbw 方法功能:定义对象的字符串表示形式
|
|
|
|
|
|
|
|
#zbw 返回值:分类名称,便于在admin后台及调试时识别对象
|
|
|
|
def __str__(self):
|
|
|
|
def __str__(self):
|
|
|
|
return self.name
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
|
|
@cache_decorator(60 * 60 * 10)
|
|
|
|
#zbw 方法功能:递归获取当前分类的所有父级分类(含自身),形成分类树
|
|
|
|
|
|
|
|
#zbw 缓存装饰器:结果缓存1小时(平衡性能与数据新鲜度),减少数据库查询
|
|
|
|
|
|
|
|
#zbw 返回值:list of Category objects,从当前分类到顶级分类的有序列表
|
|
|
|
|
|
|
|
@cache_decorator(60 * 60 * 1)
|
|
|
|
def get_category_tree(self):
|
|
|
|
def get_category_tree(self):
|
|
|
|
"""
|
|
|
|
|
|
|
|
递归获得分类目录的父级
|
|
|
|
|
|
|
|
:return:
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
categorys = []
|
|
|
|
categorys = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#zbw 内部递归函数:将分类添加到列表,若有父分类则继续递归
|
|
|
|
def parse(category):
|
|
|
|
def parse(category):
|
|
|
|
categorys.append(category)
|
|
|
|
categorys.append(category)
|
|
|
|
if category.parent_category:
|
|
|
|
if category.parent_category:
|
|
|
|
@ -218,28 +245,39 @@ class Category(BaseModel):
|
|
|
|
parse(self)
|
|
|
|
parse(self)
|
|
|
|
return categorys
|
|
|
|
return categorys
|
|
|
|
|
|
|
|
|
|
|
|
@cache_decorator(60 * 60 * 10)
|
|
|
|
#zbw 方法功能:获取当前分类的所有子分类(含自身)
|
|
|
|
|
|
|
|
#zbw 缓存装饰器:结果缓存1小时,优化性能
|
|
|
|
|
|
|
|
#zbw 返回值:list of Category objects,包含当前分类及所有子孙分类
|
|
|
|
|
|
|
|
@cache_decorator(60 * 60 * 1)
|
|
|
|
def get_sub_categorys(self):
|
|
|
|
def get_sub_categorys(self):
|
|
|
|
"""
|
|
|
|
|
|
|
|
获得当前分类目录所有子集
|
|
|
|
|
|
|
|
:return:
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
categorys = []
|
|
|
|
categorys = []
|
|
|
|
|
|
|
|
#zbw 一次性查询所有分类,避免递归中多次查询数据库(N+1问题优化)
|
|
|
|
all_categorys = Category.objects.all()
|
|
|
|
all_categorys = Category.objects.all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#zbw 内部递归函数:递归获取所有子分类,移除冗余判断
|
|
|
|
def parse(category):
|
|
|
|
def parse(category):
|
|
|
|
if category not in categorys:
|
|
|
|
if category not in categorys:
|
|
|
|
categorys.append(category)
|
|
|
|
categorys.append(category)
|
|
|
|
|
|
|
|
#zbw 从已查询的所有分类中筛选子分类,减少数据库交互
|
|
|
|
childs = all_categorys.filter(parent_category=category)
|
|
|
|
childs = all_categorys.filter(parent_category=category)
|
|
|
|
for child in childs:
|
|
|
|
for child in childs:
|
|
|
|
if category not in categorys:
|
|
|
|
parse(child) # 直接递归子分类,内部已做去重判断
|
|
|
|
categorys.append(child)
|
|
|
|
|
|
|
|
parse(child)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parse(self)
|
|
|
|
parse(self)
|
|
|
|
return categorys
|
|
|
|
return categorys
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#zbw 信号处理:当分类保存(新增/更新)时,清除相关缓存(需缓存装饰器支持key生成逻辑)
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Category) # type: ignore
|
|
|
|
|
|
|
|
def clear_category_cache(sender, instance, **kwargs):
|
|
|
|
|
|
|
|
#zbw 清除当前分类的缓存
|
|
|
|
|
|
|
|
instance.get_category_tree.clear_cache()
|
|
|
|
|
|
|
|
instance.get_sub_categorys.clear_cache()
|
|
|
|
|
|
|
|
#zbw 清除父分类的子分类缓存(因当前分类变化可能影响父分类的子树)
|
|
|
|
|
|
|
|
if instance.parent_category:
|
|
|
|
|
|
|
|
instance.parent_category.get_sub_categorys.clear_cache()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Tag(BaseModel):
|
|
|
|
class Tag(BaseModel):
|
|
|
|
"""文章标签"""
|
|
|
|
"""文章标签"""
|
|
|
|
name = models.CharField(_('tag name'), max_length=30, unique=True)
|
|
|
|
name = models.CharField(_('tag name'), max_length=30, unique=True)
|
|
|
|
|