Marwan Alwali ab2c4a36c5 update
2025-10-02 10:13:03 +03:00

2853 lines
94 KiB
Python

"""
Laboratory app views with healthcare-focused CRUD operations.
Implements appropriate access patterns for clinical laboratory workflows.
"""
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import (
ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
)
from django.http import JsonResponse, HttpResponse
from django.db.models import Q, Count, Avg, Sum, F
from django.utils import timezone
from django.contrib import messages
from django.urls import reverse_lazy, reverse
from django.core.paginator import Paginator
from django.template.loader import render_to_string
from datetime import datetime, timedelta, date
import json
from core.utils import AuditLogger
from .models import (
LabTest, LabOrder, Specimen, LabResult, QualityControl, ReferenceRange
)
from .forms import (
LabTestForm, LabOrderForm, SpecimenForm, LabResultForm,
QualityControlForm, ReferenceRangeForm
)
class LaboratoryDashboardView(LoginRequiredMixin, TemplateView):
"""
Main laboratory dashboard with key metrics and recent activity.
"""
template_name = 'laboratory/dashboard.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tenant = self.request.user.tenant
today = timezone.now().date()
# Dashboard statistics
context.update({
'pending_orders': LabOrder.objects.filter(
tenant=tenant,
status='PENDING'
).count(),
'in_progress_orders': LabOrder.objects.filter(
tenant=tenant,
status='IN_PROGRESS'
).count(),
'specimens_collected': Specimen.objects.filter(
order__tenant=tenant,
collected_datetime__date=today
).count(),
'results_pending': LabResult.objects.filter(
order__tenant=tenant,
status='PENDING'
).count(),
'results_completed_today': LabResult.objects.filter(
order__tenant=tenant,
analyzed_datetime__date=today,
status='VERIFIED'
).count(),
'qc_tests_today': QualityControl.objects.filter(
tenant=tenant,
run_datetime=today
).count(),
'total_tests_available': LabTest.objects.filter(
tenant=tenant,
is_active=True
).count(),
'critical_results': LabResult.objects.filter(
order__tenant=tenant,
is_critical=True,
status='VERIFIED',
analyzed_datetime__date=today
).count(),
})
# Recent orders
context['recent_orders'] = LabOrder.objects.filter(
tenant=tenant
).select_related('patient', 'ordering_provider').order_by('-order_datetime')[:10]
# Recent results
context['recent_results'] = LabResult.objects.filter(
order__tenant=tenant,
status='VERIFIED'
).select_related('order', 'test').order_by('-analyzed_datetime')[:10]
return context
class LabTestListView(LoginRequiredMixin, ListView):
"""
List all laboratory tests with filtering and search.
"""
model = LabTest
template_name = 'laboratory/tests/lab_test_list.html'
context_object_name = 'lab_tests'
paginate_by = 25
def get_queryset(self):
queryset = LabTest.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(test_name__icontains=search) |
Q(test_code__icontains=search) |
Q(test_description__icontains=search)
)
# Filter by category
category = self.request.GET.get('category')
if category:
queryset = queryset.filter(test_category=category)
# Filter by active status
active_only = self.request.GET.get('active_only')
if active_only:
queryset = queryset.filter(is_active=True)
return queryset.order_by('test_name')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'categories': LabTest._meta.get_field('test_category').choices,
'search_query': self.request.GET.get('search', ''),
})
return context
class LabTestDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a laboratory test.
"""
model = LabTest
template_name = 'laboratory/tests/lab_test_detail.html'
context_object_name = 'lab_test'
def get_queryset(self):
return LabTest.objects.filter(tenant=self.request.user.tenant)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
lab_test = self.object
# Get reference ranges for this test
context['reference_ranges'] = lab_test.reference_ranges.all().order_by('age_min', 'gender')
# Get recent orders for this test
context['recent_orders'] = LabOrder.objects.filter(
tests=lab_test,
tenant=self.request.user.tenant
).select_related('patient').order_by('-order_datetime')[:10]
# Get quality control records
context['qc_records'] = QualityControl.objects.filter(
test=lab_test,
tenant=self.request.user.tenant
).order_by('-created_at')[:5]
return context
class LabTestCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new laboratory test.
"""
model = LabTest
form_class = LabTestForm
template_name = 'laboratory/tests/lab_test_form.html'
permission_required = 'laboratory.add_labtest'
success_url = reverse_lazy('laboratory:lab_test_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='LAB_TEST_CREATED',
model='LabTest',
object_id=str(self.object.test_id),
details={
'test_name': self.object.test_name,
'test_code': self.object.test_code
}
)
messages.success(self.request, f'Lab test "{self.object.test_name}" created successfully.')
return response
class LabTestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update an existing laboratory test.
"""
model = LabTest
form_class = LabTestForm
template_name = 'laboratory/tests/lab_test_form.html'
permission_required = 'laboratory.change_labtest'
def get_queryset(self):
return LabTest.objects.filter(tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('laboratory:lab_test_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='LAB_TEST_UPDATED',
model='LabTest',
object_id=str(self.object.test_id),
details={
'test_name': self.object.test_name,
'changes': form.changed_data
}
)
messages.success(self.request, f'Lab test "{self.object.test_name}" updated successfully.')
return response
class LabTestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
"""
Delete a laboratory test (soft delete by deactivating).
"""
model = LabTest
template_name = 'laboratory/tests/lab_test_confirm_delete.html'
permission_required = 'laboratory.delete_labtest'
success_url = reverse_lazy('laboratory:lab_test_list')
def get_queryset(self):
return LabTest.objects.filter(tenant=self.request.user.tenant)
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
# Soft delete by deactivating instead of actual deletion
self.object.is_active = False
self.object.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='LAB_TEST_DEACTIVATED',
model='LabTest',
object_id=str(self.object.test_id),
details={'test_name': self.object.test_name}
)
messages.success(request, f'Lab test "{self.object.test_name}" deactivated successfully.')
return redirect(self.success_url)
class ReferenceRangeListView(LoginRequiredMixin, ListView):
"""
List all reference ranges with filtering.
"""
model = ReferenceRange
template_name = 'laboratory/reference_ranges/reference_range_list.html'
context_object_name = 'reference_ranges'
paginate_by = 25
def get_queryset(self):
queryset = ReferenceRange.objects.filter(test__tenant=self.request.user.tenant)
# Filter by test
test_id = self.request.GET.get('test')
if test_id:
queryset = queryset.filter(test_id=test_id)
# Filter by gender
gender = self.request.GET.get('gender')
if gender:
queryset = queryset.filter(gender=gender)
return queryset.select_related('test').order_by('test__test_name', 'age_min')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'lab_tests': LabTest.objects.filter(
tenant=self.request.user.tenant,
is_active=True
).order_by('test_name'),
'genders': ReferenceRange.Gender.choices,
})
return context
class ReferenceRangeDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a reference range.
"""
model = ReferenceRange
template_name = 'laboratory/reference_ranges/reference_range_detail.html'
context_object_name = 'reference_range'
def get_queryset(self):
return ReferenceRange.objects.filter(test__tenant=self.request.user.tenant)
class ReferenceRangeCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new reference range.
"""
model = ReferenceRange
form_class = ReferenceRangeForm
template_name = 'laboratory/reference_ranges/reference_range_form.html'
permission_required = 'laboratory.add_referencerange'
success_url = reverse_lazy('laboratory:reference_range_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='REFERENCE_RANGE_CREATED',
model='ReferenceRange',
object_id=str(self.object.id),
details={
'test_name': self.object.test.test_name,
'gender': self.object.gender,
'age_range': f"{self.object.age_min}-{self.object.age_max}"
}
)
messages.success(self.request, 'Reference range created successfully.')
return response
class ReferenceRangeUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update an existing reference range.
"""
model = ReferenceRange
form_class = ReferenceRangeForm
template_name = 'laboratory/reference_ranges/reference_range_form.html'
permission_required = 'laboratory.change_referencerange'
def get_queryset(self):
return ReferenceRange.objects.filter(test__tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('laboratory:reference_range_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='REFERENCE_RANGE_UPDATED',
model='ReferenceRange',
object_id=str(self.object.id),
details={
'test_name': self.object.test.test_name,
'changes': form.changed_data
}
)
messages.success(self.request, 'Reference range updated successfully.')
return response
class ReferenceRangeDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
"""
Delete a reference range.
"""
model = ReferenceRange
template_name = 'laboratory/reference_ranges/reference_range_confirm_delete.html'
permission_required = 'laboratory.delete_referencerange'
success_url = reverse_lazy('laboratory:reference_range_list')
def get_queryset(self):
return ReferenceRange.objects.filter(test__tenant=self.request.user.tenant)
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
test_name = self.object.test.test_name
response = super().delete(request, *args, **kwargs)
# Log the action
AuditLogger.log_event(
user=request.user,
action='REFERENCE_RANGE_DELETED',
model='ReferenceRange',
object_id=str(self.object.id),
details={'test_name': test_name}
)
messages.success(request, 'Reference range deleted successfully.')
return response
class LabOrderListView(LoginRequiredMixin, ListView):
"""
List all laboratory orders with filtering and search.
"""
model = LabOrder
template_name = 'laboratory/orders/lab_order_list.html'
context_object_name = 'orders'
paginate_by = 25
def get_queryset(self):
queryset = LabOrder.objects.filter(tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(patient__first_name__icontains=search) |
Q(patient__last_name__icontains=search) |
Q(patient__mrn__icontains=search) |
Q(tests__test_name__icontains=search)
)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by priority
priority = self.request.GET.get('priority')
if priority:
queryset = queryset.filter(priority=priority)
# Filter by date range
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
if date_from:
queryset = queryset.filter(order_datetime__date__gte=date_from)
if date_to:
queryset = queryset.filter(order_datetime__date__lte=date_to)
return queryset.select_related(
'patient', 'ordering_provider'
).order_by('-order_datetime')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'statuses': LabOrder._meta.get_field('status').choices,
'priorities': LabOrder._meta.get_field('priority').choices,
})
return context
class LabOrderDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a laboratory order.
"""
model = LabOrder
template_name = 'laboratory/orders/lab_order_detail.html'
context_object_name = 'lab_order'
def get_queryset(self):
return LabOrder.objects.filter(tenant=self.request.user.tenant)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
lab_order = self.object
# Get specimens for this order
context['specimens'] = lab_order.specimens.all().order_by('-collected_datetime')
# Get results for this order
context['results'] = lab_order.results.all().order_by('-created_at')
return context
class LabOrderCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new laboratory order.
"""
model = LabOrder
form_class = LabOrderForm
template_name = 'laboratory/orders/lab_order_form.html'
permission_required = 'laboratory.add_laborder'
success_url = reverse_lazy('laboratory:lab_order_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.ordering_provider = self.request.user
form.instance.order_datetime = timezone.now()
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='LAB_ORDER_CREATED',
model='LabOrder',
object_id=str(self.object.order_id),
details={
'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
'test_name': self.object.test.test_name,
'priority': self.object.priority
}
)
messages.success(self.request, 'Laboratory order created successfully.')
return response
class LabOrderUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update laboratory order (limited to status and notes only).
"""
model = LabOrder
fields = ['status', 'notes'] # Restricted fields for clinical orders
template_name = 'laboratory/orders/lab_order_form.html'
permission_required = 'laboratory.change_laborder'
def get_queryset(self):
return LabOrder.objects.filter(tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('laboratory:lab_order_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='LAB_ORDER_UPDATED',
model='LabOrder',
object_id=str(self.object.order_id),
details={
'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
'changes': form.changed_data
}
)
messages.success(self.request, 'Laboratory order updated successfully.')
return response
class SpecimenListView(LoginRequiredMixin, ListView):
"""
List all specimens with filtering and search.
"""
model = Specimen
template_name = 'laboratory/specimens/specimen_list.html'
context_object_name = 'specimens'
paginate_by = 25
def get_queryset(self):
queryset = Specimen.objects.filter(order__tenant=self.request.user.tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(specimen_id__icontains=search) |
Q(order__patient__first_name__icontains=search) |
Q(order__patient__last_name__icontains=search) |
Q(order__patient__mrn__icontains=search)
)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by specimen type
specimen_type = self.request.GET.get('specimen_type')
if specimen_type:
queryset = queryset.filter(specimen_type=specimen_type)
return queryset.select_related('order__patient').order_by('-collected_datetime')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'statuses': Specimen._meta.get_field('status').choices,
'specimen_types': Specimen._meta.get_field('specimen_type').choices,
})
return context
class SpecimenDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a specimen.
"""
model = Specimen
template_name = 'laboratory/specimens/specimen_detail.html'
context_object_name = 'specimen'
def get_queryset(self):
return Specimen.objects.filter(order__tenant=self.request.user.tenant)
class SpecimenCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new specimen record.
"""
model = Specimen
form_class = SpecimenForm
template_name = 'laboratory/specimens/specimen_form.html'
permission_required = 'laboratory.add_specimen'
success_url = reverse_lazy('laboratory:specimen_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.collected_by = self.request.user
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='SPECIMEN_COLLECTED',
model='Specimen',
object_id=str(self.object.specimen_id),
details={
'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
'specimen_type': self.object.specimen_type
}
)
messages.success(self.request, 'Specimen record created successfully.')
return response
class SpecimenUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update specimen (limited to status and processing notes).
"""
model = Specimen
fields = ['status'] # Restricted fields
template_name = 'laboratory/specimens/specimen_form.html'
permission_required = 'laboratory.change_specimen'
def get_queryset(self):
return Specimen.objects.filter(order__tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('laboratory:specimen_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_event(
user=self.request.user,
action='SPECIMEN_UPDATED',
model='Specimen',
object_id=str(self.object.specimen_id),
details={
'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
'changes': form.changed_data
}
)
messages.success(self.request, 'Specimen updated successfully.')
return response
class LabResultListView(LoginRequiredMixin, ListView):
"""
List all laboratory results with filtering and search.
"""
model = LabResult
template_name = 'laboratory/results/lab_result_list.html'
context_object_name = 'lab_results'
paginate_by = 25
def get_queryset(self):
tenant = self.request.user.tenant
queryset = LabResult.objects.filter(order__tenant=tenant)
# Search functionality
search = self.request.GET.get('search')
if search:
queryset = queryset.filter(
Q(order__patient__first_name__icontains=search) |
Q(order__patient__last_name__icontains=search) |
Q(order__patient__mrn__icontains=search) |
Q(test__test_name__icontains=search)
)
# Filter by status
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by critical results
critical_only = self.request.GET.get('critical_only')
if critical_only:
queryset = queryset.filter(is_critical=True)
return queryset.select_related(
'order__patient', 'test', 'verified_by'
).order_by('-created_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'statuses': LabResult._meta.get_field('status').choices,
})
return context
class LabResultDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a laboratory result.
"""
model = LabResult
template_name = 'laboratory/results/lab_result_detail.html'
context_object_name = 'lab_result'
def get_queryset(self):
tenant = self.request.user.tenant
return LabResult.objects.filter(order__tenant=tenant)
def get_context_data(self, **kwargs):
tenant = self.request.user.tenant
context = super().get_context_data(**kwargs)
lab_result = self.object
# Get applicable reference ranges
patient = lab_result.order.patient
context['reference_ranges'] = ReferenceRange.objects.filter(
test=lab_result.test,
test__tenant=tenant,
age_min__lte=patient.age if hasattr(patient, 'age') else 999,
age_max__gte=patient.age if hasattr(patient, 'age') else 0,
).filter(
Q(gender=patient.gender) | Q(gender='BOTH')
)
return context
class LabResultCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new laboratory result.
"""
model = LabResult
form_class = LabResultForm
template_name = 'laboratory/results/lab_result_form.html'
permission_required = 'laboratory.add_labresult'
success_url = reverse_lazy('laboratory:lab_result_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.result_datetime = timezone.now()
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='LAB_RESULT_ENTERED',
model='LabResult',
object_id=str(self.object.result_id),
details={
'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
'test_name': self.object.test.test_name,
'is_critical': self.object.is_critical
}
)
messages.success(self.request, 'Laboratory result entered successfully.')
return response
# Note: No UpdateView or DeleteView for LabResult - Append-only for clinical records
# Results can only be verified or corrected through addendum process
class QualityControlListView(LoginRequiredMixin, ListView):
"""
List all quality control records.
"""
model = QualityControl
template_name = 'laboratory/quality_control/qc_sample_list.html'
context_object_name = 'qc_samples'
paginate_by = 25
def get_queryset(self):
queryset = QualityControl.objects.filter(tenant=self.request.user.tenant)
# Filter by test
test_id = self.request.GET.get('test')
if test_id:
queryset = queryset.filter(test_id=test_id)
# Filter by result
result = self.request.GET.get('result')
if result:
queryset = queryset.filter(result=result)
return queryset.select_related('test', 'performed_by')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'lab_tests': LabTest.objects.filter(
tenant=self.request.user.tenant,
is_active=True
).order_by('test_name'),
'results': QualityControl._meta.get_field('result').choices,
})
return context
class QualityControlDetailView(LoginRequiredMixin, DetailView):
"""
Display detailed information about a quality control record.
"""
model = QualityControl
template_name = 'laboratory/quality_control/qc_sample_detail.html'
context_object_name = 'qc_record'
def get_queryset(self):
return QualityControl.objects.filter(tenant=self.request.user.tenant)
class QualityControlCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
Create a new quality control record.
"""
model = QualityControl
form_class = QualityControlForm
template_name = 'laboratory/quality_control/qc_sample_form.html'
permission_required = 'laboratory.add_qualitycontrol'
success_url = reverse_lazy('laboratory:quality_control_list')
def form_valid(self, form):
form.instance.tenant = self.request.user.tenant
form.instance.performed_by = self.request.user
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='QC_TEST_PERFORMED',
model='QualityControl',
object_id=str(self.object.id),
details={
'test_name': self.object.test.test_name,
'result': self.object.result
}
)
messages.success(self.request, 'Quality control record created successfully.')
return response
class QualityControlUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
Update quality control record (limited fields).
"""
model = QualityControl
fields = ['result', 'comments'] # Limited fields for operational data
template_name = 'laboratory/quality_control_form.html'
permission_required = 'laboratory.change_qualitycontrol'
def get_queryset(self):
return QualityControl.objects.filter(tenant=self.request.user.tenant)
def get_success_url(self):
return reverse('laboratory:quality_control_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
user=self.request.user,
action='QC_TEST_UPDATED',
model='QualityControl',
object_id=str(self.object.id),
details={
'test_name': self.object.test.test_name,
'changes': form.changed_data
}
)
messages.success(self.request, 'Quality control record updated successfully.')
return response
@login_required
def laboratory_stats(request):
"""
HTMX endpoint for laboratory statistics.
"""
tenant = request.user.tenant
today = timezone.now().date()
stats = {
'pending_orders': LabOrder.objects.filter(
tenant=tenant,
status='PENDING'
).count(),
'in_progress_orders': LabOrder.objects.filter(
tenant=tenant,
status='IN_PROGRESS'
).count(),
'results_pending': LabResult.objects.filter(
order__tenant=tenant,
status='PENDING'
).count(),
'critical_results': LabResult.objects.filter(
order__tenant=tenant,
is_critical=True,
status='VERIFIED',
created_at__date=today
).count(),
'specimens_collected_today': LabResult.objects.filter(
order__tenant=tenant,
specimen__status='COLLECTED',
specimen__collected_datetime__date=today
).count(),
}
return render(request, 'laboratory/partials/lab_stats.html', {'stats': stats})
@login_required
def order_search(request):
"""
HTMX endpoint for order search.
"""
search = request.GET.get('search', '')
status = request.GET.get('status', '')
queryset = LabOrder.objects.filter(tenant=request.user.tenant)
if search:
queryset = queryset.filter(
Q(patient__first_name__icontains=search) |
Q(patient__last_name__icontains=search) |
Q(patient__mrn__icontains=search) |
Q(test__test_name__icontains=search)
)
if status:
queryset = queryset.filter(status=status)
orders = queryset.select_related(
'patient', 'test', 'ordering_provider'
).order_by('-order_datetime')[:20]
return render(request, 'laboratory/partials/order_list.html', {'orders': orders})
@login_required
def receive_specimen(request, order_id):
"""
Mark specimen as received for processing.
"""
if request.method == 'POST':
order = get_object_or_404(
LabOrder,
id=order_id,
tenant=request.user.tenant
)
# Update specimen status
specimens = order.specimens.filter(status='COLLECTED')
specimens.update(
status='RECEIVED',
received_datetime=timezone.now(),
received_by=request.user
)
# Update order status
order.status = 'IN_PROGRESS'
order.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='SPECIMEN_RECEIVED',
model='LabOrder',
object_id=str(order.order_id),
details={
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
'specimens_count': specimens.count()
}
)
messages.success(request, 'Specimen(s) marked as received.')
if request.headers.get('HX-Request'):
return render(request, 'laboratory/partials/order_status.html', {'order': order})
return redirect('laboratory:lab_order_detail', pk=order.pk)
return JsonResponse({'success': False})
@login_required
def start_processing(request, order_id):
"""
Start processing laboratory order.
"""
if request.method == 'POST':
order = get_object_or_404(
LabOrder,
id=order_id,
tenant=request.user.tenant
)
order.status = 'IN_PROGRESS'
order.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='LAB_PROCESSING_STARTED',
model='LabOrder',
object_id=str(order.order_id),
details={
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
'test_name': order.test.test_name
}
)
messages.success(request, 'Laboratory processing started.')
if request.headers.get('HX-Request'):
return render(request, 'laboratory/partials/order_status.html', {'order': order})
return redirect('laboratory:lab_order_detail', pk=order.pk)
return JsonResponse({'success': False})
@login_required
def reject_specimen(request, specimen_id):
"""
Reject a specimen with reason.
"""
if request.method == 'POST':
specimen = get_object_or_404(
Specimen,
id=specimen_id,
tenant=request.user.tenant
)
rejection_reason = request.POST.get('rejection_reason', '')
specimen.status = 'REJECTED'
specimen.rejection_reason = rejection_reason
specimen.rejected_by = request.user
specimen.rejected_datetime = timezone.now()
specimen.save()
# Update order status
specimen.order.status = 'CANCELLED'
specimen.order.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='SPECIMEN_REJECTED',
model='Specimen',
object_id=str(specimen.specimen_id),
details={
'patient_name': f"{specimen.order.patient.first_name} {specimen.order.patient.last_name}",
'rejection_reason': rejection_reason
}
)
messages.warning(request, 'Specimen rejected.')
if request.headers.get('HX-Request'):
return render(request, 'laboratory/partials/specimen_status.html', {'specimen': specimen})
return redirect('laboratory:specimen_detail', pk=specimen.pk)
return JsonResponse({'success': False})
@login_required
def schedule_collection(request, order_id):
"""
Schedule specimen collection for an order.
"""
if request.method == 'POST':
order = get_object_or_404(
LabOrder,
id=order_id,
tenant=request.user.tenant
)
collection_datetime = request.POST.get('collection_datetime')
if collection_datetime:
collection_datetime = timezone.datetime.fromisoformat(collection_datetime)
order.scheduled_collection = collection_datetime
order.status = 'SCHEDULED'
order.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='COLLECTION_SCHEDULED',
model='LabOrder',
object_id=str(order.order_id),
details={
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
'scheduled_time': collection_datetime.isoformat()
}
)
messages.success(request, 'Collection scheduled successfully.')
if request.headers.get('HX-Request'):
return render(request, 'laboratory/partials/order_status.html', {'order': order})
return redirect('laboratory:lab_order_detail', pk=order.pk)
return JsonResponse({'success': False})
@login_required
def mark_collected(request, order_id):
"""
Mark specimen as collected.
"""
if request.method == 'POST':
order = get_object_or_404(
LabOrder,
id=order_id,
tenant=request.user.tenant
)
# Create specimen record if it doesn't exist
specimen, created = Specimen.objects.get_or_create(
order=order,
tenant=request.user.tenant,
defaults={
'specimen_type': 'BLOOD', # Default, should be specified
'collection_datetime': timezone.now(),
'collected_by': request.user,
'status': 'COLLECTED'
}
)
if not created:
specimen.status = 'COLLECTED'
specimen.collection_datetime = timezone.now()
specimen.collected_by = request.user
specimen.save()
# Update order status
order.status = 'COLLECTED'
order.save()
# Log the action
AuditLogger.log_event(
user=request.user,
action='SPECIMEN_COLLECTED',
model='LabOrder',
object_id=str(order.order_id),
details={
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
'specimen_type': specimen.specimen_type
}
)
messages.success(request, 'Specimen marked as collected.')
if request.headers.get('HX-Request'):
return render(request, 'laboratory/partials/order_status.html', {'order': order})
return redirect('laboratory:lab_order_detail', pk=order.pk)
return JsonResponse({'success': False})
#
# """
# Laboratory app views with healthcare-focused CRUD operations.
# Implements appropriate access patterns for clinical laboratory workflows.
# """
#
# from django.shortcuts import render, get_object_or_404, redirect
# from django.contrib.auth.decorators import login_required
# from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# from django.views.generic import (
# ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView
# )
# from django.http import JsonResponse, HttpResponse
# from django.db.models import Q, Count, Avg, Sum, F
# from django.utils import timezone
# from django.contrib import messages
# from django.urls import reverse_lazy, reverse
# from django.core.paginator import Paginator
# from django.template.loader import render_to_string
# from datetime import datetime, timedelta, date
# import json
#
# from core.utils import AuditLogger
# from .models import (
# LabTest, LabOrder, Specimen, LabResult, QualityControl, ReferenceRange
# )
# from .forms import (
# LabTestForm, LabOrderForm, SpecimenForm, LabResultForm,
# QualityControlForm, ReferenceRangeForm
# )
#
#
# # ============================================================================
# # DASHBOARD AND OVERVIEW VIEWS
# # ============================================================================
#
# class LaboratoryDashboardView(LoginRequiredMixin, TemplateView):
# """
# Main laboratory dashboard with key metrics and recent activity.
# """
# template_name = 'laboratory/dashboard.html'
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# tenant = self.request.user.tenant
# today = timezone.now().date()
#
# # Dashboard statistics
# context.update({
# 'pending_orders': LabOrder.objects.filter(
# tenant=tenant,
# status='PENDING'
# ).count(),
# 'in_progress_orders': LabOrder.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# 'specimens_collected': Specimen.objects.filter(
# tenant=tenant,
# collection_datetime__date=today
# ).count(),
# 'results_pending': LabResult.objects.filter(
# tenant=tenant,
# status='PENDING'
# ).count(),
# 'results_completed_today': LabResult.objects.filter(
# tenant=tenant,
# result_datetime__date=today,
# status='VERIFIED'
# ).count(),
# 'qc_tests_today': QualityControl.objects.filter(
# tenant=tenant,
# test_date=today
# ).count(),
# 'total_tests_available': LabTest.objects.filter(
# tenant=tenant,
# is_active=True
# ).count(),
# 'critical_results': LabResult.objects.filter(
# tenant=tenant,
# is_critical=True,
# status='VERIFIED',
# result_datetime__date=today
# ).count(),
# })
#
# # Recent orders
# context['recent_orders'] = LabOrder.objects.filter(
# tenant=tenant
# ).select_related('patient', 'ordering_provider').order_by('-order_datetime')[:10]
#
# # Recent results
# context['recent_results'] = LabResult.objects.filter(
# tenant=tenant,
# status='VERIFIED'
# ).select_related('order', 'test').order_by('-result_datetime')[:10]
#
# return context
#
#
# # ============================================================================
# # LAB TEST VIEWS (FULL CRUD - Master Data)
# # ============================================================================
#
# class LabTestListView(LoginRequiredMixin, ListView):
# """
# List all laboratory tests with filtering and search.
# """
# model = LabTest
# template_name = 'laboratory/lab_test_list.html'
# context_object_name = 'lab_tests'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = LabTest.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(test_name__icontains=search) |
# Q(test_code__icontains=search) |
# Q(test_description__icontains=search)
# )
#
# # Filter by category
# category = self.request.GET.get('category')
# if category:
# queryset = queryset.filter(category=category)
#
# # Filter by active status
# active_only = self.request.GET.get('active_only')
# if active_only:
# queryset = queryset.filter(is_active=True)
#
# return queryset.order_by('test_name')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'categories': LabTest._meta.get_field('category').choices,
# 'search_query': self.request.GET.get('search', ''),
# })
# return context
#
#
# class LabTestDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a laboratory test.
# """
# model = LabTest
# template_name = 'laboratory/lab_test_detail.html'
# context_object_name = 'lab_test'
#
# def get_queryset(self):
# return LabTest.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# lab_test = self.object
#
# # Get reference ranges for this test
# context['reference_ranges'] = lab_test.reference_ranges.all().order_by('age_min', 'gender')
#
# # Get recent orders for this test
# context['recent_orders'] = LabOrder.objects.filter(
# test=lab_test,
# tenant=self.request.user.tenant
# ).select_related('patient').order_by('-order_datetime')[:10]
#
# # Get quality control records
# context['qc_records'] = QualityControl.objects.filter(
# test=lab_test,
# tenant=self.request.user.tenant
# ).order_by('-test_date')[:5]
#
# return context
#
#
# class LabTestCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new laboratory test.
# """
# model = LabTest
# form_class = LabTestForm
# template_name = 'laboratory/lab_test_form.html'
# permission_required = 'laboratory.add_labtest'
# success_url = reverse_lazy('laboratory:lab_test_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='LAB_TEST_CREATED',
# model='LabTest',
# object_id=str(self.object.test_id),
# details={
# 'test_name': self.object.test_name,
# 'test_code': self.object.test_code
# }
# )
#
# messages.success(self.request, f'Lab test "{self.object.test_name}" created successfully.')
# return response
#
#
# class LabTestUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an existing laboratory test.
# """
# model = LabTest
# form_class = LabTestForm
# template_name = 'laboratory/lab_test_form.html'
# permission_required = 'laboratory.change_labtest'
#
# def get_queryset(self):
# return LabTest.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('laboratory:lab_test_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='LAB_TEST_UPDATED',
# model='LabTest',
# object_id=str(self.object.test_id),
# details={
# 'test_name': self.object.test_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, f'Lab test "{self.object.test_name}" updated successfully.')
# return response
#
#
# class LabTestDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete a laboratory test (soft delete by deactivating).
# """
# model = LabTest
# template_name = 'laboratory/lab_test_confirm_delete.html'
# permission_required = 'laboratory.delete_labtest'
# success_url = reverse_lazy('laboratory:lab_test_list')
#
# def get_queryset(self):
# return LabTest.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
#
# # Soft delete by deactivating instead of actual deletion
# self.object.is_active = False
# self.object.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='LAB_TEST_DEACTIVATED',
# model='LabTest',
# object_id=str(self.object.test_id),
# details={'test_name': self.object.test_name}
# )
#
# messages.success(request, f'Lab test "{self.object.test_name}" deactivated successfully.')
# return redirect(self.success_url)
#
#
# # ============================================================================
# # REFERENCE RANGE VIEWS (FULL CRUD - Reference Data)
# # ============================================================================
#
# class ReferenceRangeListView(LoginRequiredMixin, ListView):
# """
# List all reference ranges with filtering.
# """
# model = ReferenceRange
# template_name = 'laboratory/reference_range_list.html'
# context_object_name = 'reference_ranges'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = ReferenceRange.objects.filter(tenant=self.request.user.tenant)
#
# # Filter by test
# test_id = self.request.GET.get('test')
# if test_id:
# queryset = queryset.filter(test_id=test_id)
#
# # Filter by gender
# gender = self.request.GET.get('gender')
# if gender:
# queryset = queryset.filter(gender=gender)
#
# return queryset.select_related('test').order_by('test__test_name', 'age_min')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'lab_tests': LabTest.objects.filter(
# tenant=self.request.user.tenant,
# is_active=True
# ).order_by('test_name'),
# 'genders': ReferenceRange._meta.get_field('gender').choices,
# })
# return context
#
#
# class ReferenceRangeDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a reference range.
# """
# model = ReferenceRange
# template_name = 'laboratory/reference_range_detail.html'
# context_object_name = 'reference_range'
#
# def get_queryset(self):
# return ReferenceRange.objects.filter(tenant=self.request.user.tenant)
#
#
# class ReferenceRangeCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new reference range.
# """
# model = ReferenceRange
# form_class = ReferenceRangeForm
# template_name = 'laboratory/reference_range_form.html'
# permission_required = 'laboratory.add_referencerange'
# success_url = reverse_lazy('laboratory:reference_range_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='REFERENCE_RANGE_CREATED',
# model='ReferenceRange',
# object_id=str(self.object.id),
# details={
# 'test_name': self.object.test.test_name,
# 'gender': self.object.gender,
# 'age_range': f"{self.object.age_min}-{self.object.age_max}"
# }
# )
#
# messages.success(self.request, 'Reference range created successfully.')
# return response
#
#
# class ReferenceRangeUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update an existing reference range.
# """
# model = ReferenceRange
# form_class = ReferenceRangeForm
# template_name = 'laboratory/reference_range_form.html'
# permission_required = 'laboratory.change_referencerange'
#
# def get_queryset(self):
# return ReferenceRange.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('laboratory:reference_range_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='REFERENCE_RANGE_UPDATED',
# model='ReferenceRange',
# object_id=str(self.object.id),
# details={
# 'test_name': self.object.test.test_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Reference range updated successfully.')
# return response
#
#
# class ReferenceRangeDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
# """
# Delete a reference range.
# """
# model = ReferenceRange
# template_name = 'laboratory/reference_range_confirm_delete.html'
# permission_required = 'laboratory.delete_referencerange'
# success_url = reverse_lazy('laboratory:reference_range_list')
#
# def get_queryset(self):
# return ReferenceRange.objects.filter(tenant=self.request.user.tenant)
#
# def delete(self, request, *args, **kwargs):
# self.object = self.get_object()
# test_name = self.object.test.test_name
#
# response = super().delete(request, *args, **kwargs)
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='REFERENCE_RANGE_DELETED',
# model='ReferenceRange',
# object_id=str(self.object.id),
# details={'test_name': test_name}
# )
#
# messages.success(request, 'Reference range deleted successfully.')
# return response
#
#
# # ============================================================================
# # LAB ORDER VIEWS (RESTRICTED CRUD - Clinical Orders)
# # ============================================================================
#
# class LabOrderListView(LoginRequiredMixin, ListView):
# """
# List all laboratory orders with filtering and search.
# """
# model = LabOrder
# template_name = 'laboratory/lab_order_list.html'
# context_object_name = 'lab_orders'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = LabOrder.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search) |
# Q(patient__last_name__icontains=search) |
# Q(patient__mrn__icontains=search) |
# Q(test__test_name__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by priority
# priority = self.request.GET.get('priority')
# if priority:
# queryset = queryset.filter(priority=priority)
#
# # Filter by date range
# date_from = self.request.GET.get('date_from')
# date_to = self.request.GET.get('date_to')
# if date_from:
# queryset = queryset.filter(order_datetime__date__gte=date_from)
# if date_to:
# queryset = queryset.filter(order_datetime__date__lte=date_to)
#
# return queryset.select_related(
# 'patient', 'test', 'ordering_provider'
# ).order_by('-order_datetime')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': LabOrder._meta.get_field('status').choices,
# 'priorities': LabOrder._meta.get_field('priority').choices,
# })
# return context
#
#
# class LabOrderDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a laboratory order.
# """
# model = LabOrder
# template_name = 'laboratory/lab_order_detail.html'
# context_object_name = 'lab_order'
#
# def get_queryset(self):
# return LabOrder.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# lab_order = self.object
#
# # Get specimens for this order
# context['specimens'] = lab_order.specimens.all().order_by('-collection_datetime')
#
# # Get results for this order
# context['results'] = lab_order.results.all().order_by('-result_datetime')
#
# return context
#
#
# class LabOrderCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new laboratory order.
# """
# model = LabOrder
# form_class = LabOrderForm
# template_name = 'laboratory/lab_order_form.html'
# permission_required = 'laboratory.add_laborder'
# success_url = reverse_lazy('laboratory:lab_order_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.ordering_provider = self.request.user
# form.instance.order_datetime = timezone.now()
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='LAB_ORDER_CREATED',
# model='LabOrder',
# object_id=str(self.object.order_id),
# details={
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
# 'test_name': self.object.test.test_name,
# 'priority': self.object.priority
# }
# )
#
# messages.success(self.request, 'Laboratory order created successfully.')
# return response
#
#
# class LabOrderUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update laboratory order (limited to status and notes only).
# """
# model = LabOrder
# fields = ['status', 'notes'] # Restricted fields for clinical orders
# template_name = 'laboratory/lab_order_update_form.html'
# permission_required = 'laboratory.change_laborder'
#
# def get_queryset(self):
# return LabOrder.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('laboratory:lab_order_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='LAB_ORDER_UPDATED',
# model='LabOrder',
# object_id=str(self.object.order_id),
# details={
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Laboratory order updated successfully.')
# return response
#
#
# # ============================================================================
# # SPECIMEN VIEWS (RESTRICTED CRUD - Clinical Data)
# # ============================================================================
#
# class SpecimenListView(LoginRequiredMixin, ListView):
# """
# List all specimens with filtering and search.
# """
# model = Specimen
# template_name = 'laboratory/specimen_list.html'
# context_object_name = 'specimens'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = Specimen.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(specimen_id__icontains=search) |
# Q(order__patient__first_name__icontains=search) |
# Q(order__patient__last_name__icontains=search) |
# Q(order__patient__mrn__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by specimen type
# specimen_type = self.request.GET.get('specimen_type')
# if specimen_type:
# queryset = queryset.filter(specimen_type=specimen_type)
#
# return queryset.select_related('order__patient', 'order__test').order_by('-collection_datetime')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': Specimen._meta.get_field('status').choices,
# 'specimen_types': Specimen._meta.get_field('specimen_type').choices,
# })
# return context
#
#
# class SpecimenDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a specimen.
# """
# model = Specimen
# template_name = 'laboratory/specimen_detail.html'
# context_object_name = 'specimen'
#
# def get_queryset(self):
# return Specimen.objects.filter(tenant=self.request.user.tenant)
#
#
# class SpecimenCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new specimen record.
# """
# model = Specimen
# form_class = SpecimenForm
# template_name = 'laboratory/specimen_form.html'
# permission_required = 'laboratory.add_specimen'
# success_url = reverse_lazy('laboratory:specimen_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.collected_by = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='SPECIMEN_COLLECTED',
# model='Specimen',
# object_id=str(self.object.specimen_id),
# details={
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
# 'specimen_type': self.object.specimen_type
# }
# )
#
# messages.success(self.request, 'Specimen record created successfully.')
# return response
#
#
# class SpecimenUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update specimen (limited to status and processing notes).
# """
# model = Specimen
# fields = ['status', 'processing_notes'] # Restricted fields
# template_name = 'laboratory/specimen_update_form.html'
# permission_required = 'laboratory.change_specimen'
#
# def get_queryset(self):
# return Specimen.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('laboratory:specimen_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='SPECIMEN_UPDATED',
# model='Specimen',
# object_id=str(self.object.specimen_id),
# details={
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Specimen updated successfully.')
# return response
#
#
# # ============================================================================
# # LAB RESULT VIEWS (APPEND-ONLY - Clinical Records)
# # ============================================================================
#
# class LabResultListView(LoginRequiredMixin, ListView):
# """
# List all laboratory results with filtering and search.
# """
# model = LabResult
# template_name = 'laboratory/lab_result_list.html'
# context_object_name = 'lab_results'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = LabResult.objects.filter(tenant=self.request.user.tenant)
#
# # Search functionality
# search = self.request.GET.get('search')
# if search:
# queryset = queryset.filter(
# Q(order__patient__first_name__icontains=search) |
# Q(order__patient__last_name__icontains=search) |
# Q(order__patient__mrn__icontains=search) |
# Q(test__test_name__icontains=search)
# )
#
# # Filter by status
# status = self.request.GET.get('status')
# if status:
# queryset = queryset.filter(status=status)
#
# # Filter by critical results
# critical_only = self.request.GET.get('critical_only')
# if critical_only:
# queryset = queryset.filter(is_critical=True)
#
# return queryset.select_related(
# 'order__patient', 'test', 'verified_by'
# ).order_by('-result_datetime')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'statuses': LabResult._meta.get_field('status').choices,
# })
# return context
#
#
# class LabResultDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a laboratory result.
# """
# model = LabResult
# template_name = 'laboratory/lab_result_detail.html'
# context_object_name = 'lab_result'
#
# def get_queryset(self):
# return LabResult.objects.filter(tenant=self.request.user.tenant)
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# lab_result = self.object
#
# # Get applicable reference ranges
# patient = lab_result.order.patient
# context['reference_ranges'] = ReferenceRange.objects.filter(
# test=lab_result.test,
# tenant=self.request.user.tenant,
# age_min__lte=patient.age if hasattr(patient, 'age') else 999,
# age_max__gte=patient.age if hasattr(patient, 'age') else 0,
# ).filter(
# Q(gender=patient.gender) | Q(gender='BOTH')
# )
#
# return context
#
#
# class LabResultCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new laboratory result.
# """
# model = LabResult
# form_class = LabResultForm
# template_name = 'laboratory/lab_result_form.html'
# permission_required = 'laboratory.add_labresult'
# success_url = reverse_lazy('laboratory:lab_result_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.result_datetime = timezone.now()
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='LAB_RESULT_ENTERED',
# model='LabResult',
# object_id=str(self.object.result_id),
# details={
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
# 'test_name': self.object.test.test_name,
# 'is_critical': self.object.is_critical
# }
# )
#
# messages.success(self.request, 'Laboratory result entered successfully.')
# return response
#
#
# # Note: No UpdateView or DeleteView for LabResult - Append-only for clinical records
# # Results can only be verified or corrected through addendum process
#
#
# # ============================================================================
# # QUALITY CONTROL VIEWS (LIMITED CRUD - Operational Data)
# # ============================================================================
#
# class QualityControlListView(LoginRequiredMixin, ListView):
# """
# List all quality control records.
# """
# model = QualityControl
# template_name = 'laboratory/quality_control_list.html'
# context_object_name = 'qc_records'
# paginate_by = 25
#
# def get_queryset(self):
# queryset = QualityControl.objects.filter(tenant=self.request.user.tenant)
#
# # Filter by test
# test_id = self.request.GET.get('test')
# if test_id:
# queryset = queryset.filter(test_id=test_id)
#
# # Filter by result
# result = self.request.GET.get('result')
# if result:
# queryset = queryset.filter(result=result)
#
# return queryset.select_related('test', 'performed_by').order_by('-test_date')
#
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context.update({
# 'lab_tests': LabTest.objects.filter(
# tenant=self.request.user.tenant,
# is_active=True
# ).order_by('test_name'),
# 'results': QualityControl._meta.get_field('result').choices,
# })
# return context
#
#
# class QualityControlDetailView(LoginRequiredMixin, DetailView):
# """
# Display detailed information about a quality control record.
# """
# model = QualityControl
# template_name = 'laboratory/quality_control_detail.html'
# context_object_name = 'qc_record'
#
# def get_queryset(self):
# return QualityControl.objects.filter(tenant=self.request.user.tenant)
#
#
# class QualityControlCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
# """
# Create a new quality control record.
# """
# model = QualityControl
# form_class = QualityControlForm
# template_name = 'laboratory/quality_control_form.html'
# permission_required = 'laboratory.add_qualitycontrol'
# success_url = reverse_lazy('laboratory:quality_control_list')
#
# def form_valid(self, form):
# form.instance.tenant = self.request.user.tenant
# form.instance.performed_by = self.request.user
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='QC_TEST_PERFORMED',
# model='QualityControl',
# object_id=str(self.object.id),
# details={
# 'test_name': self.object.test.test_name,
# 'result': self.object.result
# }
# )
#
# messages.success(self.request, 'Quality control record created successfully.')
# return response
#
#
# class QualityControlUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
# """
# Update quality control record (limited fields).
# """
# model = QualityControl
# fields = ['result', 'comments'] # Limited fields for operational data
# template_name = 'laboratory/quality_control_form.html'
# permission_required = 'laboratory.change_qualitycontrol'
#
# def get_queryset(self):
# return QualityControl.objects.filter(tenant=self.request.user.tenant)
#
# def get_success_url(self):
# return reverse('laboratory:quality_control_detail', kwargs={'pk': self.object.pk})
#
# def form_valid(self, form):
# response = super().form_valid(form)
#
# # Log the action
# AuditLogger.log_action(
# user=self.request.user,
# action='QC_TEST_UPDATED',
# model='QualityControl',
# object_id=str(self.object.id),
# details={
# 'test_name': self.object.test.test_name,
# 'changes': form.changed_data
# }
# )
#
# messages.success(self.request, 'Quality control record updated successfully.')
# return response
#
#
# # ============================================================================
# # HTMX VIEWS FOR REAL-TIME UPDATES
# # ============================================================================
#
# @login_required
# def laboratory_stats(request):
# """
# HTMX endpoint for laboratory statistics.
# """
# tenant = request.user.tenant
# today = timezone.now().date()
#
# stats = {
# 'pending_orders': LabOrder.objects.filter(
# tenant=tenant,
# status='PENDING'
# ).count(),
# 'in_progress_orders': LabOrder.objects.filter(
# tenant=tenant,
# status='IN_PROGRESS'
# ).count(),
# 'results_pending': LabResult.objects.filter(
# tenant=tenant,
# status='PENDING'
# ).count(),
# 'critical_results': LabResult.objects.filter(
# tenant=tenant,
# is_critical=True,
# status='VERIFIED',
# result_datetime__date=today
# ).count(),
# }
#
# return render(request, 'laboratory/partials/laboratory_stats.html', {'stats': stats})
#
#
# @login_required
# def order_search(request):
# """
# HTMX endpoint for order search.
# """
# search = request.GET.get('search', '')
# status = request.GET.get('status', '')
#
# queryset = LabOrder.objects.filter(tenant=request.user.tenant)
#
# if search:
# queryset = queryset.filter(
# Q(patient__first_name__icontains=search) |
# Q(patient__last_name__icontains=search) |
# Q(patient__mrn__icontains=search) |
# Q(test__test_name__icontains=search)
# )
#
# if status:
# queryset = queryset.filter(status=status)
#
# orders = queryset.select_related(
# 'patient', 'test', 'ordering_provider'
# ).order_by('-order_datetime')[:20]
#
# return render(request, 'laboratory/partials/order_list.html', {'orders': orders})
#
#
# # ============================================================================
# # ACTION VIEWS
# # ============================================================================
#
# @login_required
# def receive_specimen(request, order_id):
# """
# Mark specimen as received for processing.
# """
# if request.method == 'POST':
# order = get_object_or_404(
# LabOrder,
# id=order_id,
# tenant=request.user.tenant
# )
#
# # Update specimen status
# specimens = order.specimens.filter(status='COLLECTED')
# specimens.update(
# status='RECEIVED',
# received_datetime=timezone.now(),
# received_by=request.user
# )
#
# # Update order status
# order.status = 'IN_PROGRESS'
# order.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SPECIMEN_RECEIVED',
# model='LabOrder',
# object_id=str(order.order_id),
# details={
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
# 'specimens_count': specimens.count()
# }
# )
#
# messages.success(request, 'Specimen(s) marked as received.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'laboratory/partials/order_status.html', {'order': order})
#
# return redirect('laboratory:lab_order_detail', pk=order.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def start_processing(request, order_id):
# """
# Start processing laboratory order.
# """
# if request.method == 'POST':
# order = get_object_or_404(
# LabOrder,
# id=order_id,
# tenant=request.user.tenant
# )
#
# order.status = 'IN_PROGRESS'
# order.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='LAB_PROCESSING_STARTED',
# model='LabOrder',
# object_id=str(order.order_id),
# details={
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
# 'test_name': order.test.test_name
# }
# )
#
# messages.success(request, 'Laboratory processing started.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'laboratory/partials/order_status.html', {'order': order})
#
# return redirect('laboratory:lab_order_detail', pk=order.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def reject_specimen(request, specimen_id):
# """
# Reject a specimen with reason.
# """
# if request.method == 'POST':
# specimen = get_object_or_404(
# Specimen,
# id=specimen_id,
# tenant=request.user.tenant
# )
#
# rejection_reason = request.POST.get('rejection_reason', '')
#
# specimen.status = 'REJECTED'
# specimen.rejection_reason = rejection_reason
# specimen.rejected_by = request.user
# specimen.rejected_datetime = timezone.now()
# specimen.save()
#
# # Update order status
# specimen.order.status = 'CANCELLED'
# specimen.order.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SPECIMEN_REJECTED',
# model='Specimen',
# object_id=str(specimen.specimen_id),
# details={
# 'patient_name': f"{specimen.order.patient.first_name} {specimen.order.patient.last_name}",
# 'rejection_reason': rejection_reason
# }
# )
#
# messages.warning(request, 'Specimen rejected.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'laboratory/partials/specimen_status.html', {'specimen': specimen})
#
# return redirect('laboratory:specimen_detail', pk=specimen.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def schedule_collection(request, order_id):
# """
# Schedule specimen collection for an order.
# """
# if request.method == 'POST':
# order = get_object_or_404(
# LabOrder,
# id=order_id,
# tenant=request.user.tenant
# )
#
# collection_datetime = request.POST.get('collection_datetime')
# if collection_datetime:
# collection_datetime = timezone.datetime.fromisoformat(collection_datetime)
#
# order.scheduled_collection = collection_datetime
# order.status = 'SCHEDULED'
# order.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='COLLECTION_SCHEDULED',
# model='LabOrder',
# object_id=str(order.order_id),
# details={
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
# 'scheduled_time': collection_datetime.isoformat()
# }
# )
#
# messages.success(request, 'Collection scheduled successfully.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'laboratory/partials/order_status.html', {'order': order})
#
# return redirect('laboratory:lab_order_detail', pk=order.pk)
#
# return JsonResponse({'success': False})
#
#
# @login_required
# def mark_collected(request, order_id):
# """
# Mark specimen as collected.
# """
# if request.method == 'POST':
# order = get_object_or_404(
# LabOrder,
# id=order_id,
# tenant=request.user.tenant
# )
#
# # Create specimen record if it doesn't exist
# specimen, created = Specimen.objects.get_or_create(
# order=order,
# tenant=request.user.tenant,
# defaults={
# 'specimen_type': 'BLOOD', # Default, should be specified
# 'collection_datetime': timezone.now(),
# 'collected_by': request.user,
# 'status': 'COLLECTED'
# }
# )
#
# if not created:
# specimen.status = 'COLLECTED'
# specimen.collection_datetime = timezone.now()
# specimen.collected_by = request.user
# specimen.save()
#
# # Update order status
# order.status = 'COLLECTED'
# order.save()
#
# # Log the action
# AuditLogger.log_action(
# user=request.user,
# action='SPECIMEN_COLLECTED',
# model='LabOrder',
# object_id=str(order.order_id),
# details={
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
# 'specimen_type': specimen.specimen_type
# }
# )
#
# messages.success(request, 'Specimen marked as collected.')
#
# if request.headers.get('HX-Request'):
# return render(request, 'laboratory/partials/order_status.html', {'order': order})
#
# return redirect('laboratory:lab_order_detail', pk=order.pk)
#
# return JsonResponse({'success': False})
#
#
#
@login_required
def verify_result(request, result_id):
"""
Verify a laboratory result.
"""
if request.method == 'POST':
result = get_object_or_404(
LabResult,
id=result_id,
order__tenant=request.user.tenant
)
if result.status != 'PENDING':
return JsonResponse({'success': False, 'error': 'Result is not pending verification'})
result.status = 'VERIFIED'
result.verified_by = request.user
result.verified_datetime = timezone.now()
result.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='LAB_RESULT_VERIFIED',
model='LabResult',
object_id=str(result.result_id),
details={
'patient_name': f"{result.order.patient.first_name} {result.order.patient.last_name}",
'test_name': result.test.test_name,
'result_value': result.value
}
)
messages.success(request, 'Result verified successfully.')
if request.headers.get('HX-Request'):
return render(request, 'laboratory/partials/result_row.html', {'result': result})
return redirect('laboratory:lab_result_list')
return JsonResponse({'success': False})
@login_required
def review_result(request, result_id):
"""
Mark a laboratory result as reviewed.
"""
if request.method == 'POST':
result = get_object_or_404(
LabResult,
id=result_id,
order__tenant=request.user.tenant
)
# Add reviewed status or field if it exists in your model
# For now, we'll use a custom field or status
if hasattr(result, 'reviewed_by'):
result.reviewed_by = request.user
result.reviewed_datetime = timezone.now()
result.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='LAB_RESULT_REVIEWED',
model='LabResult',
object_id=str(result.result_id),
details={
'patient_name': f"{result.order.patient.first_name} {result.order.patient.last_name}",
'test_name': result.test.test_name,
'result_value': result.value
}
)
return JsonResponse({'success': True, 'message': 'Result marked as reviewed'})
else:
return JsonResponse({'success': False, 'error': 'Review functionality not available'})
return JsonResponse({'success': False, 'error': 'Invalid request method'})
@login_required
def email_result(request, result_id):
"""
Email a laboratory result to specified address.
"""
if request.method == 'POST':
result = get_object_or_404(
LabResult,
id=result_id,
order__tenant=request.user.tenant
)
email = request.POST.get('email') or request.GET.get('email')
if not email:
return JsonResponse({'success': False, 'error': 'Email address is required'})
# Here you would implement actual email sending
# For now, we'll simulate success
try:
# TODO: Implement actual email sending logic
# from django.core.mail import send_mail
# send_mail(
# f'Laboratory Result: {result.test.test_name}',
# f'Patient: {result.order.patient.get_full_name()}\nResult: {result.value} {result.unit}\nStatus: {result.get_status_display()}',
# 'noreply@hospital.com',
# [email],
# fail_silently=False,
# )
# Log the action
AuditLogger.log_action(
user=request.user,
action='LAB_RESULT_EMAILED',
model='LabResult',
object_id=str(result.result_id),
details={
'patient_name': f"{result.order.patient.first_name} {result.order.patient.last_name}",
'test_name': result.test.test_name,
'email_recipient': email
}
)
return JsonResponse({'success': True, 'message': f'Result emailed to {email}'})
except Exception as e:
return JsonResponse({'success': False, 'error': f'Failed to send email: {str(e)}'})
return JsonResponse({'success': False, 'error': 'Invalid request method'})
@login_required
def notify_critical(request, result_id):
"""
Send critical result notification to ordering provider.
"""
if request.method == 'POST':
result = get_object_or_404(
LabResult,
id=result_id,
order__tenant=request.user.tenant
)
if result.critical_flag != 'CRITICAL':
return JsonResponse({'success': False, 'error': 'Result is not critical'})
# Here you would implement actual notification sending
# For now, we'll simulate success
try:
# TODO: Implement actual notification logic (email, SMS, etc.)
# This could send notifications to:
# - Ordering provider
# - Primary care physician
# - Critical result notification system
# Log the action
AuditLogger.log_action(
user=request.user,
action='CRITICAL_RESULT_NOTIFIED',
model='LabResult',
object_id=str(result.result_id),
details={
'patient_name': f"{result.order.patient.first_name} {result.order.patient.last_name}",
'test_name': result.test.test_name,
'result_value': result.value,
'ordering_provider': result.order.ordering_provider.get_full_name() if result.order.ordering_provider else 'Unknown'
}
)
return JsonResponse({'success': True, 'message': 'Critical result notification sent'})
except Exception as e:
return JsonResponse({'success': False, 'error': f'Failed to send notification: {str(e)}'})
return JsonResponse({'success': False, 'error': 'Invalid request method'})
@login_required
def bulk_verify_results(request):
"""
Bulk verify multiple laboratory results.
"""
if request.method == 'POST':
result_ids = request.POST.getlist('result_ids[]')
tenant = request.user.tenant
results = LabResult.objects.filter(
id__in=result_ids,
order__tenant=tenant,
status='PENDING'
)
verified_count = results.update(
status='VERIFIED',
verified_by=request.user,
verified_datetime=timezone.now()
)
# Log the action
AuditLogger.log_action(
user=request.user,
action='LAB_RESULTS_BULK_VERIFIED',
model='LabResult',
object_id=','.join(result_ids),
details={
'verified_count': verified_count,
'total_requested': len(result_ids)
}
)
messages.success(request, f'{verified_count} results verified successfully.')
if request.headers.get('HX-Request'):
return JsonResponse({'success': True, 'verified_count': verified_count})
return redirect('laboratory:lab_result_list')
return JsonResponse({'success': False})
@login_required
def bulk_release_results(request):
"""
Bulk release multiple laboratory results.
"""
if request.method == 'POST':
result_ids = request.POST.getlist('result_ids[]')
tenant = request.user.tenant
results = LabResult.objects.filter(
id__in=result_ids,
order__tenant=tenant,
status='VERIFIED'
)
released_count = results.update(status='RELEASED')
# Log the action
AuditLogger.log_action(
user=request.user,
action='LAB_RESULTS_BULK_RELEASED',
model='LabResult',
object_id=','.join(result_ids),
details={
'released_count': released_count,
'total_requested': len(result_ids)
}
)
messages.success(request, f'{released_count} results released successfully.')
if request.headers.get('HX-Request'):
return JsonResponse({'success': True, 'released_count': released_count})
return redirect('laboratory:lab_result_list')
return JsonResponse({'success': False})
@login_required
def print_result_reports(request):
"""
Print reports for selected laboratory results.
"""
ids = request.GET.get('ids', '')
if not ids:
return HttpResponse('No results selected', status=400)
result_ids = ids.split(',')
tenant = request.user.tenant
results = LabResult.objects.filter(
id__in=result_ids,
order__tenant=tenant
).select_related('order__patient', 'test', 'verified_by')
# Generate PDF or HTML report
context = {
'results': results,
'generated_at': timezone.now(),
'generated_by': request.user
}
return render(request, 'laboratory/reports/result_reports.html', context)
@login_required
def export_results(request):
"""
Export laboratory results to CSV/Excel.
"""
tenant = request.user.tenant
# Get filter parameters
status = request.GET.get('status')
date_from = request.GET.get('date_from')
date_to = request.GET.get('date_to')
queryset = LabResult.objects.filter(order__tenant=tenant)
if status:
queryset = queryset.filter(status=status)
if date_from:
queryset = queryset.filter(result_date__gte=date_from)
if date_to:
queryset = queryset.filter(result_date__lte=date_to)
results = queryset.select_related('order__patient', 'test', 'verified_by')
# Generate CSV response
import csv
from django.http import HttpResponse
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="lab_results.csv"'
writer = csv.writer(response)
writer.writerow([
'Result ID', 'Patient Name', 'Patient ID', 'Test Name', 'Result Value',
'Unit', 'Reference Range', 'Status', 'Result Date', 'Technologist',
'Verified By', 'Verified Date'
])
for result in results:
writer.writerow([
result.result_id,
result.order.patient.get_full_name(),
result.order.patient.patient_id,
result.test.test_name,
result.value,
result.unit or '',
result.reference_range or '',
result.get_status_display(),
result.result_date.strftime('%Y-%m-%d %H:%M'),
result.technologist.get_full_name() if result.technologist else '',
result.verified_by.get_full_name() if result.verified_by else '',
result.verified_datetime.strftime('%Y-%m-%d %H:%M') if result.verified_datetime else ''
])
return response
@login_required
def htmx_result_stats(request):
"""
HTMX endpoint for result statistics.
"""
tenant = request.user.tenant
stats = {
'total_results': LabResult.objects.filter(order__tenant=tenant).count(),
'pending_review': LabResult.objects.filter(order__tenant=tenant, status='PENDING').count(),
'critical_results': LabResult.objects.filter(order__tenant=tenant, is_critical=True).count(),
'verified_results': LabResult.objects.filter(order__tenant=tenant, status='VERIFIED').count(),
}
return render(request, 'laboratory/partials/result_stats.html', {'stats': stats})
@login_required
def htmx_filter_results(request):
"""
HTMX endpoint for filtering results.
"""
tenant = request.user.tenant
status = request.GET.get('status')
critical = request.GET.get('critical')
date_period = request.GET.get('date')
queryset = LabResult.objects.filter(order__tenant=tenant)
# Apply filters
if status and status != 'all':
queryset = queryset.filter(status=status)
if critical and critical != 'all':
queryset = queryset.filter(critical_flag=critical)
# Date filtering
if date_period and date_period != 'all':
today = timezone.now().date()
if date_period == 'today':
queryset = queryset.filter(result_date__date=today)
elif date_period == 'week':
week_ago = today - timedelta(days=7)
queryset = queryset.filter(result_date__date__gte=week_ago)
elif date_period == 'month':
month_ago = today - timedelta(days=30)
queryset = queryset.filter(result_date__date__gte=month_ago)
results = queryset.select_related(
'order__patient', 'test', 'verified_by'
).order_by('-created_at')[:50] # Limit for performance
return render(request, 'laboratory/partials/result_list.html', {'object_list': results})
@login_required
def htmx_search_results(request):
"""
HTMX endpoint for searching results.
"""
tenant = request.user.tenant
search_term = request.GET.get('search', '').strip()
if not search_term:
return render(request, 'laboratory/partials/result_list.html', {'object_list': []})
queryset = LabResult.objects.filter(order__tenant=tenant).filter(
Q(order__patient__first_name__icontains=search_term) |
Q(order__patient__last_name__icontains=search_term) |
Q(order__patient__mrn__icontains=search_term) |
Q(test__test_name__icontains=search_term) |
Q(result_value__icontains=search_term)
).select_related(
'order__patient', 'test', 'verified_by'
).order_by('-analyzed_datetime')[:50]
return render(request, 'laboratory/partials/result_list.html', {'object_list': queryset})
@login_required
def check_critical_results(request):
"""
API endpoint to check for critical results that need attention.
Referenced in dashboard template.
"""
tenant = request.user.tenant
# Get critical results that haven't been called yet
critical_results = LabResult.objects.filter(
order__tenant=tenant,
is_critical=True,
critical_called=False,
status='VERIFIED'
).count()
return JsonResponse({
'critical_count': critical_results,
'success': True
})