220 lines
7.1 KiB
Python
220 lines
7.1 KiB
Python
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': ('start_time', 'end_time', 'location'),
|
|
'classes': ('tab',)
|
|
}),
|
|
('报名设置', {
|
|
'fields': ('max_participants',)
|
|
}),
|
|
)
|
|
|
|
@display(description="Banner")
|
|
def banner_display(self, obj):
|
|
if obj.banner:
|
|
return format_html('<img src="{}" height="50" style="border-radius: 4px;" />', obj.banner.url)
|
|
elif obj.banner_url:
|
|
return format_html('<img src="{}" height="50" style="border-radius: 4px;" />', 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')
|
|
list_filter = ('status', 'signup_time', 'activity')
|
|
search_fields = ('user__nickname', 'activity__title')
|
|
autocomplete_fields = ['activity', 'user']
|
|
|
|
fieldsets = (
|
|
('报名详情', {
|
|
'fields': ('activity', 'user', 'status')
|
|
}),
|
|
('时间信息', {
|
|
'fields': ('signup_time',),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
readonly_fields = ('signup_time',)
|
|
|
|
@display(
|
|
description="状态",
|
|
label={
|
|
"pending": "warning",
|
|
"confirmed": "success",
|
|
"cancelled": "danger",
|
|
}
|
|
)
|
|
def status_label(self, obj):
|
|
return obj.status
|
|
|
|
@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('<img src="{}" height="50" style="border-radius: 4px;" />', 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('<img src="{}" height="50" style="border-radius: 4px;" />', 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
|