HH/apps/surveys/admin.py
2026-03-28 14:03:56 +03:00

333 lines
11 KiB
Python

"""
Surveys admin
"""
from django.contrib import admin
from django.utils.html import format_html
from django.db.models import Count
from .models import (
SurveyInstance,
SurveyQuestion,
SurveyResponse,
SurveyTemplate,
SurveyTracking,
)
class SurveyQuestionInline(admin.TabularInline):
"""Inline admin for survey questions"""
model = SurveyQuestion
extra = 1
fields = ["order", "text", "question_type", "is_required", "is_base", "event_type"]
ordering = ["order"]
@admin.register(SurveyTemplate)
class SurveyTemplateAdmin(admin.ModelAdmin):
"""Survey template admin"""
list_display = [
"name",
"survey_type",
"hospital",
"scoring_method",
"negative_threshold",
"get_question_count",
"is_active",
]
list_filter = ["survey_type", "scoring_method", "is_active", "hospital"]
search_fields = ["name", "name_ar"]
ordering = ["hospital", "name"]
inlines = [SurveyQuestionInline]
fieldsets = (
(None, {"fields": ("name", "name_ar")}),
("Configuration", {"fields": ("hospital", "survey_type")}),
("Scoring", {"fields": ("scoring_method", "negative_threshold")}),
("Status", {"fields": ("is_active",)}),
("Metadata", {"fields": ("created_at", "updated_at")}),
)
readonly_fields = ["created_at", "updated_at"]
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related("hospital").prefetch_related("questions")
@admin.register(SurveyQuestion)
class SurveyQuestionAdmin(admin.ModelAdmin):
"""Survey question admin"""
list_display = ["survey_template", "order", "text_preview", "question_type", "is_base", "is_required"]
list_filter = ["survey_template", "question_type", "is_required", "is_base"]
search_fields = ["text", "text_ar"]
ordering = ["survey_template", "order"]
fieldsets = (
(None, {"fields": ("survey_template", "order")}),
("Question Text", {"fields": ("text", "text_ar")}),
("Configuration", {"fields": ("question_type", "is_required", "is_base", "event_type")}),
("Choices (for multiple choice)", {"fields": ("choices_json",), "classes": ("collapse",)}),
("Metadata", {"fields": ("created_at", "updated_at")}),
)
readonly_fields = ["created_at", "updated_at"]
def text_preview(self, obj):
"""Show preview of question text"""
return obj.text[:100] + "..." if len(obj.text) > 100 else obj.text
text_preview.short_description = "Question"
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related("survey_template")
class SurveyResponseInline(admin.TabularInline):
"""Inline admin for survey responses"""
model = SurveyResponse
extra = 0
fields = ["question", "numeric_value", "text_value", "choice_value"]
readonly_fields = ["question"]
ordering = ["question__order"]
def has_add_permission(self, request, obj=None):
return False
class SurveyTrackingInline(admin.TabularInline):
"""Inline admin for survey tracking events"""
model = SurveyTracking
extra = 0
fields = ["event_type", "device_type", "browser", "total_time_spent", "created_at"]
readonly_fields = ["event_type", "device_type", "browser", "total_time_spent", "created_at"]
ordering = ["-created_at"]
can_delete = False
def has_add_permission(self, request, obj=None):
return False
@admin.register(SurveyInstance)
class SurveyInstanceAdmin(admin.ModelAdmin):
"""Survey instance admin"""
list_display = [
"survey_label",
"patient",
"encounter_id",
"status_badge",
"delivery_channel",
"open_count",
"time_spent_display",
"total_score",
"is_negative",
"sent_at",
"completed_at",
]
list_filter = [
"status",
"delivery_channel",
"is_negative",
"survey_template__survey_type",
"sent_at",
"completed_at",
]
search_fields = ["patient__mrn", "patient__first_name", "patient__last_name", "encounter_id", "access_token"]
ordering = ["-created_at"]
inlines = [SurveyResponseInline, SurveyTrackingInline]
fieldsets = (
(None, {"fields": ("survey_template", "patient", "encounter_id")}),
("Journey Linkage", {"fields": ("journey_instance",), "classes": ("collapse",)}),
(
"Delivery",
{"fields": ("delivery_channel", "recipient_phone", "recipient_email", "access_token", "token_expires_at")},
),
("Status & Timestamps", {"fields": ("status", "sent_at", "opened_at", "completed_at")}),
("Tracking", {"fields": ("open_count", "last_opened_at", "time_spent_seconds")}),
("Scoring", {"fields": ("total_score", "is_negative")}),
("Patient Comment", {"fields": ("comment", "comment_analyzed")}),
("Comment Analysis", {"fields": ("comment_analysis",), "classes": ("collapse",)}),
("Metadata", {"fields": ("metadata", "created_at", "updated_at"), "classes": ("collapse",)}),
)
readonly_fields = [
"access_token",
"token_expires_at",
"sent_at",
"opened_at",
"completed_at",
"open_count",
"last_opened_at",
"time_spent_seconds",
"total_score",
"is_negative",
"comment_analyzed",
"comment_analysis",
"created_at",
"updated_at",
]
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related("survey_template", "patient", "journey_instance").prefetch_related(
"responses", "tracking_events"
)
def status_badge(self, obj):
"""Display status with color badge"""
colors = {
"sent": "secondary",
"viewed": "info",
"in_progress": "warning",
"completed": "success",
"abandoned": "danger",
"expired": "secondary",
"cancelled": "dark",
}
color = colors.get(obj.status, "secondary")
return format_html('<span class="badge bg-{}">{}</span>', color, obj.get_status_display())
status_badge.short_description = "Status"
def survey_label(self, obj):
if obj.survey_template:
return obj.survey_template.name
return obj.metadata.get("patient_type", "Event-based Survey")
survey_label.short_description = "Survey"
def time_spent_display(self, obj):
"""Display time spent in human-readable format"""
if obj.time_spent_seconds:
minutes = obj.time_spent_seconds // 60
seconds = obj.time_spent_seconds % 60
return f"{minutes}m {seconds}s"
return "-"
time_spent_display.short_description = "Time Spent"
@admin.register(SurveyTracking)
class SurveyTrackingAdmin(admin.ModelAdmin):
"""Survey tracking admin"""
list_display = [
"survey_instance_link",
"event_type_badge",
"device_type",
"browser",
"ip_address",
"total_time_spent_display",
"created_at",
]
list_filter = ["event_type", "device_type", "browser", "survey_instance__survey_template", "created_at"]
search_fields = [
"survey_instance__patient__mrn",
"survey_instance__patient__first_name",
"survey_instance__patient__last_name",
"ip_address",
"user_agent",
]
ordering = ["-created_at"]
fieldsets = (
(None, {"fields": ("survey_instance", "event_type")}),
("Timing", {"fields": ("time_on_page", "total_time_spent")}),
("Context", {"fields": ("current_question",)}),
("Device Info", {"fields": ("user_agent", "ip_address", "device_type", "browser")}),
("Location", {"fields": ("country", "city"), "classes": ("collapse",)}),
("Metadata", {"fields": ("metadata", "created_at"), "classes": ("collapse",)}),
)
readonly_fields = ["created_at"]
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related("survey_instance", "survey_instance__patient")
def survey_instance_link(self, obj):
"""Link to survey instance"""
url = f"/admin/surveys/surveyinstance/{obj.survey_instance.id}/change/"
label = (
obj.survey_instance.survey_template.name
if obj.survey_instance.survey_template
else obj.survey_instance.metadata.get("patient_type", "Survey")
)
return format_html(
'<a href="{}">{} - {}</a>',
url,
label,
obj.survey_instance.patient.get_full_name(),
)
survey_instance_link.short_description = "Survey"
def event_type_badge(self, obj):
"""Display event type with color badge"""
colors = {
"page_view": "info",
"survey_started": "primary",
"question_answered": "secondary",
"survey_completed": "success",
"survey_abandoned": "danger",
"reminder_sent": "warning",
}
color = colors.get(obj.event_type, "secondary")
return format_html('<span class="badge bg-{}">{}</span>', color, obj.get_event_type_display())
event_type_badge.short_description = "Event Type"
def total_time_spent_display(self, obj):
"""Display time spent in human-readable format"""
if obj.total_time_spent:
minutes = obj.total_time_spent // 60
seconds = obj.total_time_spent % 60
return f"{minutes}m {seconds}s"
return "-"
total_time_spent_display.short_description = "Time Spent"
@admin.register(SurveyResponse)
class SurveyResponseAdmin(admin.ModelAdmin):
"""Survey response admin"""
list_display = ["survey_instance", "question_preview", "numeric_value", "text_value_preview", "created_at"]
list_filter = ["survey_instance__survey_template", "question__question_type", "created_at"]
search_fields = ["survey_instance__patient__mrn", "question__text", "text_value"]
ordering = ["survey_instance", "question__order"]
fieldsets = (
(None, {"fields": ("survey_instance", "question")}),
("Response", {"fields": ("numeric_value", "text_value", "choice_value")}),
("Metadata", {"fields": ("created_at", "updated_at")}),
)
readonly_fields = ["created_at", "updated_at"]
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related("survey_instance", "question")
def question_preview(self, obj):
"""Show preview of question"""
return obj.question.text[:50] + "..." if len(obj.question.text) > 50 else obj.question.text
question_preview.short_description = "Question"
def text_value_preview(self, obj):
"""Show preview of text response"""
if obj.text_value:
return obj.text_value[:50] + "..." if len(obj.text_value) > 50 else obj.text_value
return "-"
text_value_preview.short_description = "Text Response"