559 lines
15 KiB
Python
559 lines
15 KiB
Python
"""
|
|
Admin configuration for laboratory app.
|
|
"""
|
|
|
|
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from .models import (
|
|
LabTest, LabOrder, Specimen, LabResult,
|
|
QualityControl, ReferenceRange
|
|
)
|
|
|
|
|
|
@admin.register(LabTest)
|
|
class LabTestAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for LabTest model.
|
|
"""
|
|
list_display = [
|
|
'test_code', 'test_name', 'test_category', 'department',
|
|
'specimen_type', 'turnaround_time', 'is_active'
|
|
]
|
|
list_filter = [
|
|
'test_category', 'test_type', 'department', 'specimen_type',
|
|
'is_active', 'is_orderable', 'stat_available', 'fasting_required'
|
|
]
|
|
search_fields = [
|
|
'test_code', 'test_name', 'loinc_code', 'cpt_code', 'snomed_code'
|
|
]
|
|
readonly_fields = ['test_id', 'created_at', 'updated_at']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'test_id', 'test_code', 'test_name', 'test_description'
|
|
)
|
|
}),
|
|
('Medical Coding', {
|
|
'fields': (
|
|
'loinc_code', 'cpt_code', 'snomed_code'
|
|
)
|
|
}),
|
|
('Classification', {
|
|
'fields': (
|
|
'test_category', 'test_type', 'department'
|
|
)
|
|
}),
|
|
('Specimen Requirements', {
|
|
'fields': (
|
|
'specimen_type', 'specimen_volume', 'collection_container',
|
|
'collection_instructions'
|
|
)
|
|
}),
|
|
('Processing', {
|
|
'fields': (
|
|
'processing_time', 'turnaround_time', 'stat_available',
|
|
'stat_turnaround_time'
|
|
)
|
|
}),
|
|
('Storage and Transport', {
|
|
'fields': (
|
|
'storage_temperature', 'transport_requirements', 'stability_time'
|
|
)
|
|
}),
|
|
('Clinical Information', {
|
|
'fields': (
|
|
'clinical_significance', 'indications', 'contraindications'
|
|
)
|
|
}),
|
|
('Patient Preparation', {
|
|
'fields': (
|
|
'patient_preparation', 'fasting_required', 'fasting_hours'
|
|
)
|
|
}),
|
|
('Methodology', {
|
|
'fields': (
|
|
'methodology', 'analyzer'
|
|
)
|
|
}),
|
|
('Quality Control', {
|
|
'fields': (
|
|
'qc_frequency',
|
|
)
|
|
}),
|
|
('Pricing and Availability', {
|
|
'fields': (
|
|
'cost', 'is_active', 'is_orderable'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def get_queryset(self, request):
|
|
"""Filter by tenant."""
|
|
qs = super().get_queryset(request)
|
|
if hasattr(request.user, 'tenant'):
|
|
return qs.filter(tenant=request.user.tenant)
|
|
return qs.none()
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
"""Set tenant and created_by."""
|
|
if not change:
|
|
obj.tenant = request.user.tenant
|
|
obj.created_by = request.user
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
class SpecimenInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for specimens.
|
|
"""
|
|
model = Specimen
|
|
extra = 0
|
|
readonly_fields = ['specimen_id', 'specimen_number', 'created_at']
|
|
fields = [
|
|
'specimen_type', 'container_type', 'volume', 'collected_datetime',
|
|
'collected_by', 'quality', 'status'
|
|
]
|
|
|
|
|
|
class LabResultInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for lab results.
|
|
"""
|
|
model = LabResult
|
|
extra = 0
|
|
readonly_fields = ['result_id', 'created_at']
|
|
fields = [
|
|
'test', 'specimen', 'result_value', 'result_unit',
|
|
'abnormal_flag', 'is_critical', 'status'
|
|
]
|
|
|
|
|
|
@admin.register(LabOrder)
|
|
class LabOrderAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for LabOrder model.
|
|
"""
|
|
list_display = [
|
|
'order_number', 'patient_name', 'ordering_provider',
|
|
'priority', 'status', 'order_datetime', 'test_count'
|
|
]
|
|
list_filter = [
|
|
'status', 'priority', 'order_datetime', 'fasting_status'
|
|
]
|
|
search_fields = [
|
|
'order_number', 'patient__first_name', 'patient__last_name',
|
|
'patient__mrn', 'ordering_provider__first_name', 'ordering_provider__last_name'
|
|
]
|
|
readonly_fields = ['order_id', 'order_number', 'created_at', 'updated_at']
|
|
inlines = [SpecimenInline, LabResultInline]
|
|
filter_horizontal = ['tests']
|
|
|
|
fieldsets = (
|
|
('Order Information', {
|
|
'fields': (
|
|
'order_id', 'order_number', 'patient', 'ordering_provider'
|
|
)
|
|
}),
|
|
('Tests', {
|
|
'fields': (
|
|
'tests',
|
|
)
|
|
}),
|
|
('Order Details', {
|
|
'fields': (
|
|
'order_datetime', 'priority'
|
|
)
|
|
}),
|
|
('Clinical Information', {
|
|
'fields': (
|
|
'clinical_indication', 'diagnosis_code', 'clinical_notes'
|
|
)
|
|
}),
|
|
('Collection Information', {
|
|
'fields': (
|
|
'collection_datetime', 'collection_location', 'fasting_status'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status',
|
|
)
|
|
}),
|
|
('Related Information', {
|
|
'fields': (
|
|
'encounter',
|
|
)
|
|
}),
|
|
('Special Instructions', {
|
|
'fields': (
|
|
'special_instructions',
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
"""Get patient name."""
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def get_queryset(self, request):
|
|
"""Filter by tenant."""
|
|
qs = super().get_queryset(request)
|
|
if hasattr(request.user, 'tenant'):
|
|
return qs.filter(tenant=request.user.tenant)
|
|
return qs.none()
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
"""Set tenant."""
|
|
if not change:
|
|
obj.tenant = request.user.tenant
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(Specimen)
|
|
class SpecimenAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for Specimen model.
|
|
"""
|
|
list_display = [
|
|
'specimen_number', 'patient_name', 'specimen_type',
|
|
'collected_datetime', 'quality', 'status'
|
|
]
|
|
list_filter = [
|
|
'specimen_type', 'quality', 'status', 'collected_datetime',
|
|
'storage_temperature'
|
|
]
|
|
search_fields = [
|
|
'specimen_number', 'order__patient__first_name',
|
|
'order__patient__last_name', 'order__order_number'
|
|
]
|
|
readonly_fields = [
|
|
'specimen_id', 'specimen_number', 'created_at', 'updated_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('Specimen Information', {
|
|
'fields': (
|
|
'specimen_id', 'specimen_number', 'order'
|
|
)
|
|
}),
|
|
('Specimen Details', {
|
|
'fields': (
|
|
'specimen_type', 'container_type', 'volume'
|
|
)
|
|
}),
|
|
('Collection Information', {
|
|
'fields': (
|
|
'collected_datetime', 'collected_by', 'collection_site',
|
|
'collection_method'
|
|
)
|
|
}),
|
|
('Specimen Quality', {
|
|
'fields': (
|
|
'quality', 'rejection_reason', 'quality_notes'
|
|
)
|
|
}),
|
|
('Processing Information', {
|
|
'fields': (
|
|
'received_datetime', 'received_by'
|
|
)
|
|
}),
|
|
('Storage Information', {
|
|
'fields': (
|
|
'storage_location', 'storage_temperature'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status',
|
|
)
|
|
}),
|
|
('Chain of Custody', {
|
|
'fields': (
|
|
'chain_of_custody',
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
"""Get patient name."""
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
|
|
@admin.register(LabResult)
|
|
class LabResultAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for LabResult model.
|
|
"""
|
|
list_display = [
|
|
'patient_name', 'test_name', 'result_value', 'result_unit',
|
|
'abnormal_flag', 'is_critical', 'status', 'verified'
|
|
]
|
|
list_filter = [
|
|
'status', 'abnormal_flag', 'is_critical', 'verified',
|
|
'analyzed_datetime', 'test__test_category'
|
|
]
|
|
search_fields = [
|
|
'order__patient__first_name', 'order__patient__last_name',
|
|
'test__test_name', 'test__test_code', 'specimen__specimen_number'
|
|
]
|
|
readonly_fields = [
|
|
'result_id', 'created_at', 'updated_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('Result Information', {
|
|
'fields': (
|
|
'result_id', 'order', 'test', 'specimen'
|
|
)
|
|
}),
|
|
('Result Values', {
|
|
'fields': (
|
|
'result_value', 'result_unit', 'result_type'
|
|
)
|
|
}),
|
|
('Reference Range', {
|
|
'fields': (
|
|
'reference_range', 'abnormal_flag'
|
|
)
|
|
}),
|
|
('Critical Values', {
|
|
'fields': (
|
|
'is_critical', 'critical_called', 'critical_called_datetime',
|
|
'critical_called_to'
|
|
)
|
|
}),
|
|
('Processing Information', {
|
|
'fields': (
|
|
'analyzed_datetime', 'analyzed_by', 'analyzer'
|
|
)
|
|
}),
|
|
('Verification', {
|
|
'fields': (
|
|
'verified', 'verified_by', 'verified_datetime'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status',
|
|
)
|
|
}),
|
|
('Comments', {
|
|
'fields': (
|
|
'technician_comments', 'pathologist_comments'
|
|
)
|
|
}),
|
|
('Quality Control', {
|
|
'fields': (
|
|
'qc_passed', 'qc_notes'
|
|
)
|
|
}),
|
|
('Reporting', {
|
|
'fields': (
|
|
'reported_datetime',
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
"""Get patient name."""
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def test_name(self, obj):
|
|
"""Get test name."""
|
|
return obj.test.test_name
|
|
test_name.short_description = 'Test'
|
|
|
|
|
|
@admin.register(QualityControl)
|
|
class QualityControlAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for QualityControl model.
|
|
"""
|
|
list_display = [
|
|
'test_name', 'control_level', 'run_datetime',
|
|
'target_value', 'observed_value', 'status'
|
|
]
|
|
list_filter = [
|
|
'status', 'control_level', 'run_datetime', 'test__test_category'
|
|
]
|
|
search_fields = [
|
|
'test__test_name', 'test__test_code', 'control_material'
|
|
]
|
|
readonly_fields = [
|
|
'qc_id', 'created_at', 'updated_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('QC Information', {
|
|
'fields': (
|
|
'qc_id', 'test'
|
|
)
|
|
}),
|
|
('QC Material', {
|
|
'fields': (
|
|
'control_material', 'control_lot', 'control_level'
|
|
)
|
|
}),
|
|
('Expected Values', {
|
|
'fields': (
|
|
'target_value', 'acceptable_range_low', 'acceptable_range_high'
|
|
)
|
|
}),
|
|
('QC Run Information', {
|
|
'fields': (
|
|
'run_datetime', 'observed_value'
|
|
)
|
|
}),
|
|
('QC Status', {
|
|
'fields': (
|
|
'status',
|
|
)
|
|
}),
|
|
('Staff', {
|
|
'fields': (
|
|
'performed_by', 'reviewed_by'
|
|
)
|
|
}),
|
|
('Analyzer', {
|
|
'fields': (
|
|
'analyzer',
|
|
)
|
|
}),
|
|
('Comments', {
|
|
'fields': (
|
|
'comments', 'corrective_action'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def test_name(self, obj):
|
|
"""Get test name."""
|
|
return obj.test.test_name
|
|
test_name.short_description = 'Test'
|
|
|
|
def get_queryset(self, request):
|
|
"""Filter by tenant."""
|
|
qs = super().get_queryset(request)
|
|
if hasattr(request.user, 'tenant'):
|
|
return qs.filter(tenant=request.user.tenant)
|
|
return qs.none()
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
"""Set tenant."""
|
|
if not change:
|
|
obj.tenant = request.user.tenant
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(ReferenceRange)
|
|
class ReferenceRangeAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for ReferenceRange model.
|
|
"""
|
|
list_display = [
|
|
'test_name', 'gender', 'age_range', 'range_display',
|
|
'unit', 'is_active'
|
|
]
|
|
list_filter = [
|
|
'gender', 'is_active', 'test__test_category'
|
|
]
|
|
search_fields = [
|
|
'test__test_name', 'test__test_code'
|
|
]
|
|
readonly_fields = [
|
|
'range_id', 'created_at', 'updated_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('Range Information', {
|
|
'fields': (
|
|
'range_id', 'test'
|
|
)
|
|
}),
|
|
('Demographics', {
|
|
'fields': (
|
|
'gender', 'age_min', 'age_max'
|
|
)
|
|
}),
|
|
('Range Values', {
|
|
'fields': (
|
|
'range_low', 'range_high', 'range_text'
|
|
)
|
|
}),
|
|
('Critical Values', {
|
|
'fields': (
|
|
'critical_low', 'critical_high'
|
|
)
|
|
}),
|
|
('Units', {
|
|
'fields': (
|
|
'unit',
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'is_active',
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def test_name(self, obj):
|
|
"""Get test name."""
|
|
return obj.test.test_name
|
|
test_name.short_description = 'Test'
|
|
|
|
def age_range(self, obj):
|
|
"""Get age range display."""
|
|
if obj.age_min is not None or obj.age_max is not None:
|
|
return f"{obj.age_min or 0}-{obj.age_max or '∞'} years"
|
|
return "All ages"
|
|
age_range.short_description = 'Age Range'
|
|
|
|
def range_display(self, obj):
|
|
"""Get range display."""
|
|
return obj.display_range
|
|
range_display.short_description = 'Range'
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
"""Set created_by."""
|
|
if not change:
|
|
obj.created_by = request.user
|
|
super().save_model(request, obj, form, change)
|
|
|