"""
Integrations admin
"""
from django.contrib import admin
from django.utils.html import format_html, mark_safe
from .models import (
EventMapping,
ExternalAPIKey,
HISEventType,
HISTestPatient,
HISTestVisit,
HISPatientVisit,
HISVisitEvent,
InboundEvent,
IntegrationConfig,
SurveyTemplateMapping,
)
@admin.register(InboundEvent)
class InboundEventAdmin(admin.ModelAdmin):
"""Inbound event admin"""
list_display = ["source_system", "event_code", "encounter_id", "status_badge", "processing_attempts", "received_at"]
list_filter = ["status", "source_system", "received_at"]
search_fields = ["encounter_id", "patient_identifier", "event_code"]
ordering = ["-received_at"]
date_hierarchy = "received_at"
fieldsets = (
("Event Information", {"fields": ("source_system", "event_code", "encounter_id", "patient_identifier")}),
("Payload", {"fields": ("payload_json",), "classes": ("collapse",)}),
("Processing Status", {"fields": ("status", "processing_attempts", "error")}),
("Extracted Context", {"fields": ("physician_license", "department_code"), "classes": ("collapse",)}),
("Timestamps", {"fields": ("received_at", "processed_at", "created_at", "updated_at")}),
("Metadata", {"fields": ("metadata",), "classes": ("collapse",)}),
)
readonly_fields = ["received_at", "processed_at", "created_at", "updated_at"]
def status_badge(self, obj):
"""Display status with color badge"""
colors = {
"pending": "warning",
"processing": "info",
"processed": "success",
"failed": "danger",
"ignored": "secondary",
}
color = colors.get(obj.status, "secondary")
return format_html('{}', color, obj.get_status_display())
status_badge.short_description = "Status"
def has_add_permission(self, request):
# Events should only be created via API
return False
class EventMappingInline(admin.TabularInline):
"""Inline admin for event mappings"""
model = EventMapping
extra = 1
fields = ["external_event_code", "internal_event_code", "is_active"]
@admin.register(IntegrationConfig)
class IntegrationConfigAdmin(admin.ModelAdmin):
"""Integration configuration admin"""
list_display = ["name", "source_system", "is_active", "last_sync_at", "created_at"]
list_filter = ["source_system", "is_active"]
search_fields = ["name", "description"]
ordering = ["name"]
inlines = [EventMappingInline]
fieldsets = (
(None, {"fields": ("name", "source_system", "description")}),
("Connection", {"fields": ("api_url", "api_key"), "classes": ("collapse",)}),
("Configuration", {"fields": ("is_active", "config_json"), "classes": ("collapse",)}),
("Metadata", {"fields": ("last_sync_at", "created_at", "updated_at")}),
)
readonly_fields = ["last_sync_at", "created_at", "updated_at"]
@admin.register(SurveyTemplateMapping)
class SurveyTemplateMappingAdmin(admin.ModelAdmin):
"""Survey template mapping admin"""
list_display = ["hospital", "patient_type", "survey_template", "is_active"]
list_filter = ["hospital", "patient_type", "is_active"]
search_fields = ["hospital__name", "survey_template__name"]
ordering = ["hospital", "patient_type"]
fieldsets = (
("Mapping Configuration", {"fields": ("hospital", "patient_type", "survey_template")}),
("Settings", {"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", "survey_template")
class HISVisitEventInline(admin.TabularInline):
model = HISVisitEvent
extra = 0
fields = ["event_type", "bill_date", "parsed_date", "visit_category"]
readonly_fields = ["event_type", "bill_date", "parsed_date", "visit_category"]
show_change_link = False
@admin.register(HISPatientVisit)
class HISPatientVisitAdmin(admin.ModelAdmin):
"""HIS patient visit admin"""
list_display = [
"patient",
"patient_type",
"admission_id",
"hospital",
"is_visit_complete",
"survey_linked",
"discharge_date",
"last_his_fetch_at",
]
list_filter = ["patient_type", "is_visit_complete", "hospital"]
search_fields = ["admission_id", "reg_code", "patient_id_his", "patient__first_name", "patient__last_name"]
ordering = ["-last_his_fetch_at"]
date_hierarchy = "last_his_fetch_at"
inlines = [HISVisitEventInline]
fieldsets = (
(
"Patient & Hospital",
{"fields": ("patient", "hospital", "admission_id", "reg_code", "patient_id_his", "patient_type")},
),
("Visit Dates", {"fields": ("admit_date", "discharge_date", "effective_discharge_date")}),
("Status", {"fields": ("is_visit_complete", "survey_instance", "last_his_fetch_at")}),
("HIS Data", {"fields": ("visit_data", "visit_timeline"), "classes": ("collapse",)}),
("Doctor & Consultant", {"fields": ("primary_doctor", "consultant_id", "primary_doctor_fk", "consultant_fk")}),
("Insurance & Billing", {"fields": ("company_name", "grade_name", "insurance_company_name", "bill_type")}),
("VIP & Nationality", {"fields": ("is_vip", "nationality")}),
("Timestamps", {"fields": ("created_at", "updated_at")}),
)
readonly_fields = ["created_at", "updated_at"]
def survey_linked(self, obj):
if obj.survey_instance:
return format_html(
'{}', obj.survey_instance.id, obj.survey_instance.id
)
return mark_safe('None')
survey_linked.short_description = "Survey"
def has_add_permission(self, request):
return False
@admin.register(HISVisitEvent)
class HISVisitEventAdmin(admin.ModelAdmin):
"""HIS visit event admin"""
list_display = ["visit", "event_type", "bill_date", "parsed_date", "visit_category", "admission_id"]
list_filter = ["visit_category", "patient_type"]
search_fields = ["admission_id", "patient_id", "event_type"]
ordering = ["-parsed_date"]
date_hierarchy = "parsed_date"
fieldsets = (
("Visit", {"fields": ("visit",)}),
("Event Details", {"fields": ("event_type", "bill_date", "parsed_date", "visit_category")}),
(
"Patient Info",
{"fields": ("patient_type", "admission_id", "patient_id", "reg_code", "ssn", "mobile_no")},
),
("Timestamps", {"fields": ("created_at", "updated_at")}),
)
readonly_fields = ["created_at", "updated_at"]
def has_add_permission(self, request):
return False
@admin.register(HISEventType)
class HISEventTypeAdmin(admin.ModelAdmin):
"""HIS event type admin - auto-populated from HIS data"""
list_display = ["event_type", "patient_types_display", "event_count", "last_seen_at"]
search_fields = ["event_type"]
ordering = ["event_type"]
list_filter = ["last_seen_at"]
readonly_fields = ["event_type", "patient_types", "event_count", "last_seen_at", "created_at", "updated_at"]
def has_add_permission(self, request):
return False
def patient_types_display(self, obj):
if not obj.patient_types:
return mark_safe('None')
return ", ".join(obj.patient_types)
patient_types_display.short_description = "Patient Types"
@admin.register(EventMapping)
class EventMappingAdmin(admin.ModelAdmin):
"""Event mapping admin"""
list_display = ["integration_config", "external_event_code", "internal_event_code", "is_active"]
list_filter = ["integration_config", "is_active"]
search_fields = ["external_event_code", "internal_event_code"]
ordering = ["integration_config", "external_event_code"]
fieldsets = (
(None, {"fields": ("integration_config", "external_event_code", "internal_event_code")}),
("Field Mappings", {"fields": ("field_mappings",), "classes": ("collapse",)}),
("Configuration", {"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("integration_config")
@admin.register(HISTestPatient)
class HISTestPatientAdmin(admin.ModelAdmin):
list_display = ["admission_id", "patient_name", "patient_type", "hospital_name", "admit_date", "discharge_date"]
list_filter = ["patient_type", "hospital_name"]
search_fields = ["admission_id", "patient_id", "ssn", "mobile_no", "patient_name"]
ordering = ["-admit_date"]
date_hierarchy = "admit_date"
readonly_fields = ["created_at", "updated_at"]
def has_add_permission(self, request):
return False
@admin.register(HISTestVisit)
class HISTestVisitAdmin(admin.ModelAdmin):
list_display = ["admission_id", "visit_category", "event_type", "bill_date"]
list_filter = ["visit_category", "event_type"]
search_fields = ["admission_id", "patient_id"]
ordering = ["-bill_date"]
date_hierarchy = "bill_date"
readonly_fields = ["created_at", "updated_at"]
def has_add_permission(self, request):
return False
@admin.register(ExternalAPIKey)
class ExternalAPIKeyAdmin(admin.ModelAdmin):
"""Admin for External API Keys - shows the plaintext key ONLY on creation."""
list_display = ["name", "key_prefix", "hospital", "is_active", "allowed_entities_display", "last_used_at", "created_at"]
list_filter = ["is_active", "hospital"]
search_fields = ["name", "key_prefix", "description"]
ordering = ["-created_at"]
readonly_fields = ["key_hash", "key_prefix", "last_used_at", "created_at", "updated_at"]
fieldsets = (
(
"Identity",
{"fields": ("name", "description", "key_prefix", "key_hash")},
),
(
"Scope",
{"fields": ("hospital", "allowed_entities", "rate_limit")},
),
(
"Status",
{"fields": ("is_active", "expires_at", "last_used_at")},
),
(
"Metadata",
{"fields": ("created_by", "created_at", "updated_at")},
),
)
def allowed_entities_display(self, obj):
if not obj.allowed_entities:
return mark_safe('All Entities')
return ", ".join(obj.allowed_entities)
allowed_entities_display.short_description = "Allowed Entities"
def save_model(self, request, obj, form, change):
if not change:
# Creating a new key — generate it
obj.created_by = request.user
obj.save()
# Store the raw key to show in admin message
# We need to generate the key ourselves since save_model doesn't return it
import secrets
import hashlib
raw_key = secrets.token_hex(32)
obj.key_prefix = raw_key[:8]
obj.key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
obj.save(update_fields=["key_prefix", "key_hash"])
self._generated_key = raw_key
else:
obj.save()
def response_add(self, request, obj, post_url_continue=None):
resp = super().response_add(request, obj, post_url_continue)
if hasattr(self, "_generated_key"):
from django.contrib import messages
messages.success(
request,
f'API Key created. Copy it now — it will NOT be shown again: {self._generated_key}',
)
# Clear it
del self._generated_key
return resp