Coverage for app/logic/emailHandler.py: 51%
149 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-24 14:13 +0000
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-24 14:13 +0000
1from datetime import datetime
2from peewee import DoesNotExist, JOIN
3from flask_mail import Mail, Message
4import os
6from app import app
7from app.models.programEvent import ProgramEvent
8from app.models.interest import Interest
9from app.models.user import User
10from app.models.program import Program
11from app.models.eventRsvp import EventRsvp
12from app.models.emailTemplate import EmailTemplate
13from app.models.emailLog import EmailLog
14from app.models.event import Event
15from app.models.eventParticipant import EventParticipant
16from app.models.programBan import ProgramBan
17from app.models.term import Term
19class EmailHandler:
20 def __init__(self, raw_form_data, url_domain, sender_object, attachment_file=None):
22 self.mail = Mail(app)
23 self.raw_form_data = raw_form_data
24 self.url_domain = url_domain
25 self.override_all_mail = app.config['MAIL_OVERRIDE_ALL']
26 self.sender = sender_object
27 self.template_identifier = None
28 self.subject = None
29 self.body = None
30 self.reply_to = None
31 self.event = None
32 self.program_ids = None
33 self.recipients = None
34 self.sl_course_id = None
35 self.attachment_path = app.config['files']['base_path'] + app.config['files']['email_attachment_path']
36 self.attachment_file = attachment_file
38 def process_data(self):
39 """ Processes raw data and stores it in class variables to be used by other methods """
40 # Email Template Data
41 self.template_identifier = self.raw_form_data['templateIdentifier']
43 if 'subject' in self.raw_form_data:
44 self.subject = self.raw_form_data['subject']
46 if 'body' in self.raw_form_data:
47 self.body = self.raw_form_data['body']
49 if 'replyTo' in self.raw_form_data:
50 self.reply_to = self.raw_form_data['replyTo']
52 # Event
53 if 'eventID' in self.raw_form_data:
54 event = Event.get_by_id(self.raw_form_data['eventID'])
55 self.event = event
57 # Program
58 if 'programID' in self.raw_form_data:
59 self.program_ids = self.fetch_event_programs(self.raw_form_data['programID'])
61 # Recipients
62 if 'recipientsCategory' in self.raw_form_data:
63 self.recipients_category = self.raw_form_data['recipientsCategory']
64 self.recipients = self.retrieve_recipients(self.recipients_category)
66 # Service-Learning Course
67 if 'slCourseId' in self.raw_form_data:
68 self.sl_course_id = self.raw_form_data['slCourseId']
70 def fetch_event_programs(self, program_id):
71 """ Fetches all the programs of a particular event """
72 # Non-student-led programs have "Unknown" as their id
73 if program_id == 'Unknown' or program_id is None:
74 programEvents = ProgramEvent.select(ProgramEvent.program).where(ProgramEvent.event==self.event.id)
75 return [program.program for program in programEvents.objects()]
76 else:
77 return [Program.get_by_id(program_id)]
79 def update_sender_config(self):
80 # We might need this.
81 # This functionality should be moved somewhere else.
82 # The function in another file would receive email_info[sender]
83 # and update the config based on that and wherever we will end up saving emails and passwords
84 #The sender information should be saved like so: {"name": [email, password], } or in the database
85 pass
87 def retrieve_recipients(self, recipients_category):
88 """ Retrieves recipient based on which category is chosen in the 'To' section of the email modal """
89 # Other potential recipients:
90 # - course instructors
91 # - course Participants
92 # - outside participants'
93 if recipients_category == "Interested":
94 recipients = (User.select()
95 .join(Interest)
96 .join(Program, on=(Program.id==Interest.program))
97 .where(Program.id.in_([p.id for p in self.program_ids])))
98 if recipients_category == "RSVP'd":
99 recipients = (User.select()
100 .join(EventRsvp)
101 .where(EventRsvp.event==self.event.id))
103 if recipients_category == "Eligible Students":
104 # all terms with the same accademic year as the current term,
105 # the allVolunteer training term then needs to be in that query
106 Term2 = Term.alias()
108 sameYearTerms = Term.select().join(Term2, on=(Term.academicYear == Term2.academicYear)).where(Term2.isCurrentTerm == True)
110 bannedUsers = ProgramBan.select(ProgramBan.user_id).where((ProgramBan.endDate > datetime.now()) | (ProgramBan.endDate is None), ProgramBan.program_id.in_([p.id for p in self.program_ids]))
111 allVolunteer = Event.select().where(Event.isAllVolunteerTraining == True, Event.term.in_(sameYearTerms))
112 recipients = User.select().join(EventParticipant).where(User.username.not_in(bannedUsers), EventParticipant.event.in_(allVolunteer))
113 return [recipient for recipient in recipients]
115 def replace_general_template_placeholders(self, email_body=None):
116 """ Replaces all template placeholders except name """
117 event_link = f"{self.url_domain}/eventsList/{self.event.id}/edit"
119 new_body = email_body.format(event_name=self.event.name,
120 location=self.event.location,
121 start_date=(self.event.startDate).strftime('%m/%d/%Y'),
122 end_date=(self.event.endDate).strftime('%m/%d/%Y'),
123 start_time=(self.event.timeStart).strftime('%I:%M'),
124 end_time=(self.event.timeEnd).strftime('%I:%M'),
125 event_link=event_link,
126 name="{name}")
127 return new_body
129 def replace_name_placeholder(self, name, body):
130 """ Replaces name placeholder with recipient's full name """
131 new_body = body.format(name=name)
132 return new_body
134 def retrieve_and_modify_email_template(self):
135 """ Retrieves email template based on idenitifer and calls replace_general_template_placeholders"""
137 email_template = EmailTemplate.get(EmailTemplate.purpose==self.template_identifier) # --Q: should we keep purpose as the identifier?
138 template_id = email_template.id
140 subject = self.subject if self.subject else email_template.subject
142 body = self.body if self.body else email_template.body
143 new_body = self.replace_general_template_placeholders(body)
145 self.reply_to = email_template.replyToAddress
146 return (template_id, subject, new_body)
148 def getAttachmentFullPath(self, newfile=None):
149 """
150 This creates the directory/path for the object from the "Choose File" input in the emailModal.html file.
151 :returns: directory path for attachment
152 """
153 attachmentFullPath = None
154 try:
155 # tries to create the full path of the files location and passes if
156 # the directories already exist or there is no attachment
157 attachmentFullPath = os.path.join(self.attachment_path, newfile.filename)
158 if attachmentFullPath[:-1] == self.attachment_path:
159 return None
160 os.mkdir(self.attachment_path)
162 except AttributeError: # will pass if there is no attachment to save
163 pass
164 except FileExistsError: # will pass if the file already exists
165 pass
166 return attachmentFullPath
168 def saveAttachment(self):
169 """ Saves the attachment in the app/static/files/attachments/ directory """
170 try:
171 for file in self.attachment_file:
172 attachmentFullPath = self.getAttachmentFullPath(newfile = file)
173 if attachmentFullPath:
174 file.save(attachmentFullPath) # saves attachment in directory
176 except AttributeError: # will pass if there is no attachment to save
177 pass
179 def store_sent_email(self, subject, template_id):
180 """ Stores sent email in the email log """
181 date_sent = datetime.now()
183 attachmentNames = []
184 for file in self.attachment_file:
185 attachmentNames.append(file.filename)
187 EmailLog.create(
188 event = self.event.id,
189 subject = subject,
190 templateUsed = template_id,
191 recipientsCategory = self.recipients_category,
192 recipients = ", ".join(recipient.email for recipient in self.recipients),
193 dateSent = date_sent,
194 sender = self.sender,
195 attachmentNames = attachmentNames)
197 def build_email(self):
198 # Most General Scenario
199 self.saveAttachment()
200 self.process_data()
201 template_id, subject, body = self.retrieve_and_modify_email_template()
202 return (template_id, subject, body)
204 def send_email(self):
205 defaultEmailInfo = {"senderName":"Sandesh", "replyTo":self.reply_to}
206 template_id, subject, body = self.build_email()
208 if len(self.program_ids) == 1:
209 if self.program_ids[0].contactEmail:
210 defaultEmailInfo["replyTo"] = self.program_ids[0].contactEmail
211 if self.program_ids[0].contactName:
212 defaultEmailInfo["senderName"] = self.program_ids[0].contactName
214 try:
215 with self.mail.connect() as conn:
216 for recipient in self.recipients:
217 full_name = f'{recipient.firstName} {recipient.lastName}'
218 email_body = self.replace_name_placeholder(full_name, body)
220 conn.send(Message(
221 subject,
222 # [recipient.email],
223 [self.override_all_mail],
224 email_body,
225 file_attachment = self.getAttachmentFullPath(), #needs to be modified later
226 reply_to = defaultEmailInfo["replyTo"],
227 sender = (defaultEmailInfo["senderName"], defaultEmailInfo["replyTo"])
228 ))
229 self.store_sent_email(subject, template_id)
230 return True
231 except Exception as e:
232 print("Error on sending email: ", e)
233 return False
235 def update_email_template(self):
236 try:
237 self.process_data()
238 (EmailTemplate.update({
239 EmailTemplate.subject: self.subject,
240 EmailTemplate.body: self.body,
241 EmailTemplate.replyToAddress: self.reply_to
242 }).where(EmailTemplate.purpose==self.template_identifier)).execute()
243 return True
244 except Exception as e:
245 print("Error updating email template record: ", e)
246 return False
248 def retrieve_last_email(event_id):
249 try:
250 last_email = EmailLog.select().where(EmailLog.event==event_id).order_by(EmailLog.dateSent.desc()).get()
251 return last_email
252 except DoesNotExist:
253 return None