创赢未来评分系统 - 初始化提交(移除大文件)
All checks were successful
Deploy to Server / deploy (push) Successful in 18s
All checks were successful
Deploy to Server / deploy (push) Successful in 18s
This commit is contained in:
285
backend/community/models.py
Normal file
285
backend/community/models.py
Normal file
@@ -0,0 +1,285 @@
|
||||
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_paid = models.BooleanField(default=False, verbose_name="是否收费")
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="报名费用")
|
||||
|
||||
author = models.ForeignKey(WeChatUser, to_field='phone_number', on_delete=models.SET_NULL, related_name='activities', verbose_name="发布者", null=True, blank=True)
|
||||
|
||||
is_active = models.BooleanField(default=True, verbose_name="是否启用")
|
||||
is_visible = models.BooleanField(default=True, verbose_name="是否显示", help_text="关闭后将不在前端列表页显示")
|
||||
auto_confirm = models.BooleanField(default=False, verbose_name="无需审核", help_text="开启后,付费活动支付成功或免费活动报名后直接显示报名成功,不需要审核")
|
||||
|
||||
# 常用报名信息开关
|
||||
ask_name = models.BooleanField(default=False, verbose_name="收集姓名")
|
||||
ask_phone = models.BooleanField(default=False, verbose_name="收集手机号")
|
||||
ask_wechat = models.BooleanField(default=False, verbose_name="收集微信号")
|
||||
ask_company = models.BooleanField(default=False, verbose_name="收集公司/机构")
|
||||
|
||||
signup_form_config = models.JSONField(
|
||||
default=list,
|
||||
verbose_name="自定义报名配置",
|
||||
blank=True,
|
||||
help_text='JSON格式的高级配置,若填写则优先于上方开关。例如:[{"name": "job", "label": "职位", "type": "text", "required": true}]'
|
||||
)
|
||||
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
|
||||
|
||||
@property
|
||||
def current_signups(self):
|
||||
"""
|
||||
当前有效报名人数(仅统计已确认/已支付的报名)
|
||||
"""
|
||||
return self.signups.filter(status='confirmed').count()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
verbose_name = "社区活动"
|
||||
verbose_name_plural = "社区活动管理"
|
||||
|
||||
|
||||
class ActivitySignup(models.Model):
|
||||
"""
|
||||
活动报名记录
|
||||
"""
|
||||
STATUS_CHOICES = (
|
||||
('unpaid', '待支付'),
|
||||
('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="报名时间")
|
||||
signup_info = models.JSONField(
|
||||
default=dict,
|
||||
verbose_name="报名信息",
|
||||
blank=True
|
||||
)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='confirmed', verbose_name="状态")
|
||||
|
||||
# 关联订单(针对付费活动)
|
||||
order = models.ForeignKey('shop.Order', on_delete=models.SET_NULL, null=True, blank=True, verbose_name="关联订单", related_name='activity_signups')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.nickname} - {self.activity.title}"
|
||||
|
||||
def check_payment_status(self):
|
||||
"""
|
||||
检查并同步关联订单的支付状态
|
||||
"""
|
||||
if self.status == 'unpaid' and self.order:
|
||||
if self.order.status == 'paid':
|
||||
self.status = 'confirmed' if self.activity.auto_confirm else 'pending'
|
||||
self.save()
|
||||
return True
|
||||
return False
|
||||
|
||||
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="分类")
|
||||
|
||||
STATUS_CHOICES = (
|
||||
('pending', '待审核'),
|
||||
('published', '已发布'),
|
||||
('rejected', '已拒绝'),
|
||||
)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='published', 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="浏览量")
|
||||
likes = models.ManyToManyField(WeChatUser, related_name='liked_topics', blank=True, 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="更新时间")
|
||||
order = models.IntegerField(default=0, verbose_name="排序")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# 记录是否为新对象,因为super().save后pk就有了
|
||||
is_new = self.pk is None
|
||||
|
||||
# 第一次保存,先入库
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# 如果是新创建,且 order 默认为 0(未指定)
|
||||
if is_new and getattr(self, 'order', 0) == 0:
|
||||
# 将所有其他帖子的 order + 1,腾出 0 的位置
|
||||
Topic.objects.exclude(pk=self.pk).filter(order__gte=0).update(order=models.F('order') + 1)
|
||||
# 确保自己是 0
|
||||
Topic.objects.filter(pk=self.pk).update(order=0)
|
||||
self.order = 0
|
||||
|
||||
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 = ['order', '-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="回复楼层")
|
||||
likes = models.ManyToManyField(WeChatUser, related_name='liked_replies', blank=True, verbose_name="点赞用户")
|
||||
is_pinned = models.BooleanField(default=False, 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 = ['-is_pinned', '-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']
|
||||
Reference in New Issue
Block a user