feat(blog): expand publishing and moderation APIs

This commit is contained in:
2026-06-11 21:20:44 +03:30
parent 4039be0187
commit 5045f8da47
7 changed files with 600 additions and 42 deletions

View File

@@ -4,22 +4,22 @@ from django.contrib import admin
from import_export.admin import ImportExportModelAdmin
from simplemde.widgets import SimpleMDEEditor
from apps.blog.models import Category, Tag, Post, PostAsset, Comment, Like, SavedPost
from apps.blog.models import BlogBanner, Category, Tag, Post, PostAsset, Comment, Like, SavedPost
from apps.blog.resources import PostResource, CategoryResource
from core.admin import SoftDeleteListFilter, BaseModelAdmin
@admin.register(Category)
class CategoryAdmin(BaseModelAdmin, ImportExportModelAdmin):
resource_class = CategoryResource
list_display = ('name', 'slug', 'created_at', 'is_deleted')
list_display = ('name', 'parent', 'slug', 'created_at', 'is_deleted')
list_filter = ('created_at', 'is_deleted', SoftDeleteListFilter)
search_fields = ('name', 'description')
search_fields = ('name', 'description', 'parent__name')
prepopulated_fields = {'slug': ('name',)}
readonly_fields = ('created_at', 'updated_at', 'deleted_at')
fieldsets = (
('Content', {
'fields': ('name', 'slug', 'description')
'fields': ('name', 'parent', 'slug', 'description')
}),
('Metadata', {
'fields': ('created_at', 'updated_at')
@@ -76,7 +76,7 @@ class PostAdmin(BaseModelAdmin, ImportExportModelAdmin):
list_filter = ('status', 'is_featured', 'category', 'tags', 'created_at', 'published_at', SoftDeleteListFilter)
search_fields = ('title', 'content', 'author__username')
prepopulated_fields = {'slug': ('title',)}
filter_horizontal = ('tags',)
filter_horizontal = ('tags', 'writers')
date_hierarchy = 'published_at'
fieldsets = (
@@ -87,7 +87,7 @@ class PostAdmin(BaseModelAdmin, ImportExportModelAdmin):
'fields': ('seo_title', 'seo_description', 'canonical_url', 'og_title', 'og_description', 'og_image', 'noindex', 'focus_keyword', 'reading_time')
}),
('Metadata', {
'fields': ('author', 'category', 'tags', 'status', 'is_featured', 'submitted_at', 'reviewed_at', 'reviewed_by', 'review_note', 'published_at', 'published_by')
'fields': ('author', 'writers', 'category', 'tags', 'status', 'is_featured', 'submitted_at', 'reviewed_at', 'reviewed_by', 'review_note', 'published_at', 'published_by')
}),
('Soft Delete', {
'fields': ('is_deleted', 'deleted_at'),
@@ -132,8 +132,8 @@ class PostAdmin(BaseModelAdmin, ImportExportModelAdmin):
@admin.register(Comment)
class CommentAdmin(BaseModelAdmin):
list_display = ('author', 'post', 'content_preview', 'is_approved', 'created_at')
list_filter = ('is_approved', 'created_at', 'post', SoftDeleteListFilter)
list_display = ('author', 'post', 'content_preview', 'is_approved', 'is_hidden', 'is_deleted', 'created_at')
list_filter = ('is_approved', 'is_hidden', 'created_at', 'post', SoftDeleteListFilter)
search_fields = ('content', 'author__username', 'author__last_name', 'author__first_name', 'post__title')
readonly_fields = ('content_preview', 'created_at', 'updated_at', 'deleted_at')
@@ -142,28 +142,35 @@ class CommentAdmin(BaseModelAdmin):
'fields': ('post', 'author', 'content')
}),
('Metadata', {
'fields': ('is_approved', 'hidden_by', 'hidden_at', 'moderation_note', 'created_at', 'updated_at')
'fields': ('is_approved', 'is_hidden', 'hidden_by', 'hidden_at', 'moderation_note', 'created_at', 'updated_at')
}),
('Soft Delete', {
'fields': ('is_deleted', 'deleted_at'),
'fields': ('is_deleted', 'deleted_at', 'deleted_by', 'delete_note'),
'classes': ('collapse',)
})
)
actions = BaseModelAdmin.actions + ['approve_comments', 'disapprove_comments']
actions = BaseModelAdmin.actions + ['approve_comments', 'hide_comments', 'unhide_comments']
def content_preview(self, obj):
return obj.content[:50] + '...' if len(obj.content) > 50 else obj.content
content_preview.short_description = 'Content Preview'
def approve_comments(self, request, queryset):
queryset.update(is_approved=True)
queryset.update(is_approved=True, is_hidden=False, hidden_by=None, hidden_at=None, moderation_note='')
self.message_user(request, f"Approved {queryset.count()} comments.")
approve_comments.short_description = "Approve selected comments"
def disapprove_comments(self, request, queryset):
queryset.update(is_approved=False)
self.message_user(request, f"Disapproved {queryset.count()} comments.")
disapprove_comments.short_description = "Disapprove selected comments"
def hide_comments(self, request, queryset):
for comment in queryset:
comment.hide(request.user)
self.message_user(request, f"Hidden {queryset.count()} comments.")
hide_comments.short_description = "Hide selected comments"
def unhide_comments(self, request, queryset):
for comment in queryset:
comment.unhide()
self.message_user(request, f"Restored {queryset.count()} comments.")
unhide_comments.short_description = "Unhide selected comments"
@admin.register(Like)
class LikeAdmin(admin.ModelAdmin):
@@ -185,3 +192,24 @@ class PostAssetAdmin(BaseModelAdmin):
list_filter = ('file_type', 'mime_type', 'created_at')
search_fields = ('title', 'caption', 'alt_text', 'post__title', 'uploaded_by__username')
readonly_fields = ('size', 'mime_type', 'created_at', 'updated_at', 'deleted_at')
@admin.register(BlogBanner)
class BlogBannerAdmin(BaseModelAdmin):
list_display = ('title', 'url', 'is_active', 'sort_order', 'created_at', 'is_deleted')
list_filter = ('is_active', 'created_at', 'is_deleted', SoftDeleteListFilter)
search_fields = ('title', 'alt_text', 'url')
readonly_fields = ('created_at', 'updated_at', 'deleted_at')
fieldsets = (
('Banner', {
'fields': ('title', 'alt_text', 'image', 'url', 'is_active', 'sort_order')
}),
('Metadata', {
'fields': ('created_at', 'updated_at')
}),
('Soft Delete', {
'fields': ('is_deleted', 'deleted_at'),
'classes': ('collapse',)
}),
)