""" Radiology app forms for healthcare-focused CRUD operations. """ from django import forms from django.core.exceptions import ValidationError from django.utils import timezone from .models import ( ImagingOrder, ImagingStudy, ImagingSeries, DICOMImage, RadiologyReport, ReportTemplate ) from patients.models import PatientProfile from accounts.models import User class ReportTemplateForm(forms.ModelForm): """ Form for creating and updating radiology report templates. """ class Meta: model = ReportTemplate fields = [ 'name', 'modality', 'body_part', 'description', 'findings_template', 'impression_template', 'is_active' ] widgets = { 'description': forms.Textarea(attrs={'rows': 8}), 'findings_template': forms.Textarea(attrs={'rows': 5}), 'impression_template': forms.Textarea(attrs={'rows': 3}), } def clean_name(self): name = self.cleaned_data['name'] # Check for duplicate template names within the same tenant if self.instance.pk: existing = ReportTemplate.objects.filter( name=name, tenant=self.instance.tenant ).exclude(pk=self.instance.pk) else: # For new instances, tenant will be set in the view existing = ReportTemplate.objects.filter(name=name) if existing.exists(): raise ValidationError('A template with this name already exists.') return name class ImagingOrderForm(forms.ModelForm): """ Form for creating imaging orders. """ class Meta: model = ImagingOrder fields = [ 'patient', 'modality', 'study_description', 'body_part', 'clinical_indication', 'priority', 'contrast_required', 'special_instructions', 'clinical_history' ] widgets = { 'clinical_indication': forms.Textarea(attrs={'rows': 3}), 'special_instructions': forms.Textarea(attrs={'rows': 2}), 'clinical_history': forms.Textarea(attrs={'rows': 2}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['patient'].queryset = PatientProfile.objects.filter( tenant=user.tenant ).order_by('last_name', 'first_name') # Make urgent_reason required if priority is URGENT if self.data and self.data.get('priority') == 'URGENT': self.fields['urgent_reason'].required = True def clean(self): cleaned_data = super().clean() priority = cleaned_data.get('priority') urgent_reason = cleaned_data.get('urgent_reason') contrast_required = cleaned_data.get('contrast_required') modality = cleaned_data.get('modality') # Validate urgent reason for urgent orders if priority == 'URGENT' and not urgent_reason: raise ValidationError('Urgent reason is required for urgent orders.') # Validate contrast requirement for certain modalities if modality in ['CT', 'MRI'] and contrast_required is None: self.add_error( 'contrast_required', f'Please specify if contrast is required for {modality} studies.' ) return cleaned_data class ImagingStudyForm(forms.ModelForm): """ Form for creating imaging studies. """ class Meta: model = ImagingStudy fields = [ 'imaging_order', 'study_datetime', 'radiologist', 'station_name', 'modality', 'study_description', 'body_part', 'clinical_indication', 'status' ] widgets = { 'study_datetime': forms.DateTimeInput( attrs={'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M' ), 'clinical_indication': forms.Textarea(attrs={'rows': 3}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['order'].queryset = ImagingOrder.objects.filter( tenant=user.tenant, status__in=['PENDING', 'SCHEDULED'] ).select_related('patient').order_by('-order_datetime') self.fields['technologist'].queryset = User.objects.filter( tenant=user.tenant, groups__name__in=['Technologists', 'Radiologic Technologists'] ).order_by('first_name', 'last_name') # Set default study time to now if not self.instance.pk: self.fields['study_datetime'].initial = timezone.now() def clean(self): cleaned_data = super().clean() contrast_used = cleaned_data.get('contrast_used') contrast_agent = cleaned_data.get('contrast_agent') contrast_volume_ml = cleaned_data.get('contrast_volume_ml') study_datetime = cleaned_data.get('study_datetime') # Validate contrast information if contrast_used: if not contrast_agent: raise ValidationError('Contrast agent is required when contrast is used.') if not contrast_volume_ml: raise ValidationError('Contrast volume is required when contrast is used.') # Validate study datetime if study_datetime and study_datetime > timezone.now(): # Allow future scheduling but warn pass return cleaned_data class ImagingSeriesForm(forms.ModelForm): """ Form for creating imaging series. """ class Meta: model = ImagingSeries fields = [ 'study', 'series_number', 'series_description', 'modality', 'body_part', 'slice_thickness', 'pixel_spacing', 'protocol_name', 'number_of_instances' ] widgets = { 'slice_thickness': forms.NumberInput(attrs={'min': 0.1, 'step': 0.1}), 'pixel_spacing': forms.NumberInput(attrs={'min': 0.01, 'step': 0.01}), 'number_of_instances': forms.NumberInput(attrs={'min': 1}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['study'].queryset = ImagingStudy.objects.filter( tenant=user.tenant, status__in=['IN_PROGRESS', 'COMPLETED'] ).select_related('order__patient').order_by('-study_datetime') def clean(self): cleaned_data = super().clean() study = cleaned_data.get('study') series_number = cleaned_data.get('series_number') # Check for duplicate series numbers within the same study if study and series_number: existing = ImagingSeries.objects.filter( study=study, series_number=series_number ) if self.instance.pk: existing = existing.exclude(pk=self.instance.pk) if existing.exists(): raise ValidationError('A series with this number already exists for this study.') return cleaned_data class RadiologyReportForm(forms.ModelForm): """ Form for creating radiology reports. """ class Meta: model = RadiologyReport fields = [ 'study', 'template_used', 'clinical_history', 'technique', 'findings', 'impression', 'recommendations', 'critical_finding', 'radiologist' ] widgets = { 'clinical_history': forms.Textarea(attrs={'rows': 2}), 'technique': forms.Textarea(attrs={'rows': 3}), 'findings': forms.Textarea(attrs={'rows': 6}), 'impression': forms.Textarea(attrs={'rows': 3}), 'recommendations': forms.Textarea(attrs={'rows': 2}), } def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super().__init__(*args, **kwargs) if user and hasattr(user, 'tenant'): self.fields['study'].queryset = ImagingStudy.objects.filter( tenant=user.tenant, status='COMPLETED' ).select_related('order__patient').order_by('-completed_datetime') self.fields['template'].queryset = ReportTemplate.objects.filter( tenant=user.tenant, is_active=True ).order_by('template_name') # Make critical findings description required if has_critical_findings is True if self.data and self.data.get('has_critical_findings'): self.fields['critical_findings_description'].required = True def clean(self): cleaned_data = super().clean() has_critical_findings = cleaned_data.get('has_critical_findings') critical_findings_description = cleaned_data.get('critical_findings_description') study = cleaned_data.get('study') # Validate critical findings if has_critical_findings and not critical_findings_description: raise ValidationError('Critical findings description is required when critical findings are present.') # Check if report already exists for this study if study: existing = RadiologyReport.objects.filter(study=study) if self.instance.pk: existing = existing.exclude(pk=self.instance.pk) if existing.exists(): raise ValidationError('A report already exists for this study.') return cleaned_data # Additional forms for search and filtering class ImagingOrderSearchForm(forms.Form): """ Form for searching and filtering imaging orders. """ search = forms.CharField( max_length=100, required=False, widget=forms.TextInput(attrs={ 'placeholder': 'Search by patient name, MRN, or study...', 'class': 'form-control' }) ) status = forms.ChoiceField( choices=[('', 'All Statuses')] + ImagingOrder._meta.get_field('status').choices, required=False, widget=forms.Select(attrs={'class': 'form-control'}) ) modality = forms.ChoiceField( choices=[('', 'All Modalities')] + ImagingOrder._meta.get_field('modality').choices, required=False, widget=forms.Select(attrs={'class': 'form-control'}) ) priority = forms.ChoiceField( choices=[('', 'All Priorities')] + ImagingOrder._meta.get_field('priority').choices, required=False, widget=forms.Select(attrs={'class': 'form-control'}) ) date_from = forms.DateField( required=False, widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}) ) date_to = forms.DateField( required=False, widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}) ) class ImagingStudySearchForm(forms.Form): """ Form for searching and filtering imaging studies. """ search = forms.CharField( max_length=100, required=False, widget=forms.TextInput(attrs={ 'placeholder': 'Search by patient name, MRN, or study ID...', 'class': 'form-control' }) ) status = forms.ChoiceField( choices=[('', 'All Statuses')] + ImagingStudy._meta.get_field('status').choices, required=False, widget=forms.Select(attrs={'class': 'form-control'}) ) modality = forms.ChoiceField( choices=[('', 'All Modalities')] + ImagingOrder._meta.get_field('modality').choices, required=False, widget=forms.Select(attrs={'class': 'form-control'}) ) date_from = forms.DateField( required=False, widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}) ) date_to = forms.DateField( required=False, widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}) ) class RadiologyReportSearchForm(forms.Form): """ Form for searching and filtering radiology reports. """ search = forms.CharField( max_length=100, required=False, widget=forms.TextInput(attrs={ 'placeholder': 'Search by patient name, MRN, findings, or impression...', 'class': 'form-control' }) ) status = forms.ChoiceField( choices=[('', 'All Statuses')] + RadiologyReport._meta.get_field('status').choices, required=False, widget=forms.Select(attrs={'class': 'form-control'}) ) critical_only = forms.BooleanField( required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) ) date_from = forms.DateField( required=False, widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}) ) date_to = forms.DateField( required=False, widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}) ) class ReportTemplateSearchForm(forms.Form): """ Form for searching and filtering report templates. """ search = forms.CharField( max_length=100, required=False, widget=forms.TextInput(attrs={ 'placeholder': 'Search by template name, modality, or body part...', 'class': 'form-control' }) ) modality = forms.ChoiceField( choices=[('', 'All Modalities')] + ReportTemplate._meta.get_field('modality').choices, required=False, widget=forms.Select(attrs={'class': 'form-control'}) ) active_only = forms.BooleanField( required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) ) # Quick action forms class StudyScheduleForm(forms.Form): """ Form for scheduling imaging studies. """ scheduled_datetime = forms.DateTimeField( widget=forms.DateTimeInput( attrs={'type': 'datetime-local', 'class': 'form-control'}, format='%Y-%m-%dT%H:%M' ) ) room_location = forms.CharField( max_length=50, required=False, widget=forms.TextInput(attrs={'class': 'form-control'}) ) notes = forms.CharField( required=False, widget=forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}) ) def clean_scheduled_datetime(self): scheduled_datetime = self.cleaned_data['scheduled_datetime'] # Don't allow scheduling in the past (except for emergency cases) if scheduled_datetime < timezone.now(): raise ValidationError('Cannot schedule studies in the past.') return scheduled_datetime class ReportSigningForm(forms.Form): """ Form for signing radiology reports. """ electronic_signature = forms.CharField( max_length=100, widget=forms.PasswordInput(attrs={ 'class': 'form-control', 'placeholder': 'Enter your electronic signature' }) ) signing_notes = forms.CharField( required=False, widget=forms.Textarea(attrs={ 'rows': 2, 'class': 'form-control', 'placeholder': 'Optional signing notes' }) ) def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) super().__init__(*args, **kwargs) def clean_electronic_signature(self): signature = self.cleaned_data['electronic_signature'] # Validate electronic signature (implement your signature validation logic) if self.user and signature != self.user.username: raise ValidationError('Invalid electronic signature.') return signature