Skip to content

Commit

Permalink
Merge pull request #641 from Cal-CS-61A-Staff/v3/knrafto/submissions
Browse files Browse the repository at this point in the history
Group backups and submissions
  • Loading branch information
Sumukh committed Jan 24, 2016
2 parents ab90f42 + 1475fc0 commit 739d34a
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 225 deletions.
22 changes: 7 additions & 15 deletions manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from flask.ext.script.commands import ShowUrls, Clean
from flask.ext.migrate import Migrate, MigrateCommand
from server import create_app
from server.models import db, User, Course, Assignment, Participant, \
Backup, Submission, Message
from server.models import db, User, Course, Assignment, Enrollment, \
Backup, Message

# default to dev config because no one should use this in
# production anyway.
Expand All @@ -34,22 +34,14 @@ def make_shell_context():

def make_backup(user, assign, time, messages, submit=True):
backup = Backup(client_time=time,
submitter=user.id,
assignment=assign.id, submit=submit)
submitter=user,
assignment=assign, submit=submit)
messages = [Message(kind=k, backup=backup,
contents=m) for k, m in messages.items()]
backup.messages = messages
db.session.add_all(messages)
db.session.add(backup)

if submit:
subm = Submission(submitter=user.id,
assignment=assign.id, backup=backup)
db.session.add(subm)




@manager.command
def seed():
""" Create default records for development.
Expand Down Expand Up @@ -83,14 +75,14 @@ def seed():
make_backup(staff_member, assign2, time, messages)
db.session.commit()

staff = Participant(user_id=staff_member.id, course_id=courses[0].id,
staff = Enrollment(user_id=staff_member.id, course_id=courses[0].id,
role="staff")
db.session.add(staff)
staff_also_student = Participant(user_id=staff_member.id,
staff_also_student = Enrollment(user_id=staff_member.id,
course_id=courses[1].id, role="student")
db.session.add(staff_also_student)

student_enrollment = [Participant(user_id=student.id, role="student",
student_enrollment = [Enrollment(user_id=student.id, role="student",
course_id=courses[0].id) for student in students]
db.session.add_all(student_enrollment)

Expand Down
14 changes: 7 additions & 7 deletions server/controllers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytz
import csv

from server.models import User, Course, Assignment, Participant, db
from server.models import User, Course, Assignment, Enrollment, db
from server.constants import STAFF_ROLES, VALID_ROLES, STUDENT_ROLE
import server.forms as forms

Expand Down Expand Up @@ -139,7 +139,7 @@ def enrollment(cid):
single_form = forms.EnrollmentForm(prefix="single")
if single_form.validate_on_submit():
email, role = single_form.email.data, single_form.role.data
Participant.enroll_from_form(cid, single_form)
Enrollment.enroll_from_form(cid, single_form)
flash("Added {email} as {role}".format(email=email, role=role), "success")

query = request.args.get('query', '').strip()
Expand All @@ -150,15 +150,15 @@ def enrollment(cid):
find_student = User.query.filter_by(email=query)
student = find_student.first()
if student:
students = Participant.query.filter_by(course_id=cid, role=STUDENT_ROLE,
students = Enrollment.query.filter_by(course_id=cid, role=STUDENT_ROLE,
user_id=student.id).paginate(page=page, per_page=1)
else:
flash("No student found with email {}".format(query), "warning")
if not students:
students = Participant.query.filter_by(course_id=cid,
students = Enrollment.query.filter_by(course_id=cid,
role=STUDENT_ROLE).paginate(page=page, per_page=5)
staff = Participant.query.filter(Participant.course_id == cid,
Participant.role.in_(STAFF_ROLES)).all()
staff = Enrollment.query.filter(Enrollment.course_id == cid,
Enrollment.role.in_(STAFF_ROLES)).all()

return render_template('staff/course/enrollment.html',
enrollments=students, staff=staff, query=query,
Expand All @@ -174,7 +174,7 @@ def batch_enroll(cid):
courses, current_course = get_courses(cid)
batch_form = forms.BatchEnrollmentForm()
if batch_form.validate_on_submit():
new, updated = Participant.enroll_from_csv(cid, batch_form)
new, updated = Enrollment.enroll_from_csv(cid, batch_form)
msg = "Added {new}, Updated {old} students".format(new=new, old=updated)
flash(msg, "success")
return redirect(url_for(".enrollment", cid=cid))
Expand Down
73 changes: 11 additions & 62 deletions server/controllers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,14 @@ def get(self):
# TODO Permsisions for API actions


def make_backup(user, assignment, messages, submit):
def make_backup(user, assignment_id, messages, submit):
"""
Create backup with message objects.
:param user: (object) caller
:param assignment: (int) Assignment ID
:param assignment_id: (int) Assignment ID
:param messages: Data content of backup/submission
:param submit: Whether this backup is a submission to be graded
:param submitter: (object) caller or submitter
:return: (Backup) backup
"""

Expand All @@ -187,8 +186,8 @@ def make_backup(user, assignment, messages, submit):
else:
client_time = datetime.datetime.now()

backup = models.Backup(client_time=client_time, submitter=user.id,
assignment=assignment, submit=submit)
backup = models.Backup(client_time=client_time, submitter=user,
assignment_id=assignment_id, submit=submit)
messages = [models.Message(kind=k, backup=backup,
contents=m) for k, m in messages.items()]
backup.messages = messages
Expand Down Expand Up @@ -239,7 +238,7 @@ class BackupSchema(APISchema):
}

post_fields = {
'email': fields.Integer,
'email': fields.String,
'key': fields.String,
'course': fields.String,
'assign': fields.String,
Expand All @@ -251,11 +250,8 @@ def __init__(self):
help='Name of Assignment')
self.parser.add_argument('messages', type=dict, required=True,
help='Backup Contents as JSON')

# Optional - probably not needed now that there are two endpoints
self.parser.add_argument('submit', type=bool,
self.parser.add_argument('submit', type=bool, default=False,
help='Flagged as a submission')
self.parser.add_argument('submitter', help='Name of Assignment')

def store_backup(self, user):
args = self.parse_args()
Expand All @@ -266,18 +262,6 @@ def store_backup(self, user):
return backup


class SubmissionSchema(BackupSchema):

get_fields = {
'id': fields.Integer,
'assignment': fields.Integer,
'submitter': fields.Integer,
'backup': fields.Nested(BackupSchema.get_fields),
'flagged': fields.Boolean,
'revision': fields.Boolean,
'created': fields.DateTime(dt_format='rfc822')
}

class CourseSchema(APISchema):
get_fields = {
'id': fields.Integer,
Expand All @@ -301,6 +285,7 @@ class EnrollmentSchema(APISchema):
}


# TODO: should be two classes, one for /backups/ and one for /backups/<int:key>/
class Backup(Resource):
""" Submission retreival resource.
Authenticated. Permissions: >= User/Staff
Expand All @@ -326,43 +311,8 @@ def post(self, user, key=None):
return {
'email': current_user.email,
'key': backup.id,
'course': backup.assign.course_id,
'assign': backup.assignment
}



