def security_send_mail(subject, recipient, template, **context): # this may be called from view which doesn't reference interest # if so pick up user's first interest to get from_email address if not g.interest: g.interest = context['user'].interests[0].interest if context['user'].interests else None if g.interest: from_email = localinterest().from_email # use default if user didn't have any interests else: from_email = current_app.config['SECURITY_EMAIL_SENDER'] # copied from flask_security.utils.send_mail if isinstance(from_email, LocalProxy): from_email = from_email._get_current_object() ctx = ('security/email', template) html = render_template('%s/%s.html' % ctx, **context) text = render_template('%s/%s.txt' % ctx, **context) sendmail(subject, from_email, recipient, html=html, text=text)
def send_discuss_email(meeting_id): """ send email to meeting invitees :param meeting_id: id of meeting :param subject: subject for message :param message: message in html format :return: list of addresses email was sent to """ invites = Invite.query.filter_by(meeting_id=meeting_id).all() meeting = Meeting.query.filter_by(id=meeting_id).one() tolist = ['{} <{}>'.format(i.user.name, i.user.email) for i in invites] # use from address configured for email email = Email.query.filter_by(meeting_id=meeting_id, type=MEETING_INVITE_EMAIL, interest=localinterest()).one() fromaddr = email.from_email subject = email.subject message = email.message # create and send email context = { 'meeting': meeting, 'message': message, 'meeting_text': meeting.meetingtype.meetingwording, 'statusreport_text': meeting.meetingtype.statusreportwording, 'invitation_text': meeting.meetingtype.invitewording, 'aninvitation_text': inflect_engine.a(meeting.meetingtype.invitewording), } for meetingoption in MEETING_OPTIONS: context[meetingoption] = meeting_has_option(meeting, meetingoption) html = render_template('meeting-discuss-email.jinja2', **context) sendmail(subject, fromaddr, tolist, html) return tolist
def send_evote_req(motion, localuser, from_addr, subject, message): """ send user evote request :param motion: Motion instance :param localuser: LocalUser instance :param from_addr: from address for email :param subject: subject for email :param message: message for email :return: MotionVote instance """ evote = MotionVote.query.filter_by(interest=localinterest(), motion=motion, user=localuser).one_or_none() if not evote: raise ParameterError( 'motion vote for motion {}, user {} not found'.format( motion.id, localuser.email)) evoteurl = page_url_for( 'admin.motionvote', interest=g.interest, urlargs={MOTIONVOTE_KEY_URLARG: evote.motionvotekey}, _external=True) context = { 'motion': motion, 'meeting': motion.meeting, 'evoteurl': evoteurl, 'message': message, } html = render_template('motion-evote-email.jinja2', **context) tolist = localuser.email cclist = None sendmail(subject, from_addr, tolist, html, ccaddr=cclist) return evote
def main(): parser = ArgumentParser() parser.add_argument('interest') parser.add_argument('--nomembers', default=False, action='store_const', const=True, help='use --nomembers to skip members') parser.add_argument('--nomanagers', default=False, action='store_const', const=True, help='use --nomanagers to skip managers') args = parser.parse_args() scriptdir = dirname(__file__) # two levels up scriptfolder = dirname(dirname(scriptdir)) configdir = join(scriptfolder, 'config') memberconfigfile = "members.cfg" memberconfigpath = join(configdir, memberconfigfile) userconfigfile = "users.cfg" userconfigpath = join(configdir, userconfigfile) # create app and get configuration # use this order so members.cfg overrrides users.cfg configfiles = [userconfigpath, memberconfigpath] app = create_app(Development(configfiles), configfiles) # set up database db.init_app(app) # set up app and request contexts so that taskdetails works # https://flask.palletsprojects.com/en/1.1.x/testing/ with app.app_context(), app.test_request_context(): # turn on logging setlogging() # g has to be set within app context g.interest = args.interest membertasks = [] taskdetails.open() for row in taskdetails.rows: membertask = taskdetails.dte.get_response_data(row) # add user record localuserid, taskid = taskdetails.getids(row.id) membertask['User'] = localuser2user(localuserid) membertask['Task'] = Task.query.filter_by(id=taskid).one() membertasks.append(membertask) # create member based data structure mem2tasks = {} for membertask in membertasks: mem2tasks.setdefault(membertask['User'].email, {'tasks': []}) mem2tasks[membertask['User'].email]['tasks'].append(membertask) for member in mem2tasks: mem2tasks[member]['tasks'].sort(key=lambda t: t['order']) # default is from interest, may be overridden below, based on emailtemplate configuration fromlist = localinterest().from_email # allows for debugging of each section separately if not args.nomembers: emailtemplate = EmailTemplate.query.filter_by( templatename='member-email', interest=localinterest()).one() template = Template(emailtemplate.template) subject = emailtemplate.subject if emailtemplate.from_email: fromlist = emailtemplate.from_email refurl = url_for('admin.taskchecklist', interest=g.interest, _external=True) for emailaddr in mem2tasks: # only send email if there are tasks overdue or upcoming sendforstatus = [STATUS_EXPIRES_SOON, STATUS_OVERDUE] if len([ t for t in mem2tasks[emailaddr]['tasks'] if t['status'] in sendforstatus ]) > 0: html = template.render(**mem2tasks[emailaddr], statuses=sendforstatus, refurl=refurl) tolist = emailaddr cclist = None sendmail(subject, fromlist, tolist, html, ccaddr=cclist) # allows for debugging of each section separately if not args.nomanagers: # what groups are each member a part of? member2groups = {} for memberlocal in LocalUser.query.filter_by( active=True, interest=localinterest()).all(): memberglobal = localuser2user(memberlocal) member2groups[memberglobal.email] = { 'worker': memberlocal, 'taskgroups': set() } # drill down to get all taskgroups the member is responsible for for position in positions_active(memberlocal, date.today()): get_position_taskgroups( position, member2groups[memberglobal.email]['taskgroups']) for taskgroup in memberlocal.taskgroups: get_taskgroup_taskgroups( taskgroup, member2groups[memberglobal.email]['taskgroups']) # get list of responsible managers responsibility = {} positions = Position.query.filter_by( interest=localinterest()).all() for position in positions: positiontasks = set() positionworkers = set() theseemailgroups = set(position.emailgroups) for workeremail in member2groups: # add worker if the worker's taskgroups intersect with these email groups if theseemailgroups & member2groups[workeremail][ 'taskgroups']: positionworkers |= { member2groups[workeremail]['worker'] } for emailgroup in position.emailgroups: get_taskgroup_tasks(emailgroup, positiontasks) # only set responsibility if this position has management for some groups if position.emailgroups: for manager in members_active(position, date.today()): manageruser = localuser2user(manager) responsibility.setdefault(manageruser.email, { 'tasks': set(), 'workers': set() }) responsibility[ manageruser.email]['tasks'] |= positiontasks responsibility[ manageruser.email]['workers'] |= positionworkers # set up template engine emailtemplate = EmailTemplate.query.filter_by( templatename='leader-email', interest=localinterest()).one() template = Template(emailtemplate.template) subject = emailtemplate.subject if emailtemplate.from_email: fromlist = emailtemplate.from_email refurl = url_for('admin.taskdetails', interest=g.interest, _external=True) # loop through responsible managers, setting up their email for manager in responsibility: manager2members = {'members': []} # need to convert to ids which are given by taskdetails for positionworker in responsibility[manager]['workers']: resptasks = [ taskdetails.setid(positionworker.id, t.id) for t in responsibility[manager]['tasks'] ] positionuser = localuser2user(positionworker) thesetasks = [ t for t in mem2tasks[positionuser.email]['tasks'] if t['rowid'] in resptasks and t['status'] in [STATUS_OVERDUE] ] if thesetasks: manager2members['members'].append({ 'name': positionuser.name, 'tasks': thesetasks }) # only send if something to send if manager2members['members']: html = template.render(**manager2members, refurl=refurl) tolist = manager cclist = None sendmail(subject, fromlist, tolist, html, ccaddr=cclist)
def check_add_invite(meeting, localuser, agendaitem, sendemail=True): """ check if user invite needs to be added :param meeting: Meeting instance :param localuser: LocalUser instance :param agendaitem: AgendaItem instance for invite to be attached to :param sendemail: True means send email to localuser :return: invite (may have been created) """ invite = Invite.query.filter_by(interest=localinterest(), meeting=meeting, user=localuser).one_or_none() if not invite: # create unique key for invite - uuid4 gives unique key invitekey = uuid4().hex invite = Invite( interest=localinterest(), meeting=meeting, user=localuser, agendaitem=agendaitem, invitekey=invitekey, activeinvite=True, lastreminder=datetime.now(), ) db.session.add(invite) db.session.flush() # optionally send email to user if sendemail: # get user's outstanding action items actionitems = ActionItem.query.filter_by(interest=localinterest(), assignee=localuser). \ filter(ActionItem.status != ACTION_STATUS_CLOSED).all() email = Email.query.filter_by(meeting=meeting, type=MEETING_INVITE_EMAIL).one() subject = email.subject fromlist = email.from_email rsvpurl = page_url_for('admin.memberstatusreport', interest=g.interest, urlargs={INVITE_KEY_URLARG: invitekey}, _external=True) actionitemurl = page_url_for('admin.myactionitems', interest=g.interest, _external=True) context = { 'meeting': meeting, 'actionitems': actionitems, 'rsvpurl': rsvpurl, 'actionitemurl': actionitemurl, 'message': email.message, 'meeting_text': meeting.meetingtype.meetingwording, 'statusreport_text': meeting.meetingtype.statusreportwording, 'invitation_text': meeting.meetingtype.invitewording, 'aninvitation_text': inflect_engine.a(meeting.meetingtype.invitewording) } for meetingoption in MEETING_OPTIONS: context[meetingoption] = meeting_has_option( meeting, meetingoption) html = render_template('meeting-invite-email.jinja2', **context) tolist = localuser.email cclist = None sendmail(subject, fromlist, tolist, html, ccaddr=cclist) invite.activeinvite = True return invite
def generatereminder(meetingid, member, positions): """ generate a meeting reminder email to the user :param meetingid: id of meeting :param member: member to remind :param positions: positions for which this reminder is about :return: False if new invite sent, True if reminder sent """ # find member's invitation, if it exists invite = Invite.query.filter_by(meeting_id=meetingid, user=member).one_or_none() meeting = Meeting.query.filter_by(id=meetingid).one() # invite already exists, send reminder if invite: # email record should exist, else software error, so it's ok to use one() email = Email.query.filter_by(interest=localinterest(), meeting_id=meetingid, type=MEETING_REMINDER_EMAIL).one() # send reminder email to user subject = email.subject fromlist = email.from_email message = email.message tolist = member.email cclist = None # options = email.options # get user's outstanding action items actionitems = ActionItem.query.filter_by(interest=localinterest(), assignee=member). \ filter(ActionItem.status != ACTION_STATUS_CLOSED).all() # set up urls for email rsvpurl = page_url_for('admin.memberstatusreport', interest=g.interest, urlargs={INVITE_KEY_URLARG: invite.invitekey}, _external=True) actionitemurl = page_url_for('admin.myactionitems', interest=g.interest, _external=True) # filter positions to those which affect this member active_positions = positions_active(member, invite.meeting.date) memberpositions = [p for p in positions if p in active_positions] # create and send email context = { 'meeting': invite.meeting, 'message': message, 'actionitems': actionitems, 'rsvpurl': rsvpurl, 'actionitemurl': actionitemurl, 'meeting_text': invite.meeting.meetingtype.meetingwording, 'statusreport_text': invite.meeting.meetingtype.statusreportwording, 'invitation_text': invite.meeting.meetingtype.invitewording, 'aninvitation_text': inflect_engine.a(invite.meeting.meetingtype.invitewording), 'positions': memberpositions, } for meetingoption in MEETING_OPTIONS: context[meetingoption] = meeting_has_option( invite.meeting, meetingoption) html = render_template('meeting-reminder-email.jinja2', **context) sendmail(subject, fromlist, tolist, html, ccaddr=cclist) invite.lastreminder = datetime.now() reminder = True # invite doesn't exist yet, create and send invite else: meeting = Meeting.query.filter_by(id=meetingid).one() anyinvite = Invite.query.filter_by(interest=localinterest(), meeting=meeting).first() check_add_invite(meeting, member, anyinvite.agendaitem) reminder = False return reminder
def post(self): try: val = DteFormValidate(ApplnValidator(allow_extra_fields=True)) results = val.validate(request.form) if results['results']: raise ParameterError(results['results']) formdata = results['python'] name = formdata['name'] config = RacingTeamConfig.query.filter_by( **localinterest_query_params()).one_or_none() interest = Interest.query.filter_by(interest=g.interest).one() if not config: raise ParameterError( 'interest configuration needs to be created') # we're logging this now logtime = datetime.now() applnrec = RacingTeamApplication(interest=localinterest(), logtime=logtime) applnformfields = 'name,email,dob,gender,applntype,comments'.split( ',') applndbfields = 'name,email,dateofbirth,gender,type,comments'.split( ',') applnmapping = dict(zip(applndbfields, applnformfields)) form2appln = Transform(applnmapping, sourceattr=False) form2appln.transform(request.form, applnrec) db.session.add(applnrec) # race result information mailfields = OrderedDict([ ('name', 'Name'), ('email', 'Email'), ('dob', 'Birth Date'), ('gender', 'Gender'), ('race1_name', 'Race 1 - Name'), ('race1_location', 'Race 1 - Location'), ('race1_date', 'Race 1 - Date'), ('race1_age', 'Race 1 - Age'), ('race1_distance', 'Race 1 - Distance'), ('race1_units', ''), ('race1_time', 'Race 1 - Official Time (hh:mm:ss)'), ('race1_resultslink', 'Race 1 - Results Website'), ('race1_agegrade', 'Race 1 - Age Grade'), ('race2_name', 'Race 2 - Name'), ('race2_location', 'Race 2 - Location'), ('race2_date', 'Race 2 - Date'), ('race2_age', 'Race 2 - Age'), ('race2_distance', 'Race 2 - Distance'), ('race2_units', ''), ('race2_time', 'Race 2 - Official Time (hh:mm:ss)'), ('race2_resultslink', 'Race 2 - Results Website'), ('race2_agegrade', 'Race 2 - Age Grade'), ('comments', 'Comments'), ]) for ndx in [1, 2]: resultsrec = RacingTeamResult(interest=localinterest()) resultsform = f'race{ndx}_name,race{ndx}_date,race{ndx}_location,race{ndx}_resultslink,race{ndx}_distance,race{ndx}_units,race{ndx}_time,race{ndx}_agegrade,race{ndx}_age'.split( ',') resultsdb = 'eventname,eventdate,location,url,distance,units,time,agegrade,age'.split( ',') resultsmapping = dict(zip(resultsdb, resultsform)) resultsxform = Transform(resultsmapping, sourceattr=False) resultsxform.transform(request.form, resultsrec) resultsrec.application = applnrec db.session.add(resultsrec) # commit database changes db.session.commit() # send confirmation email subject = f"[racing-team-application] New racing team application from {name}" body = div() with body: p('The following application for the racing team was submitted. If this is correct, ' f'no action is required. If you have any changes, please contact {config.fromemail}' ) with table(), tbody(): for field in mailfields: with tr(): td(mailfields[field]) td(request.form[field]) with p(): text(f'Racing Team - {config.fromemail}') br() text(f'{interest.description}') html = body.render() tolist = formdata['email'] fromlist = config.fromemail cclist = config.applnccemail sendmail(subject, fromlist, tolist, html, ccaddr=cclist) return jsonify({'status': 'OK'}) except Exception as e: db.session.rollback() exc_type, exc_value, exc_traceback = exc_info() current_app.logger.error(''.join(format_tb(exc_traceback))) error = format_exc() return jsonify({'status': 'error', 'error': escape(repr(e))})
def post(self): try: val = DteFormValidate(InfoValidator(allow_extra_fields=True)) results = val.validate(request.form) if results['results']: raise ParameterError(results['results']) formdata = results['python'] name = formdata['common_name'] localuser = LocalUser.query.filter_by( name=name, active=True, **localinterest_query_params()).one() member = RacingTeamMember.query.filter_by( localuser=localuser, **localinterest_query_params()).one() config = RacingTeamConfig.query.filter_by( **localinterest_query_params()).one_or_none() interest = Interest.query.filter_by(interest=g.interest).one() if not config: raise ParameterError( 'interest configuration needs to be created') # we're logging this now logtime = datetime.now() inforec = RacingTeamInfo(interest=localinterest(), member=member, logtime=logtime) db.session.add(inforec) # race result information if formdata['common_infotype'] == 'raceresult': mailfields = OrderedDict([ ('common_name', 'Name'), ('common_eventname', 'Event Name'), ('common_eventdate', 'Event Date'), ('common_infotype', 'Submission Type'), ('raceresult_distance', 'Distance'), ('raceresult_units', ''), ('raceresult_time', 'Official Time (hh:mm:ss)'), ('raceresult_age', 'Age (Race Date)'), ('raceresult_agegrade', 'Age Grade'), ('raceresult_awards', 'Awards'), ]) resultfields = { 'common_eventname': 'eventname', 'raceresult_distance': 'distance', 'raceresult_units': 'units', 'raceresult_time': 'time', 'raceresult_age': 'age', 'raceresult_agegrade': 'agegrade', 'raceresult_awards': 'awards', } resultsrec = RacingTeamResult(interest=localinterest()) for field in resultfields: setattr(resultsrec, resultfields[field], request.form[field]) # use conversion to datetime resultsrec.eventdate = formdata['common_eventdate'] resultsrec.info = inforec db.session.add(resultsrec) else: mailfields = OrderedDict([ ('common_name', 'Name'), ('common_eventname', 'Event Name'), ('common_eventdate', 'Event Date'), ('common_infotype', 'Submission Type'), ('volunteer_hours', 'How Many Hours'), ('volunteer_comments', 'Comments'), ]) resultfields = { 'common_eventname': 'eventname', 'volunteer_hours': 'hours', 'volunteer_comments': 'comment', } volunteerrec = RacingTeamVolunteer(interest=localinterest()) for field in resultfields: setattr(volunteerrec, resultfields[field], request.form[field]) # use conversion to datetime volunteerrec.eventdate = formdata['common_eventdate'] volunteerrec.info = inforec db.session.add(volunteerrec) # commit database changes db.session.commit() # send confirmation email subject = "[racing-team-info] New racing team information from {}".format( name) body = div() with body: p('The following information for the racing team was submitted. If this is correct, ' f'no action is required. If you have any changes, please contact {config.fromemail}' ) with table(), tbody(): for field in mailfields: with tr(): td(mailfields[field]) td(request.form[field]) with p(): text(f'Racing Team - {config.fromemail}') br() text(f'{interest.description}') html = body.render() tolist = member.localuser.email fromlist = config.fromemail cclist = config.infoccemail sendmail(subject, fromlist, tolist, html, ccaddr=cclist) return jsonify({'status': 'OK'}) except Exception as e: db.session.rollback() exc_type, exc_value, exc_traceback = exc_info() current_app.logger.error(''.join(format_tb(exc_traceback))) error = format_exc() return jsonify({'status': 'error', 'error': escape(repr(e))})
def post(self): # see https://flask.palletsprojects.com/en/1.1.x/patterns/fileuploads/ # check if the post request has the file part try: if 'file[0]' not in request.files: return jsonify({ 'status': 'error', 'error': 'No files submitted' }) gs = FsrcGoogleAuthService( current_app.config['GSUITE_SERVICE_KEY_FILE'], current_app.config['GSUITE_SCOPES']) parentid = current_app.config['FSRC_SCHOLARSHIP_FOLDER'] tmpdir = TemporaryDirectory(prefix='mbr-frsc-') for i in range(int(request.form['numfiles'])): file = request.files['file[{}]'.format(i)] # if user does not select file, browser also # submit an empty part without filename if file.filename == '': return jsonify({ 'status': 'error', 'error': 'Empty filename detected for file {}'.format(i) }) name = request.form.get('name', '') if not name: return jsonify({ 'status': 'error', 'error': 'Name must be supplied' }) email = request.form.get('email', '') if not email: return jsonify({ 'status': 'error', 'error': 'Email must be supplied' }) if file and allowed_file(file.filename): filename = secure_filename(file.filename) applnfoldername = '{} {}'.format(name, email) # has applicant already submitted? reuse filedid if so # should not be more than one of these, but use the first one if any found applnfolders = gs.list_files(parentid, filename=applnfoldername) if applnfolders: folderid = applnfolders[0][applnfoldername] else: folderid = gs.create_folder(parentid, applnfoldername) current_app.logger.info( 'fsrcmemscholarshipappl: {}/{} processing ' 'file {}'.format(name, email, filename)) docpath = pathjoin(tmpdir.name, filename) file.save(docpath) fileid = gs.create_file(folderid, filename, docpath, doctype=None) # remove temporary directory rmtree(tmpdir.name, ignore_errors=True) # send mail to administrator foldermeta = gs.drive.files().get(fileId=folderid, fields='webViewLink').execute() folderlink = foldermeta['webViewLink'] subject = "[FSRC Memorial Scholarship] Application from {}".format( name) from dominate.tags import div, p, a from dominate.util import text body = div() with body: p('Application received from {} {}'.format(name, email)) with p(): text('See ') a(folderlink, href=folderlink) html = body.render() tolist = current_app.config['FSRC_SCHOLARSHIP_EMAIL'] fromlist = current_app.config['FSRC_SCHOLARSHIP_EMAIL'] cclist = None sendmail(subject, fromlist, tolist, html, ccaddr=cclist) return jsonify({'status': 'OK'}) except Exception as e: from traceback import format_exc from html import escape error = format_exc() return jsonify({'status': 'error', 'error': escape(repr(e))})