Beispiel #1
0
def entries(org_label, project_label, entry_id):
    ''' show a specific entry mostly for editing purposes
    '''
    user = User.objects(email=session['email'])[0]
    
    # find the org
    orgs = Organization.objects(label=org_label)
    if not orgs:
        flash('Organization "%s" not found, sorry!' % org_label, 'warning')
        return redirect(url_for('organizations'))
    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:
        flash('Project "%s" not found, sorry!' % project_label, 'warning')
        return redirect(url_for('organizations', org_label=org.label))
    project = projects[0]

    if not entry_id:
        if request.method == 'POST':
            # downloading entries with applied filters
            filter_labels = request.form.getlist('filters')
            apply_any_filters = request.form.get('apply_any_filters', '')
            if apply_any_filters == 'true':
                apply_any_filters = True
            else:
                apply_any_filters = False

            # make a list of filter objects
            filters = []
            for filter_label in filter_labels:
                filters.append(Filter.objects(label=filter_label
                    , project=project)[0])
            
            # serve up a file of the project entries
            absolute_filename = utilities.download_all_entries(project
                , filters, format='xls', apply_any_filters=apply_any_filters)

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

        '''GET request..
        display entries for the project
        '''
        duplicate_entries_count = Entry.objects(project=project
            , unique=False).count()
        hidden_entries_count = Entry.objects(project=project, unique=True
            , visible=False).count()
        unique_entries_count = Entry.objects(project=project, unique=True
            , visible=True).count()

        entry_type = request.args.get('type', '')

        if entry_type == 'duplicate':
            # show unique=False entries
            count = duplicate_entries_count
            unique = False
            visible = None
            template = 'project_duplicate_entries.html'

        elif entry_type == 'hidden':
            # show visible=False, unique=True entries
            count = hidden_entries_count
            unique = True
            visible = False
            template = 'project_hidden_entries.html'

        else:
            # show uniques
            count = unique_entries_count
            unique = True
            visible = True
            template = 'project_entries.html'

        entries_per_page = 10
        pages = utilities.calculate_pages(count
            , entries_per_page=entries_per_page)

        # validate the requested page
        current_page = utilities.validate_page_request(
            request.args.get('page', 1), pages)

        entries = utilities.query_entries(project
            , unique=unique
            , visible=visible
            , skip=(entries_per_page * (current_page - 1))
            , limit=entries_per_page)

        # present some filters if data is downloaded
        available_filters = Filter.objects(project=project)

        return render_template(template
            , project=project
            , entries=entries
            , unique_entries_count=unique_entries_count
            , duplicate_entries_count=duplicate_entries_count
            , hidden_entries_count=hidden_entries_count
            , available_filters=available_filters
            , current_page = current_page
            , number_of_pages = pages)
    

    # we have an entry_id, try to find the object
    entries = Entry.objects(id=entry_id)
    if not entries:
        flash('Entry "%s" not found, sorry!' % entry_id, 'warning')
        return redirect(url_for('entries', org_label=org.label
            , project_label=project.label))
    entry = entries[0]

    if request.method == 'GET':
        if request.args.get('edit', '') == 'true':
            return render_template('entry_edit.html', entry=entry)
        else:
            return render_template('entry.html', entry=entry)

    elif request.method == 'POST':
        form_type = request.form.get('form_type', '')

        if form_type == 'info':
            # track all modifications to this entry
            modifications = []
            # don't think I can set just one value in the dict with set__
            # so let's make a copy then alter it, then update it
            values = dict(entry.values)

            for header in entry.project.ordered_schema:
                if header.data_type == 'datetime':
                    full_dt = '%s %s' % (
                            request.form.get('%s__date' % header.id, '')
                            , request.form.get('%s__time' % header.id, ''))
                    try:
                        struct = time.strptime(full_dt, '%B %d, %Y %I:%M %p')
                        edited_val = datetime.datetime.fromtimestamp(
                                time.mktime(struct))
                    except ValueError:
                        # requested change was improperly formatted
                        message = ('Error.  Date-time data expected for the'
                                ' field "%s."' % header.label)
                        return redirect_to_editing(entry, 'error', message)

                elif header.data_type == 'number':
                    try:
                        edited_val = float(request.form.get(
                            str(header.id), ''))
                    except ValueError:
                        # requested change wasn't a number
                        message = ('Error.  Numerical data expected for the'
                                ' field "%s."' % header.label)
                        return redirect_to_editing(entry, 'error', message)

                else:
                    edited_val = request.form.get(str(header.id), '')

                # values that were originally None will show up here as ''
                if edited_val == '' and entry.values[header.name] == None:
                    continue

                if edited_val != entry.values[header.name]:
                    values[header.name] = edited_val
                    modifications.append('updated "%s" from "%s" to "%s"' %
                            (header.label, entry.values[header.name]
                                , edited_val))

            if modifications:
                ''' update the entry
                '''
                entry.update(set__values = values)
                entry.update(set__was_never_edited = False)

                ''' generate some hashes
                used to check other possible duplicative and hidden entries
                '''
                old_hash = str(entry.value_hash)
                # compute a hash for the new values
                m = hashlib.md5()
                #sorted_headers = [h.name for h in project.ordered_schema]
                sorted_headers = values.keys()
                sorted_headers.sort()
                for header in sorted_headers:
                    value = values[header]
                    if type(value) == type(u'unicode'):
                        m.update(value.encode('utf8'))
                    else:
                        m.update(str(value))
                new_hash = m.hexdigest()

                # if the old entry was unique and had a dupe..
                # ..that dupe is now unique
                # limit one to flip just one of several possible dupes
                if entry.unique:
                    old_dupes = Entry.objects(project=project, unique=False
                            , value_hash=old_hash).limit(1)
                    if old_dupes:
                        flash('The entry you have edited had a duplicate in'
                            ' the system.  That duplicate has now been marked'
                            ' "unique."', 'success')
                        old_dupes[0].update(set__unique = True)

                # process hidden entries
                # if there are hidden entries with these values..
                # ..sound the alarm
                hidden_entries = Entry.objects(project=project
                        , visible=False).only('value_hash')
                hidden_hashes = [h['value_hash'] for h in hidden_entries]
                if entry.visible and new_hash in hidden_hashes:
                    flash('Warning: an entry with these values was previously'
                            ' "hidden," i.e. removed from analysis.  Consider'
                            ' hiding this entry.', 'warning')

                # if the entry was hidden, remind the user of that fact..
                # since the values were just edited they may want to change it
                if not entry.visible:
                    flash('This entry is currently "hidden" and not included'
                        ' in analysis.  Consider un-hiding it to include your'
                        ' new edits.', 'warning')

                # search for unique and duplicate values
                uniques = Entry.objects(project=project
                        , unique=True).only('value_hash')
                unique_hashes = [u['value_hash'] for u in uniques]

                # if entry /was/ unique
                if entry.unique:
                    if new_hash in unique_hashes:
                        flash('This entry is now a duplicate of another entry'
                            ' in the system.', 'warning')
                        entry.update(set__unique=False)

                # entry /wasn't/ unique
                else:
                    if new_hash not in unique_hashes:
                        flash('This entry was formerly a duplicate but is now'
                            ' unique.', 'success')
                        entry.update(set__unique=True)

                entry.update(set__value_hash = new_hash)


                ''' create a comment encapsulating the changes
                '''
                new_comment = Comment(
                    body = '; '.join(modifications)
                    , creation_time = datetime.datetime.utcnow()
                    , editable = False
                    , entry = entry
                    , owner = user
                    , project = project
                )
                new_comment.save()

                message = ('Changes saved successfully: %s' %
                        '; '.join(modifications))
                return redirect_to_editing(entry, 'success', message)

            else:
                # no modifications made to the entry
                return redirect_to_editing(entry, None, None)


        elif form_type == 'hide_entry':
            # flip the 'visible' state of this entry
            if entry.visible:
                entry.update(set__visible = False)
                modifications = 'entry removed from analysis'
            else:
                entry.update(set__visible = True)
                modifications = 'entry re-included in analysis'
            
            # also find all duplicates in the project and hide/unhide them as well
            duplicate_entries = Entry.objects(project=project
                , value_hash=entry.value_hash)
            # if we have more than the original entry
            if len(duplicate_entries) > 1:
                for duplicate_entry in duplicate_entries:
                    if duplicate_entry.id == entry.id:
                        continue
                    entry.reload()
                    if entry.visible:
                        duplicate_entry.update(set__visible = True)
                    else:
                        duplicate_entry.update(set__visible = False)
                
                # append a note about the dupes
                # plural..
                dupes = len(duplicate_entries) - 1
                if dupes > 1:
                    plural = 's'
                else:
                    plural = ''
                modifications += ' with %s duplicate%s' % (dupes, plural)

            ''' create a comment encapsulating the changes
            hm, comments won't be attached to duplicates..alas
            '''
            new_comment = Comment(
                body = modifications
                , creation_time = datetime.datetime.utcnow()
                , editable = False
                , entry = entry
                , owner = user
                , project = project
            )
            new_comment.save()

            message = 'Changes saved successfully: %s.' % modifications
            return redirect_to_editing(entry, 'success', message)
