"""
Communications app admin configuration.
"""
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse
from django.utils.safestring import mark_safe
from decimal import Decimal
from datetime import datetime, timedelta
from .models import (
Message, MessageRecipient, NotificationTemplate, AlertRule,
AlertInstance, CommunicationChannel, DeliveryLog
)
class MessageRecipientInline(admin.TabularInline):
"""
Inline admin for message recipients.
"""
model = MessageRecipient
extra = 0
fields = [
'recipient_type', 'user', 'email_address', 'phone_number',
'status', 'sent_at', 'delivered_at', 'read_at'
]
readonly_fields = [
'recipient_id', 'sent_at', 'delivered_at', 'read_at',
'acknowledged_at', 'delivery_attempts'
]
class DeliveryLogInline(admin.TabularInline):
"""
Inline admin for delivery logs.
"""
model = DeliveryLog
extra = 0
fields = [
'channel', 'status', 'attempt_number',
'started_at', 'completed_at', 'response_code'
]
readonly_fields = [
'log_id', 'started_at', 'completed_at',
'processing_time_ms', 'payload_size_bytes'
]
@admin.register(Message)
class MessageAdmin(admin.ModelAdmin):
"""
Admin interface for messages.
"""
list_display = [
'subject', 'message_type', 'priority',
'sender', 'recipient_count_display',
'status', 'created_at', 'sent_at'
]
list_filter = [
'tenant', 'message_type', 'priority', 'status',
'is_urgent', 'is_confidential', 'created_at'
]
search_fields = [
'subject', 'content', 'sender__username',
'sender__first_name', 'sender__last_name'
]
readonly_fields = [
'message_id', 'delivery_attempts',
'created_at', 'sent_at'
]
fieldsets = [
('Message Information', {
'fields': [
'message_id', 'tenant', 'subject', 'content'
]
}),
('Message Type', {
'fields': [
'message_type', 'priority'
]
}),
('Sender', {
'fields': [
'sender'
]
}),
('Message Configuration', {
'fields': [
'is_urgent', 'requires_acknowledgment', 'is_confidential'
]
}),
('Scheduling', {
'fields': [
'scheduled_at', 'expires_at'
]
}),
('Message Status', {
'fields': [
'status'
]
}),
('Delivery Settings', {
'fields': [
'delivery_attempts', 'max_delivery_attempts'
]
}),
('Threading', {
'fields': [
'message_thread_id', 'reply_to_message'
],
'classes': ['collapse']
}),
('External References', {
'fields': [
'external_message_id'
],
'classes': ['collapse']
}),
('Content Details', {
'fields': [
'has_attachments', 'content_type'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'sent_at'
],
'classes': ['collapse']
})
]
inlines = [MessageRecipientInline, DeliveryLogInline]
date_hierarchy = 'created_at'
def recipient_count_display(self, obj):
"""Display recipient count."""
return obj.recipients.count()
recipient_count_display.short_description = 'Recipients'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(tenant=request.user.tenant)
return qs.select_related('sender', 'reply_to_message')
@admin.register(MessageRecipient)
class MessageRecipientAdmin(admin.ModelAdmin):
"""
Admin interface for message recipients.
"""
list_display = [
'message_subject', 'recipient_display', 'recipient_type',
'status', 'sent_at', 'delivered_at', 'read_at',
'delivery_attempts'
]
list_filter = [
'message__tenant', 'recipient_type', 'status',
'sent_at', 'delivered_at'
]
search_fields = [
'message__subject', 'user__username', 'user__first_name',
'user__last_name', 'email_address', 'phone_number'
]
readonly_fields = [
'recipient_id', 'sent_at', 'delivered_at', 'read_at',
'acknowledged_at', 'last_attempt_at'
]
fieldsets = [
('Recipient Information', {
'fields': [
'recipient_id', 'message', 'recipient_type'
]
}),
('Recipient Details', {
'fields': [
'user', 'email_address', 'phone_number', 'role_name'
]
}),
('Delivery Status', {
'fields': [
'status'
]
}),
('Timing', {
'fields': [
'sent_at', 'delivered_at', 'read_at', 'acknowledged_at'
]
}),
('Delivery Tracking', {
'fields': [
'delivery_attempts', 'last_attempt_at', 'error_message'
]
}),
('External Tracking', {
'fields': [
'external_delivery_id'
],
'classes': ['collapse']
})
]
inlines = [DeliveryLogInline]
def message_subject(self, obj):
"""Display message subject."""
return obj.message.subject
message_subject.short_description = 'Message'
def recipient_display(self, obj):
"""Display recipient information."""
if obj.user:
return obj.user.get_full_name() or obj.user.username
elif obj.email_address:
return obj.email_address
elif obj.phone_number:
return obj.phone_number
elif obj.role_name:
return f"Role: {obj.role_name}"
return obj.recipient_type
recipient_display.short_description = 'Recipient'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(message__tenant=request.user.tenant)
return qs.select_related('message', 'user')
@admin.register(NotificationTemplate)
class NotificationTemplateAdmin(admin.ModelAdmin):
"""
Admin interface for notification templates.
"""
list_display = [
'name', 'template_type', 'category',
'usage_count', 'is_active', 'is_system_template',
'last_used_at', 'created_at'
]
list_filter = [
'tenant', 'template_type', 'category',
'is_active', 'is_system_template', 'requires_approval'
]
search_fields = [
'name', 'description', 'subject_template', 'content_template'
]
readonly_fields = [
'template_id', 'usage_count', 'last_used_at',
'created_at', 'updated_at'
]
fieldsets = [
('Template Information', {
'fields': [
'template_id', 'tenant', 'name', 'description'
]
}),
('Template Type', {
'fields': [
'template_type', 'category'
]
}),
('Template Content', {
'fields': [
'subject_template', 'content_template'
]
}),
('Template Configuration', {
'fields': [
'variables', 'default_values', 'formatting_rules'
]
}),
('Template Settings', {
'fields': [
'is_active', 'is_system_template', 'requires_approval'
]
}),
('Usage Tracking', {
'fields': [
'usage_count', 'last_used_at'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(tenant=request.user.tenant)
return qs.select_related('created_by')
class AlertInstanceInline(admin.TabularInline):
"""
Inline admin for alert instances.
"""
model = AlertInstance
extra = 0
fields = [
'title', 'severity', 'status',
'triggered_at', 'acknowledged_at', 'resolved_at'
]
readonly_fields = [
'alert_id', 'triggered_at', 'acknowledged_at',
'resolved_at', 'escalation_level'
]
@admin.register(AlertRule)
class AlertRuleAdmin(admin.ModelAdmin):
"""
Admin interface for alert rules.
"""
list_display = [
'name', 'trigger_type', 'severity',
'trigger_count', 'last_triggered_at',
'is_active', 'is_system_rule'
]
list_filter = [
'tenant', 'trigger_type', 'severity',
'is_active', 'is_system_rule'
]
search_fields = [
'name', 'description'
]
readonly_fields = [
'rule_id', 'trigger_count', 'last_triggered_at',
'last_evaluated_at', 'created_at', 'updated_at'
]
fieldsets = [
('Rule Information', {
'fields': [
'rule_id', 'tenant', 'name', 'description'
]
}),
('Rule Type', {
'fields': [
'trigger_type', 'severity'
]
}),
('Rule Configuration', {
'fields': [
'trigger_conditions', 'evaluation_frequency', 'cooldown_period'
]
}),
('Notification Configuration', {
'fields': [
'notification_template', 'notification_channels', 'escalation_rules'
]
}),
('Recipients', {
'fields': [
'default_recipients', 'recipient_roles'
]
}),
('Rule Status', {
'fields': [
'is_active', 'is_system_rule'
]
}),
('Tracking', {
'fields': [
'trigger_count', 'last_triggered_at', 'last_evaluated_at'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
inlines = [AlertInstanceInline]
filter_horizontal = ['default_recipients']
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(tenant=request.user.tenant)
return qs.select_related('notification_template', 'created_by')
@admin.register(AlertInstance)
class AlertInstanceAdmin(admin.ModelAdmin):
"""
Admin interface for alert instances.
"""
list_display = [
'title', 'alert_rule_name', 'severity',
'status', 'triggered_at', 'acknowledged_at',
'resolved_at', 'escalation_level'
]
list_filter = [
'alert_rule__tenant', 'severity', 'status',
'triggered_at', 'escalation_level'
]
search_fields = [
'title', 'description', 'alert_rule__name'
]
readonly_fields = [
'alert_id', 'triggered_at', 'acknowledged_at',
'resolved_at', 'escalated_at', 'notifications_sent',
'last_notification_at'
]
fieldsets = [
('Alert Information', {
'fields': [
'alert_id', 'alert_rule', 'title', 'description'
]
}),
('Alert Details', {
'fields': [
'severity', 'trigger_data', 'context_data'
]
}),
('Alert Status', {
'fields': [
'status'
]
}),
('Timing', {
'fields': [
'triggered_at', 'acknowledged_at', 'resolved_at', 'expires_at'
]
}),
('Response Tracking', {
'fields': [
'acknowledged_by', 'resolved_by', 'resolution_notes'
]
}),
('Escalation Tracking', {
'fields': [
'escalation_level', 'escalated_at'
]
}),
('Notification Tracking', {
'fields': [
'notifications_sent', 'last_notification_at'
],
'classes': ['collapse']
})
]
date_hierarchy = 'triggered_at'
def alert_rule_name(self, obj):
"""Display alert rule name."""
return obj.alert_rule.name
alert_rule_name.short_description = 'Alert Rule'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(alert_rule__tenant=request.user.tenant)
return qs.select_related('alert_rule', 'acknowledged_by', 'resolved_by')
@admin.register(CommunicationChannel)
class CommunicationChannelAdmin(admin.ModelAdmin):
"""
Admin interface for communication channels.
"""
list_display = [
'name', 'channel_type', 'provider_type',
'health_status_display', 'success_rate_display',
'message_count', 'last_used_at', 'is_active'
]
list_filter = [
'tenant', 'channel_type', 'provider_type',
'is_active', 'is_healthy'
]
search_fields = [
'name', 'description'
]
readonly_fields = [
'channel_id', 'is_healthy', 'last_health_check',
'message_count', 'success_count', 'failure_count',
'last_used_at', 'created_at', 'updated_at'
]
fieldsets = [
('Channel Information', {
'fields': [
'channel_id', 'tenant', 'name', 'description'
]
}),
('Channel Type', {
'fields': [
'channel_type', 'provider_type'
]
}),
('Configuration', {
'fields': [
'configuration', 'authentication_config', 'rate_limits'
]
}),
('Channel Status', {
'fields': [
'is_active', 'is_healthy', 'last_health_check', 'health_check_interval'
]
}),
('Usage Tracking', {
'fields': [
'message_count', 'success_count', 'failure_count', 'last_used_at'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
def health_status_display(self, obj):
"""Display health status with color coding."""
if obj.is_healthy:
return format_html('✓ Healthy')
return format_html('✗ Unhealthy')
health_status_display.short_description = 'Health'
def success_rate_display(self, obj):
"""Display success rate with color coding."""
rate = obj.success_rate
if rate >= 95:
color = 'green'
elif rate >= 85:
color = 'orange'
else:
color = 'red'
return format_html(
'{:.1f}%',
color, rate
)
success_rate_display.short_description = 'Success Rate'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(tenant=request.user.tenant)
return qs.select_related('created_by')
@admin.register(DeliveryLog)
class DeliveryLogAdmin(admin.ModelAdmin):
"""
Admin interface for delivery logs.
"""
list_display = [
'message_subject', 'recipient_display', 'channel_name',
'status', 'attempt_number', 'started_at',
'duration_display', 'response_code'
]
list_filter = [
'message__tenant', 'status', 'channel__channel_type',
'started_at', 'attempt_number'
]
search_fields = [
'message__subject', 'recipient__user__username',
'recipient__email_address', 'channel__name'
]
readonly_fields = [
'log_id', 'started_at', 'completed_at',
'processing_time_ms', 'payload_size_bytes'
]
fieldsets = [
('Delivery Information', {
'fields': [
'log_id', 'message', 'recipient', 'channel'
]
}),
('Delivery Details', {
'fields': [
'status', 'attempt_number'
]
}),
('Timing', {
'fields': [
'started_at', 'completed_at'
]
}),
('Response Tracking', {
'fields': [
'external_id', 'response_code', 'response_message', 'error_details'
]
}),
('Performance Metrics', {
'fields': [
'processing_time_ms', 'payload_size_bytes'
]
}),
('Metadata', {
'fields': [
'metadata'
],
'classes': ['collapse']
})
]
date_hierarchy = 'started_at'
def message_subject(self, obj):
"""Display message subject."""
return obj.message.subject
message_subject.short_description = 'Message'
def recipient_display(self, obj):
"""Display recipient information."""
if obj.recipient.user:
return obj.recipient.user.get_full_name() or obj.recipient.user.username
elif obj.recipient.email_address:
return obj.recipient.email_address
elif obj.recipient.phone_number:
return obj.recipient.phone_number
return obj.recipient.recipient_type
recipient_display.short_description = 'Recipient'
def channel_name(self, obj):
"""Display channel name."""
return obj.channel.name
channel_name.short_description = 'Channel'
def duration_display(self, obj):
"""Display delivery duration."""
duration = obj.duration
if duration:
total_seconds = duration.total_seconds()
if total_seconds >= 60:
minutes = int(total_seconds // 60)
seconds = int(total_seconds % 60)
return f"{minutes}m {seconds}s"
return f"{total_seconds:.1f}s"
return "-"
duration_display.short_description = 'Duration'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(message__tenant=request.user.tenant)
return qs.select_related('message', 'recipient', 'channel')
# Customize admin site
admin.site.site_header = "Hospital Management System - Communications"
admin.site.site_title = "Communications Admin"
admin.site.index_title = "Communications Administration"