Пример #1
0
def check_dates(app):
    """Check existing deadlines and reminders in the database, executes any corresponding action if one has passed"""
    now = datetime.utcnow()

    with app.app_context():
        deadline = db.session.query(Deadline).first()
        # if deadline exists, check if already passed
        if deadline is not None and not deadline.is_valid(
                now) and not deadline.executed:
            print(log_header('AUTOMATED EMAILS'))
            send_all_prof_emails()
            deadline.executed = True
            db.session.commit()
        else:
            reminders = db.session.query(Reminder).order_by(
                Reminder.datetime).all()
            r_sent = False
            for reminder in reminders:
                if not reminder.is_valid(now):
                    # if a reminder has been sent already, other reminders that may have passed within the same interval should not trigger another reminder (and should be also removed)
                    db.session.delete(reminder)
                    db.session.commit()
                    # if no emails have been sent yet
                    if not r_sent:
                        r_sent = True
                        print(log_header('AUTOMATED EMAILS'))
                        send_all_student_emails(True)
Пример #2
0
def send_all_student_emails(reminder=False):
    """This function sends emails with individualized links to *all* students in the database to take the survey"""
    sender = current_app.config['MAIL_ADDRESS']
    deadline = Deadline.query.first()
    if deadline is not None and deadline.is_valid():
        msgs = list()
        threads = list()
        current_app_context = current_app._get_current_object()
        for student in Student.query.all():
            # skip this student if this is a reminder email and the student has already submitted their survey
            if not reminder or not student.survey_submitted:
                try:
                    # get_survey_link() has to be done here because request context cannot be pushed
                    t = Thread(target=lambda m, app_con, stu, sub, num, lin, dea, rem: \
                        m.insert(0, create_student_msg(app_con, stu, sub, num, lin, dea, rem)), \
                        args=(msgs, current_app_context, student, student.section.subject, \
                            student.section.course_num, student.get_survey_link(), deadline, reminder))
                    threads.append(t)
                    t.start()
                except Exception as e:
                    print('ERROR: Failed to create message for {} {} - {}'.format(student.s_id, student.email, e))
        # block until all threads have finished
        for t in threads:
            t.join()
        # send emails
        if reminder:
            print(log_header('REMINDER EMAILS'))
        else:
            print(log_header('STUDENT EMAILS'))
        Thread(target=send_async_emails, args=(current_app_context,), kwargs={'msgs': msgs}).start()
        print('SENT {} EMAILS'.format(len(msgs)))
    elif deadline is None:
        print('ERROR: Student emails cannot be sent without a deadline')
    elif not deadline.is_valid():
        print('ERROR: Student emails cannot be sent without a valid deadline')
Пример #3
0
def override():
    """Allow developers to wipe database if needed"""
    if User.query.count() == 0:
        return redirect(url_for('main.login'))
    form = OverrideForm()
    if form.validate_on_submit():
        if form.password.data == current_app.config['OVERRIDE_PW']:
            print(log_header('OVERRIDE - {}'.format(form.dev_id.data)))
            wipeAdmin()
            flash('Admin data erased')
            wipeSurveyData()
            flash('Survey data erased')
            return redirect(url_for('main.register'))
        else:
            flash('Incorrect password')
    return render_template('override.html',
                           title='DEVELOPER OVERRIDE',
                           form=form)
Пример #4
0
def send_all_prof_emails():
    """This function sends emails with individualized statistics to *all* professors in the database (represented as sections)"""
    current_app_context = current_app._get_current_object()
    msgs = list()
    threads = list()
    pdfs = list()
    for section in Section.query.all():
        t = None
        # create section data
        results_count = section.results.count()
        students_count = section.students.count()
        means = section.get_means()
        stds = section.get_stds()
        frq_responses = section.get_frq_responses()
        try:
            if results_count > 0:
                pdf = PDFPlotter(section)
                t = Thread(target=lambda m, app_con, s, r_c, s_c, me, st, f_r, f: \
                    m.insert(0, create_prof_emails(app_con, s, r_c, s_c, me, st, f_r, f)), \
                    args=(msgs, current_app_context, section, results_count, students_count, means, stds, frq_responses, pdf.file))
                pdfs.append(pdf)
            else:
                # don't create PDF if no results were submitted
                t = Thread(target=lambda m, app_con, s, r_c, s_c, me, st, f_r: \
                    m.insert(0, create_prof_emails(app_con, s, r_c, s_c, me, st, f_r)), \
                    args=(msgs, current_app_context, section, results_count, students_count, means, stds, frq_responses))
            threads.append(t)
            t.start()
        except Exception as e:
            print('ERROR: Failed to create message for {} {} - {}'.format(section.course_id, section.prof_email, e))
    # block until all threads are finished
    for t in threads:
        t.join()
    # delete pdfs after emails are sent
    for pdf in pdfs:
        pdf.deleteFile()
    # send emails
    print(log_header('PROFESSOR EMAILS'))
    Thread(target=send_async_emails, args=(current_app_context,), kwargs={'msgs': msgs}).start()
    print('SENT {} EMAILS'.format(len(msgs)))
