""" 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 = '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(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}) # # # #