from django.contrib import admin from django.utils.html import format_html from unfold.admin import ModelAdmin from unfold.decorators import display from .models import Competition, CompetitionEnrollment, ScoreDimension, Project, ProjectFile, Score, Comment, HomePageConfig, CarouselItem class CarouselItemInline(admin.TabularInline): model = CarouselItem extra = 1 tab = True fields = ('carousel_type', 'image', 'image_url', 'title', 'subtitle', 'status', 'status_color', 'date', 'location', 'order', 'is_active') autocomplete_fields = [] @admin.register(HomePageConfig) class HomePageConfigAdmin(ModelAdmin): list_display = ['id', 'main_title', 'carousel1_title', 'carousel2_title', 'organizer', 'undertaker', 'is_active'] list_editable = ['main_title', 'carousel1_title', 'carousel2_title', 'organizer', 'undertaker', 'is_active'] fieldsets = ( ('首页Banner', { 'fields': ('banner_image', 'banner_image_url'), 'description': '首页顶部Banner图片,可以上传本地图片或填写URL' }), ('标题设置', { 'fields': ('main_title', 'carousel1_title', 'carousel2_title') }), ('主办单位', { 'fields': ('organizer', 'undertaker') }), ('状态', { 'fields': ('is_active',) }), ) @admin.register(CarouselItem) class CarouselItemAdmin(ModelAdmin): list_display = ['title', 'carousel_type', 'status', 'location', 'order', 'is_active', 'created_at'] list_filter = ['carousel_type', 'status', 'is_active'] search_fields = ['title', 'subtitle', 'location'] readonly_fields = ['image_preview'] fieldsets = ( ('轮播图类型', { 'fields': ('carousel_type',) }), ('图片设置', { 'fields': ('image', 'image_preview', 'image_url'), 'description': '优先使用本地上传的图片,上传后可预览' }), ('内容设置', { 'fields': ('title', 'subtitle', 'status', 'status_color', 'date', 'location') }), ('显示设置', { 'fields': ('order', 'is_active') }), ) @display(description='图片预览') def image_preview(self, obj): if obj.image: return format_html('', obj.image.url) elif obj.image_url: return format_html('', obj.image_url) return "暂无图片" class ScoreDimensionInline(admin.TabularInline): model = ScoreDimension extra = 1 tab = True fields = ('name', 'description', 'weight', 'max_score', 'is_public', 'is_peer_review', 'order') 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') }), ) 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 = "评语内容"