Пример #5
0
def parse_roster(form_roster_data):
    """Use uploaded roster to create corresponding database objects - expects a wtforms.fields.FileField object (i.e. form.<uploaded_file>.data)"""
    # save file locally
    filename = secure_filename(form_roster_data.filename)
    form_roster_data.save(filename)
    csv_filepath = os.path.join('documents', filename)

    ext = filename[filename.rindex('.'):]
    # if Excel file, convert to CSV and remove Excel version
    if ext == '.xlsx' or ext == '.xls':
        wb = xlrd.open_workbook(filename)
        sheet = wb.sheet_by_index(0)
        # convert
        with open(csv_filepath, 'w', newline='') as f_roster:
            csv_roster = csv.writer(f_roster, delimiter=',')
            for row_num in range(sheet.nrows):
                csv_roster.writerow(sheet.row_values(row_num))
        # remove Excel file
        os.remove(filename)
    # if already CSV file, simply move file
    elif ext == '.csv':
        os.rename(filename, csv_filepath)

    # indices as expected by the given SCU roster template
    C_ID_I = 1
    SUBJECT_I = 2
    COURSE_I = 3
    PROF_NAME_I = 6
    PROF_EMAIL_I = 7
    S_ID_I = 8
    STUDENT_EMAIL_I = 9

    # expected headers:
    C_ID_S = 'Class Nbr'
    SUBJECT_S = 'Subject'
    COURSE_S = 'Catalog'
    PROF_NAME_S = 'Instructor'
    PROF_EMAIL_S = 'Instructor Email'
    S_ID_S = 'Student ID'
    STUDENT_EMAIL_S = 'Student Email'

    # counters
    section_count = 0
    student_count = 0

    with open(csv_filepath, 'r', newline='') as f_roster:
        rows = csv.reader(f_roster, delimiter=',')
        header_row = next(rows)
        # ensure important columns have correct headers
        if not (header_row[C_ID_I].lower() == C_ID_S.lower() and \
                header_row[SUBJECT_I].lower() == SUBJECT_S.lower() and \
                header_row[COURSE_I].lower() == COURSE_S.lower() and \
                header_row[PROF_NAME_I].lower() == PROF_NAME_S.lower() and \
                header_row[PROF_EMAIL_I].lower() == PROF_EMAIL_S.lower() and \
                header_row[S_ID_I].lower() == S_ID_S.lower() and \
                header_row[STUDENT_EMAIL_I].lower() == STUDENT_EMAIL_S.lower()):
            # remove and return false (therefore exiting early) if roster does not have expected headers
            print('COMPARED {} vs. {}'.format(header_row[C_ID_I].lower(),
                                              C_ID_S.lower()))
            print('COMPARED {} vs. {}'.format(header_row[SUBJECT_I].lower(),
                                              SUBJECT_S.lower()))
            print('COMPARED {} vs. {}'.format(header_row[COURSE_I].lower(),
                                              COURSE_S.lower()))
            print('COMPARED {} vs. {}'.format(header_row[PROF_NAME_I].lower(),
                                              PROF_NAME_S.lower()))
            print('COMPARED {} vs. {}'.format(header_row[PROF_EMAIL_I].lower(),
                                              PROF_EMAIL_S.lower()))
            print('COMPARED {} vs. {}'.format(header_row[S_ID_I].lower(),
                                              S_ID_S.lower()))
            print('COMPARED {} vs. {}'.format(
                header_row[STUDENT_EMAIL_I].lower(), STUDENT_EMAIL_S.lower()))
            os.remove(csv_filepath)
            return False

        prev_c_id = -1
        print(log_header('ROSTER UPLOADED - PARSING'))
        student_threads = list()
        for row in rows:
            # add sections - addSection() avoids repeats
            subject = row[SUBJECT_I]
            course_num = row[COURSE_I]
            c_id = removeZeroes(row[C_ID_I])
            prof_name = row[PROF_NAME_I]
            prof_email = row[PROF_EMAIL_I]
            # only attempt to add a new section if moved onto new valid section ID with properly formatted SCU email
            if prev_c_id != c_id and isValidEmail(prof_email) and isID(c_id):
                addSection(subject, course_num, c_id, prof_name, prof_email)
                section_count += 1
                prev_c_id = c_id
            # make one student per row
            s_id = removeZeroes(row[S_ID_I])
            stud_email = row[STUDENT_EMAIL_I]
            if isValidEmail(stud_email) and isID(s_id) and prev_c_id == c_id:
                # only add student if has correctly formatted SCU email
                # prev_c_id only matches c_id if section with c_id was created - if not, don't add student because his/her section DNE in DB
                t = Thread(target=addStudent,
                           args=(current_app._get_current_object(), s_id, c_id,
                                 stud_email))
                student_threads.append(t)
                t.start()
        # make sure all adding threads finish before exiting (because emailing is called next and it might be called before all addStudent threads finish)
        for t in student_threads:
            t.join()
            student_count += 1
    os.remove(csv_filepath)
    print('ADDED {} SECTIONS AND {} STUDENTS'.format(section_count,
                                                     student_count))
    return True