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

482 lines
16 KiB
Python

"""
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 critical_finding is True
if self.data and self.data.get('critical_finding'):
self.fields['critical_findings_description'].required = True
def clean(self):
cleaned_data = super().clean()
critical_finding = cleaned_data.get('critical_finding')
critical_findings_description = cleaned_data.get('critical_findings_description')
study = cleaned_data.get('study')
# Validate critical findings
if critical_finding 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