agdar/medical/forms.py
2025-11-02 14:35:35 +03:00

442 lines
18 KiB
Python

"""
Medical forms for the Tenhal Multidisciplinary Healthcare Platform.
This module contains forms for medical consultations and follow-ups.
Based on MD-F-1 and MD-F-2 forms.
"""
from django import forms
from django.forms import inlineformset_factory
from django.utils.translation import gettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Row, Column, Submit, HTML
from .models import MedicalConsultation, MedicationPlan, MedicalFollowUp, ConsultationResponse, ConsultationFeedback
class MedicalConsultationForm(forms.ModelForm):
"""
Form for medical consultation (MD-F-1).
Comprehensive initial consultation form.
"""
class Meta:
model = MedicalConsultation
fields = [
'patient', 'appointment', 'consultation_date', 'provider',
'chief_complaint', 'present_illness_history', 'past_medical_history',
'vaccination_status', 'family_history',
'social_history', 'pregnancy_history', 'neonatal_history',
'developmental_motor_milestones', 'developmental_language_milestones',
'developmental_social_milestones', 'developmental_cognitive_milestones',
'behavioral_symptoms', 'physical_exam', 'clinical_summary', 'recommendations',
]
widgets = {
'consultation_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
'chief_complaint': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'present_illness_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'past_medical_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'vaccination_status': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'family_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'social_history': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'pregnancy_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'neonatal_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'developmental_motor_milestones': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'developmental_language_milestones': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'developmental_social_milestones': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'developmental_cognitive_milestones': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'behavioral_symptoms': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'physical_exam': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'clinical_summary': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'recommendations': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'patient': forms.HiddenInput(),
'appointment': forms.HiddenInput(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
'patient',
'appointment',
Fieldset(
_('Consultation Information'),
Row(
Column('consultation_date', css_class='form-group col-md-6 mb-0'),
Column('provider', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
),
Fieldset(
_('Chief Complaint & Present History'),
'chief_complaint',
'present_illness_history',
),
Fieldset(
_('Past History'),
'past_medical_history',
'vaccination_status',
),
Fieldset(
_('Family & Social History'),
'family_history',
'social_history',
),
Fieldset(
_('Pregnancy & Neonatal History'),
'pregnancy_history',
'neonatal_history',
),
Fieldset(
_('Developmental History'),
HTML('<p class="text-muted">{}</p>'.format(_('Motor, Language, Social, and Cognitive Milestones'))),
'developmental_motor_milestones',
'developmental_language_milestones',
'developmental_social_milestones',
'developmental_cognitive_milestones',
),
Fieldset(
_('Behavioral Symptoms'),
HTML('<p class="text-muted">{}</p>'.format(_('Checklist of behavioral observations'))),
'behavioral_symptoms',
),
Fieldset(
_('Physical Examination'),
'physical_exam',
),
Fieldset(
_('Summary & Recommendations'),
'clinical_summary',
'recommendations',
),
HTML('<hr><h5>{}</h5>'.format(_('Medications'))),
HTML('<p class="text-muted">{}</p>'.format(_('Add current medications below'))),
Submit('submit', _('Save Consultation'), css_class='btn btn-primary')
)
class MedicationPlanForm(forms.ModelForm):
"""
Form for medication plans.
"""
class Meta:
model = MedicationPlan
fields = [
'drug_name', 'dose', 'frequency', 'compliance',
'gains', 'side_effects', 'target_behavior', 'improved',
]
widgets = {
'drug_name': forms.TextInput(attrs={'class': 'form-control'}),
'dose': forms.TextInput(attrs={'class': 'form-control'}),
'frequency': forms.TextInput(attrs={'class': 'form-control'}),
'compliance': forms.Select(attrs={'class': 'form-control'}),
'gains': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'side_effects': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'target_behavior': forms.TextInput(attrs={'class': 'form-control'}),
'improved': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
# Inline formset for medications
MedicationPlanFormSet = inlineformset_factory(
MedicalConsultation,
MedicationPlan,
form=MedicationPlanForm,
extra=3,
can_delete=True,
min_num=0,
)
class MedicalFollowUpForm(forms.ModelForm):
"""
Form for medical follow-up (MD-F-2).
Links to previous consultation.
"""
class Meta:
model = MedicalFollowUp
fields = [
'patient', 'appointment', 'followup_date', 'provider',
'previous_consultation', 'previous_complaints_status',
'new_complaints', 'nursing_vitals', 'assessment',
'recommendations', 'family_satisfaction',
]
widgets = {
'patient': forms.HiddenInput(),
'appointment': forms.HiddenInput(),
'followup_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
'previous_consultation': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select previous consultation'}),
'previous_complaints_status': forms.HiddenInput(),
'new_complaints': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'nursing_vitals': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select nursing vitals'}),
'assessment': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'recommendations': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'family_satisfaction': forms.RadioSelect(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Filter previous_consultation queryset to only show consultations for the same patient
if self.instance and self.instance.patient_id:
self.fields['previous_consultation'].queryset = MedicalConsultation.objects.filter(
patient=self.instance.patient
).order_by('-consultation_date')
elif self.initial.get('patient'):
from core.models import Patient
try:
patient = Patient.objects.get(pk=self.initial['patient'])
self.fields['previous_consultation'].queryset = MedicalConsultation.objects.filter(
patient=patient
).order_by('-consultation_date')
except Patient.DoesNotExist:
self.fields['previous_consultation'].queryset = MedicalConsultation.objects.none()
else:
self.fields['previous_consultation'].queryset = MedicalConsultation.objects.none()
# Filter nursing_vitals to same patient if available
if self.instance and self.instance.patient_id:
from nursing.models import NursingEncounter
self.fields['nursing_vitals'].queryset = NursingEncounter.objects.filter(
patient=self.instance.patient
).order_by('-encounter_date')
elif self.initial.get('patient'):
from core.models import Patient
from nursing.models import NursingEncounter
try:
patient = Patient.objects.get(pk=self.initial['patient'])
self.fields['nursing_vitals'].queryset = NursingEncounter.objects.filter(
patient=patient
).order_by('-encounter_date')
except Patient.DoesNotExist:
self.fields['nursing_vitals'].queryset = NursingEncounter.objects.none()
else:
from nursing.models import NursingEncounter
self.fields['nursing_vitals'].queryset = NursingEncounter.objects.none()
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
'patient',
'appointment',
Fieldset(
_('Follow-up Information'),
Row(
Column('followup_date', css_class='form-group col-md-6 mb-0'),
Column('provider', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
'previous_consultation',
'nursing_vitals',
),
Fieldset(
_('Previous Complaints Status'),
HTML('<p class="text-muted">{}</p>'.format(_('Status of complaints from previous consultation'))),
'previous_complaints_status',
),
Fieldset(
_('New Complaints'),
'new_complaints',
),
Fieldset(
_('Assessment & Plan'),
'assessment',
'recommendations',
),
Fieldset(
_('Family Satisfaction'),
HTML('<p class="text-muted">{}</p>'.format(_('Rate family satisfaction: 0 (Not Satisfied), 50 (Neutral), 100 (Very Satisfied)'))),
'family_satisfaction',
),
HTML('<hr><h5>{}</h5>'.format(_('Current Medications'))),
HTML('<p class="text-muted">{}</p>'.format(_('Medication snapshot from previous consultation will be displayed here'))),
Submit('submit', _('Save Follow-up'), css_class='btn btn-primary')
)
def clean(self):
cleaned_data = super().clean()
previous_consultation = cleaned_data.get('previous_consultation')
patient = cleaned_data.get('patient')
# Validate that previous consultation belongs to same patient
if previous_consultation and patient:
if previous_consultation.patient != patient:
raise forms.ValidationError(
_('Previous consultation must belong to the same patient.')
)
return cleaned_data
class ConsultationResponseForm(forms.ModelForm):
"""
Form for responding to medical consultation from other disciplines.
"""
class Meta:
model = ConsultationResponse
fields = [
'consultation', 'response_type', 'responder', 'response_date',
'assessment', 'recommendations', 'follow_up_needed', 'notes',
]
widgets = {
'consultation': forms.HiddenInput(),
'response_type': forms.Select(attrs={'class': 'form-control'}),
'responder': forms.HiddenInput(),
'response_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'assessment': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'recommendations': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'follow_up_needed': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'notes': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
'consultation',
'responder',
Fieldset(
_('Response Information'),
Row(
Column('response_type', css_class='form-group col-md-6 mb-0'),
Column('response_date', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
),
Fieldset(
_('Assessment & Recommendations'),
'assessment',
'recommendations',
),
Fieldset(
_('Follow-up'),
Row(
Column('follow_up_needed', css_class='form-group col-md-12 mb-0'),
css_class='form-row'
),
'notes',
),
Submit('submit', _('Submit Response'), css_class='btn btn-primary')
)
class ConsultationFeedbackForm(forms.ModelForm):
"""
Form for providing feedback on medical consultation.
"""
class Meta:
model = ConsultationFeedback
fields = [
'consultation', 'feedback_type', 'submitted_by', 'submitted_by_name',
'feedback_date', 'satisfaction_rating', 'communication_rating',
'care_quality_rating', 'comments', 'concerns', 'suggestions',
]
widgets = {
'consultation': forms.HiddenInput(),
'submitted_by': forms.HiddenInput(),
'feedback_type': forms.Select(attrs={'class': 'form-control'}),
'submitted_by_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Name (for family/caregiver)')}),
'feedback_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'satisfaction_rating': forms.RadioSelect(),
'communication_rating': forms.RadioSelect(),
'care_quality_rating': forms.RadioSelect(),
'comments': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'concerns': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'suggestions': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
'consultation',
'submitted_by',
Fieldset(
_('Feedback Information'),
Row(
Column('feedback_type', css_class='form-group col-md-6 mb-0'),
Column('feedback_date', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
'submitted_by_name',
),
Fieldset(
_('Ratings'),
HTML('<p class="text-muted">{}</p>'.format(_('Rate your experience (1=Very Dissatisfied, 5=Very Satisfied)'))),
'satisfaction_rating',
'communication_rating',
'care_quality_rating',
),
Fieldset(
_('Comments & Suggestions'),
'comments',
'concerns',
'suggestions',
),
Submit('submit', _('Submit Feedback'), css_class='btn btn-primary')
)
class MedicalConsultationSearchForm(forms.Form):
"""
Form for searching medical consultations.
"""
search_query = forms.CharField(
required=False,
label=_('Search'),
widget=forms.TextInput(attrs={
'placeholder': _('Patient name, MRN, Chief complaint...'),
'class': 'form-control'
})
)
provider = forms.ModelChoiceField(
required=False,
label=_('Provider'),
queryset=None, # Will be set in __init__
widget=forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'})
)
date_from = forms.DateField(
required=False,
label=_('From Date'),
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})
)
date_to = forms.DateField(
required=False,
label=_('To Date'),
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})
)
def __init__(self, *args, **kwargs):
tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
if tenant:
from core.models import User
self.fields['provider'].queryset = User.objects.filter(
tenant=tenant,
role=User.Role.DOCTOR
)
self.helper = FormHelper()
self.helper.form_method = 'get'
self.helper.layout = Layout(
Row(
Column('search_query', css_class='form-group col-md-4 mb-0'),
Column('provider', css_class='form-group col-md-3 mb-0'),
Column('date_from', css_class='form-group col-md-2 mb-0'),
Column('date_to', css_class='form-group col-md-2 mb-0'),
css_class='form-row'
),
Submit('search', _('Search'), css_class='btn btn-primary')
)