183 lines
6.8 KiB
Python
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)
|