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_active', 'created_at')
list_filter = ('is_active', 'start_time')
search_fields = ('title', 'location')
inlines = [ActivitySignupInline]
fieldsets = (
('基本信息', {
'fields': ('title', 'description', 'banner', 'banner_url', 'is_active')
}),
('费用与时间', {
'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",
}
)
def status_label(self, obj):
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', 'created_at')
list_filter = ('created_at',)
search_fields = ('content', 'author__nickname', 'topic__title')
autocomplete_fields = ['author', 'topic', 'reply_to']
inlines = [TopicMediaInline]
fieldsets = (
('回复内容', {
'fields': ('topic', 'reply_to', 'content')
}),
('发布信息', {
'fields': ('author', '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('