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)