from django.contrib import admin from django.utils.translation import gettext_lazy as _ from unfold.admin import ModelAdmin, StackedInline, TabularInline from unfold.decorators import display, action from apps.cms.models.post import ( PostCategory, PostTag, Post, PostRevision, PostComment, PostLike, MediaAsset, ) from utils.admin import project_admin_site class PostRevisionInline(TabularInline): model = PostRevision extra = 0 fields = ('id', 'author', 'title', 'status', 'reviewed_by', 'reviewed_at', 'created_at') readonly_fields = ('id', 'author', 'title', 'status', 'reviewed_by', 'reviewed_at', 'created_at') show_change_link = True class PostCommentInline(TabularInline): model = PostComment extra = 0 fields = ('author_name', 'author_email', 'user', 'content', 'status', 'created_at') readonly_fields = ('created_at',) @admin.register(Post, site=project_admin_site) class PostAdmin(ModelAdmin): list_display = ( 'title', 'post_type_badge', 'language', 'category', 'status_badge', 'author_type', 'author', 'institution', 'views_count', 'likes_count', 'is_featured', 'publish_date', ) list_display_links = ('title', 'post_type_badge', 'category', 'author') list_filter = ('post_type', 'author_type', 'language', 'status', 'is_featured', 'category', 'institution') search_fields = ('title', 'excerpt', 'content', 'author__email', 'author__fullname', 'institution__name') prepopulated_fields = {'slug': ('title',)} readonly_fields = ('reading_time_minutes', 'views_count', 'likes_count', 'reviewed_at', 'created_at', 'updated_at') inlines = [PostRevisionInline, PostCommentInline] actions = ['publish_posts', 'mark_pending_review', 'archive_posts', 'toggle_featured'] fieldsets = ( (_('Article Content'), { 'fields': ('title', 'slug', 'post_type', 'category', 'language', 'status', 'is_featured') }), (_('Editorial & Moderation'), { 'fields': ('rejection_reason', 'reviewed_by', 'reviewed_at') }), (_('Author & Affiliation'), { 'fields': ('author_type', 'author', 'institution', 'author_role_label', 'author_custom_avatar') }), (_('Media & Excerpt'), { 'fields': ('featured_image', 'excerpt', 'content', 'tags') }), (_('Metrics & Scheduling'), { 'fields': ('reading_time_minutes', 'views_count', 'likes_count', 'publish_date', 'created_at', 'updated_at') }), ) @display(description=_('Type')) def post_type_badge(self, obj): return obj.get_post_type_display() @display(description=_('Status'), label={ Post.Status.PUBLISHED: "success", Post.Status.PENDING_REVIEW: "warning", Post.Status.DRAFT: "info", Post.Status.REJECTED: "danger", Post.Status.ARCHIVED: "secondary", }) def status_badge(self, obj): return obj.get_status_display() @action(description=_('Publish selected posts')) def publish_posts(self, request, queryset): count = queryset.update(status=Post.Status.PUBLISHED) self.message_user(request, _(f"{count} post(s) published successfully.")) @action(description=_('Mark selected posts as Pending Review')) def mark_pending_review(self, request, queryset): count = queryset.update(status=Post.Status.PENDING_REVIEW) self.message_user(request, _(f"{count} post(s) marked as pending review.")) @action(description=_('Archive selected posts')) def archive_posts(self, request, queryset): count = queryset.update(status=Post.Status.ARCHIVED) self.message_user(request, _(f"{count} post(s) archived.")) @action(description=_('Toggle featured status for selected posts')) def toggle_featured(self, request, queryset): for item in queryset: item.is_featured = not item.is_featured item.save(update_fields=['is_featured']) self.message_user(request, _("Featured status updated.")) @admin.register(PostCategory, site=project_admin_site) class PostCategoryAdmin(ModelAdmin): list_display = ('name', 'slug', 'language', 'created_at') list_display_links = ('name', 'slug') list_filter = ('language',) search_fields = ('name', 'description') prepopulated_fields = {'slug': ('name',)} @admin.register(PostTag, site=project_admin_site) class PostTagAdmin(ModelAdmin): list_display = ('name', 'slug') list_display_links = ('name', 'slug') search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} @admin.register(PostComment, site=project_admin_site) class PostCommentAdmin(ModelAdmin): list_display = ('author_display', 'post', 'status_badge', 'created_at') list_display_links = ('author_display', 'post') list_filter = ('status', 'created_at') search_fields = ('author_name', 'author_email', 'content', 'post__title') actions = ['approve_comments', 'mark_as_spam'] @display(description=_('Author')) def author_display(self, obj): return obj.author_name or (obj.user.fullname if obj.user else "Anonymous") @display(description=_('Status'), label={ PostComment.Status.APPROVED: "success", PostComment.Status.PENDING: "warning", PostComment.Status.SPAM: "danger", }) def status_badge(self, obj): return obj.get_status_display() @action(description=_('Approve selected comments')) def approve_comments(self, request, queryset): count = queryset.update(status=PostComment.Status.APPROVED) self.message_user(request, _(f"{count} comment(s) approved.")) @action(description=_('Mark selected comments as spam')) def mark_as_spam(self, request, queryset): count = queryset.update(status=PostComment.Status.SPAM) self.message_user(request, _(f"{count} comment(s) marked as spam.")) @admin.register(PostLike, site=project_admin_site) class PostLikeAdmin(ModelAdmin): list_display = ('user', 'post', 'created_at') list_display_links = ('user', 'post') search_fields = ('user__email', 'user__fullname', 'post__title') readonly_fields = ('created_at',) @admin.register(MediaAsset, site=project_admin_site) class MediaAssetAdmin(ModelAdmin): list_display = ('title', 'media_type', 'file_size', 'uploaded_by', 'created_at') list_display_links = ('title', 'uploaded_by') list_filter = ('media_type', 'created_at') search_fields = ('title', 'uploaded_by__email') readonly_fields = ('created_at',) @admin.register(PostRevision, site=project_admin_site) class PostRevisionAdmin(ModelAdmin): list_display = ('id', 'post', 'author', 'status_badge', 'created_at', 'reviewed_by', 'reviewed_at') list_display_links = ('id', 'post') list_filter = ('status', 'created_at') search_fields = ('title', 'content', 'author__email', 'post__title') readonly_fields = ('created_at', 'updated_at', 'reviewed_at') actions = ['approve_revisions'] @display(description=_('Status'), label={ PostRevision.Status.APPROVED: "success", PostRevision.Status.PENDING: "warning", PostRevision.Status.REJECTED: "danger", }) def status_badge(self, obj): return obj.get_status_display() @action(description=_('Approve and merge selected revisions')) def approve_revisions(self, request, queryset): count = 0 for rev in queryset.filter(status=PostRevision.Status.PENDING): rev.apply_to_post(editor=request.user) count += 1 self.message_user(request, _(f"{count} revision(s) approved and merged into live posts."))