203 lines
8.1 KiB
Python
203 lines
8.1 KiB
Python
from django.contrib import admin
|
||
from unfold.admin import ModelAdmin
|
||
from unfold.decorators import display
|
||
from .models import Competition, CompetitionEnrollment, ScoreDimension, Project, ProjectFile, Score, Comment, ScoreFormula
|
||
|
||
class ScoreDimensionInline(admin.TabularInline):
|
||
model = ScoreDimension
|
||
extra = 1
|
||
tab = True
|
||
fields = ('name', 'description', 'weight', 'max_score', 'formula_type', 'formula', 'is_public', 'is_peer_review', 'order')
|
||
|
||
@admin.display(description="算式预览")
|
||
def formula_preview(self, obj):
|
||
preview = obj.get_formula_preview()
|
||
return preview if preview else "-"
|
||
|
||
class ProjectFileInline(admin.TabularInline):
|
||
model = ProjectFile
|
||
extra = 0
|
||
tab = True
|
||
|
||
@admin.register(Competition)
|
||
class CompetitionAdmin(ModelAdmin):
|
||
list_display = ['title', 'status', 'allow_contestant_grading', 'start_time', 'end_time', 'is_active', 'created_at']
|
||
list_filter = ['status', 'allow_contestant_grading', 'is_active']
|
||
search_fields = ['title', 'description']
|
||
inlines = [ScoreDimensionInline]
|
||
|
||
fieldsets = (
|
||
('基本信息', {
|
||
'fields': ('title', 'description', 'rule_description', 'condition_description')
|
||
}),
|
||
('封面设置', {
|
||
'fields': ('cover_image', 'cover_image_url'),
|
||
'description': '封面图可以上传本地图片,也可以填写外部链接,优先显示本地上传的图片'
|
||
}),
|
||
('时间和状态', {
|
||
'fields': ('start_time', 'end_time', 'status', 'project_visibility', 'allow_contestant_grading', 'is_active')
|
||
}),
|
||
('评分计算设置', {
|
||
'fields': ('score_calculation_type', 'custom_score_formula'),
|
||
'description': '配置得分计算方式:默认模式使用(维度分数×权重)求和后取评委平均;自定义算式使用下方公式直接计算最终得分。变量格式: dimension_维度ID,如 dimension_1, dimension_2'
|
||
}),
|
||
)
|
||
|
||
actions = ['make_published', 'make_ended']
|
||
|
||
def make_published(self, request, queryset):
|
||
queryset.update(status='published')
|
||
make_published.short_description = "发布选中比赛"
|
||
|
||
def make_ended(self, request, queryset):
|
||
queryset.update(status='ended')
|
||
make_ended.short_description = "结束选中比赛"
|
||
|
||
@admin.register(CompetitionEnrollment)
|
||
class CompetitionEnrollmentAdmin(ModelAdmin):
|
||
list_display = ['competition', 'user_info_display', 'role', 'status', 'created_at']
|
||
list_filter = ['competition', 'role', 'status']
|
||
search_fields = ['user__nickname', 'user__phone_number', 'competition__title']
|
||
autocomplete_fields = ['user', 'competition']
|
||
actions = ['approve_enrollment', 'reject_enrollment']
|
||
|
||
@display(description="报名用户 (手机号/昵称)")
|
||
def user_info_display(self, obj):
|
||
if not obj.user:
|
||
return "-"
|
||
phone = obj.user.phone_number or "无手机号"
|
||
nickname = obj.user.nickname or "无昵称"
|
||
return f"{phone} ({nickname})"
|
||
|
||
def approve_enrollment(self, request, queryset):
|
||
queryset.update(status='approved')
|
||
approve_enrollment.short_description = "通过审核"
|
||
|
||
def reject_enrollment(self, request, queryset):
|
||
queryset.update(status='rejected')
|
||
reject_enrollment.short_description = "拒绝申请"
|
||
|
||
@admin.register(Project)
|
||
class ProjectAdmin(ModelAdmin):
|
||
list_display = ['id', 'title', 'competition', 'contestant_info_display', 'status', 'final_score', 'created_at']
|
||
list_filter = ['competition', 'status']
|
||
search_fields = ['id', 'title', 'contestant__user__nickname', 'contestant__user__phone_number']
|
||
autocomplete_fields = ['competition', 'contestant']
|
||
inlines = [ProjectFileInline]
|
||
readonly_fields = ['id', 'final_score']
|
||
|
||
fieldsets = (
|
||
('基本信息', {
|
||
'fields': ('competition', 'contestant', 'title', 'description', 'team_info')
|
||
}),
|
||
('封面设置', {
|
||
'fields': ('cover_image', 'cover_image_url'),
|
||
'description': '封面图可以上传本地图片,也可以填写外部链接,优先显示本地上传的图片'
|
||
}),
|
||
('状态和得分', {
|
||
'fields': ('status', 'final_score')
|
||
}),
|
||
)
|
||
|
||
@display(description="参赛人员 (手机号/昵称)")
|
||
def contestant_info_display(self, obj):
|
||
if not obj.contestant or not obj.contestant.user:
|
||
return "-"
|
||
user = obj.contestant.user
|
||
phone = user.phone_number or "无手机号"
|
||
nickname = user.nickname or "无昵称"
|
||
return f"{phone} ({nickname})"
|
||
|
||
@admin.register(Score)
|
||
class ScoreAdmin(ModelAdmin):
|
||
list_display = ['project', 'judge_info_display', 'dimension', 'score', 'created_at']
|
||
list_filter = ['project__competition', 'dimension']
|
||
search_fields = ['project__title', 'judge__user__nickname', 'judge__user__phone_number']
|
||
autocomplete_fields = ['project', 'judge']
|
||
|
||
@display(description="评委 (手机号/昵称)")
|
||
def judge_info_display(self, obj):
|
||
if not obj.judge or not obj.judge.user:
|
||
return "-"
|
||
user = obj.judge.user
|
||
phone = user.phone_number or "无手机号"
|
||
nickname = user.nickname or "无昵称"
|
||
return f"{phone} ({nickname})"
|
||
|
||
@admin.register(Comment)
|
||
class CommentAdmin(ModelAdmin):
|
||
list_display = ['project', 'judge_info_display', 'content_preview', 'created_at']
|
||
list_filter = ['project__competition']
|
||
search_fields = ['project__title', 'judge__user__nickname', 'judge__user__phone_number', 'content']
|
||
autocomplete_fields = ['project', 'judge']
|
||
|
||
@display(description="评委 (手机号/昵称)")
|
||
def judge_info_display(self, obj):
|
||
if not obj.judge or not obj.judge.user:
|
||
return "-"
|
||
user = obj.judge.user
|
||
phone = user.phone_number or "无手机号"
|
||
nickname = user.nickname or "无昵称"
|
||
return f"{phone} ({nickname})"
|
||
|
||
def content_preview(self, obj):
|
||
return obj.content[:50] + '...' if len(obj.content) > 50 else obj.content
|
||
content_preview.short_description = "评语内容"
|
||
|
||
|
||
class ScoreFormulaAdmin(ModelAdmin):
|
||
"""
|
||
评分公式管理
|
||
提供可视化公式编辑功能
|
||
"""
|
||
list_display = ['name', 'competition', 'formula_preview_display', 'is_active', 'is_default', 'created_at']
|
||
list_filter = ['competition', 'is_active', 'is_default']
|
||
search_fields = ['name', 'description', 'formula', 'competition__title']
|
||
autocomplete_fields = ['competition']
|
||
|
||
fieldsets = (
|
||
('基本信息', {
|
||
'fields': ('competition', 'name', 'description')
|
||
}),
|
||
('公式配置', {
|
||
'fields': ('formula',),
|
||
'description': '使用维度名称作为变量,例如: 创新性 * 0.3 + 实用性 * 0.5 + 演示效果 * 0.2'
|
||
}),
|
||
('公式设置', {
|
||
'fields': ('is_active', 'is_default')
|
||
}),
|
||
)
|
||
|
||
@display(description="公式预览")
|
||
def formula_preview_display(self, obj):
|
||
preview = obj.get_formula_preview()
|
||
return preview[:100] + '...' if len(preview) > 100 else preview if preview else '-'
|
||
|
||
def get_form_kwargs(self, request, *args, **kwargs):
|
||
kwargs = super().get_form_kwargs(request, *args, **kwargs)
|
||
return kwargs
|
||
|
||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||
extra_context = extra_context or {}
|
||
|
||
if request.method == 'GET' and not object_id:
|
||
competition_id = request.GET.get('competition')
|
||
if competition_id:
|
||
try:
|
||
from .models import ScoreDimension
|
||
dimensions = ScoreDimension.objects.filter(competition_id=competition_id)
|
||
extra_context['dimensions'] = dimensions
|
||
except:
|
||
pass
|
||
|
||
return super().changeform_view(request, object_id, form_url, extra_context)
|
||
|
||
class Media:
|
||
css = {
|
||
'all': ('competition/admin/css/formula-editor.css',)
|
||
}
|
||
js = ('competition/admin/js/formula-editor.js',)
|
||
|
||
|
||
admin.site.register(ScoreFormula, ScoreFormulaAdmin)
|