agdar/ot/forms.py
Marwan Alwali 2f1681b18c update
2025-11-11 13:44:48 +03:00

460 lines
18 KiB
Python

"""
OT (Occupational Therapy) forms for the Tenhal Multidisciplinary Healthcare Platform.
This module contains forms for OT consultations, sessions, and progress tracking.
Based on OT-F-1 and OT-F-3 forms with comprehensive field-level data capture.
"""
from django import forms
from django.forms import inlineformset_factory, modelformset_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, Div
from .models import (
OTConsult, OTDifficultyArea, OTMilestone, OTSelfHelpSkill,
OTInfantBehavior, OTCurrentBehavior, OTSession, OTTargetSkill,
OTProgressReport, OTScoringConfig
)
class OTConsultForm(forms.ModelForm):
"""
Main form for OT consultation (OT-F-1).
Initial occupational therapy assessment.
"""
class Meta:
model = OTConsult
fields = [
'patient', 'appointment', 'consultation_date', 'provider',
'referral_reason',
'motor_learning_difficulty', 'motor_learning_details',
'motor_skill_regression', 'regression_details',
'eats_healthy_variety', 'eats_variety_textures',
'participates_family_meals', 'eating_comments',
'infant_behavior_comments', 'current_behavior_comments',
'recommendation', 'recommendation_notes',
'clinician_name', 'clinician_signature',
]
widgets = {
'patient': forms.HiddenInput(),
'appointment': forms.HiddenInput(),
'consultation_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': _('Select provider')}),
'referral_reason': forms.Select(attrs={'class': 'form-select'}),
'motor_learning_difficulty': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}),
'motor_learning_details': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('If yes, describe the difficulties')}),
'motor_skill_regression': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}),
'regression_details': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('If yes, describe the regression')}),
'eats_healthy_variety': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}),
'eats_variety_textures': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}),
'participates_family_meals': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}),
'eating_comments': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('Comments, concerns, or questions related to eating')}),
'infant_behavior_comments': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'current_behavior_comments': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'recommendation': forms.Select(attrs={'class': 'form-select'}),
'recommendation_notes': forms.Textarea(attrs={'rows': 4, 'class': 'form-control border-success border-2'}),
'clinician_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter full name')}),
'clinician_signature': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Type full name as signature')}),
}
class OTDifficultyAreaForm(forms.ModelForm):
"""
Form for difficulty areas (max 3 selections).
"""
class Meta:
model = OTDifficultyArea
fields = ['area', 'details', 'order']
widgets = {
'area': forms.Select(attrs={'class': 'form-select difficulty-area-select'}),
'details': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('Brief details')}),
'order': forms.HiddenInput(),
}
class OTMilestoneForm(forms.ModelForm):
"""
Form for developmental milestones.
"""
class Meta:
model = OTMilestone
fields = ['milestone', 'age_achieved', 'notes', 'is_required']
widgets = {
'milestone': forms.Select(attrs={'class': 'form-select form-select-sm'}),
'age_achieved': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('e.g., 6 months')}),
'notes': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('Optional notes')}),
'is_required': forms.HiddenInput(),
}
class OTSelfHelpSkillForm(forms.ModelForm):
"""
Form for self-help skills assessment.
"""
class Meta:
model = OTSelfHelpSkill
fields = ['age_range', 'skill_name', 'response', 'comments']
widgets = {
'age_range': forms.Select(attrs={'class': 'form-select form-select-sm'}),
'skill_name': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('e.g., Feeds self with spoon')}),
'response': forms.RadioSelect(attrs={'class': 'form-check-input'}),
'comments': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('Optional comments')}),
}
class OTInfantBehaviorForm(forms.ModelForm):
"""
Form for infant behavior assessment.
"""
class Meta:
model = OTInfantBehavior
fields = ['behavior', 'response']
widgets = {
'behavior': forms.HiddenInput(),
'response': forms.RadioSelect(attrs={'class': 'form-check-input'}),
}
class OTCurrentBehaviorForm(forms.ModelForm):
"""
Form for current behavior assessment.
"""
class Meta:
model = OTCurrentBehavior
fields = ['behavior', 'response']
widgets = {
'behavior': forms.HiddenInput(),
'response': forms.RadioSelect(attrs={'class': 'form-check-input'}),
}
# Formsets for related models
OTDifficultyAreaFormSet = inlineformset_factory(
OTConsult,
OTDifficultyArea,
form=OTDifficultyAreaForm,
extra=3,
max_num=3,
can_delete=True,
validate_max=True,
)
OTMilestoneFormSet = inlineformset_factory(
OTConsult,
OTMilestone,
form=OTMilestoneForm,
extra=1,
can_delete=True,
)
OTSelfHelpSkillFormSet = inlineformset_factory(
OTConsult,
OTSelfHelpSkill,
form=OTSelfHelpSkillForm,
extra=1,
can_delete=True,
)
OTInfantBehaviorFormSet = inlineformset_factory(
OTConsult,
OTInfantBehavior,
form=OTInfantBehaviorForm,
extra=0,
can_delete=False,
)
OTCurrentBehaviorFormSet = inlineformset_factory(
OTConsult,
OTCurrentBehavior,
form=OTCurrentBehaviorForm,
extra=0,
can_delete=False,
)
# ============================================================================
# OT Session Forms (OT-F-3)
# ============================================================================
class OTSessionForm(forms.ModelForm):
"""
Form for OT session notes (OT-F-3).
Session documentation with target skills.
"""
class Meta:
model = OTSession
fields = [
'patient', 'appointment', 'session_date',
'provider', 'session_type', 'cooperative_level',
'distraction_tolerance', 'activities_checklist',
'observations', 'activities_performed', 'recommendations',
]
widgets = {
'session_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
'session_type': forms.Select(attrs={'class': 'form-control'}),
'cooperative_level': forms.NumberInput(attrs={'class': 'form-control', 'min': '1', 'max': '4'}),
'distraction_tolerance': forms.NumberInput(attrs={'class': 'form-control', 'min': '1', 'max': '4'}),
'activities_checklist': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Fine motor skills, Gross motor skills, Sensory integration')}),
'observations': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'activities_performed': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'recommendations': forms.Textarea(attrs={'rows': 3, '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(
_('Session Information'),
Row(
Column('session_date', css_class='form-group col-md-6 mb-0'),
Column('session_type', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
'provider',
),
Fieldset(
_('Session Metrics'),
Row(
Column('cooperative_level', css_class='form-group col-md-6 mb-0'),
Column('distraction_tolerance', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
HTML('<small class="text-muted">{}</small>'.format(_('Rate from 1 (Poor) to 4 (Excellent)'))),
),
Fieldset(
_('Today We Work On...'),
'activities_checklist',
),
Fieldset(
_('Session Notes'),
'observations',
'activities_performed',
'recommendations',
),
HTML('<hr><h5>{}</h5>'.format(_('Target Skills (0-10 Scoring)'))),
HTML('<p class="text-muted">{}</p>'.format(_('Add target skills below with baseline and current scores'))),
Submit('submit', _('Save OT Session'), css_class='btn btn-primary')
)
def clean(self):
cleaned_data = super().clean()
cooperative_level = cleaned_data.get('cooperative_level')
distraction_tolerance = cleaned_data.get('distraction_tolerance')
# Validate levels are within range
if cooperative_level and (cooperative_level < 1 or cooperative_level > 4):
self.add_error('cooperative_level', _('Level must be between 1 and 4'))
if distraction_tolerance and (distraction_tolerance < 1 or distraction_tolerance > 4):
self.add_error('distraction_tolerance', _('Level must be between 1 and 4'))
return cleaned_data
class OTTargetSkillForm(forms.ModelForm):
"""
Form for OT target skills with 0-10 scoring.
"""
class Meta:
model = OTTargetSkill
fields = [
'skill_name', 'score', 'notes', 'order',
]
widgets = {
'skill_name': forms.TextInput(attrs={'placeholder': _('e.g., Pencil grip'), 'class': 'form-control'}),
'score': forms.NumberInput(attrs={'class': 'form-control', 'min': '0', 'max': '10'}),
'notes': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'order': forms.HiddenInput(),
}
def clean(self):
cleaned_data = super().clean()
score = cleaned_data.get('score')
# Validate score is within 0-10 range
if score is not None and (score < 0 or score > 10):
self.add_error('score', _('Score must be between 0 and 10'))
return cleaned_data
# Inline formset for target skills
OTTargetSkillFormSet = inlineformset_factory(
OTSession,
OTTargetSkill,
form=OTTargetSkillForm,
extra=3,
can_delete=True,
min_num=1,
validate_min=True,
)
class OTProgressReportForm(forms.ModelForm):
"""
Form for OT progress reports.
"""
class Meta:
model = OTProgressReport
fields = [
'patient', 'report_date', 'provider',
'sessions_scheduled', 'sessions_attended',
'goals_progress', 'overall_progress',
'recommendations', 'continue_treatment',
]
widgets = {
'patient': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select patient'}),
'report_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
'sessions_scheduled': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'sessions_attended': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'goals_progress': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('e.g., Goal 1: Achieved, Goal 2: In Progress, Goal 3: Not Started')}),
'overall_progress': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'recommendations': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'continue_treatment': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
'patient',
Row(
Column('report_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(
_('Session Summary'),
Row(
Column('sessions_scheduled', css_class='form-group col-md-6 mb-0'),
Column('sessions_attended', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
),
Fieldset(
_('Progress Summary'),
'goals_progress',
'overall_progress',
),
Fieldset(
_('Recommendations'),
'recommendations',
'continue_treatment',
),
Submit('submit', _('Save Progress Report'), css_class='btn btn-primary')
)
class OTSessionSearchForm(forms.Form):
"""
Form for searching OT sessions.
"""
search_query = forms.CharField(
required=False,
label=_('Search'),
widget=forms.TextInput(attrs={
'placeholder': _('Patient name, MRN...'),
'class': 'form-control'
})
)
session_type = forms.ChoiceField(
required=False,
label=_('Session Type'),
choices=[('', _('All'))] + list(OTSession.SessionType.choices),
widget=forms.Select(attrs={'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.OT
)
self.helper = FormHelper()
self.helper.form_method = 'get'
self.helper.layout = Layout(
Row(
Column('search_query', css_class='form-group col-md-3 mb-0'),
Column('session_type', css_class='form-group col-md-2 mb-0'),
Column('provider', css_class='form-group col-md-2 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')
)
class OTScoringConfigForm(forms.ModelForm):
"""
Form for OT scoring configuration.
"""
class Meta:
model = OTScoringConfig
fields = [
'name', 'is_active',
'self_help_max', 'behavior_max', 'developmental_max', 'eating_max',
'immediate_attention_threshold', 'moderate_difficulty_threshold',
'immediate_attention_label', 'moderate_difficulty_label', 'age_appropriate_label',
'immediate_attention_recommendation', 'moderate_difficulty_recommendation',
'age_appropriate_recommendation',
]
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'self_help_max': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'behavior_max': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'developmental_max': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'eating_max': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'immediate_attention_threshold': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'moderate_difficulty_threshold': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'immediate_attention_label': forms.TextInput(attrs={'class': 'form-control'}),
'moderate_difficulty_label': forms.TextInput(attrs={'class': 'form-control'}),
'age_appropriate_label': forms.TextInput(attrs={'class': 'form-control'}),
'immediate_attention_recommendation': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'moderate_difficulty_recommendation': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'age_appropriate_recommendation': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
}