Example #1
0
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)
Example #2
0
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())