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 django.shortcuts import redirect from django.urls import reverse from unfold.decorators import action 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") from django.contrib.auth import get_user_model User = get_user_model() class RoomMessageAdmin(ModelAdmin): list_display = ( 'name', 'room_type_badge', 'course', 'initiator', 'messages_count', 'view_messages_button','is_locked' ) list_filter = ( 'room_type', ('created_at', RangeDateTimeFilter), ('updated_at', RangeDateTimeFilter), 'course','is_locked' ) 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','is_locked'), 'classes': ('grid-col-2',), }), (_("Relations"), { 'fields': ('course', 'initiator', 'recipient'), 'classes': ('grid-col-2',), }), (_("Timestamps"), { 'fields': ('created_at', 'updated_at'), 'classes': ('grid-col-2',), }), ) def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "initiator": kwargs["queryset"] = User.objects.filter(is_active=True, email__isnull=False) if db_field.name == "recipient": kwargs["queryset"] = User.objects.filter(is_active=True, email__isnull=False) return super().formfield_for_foreignkey(db_field, request, **kwargs) 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',), }), ) actions_list = ["back_to_chat_rooms"] @action( description=_("Back to Chat Rooms"), icon="arrow_back", # Unfold natively supports Google Material Icons! ) def back_to_chat_rooms(self, request): """Redirects the admin back to the RoomMessage list""" url = reverse('admin:chat_roommessage_changelist') return redirect(url) 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( '{}