264 lines
10 KiB
Python
264 lines
10 KiB
Python
from django.db import models
|
||
from shop.models import WeChatUser
|
||
|
||
class Competition(models.Model):
|
||
"""
|
||
比赛管理模型
|
||
"""
|
||
STATUS_CHOICES = (
|
||
('draft', '草稿'),
|
||
('published', '已发布'),
|
||
('registration', '报名中'),
|
||
('submission', '作品提交中'),
|
||
('judging', '评审中'),
|
||
('ended', '已结束'),
|
||
)
|
||
|
||
PROJECT_VISIBILITY_CHOICES = (
|
||
('public', '公开可见'),
|
||
('contestant', '选手及以上可见'),
|
||
('guest', '嘉宾及评委可见'),
|
||
('judge', '仅评委可见'),
|
||
)
|
||
|
||
title = models.CharField(max_length=200, verbose_name="比赛名称")
|
||
description = models.TextField(verbose_name="比赛简介")
|
||
rule_description = models.TextField(verbose_name="规则说明")
|
||
condition_description = models.TextField(verbose_name="参赛条件说明", blank=True)
|
||
|
||
cover_image = models.ImageField(upload_to='competitions/covers/', verbose_name="封面图", null=True, blank=True)
|
||
cover_image_url = models.URLField(verbose_name="封面图URL", null=True, blank=True, help_text="优先使用上传的图片")
|
||
|
||
start_time = models.DateTimeField(verbose_name="开始时间")
|
||
end_time = models.DateTimeField(verbose_name="结束时间")
|
||
|
||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft', verbose_name="状态")
|
||
project_visibility = models.CharField(max_length=20, choices=PROJECT_VISIBILITY_CHOICES, default='public', verbose_name="项目可见性")
|
||
|
||
is_active = models.BooleanField(default=True, 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
|
||
|
||
class Meta:
|
||
verbose_name = "比赛"
|
||
verbose_name_plural = "比赛管理"
|
||
ordering = ['-created_at']
|
||
|
||
|
||
class CompetitionEnrollment(models.Model):
|
||
"""
|
||
比赛人员报名/角色分配
|
||
"""
|
||
ROLE_CHOICES = (
|
||
('contestant', '选手'),
|
||
('judge', '评委'),
|
||
('guest', '嘉宾'),
|
||
)
|
||
|
||
STATUS_CHOICES = (
|
||
('pending', '待审核'),
|
||
('approved', '已通过'),
|
||
('rejected', '已拒绝'),
|
||
)
|
||
|
||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE, related_name='enrollments', verbose_name="所属比赛")
|
||
user = models.ForeignKey(WeChatUser, on_delete=models.CASCADE, related_name='competitions', verbose_name="用户")
|
||
|
||
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='contestant', verbose_name="角色")
|
||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="状态")
|
||
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="申请时间")
|
||
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
||
|
||
class Meta:
|
||
verbose_name = "比赛人员"
|
||
verbose_name_plural = "人员管理"
|
||
unique_together = ('competition', 'user')
|
||
|
||
def __str__(self):
|
||
return f"{self.competition.title} - {self.user.nickname} ({self.get_role_display()})"
|
||
|
||
|
||
class ScoreDimension(models.Model):
|
||
"""
|
||
评分维度配置
|
||
"""
|
||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE, related_name='score_dimensions', verbose_name="所属比赛")
|
||
name = models.CharField(max_length=100, verbose_name="维度名称")
|
||
description = models.TextField(verbose_name="维度说明", blank=True)
|
||
weight = models.DecimalField(max_digits=5, decimal_places=2, default=1.00, verbose_name="权重", help_text="例如 0.3 表示 30%")
|
||
max_score = models.IntegerField(default=100, verbose_name="满分值")
|
||
|
||
order = models.IntegerField(default=0, verbose_name="排序权重")
|
||
|
||
class Meta:
|
||
verbose_name = "评分维度"
|
||
verbose_name_plural = "评分维度配置"
|
||
ordering = ['order']
|
||
|
||
def __str__(self):
|
||
return f"{self.competition.title} - {self.name}"
|
||
|
||
|
||
class Project(models.Model):
|
||
"""
|
||
参赛项目/作品
|
||
"""
|
||
STATUS_CHOICES = (
|
||
('draft', '草稿'),
|
||
('submitted', '已提交'),
|
||
)
|
||
|
||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE, related_name='projects', verbose_name="所属比赛")
|
||
contestant = models.ForeignKey(CompetitionEnrollment, on_delete=models.CASCADE, related_name='projects', verbose_name="参赛选手")
|
||
|
||
title = models.CharField(max_length=200, verbose_name="项目名称")
|
||
description = models.TextField(verbose_name="项目介绍")
|
||
team_info = models.TextField(verbose_name="团队介绍", blank=True)
|
||
|
||
cover_image = models.ImageField(upload_to='competitions/projects/covers/', verbose_name="项目封面", null=True, blank=True)
|
||
cover_image_url = models.URLField(verbose_name="项目封面URL", null=True, blank=True, help_text="优先使用上传的图片")
|
||
|
||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft', verbose_name="状态")
|
||
|
||
# 最终得分缓存,避免每次实时计算
|
||
final_score = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name="最终得分")
|
||
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
||
|
||
class Meta:
|
||
verbose_name = "参赛项目"
|
||
verbose_name_plural = "项目管理"
|
||
ordering = ['-final_score', '-created_at']
|
||
|
||
def __str__(self):
|
||
return self.title
|
||
|
||
def calculate_score(self):
|
||
"""
|
||
计算项目得分
|
||
计算公式:
|
||
1. 获取所有评委对该项目的打分
|
||
2. 按维度加权平均
|
||
这里简化处理:
|
||
总分 = (所有评委的总加权分之和) / 评委人数
|
||
其中每个评委对项目的打分 = sum(维度分 * 维度权重)
|
||
"""
|
||
# 获取所有评分
|
||
scores = self.scores.all()
|
||
if not scores.exists():
|
||
return 0
|
||
|
||
# 找出所有参与评分的评委
|
||
judges = set(score.judge for score in scores)
|
||
if not judges:
|
||
return 0
|
||
|
||
total_weighted_score = 0
|
||
|
||
for judge in judges:
|
||
judge_score = 0
|
||
# 获取该评委对该项目的所有维度打分
|
||
judge_scores = scores.filter(judge=judge)
|
||
|
||
current_judge_total_score = 0
|
||
current_judge_total_weight = 0
|
||
|
||
for score in judge_scores:
|
||
current_judge_total_score += score.score * score.dimension.weight
|
||
current_judge_total_weight += score.dimension.weight
|
||
|
||
if current_judge_total_weight > 0:
|
||
judge_score = current_judge_total_score / current_judge_total_weight
|
||
# 如果是百分制,这里算出来就是0-100
|
||
|
||
total_weighted_score += judge_score
|
||
|
||
# 平均分
|
||
avg_score = total_weighted_score / len(judges)
|
||
self.final_score = avg_score
|
||
self.save()
|
||
return avg_score
|
||
|
||
|
||
class ProjectFile(models.Model):
|
||
"""
|
||
项目附件
|
||
"""
|
||
FILE_TYPE_CHOICES = (
|
||
('ppt', 'PPT演示文稿'),
|
||
('pdf', 'PDF文档'),
|
||
('image', '图片'),
|
||
('video', '视频'),
|
||
('doc', '文档'),
|
||
('other', '其他'),
|
||
)
|
||
|
||
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='files', verbose_name="所属项目")
|
||
file_type = models.CharField(max_length=20, choices=FILE_TYPE_CHOICES, default='other', verbose_name="文件类型")
|
||
|
||
file = models.FileField(upload_to='competitions/projects/files/', verbose_name="文件", null=True, blank=True)
|
||
file_url = models.URLField(verbose_name="文件链接", null=True, blank=True, help_text="视频等大文件建议使用外部链接")
|
||
|
||
name = models.CharField(max_length=100, verbose_name="文件名称", blank=True)
|
||
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间")
|
||
|
||
class Meta:
|
||
verbose_name = "项目附件"
|
||
verbose_name_plural = "附件管理"
|
||
|
||
def __str__(self):
|
||
return self.name or f"{self.get_file_type_display()}"
|
||
|
||
|
||
class Score(models.Model):
|
||
"""
|
||
评委打分
|
||
"""
|
||
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='scores', verbose_name="所属项目")
|
||
judge = models.ForeignKey(CompetitionEnrollment, on_delete=models.CASCADE, related_name='given_scores', verbose_name="评委")
|
||
dimension = models.ForeignKey(ScoreDimension, on_delete=models.CASCADE, verbose_name="评分维度")
|
||
|
||
score = models.DecimalField(max_digits=5, decimal_places=1, verbose_name="得分")
|
||
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="打分时间")
|
||
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
||
|
||
class Meta:
|
||
verbose_name = "评分记录"
|
||
verbose_name_plural = "评分记录"
|
||
unique_together = ('project', 'judge', 'dimension')
|
||
|
||
def __str__(self):
|
||
return f"{self.judge.user.nickname} -> {self.project.title}: {self.score}"
|
||
|
||
def save(self, *args, **kwargs):
|
||
super().save(*args, **kwargs)
|
||
# 触发重新计算分数
|
||
self.project.calculate_score()
|
||
|
||
|
||
class Comment(models.Model):
|
||
"""
|
||
评委评语
|
||
"""
|
||
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='comments', verbose_name="所属项目")
|
||
judge = models.ForeignKey(CompetitionEnrollment, on_delete=models.CASCADE, related_name='given_comments', verbose_name="评委")
|
||
|
||
content = models.TextField(verbose_name="评语内容")
|
||
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="评论时间")
|
||
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
||
|
||
class Meta:
|
||
verbose_name = "评委评语"
|
||
verbose_name_plural = "评语管理"
|
||
|
||
def __str__(self):
|
||
return f"{self.judge.user.nickname} -> {self.project.title}"
|