This commit is contained in:
@@ -153,10 +153,11 @@ class TopicAdmin(ModelAdmin):
|
||||
|
||||
@admin.register(Reply)
|
||||
class ReplyAdmin(ModelAdmin):
|
||||
list_display = ('short_content', 'topic', 'author', 'created_at')
|
||||
list_filter = ('created_at',)
|
||||
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 = (
|
||||
@@ -164,7 +165,7 @@ class ReplyAdmin(ModelAdmin):
|
||||
'fields': ('topic', 'reply_to', 'content')
|
||||
}),
|
||||
('发布信息', {
|
||||
'fields': ('author', 'created_at')
|
||||
'fields': ('author', 'is_pinned', 'created_at')
|
||||
}),
|
||||
)
|
||||
readonly_fields = ('created_at',)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-24 16:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('community', '0012_activity_is_visible'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='reply',
|
||||
options={'ordering': ['-is_pinned', '-created_at'], 'verbose_name': '帖子回复', 'verbose_name_plural': '帖子回复管理'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='reply',
|
||||
name='is_pinned',
|
||||
field=models.BooleanField(default=False, verbose_name='置顶'),
|
||||
),
|
||||
]
|
||||
@@ -186,6 +186,7 @@ class Reply(models.Model):
|
||||
content = models.TextField(verbose_name="回复内容", help_text="支持Markdown格式")
|
||||
author = models.ForeignKey(WeChatUser, on_delete=models.CASCADE, related_name='replies', verbose_name="回复者")
|
||||
reply_to = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children', verbose_name="回复楼层")
|
||||
is_pinned = models.BooleanField(default=False, verbose_name="置顶")
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="回复时间")
|
||||
|
||||
def __str__(self):
|
||||
@@ -194,7 +195,7 @@ class Reply(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "帖子回复"
|
||||
verbose_name_plural = "帖子回复管理"
|
||||
ordering = ['created_at']
|
||||
ordering = ['-is_pinned', '-created_at']
|
||||
|
||||
|
||||
class TopicMedia(models.Model):
|
||||
|
||||
@@ -91,7 +91,7 @@ class ReplySerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Reply
|
||||
fields = ['id', 'topic', 'content', 'author', 'author_info', 'reply_to', 'media', 'created_at', 'media_ids']
|
||||
fields = ['id', 'topic', 'content', 'author', 'author_info', 'reply_to', 'media', 'created_at', 'media_ids', 'is_pinned']
|
||||
read_only_fields = ['author', 'created_at']
|
||||
|
||||
def create(self, validated_data):
|
||||
|
||||
@@ -101,19 +101,7 @@ DATABASES = {
|
||||
|
||||
#从环境变量获取数据库配置 (Docker 环境会自动注入这些变量。
|
||||
|
||||
DB_HOST = os.environ.get('DB_HOST', '6.6.6.66')
|
||||
if DB_HOST:
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.environ.get('DB_NAME', 'market'),
|
||||
'USER': os.environ.get('DB_USER', 'market'),
|
||||
'PASSWORD': os.environ.get('DB_PASSWORD', '123market'),
|
||||
'HOST': DB_HOST,
|
||||
'PORT': os.environ.get('DB_PORT', '5432'),
|
||||
}
|
||||
|
||||
|
||||
# DB_HOST = os.environ.get('DB_HOST', '121.43.104.161')
|
||||
# DB_HOST = os.environ.get('DB_HOST', '6.6.6.66')
|
||||
# if DB_HOST:
|
||||
# DATABASES['default'] = {
|
||||
# 'ENGINE': 'django.db.backends.postgresql',
|
||||
@@ -121,10 +109,22 @@ if DB_HOST:
|
||||
# 'USER': os.environ.get('DB_USER', 'market'),
|
||||
# 'PASSWORD': os.environ.get('DB_PASSWORD', '123market'),
|
||||
# 'HOST': DB_HOST,
|
||||
# 'PORT': os.environ.get('DB_PORT', '6433'),
|
||||
# 'PORT': os.environ.get('DB_PORT', '5432'),
|
||||
# }
|
||||
|
||||
|
||||
DB_HOST = os.environ.get('DB_HOST', '121.43.104.161')
|
||||
if DB_HOST:
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.environ.get('DB_NAME', 'market'),
|
||||
'USER': os.environ.get('DB_USER', 'market'),
|
||||
'PASSWORD': os.environ.get('DB_PASSWORD', '123market'),
|
||||
'HOST': DB_HOST,
|
||||
'PORT': os.environ.get('DB_PORT', '6433'),
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
||||
|
||||
|
||||
@@ -235,6 +235,29 @@ class VCCourseAdmin(OrderableAdminMixin, ModelAdmin):
|
||||
list_display = ('title', 'course_type', 'price', 'tag', 'instructor', 'lesson_count', 'duration', 'created_at', 'order_actions')
|
||||
search_fields = ('title', 'description', 'instructor', 'tag')
|
||||
list_filter = ('course_type', 'instructor', 'tag')
|
||||
actions = ['reset_ordering']
|
||||
|
||||
@admin.action(description="重置排序 (按ID顺序)")
|
||||
def reset_ordering(self, request, queryset):
|
||||
"""
|
||||
将选中的课程(或全部)按ID顺序重新分配order值
|
||||
"""
|
||||
# 如果没有选中任何项,默认处理所有(Django Admin默认行为是选中了才会触发Action,但为了稳健)
|
||||
# 这里既然是Action,用户必须选中。建议用户选中所有。
|
||||
# 为了方便,如果用户只选了一个,我们可以提示他选更多,或者我们其实可以忽略queryset,直接重置所有?
|
||||
# 通常Action是针对queryset的。
|
||||
# 更好的做法:对选中的queryset按ID排序,然后更新order。
|
||||
|
||||
# 这种实现方式:只重置选中的部分,可能会导致order冲突。
|
||||
# 稳妥方式:重置整个表的排序。
|
||||
|
||||
all_objects = VCCourse.objects.all().order_by('id')
|
||||
for index, obj in enumerate(all_objects, start=1):
|
||||
obj.order = index
|
||||
obj.save(update_fields=['order'])
|
||||
|
||||
self.message_user(request, f"成功重置了 {all_objects.count()} 个课程的排序权重。")
|
||||
|
||||
fieldsets = (
|
||||
('基本信息', {
|
||||
'fields': ('title', 'description', 'course_type', 'tag', 'price')
|
||||
|
||||
@@ -316,6 +316,7 @@ const ForumDetail = () => {
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||
<Space size={isMobile ? 'small' : 'middle'} align="center">
|
||||
<Text style={{ color: '#aaa', fontWeight: 'bold', fontSize: isMobile ? 13 : 14 }}>{reply.author_info?.nickname}</Text>
|
||||
{reply.is_pinned && <Tag color="red" style={{ margin: 0 }}>置顶</Tag>}
|
||||
<Text style={{ color: '#666', fontSize: 12 }}>{new Date(reply.created_at).toLocaleString()}</Text>
|
||||
<Button
|
||||
type="link"
|
||||
|
||||
@@ -230,7 +230,10 @@ const ForumDetail = () => {
|
||||
<View className='reply-main'>
|
||||
<View className='reply-header'>
|
||||
<View style={{display: 'flex', flexDirection: 'column'}}>
|
||||
<Text className='nickname'>{reply.author_info?.nickname}</Text>
|
||||
<View style={{display: 'flex', alignItems: 'center'}}>
|
||||
<Text className='nickname'>{reply.author_info?.nickname}</Text>
|
||||
{reply.is_pinned && <Text style={{color: '#ff4d4f', marginLeft: 4, fontSize: 10, border: '1px solid #ff4d4f', padding: '0 4px', borderRadius: 4}}>置顶</Text>}
|
||||
</View>
|
||||
<Text style={{fontSize: 10, color: '#666', marginTop: 2}}>#{idx + 1} • {new Date(reply.created_at).toLocaleDateString()}</Text>
|
||||
</View>
|
||||
<View style={{display: 'flex', alignItems: 'center'}}>
|
||||
|
||||
Reference in New Issue
Block a user