class Submission(Resource):
""" Submission resource.
Authenticated. Permissions: >= Student/Staff
Used by: Ok Client Submission.
"""
model = models.Submission
schema = SubmissionSchema()

@marshal_with(schema.get_fields)
def get(self, user, key=None):
if key is None:
restful.abort(405)
submission = self.model.query.filter_by(id=key).first()
# TODO CHECK .user obj
if submission.user != user or not user.is_admin:
restful.abort(403)
return submission

def post(self, user, key=None):
if key is not None:
restful.abort(405)
backup = self.schema.store_backup(user)
subm = models.Submission(backup_id=backup.id, assignment=backup.assignment,
submitter=user.id)
models.db.session.add(subm)
models.db.session.commit()
return {
'email': current_user.email,
'key': backup.id,
'course': backup.assign.course_id,
'assign': backup.assignment
'course': backup.assignment.course_id,
'assignment': backup.assignment_id
}

class Score(Resource):
Expand All @@ -387,7 +337,7 @@ class Enrollment(PublicResource):
Authenticated. Permissions: >= User
Used by: Ok Client Auth
"""
model = models.Participant
model = models.Enrollment
schema = EnrollmentSchema()

@marshal_with(schema.get_fields)
Expand All @@ -400,7 +350,6 @@ def get(self, email):

api.add_resource(v3Info, '/v3')

api.add_resource(Submission, '/v3/submission', '/v3/submission/<int:key>')
api.add_resource(Backup, '/v3/backup', '/v3/backup/<int:key>')
api.add_resource(Backup, '/v3/backups/', '/v3/backups/<int:key>/')
api.add_resource(Score, '/v3/score')
api.add_resource(Enrollment, '/v3/enrollment/<string:email>')
10 changes: 5 additions & 5 deletions server/controllers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ def use_testing_login():
return current_app.config.get('TESTING_LOGIN', False) and \
current_app.config.get('ENV') != 'prod'

@auth.route("/login")
@auth.route("/login/")
def login():
"""
Authenticates a user with an access token using Google APIs.
"""
return google_auth.authorize(callback=url_for('.authorized', _external=True))

@auth.route('/login/authorized')
@auth.route('/login/authorized/')
@google_auth.authorized_handler
def authorized(resp):
if resp is None or 'access_token' not in resp:
Expand All @@ -118,20 +118,20 @@ def authorized(resp):
# Backdoor log in if you want to impersonate a user.
# Will not give you a Google auth token.
# Requires that TESTING_LOGIN = True in the config and we must not be running in prod.
@auth.route('/testing-login')
@auth.route('/testing-login/')
def testing_login():
if not use_testing_login():
abort(404)
return render_template('testing-login.html', callback=url_for(".testing_authorized"))

@auth.route('/testing-login/authorized', methods=['POST'])
@auth.route('/testing-login/authorized/', methods=['POST'])
def testing_authorized():
if not use_testing_login():
abort(404)
user = user_from_email(request.form['email'])
return authorize_user(user)

@auth.route("/logout")
@auth.route("/logout/")
def logout():
logout_user()
session.pop('google_token', None)
Expand Down
36 changes: 18 additions & 18 deletions server/controllers/student.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,17 @@ def wrapper(*args, **kwargs):
@is_enrolled
def course(cid):
course = Course.query.get(cid)
# TODO : Should consider group submissions as well.
user_id = current_user.id
def assignment_info(assignment):
# TODO does this make O(n) db queries?
# TODO need group info too
user_ids = assignment.active_user_ids(current_user.id)
final_submission = assignment.final_submission(user_ids)
submission_time = final_submission and final_submission.client_time
return assignment, submission_time

assignments = {
'active': [(a, a.submission_time(user_id), a.group(user_id)) \
for a in course.assignments if a.active],
'inactive': [(a, a.submission_time(user_id), a.group(user_id)) \
for a in course.assignments if not a.active]
'active': [assignment_info(a) for a in course.assignments if a.active],
'inactive': [assignment_info(a) for a in course.assignments if not a.active]
}
return render_template('student/course/index.html', course=course,
**assignments)
Expand All @@ -74,15 +78,11 @@ def assignment(cid, aid):
assgn = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none()
if assgn:
course = assgn.course
group = Group.lookup(current_user, assgn)
if group:
# usr_ids = [u.id for u in group.users()]
# TODO : Fetch backups from group.
pass
backups = assgn.backups(current_user.id).limit(5).all()
subms = assgn.submissions(current_user.id).limit(5).all()
flagged = any([s.flagged for s in subms])
print(flagged)
user_ids = assgn.active_user_ids(current_user.id)
backups = assgn.backups(user_ids).limit(5).all()
subms = assgn.submissions(user_ids).limit(5).all()
final_submission = assgn.final_submission(user_ids)
flagged = final_submission and final_submission.flagged
return render_template('student/assignment/index.html', course=course,
assignment=assgn, backups=backups, subms=subms, flagged=flagged)
else:
Expand All @@ -96,10 +96,10 @@ def code(cid, aid, bid):
assgn = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none()
if assgn:
course = assgn.course
group = Group.lookup(current_user, assgn)
user_ids = assgn.active_user_ids(current_user.id)
backup = Backup.query.get(bid)
if backup and backup.can_view(current_user, group, assgn):
submitter = User.query.get(backup.submitter)
if backup and backup.can_view(current_user, user_ids, course):
submitter = User.query.get(backup.submitter_id)
file_contents = [m for m in backup.messages if
m.kind == "file_contents"]
if file_contents:
Expand Down
Loading

0 comments on commit 739d34a

Please sign in to comment.