""" Forms for core app. """ from django import forms from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm, PasswordChangeForm from django.utils.translation import gettext_lazy as _ from .models import TenantSetting, SettingTemplate, Patient, Consent, ConsentTemplate, User class PatientForm(forms.ModelForm): """Form for creating and updating patients.""" class Meta: model = Patient fields = [ 'first_name_en', 'father_name_en', 'grandfather_name_en', 'last_name_en', 'first_name_ar', 'father_name_ar', 'grandfather_name_ar', 'last_name_ar', 'national_id', 'date_of_birth', 'sex', 'phone', 'email', 'address', 'city', 'postal_code', 'caregiver_name', 'caregiver_phone', 'caregiver_relationship', 'emergency_contact' ] labels = { 'first_name_en': _('First Name (English)'), 'father_name_en': _("Father's Name (English)"), 'grandfather_name_en': _("Grandfather's Name (English)"), 'last_name_en': _('Last Name (English)'), 'first_name_ar': _('First Name (Arabic)'), 'father_name_ar': _("Father's Name (Arabic)"), 'grandfather_name_ar': _("Grandfather's Name (Arabic)"), 'last_name_ar': _('Last Name (Arabic)'), 'national_id': _('National ID'), 'date_of_birth': _('Date of Birth'), 'sex': _('Gender'), 'phone': _('Phone Number'), 'email': _('Email Address'), 'address': _('Address'), 'city': _('City'), 'postal_code': _('Postal Code'), 'caregiver_name': _('Caregiver Name'), 'caregiver_phone': _('Caregiver Phone'), 'caregiver_relationship': _('Relationship to Patient'), 'emergency_contact': _('Emergency Contact Information'), } widgets = { 'date_of_birth': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'first_name_en': forms.TextInput(attrs={'class': 'form-control'}), 'father_name_en': forms.TextInput(attrs={'class': 'form-control'}), 'grandfather_name_en': forms.TextInput(attrs={'class': 'form-control'}), 'last_name_en': forms.TextInput(attrs={'class': 'form-control'}), 'first_name_ar': forms.TextInput(attrs={'class': 'form-control'}), 'father_name_ar': forms.TextInput(attrs={'class': 'form-control'}), 'grandfather_name_ar': forms.TextInput(attrs={'class': 'form-control'}), 'last_name_ar': forms.TextInput(attrs={'class': 'form-control'}), 'national_id': forms.TextInput(attrs={'class': 'form-control'}), 'sex': forms.Select(attrs={'class': 'form-select'}), 'phone': forms.TextInput(attrs={'class': 'form-control'}), 'email': forms.EmailInput(attrs={'class': 'form-control'}), 'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'city': forms.TextInput(attrs={'class': 'form-control'}), 'postal_code': forms.TextInput(attrs={'class': 'form-control'}), 'caregiver_name': forms.TextInput(attrs={'class': 'form-control'}), 'caregiver_phone': forms.TextInput(attrs={'class': 'form-control'}), 'caregiver_relationship': forms.TextInput(attrs={'class': 'form-control'}), 'emergency_contact': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } class ConsentForm(forms.ModelForm): """Form for creating consents with digital signature support.""" # Additional field for signature data (base64) signature_data = forms.CharField( required=False, widget=forms.HiddenInput() ) class Meta: model = Consent fields = ['patient', 'consent_type', 'content_text', 'signed_by_name', 'signed_by_relationship', 'signature_method'] widgets = { 'patient': forms.Select(attrs={'class': 'form-select select2'}), 'consent_type': forms.Select(attrs={'class': 'form-select'}), 'content_text': forms.HiddenInput(), # Hidden, populated via JS 'signed_by_name': forms.TextInput(attrs={'class': 'form-control'}), 'signed_by_relationship': forms.TextInput(attrs={'class': 'form-control'}), 'signature_method': forms.HiddenInput(), # Hidden, set to DRAWN } def clean_signature_data(self): """Validate signature data.""" signature_data = self.cleaned_data.get('signature_data') if not signature_data: raise forms.ValidationError('Signature is required.') # Validate base64 format if not signature_data.startswith('data:image/png;base64,'): raise forms.ValidationError('Invalid signature format.') return signature_data def save(self, commit=True): """Save consent and process signature image.""" import base64 from django.core.files.base import ContentFile from django.utils import timezone import hashlib instance = super().save(commit=False) # Process signature data signature_data = self.cleaned_data.get('signature_data') if signature_data: # Extract base64 data format, imgstr = signature_data.split(';base64,') ext = format.split('/')[-1] # Decode base64 to binary signature_binary = base64.b64decode(imgstr) # Generate filename timestamp = timezone.now().strftime('%Y%m%d_%H%M%S') filename = f'signature_{instance.patient.mrn}_{timestamp}.{ext}' # Save to signature_image field instance.signature_image.save( filename, ContentFile(signature_binary), save=False ) # Calculate signature hash for verification instance.signature_hash = hashlib.sha256(signature_binary).hexdigest() # Set signed_at timestamp if not instance.signed_at: instance.signed_at = timezone.now() if commit: instance.save() return instance class PatientSearchForm(forms.Form): """Form for searching patients.""" search = forms.CharField( required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Search by name, MRN, phone, or national ID...') }) ) status = forms.ChoiceField( required=False, choices=[('', _('All')), ('active', _('Active')), ('inactive', _('Inactive'))], widget=forms.Select(attrs={'class': 'form-select'}) ) gender = forms.ChoiceField( required=False, choices=[('', _('All'))] + list(Patient.Sex.choices), widget=forms.Select(attrs={'class': 'form-select'}) ) class TenantSettingsForm(forms.Form): """ Dynamic form for tenant settings based on templates. """ def __init__(self, *args, **kwargs): self.tenant = kwargs.pop('tenant', None) self.category = kwargs.pop('category', None) self.user = kwargs.pop('user', None) super().__init__(*args, **kwargs) # Get templates for this category templates = SettingTemplate.objects.filter( category=self.category, is_active=True ).order_by('order') # Create form fields dynamically for template in templates: field = self._create_field_for_template(template) self.fields[template.key] = field # Set initial value if exists try: tenant_setting = TenantSetting.objects.get( tenant=self.tenant, template=template ) if template.data_type == SettingTemplate.DataType.FILE: self.initial[template.key] = tenant_setting.file_value elif template.data_type == SettingTemplate.DataType.ENCRYPTED: # Show placeholder for encrypted values if tenant_setting.encrypted_value: self.initial[template.key] = '***ENCRYPTED***' elif template.data_type == SettingTemplate.DataType.BOOLEAN: self.initial[template.key] = tenant_setting.value.lower() in ('true', '1', 'yes') else: self.initial[template.key] = tenant_setting.value except TenantSetting.DoesNotExist: if template.default_value: self.initial[template.key] = template.default_value def _create_field_for_template(self, template): """Create appropriate form field based on template data type.""" label = template.label_en required = template.is_required help_text = template.help_text_en if template.data_type == SettingTemplate.DataType.STRING: return forms.CharField( label=label, required=required, help_text=help_text, max_length=500, widget=forms.TextInput(attrs={'class': 'form-control'}) ) elif template.data_type == SettingTemplate.DataType.TEXT: return forms.CharField( label=label, required=required, help_text=help_text, widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3}) ) elif template.data_type == SettingTemplate.DataType.INTEGER: return forms.IntegerField( label=label, required=required, help_text=help_text, widget=forms.NumberInput(attrs={'class': 'form-control'}) ) elif template.data_type == SettingTemplate.DataType.BOOLEAN: return forms.BooleanField( label=label, required=False, help_text=help_text, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) ) elif template.data_type == SettingTemplate.DataType.CHOICE: choices = [(choice['value'], choice['label']) for choice in template.choices] return forms.ChoiceField( label=label, required=required, help_text=help_text, choices=choices, widget=forms.Select(attrs={'class': 'form-select'}) ) elif template.data_type == SettingTemplate.DataType.FILE: return forms.FileField( label=label, required=False, help_text=help_text, widget=forms.FileInput(attrs={'class': 'form-control'}) ) elif template.data_type == SettingTemplate.DataType.ENCRYPTED: return forms.CharField( label=label, required=False, help_text=help_text + ' (Leave blank to keep current value)', widget=forms.PasswordInput(attrs={'class': 'form-control'}) ) elif template.data_type == SettingTemplate.DataType.EMAIL: return forms.EmailField( label=label, required=required, help_text=help_text, widget=forms.EmailInput(attrs={'class': 'form-control'}) ) elif template.data_type == SettingTemplate.DataType.URL: return forms.URLField( label=label, required=required, help_text=help_text, widget=forms.URLInput(attrs={'class': 'form-control'}) ) elif template.data_type == SettingTemplate.DataType.PHONE: return forms.CharField( label=label, required=required, help_text=help_text, widget=forms.TextInput(attrs={'class': 'form-control', 'type': 'tel'}) ) elif template.data_type == SettingTemplate.DataType.COLOR: return forms.CharField( label=label, required=required, help_text=help_text, widget=forms.TextInput(attrs={'class': 'form-control', 'type': 'color'}) ) else: return forms.CharField( label=label, required=required, help_text=help_text, widget=forms.TextInput(attrs={'class': 'form-control'}) ) def save(self): """Save form data to tenant settings.""" from .settings_service import get_tenant_settings_service service = get_tenant_settings_service(self.tenant) saved_count = 0 for key, value in self.cleaned_data.items(): # Skip encrypted fields with placeholder value if value == '***ENCRYPTED***': continue # Skip empty encrypted fields (keep existing value) template = SettingTemplate.objects.get(key=key) if template.data_type == SettingTemplate.DataType.ENCRYPTED and not value: continue try: service.set_setting(key, value, self.user) saved_count += 1 except Exception as e: # Log error but continue print(f"Error saving setting {key}: {e}") return saved_count # ============================================================================ # USER PROFILE FORMS # ============================================================================ class UserProfileForm(forms.ModelForm): """ Form for users to edit their own profile. """ class Meta: model = User fields = [ 'first_name', 'last_name', 'email', 'phone_number', 'profile_picture', 'bio', 'timezone' ] widgets = { 'first_name': forms.TextInput(attrs={'class': 'form-control'}), 'last_name': forms.TextInput(attrs={'class': 'form-control'}), 'email': forms.EmailInput(attrs={'class': 'form-control'}), 'phone_number': forms.TextInput(attrs={'class': 'form-control'}), 'profile_picture': forms.FileInput(attrs={'class': 'form-control'}), 'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}), 'timezone': forms.Select(attrs={'class': 'form-select'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Add timezone choices self.fields['timezone'].widget.choices = [ ('Asia/Riyadh', _('Riyadh (GMT+3)')), ('Asia/Dubai', _('Dubai (GMT+4)')), ('Asia/Kuwait', _('Kuwait (GMT+3)')), ('Asia/Bahrain', _('Bahrain (GMT+3)')), ('Asia/Qatar', _('Qatar (GMT+3)')), ('UTC', _('UTC (GMT+0)')), ] class UserPreferencesForm(forms.Form): """ Form for user preferences (stored in JSON field). """ language = forms.ChoiceField( label=_("Preferred Language"), choices=[('en', _('English')), ('ar', _('Arabic'))], widget=forms.Select(attrs={'class': 'form-select'}), required=False ) email_notifications = forms.BooleanField( label=_("Email Notifications"), required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) ) sms_notifications = forms.BooleanField( label=_("SMS Notifications"), required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) ) appointment_reminders = forms.BooleanField( label=_("Appointment Reminders"), required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) ) dashboard_layout = forms.ChoiceField( label=_("Dashboard Layout"), choices=[ ('default', _('Default')), ('compact', _('Compact')), ('detailed', _('Detailed')) ], widget=forms.Select(attrs={'class': 'form-select'}), required=False ) def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) super().__init__(*args, **kwargs) # Load current preferences if self.user and self.user.preferences: for field_name in self.fields: if field_name in self.user.preferences: self.initial[field_name] = self.user.preferences[field_name] def save(self): """Save preferences to user's preferences JSON field.""" if not self.user: return # Update preferences if not self.user.preferences: self.user.preferences = {} for field_name, value in self.cleaned_data.items(): self.user.preferences[field_name] = value self.user.save(update_fields=['preferences']) class UserPasswordChangeForm(PasswordChangeForm): """ Custom password change form with Bootstrap styling. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Add Bootstrap classes for field_name in self.fields: self.fields[field_name].widget.attrs['class'] = 'form-control' class UserAdminForm(forms.ModelForm): """ Form for admins to create/edit staff members. """ class Meta: model = User fields = [ 'username', 'first_name', 'last_name', 'email', 'phone_number', 'employee_id', 'role', 'is_active', 'is_staff', 'profile_picture', 'bio' ] widgets = { 'username': forms.TextInput(attrs={'class': 'form-control'}), 'first_name': forms.TextInput(attrs={'class': 'form-control'}), 'last_name': forms.TextInput(attrs={'class': 'form-control'}), 'email': forms.EmailInput(attrs={'class': 'form-control'}), 'phone_number': forms.TextInput(attrs={'class': 'form-control'}), 'employee_id': forms.TextInput(attrs={'class': 'form-control'}), 'role': forms.Select(attrs={'class': 'form-select'}), 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'is_staff': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'profile_picture': forms.FileInput(attrs={'class': 'form-control'}), 'bio': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}), } password1 = forms.CharField( label=_("Password"), required=False, widget=forms.PasswordInput(attrs={'class': 'form-control'}), help_text=_("Leave blank to keep current password (for updates)") ) password2 = forms.CharField( label=_("Password Confirmation"), required=False, widget=forms.PasswordInput(attrs={'class': 'form-control'}), help_text=_("Enter the same password as before, for verification") ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Make password required for new users if not self.instance.pk: self.fields['password1'].required = True self.fields['password2'].required = True def clean_password2(self): """Validate that passwords match.""" password1 = self.cleaned_data.get('password1') password2 = self.cleaned_data.get('password2') if password1 and password2 and password1 != password2: raise forms.ValidationError(_("Passwords don't match")) return password2 def save(self, commit=True): """Save user with password if provided.""" user = super().save(commit=False) # Set password if provided password = self.cleaned_data.get('password1') if password: user.set_password(password) if commit: user.save() return user class UserSearchForm(forms.Form): """ Form for searching staff members. """ search = forms.CharField( required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Search by name, email, or employee ID...') }) ) role = forms.ChoiceField( required=False, choices=[('', _('All Roles'))] + list(User.Role.choices), widget=forms.Select(attrs={'class': 'form-select'}) ) status = forms.ChoiceField( required=False, choices=[ ('', _('All')), ('active', _('Active')), ('inactive', _('Inactive')) ], widget=forms.Select(attrs={'class': 'form-select'}) ) # ============================================================================ # USER SIGNUP FORM # ============================================================================ class ConsentTemplateForm(forms.ModelForm): """Form for creating and updating consent templates.""" class Meta: model = ConsentTemplate fields = [ 'consent_type', 'title_en', 'title_ar', 'content_en', 'content_ar', 'is_active', 'version' ] widgets = { 'consent_type': forms.Select(attrs={'class': 'form-select'}), 'title_en': forms.TextInput(attrs={'class': 'form-control'}), 'title_ar': forms.TextInput(attrs={'class': 'form-control'}), 'content_en': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 10, 'placeholder': _('Use placeholders: {patient_name}, {patient_mrn}, {date}, {patient_dob}, {patient_age}') }), 'content_ar': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 10, 'placeholder': _('Use placeholders: {patient_name}, {patient_mrn}, {date}, {patient_dob}, {patient_age}') }), 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'version': forms.NumberInput(attrs={'class': 'form-control'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Make version read-only for updates if self.instance.pk: self.fields['version'].disabled = True self.fields['version'].help_text = _('Version is auto-incremented on save') class UserSignupForm(BaseUserCreationForm): """ Custom user signup form with additional fields. """ email = forms.EmailField( label=_("Email Address"), required=True, widget=forms.EmailInput(attrs={'class': 'form-control fs-13px h-45px'}) ) first_name = forms.CharField( label=_("First Name"), required=False, max_length=150, widget=forms.TextInput(attrs={'class': 'form-control fs-13px h-45px'}) ) last_name = forms.CharField( label=_("Last Name"), required=False, max_length=150, widget=forms.TextInput(attrs={'class': 'form-control fs-13px h-45px'}) ) class Meta: model = User fields = ('username', 'email', 'first_name', 'last_name', 'password1', 'password2') widgets = { 'username': forms.TextInput(attrs={'class': 'form-control fs-13px h-45px'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Add Bootstrap classes to password fields self.fields['password1'].widget.attrs['class'] = 'form-control fs-13px h-45px' self.fields['password2'].widget.attrs['class'] = 'form-control fs-13px h-45px' def clean_email(self): """Validate that email is unique.""" email = self.cleaned_data.get('email') if User.objects.filter(email=email).exists(): raise forms.ValidationError(_("A user with this email already exists.")) return email def save(self, commit=True): """Save user with email.""" user = super().save(commit=False) user.email = self.cleaned_data['email'] user.first_name = self.cleaned_data.get('first_name', '') user.last_name = self.cleaned_data.get('last_name', '') if commit: user.save() return user