HH/apps/projects/forms.py
2026-03-09 16:10:24 +03:00

299 lines
13 KiB
Python

"""
QI Projects Forms
Forms for creating and managing Quality Improvement projects and tasks.
"""
from django import forms
from django.utils.translation import gettext_lazy as _
from apps.core.form_mixins import HospitalFieldMixin
from apps.accounts.models import User
from apps.organizations.models import Department, Hospital
from .models import QIProject, QIProjectTask
class QIProjectForm(HospitalFieldMixin, forms.ModelForm):
"""
Form for creating and editing QI Projects.
Hospital field visibility:
- PX Admins: See dropdown with all hospitals
- Others: Hidden field, auto-set to user's hospital
"""
class Meta:
model = QIProject
fields = [
'name', 'name_ar', 'description', 'hospital', 'department',
'project_lead', 'team_members', 'status',
'start_date', 'target_completion_date', 'outcome_description'
]
widgets = {
'name': forms.TextInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'placeholder': _('Project name')
}),
'name_ar': forms.TextInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'placeholder': _('اسم المشروع'),
'dir': 'rtl'
}),
'description': forms.Textarea(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm resize-none',
'rows': 4,
'placeholder': _('Describe the project objectives and scope...')
}),
'hospital': forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
}),
'department': forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
}),
'project_lead': forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
}),
'team_members': forms.SelectMultiple(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white h-40'
}),
'status': forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
}),
'start_date': forms.DateInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'type': 'date'
}),
'target_completion_date': forms.DateInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'type': 'date'
}),
'outcome_description': forms.Textarea(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm resize-none',
'rows': 3,
'placeholder': _('Document project outcomes and results...')
}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Filter department choices based on hospital
hospital_id = None
if self.data.get('hospital'):
hospital_id = self.data.get('hospital')
elif self.initial.get('hospital'):
hospital_id = self.initial.get('hospital')
elif self.instance and self.instance.pk and self.instance.hospital:
hospital_id = self.instance.hospital.id
elif self.user and self.user.hospital:
hospital_id = self.user.hospital.id
if hospital_id:
self.fields['department'].queryset = Department.objects.filter(
hospital_id=hospital_id,
status='active'
).order_by('name')
# Filter user choices based on hospital
self.fields['project_lead'].queryset = User.objects.filter(
hospital_id=hospital_id,
is_active=True
).order_by('first_name', 'last_name')
self.fields['team_members'].queryset = User.objects.filter(
hospital_id=hospital_id,
is_active=True
).order_by('first_name', 'last_name')
else:
self.fields['department'].queryset = Department.objects.none()
self.fields['project_lead'].queryset = User.objects.none()
self.fields['team_members'].queryset = User.objects.none()
class QIProjectTaskForm(forms.ModelForm):
"""
Form for creating and editing QI Project tasks.
"""
class Meta:
model = QIProjectTask
fields = ['title', 'description', 'assigned_to', 'status', 'due_date', 'order']
widgets = {
'title': forms.TextInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'placeholder': _('Task title')
}),
'description': forms.Textarea(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm resize-none',
'rows': 3,
'placeholder': _('Task description...')
}),
'assigned_to': forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
}),
'status': forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
}),
'due_date': forms.DateInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'type': 'date'
}),
'order': forms.NumberInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'min': 0
}),
}
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project', None)
super().__init__(*args, **kwargs)
# Make order field not required (has default value of 0)
self.fields['order'].required = False
# Filter assigned_to choices based on project hospital
if self.project and self.project.hospital:
self.fields['assigned_to'].queryset = User.objects.filter(
hospital=self.project.hospital,
is_active=True
).order_by('first_name', 'last_name')
else:
self.fields['assigned_to'].queryset = User.objects.none()
class QIProjectTemplateForm(HospitalFieldMixin, forms.ModelForm):
"""
Form for creating and editing QI Project templates.
Hospital field visibility:
- PX Admins: See dropdown with all hospitals
- Others: Hidden field, auto-set to user's hospital
Templates can be:
- Global (hospital=None) - available to all
- Hospital-specific - available only to that hospital
"""
class Meta:
model = QIProject
fields = [
'name', 'name_ar', 'description', 'hospital',
'department', 'target_completion_date'
]
widgets = {
'name': forms.TextInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'placeholder': _('Template name')
}),
'name_ar': forms.TextInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'placeholder': _('اسم القالب'),
'dir': 'rtl'
}),
'description': forms.Textarea(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm resize-none',
'rows': 4,
'placeholder': _('Describe the project template...')
}),
'hospital': forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
}),
'department': forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
}),
'target_completion_date': forms.DateInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'type': 'date'
}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make hospital optional for templates (global templates)
self.fields['hospital'].required = False
self.fields['hospital'].empty_label = _('Global (All Hospitals)')
# Filter department choices based on hospital
hospital_id = None
if self.data.get('hospital'):
hospital_id = self.data.get('hospital')
elif self.initial.get('hospital'):
hospital_id = self.initial.get('hospital')
elif self.instance and self.instance.pk and self.instance.hospital:
hospital_id = self.instance.hospital.id
if hospital_id:
self.fields['department'].queryset = Department.objects.filter(
hospital_id=hospital_id,
status='active'
).order_by('name')
else:
self.fields['department'].queryset = Department.objects.none()
class ConvertToProjectForm(forms.Form):
"""
Form for converting a PX Action to a QI Project.
Allows selecting a template and customizing the project details.
"""
template = forms.ModelChoiceField(
queryset=QIProject.objects.none(),
required=False,
empty_label=_('Blank Project'),
label=_('Project Template'),
widget=forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
})
)
project_name = forms.CharField(
max_length=200,
label=_('Project Name'),
widget=forms.TextInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'placeholder': _('Enter project name')
})
)
project_lead = forms.ModelChoiceField(
queryset=User.objects.none(),
required=True,
label=_('Project Lead'),
widget=forms.Select(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white'
})
)
target_completion_date = forms.DateField(
required=False,
label=_('Target Completion Date'),
widget=forms.DateInput(attrs={
'class': 'w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm',
'type': 'date'
})
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
self.action = kwargs.pop('action', None)
super().__init__(*args, **kwargs)
if self.user and self.user.hospital:
# Filter templates by hospital (or global)
from django.db.models import Q
self.fields['template'].queryset = QIProject.objects.filter(
Q(hospital=self.user.hospital) | Q(hospital__isnull=True),
status='template' # We'll add this status or use metadata
).order_by('name')
# Filter project lead by hospital
self.fields['project_lead'].queryset = User.objects.filter(
hospital=self.user.hospital,
is_active=True
).order_by('first_name', 'last_name')
else:
self.fields['template'].queryset = QIProject.objects.none()
self.fields['project_lead'].queryset = User.objects.none()