Files
server/base/models.py
T
2026-04-02 11:01:25 +08:00

183 lines
6.8 KiB
Python

from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import models
from common.base import BaseEntity
# Create your models here.
# --- 新增:友情链接分类模型 ---
class LinkCategory(BaseEntity):
name = models.CharField("分类名称", max_length=50, unique=True) # 分类名,如"政府网站"
code = models.SlugField("分类代码", max_length=50, unique=True, help_text="用于前端模板调用的标识,如 'gov', 'fire'")
order = models.IntegerField("显示排序", default=0) # 分类本身的排序
is_active = models.BooleanField("是否启用", default=True)
class Meta:
verbose_name = "友链分类"
verbose_name_plural = "友链分类"
ordering = ['order']
def __str__(self):
return self.name
# --- 修改:友情链接模型 ---
class FriendLink(BaseEntity):
# 1. 关联到新的分类模型(一对多关系:一个分类下有多个链接)
category = models.ForeignKey(
LinkCategory,
on_delete=models.CASCADE, # 如果分类被删,关联的链接也删除
verbose_name="所属分类",
related_name='links' # 反向查询名称,方便后续调用
)
name = models.CharField("网站名称", max_length=100)
url = models.URLField("链接地址", max_length=200)
# 2. 排序和状态
order = models.IntegerField("排序", default=0)
is_active = models.BooleanField("是否启用", default=True)
class Meta:
verbose_name = "友情链接"
verbose_name_plural = "友链管理"
ordering = ['order', '-created_at']
def __str__(self):
return f"[{self.category.name}] {self.name}"
class PictureLink(BaseEntity):
name = models.CharField("网站名称", max_length=100)
picture = models.ImageField("图片链接", upload_to='picture/%Y/%m/%d/', help_text="建议尺寸:280x120")
url = models.URLField("链接地址", help_text="点击图片要访问的站点地址")
# 2. 排序和状态
order = models.IntegerField("排序", default=0)
is_active = models.BooleanField("是否启用", default=True)
class Meta:
verbose_name = "图片链接"
verbose_name_plural = "图链管理"
ordering = ['order', '-created_at']
def __str__(self):
return f"{self.name}"
def save(self, *args, **kwargs):
# 1. 逻辑判断:如果当前想要启用
if self.is_active:
# 统计现有的启用数量(排除自己)
active_count = PictureLink.objects.filter(is_active=True).exclude(pk=self.pk).count()
# 2. 如果已满 4 个
if active_count >= 4:
self.is_active = False # 强制设为不生效
# 3. 【关键】把消息暂存起来,key 用 pk(如果是新建还没 pk,用 id(self) 临时顶替)
# 注意:这里不能直接弹窗,因为没有 request
msg_key = self.pk if self.pk else id(self)
msg = "限制生效:最多只能有 4 个启用的链接,当前对象已被自动设置为【不启用】。"
cache.set(f"picture_count_limit_{msg_key}", msg) # 缓存 30 秒
super().save(*args, **kwargs)
class MainMenu(BaseEntity):
# --- 基础信息 ---
title = models.CharField("标题", max_length=100)
code = models.SlugField("唯一标识", max_length=50, unique=True, help_text="用于URL或模板识别,建议英文")
# --- 树形结构 ---
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='children',
verbose_name="父级节点"
)
# --- 菜单特有属性 ---
# 只有顶级节点(主菜单)才需要填写这个,子节点作为板块时通常不需要跳转链接
url = models.CharField("链接地址", max_length=200, blank=True, help_text="如 /menu/news.html 或 外链")
is_external = models.BooleanField("是否外链", default=False)
is_carousel = models.BooleanField("轮播图", default=False)
# --- 通用属性 ---
order = models.PositiveSmallIntegerField("排序权重", default=0)
visible = models.BooleanField("显示状态", default=True)
class Meta:
verbose_name = "站点导航"
verbose_name_plural = "站点导航"
ordering = ('order', 'title')
def __str__(self):
# 显示层级路径,例如:新闻中心 → 国内新闻
if self.parent:
return f"{self.parent.title}{self.title}"
return self.title
def clean(self):
super().clean()
# 限制层级:最多 2 级
if self.parent and self.parent.parent:
raise ValidationError('层级过深:子节点不能再设置子级,最多只允许两级结构。')
# 防止自引用
if self.pk and self.parent and self.parent.pk == self.pk:
raise ValidationError('节点不能以自身为父级。')
# --- 核心逻辑属性 ---
@property
def is_sub(self):
"""是否为子板块(即是否有父级)"""
return self.parent is not None
@property
def is_main(self):
"""是否为主菜单(即是否无父级)"""
return not self.parent
@property
def active_children(self):
"""
获取当前节点下可见的子节点(用于前端导航渲染)
"""
return self.children.filter(visible=True)
class BaseInfo(BaseEntity):
title = models.CharField("单位名称", max_length=128)
e_title = models.CharField("英文名称", max_length=256)
address = models.CharField("单位地址", max_length=200)
postal = models.CharField("邮编", default="")
telephone = models.CharField("服务热线", default="")
fax = models.CharField("传真", default="")
unit = models.CharField("版权单位", default="")
filing = models.CharField("备案号", default="")
official_account = models.ImageField("公众号二维码", default="", upload_to='carousel/')
weibo = models.ImageField("微博二维码", default="", upload_to='carousel/')
is_active = models.BooleanField("生效", default=False)
class Meta:
verbose_name = "单位信息"
verbose_name_plural = "单位信息"
def __str__(self):
return f"{self.title} - 状态:{'使用中' if self.is_active else '已停用'}"
def save(self, *args, **kwargs):
# 1. 如果当前这条数据被标记为“生效” (is_active=True)
if self.is_active:
# 2. 将数据库中【除了当前这条】以外的所有 FooterLink 都更新为“不生效”
# self.pk 是当前对象的唯一标识
BaseInfo.objects.exclude(pk=self.pk).update(is_active=False)
# 3. 执行正常的保存操作
super().save(*args, **kwargs)