def _get_next_step(current_step): # I know this kind of logic requires O(2^N) different cases, but right now there's only 1 config # option that affects this list (it's the student photos enable/disable), so it's simplest to # express the 2 alternatives this way. # # When we add more features to the onboarding process, you can come up with a better, # generalized way of determining the onboarding steps. if config.student_photos_enabled: steps = ["onboarding.student_id", "onboarding.photo"] else: steps = ["onboarding.student_id"] if current_step is None: return steps[0] step_i = steps.index(current_step) if step_i == len(steps) - 1: # This is the final step of onboarding. # Authenticate the user and redirect them to the dashboard. with DbCursor() as c: user = get_user_by_github(c, github_username()) assert user user_id_, _, _, _, _, _ = user authenticate_as_user(user_id_) return "onboarding.welcome" else: return steps[step_i + 1]
def photo(): github = github_username() if request.method == "GET": return render_template("onboarding/photo.html", github=github) try: with DbCursor() as c: user = get_user_by_github(c, github) user_id, _, _, _, _, _ = user photo_base64 = request.form.get("f_photo_cropped") photo_prefix = "data:image/jpeg;base64," if not photo_base64: fail_validation( "No photo submitted. Please choose a photo.") if not photo_base64.startswith(photo_prefix): fail_validation( "Unrecognized photo format. (Potential autograder bug?)" ) photo_binary = buffer( binascii.a2b_base64(photo_base64[len(photo_prefix):])) if len(photo_binary) > 2**21: fail_validation( "Photo exceeds maximum allowed size (2MiB).") c.execute("UPDATE users SET photo = ? WHERE id = ?", [photo_binary, user_id]) return redirect(url_for(_get_next_step("onboarding.photo"))) except ValidationError as e: return redirect_with_error(url_for("onboarding.photo"), e)
def student_id(): github = github_username() if request.method == "GET": return render_template("onboarding/student_id.html", github=github) try: mailer_job = None github_job = None with DbCursor() as c: user = get_user_by_github(c, github) if user: return redirect(url_for("dashboard.index")) student_id = request.form.get("f_student_id") if not student_id: fail_validation("Student ID is required") user = get_user_by_student_id(c, student_id) if not user: fail_validation("Student not found with that student ID") user_id, name, _, login, old_github, email = user if old_github: fail_validation( "Another GitHub account has been associated with that student " "ID already.") if not name: fail_validation( "There is no name associated with this account. (Contact your TA?)" ) if not login: fail_validation( "There is no login associated with this account. (Contact your " "TA?)") if not email: fail_validation( "There is no email associated with this account. (Contact your " "TA?)") c.execute('''UPDATE users SET github = ? WHERE sid = ?''', [github, student_id]) if not config.github_read_only_mode: github_job = repomanager_queue.create(c, "assign_repo", (login, [github])) if config.mailer_enabled: if config.inst_account_enabled: attachments = [("pdf", get_inst_account_form_path(login))] else: attachments = [] email_payload = create_email( "onboarding_confirm", email, "%s Autograder Registration" % config.course_number, _attachments=attachments, name=name, login=login, inst_account_enabled=config.inst_account_enabled) mailer_job = mailer_queue.create(c, "send", email_payload) if config.mailer_enabled and mailer_job: mailer_queue.enqueue(mailer_job) if not config.github_read_only_mode and github_job: repomanager_queue.enqueue(github_job) return redirect(url_for(_get_next_step("onboarding.student_id"))) except ValidationError as e: return redirect_with_error(url_for("onboarding.student_id"), e)
def students_one(identifier, type_): with DbCursor() as c: student = None if type_ in ("id", "user_id"): student = get_user_by_id(c, identifier) elif type_ in ("github", "_github_explicit"): student = get_user_by_github(c, identifier) elif type_ == "login": student = get_user_by_login(c, identifier) elif type_ in ("sid", "student_id"): student = get_user_by_student_id(c, identifier) if student is None: abort(404) user_id, _, _, _, _, _ = student super_ = get_super(c, user_id) photo = None if student_photos_enabled: photo = get_photo(c, user_id) c.execute( '''SELECT users.id, users.name, users.github, groupsusers.`group` FROM groupsusers LEFT JOIN users ON groupsusers.user = users.id WHERE groupsusers.`group` IN (SELECT `group` FROM groupsusers WHERE user = ?)''', [user_id]) groups = OrderedDict() for g_user_id, g_name, g_github, g_group in c.fetchall(): groups.setdefault(g_group, []).append( (g_user_id, g_name, g_github)) grouplimit = get_grouplimit(c, user_id) c.execute( '''SELECT transaction_name, source, assignment, score, slipunits, updated, description FROM gradeslog WHERE user = ? ORDER BY updated DESC''', [user_id]) entries = c.fetchall() full_scores = { assignment.name: assignment.full_score for assignment in config.assignments } events = [entry + (full_scores.get(entry[2]), ) for entry in entries] c.execute( "SELECT assignment, score, slipunits, updated FROM grades WHERE user = ?", [user_id]) grade_info = { assignment: (score, slipunits, updated) for assignment, score, slipunits, updated in c.fetchall() } assignments_info = [(a.name, a.full_score, a.weight, a.due_date) + grade_info.get(a.name, (None, None, None)) for a in config.assignments] return render_template("ta/students_one.html", student=student, super_=super_, photo=photo, groups=groups.items(), grouplimit=grouplimit, events=events, assignments_info=assignments_info, **_template_common())
def students_one(identifier, type_): with DbCursor() as c: student = None if type_ in ("id", "user_id"): student = get_user_by_id(c, identifier) elif type_ in ("github", "_github_explicit"): student = get_user_by_github(c, identifier) elif type_ == "login": student = get_user_by_login(c, identifier) elif type_ in ("sid", "student_id"): student = get_user_by_student_id(c, identifier) if student is None: abort(404) user_id, _, _, _, _, _ = student super_ = get_super(c, user_id) photo = None if student_photos_enabled: photo = get_photo(c, user_id) c.execute( """SELECT users.id, users.name, users.github, groupsusers.`group` FROM groupsusers LEFT JOIN users ON groupsusers.user = users.id WHERE groupsusers.`group` IN (SELECT `group` FROM groupsusers WHERE user = ?)""", [user_id], ) groups = OrderedDict() for g_user_id, g_name, g_github, g_group in c.fetchall(): groups.setdefault(g_group, []).append((g_user_id, g_name, g_github)) grouplimit = get_grouplimit(c, user_id) c.execute( """SELECT transaction_name, source, assignment, score, slipunits, updated, description FROM gradeslog WHERE user = ? ORDER BY updated DESC""", [user_id], ) entries = c.fetchall() full_scores = {assignment.name: assignment.full_score for assignment in config.assignments} events = [entry + (full_scores.get(entry[2]),) for entry in entries] c.execute("SELECT assignment, score, slipunits, updated FROM grades WHERE user = ?", [user_id]) grade_info = {assignment: (score, slipunits, updated) for assignment, score, slipunits, updated in c.fetchall()} assignments_info = [ (a.name, a.full_score, a.weight, a.due_date) + grade_info.get(a.name, (None, None, None)) for a in config.assignments ] return render_template( "ta/students_one.html", student=student, super_=super_, photo=photo, groups=groups.items(), grouplimit=grouplimit, events=events, assignments_info=assignments_info, **_template_common() )
def group_create(): if not config.groups_enabled: abort(404) try: githubs = request.form.getlist("f_github") mailer_jobs = [] with DbCursor() as c: student = _get_student(c) inviter_user_id, inviter_name, _, _, inviter_github, _ = student grouplimit = get_grouplimit(c, inviter_user_id) if grouplimit < 1: fail_validation("You are in too many groups already") invitees = [] invitation_user_ids = set() for github in githubs: if not github: continue invitee = get_user_by_github(c, github) if invitee is None: fail_validation("GitHub username not found: %s" % github) invitee_id, _, _, _, _, _ = invitee if invitee_id == inviter_user_id: continue if invitee_id in invitation_user_ids: continue invitation_user_ids.add(invitee_id) invitees.append(invitee) if not config.group_min_size <= len(invitation_user_ids) + 1 <= config.group_max_size: fail_validation("You need between %d and %d people in your group" % ( config.group_min_size, config.group_max_size)) if config.mailer_enabled: for _, invitee_name, _, _, _, invitee_email in invitees: email_payload = create_email("group_invite", invitee_email, "%s has invited you to a group" % inviter_name, inviter_name=inviter_name, inviter_github=inviter_github, invitee_name=invitee_name, invitees=invitees) mailer_job = mailer_queue.create(c, "send", email_payload) mailer_jobs.append(mailer_job) invitation_id = get_next_autoincrementing_value(c, "group_next_invitation_id") for invitation_user_id in invitation_user_ids: c.execute("INSERT INTO invitations (invitation_id, user, status) VALUES (?, ?, ?)", [invitation_id, invitation_user_id, INVITED]) c.execute("INSERT INTO invitations (invitation_id, user, status) VALUES (?, ?, ?)", [invitation_id, inviter_user_id, ACCEPTED]) modify_grouplimit(c, inviter_user_id, -1) finalize_group_if_ready(c, invitation_id) if config.mailer_enabled: for mailer_job in mailer_jobs: mailer_queue.enqueue(mailer_job) return redirect(url_for("dashboard.group")) except ValidationError as e: return redirect_with_error(url_for("dashboard.group"), e)
def student_id(): github = github_username() if request.method == "GET": return render_template("onboarding/student_id.html", github=github) try: mailer_job = None github_job = None with DbCursor() as c: user = get_user_by_github(c, github) if user: return redirect(url_for("dashboard.index")) student_id = request.form.get("f_student_id") if not student_id: fail_validation("Student ID is required") user = get_user_by_student_id(c, student_id) if not user: fail_validation("Student not found with that student ID") user_id, name, _, login, old_github, email = user if old_github: fail_validation("Another GitHub account has been associated with that student " "ID already.") if not name: fail_validation("There is no name associated with this account. (Contact your TA?)") if not login: fail_validation("There is no login associated with this account. (Contact your " "TA?)") if not email: fail_validation("There is no email associated with this account. (Contact your " "TA?)") c.execute('''UPDATE users SET github = ? WHERE sid = ?''', [github, student_id]) if not config.github_read_only_mode: github_job = repomanager_queue.create(c, "assign_repo", (login, [github])) if config.mailer_enabled: if config.inst_account_enabled: attachments = [("pdf", get_inst_account_form_path(login))] else: attachments = [] email_payload = create_email("onboarding_confirm", email, "%s Autograder Registration" % config.course_number, _attachments=attachments, name=name, login=login, inst_account_enabled=config.inst_account_enabled) mailer_job = mailer_queue.create(c, "send", email_payload) if config.mailer_enabled and mailer_job: mailer_queue.enqueue(mailer_job) if not config.github_read_only_mode and github_job: repomanager_queue.enqueue(github_job) return redirect(url_for(_get_next_step("onboarding.student_id"))) except ValidationError as e: return redirect_with_error(url_for("onboarding.student_id"), e)
def _get_current_step(): if is_ta(): return "ta.index" if user_id(): return "dashboard.index" github = github_username() if not github: return "onboarding.log_in" with DbCursor() as c: user = get_user_by_github(c, github) if not user: return "onboarding.student_id" user_id_, _, _, _, _, _ = user if config.student_photos_enabled: photo = get_photo(c, user_id_) if not photo: return "onboarding.photo" authenticate_as_user(user_id_) return "dashboard.index"
def photo(): github = github_username() if request.method == "GET": return render_template("onboarding/photo.html", github=github) try: with DbCursor() as c: user = get_user_by_github(c, github) user_id, _, _, _, _, _ = user photo_base64 = request.form.get("f_photo_cropped") photo_prefix = "data:image/jpeg;base64," if not photo_base64: fail_validation("No photo submitted. Please choose a photo.") if not photo_base64.startswith(photo_prefix): fail_validation("Unrecognized photo format. (Potential autograder bug?)") photo_binary = buffer(binascii.a2b_base64(photo_base64[len(photo_prefix):])) if len(photo_binary) > 2**21: fail_validation("Photo exceeds maximum allowed size (2MiB).") c.execute("UPDATE users SET photo = ? WHERE id = ?", [photo_binary, user_id]) return redirect(url_for(_get_next_step("onboarding.photo"))) except ValidationError as e: return redirect_with_error(url_for("onboarding.photo"), e)
def group_create(): if not config.groups_enabled: abort(404) try: githubs = request.form.getlist("f_github") github_job = None mailer_jobs = [] with DbCursor() as c: student = _get_student(c) inviter_user_id, inviter_name, _, _, inviter_github, _ = student grouplimit = get_grouplimit(c, inviter_user_id) if grouplimit < 1: fail_validation("You are in too many groups already") invitees = [] invitation_user_ids = set() for github in githubs: if not github: continue invitee = get_user_by_github(c, github) if invitee is None: fail_validation("GitHub username not found: %s" % github) invitee_id, _, _, _, _, _ = invitee if invitee_id == inviter_user_id: continue if invitee_id in invitation_user_ids: continue invitation_user_ids.add(invitee_id) invitees.append(invitee) if not config.group_min_size <= len( invitation_user_ids) + 1 <= config.group_max_size: fail_validation( "You need between %d and %d people in your group" % (config.group_min_size, config.group_max_size)) if config.mailer_enabled: for _, invitee_name, _, _, _, invitee_email in invitees: email_payload = create_email( "group_invite", invitee_email, "%s has invited you to a group" % inviter_name, inviter_name=inviter_name, inviter_github=inviter_github, invitee_name=invitee_name, invitees=invitees) mailer_job = mailer_queue.create(c, "send", email_payload) mailer_jobs.append(mailer_job) invitation_id = get_next_autoincrementing_value( c, "group_next_invitation_id") for invitation_user_id in invitation_user_ids: c.execute( "INSERT INTO invitations (invitation_id, user, status) VALUES (?, ?, ?)", [invitation_id, invitation_user_id, INVITED]) c.execute( "INSERT INTO invitations (invitation_id, user, status) VALUES (?, ?, ?)", [invitation_id, inviter_user_id, ACCEPTED]) modify_grouplimit(c, inviter_user_id, -1) group_name, group_members = finalize_group_if_ready( c, invitation_id) if group_name: if not config.github_read_only_mode: group_githubs = [] for _, _, _, github in group_members: assert github, "GitHub handle is empty" group_githubs.append(github) github_job = repomanager_queue.create( c, "assign_repo", (group_name, group_githubs)) if config.mailer_enabled: for _, name, email, github in group_members: email_payload = create_email( "group_confirm", email, "%s has been created" % group_name, group_name=group_name, name=name, group_members=group_members) mailer_job = mailer_queue.create( c, "send", email_payload) mailer_jobs.append(mailer_job) if config.mailer_enabled: for mailer_job in mailer_jobs: mailer_queue.enqueue(mailer_job) if github_job and not config.github_read_only_mode: repomanager_queue.enqueue(github_job) return redirect(url_for("dashboard.group")) except ValidationError as e: return redirect_with_error(url_for("dashboard.group"), e)