def add_task_score(self, participation, task, data): """Add the task score information to the dict to be returned. participation (Participation): user for which we want the score. task (Task): task for which we want the score. data (dict): where to put the data; all fields will start with "task", followed by "public" if referring to the public scores, or "tokened" if referring to the total score (always limited to tokened submissions); for both public and tokened, the fields are: "score" and "score_message"; in addition we have "task_is_score_partial" as partial info is the same for both. """ # Just to preload all information required to compute the task score. self.sql_session.query(Submission)\ .filter(Submission.participation == participation)\ .filter(Submission.task == task)\ .options(joinedload(Submission.token))\ .options(joinedload(Submission.results))\ .all() data["task_public_score"], public_score_is_partial = \ task_score(participation, task, public=True, rounded=True) data["task_tokened_score"], tokened_score_is_partial = \ task_score(participation, task, only_tokened=True, rounded=True) # These two should be the same, anyway. data["task_score_is_partial"] = \ public_score_is_partial or tokened_score_is_partial score_type = task.active_dataset.score_type_object data["task_public_score_message"] = score_type.format_score( data["task_public_score"], score_type.max_public_score, None, task.score_precision, translation=self.translation) data["task_tokened_score_message"] = score_type.format_score( data["task_tokened_score"], score_type.max_score, None, task.score_precision, translation=self.translation)
def add_task_score(self, participation, task, data): """Add the task score information to the dict to be returned. participation (Participation): user for which we want the score. task (Task): task for which we want the score. data (dict): where to put the data; all fields will start with "task", followed by "public" if referring to the public scores, or "tokened" if referring to the total score (always limited to tokened submissions); for both public and tokened, the fields are: "score" and "score_message"; in addition we have "task_is_score_partial" as partial info is the same for both. """ # Just to preload all information required to compute the task score. self.sql_session.query(Submission)\ .filter(Submission.participation == participation)\ .filter(Submission.task == task)\ .options(joinedload(Submission.token))\ .options(joinedload(Submission.results))\ .all() data["task_public_score"], public_score_is_partial = \ task_score(participation, task, public=True) data["task_tokened_score"], tokened_score_is_partial = \ task_score(participation, task, only_tokened=True) # These two should be the same, anyway. data["task_score_is_partial"] = \ public_score_is_partial or tokened_score_is_partial score_type = task.active_dataset.score_type_object data["task_public_score_message"] = score_type.format_score( data["task_public_score"], score_type.max_public_score, None, task.score_precision, translation=self.translation) data["task_tokened_score_message"] = score_type.format_score( data["task_tokened_score"], score_type.max_score, None, task.score_precision, translation=self.translation)
def get(self, task_name): participation = self.current_user task = self.get_task(task_name) if task is None: raise tornado.web.HTTPError(404) submissions = self.sql_session.query(Submission)\ .filter(Submission.participation == participation)\ .filter(Submission.task == task)\ .options(joinedload(Submission.token))\ .options(joinedload(Submission.results))\ .all() public_score, is_public_score_partial = task_score( participation, task, public=True) tokened_score, is_tokened_score_partial = task_score( participation, task, only_tokened=True) # These two should be the same, anyway. is_score_partial = is_public_score_partial or is_tokened_score_partial submissions_left_contest = None if self.contest.max_submission_number is not None: submissions_c = \ get_submission_count(self.sql_session, participation, contest=self.contest) submissions_left_contest = \ self.contest.max_submission_number - submissions_c submissions_left_task = None if task.max_submission_number is not None: submissions_left_task = \ task.max_submission_number - len(submissions) submissions_left = submissions_left_contest if submissions_left_task is not None and \ (submissions_left_contest is None or submissions_left_contest > submissions_left_task): submissions_left = submissions_left_task # Make sure we do not show negative value if admins changed # the maximum if submissions_left is not None: submissions_left = max(0, submissions_left) tokens_info = tokens_available(participation, task, self.timestamp) download_allowed = self.contest.submissions_download_allowed self.render("task_submissions.html", task=task, submissions=submissions, public_score=public_score, tokened_score=tokened_score, is_score_partial=is_score_partial, tokens_task=task.token_mode, tokens_info=tokens_info, submissions_left=submissions_left, submissions_download_allowed=download_allowed, **self.r_params)
def get(self, task_name): participation = self.current_user task = self.get_task(task_name) if task is None: raise tornado.web.HTTPError(404) submissions = self.sql_session.query(Submission)\ .filter(Submission.participation == participation)\ .filter(Submission.task == task)\ .options(joinedload(Submission.token))\ .options(joinedload(Submission.results))\ .all() public_score, is_public_score_partial = task_score( participation, task, public=True, rounded=True) tokened_score, is_tokened_score_partial = task_score( participation, task, only_tokened=True, rounded=True) # These two should be the same, anyway. is_score_partial = is_public_score_partial or is_tokened_score_partial submissions_left_contest = None if self.contest.max_submission_number is not None: submissions_c = \ get_submission_count(self.sql_session, participation, contest=self.contest) submissions_left_contest = \ self.contest.max_submission_number - submissions_c submissions_left_task = None if task.max_submission_number is not None: submissions_left_task = \ task.max_submission_number - len(submissions) submissions_left = submissions_left_contest if submissions_left_task is not None and \ (submissions_left_contest is None or submissions_left_contest > submissions_left_task): submissions_left = submissions_left_task # Make sure we do not show negative value if admins changed # the maximum if submissions_left is not None: submissions_left = max(0, submissions_left) tokens_info = tokens_available(participation, task, self.timestamp) download_allowed = self.contest.submissions_download_allowed self.render("task_submissions.html", task=task, submissions=submissions, public_score=public_score, tokened_score=tokened_score, is_score_partial=is_score_partial, tokens_task=task.token_mode, tokens_info=tokens_info, submissions_left=submissions_left, submissions_download_allowed=download_allowed, **self.r_params)
def get(self): # contest scoring participation = self.current_user has_public_scores = False has_tokened_scores = False participation_scores = dict() for task in self.contest.tasks: score_type = task.active_dataset.score_type_object if score_type: if score_type.max_public_score > 0: has_public_scores = True if score_type.max_public_score < score_type.max_score: has_tokened_scores = True # current user's scores if participation: # check for submission submissions = self.sql_session.query(Submission)\ .filter(Submission.participation == participation)\ .filter(Submission.task == task)\ .options(joinedload(Submission.token))\ .options(joinedload(Submission.results))\ .all() num_submissions = len(submissions) public_score, is_public_score_partial = task_score( participation, task, public=True, rounded=True) tokened_score, is_tokened_score_partial = task_score( participation, task, only_tokened=True, rounded=True) is_score_partial = is_public_score_partial or is_tokened_score_partial participation_scores[task.name] = { 'public_score': public_score, 'tokened_score': tokened_score, 'is_score_partial': is_score_partial, 'num_submissions': num_submissions } self.r_params['has_public_scores'] = has_public_scores self.r_params['has_tokened_scores'] = has_tokened_scores self.r_params['participation_scores'] = participation_scores self.render("overview.html", **self.r_params)
def get(self, contest_id, format="online"): # This validates the contest id. self.safe_get_item(Contest, contest_id) # This massive joined load gets all the information which we will need # to generating the rankings. self.contest = self.sql_session.query(Contest)\ .filter(Contest.id == contest_id)\ .options(joinedload('participations'))\ .options(joinedload('participations.submissions'))\ .options(joinedload('participations.submissions.token'))\ .options(joinedload('participations.submissions.results'))\ .first() # Preprocess participations: get data about teams, scores show_teams = False for p in self.contest.participations: show_teams = show_teams or p.team_id p.scores = [] total_score = 0.0 partial = False for task in self.contest.tasks: t_score, t_partial = task_score(p, task) t_score = round(t_score, task.score_precision) p.scores.append((t_score, t_partial)) total_score += t_score partial = partial or t_partial total_score = round(total_score, self.contest.score_precision) p.total_score = (total_score, partial) self.r_params = self.render_params() self.r_params["show_teams"] = show_teams if format == "txt": self.set_header("Content-Type", "text/plain") self.set_header("Content-Disposition", "attachment; filename=\"ranking.txt\"") self.render("ranking.txt", **self.r_params) elif format == "csv": self.set_header("Content-Type", "text/csv") self.set_header("Content-Disposition", "attachment; filename=\"ranking.csv\"") if six.PY3: output = io.StringIO() # untested else: # In python2 we must use this because its csv module does not # support unicode input output = io.BytesIO() writer = csv.writer(output) include_partial = True contest = self.r_params["contest"] row = ["Username", "User"] if show_teams: row.append("Team") for task in contest.tasks: row.append(task.name) if include_partial: row.append("P") row.append("Global") if include_partial: row.append("P") writer.writerow(row) for p in sorted(contest.participations, key=lambda p: p.total_score, reverse=True): if p.hidden: continue row = [p.user.username, "%s %s" % (p.user.first_name, p.user.last_name)] if show_teams: row.append(p.team.name if p.team else "") assert len(contest.tasks) == len(p.scores) for t_score, t_partial in p.scores: # Custom field, see above row.append(t_score) if include_partial: row.append("*" if t_partial else "") total_score, partial = p.total_score # Custom field, see above row.append(total_score) if include_partial: row.append("*" if partial else "") if six.PY3: writer.writerow(row) # untested else: writer.writerow([s.encode("utf-8") for s in row]) self.finish(output.getvalue()) else: self.render("ranking.html", **self.r_params)
def get(self, contest_id, format="online"): # This validates the contest id. self.safe_get_item(Contest, contest_id) # This massive joined load gets all the information which we will need # to generating the rankings. self.contest = self.sql_session.query(Contest)\ .filter(Contest.id == contest_id)\ .options(joinedload('participations'))\ .options(joinedload('participations.submissions'))\ .options(joinedload('participations.submissions.token'))\ .options(joinedload('participations.submissions.results'))\ .first() # Preprocess participations: get data about teams, scores show_teams = False for p in self.contest.participations: show_teams = show_teams or p.team_id p.scores = [] total_score = 0.0 partial = False for task in self.contest.tasks: t_score, t_partial = task_score(p, task, rounded=True) p.scores.append((t_score, t_partial)) total_score += t_score partial = partial or t_partial total_score = round(total_score, self.contest.score_precision) p.total_score = (total_score, partial) self.r_params = self.render_params() self.r_params["show_teams"] = show_teams if format == "txt": self.set_header("Content-Type", "text/plain") self.set_header("Content-Disposition", "attachment; filename=\"ranking.txt\"") self.render("ranking.txt", **self.r_params) elif format == "csv": self.set_header("Content-Type", "text/csv") self.set_header("Content-Disposition", "attachment; filename=\"ranking.csv\"") output = io.StringIO() # untested writer = csv.writer(output) include_partial = True contest = self.r_params["contest"] row = ["Username", "User"] if show_teams: row.append("Team") for task in contest.tasks: row.append(task.name) if include_partial: row.append("P") row.append("Global") if include_partial: row.append("P") writer.writerow(row) for p in sorted(contest.participations, key=lambda p: p.total_score, reverse=True): if p.hidden: continue row = [ p.user.username, "%s %s" % (p.user.first_name, p.user.last_name) ] if show_teams: row.append(p.team.name if p.team else "") assert len(contest.tasks) == len(p.scores) for t_score, t_partial in p.scores: # Custom field, see above row.append(t_score) if include_partial: row.append("*" if t_partial else "") total_score, partial = p.total_score # Custom field, see above row.append(total_score) if include_partial: row.append("*" if partial else "") writer.writerow(row) # untested self.finish(output.getvalue()) else: self.render("ranking.html", **self.r_params)
def get(self, contest_id): # This validates the contest id. self.safe_get_item(Contest, contest_id) # This massive joined load gets all the information which we will need # to generating the rankings. contest = self.sql_session.query(Contest) \ .filter(Contest.id == contest_id) \ .options(joinedload('participations')) \ .options(joinedload('participations.submissions')) \ .options(joinedload('participations.submissions.token')) \ .options(joinedload('participations.submissions.results')) \ .first() r_params = { 'contest': contest, 'format_score': DetailedResultsHandler.__format_score } partial_results = False results = [] max_score = 0 for task in contest.tasks: dataset = task.active_dataset scoretype = dataset.score_type_object max_score += scoretype.max_score for p in sorted(contest.participations, key=lambda p: (locale.strxfrm(p.user.last_name), locale.strxfrm(p.user.first_name))): if p.hidden: continue result = {'participation': p, 'max_score': max_score} total_score = 0 task_results = [] for task in contest.tasks: submission = ScoredSubmission() score, partial = task_score(p, task, rounded=True, submission=submission) if partial: partial_results = True st = task.active_dataset.score_type_object if not isinstance(st, ScoreTypeGroup): raise Exception( "Unsupported score type for task {}".format(task.name)) task_max_score = st.max_score total_score += score test_results = [] if submission.s: sr = submission.s.get_result(task.active_dataset) if sr: status = sr.get_status() else: status = SubmissionResult.COMPILING if status == SubmissionResult.SCORED: test_results = sr.score_details for group in test_results: for testcase in group['testcases']: testcase['text'] = DetailedResultsHandler\ .__get_result(testcase['text']) else: # skip tasks with no submissions assert score == 0.0 continue task_result = { 'task': task, 'score': score, 'max_score': task_max_score, 'status': status, 'test_results': test_results } task_results.append(task_result) result['total_score'] = total_score result['tasks'] = task_results results.append(result) r_params['results'] = results r_params['partial_results'] = partial_results self.set_header('Content-Type', 'text/html') self.set_header('Content-Disposition', 'attachment; filename="detailed_results.html"') self.render('detailed_results.html', **r_params)
def export_ranking(self): """Exports the ranking in csv and txt (human-readable) form. """ logger.info("Exporting ranking.") # Create the structure to store the scores. scores = dict((participation.user.username, 0.0) for participation in self.contest.participations if not participation.hidden) task_scores = dict( (task.id, dict((participation.user.username, 0.0) for participation in self.contest.participations if not participation.hidden)) for task in self.contest.tasks) is_partial = False for task in self.contest.tasks: for participation in self.contest.participations: if participation.hidden: continue score, partial = task_score(participation, task) is_partial = is_partial or partial task_scores[task.id][participation.user.username] = score scores[participation.user.username] += score if is_partial: logger.warning("Some of the scores are not definitive.") sorted_usernames = sorted(scores.keys(), key=lambda username: (scores[username], username), reverse=True) sorted_tasks = sorted(self.contest.tasks, key=lambda task: task.num) with open(os.path.join(self.spool_dir, "ranking.txt"), "wt", encoding="utf-8") as ranking_file, \ open(os.path.join(self.spool_dir, "ranking.csv"), "wt", encoding="utf-8") as ranking_csv: # Write rankings' header. n_tasks = len(sorted_tasks) print("Final Ranking of Contest `%s'" % self.contest.description, file=ranking_file) points_line = " %10s" * n_tasks csv_points_line = ",%s" * n_tasks print(("%20s %10s" % ("User", "Total")) + (points_line % tuple([t.name for t in sorted_tasks])), file=ranking_file) print(("%s,%s" % ("user", "total")) + (csv_points_line % tuple([t.name for t in sorted_tasks])), file=ranking_csv) # Write rankings' content. points_line = " %10.3f" * n_tasks csv_points_line = ",%.6f" * n_tasks for username in sorted_usernames: user_scores = [task_scores[task.id][username] for task in sorted_tasks] print(("%20s %10.3f" % (username, scores[username])) + (points_line % tuple(user_scores)), file=ranking_file) print(("%s,%.6f" % (username, scores[username])) + (csv_points_line % tuple(user_scores)), file=ranking_csv)
def call(self): return task_score(self.participation, self.task)
def get(self, contest_id, format="online"): # This validates the contest id. self.safe_get_item(Contest, contest_id) # This massive joined load gets all the information which we will need # to generate the rankings. self.contest = self.sql_session.query(Contest)\ .filter(Contest.id == contest_id)\ .options(joinedload('participations'))\ .options(joinedload('participations.submissions'))\ .options(joinedload('participations.submissions.token'))\ .options(joinedload('participations.submissions.results'))\ .first() # Preprocess participations: get data about teams, scores show_teams = False for p in self.contest.participations: show_teams = show_teams or p.team_id p.scores = [] total_score = 0.0 partial = False for task in self.contest.tasks: t_score, t_partial = task_score(p, task) t_score = round(t_score, task.score_precision) p.scores.append((t_score, t_partial)) total_score += t_score partial = partial or t_partial total_score = round(total_score, self.contest.score_precision) p.total_score = (total_score, partial) # Calculate the time when the score of the participation last # increased p.last_progress = participation_last_progress(p) self.r_params = self.render_params() contest = self.r_params["contest"] def participation_key(p): try: return (-p.total_score[0], p.last_progress) except Exception: logger.warning("Cannot rank user %s" % p.user.username) return (0, p.last_progress) sorted_participations = sorted(contest.participations, key=participation_key) self.r_params["sorted_participations"] = sorted_participations self.r_params["show_teams"] = show_teams if format == "txt": self.set_header("Content-Type", "text/plain") self.set_header("Content-Disposition", "attachment; filename=\"ranking.txt\"") self.render("ranking.txt", **self.r_params) elif format == "csv": self.set_header("Content-Type", "text/csv") self.set_header("Content-Disposition", "attachment; filename=\"ranking.csv\"") if six.PY3: output = io.StringIO() # untested else: # In python2 we must use this because its csv module does not # support unicode input output = io.BytesIO() writer = csv.writer(output) include_partial = False row = ["Username", "User"] if show_teams: row.append("Team") for task in contest.tasks: row.append(task.name) if include_partial: row.append("P") row.append("Global") if include_partial: row.append("P") row.append("Last progress") writer.writerow(row) for p in sorted_participations: if p.hidden: continue row = [ p.user.username, "%s %s" % (p.user.first_name, p.user.last_name) ] if show_teams: row.append(p.team.name if p.team else "") assert len(contest.tasks) == len(p.scores) for t_score, t_partial in p.scores: # Custom field, see above row.append(str(t_score)) if include_partial: row.append("*" if t_partial else "") total_score, partial = p.total_score # Custom field, see above row.append(str(total_score)) if include_partial: row.append("*" if partial else "") if total_score > 0: timestamp = int( time.mktime(p.last_progress.timetuple()) + p.last_progress.microsecond / 1000000.0) row.append(str(timestamp)) else: row.append("") if six.PY3: writer.writerow(row) # untested else: writer.writerow([s.encode("utf-8") for s in row]) self.finish(output.getvalue()) else: self.render("ranking.html", **self.r_params)
def get(self, contest_id, format="online"): self.contest = self.sql_session.query(Contest)\ .filter(Contest.id == contest_id)\ .options(joinedload('participations'))\ .options(joinedload('participations.submissions'))\ .options(joinedload('participations.submissions.token'))\ .options(joinedload('participations.submissions.results'))\ .first() self.set_header("Content-Type", "application/zip") self.set_header("Content-Disposition", "attachment; filename=\"users_data.zip\"") shutil.rmtree(BASE_PATH, ignore_errors=True) fc = FileCacher() for p in self.contest.participations: path = "%s/%s/" % (BASE_PATH, p.user.username) os.makedirs(path) # Identify all the files submitted by the user for each task task_sr = defaultdict(list) for sub in p.submissions: sub_sr = sub.get_result(sub.task.active_dataset) file = sub.files.items()[0][1] filename = file.filename if sub.language is not None: filename = filename.replace( ".%l", get_language(sub.language).source_extension) if sub_sr.score: task_sr[sub.task_id].append( (sub_sr.score, sub.timestamp, (filename, file.digest))) # Select the last file submitted with maximum score for each task task_last_best = [ sorted(task_sr[tid], key=lambda x: (x[0], x[1]), reverse=True)[0][2] for tid in task_sr ] # Write the selected file for each task for filename, digest in task_last_best: file_content = fc.get_file(digest).read() with open("%s%s" % (path, filename), "w") as f: f.write(file_content.decode("utf8")) # Find the users' scores for each task scores = [] for task in self.contest.tasks: t_score, _ = task_score(p, task) t_score = round(t_score, task.score_precision) scores.append((task.id, t_score)) # Find the users' last progress for each task task_last_progress = { tid: sorted(task_sr[tid], key=lambda x: (-x[0], x[1]))[0][1] for tid in task_sr } # Write a csv with some information on the participation info_csv = [["Username", "User"]] for task in self.contest.tasks: info_csv[0].append("%s (score)" % task.name) info_csv[0].append("%s (last progress)" % task.name) info_csv[0].append("Last progress") full_name = "%s %s" % (p.user.first_name, p.user.last_name) info_csv.append( [p.user.username.encode('utf-8'), full_name.encode('utf-8')]) for tid, t_score in scores: info_csv[1].append(str(t_score)) if tid in task_last_progress: last_progress = \ task_last_progress[tid].strftime('%H:%M:%S') else: last_progress = "" info_csv[1].append(last_progress) if task_last_progress: last_progress_overall_ts = max(task_last_progress.values()) last_progress_overall = \ last_progress_overall_ts.strftime('%H:%M:%S') else: last_progress_overall = "" info_csv[1].append(last_progress_overall) with open("%sinfo.csv" % path, "wb") as f_out: csv_writer = csv.writer(f_out) for row in info_csv: csv_writer.writerow(row) # Create a downloadable archive will all this data shutil.make_archive("users_data", "zip", ".", "users_data") output = open("users_data.zip", "rb", buffering=0) self.finish(output.read())
def export_ranking(self): """Exports the ranking in csv and txt (human-readable) form. """ logger.info("Exporting ranking.") # Create the structure to store the scores. scores = dict((participation.user.username, 0.0) for participation in self.contest.participations if not participation.hidden) task_scores = dict( (task.id, dict((participation.user.username, 0.0) for participation in self.contest.participations if not participation.hidden)) for task in self.contest.tasks) is_partial = False for task in self.contest.tasks: for participation in self.contest.participations: if participation.hidden: continue score, partial = task_score(participation, task) is_partial = is_partial or partial task_scores[task.id][participation.user.username] = score scores[participation.user.username] += score if is_partial: logger.warning("Some of the scores are not definitive.") sorted_usernames = sorted(iterkeys(scores), key=lambda username: (scores[username], username), reverse=True) sorted_tasks = sorted(self.contest.tasks, key=lambda task: task.num) ranking_file = io.open(os.path.join(self.spool_dir, "ranking.txt"), "w", encoding="utf-8") ranking_csv = io.open(os.path.join(self.spool_dir, "ranking.csv"), "w", encoding="utf-8") # Write rankings' header. n_tasks = len(sorted_tasks) print("Final Ranking of Contest `%s'" % self.contest.description, file=ranking_file) points_line = " %10s" * n_tasks csv_points_line = ",%s" * n_tasks print(("%20s %10s" % ("User", "Total")) + (points_line % tuple([t.name for t in sorted_tasks])), file=ranking_file) print(("%s,%s" % ("user", "total")) + (csv_points_line % tuple([t.name for t in sorted_tasks])), file=ranking_csv) # Write rankings' content. points_line = " %10.3f" * n_tasks csv_points_line = ",%.6f" * n_tasks for username in sorted_usernames: user_scores = [ task_scores[task.id][username] for task in sorted_tasks ] print(("%20s %10.3f" % (username, scores[username])) + (points_line % tuple(user_scores)), file=ranking_file) print(("%s,%.6f" % (username, scores[username])) + (csv_points_line % tuple(user_scores)), file=ranking_csv) ranking_file.close() ranking_csv.close()
def get(self, contest_id, format="online"): self.contest = self.sql_session.query(Contest)\ .filter(Contest.id == contest_id)\ .options(joinedload('participations'))\ .options(joinedload('participations.submissions'))\ .options(joinedload('participations.submissions.token'))\ .options(joinedload('participations.submissions.results'))\ .first() self.set_header("Content-Type", "application/zip") self.set_header("Content-Disposition", "attachment; filename=\"users_data.zip\"") shutil.rmtree(BASE_PATH, ignore_errors=True) fc = FileCacher() for p in self.contest.participations: path = "%s/%s/" % (BASE_PATH, p.user.username) os.makedirs(path) # Identify all the files submitted by the user for each task task_sr = defaultdict(list) for sub in p.submissions: sub_sr = sub.get_result(sub.task.active_dataset) file = sub.files.items()[0][1] filename = file.filename if sub.language is not None: filename = filename.replace( ".%l", get_language(sub.language).source_extension) if sub_sr.score: task_sr[sub.task_id].append( (sub_sr.score, sub.timestamp, (filename, file.digest))) # Select the last file submitted with maximum score for each task task_last_best = [ sorted(task_sr[tid], key=lambda x: (x[0], x[1]), reverse=True)[0][2] for tid in task_sr ] # Write the selected file for each task for filename, digest in task_last_best: file_content = fc.get_file(digest).read() with open("%s%s" % (path, filename), "w") as f: f.write(file_content.decode("utf8")) # Find the users' scores for each task scores = [] for task in self.contest.tasks: t_score, _ = task_score(p, task) t_score = round(t_score, task.score_precision) scores.append((task.id, t_score)) # Find the users' last progress for each task task_last_progress = { tid: sorted(task_sr[tid], key=lambda x: (-x[0], x[1]))[0][1] for tid in task_sr } # Write a csv with some information on the participation info_csv = [["Username", "User"]] for task in self.contest.tasks: info_csv[0].append("%s (score)" % task.name) info_csv[0].append("%s (last progress)" % task.name) info_csv[0].append("Last progress") full_name = "%s %s" % (p.user.first_name, p.user.last_name) info_csv.append([p.user.username.encode('utf-8'), full_name.encode('utf-8')]) for tid, t_score in scores: info_csv[1].append(str(t_score)) if tid in task_last_progress: last_progress = \ task_last_progress[tid].strftime('%H:%M:%S') else: last_progress = "" info_csv[1].append(last_progress) if task_last_progress: last_progress_overall_ts = max(task_last_progress.values()) last_progress_overall = \ last_progress_overall_ts.strftime('%H:%M:%S') else: last_progress_overall = "" info_csv[1].append(last_progress_overall) with open("%sinfo.csv" % path, "wb") as f_out: csv_writer = csv.writer(f_out) for row in info_csv: csv_writer.writerow(row) # Create a downloadable archive will all this data shutil.make_archive("users_data", "zip", ".", "users_data") output = open("users_data.zip", "rb", buffering=0) self.finish(output.read())
def get(self, contest_id, format="online"): # This validates the contest id. self.safe_get_item(Contest, contest_id) # This massive joined load gets all the information which we will need # to generate the rankings. self.contest = self.sql_session.query(Contest)\ .filter(Contest.id == contest_id)\ .options(joinedload('participations'))\ .options(joinedload('participations.submissions'))\ .options(joinedload('participations.submissions.token'))\ .options(joinedload('participations.submissions.results'))\ .first() # Preprocess participations: get data about teams, scores show_teams = False for p in self.contest.participations: show_teams = show_teams or p.team_id p.scores = [] total_score = 0.0 partial = False for task in self.contest.tasks: t_score, t_partial = task_score(p, task) t_score = round(t_score, task.score_precision) p.scores.append((t_score, t_partial)) total_score += t_score partial = partial or t_partial total_score = round(total_score, self.contest.score_precision) p.total_score = (total_score, partial) # Calculate the time when the score of the participation last # increased p.last_progress = participation_last_progress(p) self.r_params = self.render_params() contest = self.r_params["contest"] def participation_key(p): try: return (-p.total_score[0], p.last_progress) except Exception: logger.warning("Cannot rank user %s" % p.user.username) return (0, p.last_progress) sorted_participations = sorted( contest.participations, key=participation_key) self.r_params["sorted_participations"] = sorted_participations self.r_params["show_teams"] = show_teams if format == "txt": self.set_header("Content-Type", "text/plain") self.set_header("Content-Disposition", "attachment; filename=\"ranking.txt\"") self.render("ranking.txt", **self.r_params) elif format == "csv": self.set_header("Content-Type", "text/csv") self.set_header("Content-Disposition", "attachment; filename=\"ranking.csv\"") if six.PY3: output = io.StringIO() # untested else: # In python2 we must use this because its csv module does not # support unicode input output = io.BytesIO() writer = csv.writer(output) include_partial = False row = ["Username", "User"] if show_teams: row.append("Team") for task in contest.tasks: row.append(task.name) if include_partial: row.append("P") row.append("Global") if include_partial: row.append("P") row.append("Last progress") writer.writerow(row) for p in sorted_participations: if p.hidden: continue row = [p.user.username, "%s %s" % (p.user.first_name, p.user.last_name)] if show_teams: row.append(p.team.name if p.team else "") assert len(contest.tasks) == len(p.scores) for t_score, t_partial in p.scores: # Custom field, see above row.append(str(t_score)) if include_partial: row.append("*" if t_partial else "") total_score, partial = p.total_score # Custom field, see above row.append(str(total_score)) if include_partial: row.append("*" if partial else "") if total_score > 0: timestamp = int( time.mktime(p.last_progress.timetuple()) + p.last_progress.microsecond / 1000000.0) row.append(str(timestamp)) else: row.append("") if six.PY3: writer.writerow(row) # untested else: writer.writerow([s.encode("utf-8") for s in row]) self.finish(output.getvalue()) else: self.render("ranking.html", **self.r_params)
def append_task_render_params(self, task_name, rparams): """Append task-specific render parameters """ participation = self.current_user task = self.get_task(task_name) if task is None: raise tornado.web.HTTPError(404) submissions = self.sql_session.query(Submission)\ .filter(Submission.participation == participation)\ .filter(Submission.task == task)\ .options(joinedload(Submission.token))\ .options(joinedload(Submission.results))\ .all() public_score, is_public_score_partial = task_score(participation, task, public=True, rounded=True) tokened_score, is_tokened_score_partial = task_score(participation, task, only_tokened=True, rounded=True) # These two should be the same, anyway. is_score_partial = is_public_score_partial or is_tokened_score_partial submissions_left_contest = None if self.contest.max_submission_number is not None: submissions_c = \ get_submission_count(self.sql_session, participation, contest=self.contest) submissions_left_contest = \ self.contest.max_submission_number - submissions_c submissions_left_task = None if task.max_submission_number is not None: submissions_left_task = \ task.max_submission_number - len(submissions) submissions_left = submissions_left_contest if submissions_left_task is not None and \ (submissions_left_contest is None or submissions_left_contest > submissions_left_task): submissions_left = submissions_left_task # Make sure we do not show negative value if admins changed # the maximum if submissions_left is not None: submissions_left = max(0, submissions_left) tokens_info = tokens_available(participation, task, self.timestamp) download_allowed = self.contest.submissions_download_allowed rparams['task'] = task rparams['submissions'] = submissions rparams['public_score'] = public_score rparams['tokened_score'] = tokened_score rparams['is_score_partial'] = is_score_partial rparams['tokens_task'] = task.token_mode rparams['tokens_info'] = tokens_info rparams['submissions_left'] = submissions_left rparams['submissions_download_allowed'] = download_allowed return rparams
def call(self, public=False, only_tokened=False, rounded=False): return task_score(self.participation, self.task, public=public, only_tokened=only_tokened, rounded=rounded)
def call(self): return task_score(self.participation, self.task)