Marwan Alwali 0a037d3d9d update
2025-09-01 11:26:11 +03:00

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('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_used=template,
study__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_event(
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_event(
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 = '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,
study__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 = '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/dicom_file_list.html'
context_object_name = 'dicom_images'
paginate_by = 50
def get_queryset(self):
queryset = DICOMImage.objects.filter(series__study__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(
study__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(study__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__imaging_order__patient', 'radiologist', 'template_used'
).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 = 'report'
def get_queryset(self):
return RadiologyReport.objects.filter(study__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_event(
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})
#
#
#
#