from django.db import models from django.contrib.auth.models import User from django.db.models.signals import post_save, pre_delete from django.dispatch import receiver import json class Article(models.Model): STATUS_CHOICES = ( ("draft", "草稿"), ("published", "已发布"), ("archived", "归档"), ) title = models.CharField(max_length=200, verbose_name="标题") content = models.TextField(verbose_name="正文") author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="作者") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="draft", verbose_name="状态") views = models.PositiveIntegerField(default=0, verbose_name="浏览量") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") is_deleted = models.BooleanField(default=False, verbose_name="是否软删除") # 加分项软删除 def __str__(self): return self.title class Meta: verbose_name = "文章" verbose_name_plural = "文章" ordering = ["-created_at"] class Attachment(models.Model): article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name="attachments", verbose_name="关联文章") file = models.FileField(upload_to="attachments/", verbose_name="文件") filename = models.CharField(max_length=200, verbose_name="文件名") upload_time = models.DateTimeField(auto_now_add=True, verbose_name="上传时间") downloads = models.PositiveIntegerField(default=0, verbose_name="下载次数") # 加分项下载计数 def __str__(self): return self.filename class Meta: verbose_name = "附件" verbose_name_plural = "附件" ordering = ["-upload_time"] class AuditLog(models.Model): OP_TYPE_CHOICES = ( ("create", "创建"), ("update", "更新"), ("publish", "发布"), ("archive", "归档"), ("delete", "删除"), ) article = models.ForeignKey(Article, on_delete=models.SET_NULL, null=True, verbose_name="关联文章") operator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name="操作人") op_type = models.CharField(max_length=20, choices=OP_TYPE_CHOICES, verbose_name="操作类型") diff = models.JSONField(default=dict, verbose_name="变更摘要") time = models.DateTimeField(auto_now_add=True, verbose_name="操作时间") def __str__(self): return f"{self.get_op_type_display()} - {self.article}" class Meta: verbose_name = "审计日志" verbose_name_plural = "审计日志" ordering = ["-time"] # ========== Django信号:自动生成审计日志(作业硬性要求!禁止在视图手动创建AuditLog) ========== @receiver(post_save, sender=Article) def article_post_save(sender, instance, created,** kwargs): if created: # 新建文章 AuditLog.objects.create( article=instance, operator=instance.author, op_type="create", diff={"title": instance.title, "status": instance.status} ) else: # 更新文章,记录状态变更 # kwargs里有update_fields,用来拿到改动字段 diff_data = {} if kwargs.get("update_fields") and "status" in kwargs["update_fields"]: diff_data["status"] = {"new": instance.status} if instance.status == "published": op_type = "publish" elif instance.status == "archived": op_type = "archive" else: op_type = "update" else: op_type = "update" diff_data["title"] = instance.title AuditLog.objects.create( article=instance, operator=instance.author, op_type=op_type, diff=diff_data ) @receiver(pre_delete, sender=Article) def article_pre_delete(sender, instance,** kwargs): # 删除文章前触发信号 AuditLog.objects.create( article=instance, operator=instance.author, op_type="delete", diff={"title": instance.title} )