def submit_evaluations(self): me = request.identity['user'] students_allowed = set(user.id for user in self.group.users) for user_id, score in request.POST.items(): user_id = int(user_id) if user_id not in students_allowed: abort(403, "You cannot submit an evaluation to this user") score = float(score) if score < 0: abort(403, "Negative contributions are not allowed") e = (DBSession.query(Evaluation) .filter(Evaluation.from_student_id == me.id) .filter(Evaluation.to_student_id == user_id) .filter(Evaluation.group_id == self.group.id) .one_or_none()) if not e: e = Evaluation( from_student_id=me.id, to_student_id=user_id, group=self.group, score=score) else: e.score = score DBSession.add(e) flash('Your evaluations have been saved. Thank you.', 'success') redirect('/group/{}'.format(self.group.id))
def authenticate(self, environ, identity): try: ticket = identity['login'] except KeyError: return None if identity['identifier'] != 'mpapi': return None r = requests.post(self.mpapi_fetch, data={'tkt': ticket}) r.raise_for_status() data = r.json() if data['result'] != 'success': raise ValueError('MPAPI Failure') username = data['uid'] identity['user'] = User.from_username(username) if identity['user']: return username else: user = user_from_mpapi_attributes(data['attributes']) DBSession.add(user) DBSession.flush() transaction.commit() return username
def ov(self, output_id): output = DBSession.query(Output).get(int(output_id)) if output.group.competition_id != self.competition.id: abort(404) if not output.active: abort(404, "This output is no longer active.") user = request.identity and request.identity['user'] if user: group = (DBSession.query(Group).filter( Group.competition_id == self.competition.id).filter( Group.users.any(id=user.id)).first()) else: group = None message = request.POST.get('message') if group and message: if output.use_ground_truth: abort(403, "The instructor has already reviewed this output.") output.use_ground_truth = True protest = Protest( message=message, accepted=output.verification != output.ground_truth, submitter=group, output=output) DBSession.add(output) DBSession.add(protest) flash('Your protest has been submitted.', 'success') return { 'output': output, 'group': group, 'competition': self.competition }
def automatic_verification(self): outputs = (DBSession .query(Output) .join(Output.input) .filter(Input.group_id == self.group.id)) for output in outputs: output.verification = output.ground_truth DBSession.add(output) DBSession.flush()
def input_upload(self, input_upload=None, team_name=None): user = request.identity['user'] if not user.admin and not self.group.competition.input_upload_open: abort(403, "Sorry, input upload stage is closed.") if hasattr(input_upload, "file"): try: contents = input_upload.file.read().decode("utf-8") except UnicodeDecodeError: flash('Your input contains invalid characters. ' 'Please correct and try uploading again.', 'danger') redirect(self.base_url) problem = problemlib.load_problem(self.group.competition) try: input = problem.parse_input(StringIO(contents)) except problemlib.FileFormatError as e: flash( f"Your input is not valid: {e}. Please correct and upload again.", "danger", ) redirect(self.base_url) reformatted_contents = StringIO() input.write(reformatted_contents) f = FileIntent( BytesIO(reformatted_contents.getvalue().encode('utf-8')), 'input_group{}.txt'.format(self.group.id), 'application/octet-stream') if self.group.input is None: iput = Input(data=f, group=self.group) DBSession.add(iput) else: self.group.input.data = f DBSession.flush() if team_name is not None: if len(team_name) >= 100: flash('Your team name has been rejected, as it is too long.', 'danger') redirect(self.base_url) if not team_name: flash('You must set a team name.', 'danger') redirect(self.base_url) self.group.name = team_name flash('Thank you. Please return here for output upload on {}' .format(ftime(self.group.competition.output_upload_begins)), 'success') redirect(self.base_url)
def input_upload(self, input_upload=None, team_name=None): user = request.identity['user'] if not user.admin and not self.group.competition.input_upload_open: abort(403, "Sorry, input upload stage is closed.") if hasattr(input_upload, "file"): try: contents = file_normalize(input_upload.file.read()) except UnicodeDecodeError: flash( 'Your input contains invalid characters. ' 'Please correct and try uploading again.', 'danger') redirect(self.base_url) if len(contents) > 1E6: abort(400, "Your input exceeds the maxiumum size.") verif_mod = self.group.competition.input_verifier.module try: verif_mod.verify(StringIO(contents)) except verif_mod.VerificationError as e: flash( 'Your input has been rejected for the following reason: ' '{}. Please correct and try uploading again.'.format(e), 'danger') redirect(self.base_url) f = FileIntent(BytesIO(contents.encode('utf-8')), 'input_group{}.txt'.format(self.group.id), 'application/octet-stream') if self.group.input is None: iput = Input(data=f, group=self.group) DBSession.add(iput) else: self.group.input.data = f DBSession.flush() if team_name is not None: if len(team_name) >= 100: flash('Your team name has been rejected, as it is too long.', 'danger') redirect(self.base_url) if not team_name: flash('You must set a team name.', 'danger') redirect(self.base_url) self.group.name = team_name flash( 'Thank you. Please return here for output upload on {}'.format( ftime(self.group.competition.output_upload_begins)), 'success') redirect(self.base_url)
def new_user_by_username(self, username): url = f"{self.mpapi_url}/uid/{username}" r = requests.get(url) r.raise_for_status() data = r.json() if data["result"] != "success": raise ValueError("MPAPI Failure. User {username} may not exist?") attributes = data["attributes"] uid = attributes["uidNumber"] full_name = f"{attributes['first']} {attributes['sn']}" email = attributes["mail"] user = User(id=uid, username=username, full_name=full_name, email=email) DBSession.add(user) return user
def ov(self, output_id): output = DBSession.query(Output).get(int(output_id)) if output.group.competition_id != self.competition.id: abort(404) if not output.active: abort(404, "This output is no longer active.") user = request.identity and request.identity['user'] if user: group = (DBSession.query(Group).filter( Group.competition_id == self.competition.id).filter( Group.users.any(id=user.id)).first()) else: group = None problem = problemlib.load_problem(self.competition) problem_module = problem.get_module() show_input_downloads = ((user and user.admin) or not output.group.competition.archived) message = request.POST.get('message') if group and message: if output.use_ground_truth: abort(403, "The instructor has already reviewed this output.") output.use_ground_truth = True protest = Protest( message=message, accepted=output.verification != output.ground_truth, submitter=group, output=output) DBSession.add(output) DBSession.add(protest) flash('Your protest has been submitted.', 'success') return { "output": output, "score": problem_module.Output.repr_score(output.score), "group": group, "show_input_downloads": show_input_downloads, "competition": self.competition, }
def authenticate(self, environ, identity): try: ticket = identity['login'] except KeyError: return None if identity['identifier'] != 'mpapi': return None r = requests.post(self.mpapi_fetch, data={'tkt': ticket}) r.raise_for_status() data = r.json() if data['result'] != 'success': raise ValueError('MPAPI Failure') username = data['uid'] attributes = data['attributes'] uid = attributes['uidNumber'] full_name = '{} {}'.format(attributes['first'], attributes['sn']) email = attributes['mail'] user = DBSession.query(User).filter(User.id == uid).one_or_none() if user: # Update attributes in case of username/full name change user.username = username user.full_name = full_name user.email = email else: user = User(id=uid, username=username, full_name=full_name, email=email) DBSession.add(user) DBSession.flush() transaction.commit() return username
def submit_output(self, to_group, output_file=None): user = request.identity['user'] to_group = DBSession.query(Group).get(to_group) if not to_group: abort(404, "No such group") if to_group.competition_id != self.group.competition_id: abort(400, "Cannot submit to a group in another competition") if not hasattr(output_file, "file"): abort(400, "Must include file in submission") comp = self.group.competition existing = (DBSession.query(Output) .filter(Output.group_id == self.group.id) .filter(Output.input_id == to_group.input.id) .filter(Output.active == True) .one_or_none()) if not (user.admin or comp.output_upload_open or (comp.resolution_open and existing is not None and not existing.use_ground_truth)): abort(403, "Forbidden to upload this output at this time") try: contents = output_file.file.read().decode("utf-8") except UnicodeDecodeError: return {'status': 'error', 'msg': 'Output contains invalid characters.'} problem = problemlib.load_problem(comp) try: output = problem.parse_output(to_group.input.data.file, StringIO(contents)) except problemlib.FileFormatError as e: return { "status": "error", "msg": f"Output has formatting errors: {e}", } new_contents = StringIO() output.write(new_contents) f = FileIntent( BytesIO(new_contents.getvalue().encode("utf-8")), 'output_from_{}_to_{}.txt'.format(self.group.id, to_group.id), 'application/octet-stream') try: output.verify() except problemlib.VerificationError: ground_truth = VerificationStatus.rejected except Exception: ground_truth = VerificationStatus.waiting else: ground_truth = VerificationStatus.accepted use_ground_truth = ( not comp.verification_begins or datetime.datetime.now() >= comp.verification_begins ) if existing: if comp.resolution_open: existing.active = False else: DBSession.delete(existing) db_output = Output( data=f, group=self.group, input=to_group.input, score=output.score, original=comp.output_upload_open, ground_truth=ground_truth, use_ground_truth=use_ground_truth, ) DBSession.add(db_output) DBSession.flush() return {'status': 'success', 'url': db_output.data.url}
def submit_output(self, to_group, output_file=None): user = request.identity['user'] to_group = DBSession.query(Group).get(to_group) if not to_group: abort(404, "No such group") if to_group.competition_id != self.group.competition_id: abort(400, "Cannot submit to a group in another competition") if not hasattr(output_file, "file"): abort(400, "Must include file in submission") comp = self.group.competition existing = (DBSession.query(Output).filter( Output.group_id == self.group.id).filter( Output.input_id == to_group.input.id).filter( Output.active == True).one_or_none()) if not (user.admin or comp.output_upload_open or (comp.resolution_open and existing is not None and not existing.use_ground_truth)): abort(403, "Forbidden to upload this output at this time") try: contents = file_normalize(output_file.file.read()) except UnicodeDecodeError: return { 'status': 'error', 'msg': 'Output contains invalid characters.' } if len(contents) > 1E6: return {'status': 'error', 'msg': 'Output exceeds maximum size.'} try: score, _, _ = contents.partition('\n') score = int(score) except ValueError: return { 'status': 'error', 'msg': 'First line must only contain an integer.' } f = FileIntent( BytesIO(contents.encode('utf-8')), 'output_from_{}_to_{}.txt'.format(self.group.id, to_group.id), 'application/octet-stream') if existing: if comp.resolution_open: existing.active = False else: DBSession.delete(existing) output = Output(data=f, group=self.group, input=to_group.input, score=score, original=comp.output_upload_open) verif_mod = self.group.competition.output_verifier.module try: verif_mod.verify(to_group.input.data.file, StringIO(contents)) except verif_mod.VerificationError: output.ground_truth = VerificationStatus.rejected except Exception: output.ground_truth = VerificationStatus.waiting else: output.ground_truth = VerificationStatus.accepted if comp.resolution_open or not comp.verification_begins: output.use_ground_truth = True DBSession.add(output) DBSession.flush() return {'status': 'success', 'url': output.data.url}