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

1from datetime import datetime 

2from peewee import DoesNotExist, JOIN 

3from flask_mail import Mail, Message 

4import os 

5 

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 

18 

19class EmailHandler: 

20 def __init__(self, raw_form_data, url_domain, sender_object, attachment_file=None): 

21 

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 

37 

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'] 

42 

43 if 'subject' in self.raw_form_data: 

44 self.subject = self.raw_form_data['subject'] 

45 

46 if 'body' in self.raw_form_data: 

47 self.body = self.raw_form_data['body'] 

48 

49 if 'replyTo' in self.raw_form_data: 

50 self.reply_to = self.raw_form_data['replyTo'] 

51 

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 

56 

57 # Program 

58 if 'programID' in self.raw_form_data: 

59 self.program_ids = self.fetch_event_programs(self.raw_form_data['programID']) 

60 

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) 

65 

66 # Service-Learning Course 

67 if 'slCourseId' in self.raw_form_data: 

68 self.sl_course_id = self.raw_form_data['slCourseId'] 

69 

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)] 

78 

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 

86 

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)) 

102 

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() 

107 

108 sameYearTerms = Term.select().join(Term2, on=(Term.academicYear == Term2.academicYear)).where(Term2.isCurrentTerm == True) 

109 

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] 

114 

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" 

118 

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 

128 

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 

133 

134 def retrieve_and_modify_email_template(self): 

135 """ Retrieves email template based on idenitifer and calls replace_general_template_placeholders""" 

136 

137 email_template = EmailTemplate.get(EmailTemplate.purpose==self.template_identifier) # --Q: should we keep purpose as the identifier? 

138 template_id = email_template.id 

139 

140 subject = self.subject if self.subject else email_template.subject 

141 

142 body = self.body if self.body else email_template.body 

143 new_body = self.replace_general_template_placeholders(body) 

144 

145 self.reply_to = email_template.replyToAddress 

146 return (template_id, subject, new_body) 

147 

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) 

161 

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 

167 

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 

175 

176 except AttributeError: # will pass if there is no attachment to save 

177 pass 

178 

179 def store_sent_email(self, subject, template_id): 

180 """ Stores sent email in the email log """ 

181 date_sent = datetime.now() 

182 

183 attachmentNames = [] 

184 for file in self.attachment_file: 

185 attachmentNames.append(file.filename) 

186 

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) 

196 

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) 

203 

204 def send_email(self): 

205 defaultEmailInfo = {"senderName":"Sandesh", "replyTo":self.reply_to} 

206 template_id, subject, body = self.build_email() 

207 

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 

213 

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) 

219 

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 

234 

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 

247 

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