Files
Scoring-System/backend/competition/models.py
爽哒哒 f26d35da66
All checks were successful
Deploy to Server / deploy (push) Successful in 18s
创赢未来评分系统 - 初始化提交(移除大文件)
2026-03-18 22:41:23 +08:00

338 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from shop.models import WeChatUser
class HomePageConfig(models.Model):
"""首页配置"""
banner_image = models.ImageField(upload_to='homepage/', verbose_name="首页Banner图片", null=True, blank=True)
banner_image_url = models.URLField(verbose_name="Banner图片URL", null=True, blank=True, help_text="优先使用上传的图片")
main_title = models.CharField(max_length=200, default='"创赢未来"云南2026创业大赛', verbose_name="主标题")
carousel1_title = models.CharField(max_length=200, default='"创赢未来"云南2026创业大赛', verbose_name="轮播图1标题")
carousel2_title = models.CharField(max_length=200, default='"七彩云南创业福地"创业主题系列活动', verbose_name="轮播图2标题")
organizer = models.CharField(max_length=200, default='云南省人力资源和社会保障厅', verbose_name="主办单位")
undertaker = models.CharField(max_length=200, default='云南省就业局', 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="更新时间")
class Meta:
verbose_name = "首页配置"
verbose_name_plural = "首页配置"
def __str__(self):
return "首页配置"
def get_banner_url(self):
if self.banner_image:
return self.banner_image.url
return self.banner_image_url
class CarouselItem(models.Model):
"""轮播图项目"""
CAROUSEL_TYPE_CHOICES = (
('carousel1', '创业大赛轮播图'),
('carousel2', '创业活动轮播图'),
)
STATUS_CHOICES = (
('报名中', '报名中'),
('即将开始', '即将开始'),
('敬请期待', '敬请期待'),
('进行中', '进行中'),
)
carousel_type = models.CharField(max_length=20, choices=CAROUSEL_TYPE_CHOICES, default='carousel1', verbose_name="轮播图类型")
image = models.ImageField(upload_to='homepage/carousel/', verbose_name="轮播图片", null=True, blank=True)
image_url = models.CharField(max_length=500, verbose_name="图片URL", null=True, blank=True, help_text="可填写本地路径如 /carousel1.png 或完整URL优先使用上方上传的图片")
title = models.CharField(max_length=100, verbose_name="标题")
subtitle = models.CharField(max_length=200, verbose_name="副标题")
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='报名中', verbose_name="状态")
status_color = models.CharField(max_length=20, default='#52c41a', verbose_name="状态颜色")
date = models.CharField(max_length=100, verbose_name="日期")
location = models.CharField(max_length=100, verbose_name="地点")
order = models.IntegerField(default=0, 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="更新时间")
class Meta:
verbose_name = "轮播图项目"
verbose_name_plural = "轮播图管理"
ordering = ['order', 'id']
def __str__(self):
return f"{self.get_carousel_type_display()} - {self.title}"
def get_image_url(self):
if self.image:
return self.image.url
return self.image_url
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="项目可见性")
allow_contestant_grading = models.BooleanField(default=False, 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=6, decimal_places=4, default=1.0000, verbose_name="权重", help_text="例如 0.3000 表示 30%")
max_score = models.IntegerField(default=100, verbose_name="满分值")
is_public = models.BooleanField(default=True, verbose_name="是否公开给评委", help_text="如果关闭评委端将看不到此评分维度通常用于AI自动评分")
is_peer_review = models.BooleanField(default=False, verbose_name="是否用于选手互评", help_text="如果开启,此评分维度仅在选手互评时可见,评委和嘉宾看不到")
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(维度分数 × 维度权重)
3. 项目最终得分 = 所有评委得分的平均值
"""
# 获取所有评分
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)
for score in judge_scores:
# 直接用原始分数乘以权重相加
judge_score += score.score * score.dimension.weight
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}"