from django.db import models from shop.models import WeChatUser, ESP32Config, Order, Service, VCCourse, ServiceOrder class Activity(models.Model): """ 社区活动模型 """ title = models.CharField(max_length=100, verbose_name="活动标题") description = models.TextField(verbose_name="活动详情") banner = models.ImageField(upload_to='activities/banners/', verbose_name="活动Banner图", null=True, blank=True) banner_url = models.URLField(verbose_name="活动Banner链接", null=True, blank=True, help_text="可直接填写图片链接,若同时上传图片,将优先显示上传的图片") start_time = models.DateTimeField(verbose_name="开始时间") end_time = models.DateTimeField(verbose_name="结束时间") location = models.CharField(max_length=100, verbose_name="活动地点") max_participants = models.IntegerField(default=50, verbose_name="最大报名人数") is_active = models.BooleanField(default=True, verbose_name="是否启用") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") def clean(self): from django.core.exceptions import ValidationError if not self.banner and not self.banner_url: raise ValidationError("Banner图片和Banner链接必须至少填写一项") def save(self, *args, **kwargs): self.clean() super().save(*args, **kwargs) @property def display_banner_url(self): """ 获取Banner显示的URL,优先使用上传的图片 """ if self.banner: return self.banner.url return self.banner_url def __str__(self): return self.title class Meta: verbose_name = "社区活动" verbose_name_plural = "社区活动管理" class ActivitySignup(models.Model): """ 活动报名记录 """ STATUS_CHOICES = ( ('pending', '审核中'), ('confirmed', '报名成功'), ('cancelled', '已取消'), ) activity = models.ForeignKey(Activity, on_delete=models.CASCADE, related_name='signups', verbose_name="活动") user = models.ForeignKey(WeChatUser, on_delete=models.CASCADE, related_name='activity_signups', verbose_name="报名用户") signup_time = models.DateTimeField(auto_now_add=True, verbose_name="报名时间") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='confirmed', verbose_name="状态") def __str__(self): return f"{self.user.nickname} - {self.activity.title}" class Meta: verbose_name = "活动报名" verbose_name_plural = "活动报名管理" unique_together = ('activity', 'user') class Topic(models.Model): """ 论坛帖子/主题 """ title = models.CharField(max_length=200, verbose_name="标题") CATEGORY_CHOICES = ( ('discussion', '技术讨论'), ('help', '求助问答'), ('share', '经验分享'), ('notice', '官方公告'), ) category = models.CharField(max_length=20, choices=CATEGORY_CHOICES, default='discussion', verbose_name="分类") content = models.TextField(verbose_name="内容", help_text="支持Markdown格式,支持插入图片") author = models.ForeignKey(WeChatUser, on_delete=models.CASCADE, related_name='topics', verbose_name="作者") # 关联对象:硬件、服务、课程 related_product = models.ForeignKey(ESP32Config, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联硬件") related_service = models.ForeignKey(Service, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联服务") related_course = models.ForeignKey(VCCourse, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联课程") view_count = models.IntegerField(default=0, verbose_name="浏览量") is_pinned = models.BooleanField(default=False, verbose_name="置顶") created_at = models.DateTimeField(auto_now_add=True, verbose_name="发布时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") def __str__(self): return self.title @property def is_verified_owner(self): """ 判断作者是否为关联项目(硬件/服务/课程)的已购用户(Verified Owner) """ # 1. 验证硬件 if self.related_product: if Order.objects.filter( wechat_user=self.author, config=self.related_product, status__in=['paid', 'shipped'] ).exists(): return True # 2. 验证课程 if self.related_course: if Order.objects.filter( wechat_user=self.author, course=self.related_course, status__in=['paid', 'shipped'] ).exists(): return True # 3. 验证服务 if self.related_service: pass return False class Meta: verbose_name = "论坛帖子" verbose_name_plural = "论坛帖子管理" ordering = ['-is_pinned', '-created_at'] class Reply(models.Model): """ 帖子回复 """ topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='replies', verbose_name="所属帖子") content = models.TextField(verbose_name="回复内容", help_text="支持Markdown格式") author = models.ForeignKey(WeChatUser, on_delete=models.CASCADE, related_name='replies', verbose_name="回复者") reply_to = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children', verbose_name="回复楼层") created_at = models.DateTimeField(auto_now_add=True, verbose_name="回复时间") def __str__(self): return f"回复: {self.topic.title}" class Meta: verbose_name = "帖子回复" verbose_name_plural = "帖子回复管理" ordering = ['created_at'] class TopicMedia(models.Model): """ 论坛多媒体资源(图片/视频/文件) """ MEDIA_TYPE_CHOICES = ( ('image', '图片'), ('video', '视频'), ('file', '文件'), ) topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='media', verbose_name="所属帖子", null=True, blank=True) reply = models.ForeignKey(Reply, on_delete=models.CASCADE, related_name='media', verbose_name="所属回复", null=True, blank=True) file = models.FileField(upload_to='community/media/', verbose_name="文件", null=True, blank=True) file_url = models.URLField(max_length=500, verbose_name="文件链接", null=True, blank=True) media_type = models.CharField(max_length=10, choices=MEDIA_TYPE_CHOICES, default='image', verbose_name="媒体类型") created_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间") def __str__(self): return f"{self.media_type} - {self.file.name}" class Meta: verbose_name = "论坛媒体资源" verbose_name_plural = "论坛媒体资源管理" class Announcement(models.Model): """ 社区公告模型 """ title = models.CharField(max_length=100, verbose_name="公告标题") content = models.TextField(verbose_name="公告内容") image = models.ImageField(upload_to='announcements/', verbose_name="公告图片", null=True, blank=True) image_url = models.URLField(verbose_name="图片链接", null=True, blank=True, help_text="优先使用上传的图片") link_url = models.URLField(verbose_name="跳转链接", null=True, blank=True) is_active = models.BooleanField(default=True, verbose_name="是否启用") is_pinned = models.BooleanField(default=False, verbose_name="是否置顶") priority = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越大越靠前") start_time = models.DateTimeField(verbose_name="开始展示时间", null=True, blank=True) end_time = models.DateTimeField(verbose_name="结束展示时间", null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") @property def display_image_url(self): if self.image: return self.image.url return self.image_url def __str__(self): return self.title class Meta: verbose_name = "社区公告" verbose_name_plural = "社区公告管理" ordering = ['-is_pinned', '-priority', '-created_at']