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