from django import forms from django.contrib import admin from import_export.admin import ImportExportModelAdmin from simplemde.widgets import SimpleMDEEditor 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', 'parent', 'slug', 'created_at', 'is_deleted') list_filter = ('created_at', 'is_deleted', SoftDeleteListFilter) search_fields = ('name', 'description', 'parent__name') prepopulated_fields = {'slug': ('name',)} readonly_fields = ('created_at', 'updated_at', 'deleted_at') fieldsets = ( ('Content', { 'fields': ('name', 'parent', 'slug', 'description') }), ('Metadata', { 'fields': ('created_at', 'updated_at') }), ('Soft Delete', { 'fields': ('is_deleted', 'deleted_at'), 'classes': ('collapse',) }) ) actions = BaseModelAdmin.actions + ['restore_categories'] def restore_categories(self, request, queryset): for category in queryset: category.restore() self.message_user(request, f"Restored {queryset.count()} categories.") restore_categories.short_description = "Restore selected categories" @admin.register(Tag) class TagAdmin(BaseModelAdmin, ImportExportModelAdmin): list_display = ('name', 'slug', 'created_at', 'is_deleted') list_filter = ('created_at', 'is_deleted', SoftDeleteListFilter) search_fields = ('name',) prepopulated_fields = {'slug': ('name',)} readonly_fields = ('created_at', 'updated_at', 'deleted_at') fieldsets = ( ('Content', { 'fields': ('name', 'slug') }), ('Metadata', { 'fields': ('created_at', 'updated_at') }), ('Soft Delete', { 'fields': ('is_deleted', 'deleted_at'), 'classes': ('collapse',) }) ) class PostAdminForm(forms.ModelForm): content = forms.CharField(widget=SimpleMDEEditor()) excerpt = forms.CharField(widget=SimpleMDEEditor()) class Meta: model = Post fields = '__all__' @admin.register(Post) class PostAdmin(BaseModelAdmin, ImportExportModelAdmin): form = PostAdminForm resource_class = PostResource list_display = ('title', 'author', 'status', 'category', 'is_featured', 'submitted_at', 'published_at', 'created_at') 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', 'writers') date_hierarchy = 'published_at' fieldsets = ( ('Content', { 'fields': ('title', 'slug', 'content', 'excerpt', 'featured_image') }), ('SEO', { 'fields': ('seo_title', 'seo_description', 'canonical_url', 'og_title', 'og_description', 'og_image', 'noindex', 'focus_keyword', 'reading_time') }), ('Metadata', { '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'), 'classes': ('collapse',) }), ) readonly_fields = ('deleted_at', 'content_html', 'reading_time') actions = BaseModelAdmin.actions + ['submit_for_review', 'request_changes', 'make_published', 'make_draft', 'make_featured', 'restore_posts'] def submit_for_review(self, request, queryset): queryset.update(status='submitted') self.message_user(request, f"Submitted {queryset.count()} posts for review.") submit_for_review.short_description = "Submit selected posts for review" def request_changes(self, request, queryset): queryset.update(status='changes_requested', reviewed_by=request.user) self.message_user(request, f"Requested changes on {queryset.count()} posts.") request_changes.short_description = "Request changes on selected posts" def make_published(self, request, queryset): queryset.update(status='published', published_by=request.user) self.message_user(request, f"Published {queryset.count()} posts.") make_published.short_description = "Mark selected posts as published" def make_draft(self, request, queryset): queryset.update(status='draft') self.message_user(request, f"Marked {queryset.count()} posts as draft.") make_draft.short_description = "Mark selected posts as draft" def make_featured(self, request, queryset): queryset.update(is_featured=True) self.message_user(request, f"Featured {queryset.count()} posts.") make_featured.short_description = "Mark selected posts as featured" def restore_posts(self, request, queryset): for post in queryset: post.restore() self.message_user(request, f"Restored {queryset.count()} posts.") restore_posts.short_description = "Restore selected posts" @admin.register(Comment) class CommentAdmin(BaseModelAdmin): 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') fieldsets = ( ('Content', { 'fields': ('post', 'author', 'content') }), ('Metadata', { 'fields': ('is_approved', 'is_hidden', 'hidden_by', 'hidden_at', 'moderation_note', 'created_at', 'updated_at') }), ('Soft Delete', { 'fields': ('is_deleted', 'deleted_at', 'deleted_by', 'delete_note'), 'classes': ('collapse',) }) ) 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, 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 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): list_display = ('user', 'post', 'created_at') list_filter = ('created_at', 'post') search_fields = ('user__username', 'post__title') @admin.register(SavedPost) class SavedPostAdmin(admin.ModelAdmin): list_display = ('user', 'post', 'created_at') list_filter = ('created_at', 'post') search_fields = ('user__username', 'post__title') @admin.register(PostAsset) class PostAssetAdmin(BaseModelAdmin): list_display = ('title', 'post', 'file_type', 'mime_type', 'size', 'uploaded_by', 'created_at') 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',) }), )