def renderings(org_label, project_label, rendering_id): ''' deleting or downloading renderings /organizations/aquaya/projects/water-quality/renderings/4cmb1?delete=true : remove a rendering from the system and s3 /organizations/aquaya/projects/water-quality/renderings/4cmb1?download=true : remove a rendering from the system and s3 ''' user = User.objects(email=session['email'])[0] orgs = Organization.objects(label=org_label) if not orgs: abort(404) org = orgs[0] # permission-check if org not in user.organizations and not user.admin_rights: app.logger.error('%s tried to view a project but was \ denied for want of admin rights' % session['email']) abort(404) # find the project projects = Project.objects(label=project_label, organization=org) if not projects: abort(404) # find the specified rendering renderings = Rendering.objects(id=rendering_id) if not renderings: abort(404) rendering = renderings[0] # save the report for later redirect report = rendering.report if request.args.get('delete', '') == 'true': # remove the rendering utilities.delete_rendering(rendering, user.email) flash('Rendering successfully deleted.', 'success') return redirect(url_for('reports' , org_label=report.project.organization.label , project_label=report.project.label, report_label=report.label)) if request.args.get('download', '') == 'true': absolute_filename = utilities.download_rendering_from_s3(rendering) # delay the deletion so we have time to serve the file redis_config = app.config['REDIS_CONFIG'] use_connection(Redis(redis_config['host'], redis_config['port'] , password=redis_config['password'])) scheduler = Scheduler() scheduler.enqueue_in(datetime.timedelta(seconds=60) , delete_local_file, absolute_filename) return send_file(absolute_filename, as_attachment=True) else: abort(404)
def send_scheduled_message(message_id): ''' send a message when pdf reports are ready if renderings are not ready, schedules itself to try again later ''' messages = Message.objects(id=message_id) if not messages: return False message = messages[0] # container for all attachments file_paths = [] # check if renderings have finished if message.message_type == 'email' and message.renderings: while(True): # count to see how many s3 keys we have keys = 0 for rendering in message.renderings: if rendering.s3_key: keys += 1 if keys != len(message.renderings): # don't have all the keys yet.. time.sleep(2) message.reload() else: break # renderings are complete, download them locally for rendering in message.renderings: # some duplicate effort here # when the file is rendered we /have/ a local copy # but this copy is deleted after being sent to s3 # then we turn around and download a copy..could use some refactoring path = utilities.download_rendering_from_s3(rendering) file_paths.append(path) # add the project data file path if message.project_data_path and message.message_type == 'email': file_paths.append(message.project_data_path) # begin composing the message if message.message_type == 'email': msg = MIMEMultipart() subject = message.schedule.email_subject if not subject: subject = 'scheduled reports from %s' % app.config['APP_NAME'] msg['Subject'] = subject # use the verified sender msg['From'] = app.config['AWS']['verified_sender'] # comma-separated list of emails emails = [r['email'] for r in message.schedule.email_recipients] msg['To'] = ', '.join(emails) # what is seen if this message is read without a reader msg.preamble = 'Multipart message.\n' # message body body = message.schedule.email_body # copy any attached statistics into the body if message.statistic_results: if body: body += '\n\n\n' else: body = '' body += 'The results of some computed statistics:\n' for i, result in enumerate(message.statistic_results): body += '%s) %s\n\n' % (i+1, result) if not body: body = 'Please see the attached files.\n\nThank you.' part = MIMEText(body) msg.attach(part) # add attachments for path in file_paths: filename = path.split('/')[-1] part = MIMEApplication(open(path, 'rb').read()) part.add_header('Content-Disposition', 'attachment' , filename=filename) msg.attach(part) # send the email connection = boto.connect_ses( aws_access_key_id=app.config['AWS']['access_key_id'] , aws_secret_access_key=app.config['AWS']['secret_access_key']) # source param is where bounces are sent result = connection.send_raw_email(msg.as_string() , source=msg['From']) message.update(set__sent_time = datetime.datetime.utcnow()) # bump the schedule's last run time message.schedule.update( set__last_run_time = datetime.datetime.utcnow()) elif message.message_type == 'sms': # send the message with twilio's api client = TwilioRestClient(app.config['TWILIO']['account_sid'] , app.config['TWILIO']['auth_token']) # build up the message body body = 'Scheduled data from %s: %s' % (app.config['APP_NAME'] , ' '.join(message.statistic_results)) # twilio doesn't automatically break up messages that are longer # than their character limit, so we'll do that manually.. # if the message is ASCII-only, the limit is 160 chars # otherwise it's 70 # todo: better chunking -- send 160 char messages if it's ascii-only # otherwise split up the 160 char message into 70 char fragments character_limit = 160 try: body.decode('ascii') except: # thrown if non-ascii chars are present # couldn't catch UnicodeDecodeError here for some reason.. # will use a generic except character_limit = 70 if len(body) > character_limit: fragments = [] fragment = '' for word in body.split(): # reserving 7 characters for the " 11/13" fragment identifier # that gets appended to each message if len('%s %s' % (fragment, word)) < character_limit - 7: # gradually build up the fragment fragment = '%s %s' % (fragment, word) else: # this fragment is complete fragments.append(fragment) # start again fragment = word # iteration complete, append the leftover fragment fragments.append(fragment) # now that we know how many messages are being sent, we can append # fragment identifiers like " 2/3" # two spaces to separate this metadata from the real body individual_messages = [] for i, fragment in enumerate(fragments): identifier = '%s/%s' % (i+1, len(fragments)) individual_messages.append('%s %s' % (fragment, identifier)) else: # the whole body fits in the message individual_messages = [body] for message_body in individual_messages: # there should only be one recipient in the list sms_message = client.sms.messages.create( to = message.recipients[0]['phone_number'] , from_ = app.config['TWILIO']['verified_number'] , body = message_body) app.logger.info('SMS message forwarded to twilio with SID: %s ' \ 'and body: %s' % (sms_message.sid, message_body)) if len(individual_messages) > 1: # pause before sending the next message fragment time.sleep(3) # save that the message was sent and bump the last run time # (well it was enqueued, it'll be sent shortly..) message.update(set__sent_time = datetime.datetime.utcnow()) message.schedule.update( set__last_run_time = datetime.datetime.utcnow())