from django.contrib import admin from django.utils.html import format_html from unfold.admin import ModelAdmin, TabularInline from unfold.decorators import display from .models import Activity, ActivitySignup, Topic, Reply, TopicMedia, Announcement class ActivitySignupInline(TabularInline): model = ActivitySignup extra = 0 readonly_fields = ('signup_time',) fields = ('user', 'status', 'signup_time') autocomplete_fields = ['user'] can_delete = True show_change_link = True class ReplyInline(TabularInline): model = Reply extra = 0 readonly_fields = ('created_at',) fields = ('content', 'author', 'created_at') can_delete = True show_change_link = True class TopicMediaInline(TabularInline): model = TopicMedia extra = 0 fields = ('file', 'file_url', 'media_type', 'created_at') readonly_fields = ('created_at',) can_delete = True @admin.register(Activity) class ActivityAdmin(ModelAdmin): list_display = ('title', 'banner_display', 'start_time', 'location', 'signup_count', 'is_visible', 'is_active', 'auto_confirm', 'created_at') list_filter = ('is_visible', 'is_active', 'auto_confirm', 'start_time') search_fields = ('title', 'location') inlines = [ActivitySignupInline] fieldsets = ( ('基本信息', { 'fields': ('title', 'description', 'banner', 'banner_url', 'is_visible', 'is_active', 'auto_confirm') }), ('费用与时间', { 'fields': ('is_paid', 'price', 'start_time', 'end_time', 'location'), 'classes': ('tab',) }), ('报名设置', { 'fields': ('max_participants', 'ask_name', 'ask_phone', 'ask_wechat', 'ask_company', 'signup_form_config'), 'description': '勾选需要收集的信息,或者在下方“自定义报名配置”中填写高级JSON配置' }), ) @display(description="Banner") def banner_display(self, obj): if obj.banner: return format_html('', obj.banner.url) elif obj.banner_url: return format_html('', obj.banner_url) return "暂无" @display(description="报名人数") def signup_count(self, obj): return obj.signups.count() @admin.register(ActivitySignup) class ActivitySignupAdmin(ModelAdmin): list_display = ('activity', 'user', 'signup_time', 'status_label', 'order_link') list_filter = ('status', 'signup_time', 'activity') search_fields = ('user__nickname', 'activity__title') autocomplete_fields = ['activity', 'user'] fieldsets = ( ('报名详情', { 'fields': ('activity', 'user', 'status', 'order', 'signup_info_display') }), ('时间信息', { 'fields': ('signup_time',), 'classes': ('collapse',) }), ) readonly_fields = ('signup_time', 'signup_info_display') @display(description="报名信息") def signup_info_display(self, obj): import json if not obj.signup_info: return "无" try: # Format JSON nicely formatted_json = json.dumps(obj.signup_info, indent=2, ensure_ascii=False) return format_html('
{}
', formatted_json) except: return str(obj.signup_info) @display( description="状态", label={ "pending": "warning", "confirmed": "success", "cancelled": "danger", "unpaid": "secondary", } ) def status_label(self, obj): # Auto sync with order status on display if obj.check_payment_status(): # If status changed, return new status return obj.status return obj.status @display(description="关联订单") def order_link(self, obj): if obj.order: return format_html('Order #{}', obj.order.id, obj.order.id) return "-" @admin.register(Topic) class TopicAdmin(ModelAdmin): list_display = ('title', 'category', 'author', 'get_related_item', 'reply_count', 'view_count', 'is_pinned', 'created_at') list_filter = ('category', 'is_pinned', 'created_at', 'related_product', 'related_service', 'related_course') search_fields = ('title', 'content', 'author__nickname') autocomplete_fields = ['author', 'related_product', 'related_service', 'related_course'] inlines = [TopicMediaInline, ReplyInline] fieldsets = ( ('帖子内容', { 'fields': ('title', 'category', 'content', 'is_pinned') }), ('关联信息', { 'fields': ('author', 'related_product', 'related_service', 'related_course'), 'description': '可关联 硬件、服务 或 课程,用于技术求助或讨论' }), ('统计数据', { 'fields': ('view_count', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = ('created_at', 'updated_at') @display(description="关联项目") def get_related_item(self, obj): if obj.related_product: return f"[硬件] {obj.related_product.name}" if obj.related_service: return f"[服务] {obj.related_service.title}" if obj.related_course: return f"[课程] {obj.related_course.title}" return "-" @display(description="回复数") def reply_count(self, obj): return obj.replies.count() @admin.register(Reply) class ReplyAdmin(ModelAdmin): list_display = ('short_content', 'topic', 'author', 'is_pinned', 'created_at') list_filter = ('is_pinned', 'created_at') search_fields = ('content', 'author__nickname', 'topic__title') autocomplete_fields = ['author', 'topic', 'reply_to'] list_editable = ('is_pinned',) inlines = [TopicMediaInline] fieldsets = ( ('回复内容', { 'fields': ('topic', 'reply_to', 'content') }), ('发布信息', { 'fields': ('author', 'is_pinned', 'created_at') }), ) readonly_fields = ('created_at',) @display(description="内容摘要") def short_content(self, obj): return obj.content[:30] + '...' if len(obj.content) > 30 else obj.content @admin.register(TopicMedia) class TopicMediaAdmin(ModelAdmin): list_display = ('id', 'media_type', 'file_preview', 'topic', 'reply', 'created_at') list_filter = ('media_type', 'created_at') search_fields = ('file', 'topic__title') autocomplete_fields = ['topic', 'reply'] @display(description="预览") def file_preview(self, obj): url = "" if obj.file: url = obj.file.url elif obj.file_url: url = obj.file_url if obj.media_type == 'image' and url: return format_html('', url) return obj.file.name or "外部文件" @admin.register(Announcement) class AnnouncementAdmin(ModelAdmin): list_display = ('title', 'image_preview', 'active_label', 'pinned_label', 'priority', 'start_time', 'end_time', 'created_at') list_filter = ('is_active', 'is_pinned', 'created_at') search_fields = ('title', 'content') fieldsets = ( ('公告信息', { 'fields': ('title', 'content', 'link_url') }), ('图片设置', { 'fields': ('image', 'image_url'), 'description': '上传图片或填写图片链接,优先显示上传的图片' }), ('显示设置', { 'fields': ('is_active', 'is_pinned', 'priority'), 'classes': ('tab',) }), ('排期设置', { 'fields': ('start_time', 'end_time'), 'classes': ('tab',) }), ) @display(description="图片预览") def image_preview(self, obj): url = obj.display_image_url if url: return format_html('', url) return "无图片" @display( description="状态", label={ True: "success", False: "danger", } ) def active_label(self, obj): return obj.is_active @display( description="置顶", label={ True: "warning", False: "default", } ) def pinned_label(self, obj): return obj.is_pinned