2281 lines
79 KiB
Python
2281 lines
79 KiB
Python
"""
|
|
Radiology app views with healthcare-focused CRUD operations.
|
|
Implements appropriate access patterns for radiology and imaging 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 (
|
|
ImagingOrder, ImagingStudy, ImagingSeries, DICOMImage,
|
|
RadiologyReport, ReportTemplate
|
|
)
|
|
from .forms import (
|
|
ImagingOrderForm, ImagingStudyForm, RadiologyReportForm,
|
|
ReportTemplateForm, ImagingSeriesForm
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# DASHBOARD AND OVERVIEW VIEWS
|
|
# ============================================================================
|
|
|
|
class RadiologyDashboardView(LoginRequiredMixin, TemplateView):
|
|
"""
|
|
Main radiology dashboard with key metrics and recent activity.
|
|
"""
|
|
template_name = 'radiology/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': ImagingOrder.objects.filter(
|
|
tenant=tenant,
|
|
status='PENDING'
|
|
).count(),
|
|
'scheduled_studies': ImagingStudy.objects.filter(
|
|
tenant=tenant,
|
|
status='SCHEDULED'
|
|
).count(),
|
|
'in_progress_studies': ImagingStudy.objects.filter(
|
|
tenant=tenant,
|
|
status='IN_PROGRESS'
|
|
).count(),
|
|
# 'studies_completed_today': ImagingStudy.objects.filter(
|
|
# tenant=tenant,
|
|
# completed_datetime__date=today,
|
|
# status='COMPLETED'
|
|
# ).count(),
|
|
'reports_pending': RadiologyReport.objects.filter(
|
|
study__tenant=tenant,
|
|
status='DRAFT'
|
|
).count(),
|
|
'reports_signed_today': RadiologyReport.objects.filter(
|
|
study__tenant=tenant,
|
|
# signed_datetime__date=today,
|
|
status='SIGNED'
|
|
).count(),
|
|
'critical_findings': RadiologyReport.objects.filter(
|
|
study__tenant=tenant,
|
|
# has_critical_findings=True,
|
|
status='SIGNED',
|
|
# signed_datetime__date=today
|
|
).count(),
|
|
'total_images_today': DICOMImage.objects.filter(
|
|
series__study__tenant=tenant,
|
|
created_at__date=today
|
|
).count(),
|
|
})
|
|
|
|
# Recent orders
|
|
# context['recent_orders'] = ImagingOrder.objects.filter(
|
|
# tenant=tenant
|
|
# ).select_related('patient', 'ordering_provider').order_by('-order_datetime')[:10]
|
|
#
|
|
# # Recent studies
|
|
# context['recent_studies'] = ImagingStudy.objects.filter(
|
|
# tenant=tenant
|
|
# ).select_related('patient').order_by('-study_datetime')[:10]
|
|
#
|
|
# # Recent reports
|
|
# context['recent_reports'] = RadiologyReport.objects.filter(
|
|
# study__tenant=tenant,
|
|
# status='SIGNED'
|
|
# ).select_related('study__order__patient')[:10]
|
|
|
|
return context
|
|
|
|
|
|
# ============================================================================
|
|
# REPORT TEMPLATE VIEWS (FULL CRUD - Master Data)
|
|
# ============================================================================
|
|
|
|
class ReportTemplateListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all radiology report templates with filtering and search.
|
|
"""
|
|
model = ReportTemplate
|
|
template_name = 'radiology/report_template_list.html'
|
|
context_object_name = 'report_templates'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = ReportTemplate.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Search functionality
|
|
search = self.request.GET.get('search')
|
|
if search:
|
|
queryset = queryset.filter(
|
|
Q(template_name__icontains=search) |
|
|
Q(modality__icontains=search) |
|
|
Q(body_part__icontains=search)
|
|
)
|
|
|
|
# Filter by modality
|
|
modality = self.request.GET.get('modality')
|
|
if modality:
|
|
queryset = queryset.filter(modality=modality)
|
|
|
|
# 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('template_name')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'modalities': ReportTemplate._meta.get_field('modality').choices,
|
|
'search_query': self.request.GET.get('search', ''),
|
|
})
|
|
return context
|
|
|
|
|
|
class ReportTemplateDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a report template.
|
|
"""
|
|
model = ReportTemplate
|
|
template_name = 'radiology/templates/report_template_detail.html'
|
|
context_object_name = 'report_template'
|
|
|
|
def get_queryset(self):
|
|
return ReportTemplate.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
template = self.object
|
|
|
|
# Get recent reports using this template
|
|
context['recent_reports'] = RadiologyReport.objects.filter(
|
|
template=template,
|
|
tenant=self.request.user.tenant
|
|
).select_related('study__order__patient').order_by('-created_at')[:10]
|
|
|
|
return context
|
|
|
|
|
|
class ReportTemplateCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new report template.
|
|
"""
|
|
model = ReportTemplate
|
|
form_class = ReportTemplateForm
|
|
template_name = 'radiology/templates/report_template_form.html'
|
|
permission_required = 'radiology.add_reporttemplate'
|
|
success_url = reverse_lazy('radiology:report_template_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.created_by = self.request.user
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='REPORT_TEMPLATE_CREATED',
|
|
model='ReportTemplate',
|
|
object_id=str(self.object.id),
|
|
details={
|
|
'template_name': self.object.template_name,
|
|
'modality': self.object.modality
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, f'Report template "{self.object.template_name}" created successfully.')
|
|
return response
|
|
|
|
|
|
class ReportTemplateUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
"""
|
|
Update an existing report template.
|
|
"""
|
|
model = ReportTemplate
|
|
form_class = ReportTemplateForm
|
|
template_name = 'radiology/templates/report_template_form.html'
|
|
permission_required = 'radiology.change_reporttemplate'
|
|
|
|
def get_queryset(self):
|
|
return ReportTemplate.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse('radiology:report_template_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='REPORT_TEMPLATE_UPDATED',
|
|
model='ReportTemplate',
|
|
object_id=str(self.object.id),
|
|
details={
|
|
'template_name': self.object.template_name,
|
|
'changes': form.changed_data
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, f'Report template "{self.object.template_name}" updated successfully.')
|
|
return response
|
|
|
|
|
|
class ReportTemplateDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
|
"""
|
|
Delete a report template (soft delete by deactivating).
|
|
"""
|
|
model = ReportTemplate
|
|
template_name = 'radiology/templates/report_template_confirm_delete.html'
|
|
permission_required = 'radiology.delete_reporttemplate'
|
|
success_url = reverse_lazy('radiology:report_template_list')
|
|
|
|
def get_queryset(self):
|
|
return ReportTemplate.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='REPORT_TEMPLATE_DEACTIVATED',
|
|
model='ReportTemplate',
|
|
object_id=str(self.object.id),
|
|
details={'template_name': self.object.name}
|
|
)
|
|
|
|
messages.success(request, f'Report template "{self.object.name}" deactivated successfully.')
|
|
return redirect(self.success_url)
|
|
|
|
|
|
# ============================================================================
|
|
# IMAGING ORDER VIEWS (RESTRICTED CRUD - Clinical Orders)
|
|
# ============================================================================
|
|
|
|
class ImagingOrderListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all imaging orders with filtering and search.
|
|
"""
|
|
model = ImagingOrder
|
|
template_name = 'radiology/orders/imaging_order_list.html'
|
|
context_object_name = 'imaging_orders'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = ImagingOrder.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(study_description__icontains=search)
|
|
)
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by modality
|
|
modality = self.request.GET.get('modality')
|
|
if modality:
|
|
queryset = queryset.filter(modality=modality)
|
|
|
|
# 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': ImagingOrder._meta.get_field('status').choices,
|
|
'modalities': ImagingOrder._meta.get_field('modality').choices,
|
|
'priorities': ImagingOrder._meta.get_field('priority').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class ImagingOrderDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about an imaging order.
|
|
"""
|
|
model = ImagingOrder
|
|
template_name = 'radiology/orders/imaging_order_detail.html'
|
|
context_object_name = 'imaging_order'
|
|
|
|
def get_queryset(self):
|
|
return ImagingOrder.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
imaging_order = self.object
|
|
|
|
# Get studies for this order with prefetched reports
|
|
studies = imaging_order.studies.all().order_by('-study_datetime')
|
|
context['studies'] = studies
|
|
|
|
# Create a dictionary mapping each study to its reports
|
|
# reports = {}
|
|
# for study in studies:
|
|
study_reports = RadiologyReport.objects.filter(
|
|
study=studies.first,
|
|
# tenant=self.request.user.tenant
|
|
).order_by('-created_at')
|
|
# reports[study.id] = study_reports
|
|
|
|
context['reports'] = study_reports
|
|
|
|
# Get all reports for this order (for backward compatibility)
|
|
# context['reports'] = RadiologyReport.objects.filter(
|
|
# study__order=imaging_order,
|
|
# tenant=self.request.user.tenant
|
|
# ).order_by('-created_at')
|
|
|
|
return context
|
|
|
|
|
|
class ImagingOrderCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new imaging order.
|
|
"""
|
|
model = ImagingOrder
|
|
form_class = ImagingOrderForm
|
|
template_name = 'radiology/orders/imaging_order_form.html'
|
|
permission_required = 'radiology.add_imagingorder'
|
|
success_url = reverse_lazy('radiology:imaging_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='IMAGING_ORDER_CREATED',
|
|
model='ImagingOrder',
|
|
object_id=str(self.object.order_id),
|
|
details={
|
|
'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
|
|
'modality': self.object.modality,
|
|
'study_description': self.object.study_description,
|
|
'priority': self.object.priority
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Imaging order created successfully.')
|
|
return response
|
|
|
|
|
|
class ImagingOrderUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
"""
|
|
Update imaging order (limited to status and notes only).
|
|
"""
|
|
model = ImagingOrder
|
|
fields = ['status', 'notes'] # Restricted fields for clinical orders
|
|
template_name = 'radiology/orders/imaging_order_form.html'
|
|
permission_required = 'radiology.change_imagingorder'
|
|
|
|
def get_queryset(self):
|
|
return ImagingOrder.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse('radiology:imaging_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='IMAGING_ORDER_UPDATED',
|
|
model='ImagingOrder',
|
|
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, 'Imaging order updated successfully.')
|
|
return response
|
|
|
|
|
|
# ============================================================================
|
|
# IMAGING STUDY VIEWS (RESTRICTED CRUD - Clinical Data)
|
|
# ============================================================================
|
|
|
|
class ImagingStudyListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all imaging studies with filtering and search.
|
|
"""
|
|
model = ImagingStudy
|
|
template_name = 'radiology/studies/imaging_study_list.html'
|
|
context_object_name = 'imaging_studies'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = ImagingStudy.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Search functionality
|
|
search = self.request.GET.get('search')
|
|
if search:
|
|
queryset = queryset.filter(
|
|
Q(study_id__icontains=search) |
|
|
Q(imaging_order__patient__first_name__icontains=search) |
|
|
Q(imaging_order__patient__last_name__icontains=search) |
|
|
Q(imaging_order__patient__mrn__icontains=search)
|
|
)
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by modality
|
|
modality = self.request.GET.get('modality')
|
|
if modality:
|
|
queryset = queryset.filter(imaging_order__modality=modality)
|
|
|
|
return queryset.select_related('imaging_order__patient').order_by('-study_datetime')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'statuses': ImagingStudy._meta.get_field('status').choices,
|
|
'modalities': ImagingOrder._meta.get_field('modality').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class ImagingStudyDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about an imaging study.
|
|
"""
|
|
model = ImagingStudy
|
|
template_name = 'radiology/studies/imaging_study_detail.html'
|
|
context_object_name = 'study'
|
|
|
|
def get_queryset(self):
|
|
return ImagingStudy.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
imaging_study = self.object
|
|
|
|
# Get series for this study
|
|
context['series'] = imaging_study.series
|
|
|
|
# Get reports for this study
|
|
context['reports'] = imaging_study.report
|
|
|
|
# Get total image count
|
|
context['total_images'] = DICOMImage.objects.filter(
|
|
series__study=imaging_study,
|
|
series__study__tenant=self.request.user.tenant
|
|
).count()
|
|
|
|
return context
|
|
|
|
|
|
class ImagingStudyCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new imaging study.
|
|
"""
|
|
model = ImagingStudy
|
|
form_class = ImagingStudyForm
|
|
template_name = 'radiology/studies/imaging_study_form.html'
|
|
permission_required = 'radiology.add_imagingstudy'
|
|
success_url = reverse_lazy('radiology:imaging_study_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.technologist = self.request.user
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='IMAGING_STUDY_CREATED',
|
|
model='ImagingStudy',
|
|
object_id=str(self.object.study_id),
|
|
details={
|
|
'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
|
|
'modality': self.object.order.modality
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Imaging study created successfully.')
|
|
return response
|
|
|
|
|
|
class ImagingStudyUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
"""
|
|
Update imaging study (limited to status and technical notes).
|
|
"""
|
|
model = ImagingStudy
|
|
fields = ['status', 'technical_notes'] # Restricted fields
|
|
template_name = 'radiology/studies/imaging_study_form.html'
|
|
permission_required = 'radiology.change_imagingstudy'
|
|
|
|
def get_queryset(self):
|
|
return ImagingStudy.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
def get_success_url(self):
|
|
return reverse('radiology:imaging_study_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='IMAGING_STUDY_UPDATED',
|
|
model='ImagingStudy',
|
|
object_id=str(self.object.study_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, 'Imaging study updated successfully.')
|
|
return response
|
|
|
|
|
|
# ============================================================================
|
|
# IMAGING SERIES VIEWS (RESTRICTED CRUD - Clinical Data)
|
|
# ============================================================================
|
|
|
|
class ImagingSeriesListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all imaging series with filtering and search.
|
|
"""
|
|
model = ImagingSeries
|
|
template_name = 'radiology/series/imaging_series_list.html'
|
|
context_object_name = 'imaging_series'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = ImagingSeries.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Filter by study
|
|
study_id = self.request.GET.get('study')
|
|
if study_id:
|
|
queryset = queryset.filter(study_id=study_id)
|
|
|
|
# Search functionality
|
|
search = self.request.GET.get('search')
|
|
if search:
|
|
queryset = queryset.filter(
|
|
Q(series_description__icontains=search) |
|
|
Q(study__imaging_order__patient__first_name__icontains=search) |
|
|
Q(study__imaging_order__patient__last_name__icontains=search)
|
|
)
|
|
|
|
return queryset.select_related('study__imaging_order__patient').order_by('-created_at')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'studies': ImagingStudy.objects.filter(
|
|
tenant=self.request.user.tenant
|
|
).select_related('imaging_order__patient').order_by('-study_datetime')[:50],
|
|
})
|
|
return context
|
|
|
|
|
|
class ImagingSeriesDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about an imaging series.
|
|
"""
|
|
model = ImagingSeries
|
|
template_name = 'radiology/series/imaging_series_detail.html'
|
|
context_object_name = 'imaging_series'
|
|
|
|
def get_queryset(self):
|
|
return ImagingSeries.objects.filter(study__tenant=self.request.user.tenant)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
imaging_series = self.object
|
|
|
|
# Get images for this series
|
|
context['images'] = imaging_series.images.all().order_by('instance_number')
|
|
|
|
return context
|
|
|
|
|
|
class ImagingSeriesCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new imaging series.
|
|
"""
|
|
model = ImagingSeries
|
|
form_class = ImagingSeriesForm
|
|
template_name = 'radiology/series/imaging_series_form.html'
|
|
permission_required = 'radiology.add_imagingseries'
|
|
success_url = reverse_lazy('radiology:imaging_series_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='IMAGING_SERIES_CREATED',
|
|
model='ImagingSeries',
|
|
object_id=str(self.object.series_id),
|
|
details={
|
|
'series_description': self.object.series_description,
|
|
'study_id': str(self.object.study.study_id)
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Imaging series created successfully.')
|
|
return response
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
# DICOM IMAGE VIEWS (READ-ONLY - System Generated)
|
|
# ============================================================================
|
|
|
|
class DICOMImageListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all DICOM images with filtering and search.
|
|
"""
|
|
model = DICOMImage
|
|
template_name = 'radiology/dicom_image_list.html'
|
|
context_object_name = 'dicom_images'
|
|
paginate_by = 50
|
|
|
|
def get_queryset(self):
|
|
queryset = DICOMImage.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Filter by series
|
|
series_id = self.request.GET.get('series')
|
|
if series_id:
|
|
queryset = queryset.filter(series_id=series_id)
|
|
|
|
# Filter by study
|
|
study_id = self.request.GET.get('study')
|
|
if study_id:
|
|
queryset = queryset.filter(series__study_id=study_id)
|
|
|
|
return queryset.select_related('series__study__order__patient').order_by('instance_number')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'series': ImagingSeries.objects.filter(
|
|
tenant=self.request.user.tenant
|
|
).select_related('study__order__patient').order_by('-created_at')[:50],
|
|
})
|
|
return context
|
|
|
|
|
|
class DICOMImageDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a DICOM image.
|
|
"""
|
|
model = DICOMImage
|
|
template_name = 'radiology/dicom_image_detail.html'
|
|
context_object_name = 'dicom_image'
|
|
|
|
def get_queryset(self):
|
|
return DICOMImage.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
|
|
# ============================================================================
|
|
# RADIOLOGY REPORT VIEWS (APPEND-ONLY - Clinical Records)
|
|
# ============================================================================
|
|
|
|
class RadiologyReportListView(LoginRequiredMixin, ListView):
|
|
"""
|
|
List all radiology reports with filtering and search.
|
|
"""
|
|
model = RadiologyReport
|
|
template_name = 'radiology/reports/radiology_report_list.html'
|
|
context_object_name = 'radiology_reports'
|
|
paginate_by = 25
|
|
|
|
def get_queryset(self):
|
|
queryset = RadiologyReport.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
# Search functionality
|
|
search = self.request.GET.get('search')
|
|
if search:
|
|
queryset = queryset.filter(
|
|
Q(study__order__patient__first_name__icontains=search) |
|
|
Q(study__order__patient__last_name__icontains=search) |
|
|
Q(study__order__patient__mrn__icontains=search) |
|
|
Q(findings__icontains=search) |
|
|
Q(impression__icontains=search)
|
|
)
|
|
|
|
# Filter by status
|
|
status = self.request.GET.get('status')
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
# Filter by critical findings
|
|
critical_only = self.request.GET.get('critical_only')
|
|
if critical_only:
|
|
queryset = queryset.filter(has_critical_findings=True)
|
|
|
|
# Filter by radiologist
|
|
radiologist_id = self.request.GET.get('radiologist')
|
|
if radiologist_id:
|
|
queryset = queryset.filter(radiologist_id=radiologist_id)
|
|
|
|
return queryset.select_related(
|
|
'study__order__patient', 'radiologist', 'template'
|
|
).order_by('-created_at')
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update({
|
|
'statuses': RadiologyReport._meta.get_field('status').choices,
|
|
})
|
|
return context
|
|
|
|
|
|
class RadiologyReportDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Display detailed information about a radiology report.
|
|
"""
|
|
model = RadiologyReport
|
|
template_name = 'radiology/reports/radiology_report_detail.html'
|
|
context_object_name = 'radiology_report'
|
|
|
|
def get_queryset(self):
|
|
return RadiologyReport.objects.filter(tenant=self.request.user.tenant)
|
|
|
|
|
|
class RadiologyReportCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
"""
|
|
Create a new radiology report.
|
|
"""
|
|
model = RadiologyReport
|
|
form_class = RadiologyReportForm
|
|
template_name = 'radiology/reports/radiology_report_form.html'
|
|
permission_required = 'radiology.add_radiologyreport'
|
|
success_url = reverse_lazy('radiology:radiology_report_list')
|
|
|
|
def form_valid(self, form):
|
|
form.instance.tenant = self.request.user.tenant
|
|
form.instance.radiologist = self.request.user
|
|
response = super().form_valid(form)
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=self.request.user,
|
|
action='RADIOLOGY_REPORT_CREATED',
|
|
model='RadiologyReport',
|
|
object_id=str(self.object.report_id),
|
|
details={
|
|
'patient_name': f"{self.object.study.order.patient.first_name} {self.object.study.order.patient.last_name}",
|
|
'has_critical_findings': self.object.has_critical_findings
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, 'Radiology report created successfully.')
|
|
return response
|
|
|
|
|
|
# Note: No UpdateView or DeleteView for RadiologyReport - Append-only for clinical records
|
|
# Reports can only be amended through addendum process after signing
|
|
|
|
|
|
# ============================================================================
|
|
# HTMX VIEWS FOR REAL-TIME UPDATES
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def radiology_stats(request):
|
|
"""
|
|
HTMX endpoint for radiology statistics.
|
|
"""
|
|
tenant = request.user.tenant
|
|
today = timezone.now().date()
|
|
|
|
stats = {
|
|
'pending_orders': ImagingOrder.objects.filter(
|
|
tenant=tenant,
|
|
status='PENDING'
|
|
).count(),
|
|
'scheduled_studies': ImagingStudy.objects.filter(
|
|
tenant=tenant,
|
|
status='SCHEDULED'
|
|
).count(),
|
|
'in_progress_studies': ImagingStudy.objects.filter(
|
|
tenant=tenant,
|
|
status='IN_PROGRESS'
|
|
).count(),
|
|
'reports_pending': RadiologyReport.objects.filter(
|
|
study__tenant=tenant,
|
|
status='DRAFT'
|
|
).count(),
|
|
'critical_findings': RadiologyReport.objects.filter(
|
|
study__tenant=tenant,
|
|
has_critical_findings=True,
|
|
status='SIGNED',
|
|
signed_datetime__date=today
|
|
).count(),
|
|
}
|
|
|
|
return render(request, 'radiology/partials/radiology_stats.html', {'stats': stats})
|
|
|
|
|
|
@login_required
|
|
def order_search(request):
|
|
"""
|
|
HTMX endpoint for imaging order search.
|
|
"""
|
|
search = request.GET.get('search', '')
|
|
status = request.GET.get('status', '')
|
|
modality = request.GET.get('modality', '')
|
|
|
|
queryset = ImagingOrder.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(study_description__icontains=search)
|
|
)
|
|
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
if modality:
|
|
queryset = queryset.filter(modality=modality)
|
|
|
|
orders = queryset.select_related(
|
|
'patient', 'ordering_provider'
|
|
).order_by('-order_datetime')[:20]
|
|
|
|
return render(request, 'radiology/partials/order_list.html', {'orders': orders})
|
|
|
|
|
|
# ============================================================================
|
|
# ACTION VIEWS
|
|
# ============================================================================
|
|
|
|
@login_required
|
|
def schedule_study(request, order_id):
|
|
"""
|
|
Schedule an imaging study for an order.
|
|
"""
|
|
if request.method == 'POST':
|
|
order = get_object_or_404(
|
|
ImagingOrder,
|
|
id=order_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
scheduled_datetime = request.POST.get('scheduled_datetime')
|
|
if scheduled_datetime:
|
|
scheduled_datetime = timezone.datetime.fromisoformat(scheduled_datetime)
|
|
|
|
# Create or update study
|
|
study, created = ImagingStudy.objects.get_or_create(
|
|
order=order,
|
|
tenant=request.user.tenant,
|
|
defaults={
|
|
'study_datetime': scheduled_datetime,
|
|
'status': 'SCHEDULED',
|
|
'technologist': request.user
|
|
}
|
|
)
|
|
|
|
if not created:
|
|
study.study_datetime = scheduled_datetime
|
|
study.status = 'SCHEDULED'
|
|
study.save()
|
|
|
|
# Update order status
|
|
order.status = 'SCHEDULED'
|
|
order.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='STUDY_SCHEDULED',
|
|
model='ImagingOrder',
|
|
object_id=str(order.order_id),
|
|
details={
|
|
'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
'scheduled_time': scheduled_datetime.isoformat()
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Study scheduled successfully.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'radiology/partials/order_status.html', {'order': order})
|
|
|
|
return redirect('radiology:imaging_order_detail', pk=order.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def start_study(request, study_id):
|
|
"""
|
|
Start an imaging study.
|
|
"""
|
|
if request.method == 'POST':
|
|
study = get_object_or_404(
|
|
ImagingStudy,
|
|
id=study_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
study.status = 'IN_PROGRESS'
|
|
study.started_datetime = timezone.now()
|
|
study.technologist = request.user
|
|
study.save()
|
|
|
|
# Update order status
|
|
study.order.status = 'IN_PROGRESS'
|
|
study.order.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='STUDY_STARTED',
|
|
model='ImagingStudy',
|
|
object_id=str(study.study_id),
|
|
details={
|
|
'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
|
|
'modality': study.order.modality
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Study started successfully.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'radiology/partials/study_status.html', {'study': study})
|
|
|
|
return redirect('radiology:imaging_study_detail', pk=study.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def complete_study(request, study_id):
|
|
"""
|
|
Complete an imaging study.
|
|
"""
|
|
if request.method == 'POST':
|
|
study = get_object_or_404(
|
|
ImagingStudy,
|
|
id=study_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
study.status = 'COMPLETED'
|
|
study.completed_datetime = timezone.now()
|
|
study.save()
|
|
|
|
# Update order status
|
|
study.order.status = 'COMPLETED'
|
|
study.order.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='STUDY_COMPLETED',
|
|
model='ImagingStudy',
|
|
object_id=str(study.study_id),
|
|
details={
|
|
'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
|
|
'modality': study.order.modality
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Study completed successfully.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'radiology/partials/study_status.html', {'study': study})
|
|
|
|
return redirect('radiology:imaging_study_detail', pk=study.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def sign_report(request, report_id):
|
|
"""
|
|
Sign a radiology report.
|
|
"""
|
|
if request.method == 'POST':
|
|
report = get_object_or_404(
|
|
RadiologyReport,
|
|
id=report_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Only allow signing if report is in draft status
|
|
if report.status != 'DRAFT':
|
|
messages.error(request, 'Only draft reports can be signed.')
|
|
return redirect('radiology:radiology_report_detail', pk=report.pk)
|
|
|
|
report.status = 'SIGNED'
|
|
report.signed_datetime = timezone.now()
|
|
report.signed_by = request.user
|
|
report.save()
|
|
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='REPORT_SIGNED',
|
|
model='RadiologyReport',
|
|
object_id=str(report.report_id),
|
|
details={
|
|
'patient_name': f"{report.study.order.patient.first_name} {report.study.order.patient.last_name}",
|
|
'has_critical_findings': report.has_critical_findings
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Report signed successfully.')
|
|
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'radiology/partials/report_status.html', {'report': report})
|
|
|
|
return redirect('radiology:radiology_report_detail', pk=report.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
|
|
@login_required
|
|
def dictate_report(request, study_id):
|
|
"""
|
|
Start dictating a report for a study.
|
|
"""
|
|
if request.method == 'POST':
|
|
study = get_object_or_404(
|
|
ImagingStudy,
|
|
id=study_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
template_id = request.POST.get('template_id')
|
|
template = None
|
|
if template_id:
|
|
template = get_object_or_404(
|
|
ReportTemplate,
|
|
id=template_id,
|
|
tenant=request.user.tenant
|
|
)
|
|
|
|
# Create report if it doesn't exist
|
|
report, created = RadiologyReport.objects.get_or_create(
|
|
study=study,
|
|
tenant=request.user.tenant,
|
|
defaults={
|
|
'radiologist': request.user,
|
|
'template': template,
|
|
'status': 'DRAFT'
|
|
}
|
|
)
|
|
|
|
if created:
|
|
# Log the action
|
|
AuditLogger.log_action(
|
|
user=request.user,
|
|
action='REPORT_DICTATION_STARTED',
|
|
model='RadiologyReport',
|
|
object_id=str(report.report_id),
|
|
details={
|
|
'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
|
|
'template_used': template.template_name if template else None
|
|
}
|
|
)
|
|
|
|
messages.success(request, 'Report dictation started.')
|
|
|
|
return redirect('radiology:radiology_report_detail', pk=report.pk)
|
|
|
|
return JsonResponse({'success': False})
|
|
|
|
#
|
|
# """
|
|
# Radiology app views with healthcare-focused CRUD operations.
|
|
# Implements appropriate access patterns for radiology and imaging 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 (
|
|
# ImagingOrder, ImagingStudy, ImagingSeries, DICOMImage,
|
|
# RadiologyReport, ReportTemplate
|
|
# )
|
|
# from .forms import (
|
|
# ImagingOrderForm, ImagingStudyForm, RadiologyReportForm,
|
|
# ReportTemplateForm, ImagingSeriesForm
|
|
# )
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # DASHBOARD AND OVERVIEW VIEWS
|
|
# # ============================================================================
|
|
#
|
|
# class RadiologyDashboardView(LoginRequiredMixin, TemplateView):
|
|
# """
|
|
# Main radiology dashboard with key metrics and recent activity.
|
|
# """
|
|
# template_name = 'radiology/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': ImagingOrder.objects.filter(
|
|
# tenant=tenant,
|
|
# status='PENDING'
|
|
# ).count(),
|
|
# 'scheduled_studies': ImagingStudy.objects.filter(
|
|
# tenant=tenant,
|
|
# status='SCHEDULED'
|
|
# ).count(),
|
|
# 'in_progress_studies': ImagingStudy.objects.filter(
|
|
# tenant=tenant,
|
|
# status='IN_PROGRESS'
|
|
# ).count(),
|
|
# 'studies_completed_today': ImagingStudy.objects.filter(
|
|
# tenant=tenant,
|
|
# completed_datetime__date=today,
|
|
# status='COMPLETED'
|
|
# ).count(),
|
|
# 'reports_pending': RadiologyReport.objects.filter(
|
|
# tenant=tenant,
|
|
# status='DRAFT'
|
|
# ).count(),
|
|
# 'reports_signed_today': RadiologyReport.objects.filter(
|
|
# tenant=tenant,
|
|
# signed_datetime__date=today,
|
|
# status='SIGNED'
|
|
# ).count(),
|
|
# 'critical_findings': RadiologyReport.objects.filter(
|
|
# tenant=tenant,
|
|
# has_critical_findings=True,
|
|
# status='SIGNED',
|
|
# signed_datetime__date=today
|
|
# ).count(),
|
|
# 'total_images_today': DICOMImage.objects.filter(
|
|
# tenant=tenant,
|
|
# created_at__date=today
|
|
# ).count(),
|
|
# })
|
|
#
|
|
# # Recent orders
|
|
# context['recent_orders'] = ImagingOrder.objects.filter(
|
|
# tenant=tenant
|
|
# ).select_related('patient', 'ordering_provider').order_by('-order_datetime')[:10]
|
|
#
|
|
# # Recent studies
|
|
# context['recent_studies'] = ImagingStudy.objects.filter(
|
|
# tenant=tenant
|
|
# ).select_related('order__patient').order_by('-study_datetime')[:10]
|
|
#
|
|
# # Recent reports
|
|
# context['recent_reports'] = RadiologyReport.objects.filter(
|
|
# tenant=tenant,
|
|
# status='SIGNED'
|
|
# ).select_related('study__order__patient').order_by('-signed_datetime')[:10]
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # REPORT TEMPLATE VIEWS (FULL CRUD - Master Data)
|
|
# # ============================================================================
|
|
#
|
|
# class ReportTemplateListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all radiology report templates with filtering and search.
|
|
# """
|
|
# model = ReportTemplate
|
|
# template_name = 'radiology/report_template_list.html'
|
|
# context_object_name = 'report_templates'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = ReportTemplate.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Search functionality
|
|
# search = self.request.GET.get('search')
|
|
# if search:
|
|
# queryset = queryset.filter(
|
|
# Q(template_name__icontains=search) |
|
|
# Q(modality__icontains=search) |
|
|
# Q(body_part__icontains=search)
|
|
# )
|
|
#
|
|
# # Filter by modality
|
|
# modality = self.request.GET.get('modality')
|
|
# if modality:
|
|
# queryset = queryset.filter(modality=modality)
|
|
#
|
|
# # 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('template_name')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'modalities': ReportTemplate._meta.get_field('modality').choices,
|
|
# 'search_query': self.request.GET.get('search', ''),
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class ReportTemplateDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a report template.
|
|
# """
|
|
# model = ReportTemplate
|
|
# template_name = 'radiology/report_template_detail.html'
|
|
# context_object_name = 'report_template'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ReportTemplate.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# template = self.object
|
|
#
|
|
# # Get recent reports using this template
|
|
# context['recent_reports'] = RadiologyReport.objects.filter(
|
|
# template=template,
|
|
# tenant=self.request.user.tenant
|
|
# ).select_related('study__order__patient').order_by('-created_at')[:10]
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class ReportTemplateCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new report template.
|
|
# """
|
|
# model = ReportTemplate
|
|
# form_class = ReportTemplateForm
|
|
# template_name = 'radiology/report_template_form.html'
|
|
# permission_required = 'radiology.add_reporttemplate'
|
|
# success_url = reverse_lazy('radiology:report_template_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# form.instance.created_by = self.request.user
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='REPORT_TEMPLATE_CREATED',
|
|
# model='ReportTemplate',
|
|
# object_id=str(self.object.id),
|
|
# details={
|
|
# 'template_name': self.object.template_name,
|
|
# 'modality': self.object.modality
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, f'Report template "{self.object.template_name}" created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class ReportTemplateUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update an existing report template.
|
|
# """
|
|
# model = ReportTemplate
|
|
# form_class = ReportTemplateForm
|
|
# template_name = 'radiology/report_template_form.html'
|
|
# permission_required = 'radiology.change_reporttemplate'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ReportTemplate.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('radiology:report_template_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='REPORT_TEMPLATE_UPDATED',
|
|
# model='ReportTemplate',
|
|
# object_id=str(self.object.id),
|
|
# details={
|
|
# 'template_name': self.object.template_name,
|
|
# 'changes': form.changed_data
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, f'Report template "{self.object.template_name}" updated successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class ReportTemplateDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
|
|
# """
|
|
# Delete a report template (soft delete by deactivating).
|
|
# """
|
|
# model = ReportTemplate
|
|
# template_name = 'radiology/report_template_confirm_delete.html'
|
|
# permission_required = 'radiology.delete_reporttemplate'
|
|
# success_url = reverse_lazy('radiology:report_template_list')
|
|
#
|
|
# def get_queryset(self):
|
|
# return ReportTemplate.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='REPORT_TEMPLATE_DEACTIVATED',
|
|
# model='ReportTemplate',
|
|
# object_id=str(self.object.id),
|
|
# details={'template_name': self.object.template_name}
|
|
# )
|
|
#
|
|
# messages.success(request, f'Report template "{self.object.template_name}" deactivated successfully.')
|
|
# return redirect(self.success_url)
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # IMAGING ORDER VIEWS (RESTRICTED CRUD - Clinical Orders)
|
|
# # ============================================================================
|
|
#
|
|
# class ImagingOrderListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all imaging orders with filtering and search.
|
|
# """
|
|
# model = ImagingOrder
|
|
# template_name = 'radiology/imaging_order_list.html'
|
|
# context_object_name = 'imaging_orders'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = ImagingOrder.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(study_description__icontains=search)
|
|
# )
|
|
#
|
|
# # Filter by status
|
|
# status = self.request.GET.get('status')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# # Filter by modality
|
|
# modality = self.request.GET.get('modality')
|
|
# if modality:
|
|
# queryset = queryset.filter(modality=modality)
|
|
#
|
|
# # 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': ImagingOrder._meta.get_field('status').choices,
|
|
# 'modalities': ImagingOrder._meta.get_field('modality').choices,
|
|
# 'priorities': ImagingOrder._meta.get_field('priority').choices,
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class ImagingOrderDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about an imaging order.
|
|
# """
|
|
# model = ImagingOrder
|
|
# template_name = 'radiology/imaging_order_detail.html'
|
|
# context_object_name = 'imaging_order'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ImagingOrder.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# imaging_order = self.object
|
|
#
|
|
# # Get studies for this order
|
|
# context['studies'] = imaging_order.studies.all().order_by('-study_datetime')
|
|
#
|
|
# # Get reports for this order
|
|
# context['reports'] = RadiologyReport.objects.filter(
|
|
# study__order=imaging_order,
|
|
# tenant=self.request.user.tenant
|
|
# ).order_by('-created_at')
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class ImagingOrderCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new imaging order.
|
|
# """
|
|
# model = ImagingOrder
|
|
# form_class = ImagingOrderForm
|
|
# template_name = 'radiology/imaging_order_form.html'
|
|
# permission_required = 'radiology.add_imagingorder'
|
|
# success_url = reverse_lazy('radiology:imaging_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='IMAGING_ORDER_CREATED',
|
|
# model='ImagingOrder',
|
|
# object_id=str(self.object.order_id),
|
|
# details={
|
|
# 'patient_name': f"{self.object.patient.first_name} {self.object.patient.last_name}",
|
|
# 'modality': self.object.modality,
|
|
# 'study_description': self.object.study_description,
|
|
# 'priority': self.object.priority
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Imaging order created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class ImagingOrderUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update imaging order (limited to status and notes only).
|
|
# """
|
|
# model = ImagingOrder
|
|
# fields = ['status', 'notes'] # Restricted fields for clinical orders
|
|
# template_name = 'radiology/imaging_order_update_form.html'
|
|
# permission_required = 'radiology.change_imagingorder'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ImagingOrder.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('radiology:imaging_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='IMAGING_ORDER_UPDATED',
|
|
# model='ImagingOrder',
|
|
# 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, 'Imaging order updated successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # IMAGING STUDY VIEWS (RESTRICTED CRUD - Clinical Data)
|
|
# # ============================================================================
|
|
#
|
|
# class ImagingStudyListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all imaging studies with filtering and search.
|
|
# """
|
|
# model = ImagingStudy
|
|
# template_name = 'radiology/imaging_study_list.html'
|
|
# context_object_name = 'imaging_studies'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = ImagingStudy.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Search functionality
|
|
# search = self.request.GET.get('search')
|
|
# if search:
|
|
# queryset = queryset.filter(
|
|
# Q(study_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 modality
|
|
# modality = self.request.GET.get('modality')
|
|
# if modality:
|
|
# queryset = queryset.filter(order__modality=modality)
|
|
#
|
|
# return queryset.select_related('order__patient').order_by('-study_datetime')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'statuses': ImagingStudy._meta.get_field('status').choices,
|
|
# 'modalities': ImagingOrder._meta.get_field('modality').choices,
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class ImagingStudyDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about an imaging study.
|
|
# """
|
|
# model = ImagingStudy
|
|
# template_name = 'radiology/imaging_study_detail.html'
|
|
# context_object_name = 'imaging_study'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ImagingStudy.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# imaging_study = self.object
|
|
#
|
|
# # Get series for this study
|
|
# context['series'] = imaging_study.series.all().order_by('series_number')
|
|
#
|
|
# # Get reports for this study
|
|
# context['reports'] = imaging_study.reports.all().order_by('-created_at')
|
|
#
|
|
# # Get total image count
|
|
# context['total_images'] = DICOMImage.objects.filter(
|
|
# series__study=imaging_study,
|
|
# tenant=self.request.user.tenant
|
|
# ).count()
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class ImagingStudyCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new imaging study.
|
|
# """
|
|
# model = ImagingStudy
|
|
# form_class = ImagingStudyForm
|
|
# template_name = 'radiology/imaging_study_form.html'
|
|
# permission_required = 'radiology.add_imagingstudy'
|
|
# success_url = reverse_lazy('radiology:imaging_study_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# form.instance.technologist = self.request.user
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='IMAGING_STUDY_CREATED',
|
|
# model='ImagingStudy',
|
|
# object_id=str(self.object.study_id),
|
|
# details={
|
|
# 'patient_name': f"{self.object.order.patient.first_name} {self.object.order.patient.last_name}",
|
|
# 'modality': self.object.order.modality
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Imaging study created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# class ImagingStudyUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
|
# """
|
|
# Update imaging study (limited to status and technical notes).
|
|
# """
|
|
# model = ImagingStudy
|
|
# fields = ['status', 'technical_notes'] # Restricted fields
|
|
# template_name = 'radiology/imaging_study_update_form.html'
|
|
# permission_required = 'radiology.change_imagingstudy'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ImagingStudy.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_success_url(self):
|
|
# return reverse('radiology:imaging_study_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='IMAGING_STUDY_UPDATED',
|
|
# model='ImagingStudy',
|
|
# object_id=str(self.object.study_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, 'Imaging study updated successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # IMAGING SERIES VIEWS (RESTRICTED CRUD - Clinical Data)
|
|
# # ============================================================================
|
|
#
|
|
# class ImagingSeriesListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all imaging series with filtering and search.
|
|
# """
|
|
# model = ImagingSeries
|
|
# template_name = 'radiology/imaging_series_list.html'
|
|
# context_object_name = 'imaging_series'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = ImagingSeries.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Filter by study
|
|
# study_id = self.request.GET.get('study')
|
|
# if study_id:
|
|
# queryset = queryset.filter(study_id=study_id)
|
|
#
|
|
# # Search functionality
|
|
# search = self.request.GET.get('search')
|
|
# if search:
|
|
# queryset = queryset.filter(
|
|
# Q(series_description__icontains=search) |
|
|
# Q(study__order__patient__first_name__icontains=search) |
|
|
# Q(study__order__patient__last_name__icontains=search)
|
|
# )
|
|
#
|
|
# return queryset.select_related('study__order__patient').order_by('-created_at')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'studies': ImagingStudy.objects.filter(
|
|
# tenant=self.request.user.tenant
|
|
# ).select_related('order__patient').order_by('-study_datetime')[:50],
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class ImagingSeriesDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about an imaging series.
|
|
# """
|
|
# model = ImagingSeries
|
|
# template_name = 'radiology/imaging_series_detail.html'
|
|
# context_object_name = 'imaging_series'
|
|
#
|
|
# def get_queryset(self):
|
|
# return ImagingSeries.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# imaging_series = self.object
|
|
#
|
|
# # Get images for this series
|
|
# context['images'] = imaging_series.images.all().order_by('instance_number')
|
|
#
|
|
# return context
|
|
#
|
|
#
|
|
# class ImagingSeriesCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new imaging series.
|
|
# """
|
|
# model = ImagingSeries
|
|
# form_class = ImagingSeriesForm
|
|
# template_name = 'radiology/imaging_series_form.html'
|
|
# permission_required = 'radiology.add_imagingseries'
|
|
# success_url = reverse_lazy('radiology:imaging_series_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='IMAGING_SERIES_CREATED',
|
|
# model='ImagingSeries',
|
|
# object_id=str(self.object.series_id),
|
|
# details={
|
|
# 'series_description': self.object.series_description,
|
|
# 'study_id': str(self.object.study.study_id)
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Imaging series created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # DICOM IMAGE VIEWS (READ-ONLY - System Generated)
|
|
# # ============================================================================
|
|
#
|
|
# class DICOMImageListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all DICOM images with filtering and search.
|
|
# """
|
|
# model = DICOMImage
|
|
# template_name = 'radiology/dicom_image_list.html'
|
|
# context_object_name = 'dicom_images'
|
|
# paginate_by = 50
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = DICOMImage.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Filter by series
|
|
# series_id = self.request.GET.get('series')
|
|
# if series_id:
|
|
# queryset = queryset.filter(series_id=series_id)
|
|
#
|
|
# # Filter by study
|
|
# study_id = self.request.GET.get('study')
|
|
# if study_id:
|
|
# queryset = queryset.filter(series__study_id=study_id)
|
|
#
|
|
# return queryset.select_related('series__study__order__patient').order_by('instance_number')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'series': ImagingSeries.objects.filter(
|
|
# tenant=self.request.user.tenant
|
|
# ).select_related('study__order__patient').order_by('-created_at')[:50],
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class DICOMImageDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a DICOM image.
|
|
# """
|
|
# model = DICOMImage
|
|
# template_name = 'radiology/dicom_image_detail.html'
|
|
# context_object_name = 'dicom_image'
|
|
#
|
|
# def get_queryset(self):
|
|
# return DICOMImage.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # RADIOLOGY REPORT VIEWS (APPEND-ONLY - Clinical Records)
|
|
# # ============================================================================
|
|
#
|
|
# class RadiologyReportListView(LoginRequiredMixin, ListView):
|
|
# """
|
|
# List all radiology reports with filtering and search.
|
|
# """
|
|
# model = RadiologyReport
|
|
# template_name = 'radiology/radiology_report_list.html'
|
|
# context_object_name = 'radiology_reports'
|
|
# paginate_by = 25
|
|
#
|
|
# def get_queryset(self):
|
|
# queryset = RadiologyReport.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
# # Search functionality
|
|
# search = self.request.GET.get('search')
|
|
# if search:
|
|
# queryset = queryset.filter(
|
|
# Q(study__order__patient__first_name__icontains=search) |
|
|
# Q(study__order__patient__last_name__icontains=search) |
|
|
# Q(study__order__patient__mrn__icontains=search) |
|
|
# Q(findings__icontains=search) |
|
|
# Q(impression__icontains=search)
|
|
# )
|
|
#
|
|
# # Filter by status
|
|
# status = self.request.GET.get('status')
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# # Filter by critical findings
|
|
# critical_only = self.request.GET.get('critical_only')
|
|
# if critical_only:
|
|
# queryset = queryset.filter(has_critical_findings=True)
|
|
#
|
|
# # Filter by radiologist
|
|
# radiologist_id = self.request.GET.get('radiologist')
|
|
# if radiologist_id:
|
|
# queryset = queryset.filter(radiologist_id=radiologist_id)
|
|
#
|
|
# return queryset.select_related(
|
|
# 'study__order__patient', 'radiologist', 'template'
|
|
# ).order_by('-created_at')
|
|
#
|
|
# def get_context_data(self, **kwargs):
|
|
# context = super().get_context_data(**kwargs)
|
|
# context.update({
|
|
# 'statuses': RadiologyReport._meta.get_field('status').choices,
|
|
# })
|
|
# return context
|
|
#
|
|
#
|
|
# class RadiologyReportDetailView(LoginRequiredMixin, DetailView):
|
|
# """
|
|
# Display detailed information about a radiology report.
|
|
# """
|
|
# model = RadiologyReport
|
|
# template_name = 'radiology/radiology_report_detail.html'
|
|
# context_object_name = 'radiology_report'
|
|
#
|
|
# def get_queryset(self):
|
|
# return RadiologyReport.objects.filter(tenant=self.request.user.tenant)
|
|
#
|
|
#
|
|
# class RadiologyReportCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
|
# """
|
|
# Create a new radiology report.
|
|
# """
|
|
# model = RadiologyReport
|
|
# form_class = RadiologyReportForm
|
|
# template_name = 'radiology/radiology_report_form.html'
|
|
# permission_required = 'radiology.add_radiologyreport'
|
|
# success_url = reverse_lazy('radiology:radiology_report_list')
|
|
#
|
|
# def form_valid(self, form):
|
|
# form.instance.tenant = self.request.user.tenant
|
|
# form.instance.radiologist = self.request.user
|
|
# response = super().form_valid(form)
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=self.request.user,
|
|
# action='RADIOLOGY_REPORT_CREATED',
|
|
# model='RadiologyReport',
|
|
# object_id=str(self.object.report_id),
|
|
# details={
|
|
# 'patient_name': f"{self.object.study.order.patient.first_name} {self.object.study.order.patient.last_name}",
|
|
# 'has_critical_findings': self.object.has_critical_findings
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(self.request, 'Radiology report created successfully.')
|
|
# return response
|
|
#
|
|
#
|
|
# # Note: No UpdateView or DeleteView for RadiologyReport - Append-only for clinical records
|
|
# # Reports can only be amended through addendum process after signing
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # HTMX VIEWS FOR REAL-TIME UPDATES
|
|
# # ============================================================================
|
|
#
|
|
# @login_required
|
|
# def radiology_stats(request):
|
|
# """
|
|
# HTMX endpoint for radiology statistics.
|
|
# """
|
|
# tenant = request.user.tenant
|
|
# today = timezone.now().date()
|
|
#
|
|
# stats = {
|
|
# 'pending_orders': ImagingOrder.objects.filter(
|
|
# tenant=tenant,
|
|
# status='PENDING'
|
|
# ).count(),
|
|
# 'scheduled_studies': ImagingStudy.objects.filter(
|
|
# tenant=tenant,
|
|
# status='SCHEDULED'
|
|
# ).count(),
|
|
# 'in_progress_studies': ImagingStudy.objects.filter(
|
|
# tenant=tenant,
|
|
# status='IN_PROGRESS'
|
|
# ).count(),
|
|
# 'reports_pending': RadiologyReport.objects.filter(
|
|
# tenant=tenant,
|
|
# status='DRAFT'
|
|
# ).count(),
|
|
# 'critical_findings': RadiologyReport.objects.filter(
|
|
# tenant=tenant,
|
|
# has_critical_findings=True,
|
|
# status='SIGNED',
|
|
# signed_datetime__date=today
|
|
# ).count(),
|
|
# }
|
|
#
|
|
# return render(request, 'radiology/partials/radiology_stats.html', {'stats': stats})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def order_search(request):
|
|
# """
|
|
# HTMX endpoint for imaging order search.
|
|
# """
|
|
# search = request.GET.get('search', '')
|
|
# status = request.GET.get('status', '')
|
|
# modality = request.GET.get('modality', '')
|
|
#
|
|
# queryset = ImagingOrder.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(study_description__icontains=search)
|
|
# )
|
|
#
|
|
# if status:
|
|
# queryset = queryset.filter(status=status)
|
|
#
|
|
# if modality:
|
|
# queryset = queryset.filter(modality=modality)
|
|
#
|
|
# orders = queryset.select_related(
|
|
# 'patient', 'ordering_provider'
|
|
# ).order_by('-order_datetime')[:20]
|
|
#
|
|
# return render(request, 'radiology/partials/order_list.html', {'orders': orders})
|
|
#
|
|
#
|
|
# # ============================================================================
|
|
# # ACTION VIEWS
|
|
# # ============================================================================
|
|
#
|
|
# @login_required
|
|
# def schedule_study(request, order_id):
|
|
# """
|
|
# Schedule an imaging study for an order.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# order = get_object_or_404(
|
|
# ImagingOrder,
|
|
# id=order_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# scheduled_datetime = request.POST.get('scheduled_datetime')
|
|
# if scheduled_datetime:
|
|
# scheduled_datetime = timezone.datetime.fromisoformat(scheduled_datetime)
|
|
#
|
|
# # Create or update study
|
|
# study, created = ImagingStudy.objects.get_or_create(
|
|
# order=order,
|
|
# tenant=request.user.tenant,
|
|
# defaults={
|
|
# 'study_datetime': scheduled_datetime,
|
|
# 'status': 'SCHEDULED',
|
|
# 'technologist': request.user
|
|
# }
|
|
# )
|
|
#
|
|
# if not created:
|
|
# study.study_datetime = scheduled_datetime
|
|
# study.status = 'SCHEDULED'
|
|
# study.save()
|
|
#
|
|
# # Update order status
|
|
# order.status = 'SCHEDULED'
|
|
# order.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='STUDY_SCHEDULED',
|
|
# model='ImagingOrder',
|
|
# object_id=str(order.order_id),
|
|
# details={
|
|
# 'patient_name': f"{order.patient.first_name} {order.patient.last_name}",
|
|
# 'scheduled_time': scheduled_datetime.isoformat()
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Study scheduled successfully.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'radiology/partials/order_status.html', {'order': order})
|
|
#
|
|
# return redirect('radiology:imaging_order_detail', pk=order.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def start_study(request, study_id):
|
|
# """
|
|
# Start an imaging study.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# study = get_object_or_404(
|
|
# ImagingStudy,
|
|
# id=study_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# study.status = 'IN_PROGRESS'
|
|
# study.started_datetime = timezone.now()
|
|
# study.technologist = request.user
|
|
# study.save()
|
|
#
|
|
# # Update order status
|
|
# study.order.status = 'IN_PROGRESS'
|
|
# study.order.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='STUDY_STARTED',
|
|
# model='ImagingStudy',
|
|
# object_id=str(study.study_id),
|
|
# details={
|
|
# 'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
|
|
# 'modality': study.order.modality
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Study started successfully.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'radiology/partials/study_status.html', {'study': study})
|
|
#
|
|
# return redirect('radiology:imaging_study_detail', pk=study.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def complete_study(request, study_id):
|
|
# """
|
|
# Complete an imaging study.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# study = get_object_or_404(
|
|
# ImagingStudy,
|
|
# id=study_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# study.status = 'COMPLETED'
|
|
# study.completed_datetime = timezone.now()
|
|
# study.save()
|
|
#
|
|
# # Update order status
|
|
# study.order.status = 'COMPLETED'
|
|
# study.order.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='STUDY_COMPLETED',
|
|
# model='ImagingStudy',
|
|
# object_id=str(study.study_id),
|
|
# details={
|
|
# 'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
|
|
# 'modality': study.order.modality
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Study completed successfully.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'radiology/partials/study_status.html', {'study': study})
|
|
#
|
|
# return redirect('radiology:imaging_study_detail', pk=study.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def sign_report(request, report_id):
|
|
# """
|
|
# Sign a radiology report.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# report = get_object_or_404(
|
|
# RadiologyReport,
|
|
# id=report_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Only allow signing if report is in draft status
|
|
# if report.status != 'DRAFT':
|
|
# messages.error(request, 'Only draft reports can be signed.')
|
|
# return redirect('radiology:radiology_report_detail', pk=report.pk)
|
|
#
|
|
# report.status = 'SIGNED'
|
|
# report.signed_datetime = timezone.now()
|
|
# report.signed_by = request.user
|
|
# report.save()
|
|
#
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='REPORT_SIGNED',
|
|
# model='RadiologyReport',
|
|
# object_id=str(report.report_id),
|
|
# details={
|
|
# 'patient_name': f"{report.study.order.patient.first_name} {report.study.order.patient.last_name}",
|
|
# 'has_critical_findings': report.has_critical_findings
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Report signed successfully.')
|
|
#
|
|
# if request.headers.get('HX-Request'):
|
|
# return render(request, 'radiology/partials/report_status.html', {'report': report})
|
|
#
|
|
# return redirect('radiology:radiology_report_detail', pk=report.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
# @login_required
|
|
# def dictate_report(request, study_id):
|
|
# """
|
|
# Start dictating a report for a study.
|
|
# """
|
|
# if request.method == 'POST':
|
|
# study = get_object_or_404(
|
|
# ImagingStudy,
|
|
# id=study_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# template_id = request.POST.get('template_id')
|
|
# template = None
|
|
# if template_id:
|
|
# template = get_object_or_404(
|
|
# ReportTemplate,
|
|
# id=template_id,
|
|
# tenant=request.user.tenant
|
|
# )
|
|
#
|
|
# # Create report if it doesn't exist
|
|
# report, created = RadiologyReport.objects.get_or_create(
|
|
# study=study,
|
|
# tenant=request.user.tenant,
|
|
# defaults={
|
|
# 'radiologist': request.user,
|
|
# 'template': template,
|
|
# 'status': 'DRAFT'
|
|
# }
|
|
# )
|
|
#
|
|
# if created:
|
|
# # Log the action
|
|
# AuditLogger.log_action(
|
|
# user=request.user,
|
|
# action='REPORT_DICTATION_STARTED',
|
|
# model='RadiologyReport',
|
|
# object_id=str(report.report_id),
|
|
# details={
|
|
# 'patient_name': f"{study.order.patient.first_name} {study.order.patient.last_name}",
|
|
# 'template_used': template.template_name if template else None
|
|
# }
|
|
# )
|
|
#
|
|
# messages.success(request, 'Report dictation started.')
|
|
#
|
|
# return redirect('radiology:radiology_report_detail', pk=report.pk)
|
|
#
|
|
# return JsonResponse({'success': False})
|
|
#
|
|
#
|
|
#
|
|
#
|