1010from eventyay .base .email import get_available_placeholders
1111from eventyay .base .forms import PlaceholderValidator , SettingsForm
1212from eventyay .base .forms .widgets import SplitDateTimePickerWidget
13- from eventyay .base .models import CheckinList , Product , Order , SubEvent
13+ from eventyay .base .models .base import CachedFile
14+ from eventyay .base .models .checkin import CheckinList
15+ from eventyay .base .models .event import SubEvent
16+ from eventyay .base .models .product import Product
17+ from eventyay .base .models .organizer import Team
18+ from eventyay .base .models .orders import Order
1419from eventyay .control .forms import CachedFileField
1520from eventyay .control .forms .widgets import Select2 , Select2Multiple
21+ from eventyay .plugins .sendmail .models import ComposingFor , EmailQueue , EmailQueueToUser
22+
1623
1724MAIL_SEND_ORDER_PLACED_ATTENDEE_HELP = _ ( 'If the order contains attendees with email addresses different from the person who orders the ' 'tickets, the following email will be sent out to the attendees.' )
1825
@@ -22,7 +29,7 @@ def contains_web_channel_validate(value):
2229
2330class MailForm (forms .Form ):
2431 recipients = forms .ChoiceField (label = _ ('Send email to' ), widget = forms .RadioSelect , initial = 'orders' , choices = [])
25- sendto = forms .MultipleChoiceField () # overridden later
32+ order_status = forms .MultipleChoiceField () # overridden later
2633 subject = forms .CharField (label = _ ('Subject' ))
2734 message = forms .CharField (label = _ ('Message' ))
2835 attachment = CachedFileField (
@@ -63,7 +70,7 @@ class MailForm(forms.Form):
6370 required = True ,
6471 queryset = Product .objects .none (),
6572 )
66- filter_checkins = forms .BooleanField (label = _ ('Filter check-in status' ), required = False )
73+ has_filter_checkins = forms .BooleanField (label = _ ('Filter check-in status' ), required = False )
6774 checkin_lists = SafeModelMultipleChoiceField (
6875 queryset = CheckinList .objects .none (), required = False
6976 ) # overridden later
@@ -84,12 +91,12 @@ class MailForm(forms.Form):
8491 label = pgettext_lazy ('subevent' , 'Only send to customers of dates starting before' ),
8592 required = False ,
8693 )
87- created_from = forms .SplitDateTimeField (
94+ order_created_from = forms .SplitDateTimeField (
8895 widget = SplitDateTimePickerWidget (),
8996 label = pgettext_lazy ('subevent' , 'Only send to customers with orders created after' ),
9097 required = False ,
9198 )
92- created_to = forms .SplitDateTimeField (
99+ order_created_to = forms .SplitDateTimeField (
93100 widget = SplitDateTimePickerWidget (),
94101 label = pgettext_lazy ('subevent' , 'Only send to customers with orders created before' ),
95102 required = False ,
@@ -159,16 +166,16 @@ def __init__(self, *args, **kwargs):
159166 choices .insert (0 , ('pa' , _ ('approval pending' )))
160167 if not event .settings .get ('payment_term_expire_automatically' , as_type = bool ):
161168 choices .append (('overdue' , _ ('pending with payment overdue' )))
162- self .fields ['sendto ' ] = forms .MultipleChoiceField (
169+ self .fields ['order_status ' ] = forms .MultipleChoiceField (
163170 label = _ ('Send to customers with order status' ),
164171 widget = forms .CheckboxSelectMultiple (attrs = {'class' : 'scrolling-multiple-choice' }),
165172 choices = choices ,
166173 )
167- if not self .initial .get ('sendto ' ):
168- self .initial ['sendto ' ] = ['p' , 'na' ]
169- elif 'n' in self .initial ['sendto ' ]:
170- self .initial ['sendto ' ].append ('pa' )
171- self .initial ['sendto ' ].append ('na' )
174+ if not self .initial .get ('order_status ' ):
175+ self .initial ['order_status ' ] = ['p' , 'na' ]
176+ elif 'n' in self .initial ['order_status ' ]:
177+ self .initial ['order_status ' ].append ('pa' )
178+ self .initial ['order_status ' ].append ('na' )
172179
173180 self .fields ['products' ].queryset = event .products .all ()
174181 if not self .initial .get ('products' ):
@@ -212,6 +219,7 @@ def __init__(self, *args, **kwargs):
212219 del self .fields ['subevents_from' ]
213220 del self .fields ['subevents_to' ]
214221
222+
215223class MailContentSettingsForm (SettingsForm ):
216224 mail_text_order_placed = I18nFormField (
217225 label = _ ('Text sent to order contact address' ),
@@ -411,3 +419,179 @@ def __init__(self, *args, **kwargs):
411419 for k , v in self .base_context .items ():
412420 if k in self .fields :
413421 self ._set_field_placeholders (k , v )
422+
423+
424+ class EmailQueueEditForm (forms .ModelForm ):
425+ new_attachment = forms .FileField (
426+ required = False ,
427+ label = _ ("New attachment" ),
428+ help_text = _ ("Upload a new file to replace the existing one." )
429+ )
430+
431+ emails = forms .CharField (
432+ label = _ ("Recipients" ),
433+ help_text = _ ("Edit the list of recipient email addresses separated by commas." ),
434+ required = True ,
435+ widget = forms .Textarea (attrs = {'rows' : 2 , 'class' : 'form-control' })
436+ )
437+
438+ class Meta :
439+ model = EmailQueue
440+ fields = [
441+ 'reply_to' ,
442+ 'bcc' ,
443+ ]
444+ labels = {
445+ 'reply_to' : _ ('Reply-To' ),
446+ 'bcc' : _ ('BCC' ),
447+ }
448+ help_texts = {
449+ 'reply_to' : _ ("Any changes to the Reply-To field will apply only to this queued email." ),
450+ 'bcc' : _ ("Any changes to the BCC field will apply only to this queued email." ),
451+ }
452+ widgets = {
453+ 'reply_to' : forms .TextInput (attrs = {'class' : 'form-control' }),
454+ 'bcc' : forms .Textarea (attrs = {'class' : 'form-control' , 'rows' : 1 }),
455+ }
456+
457+ def __init__ (self , * args , ** kwargs ):
458+ self .event = kwargs .pop ('event' , None )
459+ self .read_only = kwargs .pop ('read_only' , False )
460+ super ().__init__ (* args , ** kwargs )
461+
462+ if self .instance .composing_for == ComposingFor .TEAMS :
463+ base_placeholders = ['event' , 'team' ]
464+ else :
465+ base_placeholders = ['event' , 'order' , 'position_or_address' ]
466+
467+ existing_recipients = EmailQueueToUser .objects .filter (mail = self .instance ).order_by ('id' )
468+ self .recipient_objects = list (existing_recipients )
469+ self .fields ['emails' ].initial = ", " .join ([u .email for u in self .recipient_objects ])
470+
471+ saved_locales = set ()
472+ if self .instance .subject and hasattr (self .instance .subject , '_data' ):
473+ saved_locales |= set (self .instance .subject ._data .keys ())
474+ if self .instance .message and hasattr (self .instance .message , '_data' ):
475+ saved_locales |= set (self .instance .message ._data .keys ())
476+
477+ configured_locales = set (self .event .settings .get ('locales' , [])) if self .event else set ()
478+ allowed_locales = saved_locales | configured_locales
479+
480+ self .fields ['subject' ] = I18nFormField (
481+ label = _ ('Subject' ),
482+ widget = I18nTextInput ,
483+ required = False ,
484+ locales = list (allowed_locales ),
485+ initial = self .instance .subject
486+ )
487+ self .fields ['message' ] = I18nFormField (
488+ label = _ ('Message' ),
489+ widget = I18nTextarea ,
490+ required = False ,
491+ locales = list (allowed_locales ),
492+ initial = self .instance .message
493+ )
494+
495+ if not self .read_only :
496+ self ._set_field_placeholders ('subject' , base_placeholders )
497+ self ._set_field_placeholders ('message' , base_placeholders )
498+
499+ def _set_field_placeholders (self , fn , base_parameters ):
500+ phs = ['{%s}' % p for p in sorted (get_available_placeholders (self .event , base_parameters ).keys ())]
501+ ht = _ ('Available placeholders: {list}' ).format (list = ', ' .join (phs ))
502+ if self .fields [fn ].help_text :
503+ self .fields [fn ].help_text += ' ' + str (ht )
504+ else :
505+ self .fields [fn ].help_text = ht
506+ self .fields [fn ].validators .append (PlaceholderValidator (phs ))
507+
508+ def clean_emails (self ):
509+ updated_emails = [
510+ email .strip ()
511+ for email in self .cleaned_data ['emails' ].split (',' )
512+ if email .strip ()
513+ ]
514+
515+ if len (updated_emails ) == 0 :
516+ raise ValidationError (
517+ _ ("At least one recipient must remain. You cannot remove all recipients." )
518+ )
519+
520+ if len (updated_emails ) != len (self .recipient_objects ):
521+ raise ValidationError (
522+ _ ("You cannot add new recipients or remove recipients. Only editing existing email addresses is allowed." )
523+ )
524+
525+ return updated_emails
526+
527+ def save (self , commit = True ):
528+ instance = super ().save (commit = False )
529+
530+ updated_emails = self .cleaned_data ['emails' ]
531+
532+ for i , email in enumerate (updated_emails ):
533+ self .recipient_objects [i ].email = email
534+ if commit :
535+ self .recipient_objects [i ].save ()
536+
537+ # Handle new attachment
538+ if self .cleaned_data .get ('new_attachment' ):
539+ uploaded_file = self .cleaned_data ['new_attachment' ]
540+ cf = CachedFile .objects .create (file = uploaded_file , filename = uploaded_file .name )
541+ instance .attachments = [cf .id ]
542+
543+ instance .subject = self .cleaned_data ['subject' ]
544+ instance .message = self .cleaned_data ['message' ]
545+
546+ if commit :
547+ instance .save ()
548+
549+ return instance
550+
551+
552+ class TeamMailForm (forms .Form ):
553+ attachment = CachedFileField (
554+ label = _ ('Attachment' ),
555+ required = False ,
556+ ext_whitelist = (
557+ '.png' , '.jpg' , '.gif' , '.jpeg' , '.pdf' , '.txt' , '.docx' , '.svg' , '.pptx' ,
558+ '.ppt' , '.doc' , '.xlsx' , '.xls' , '.jfif' , '.heic' , '.heif' , '.pages' , '.bmp' ,
559+ '.tif' , '.tiff' ,
560+ ),
561+ help_text = _ (
562+ 'Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. '
563+ 'We recommend only using PDFs of no more than 2 MB in size.'
564+ ),
565+ max_size = 10 * 1024 * 1024 ,
566+ )
567+
568+ def __init__ (self , * args , ** kwargs ):
569+ self .event = kwargs .pop ('event' )
570+ super ().__init__ (* args , ** kwargs )
571+
572+ locales = self .event .settings .get ('locales' ) or [self .event .locale or 'en' ]
573+ if isinstance (locales , str ):
574+ locales = [locales ]
575+
576+ placeholder_keys = get_available_placeholders (self .event , ['event' , 'team' ]).keys ()
577+ placeholder_text = _ ("Available placeholders: " ) + ', ' .join (f"{{{ key } }}" for key in sorted (placeholder_keys ))
578+
579+ self .fields ['subject' ] = I18nFormField (
580+ label = _ ('Subject' ),
581+ widget = I18nTextInput ,
582+ required = True ,
583+ locales = locales ,
584+ help_text = placeholder_text
585+ )
586+ self .fields ['message' ] = I18nFormField (
587+ label = _ ('Message' ),
588+ widget = I18nTextarea ,
589+ required = True ,
590+ locales = locales ,
591+ help_text = placeholder_text
592+ )
593+ self .fields ['teams' ] = forms .ModelMultipleChoiceField (
594+ queryset = Team .objects .filter (organizer = self .event .organizer ),
595+ widget = forms .CheckboxSelectMultiple (attrs = {'class' : 'scrolling-multiple-choice' }),
596+ label = _ ("Send to members of these teams" )
597+ )
0 commit comments