Beispiel #2
0
def send_scheduled_report(schedule_id):
    ''' starts rendering pdfs and prepares to send the message
    '''
    schedules = Schedule.objects(id=schedule_id)
    if not schedules:
        # the schedule was deleted
        return False
    schedule = schedules[0]
    print 'sending scheduled report for organization "%s", project "%s", \
        schedule "%s"' % (schedule.project.organization.name
        , schedule.project.name, schedule.name)
    
    # validate the schedule config for email and sms messages
    if not ready_to_send(schedule):
        print 'bad schedule config for "%s"' % schedule.name
        return False
    
    if schedule.message_type == 'email':
        # start rendering the reports
        new_renderings = []
        for report in schedule.reports:
            # instantiates a new rendering
            # also enqueues pdf-generation
            rendering = utilities.create_rendering(report)
            new_renderings.append(rendering)
    
        # create a new message
        new_message = Message(
            creation_time = datetime.datetime.utcnow()
            , message_type = schedule.message_type
            , recipients = schedule.email_recipients
            , renderings = new_renderings
            , schedule = schedule)
        new_message.save()

        # locally save the project data if we need to attach it
        if schedule.send_project_data:
            absolute_filename = utilities.download_all_entries(schedule.project
                , schedule.data_filters
                , format='xls'
                , apply_any_filters=schedule.apply_any_filters)

            new_message.update(set__project_data_path=absolute_filename)

        # compute the statistics and attach them to the message
        if schedule.statistics:
            text_results = []
            for statistic in schedule.statistics:
                result = utilities.format_statistic_result(statistic)
                text_results.append(result)

            new_message.update(set__statistic_results = text_results)

        # send the message
        # will wait until renderings are finished, if necessary
        redis_config = app.config['REDIS_CONFIG']
        use_connection(Redis(redis_config['host'], redis_config['port']
                , password=redis_config['password']))
        queue = Queue()
        queue.enqueue(send_scheduled_message, new_message.id)


    elif schedule.message_type == 'sms':
        # compute any statistics
        text_results = []
        if schedule.statistics:
            for statistic in schedule.statistics:
                result = utilities.format_statistic_result(statistic)
                text_results.append(result)

        # we create a job for each recipient
        for recipient in schedule.sms_recipients:
            # create a new message
            new_message = Message(
                creation_time = datetime.datetime.utcnow()
                , message_type = schedule.message_type
                , recipients = [recipient]
                , schedule = schedule
                , statistic_results = text_results)
            new_message.save()

            # send each message
            redis_config = app.config['REDIS_CONFIG']
            use_connection(Redis(redis_config['host'], redis_config['port']
                    , password=redis_config['password']))
            queue = Queue()
            queue.enqueue(send_scheduled_message, new_message.id)

    
    # and schedule the next job
    update_scheduled_send(schedule.id)
    app.logger.info('schedule interval updated')