from django.contrib import admin from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from django.db.models import Count from unfold.admin import ModelAdmin, TabularInline from unfold.contrib.filters.admin import RangeNumericFilter, RangeDateTimeFilter from apps.chat.models import RoomMessage, ChatMessage, MessageReadStatus from utils.admin import project_admin_site class ChatMessageInline(TabularInline): model = ChatMessage extra = 0 fields = ('sender', 'content', 'content_type', 'sent_at', 'is_deleted') readonly_fields = ('sent_at',) can_delete = False show_change_link = True classes = ['collapse'] verbose_name = _("Message") verbose_name_plural = _("Messages") class MessageReadStatusAdmin(ModelAdmin): list_display = ( 'user', 'message', 'is_read_status', 'read_at', ) list_filter = ( ('read_at', RangeDateTimeFilter), 'is_read', ) search_fields = ('user__username', 'user__email', 'message__content') readonly_fields = ('read_at',) def is_read_status(self, obj): if obj.is_read: return format_html('Read') return format_html('Unread') is_read_status.short_description = _("Read Status") class RoomMessageAdmin(ModelAdmin): list_display = ( 'name', 'room_type_badge', 'course', 'initiator', 'messages_count', 'view_messages_button' ) list_filter = ( 'room_type', ('created_at', RangeDateTimeFilter), ('updated_at', RangeDateTimeFilter), 'course' ) search_fields = ('name', 'description', 'course__title', 'initiator__username', 'recipient__username') ordering = ('-created_at',) readonly_fields = ('created_at', 'updated_at', 'messages_count') inlines = [ChatMessageInline] fieldsets = ( (_("Room Information"), { 'fields': ('name', 'description', 'room_type', 'messages_count'), 'classes': ('grid-col-2',), }), (_("Relations"), { 'fields': ('course', 'initiator', 'recipient'), 'classes': ('grid-col-2',), }), (_("Timestamps"), { 'fields': ('created_at', 'updated_at'), 'classes': ('grid-col-2',), }), ) def messages_count(self, obj): count = obj.messages.count() return format_html('{}', count) messages_count.short_description = _("Messages Count") def room_type_badge(self, obj): if obj.room_type == 'group': return format_html('Group') return format_html('Private') room_type_badge.short_description = _("Room Type") def get_queryset(self, request): queryset = super().get_queryset(request) queryset = queryset.annotate( total_messages=Count('messages') ) return queryset def view_messages_button(self, obj): from django.urls import reverse url = f"{reverse('admin:chat_chatmessage_changelist')}?room__id__exact={obj.id}" return format_html( '' ' {}', url, _("View Messages") ) view_messages_button.short_description = _("Messages") class MessageReadStatusInline(TabularInline): model = MessageReadStatus extra = 0 fields = ('user', 'is_read', 'read_at') readonly_fields = ('read_at',) can_delete = False show_change_link = True classes = ['collapse'] verbose_name = _("Read Status") verbose_name_plural = _("Read Statuses") class ChatMessageAdmin(ModelAdmin): change_list_template = 'admin/chat/chatmessage/change_list.html' list_display = ( 'id', 'room', 'sender', 'content_type_badge', 'content_preview', 'content_size_display', 'has_attachment', 'sent_at', 'is_deleted_status' ) list_filter = ( 'room', 'content_type', 'is_deleted', ('sent_at', RangeDateTimeFilter), ('updated_at', RangeDateTimeFilter), ('content_size', RangeNumericFilter) ) search_fields = ('room__name', 'sender__username', 'content') ordering = ('-sent_at',) readonly_fields = ('sent_at', 'updated_at', 'content_size', 'attachment_preview') inlines = [MessageReadStatusInline] fieldsets = ( (_("Message Information"), { 'fields': ('room', 'sender', 'content', 'content_type'), 'classes': ('grid-col-2',), }), (_("Attachments"), { 'fields': ('file_attachment', 'image_attachment', 'attachment_preview'), 'classes': ('grid-col-2',), }), (_("Additional Info"), { 'fields': ('content_size',), 'classes': ('grid-col-1',), }), (_("Status"), { 'fields': ('is_deleted', 'deleted_at'), 'classes': ('grid-col-2',), }), (_("Timestamps"), { 'fields': ('sent_at', 'updated_at'), 'classes': ('grid-col-2',), }), ) def content_preview(self, obj): if obj.content_type == 'text': preview = obj.content[:50] + '...' if len(obj.content) > 50 else obj.content return preview return f"{obj.content_type} content" content_preview.short_description = _("Content Preview") def content_type_badge(self, obj): badges = { 'text': ('blue', 'Text'), 'file': ('yellow', 'File'), 'audio': ('green', 'Audio'), 'image': ('pink', 'Image'), } color, label = badges.get(obj.content_type, ('gray', obj.content_type)) return format_html( '{}', color, color, label ) content_type_badge.short_description = _("Type") def is_deleted_status(self, obj): if obj.is_deleted: return format_html('Deleted') return format_html('Active') is_deleted_status.short_description = _("Status") def content_size_display(self, obj): if obj.content_size: # Format size in KB if larger than 1024 bytes if obj.content_size > 1024: size_kb = obj.content_size / 1024 return f"{size_kb:.1f} KB" return f"{obj.content_size} bytes" return "-" content_size_display.short_description = _("Size") def has_attachment(self, obj): """Show if message has file/image attachment""" if obj.image_attachment: return format_html( '📷 Image' ) elif obj.file_attachment: return format_html( '📎 File' ) elif obj.content and obj.content_type != 'text': return format_html( '🔗 Legacy' ) return "-" has_attachment.short_description = _("Attachment") def attachment_preview(self, obj): """Display attachment preview in detail view""" if obj.image_attachment: return format_html( '
', obj.image_attachment.url, obj.image_attachment.url ) elif obj.file_attachment: return format_html( '{}