""" Forms for the notifications app. """ from django import forms from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError from .models import MessageTemplate, Message, NotificationPreference class MessageTemplateForm(forms.ModelForm): """Form for creating/editing message templates.""" class Meta: model = MessageTemplate fields = [ 'code', 'name', 'channel', 'subject', 'body_en', 'body_ar', 'variables', 'is_active' ] widgets = { 'code': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('e.g., appointment_reminder') }), 'name': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Template Name') }), 'channel': forms.Select(attrs={ 'class': 'form-select' }), 'subject': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Email subject (optional)') }), 'body_en': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 6, 'placeholder': _('Message body in English. Use {variable_name} for variables.') }), 'body_ar': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 6, 'placeholder': _('Message body in Arabic (optional)') }), 'variables': forms.Textarea(attrs={ 'class': 'form-control', 'rows': 3, 'placeholder': _('["patient_name", "appointment_date", "clinic_name"]') }), 'is_active': forms.CheckboxInput(attrs={ 'class': 'form-check-input' }), } def clean_variables(self): """Validate and parse variables JSON.""" import json variables = self.cleaned_data.get('variables') if isinstance(variables, str): try: variables = json.loads(variables) except json.JSONDecodeError: raise ValidationError(_('Invalid JSON format for variables')) if not isinstance(variables, list): raise ValidationError(_('Variables must be a list')) return variables def clean_code(self): """Ensure code is unique.""" code = self.cleaned_data.get('code') # Check if code already exists (excluding current instance) qs = MessageTemplate.objects.filter(code=code) if self.instance.pk: qs = qs.exclude(pk=self.instance.pk) if qs.exists(): raise ValidationError(_('A template with this code already exists')) return code class MessageFilterForm(forms.Form): """Form for filtering messages.""" search = forms.CharField( required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Search by recipient or content...') }) ) channel = forms.ChoiceField( required=False, choices=[('', _('All Channels'))] + list(Message.Channel.choices), widget=forms.Select(attrs={ 'class': 'form-select' }) ) status = forms.ChoiceField( required=False, choices=[('', _('All Statuses'))] + list(Message.Status.choices), widget=forms.Select(attrs={ 'class': 'form-select' }) ) date_from = forms.DateField( required=False, widget=forms.DateInput(attrs={ 'class': 'form-control', 'type': 'date' }) ) date_to = forms.DateField( required=False, widget=forms.DateInput(attrs={ 'class': 'form-control', 'type': 'date' }) ) template = forms.ModelChoiceField( queryset=MessageTemplate.objects.none(), required=False, widget=forms.Select(attrs={ 'class': 'form-select' }), empty_label=_('All Templates') ) def __init__(self, *args, tenant=None, **kwargs): super().__init__(*args, **kwargs) if tenant: self.fields['template'].queryset = MessageTemplate.objects.filter( tenant=tenant ).order_by('name') class BulkMessageForm(forms.Form): """Form for sending bulk messages.""" channel = forms.ChoiceField( label=_('Channel'), choices=Message.Channel.choices, widget=forms.Select(attrs={ 'class': 'form-select' }) ) use_template = forms.BooleanField( label=_('Use Template'), required=False, initial=True, widget=forms.CheckboxInput(attrs={ 'class': 'form-check-input', 'id': 'useTemplateCheckbox' }) ) template = forms.ModelChoiceField( label=_('Template'), queryset=MessageTemplate.objects.none(), required=False, widget=forms.Select(attrs={ 'class': 'form-select select2', 'id': 'templateSelect' }) ) subject = forms.CharField( label=_('Subject'), required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Email subject (for email channel only)') }) ) message = forms.CharField( label=_('Message'), required=False, widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 6, 'placeholder': _('Enter your message here...') }) ) recipient_filter = forms.ChoiceField( label=_('Recipients'), choices=[ ('all', _('All Patients')), ('tags', _('By Tags')), ('custom', _('Custom List')), ], widget=forms.RadioSelect(attrs={ 'class': 'form-check-input' }) ) tags = forms.CharField( label=_('Patient Tags'), required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Enter tags separated by commas') }) ) recipients = forms.CharField( label=_('Recipients'), required=False, widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 4, 'placeholder': _('Enter phone numbers or emails, one per line') }) ) def __init__(self, *args, tenant=None, **kwargs): super().__init__(*args, **kwargs) if tenant: self.fields['template'].queryset = MessageTemplate.objects.filter( tenant=tenant, is_active=True ).order_by('name') def clean(self): cleaned_data = super().clean() use_template = cleaned_data.get('use_template') template = cleaned_data.get('template') message = cleaned_data.get('message') recipient_filter = cleaned_data.get('recipient_filter') tags = cleaned_data.get('tags') recipients = cleaned_data.get('recipients') # Validate message content if use_template and not template: raise ValidationError(_('Please select a template')) if not use_template and not message: raise ValidationError(_('Please enter a message')) # Validate recipients if recipient_filter == 'tags' and not tags: raise ValidationError(_('Please enter at least one tag')) if recipient_filter == 'custom' and not recipients: raise ValidationError(_('Please enter at least one recipient')) return cleaned_data class TestTemplateForm(forms.Form): """Form for testing message templates.""" template = forms.ModelChoiceField( label=_('Template'), queryset=MessageTemplate.objects.none(), widget=forms.Select(attrs={ 'class': 'form-select select2' }) ) test_recipient = forms.CharField( label=_('Test Recipient'), widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Phone number or email') }) ) language = forms.ChoiceField( label=_('Language'), choices=[('en', _('English')), ('ar', _('Arabic'))], widget=forms.Select(attrs={ 'class': 'form-select' }) ) variables = forms.CharField( label=_('Variables (JSON)'), required=False, widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 4, 'placeholder': _('{"patient_name": "Ahmed", "appointment_date": "2025-10-15"}') }) ) def __init__(self, *args, tenant=None, **kwargs): super().__init__(*args, **kwargs) if tenant: self.fields['template'].queryset = MessageTemplate.objects.filter( tenant=tenant, is_active=True ).order_by('name') def clean_variables(self): """Validate and parse variables JSON.""" import json variables = self.cleaned_data.get('variables') if not variables: return {} try: variables = json.loads(variables) except json.JSONDecodeError: raise ValidationError(_('Invalid JSON format')) if not isinstance(variables, dict): raise ValidationError(_('Variables must be a JSON object')) return variables class MessageRetryForm(forms.Form): """Form for retrying failed messages.""" message_ids = forms.CharField( widget=forms.HiddenInput() ) def clean_message_ids(self): """Parse and validate message IDs.""" import json message_ids = self.cleaned_data.get('message_ids') try: message_ids = json.loads(message_ids) except json.JSONDecodeError: raise ValidationError(_('Invalid message IDs')) if not isinstance(message_ids, list): raise ValidationError(_('Message IDs must be a list')) return message_ids class BroadcastNotificationForm(forms.Form): """Form for creating broadcast notifications (general or role-based).""" BROADCAST_TYPE_CHOICES = [ ('general', _('General (All Users)')), ('role_based', _('Role-Based (Specific Roles)')), ] broadcast_type = forms.ChoiceField( label=_('Broadcast Type'), choices=BROADCAST_TYPE_CHOICES, widget=forms.RadioSelect(attrs={ 'class': 'form-check-input' }), initial='general' ) target_roles = forms.MultipleChoiceField( label=_('Target Roles'), choices=[], # Will be populated in __init__ required=False, widget=forms.CheckboxSelectMultiple(attrs={ 'class': 'form-check-input' }), help_text=_('Select which user roles should see this notification') ) title = forms.CharField( label=_('Title'), max_length=200, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Notification title') }) ) message = forms.CharField( label=_('Message'), widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 5, 'placeholder': _('Notification message') }) ) notification_type = forms.ChoiceField( label=_('Type'), choices=[], # Will be populated in __init__ widget=forms.Select(attrs={ 'class': 'form-select' }), initial='INFO' ) action_url = forms.CharField( label=_('Action URL'), required=False, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': _('Optional URL to navigate to when clicked') }), help_text=_('Leave empty if no action is needed') ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Import here to avoid circular imports from .models import Notification from core.models import User # Set notification type choices self.fields['notification_type'].choices = Notification.NotificationType.choices # Set role choices self.fields['target_roles'].choices = User.Role.choices def clean(self): cleaned_data = super().clean() broadcast_type = cleaned_data.get('broadcast_type') target_roles = cleaned_data.get('target_roles') # Validate that roles are selected for role-based notifications if broadcast_type == 'role_based' and not target_roles: raise ValidationError({ 'target_roles': _('Please select at least one role for role-based notifications') }) return cleaned_data