def choose_contest(self): """Fill self.contest using contest passed as argument or path. If a contest was specified as argument to CWS, fill self.contest with that; otherwise extract it from the URL path. """ if self.is_multi_contest(): # Choose the contest found in the path argument # see: https://github.com/tornadoweb/tornado/issues/1673 contest_name = self.path_args[0] # Select the correct contest or return an error self.contest = self.sql_session.query(Contest)\ .filter(Contest.name == contest_name).first() if self.contest is None: self.contest = Contest( name=contest_name, description=contest_name) # render_params in this class assumes the contest is loaded, # so we cannot call it without a fully defined contest. Luckily # the one from the base class is enough to display a 404 page. self.r_params = super().render_params() raise tornado.web.HTTPError(404) else: # Select the contest specified on the command line self.contest = Contest.get_from_id( self.service.contest_id, self.sql_session)
def choose_contest(self): """Fill self.contest using contest passed as argument or path. If a contest was specified as argument to CWS, fill self.contest with that; otherwise extract it from the URL path. """ if self.is_multi_contest(): # Choose the contest found in the path argument # see: https://github.com/tornadoweb/tornado/issues/1673 contest_name = self.path_args[0] # Select the correct contest or return an error self.contest = self.sql_session.query(Contest)\ .filter(Contest.name == contest_name).first() if self.contest is None: self.contest = Contest(name=contest_name, description=contest_name) # render_params in this class assumes the contest is loaded, # so we cannot call it without a fully defined contest. Luckily # the one from the base class is enough to display a 404 page. super().prepare() self.r_params = super().render_params() raise tornado.web.HTTPError(404) else: # Select the contest specified on the command line self.contest = Contest.get_from_id(self.service.contest_id, self.sql_session)
def post(self): fallback_page = self.url("contests", "add") try: attrs = dict() self.get_string(attrs, "name", empty=None) assert attrs.get("name") is not None, "No contest name specified." attrs["description"] = attrs["name"] # Create the contest. contest = Contest(**attrs) # Add the default group group = Group(name="default") contest.groups.append(group) contest.main_group = group self.sql_session.add(contest) except Exception as error: self.service.add_notification(make_datetime(), "Invalid field(s)", repr(error)) self.redirect(fallback_page) return if self.try_commit(): # Create the contest on RWS. self.service.proxy_service.reinitialize() self.redirect(self.url("contest", contest.id)) else: self.redirect(fallback_page)
def _makecontest(self): """ Return a Contest object which can be saved to the database. return (Contest): database object for the contest """ if self.defaultgroup is None: raise Exception("You have to specify a default group") cdb = Contest(name=self.contestname, description=self._description) cdb.timezone = self._timezone cdb.allowed_localizations = self._allowed_localizations cdb.languages = self._languages self._set_tokens(cdb) cdb.max_submission_number = self.max_submission_number cdb.min_submission_interval = self.min_submission_interval cdb.max_user_test_number = self.max_user_test_number cdb.min_user_test_interval = self.min_user_test_interval self.usersdb = {} self.participationsdb = {} self.cdb = cdb gdbs = {} for g in self.groups: gdbs[g] = self._makegroup(g, cdb) cdb.main_group = gdbs[self.defaultgroup.name] return cdb
def search_jobs_not_done(self): """Sweep the database and search for work to do. Iterate over all submissions and look if they are in a suitable status to be sent (scored and not hidden) but, for some reason, haven't been sent yet (that is, their ID doesn't appear in the *_sent_to_rankings sets). In case, arrange for them to be sent. """ logger.info("Going to search for unsent subchanges.") job_count = 0 with SessionGen() as session: contest = Contest.get_from_id(self.contest_id, session) for submission in contest.get_submissions(): if submission.user.hidden: continue if submission.get_result().scored() and \ submission.id not in self.scores_sent_to_rankings: self.send_score(submission) job_count += 1 if submission.tokened() and \ submission.id not in self.tokens_sent_to_rankings: self.send_token(submission) job_count += 1 logger.info("Found %d unsent subchanges." % job_count)
def harvest_contest_data(contest_id): """Retrieve the couples username, password and the task list for a given contest. contest_id (int): the id of the contest we want. return (tuple): the first element is a dictionary mapping usernames to passwords; the second one is the list of the task names. """ users = {} tasks = [] with SessionGen() as session: contest = Contest.get_from_id(contest_id, session) for participation in contest.participations: user = participation.user # Pick participation's password if present, or the user's. password_source = participation.password if password_source is None: password_source = user.password # We can log in only if we know the plaintext password. method, password = parse_authentication(password_source) if method != "plaintext": print("Not using user %s with non-plaintext password." % user.username) continue users[user.username] = {'password': password} for task in contest.tasks: tasks.append((task.id, task.name, list(iterkeys(task.statements)))) return users, tasks
def _missing_operations(self): """Return a generator of data to be sent to the rankings.. """ counter = 0 with SessionGen() as session: contest = Contest.get_from_id(self.contest_id, session) for submission in contest.get_submissions(): if submission.user.hidden: continue if submission.get_result().scored() and \ submission.id not in self.scores_sent_to_rankings: for operation in self.operations_for_score(submission): self.enqueue(operation) counter += 1 if submission.tokened() and \ submission.id not in self.tokens_sent_to_rankings: for operation in self.operations_for_token(submission): self.enqueue(operation) counter += 1 return counter
def post(self): fallback_page = "/contests/add" try: attrs = dict() self.get_string(attrs, "name", empty=None) assert attrs.get("name") is not None, "No contest name specified." attrs["description"] = attrs["name"] # Create the contest. contest = Contest(**attrs) self.sql_session.add(contest) except Exception as error: self.application.service.add_notification( make_datetime(), "Invalid field(s)", repr(error)) self.redirect(fallback_page) return if self.try_commit(): # Create the contest on RWS. self.application.service.proxy_service.reinitialize() self.redirect("/contest/%s" % contest.id) else: self.redirect(fallback_page)
def _missing_operations(self): """Return a generator of data to be sent to the rankings.. """ counter = 0 with SessionGen() as session: contest = Contest.get_from_id(self.contest_id, session) for submission in contest.get_submissions(): if submission.user.hidden: continue # The submission result can be None if the dataset has # been just made live. sr = submission.get_result() if sr is None: continue if sr.evaluated() and \ submission.id not in self.scores_sent_to_rankings: for operation in self.operations_for_score(submission): self.enqueue(operation) counter += 1 if submission.tokened() and \ submission.id not in self.tokens_sent_to_rankings: for operation in self.operations_for_token(submission): self.enqueue(operation) counter += 1 return counter
def precache_files(self, contest_id): """RPC to ask the worker to precache of files in the contest. contest_id (int): the id of the contest """ # In order to avoid a long-living connection, first fetch the # complete list of files and then download the files; since # this is just pre-caching, possible race conditions are not # dangerous logger.info("Precaching files for contest %d.", contest_id) with SessionGen() as session: contest = Contest.get_from_id(contest_id, session) files = enumerate_files(session, contest, skip_submissions=True, skip_user_tests=True, skip_print_jobs=True) for digest in files: try: self.file_cacher.load(digest, if_needed=True) except KeyError: # No problem (at this stage) if we cannot find the # file pass logger.info("Precaching finished.")
def get_contest(self): # Check for required files if not (self.__require_file("contest.json") and self.__require_file("problem-list.txt")): return None # Load name and description contest = self.__load_contest(self.path) args = {} args['name'] = contest['name'] args['description'] = contest['description'] logger.info("Loading parameters for contest %s.", args['name']) args['token_mode'] = 'infinite' # Load datetime time_info = contest['time'] start_time_local = datetime.datetime.strptime(time_info['start'], '%Y-%m-%d %H:%M') local_tz = get_localzone() start_time = local_tz.localize(start_time_local, is_dst=None).astimezone( pytz.utc).replace(tzinfo=None) contest_len = datetime.timedelta(minutes=time_info['length']) args['start'] = start_time args['stop'] = start_time + contest_len # Tasks tasks = list( map((lambda x: x[:-1]), open(os.path.join(self.path, 'problem-list.txt'), 'r'))) # Import was successful logger.info("Contest parameters loaded.") return Contest(**args), tasks, []
def _missing_operations(self): """Return a generator of data to be sent to the rankings.. """ counter = 0 with SessionGen() as session: contest = Contest.get_from_id(self.contest_id, session) for submission in contest.get_submissions(): if submission.participation.hidden: continue # The submission result can be None if the dataset has # been just made live. sr = submission.get_result() if sr is None: continue if sr.scored() and \ submission.id not in self.scores_sent_to_rankings: for operation in self.operations_for_score(submission): self.enqueue(operation) counter += 1 if submission.tokened() and \ submission.id not in self.tokens_sent_to_rankings: for operation in self.operations_for_token(submission): self.enqueue(operation) counter += 1 return counter
def add_participations(contest_id, groupname): with SessionGen() as session: users = session.query(User) contest = Contest.get_from_id(contest_id, session) if contest is None: logger.error("No contest with id `%s' found.", contest_id) return False if groupname is None: group = contest.main_group else: group = \ session.query(Group) \ .filter(Group.contest_id == contest_id, Group.name == groupname).first() if group is None: logger.error("No group with name `%s' found.", groupname) return False for user in users: if session.query(Participation) \ .filter(Participation.contest_id == contest_id, Participation.user_id == user.id).first(): logger.info( "Participation already exists (left untouched; group not verified): '%s'", user.username) else: participation = Participation(user=user, contest=contest, group=group) session.add(participation) logger.info("Participation added: '%s'", user.username) session.commit() return True
def __init__(self, contest_id): super(MyContest, self).__init__(_session) self.contest_id = contest_id self.contest = Contest.get_from_id(contest_id, self.sql_session) self.name = self.contest.name self.description = self.contest.description self.questions = QuestionList(self.contest_id) self.announcements = AnnouncementList(self.contest_id)
def initialize(self): """Send basic data to all the rankings. It's data that's supposed to be sent before the contest, that's needed to understand what we're talking about when we send submissions: contest, users, tasks. No support for teams, flags and faces. """ logger.info("Initializing rankings.") with SessionGen() as session: contest = Contest.get_from_id(self.contest_id, session) if contest is None: logger.error( "Received request for unexistent contest " "id %s.", self.contest_id) raise KeyError("Contest not found.") contest_id = encode_id(contest.name) contest_data = { "name": contest.description, "begin": int(make_timestamp(contest.start)), "end": int(make_timestamp(contest.stop)), "score_precision": contest.score_precision } users = dict() for user in contest.users: if not user.hidden: users[encode_id(user.username)] = \ {"f_name": user.first_name, "l_name": user.last_name, "team": None} tasks = dict() for task in contest.tasks: score_type = get_score_type(dataset=task.active_dataset) tasks[encode_id(task.name)] = \ {"short_name": task.name, "name": task.title, "contest": encode_id(contest.name), "order": task.num, "max_score": score_type.max_score, "extra_headers": score_type.ranking_headers, "score_precision": task.score_precision, "score_mode": task.score_mode} self.enqueue( ProxyOperation(ProxyExecutor.CONTEST_TYPE, {contest_id: contest_data})) self.enqueue(ProxyOperation(ProxyExecutor.USER_TYPE, users)) self.enqueue(ProxyOperation(ProxyExecutor.TASK_TYPE, tasks))
def get_contest(cls, **kwargs): """Create a contest""" args = { "name": unique_unicode_id(), "description": unique_unicode_id(), } args.update(kwargs) contest = Contest(**args) return contest
def add_participation(username, contest_id, ip, delay_time, extra_time, password, method, is_hashed, team_code, hidden, unrestricted): logger.info("Creating the user's participation in the database.") delay_time = delay_time if delay_time is not None else 0 extra_time = extra_time if extra_time is not None else 0 if hidden: logger.warning("The participation will be hidden") if unrestricted: logger.warning("The participation will be unrestricted") try: with SessionGen() as session: user = \ session.query(User).filter(User.username == username).first() if user is None: logger.error("No user with username `%s' found.", username) return False contest = Contest.get_from_id(contest_id, session) if contest is None: logger.error("No contest with id `%s' found.", contest_id) return False team = None if team_code is not None: team = \ session.query(Team).filter(Team.code == team_code).first() if team is None: logger.error("No team with code `%s' found.", team_code) return False if password is not None: if is_hashed: password = build_password(password, method) else: password = hash_password(password, method) participation = Participation( user=user, contest=contest, ip=[ipaddress.ip_network(ip)] if ip is not None else None, delay_time=datetime.timedelta(seconds=delay_time), extra_time=datetime.timedelta(seconds=extra_time), password=password, team=team, hidden=hidden, unrestricted=unrestricted) session.add(participation) session.commit() except IntegrityError: logger.error("A participation for this user in this contest " "already exists.") return False logger.info("Participation added.") return True
def add_contest(self, **kwargs): """Add a contest.""" args = { "name": unique_unicode_id(), "description": unique_unicode_id(), } args.update(kwargs) contest = Contest(**args) self.session.add(contest) return contest
def initialize(self): """Send basic data to all the rankings. It's data that's supposed to be sent before the contest, that's needed to understand what we're talking about when we send submissions: contest, users, tasks. No support for teams, flags and faces. """ logger.info("Initializing rankings.") with SessionGen() as session: contest = Contest.get_from_id(self.contest_id, session) if contest is None: logger.error("Received request for unexistent contest " "id %s.", self.contest_id) raise KeyError("Contest not found.") contest_id = encode_id(contest.name) contest_data = { "name": contest.description, "begin": int(make_timestamp(contest.start)), "end": int(make_timestamp(contest.stop)), "score_precision": contest.score_precision} users = dict() for participation in contest.participations: user = participation.user if not participation.hidden: users[encode_id(user.username)] = \ {"f_name": user.first_name, "l_name": user.last_name, "team": None} tasks = dict() for task in contest.tasks: score_type = get_score_type(dataset=task.active_dataset) tasks[encode_id(task.name)] = \ {"short_name": task.name, "name": task.title, "contest": encode_id(contest.name), "order": task.num, "max_score": score_type.max_score, "extra_headers": score_type.ranking_headers, "score_precision": task.score_precision, "score_mode": task.score_mode} self.enqueue(ProxyOperation(ProxyExecutor.CONTEST_TYPE, {contest_id: contest_data})) self.enqueue(ProxyOperation(ProxyExecutor.USER_TYPE, users)) self.enqueue(ProxyOperation(ProxyExecutor.TASK_TYPE, tasks))
def prepare(self): """This method is executed at the beginning of each request. """ super(BaseHandler, self).prepare() self.contest = Contest.get_from_id(self.application.service.contest, self.sql_session) self._ = self.locale.translate self.r_params = self.render_params()
def add_user(contest_id, first_name, last_name, username, password, ip_address, email, hidden): with SessionGen(commit=True) as session: contest = Contest.get_from_id(contest_id, session) user = User(first_name=first_name, last_name=last_name, username=username, password=password, email=email, ip=ip_address, hidden=hidden, contest=contest) session.add(user)
def add_user(contest_id, first_name, last_name, username, password, ip_address, email, hidden): with SessionGen() as session: contest = Contest.get_from_id(contest_id, session) user = User(first_name=first_name, last_name=last_name, username=username, password=password, email=email, ip=ip_address, hidden=hidden, contest=contest) session.add(user) session.commit()
def prepare(self): """This method is executed at the beginning of each request. """ self.timestamp = make_datetime() self.set_header("Cache-Control", "no-cache, must-revalidate") self.sql_session = Session() self.contest = Contest.get_from_id(self.application.service.contest, self.sql_session) self._ = self.locale.translate self.r_params = self.render_params()
def choose_contest(self): """Fill self.contest using contest passed as argument or path. If a contest was specified as argument to CWS, fill self.contest with that; otherwise extract it from the URL path. """ if self.is_multi_contest(): # Choose the contest found in the path argument # see: https://github.com/tornadoweb/tornado/issues/1673 contest_name = self.path_args[0] # Select the correct contest or return an error try: self.contest = self.contest_list[contest_name] except KeyError: self.contest = Contest( name=contest_name, description=contest_name) self.r_params = self.render_params() raise tornado.web.HTTPError(404) else: # Select the contest specified on the command line self.contest = Contest.get_from_id( self.application.service.contest_id, self.sql_session)
def precache_files(self, contest_id): """RPC to ask the worker to precache of files in the contest. contest_id (int): the id of the contest """ # Lock is not needed if the admins correctly placed cache and # temp directories in the same filesystem. This is what # usually happens since they are children of the same, # cms-created, directory. logger.info("Precaching files for contest %d." % contest_id) with SessionGen() as session: contest = Contest.get_from_id(contest_id, session) for digest in contest.enumerate_files(skip_submissions=True, skip_user_tests=True): self.file_cacher.load(digest) logger.info("Precaching finished.")
def get_contest(cls, **kwargs): """Create a contest""" grpargs = {} for a in ["start", "stop", "per_user_time"]: if a in kwargs: grpargs[a] = kwargs[a] del kwargs[a] args = { "name": unique_unicode_id(), "description": unique_unicode_id(), "main_group": cls.get_group(**grpargs), } args["groups"] = [args["main_group"]] args.update(kwargs) contest = Contest(**args) return contest
def rankings_initialize(self): """Send to all the rankings all the data that are supposed to be sent before the contest: contest, users, tasks. No support for teams, flags and faces. """ logger.info("Initializing rankings.") with SessionGen(commit=False) as session: contest = Contest.get_from_id(self.contest_id, session) if contest is None: logger.error("Received request for unexistent contest " "id %s." % self.contest_id) raise KeyError contest_id = encode_id(contest.name) contest_data = { "name": contest.description, "begin": int(make_timestamp(contest.start)), "end": int(make_timestamp(contest.stop)), "score_precision": contest.score_precision} users = dict((encode_id(user.username), {"f_name": user.first_name, "l_name": user.last_name, "team": None}) for user in contest.users if not user.hidden) tasks = dict((encode_id(task.name), {"name": task.title, "contest": encode_id(contest.name), "max_score": 100.0, "score_precision": task.score_precision, "extra_headers": [], "order": task.num, "short_name": task.name}) for task in contest.tasks) for ranking in self.rankings: ranking.data_queue.put((ranking.CONTEST_TYPE, {contest_id: contest_data})) ranking.data_queue.put((ranking.USER_TYPE, users)) ranking.data_queue.put((ranking.TASK_TYPE, tasks))
def get_contest(self): """See docstring in class Loader. """ name = os.path.split(self.path)[1] conf = yaml.safe_load( io.open(os.path.join(self.path, "contest.yaml"), "rt", encoding="utf-8")) logger.info("Loading parameters for contest %s." % name) args = {} load(conf, args, ["name", "nome_breve"]) load(conf, args, ["description", "nome"]) assert name == args["name"] load(conf, args, "token_initial") load(conf, args, "token_max") load(conf, args, "token_total") load(conf, args, "token_min_interval", conv=make_timedelta) load(conf, args, "token_gen_time", conv=make_timedelta) load(conf, args, "token_gen_number") load(conf, args, ["start", "inizio"], conv=make_datetime) load(conf, args, ["stop", "fine"], conv=make_datetime) load(conf, args, "max_submission_number") load(conf, args, "max_user_test_number") load(conf, args, "min_submission_interval", conv=make_timedelta) load(conf, args, "min_user_test_interval", conv=make_timedelta) logger.info("Contest parameters loaded.") tasks = load(conf, None, ["tasks", "problemi"]) self.tasks_order = dict((name, num) for num, name in enumerate(tasks)) self.users_conf = dict((user['username'], user) for user in load(conf, None, ["users", "utenti"])) users = self.users_conf.keys() return Contest(**args), tasks, users
def contest_from_db(contest_id, session): """Return the contest object with the given id contest_id (int|None): the id of the contest, or None to return None. session (Session): SQLAlchemy session to use. return (Contest|None): None if contest_id is None, or the contest. raise (ImportDataError): if there is no contest with the given id. """ if contest_id is None: return None contest = Contest.get_from_id(contest_id, session) if contest is None: raise ImportDataError("The specified contest (id %s) does not exist." % contest_id) return contest
def contest_from_db(contest_id, session): """Return the contest object with the given id contest_id (int|None): the id of the contest, or None to return None. session (Session): SQLAlchemy session to use. return (Contest|None): None if contest_id is None, or the contest. raise (ImportDataError): if there is no contest with the given id. """ if contest_id is None: return None contest = Contest.get_from_id(contest_id, session) if contest is None: raise ImportDataError( "The specified contest (id %s) does not exist." % contest_id) return contest
def harvest_contest_data(contest_id): """Retrieve the couples username, password and the task list for a given contest. contest_id (int): the id of the contest we want. return (tuple): the first element is a dictionary mapping usernames to passwords; the second one is the list of the task names. """ users = {} tasks = [] with SessionGen() as session: contest = Contest.get_from_id(contest_id, session) for user in contest.users: users[user.username] = {'password': user.password} for task in contest.tasks: tasks.append((task.id, task.name)) return users, tasks
def search_jobs_not_done(self): """Look in the database for submissions that have not been scored for no good reasons. Put the missing job in the queue. """ # Do this only if we are not still loading old submission # (from the start of the service). if self.scoring_old_submission: return True with SessionGen(commit=False) as session: contest = Contest.get_from_id(self.contest_id, session) new_submission_results_to_score = set() for submission in contest.get_submissions(): if submission.user.hidden: continue for dataset in get_datasets_to_judge(submission.task): sr = submission.get_result(dataset) sr_id = (submission.id, dataset.id) if sr is not None and (sr.evaluated() or sr.compilation_outcome == "fail") and \ sr_id not in self.submission_results_scored: new_submission_results_to_score.add(sr_id) if submission.tokened() and \ submission.id not in self.tokens_sent_to_rankings: self.rankings_send_token(submission) new_s = len(new_submission_results_to_score) old_s = len(self.submission_results_to_score) logger.info("Submissions found to score: %d." % new_s) if new_s > 0: self.submission_results_to_score |= new_submission_results_to_score if old_s == 0: self.add_timeout(self.score_old_submissions, None, 0.5, immediately=False) # Run forever. return True
def add_participation(username, contest_id, ip, delay_time, extra_time, password, team_code, hidden): logger.info("Creating the user in the database.") delay_time = delay_time if delay_time is not None else 0 extra_time = extra_time if extra_time is not None else 0 try: with SessionGen() as session: user = \ session.query(User).filter(User.username == username).first() if user is None: logger.error("No user with username `%s' found.", username) return False contest = Contest.get_from_id(contest_id, session) if contest is None: logger.error("No contest with id `%s' found.", contest_id) return False team = None if team_code is not None: team = \ session.query(Team).filter(Team.code == team_code).first() if team is None: logger.error("No team with code `%s' found.", team_code) return False participation = Participation( user=user, contest=contest, ip=ip, delay_time=datetime.timedelta(seconds=delay_time), extra_time=datetime.timedelta(seconds=extra_time), password=password, team=team, hidden=hidden) session.add(participation) session.commit() except IntegrityError: logger.error("A participation for this user in this contest " "already exists.") return False logger.info("Participation added.") return True
def generate_passwords(contest_id, exclude_hidden, exclude_unrestricted, output_path): logger.info("Updating passwords...") with open(output_path, 'w') as io: io.write("contest_id,team,fullname,username,password\n") with SessionGen() as session: if contest_id is not None: contest = Contest.get_from_id(contest_id, session) objects = session.query(Participation).join( Participation.user).join(Participation.team) if exclude_unrestricted: objects = objects.filter( Participation.unrestricted == False) if exclude_hidden: objects = objects.filter(Participation.hidden == False) else: objects = session.query(User) for obj in objects: password = generate_random_password() obj.password = build_password(password, 'plaintext') user = obj if isinstance(obj, User) else obj.user fullname = "%s %s" % (user.first_name, user.last_name) if isinstance(obj, Participation): team = obj.team.code if obj.team is not None else '' logger.info( "Updating participation of user %s (team=%s) on contest id %d", user.username, team, contest.id) io.write( "%d,%s,%s,%s,%s\n" % (contest.id, team, fullname, user.username, password)) else: logger.info("Updating user %s", user.username) io.write(",,%s,%s,%s\n" % (fullname, user.username, password)) session.commit() logger.info("Done.") return True
def do_export(self): """Run the actual export code. """ logger.operation = "exporting contest %s" % self.contest_id logger.info("Starting export.") logger.info("Creating dir structure.") try: os.mkdir(self.spool_dir) except OSError: logger.critical("The specified directory already exists, " "I won't overwrite it.") return False os.mkdir(self.upload_dir) with SessionGen() as session: self.contest = Contest.get_from_id(self.contest_id, session) self.submissions = sorted( (submission for submission in self.contest.get_submissions() if not submission.participation.hidden), key=lambda submission: submission.timestamp) # Creating users' directory. for participation in self.contest.participations: if not participation.hidden: os.mkdir(os.path.join( self.upload_dir, participation.user.username)) try: self.export_submissions() self.export_ranking() except Exception: logger.critical("Generic error.", exc_info=True) return False logger.info("Export finished.") logger.operation = "" return True
def do_export(self): """Run the actual export code. """ logger.operation = "exporting contest %s" % self.contest_id logger.info("Starting export.") logger.info("Creating dir structure.") try: os.mkdir(self.spool_dir) except OSError: logger.critical("The specified directory already exists, " "I won't overwrite it.") return False os.mkdir(self.upload_dir) with SessionGen() as session: self.contest = Contest.get_from_id(self.contest_id, session) self.submissions = sorted( (submission for submission in self.contest.get_submissions() if not submission.participation.hidden), key=lambda submission: submission.timestamp) # Creating users' directory. for participation in self.contest.participations: if not participation.hidden: os.mkdir( os.path.join(self.upload_dir, participation.user.username)) try: self.export_submissions() self.export_ranking() except Exception: logger.critical("Generic error.", exc_info=True) return False logger.info("Export finished.") logger.operation = "" return True
def __init__(self, contest_id, export_target, dump_files, dump_model, skip_generated, skip_submissions, skip_user_tests): self.contest_id = contest_id self.dump_files = dump_files self.dump_model = dump_model self.skip_generated = skip_generated self.skip_submissions = skip_submissions self.skip_user_tests = skip_user_tests # If target is not provided, we use the contest's name. if export_target == "": with SessionGen() as session: contest = Contest.get_from_id(self.contest_id, session) self.export_target = "dump_%s.tar.gz" % contest.name logger.warning("export_target not given, using \"%s\"" % self.export_target) else: self.export_target = export_target self.file_cacher = FileCacher()
def __init__(self, contest_id, export_target, dump_files, dump_model, light, skip_submissions, skip_user_tests): self.contest_id = contest_id self.dump_files = dump_files self.dump_model = dump_model self.light = light self.skip_submissions = skip_submissions self.skip_user_tests = skip_user_tests # If target is not provided, we use the contest's name. if export_target == "": with SessionGen(commit=False) as session: contest = Contest.get_from_id(self.contest_id, session) self.export_target = "dump_%s.tar.gz" % contest.name logger.warning("export_target not given, using \"%s\"" % self.export_target) else: self.export_target = export_target self.file_cacher = FileCacher()
def add_participation(username, contest_id): try: with SessionGen() as session: user = session.query(User).filter(User.username == username).first() if user is None: return False contest = Contest.get_from_id(contest_id, session) if contest is None: return False participation = Participation( user=user, contest=contest, hidden=False, unrestricted=False ) session.add(participation) session.commit() except IntegrityError: return False logger.info("Added participation for user {}".format(username)) return True
def get_contest_object(self): """ Return the Contest database object. """ args = {} # Names. args["name"] = self.params["short_name"] args["description"] = self.params["long_name"] # Languages. args["languages"] = self.params["languages"] # Communication args["allow_questions"] = self.params.get("allow_questions", False) # Times. start_time = time_from_str(self.params["start_time"]) stop_time = time_from_str(self.params["end_time"]) args["start"] = make_datetime(time.mktime(start_time.timetuple())) args["stop"] = make_datetime(time.mktime(stop_time.timetuple())) # Limits. args["max_submission_number"] = self.params["max_submission_number"] args["max_user_test_number"] = self.params["max_user_test_number"] interval_seconds = self.params["min_submission_interval"] if interval_seconds is not None: delta = timedelta(seconds=interval_seconds) args["min_submission_interval"] = delta interval_seconds = self.params["min_user_test_interval"] if interval_seconds is not None: delta = timedelta(seconds=interval_seconds) args["min_user_test_interval"] = delta return Contest(**args)
def __init__(self, contest_id, export_target, dump_files, dump_model, skip_generated, skip_submissions, skip_user_tests): self.contest_id = contest_id self.dump_files = dump_files self.dump_model = dump_model self.skip_generated = skip_generated self.skip_submissions = skip_submissions self.skip_user_tests = skip_user_tests self.export_target = export_target # If target is not provided, we use the contest's name. if export_target == "": with SessionGen() as session: contest = Contest.get_from_id(self.contest_id, session) if contest is None: logger.critical("Please specify a valid contest id.") self.contest_id = None else: self.export_target = "dump_%s.tar.gz" % contest.name logger.warning("export_target not given, using \"%s\"" % self.export_target) self.file_cacher = FileCacher()
def precache_files(self, contest_id): """RPC to ask the worker to precache of files in the contest. contest_id (int): the id of the contest """ # In order to avoid a long-living connection, first fetch the # complete list of files and then download the files; since # this is just pre-caching, possible race conditions are not # dangerous logger.info("Precaching files for contest %d.", contest_id) with SessionGen() as session: contest = Contest.get_from_id(contest_id, session) files = contest.enumerate_files(skip_submissions=True, skip_user_tests=True) for digest in files: try: self.file_cacher.load(digest, if_needed=True) except KeyError: # No problem (at this stage) if we cannot find the # file pass logger.info("Precaching finished.")
class ContestHandler(BaseHandler): """A handler that has a contest attached. Most of the RequestHandler classes in this application will be a child of this class. """ def __init__(self, *args, **kwargs): super(ContestHandler, self).__init__(*args, **kwargs) self.contest_url = None def prepare(self): self.choose_contest() if self.contest.allowed_localizations: lang_codes = filter_language_codes( list(iterkeys(self.available_translations)), self.contest.allowed_localizations) self.available_translations = dict( (k, v) for k, v in iteritems(self.available_translations) if k in lang_codes) super(ContestHandler, self).prepare() if self.is_multi_contest(): self.contest_url = \ create_url_builder(self.url(self.contest.name)) else: self.contest_url = self.url # Run render_params() now, not at the beginning of the request, # because we need contest_name self.r_params = self.render_params() def choose_contest(self): """Fill self.contest using contest passed as argument or path. If a contest was specified as argument to CWS, fill self.contest with that; otherwise extract it from the URL path. """ if self.is_multi_contest(): # Choose the contest found in the path argument # see: https://github.com/tornadoweb/tornado/issues/1673 contest_name = self.path_args[0] # Select the correct contest or return an error self.contest = self.sql_session.query(Contest)\ .filter(Contest.name == contest_name).first() if self.contest is None: self.contest = Contest( name=contest_name, description=contest_name) # render_params in this class assumes the contest is loaded, # so we cannot call it without a fully defined contest. Luckily # the one from the base class is enough to display a 404 page. self.r_params = super(ContestHandler, self).render_params() raise tornado.web.HTTPError(404) else: # Select the contest specified on the command line self.contest = Contest.get_from_id( self.service.contest_id, self.sql_session) def get_current_user(self): """Return the currently logged in participation. The name is get_current_user because tornado requires that name. The participation is obtained from one of the possible sources: - if IP autologin is enabled, the remote IP address is matched with the participation IP address; if a match is found, that participation is returned; in case of errors, None is returned; - if username/password authentication is enabled, and the cookie is valid, the corresponding participation is returned, and the cookie is refreshed. After finding the participation, IP login and hidden users restrictions are checked. In case of any error, or of a login by other sources, the cookie is deleted. return (Participation|None): the participation object for the user logged in for the running contest. """ cookie_name = self.contest.name + "_login" participation = None if self.contest.ip_autologin: try: participation = self._get_current_user_from_ip() # If the login is IP-based, we delete previous cookies. if participation is not None: self.clear_cookie(cookie_name) except RuntimeError: return None if participation is None \ and self.contest.allow_password_authentication: participation = self._get_current_user_from_cookie() if participation is None: self.clear_cookie(cookie_name) return None # Check if user is using the right IP (or is on the right subnet), # and that is not hidden if hidden users are blocked. ip_login_restricted = \ self.contest.ip_restriction and participation.ip is not None \ and not check_ip(self.request.remote_ip, participation.ip) hidden_user_restricted = \ participation.hidden and self.contest.block_hidden_participations if ip_login_restricted or hidden_user_restricted: self.clear_cookie(cookie_name) participation = None return participation def _get_current_user_from_ip(self): """Return the current participation based on the IP address. return (Participation|None): the only participation matching the remote IP address, or None if no participations could be matched. raise (RuntimeError): if there is more than one participation matching the remote IP address. """ try: # We encode it as a network (i.e., we assign it a /32 or # /128 mask) since we're comparing it for equality with # other networks. remote_ip = ipaddress.ip_network(str(self.request.remote_ip)) except ValueError: return None participations = self.sql_session.query(Participation)\ .filter(Participation.contest == self.contest)\ .filter(Participation.ip.any(remote_ip)) # If hidden users are blocked we ignore them completely. if self.contest.block_hidden_participations: participations = participations\ .filter(Participation.hidden.is_(False)) participations = participations.all() if len(participations) == 1: return participations[0] # Having more than participation with the same IP, # is a mistake and should not happen. In such case, # we disallow login for that IP completely, in order to # make sure the problem is noticed. if len(participations) > 1: logger.error("%d participants have IP %s while" "auto-login feature is enabled." % ( len(participations), remote_ip)) raise RuntimeError("More than one participants with the same IP.") def _get_current_user_from_cookie(self): """Return the current participation based on the cookie. If a participation can be extracted, the cookie is refreshed. return (Participation|None): the participation extracted from the cookie, or None if not possible. """ cookie_name = self.contest.name + "_login" if self.get_secure_cookie(cookie_name) is None: return None # Parse cookie. try: cookie = pickle.loads(self.get_secure_cookie(cookie_name)) username = cookie[0] password = cookie[1] last_update = make_datetime(cookie[2]) except: return None # Check if the cookie is expired. if self.timestamp - last_update > \ timedelta(seconds=config.cookie_duration): return None # Load participation from DB and make sure it exists. participation = self.sql_session.query(Participation)\ .join(Participation.user)\ .options(contains_eager(Participation.user))\ .filter(Participation.contest == self.contest)\ .filter(User.username == username)\ .first() if participation is None: return None # Check that the password is correct (if a contest-specific # password is defined, use that instead of the user password). if participation.password is None: correct_password = participation.user.password else: correct_password = participation.password if password != correct_password: return None if self.refresh_cookie: self.set_secure_cookie(cookie_name, pickle.dumps((username, password, make_timestamp())), expires_days=None) return participation def render_params(self): ret = super(ContestHandler, self).render_params() ret["contest"] = self.contest if self.contest_url is not None: ret["contest_url"] = self.contest_url ret["phase"] = self.contest.phase(self.timestamp) ret["printing_enabled"] = (config.printer is not None) ret["questions_enabled"] = self.contest.allow_questions ret["testing_enabled"] = self.contest.allow_user_tests if self.current_user is not None: participation = self.current_user ret["participation"] = participation ret["user"] = participation.user res = compute_actual_phase( self.timestamp, self.contest.start, self.contest.stop, self.contest.analysis_start if self.contest.analysis_enabled else None, self.contest.analysis_stop if self.contest.analysis_enabled else None, self.contest.per_user_time, participation.starting_time, participation.delay_time, participation.extra_time) ret["actual_phase"], ret["current_phase_begin"], \ ret["current_phase_end"], ret["valid_phase_begin"], \ ret["valid_phase_end"] = res if ret["actual_phase"] == 0: ret["phase"] = 0 # set the timezone used to format timestamps ret["timezone"] = get_timezone(participation.user, self.contest) # some information about token configuration ret["tokens_contest"] = self.contest.token_mode t_tokens = set(t.token_mode for t in self.contest.tasks) if len(t_tokens) == 1: ret["tokens_tasks"] = next(iter(t_tokens)) else: ret["tokens_tasks"] = TOKEN_MODE_MIXED return ret def get_login_url(self): """The login url depends on the contest name, so we can't just use the "login_url" application parameter. """ return self.contest_url()
def do_export(self): """Run the actual export code.""" logger.info("Starting export.") # Export users users = [] with SessionGen() as session: # Get the contest contest = Contest.get_from_id(self.contest_id, session) if contest is None: logger.critical("Contest %d not found in database.", self.contest_id) exit(1) # Get participations of the contest participations = contest.participations for p in participations: users.append({ u'username': p.user.username, u'password': p.user.password, u'first_name': p.user.first_name, u'last_name': p.user.last_name }) if self.json: j = {'users': users} with io.open(os.path.join(self.export_target), "wb") as fout: json.dump(j, fout, encoding="utf-8", indent=2, sort_keys=True) else: html = """ <table> <tr> <th>Prénom</th> <th>Nom</th> <th>Pseudo</th> <th>Mot de passe</th> </tr> """ for u in users: html += """ <tr> <td>{first_name}</td> <td>{last_name}</td> <td>{username}</td> <td>{password}</td> </tr> """.format( first_name=u['first_name'], last_name=u['last_name'], username=u['username'], password=u['password'], ) html += "</table>" html = """ <!DOCTYPE html> <html lang="fr"> <head> <meta charset="utf-8"> <title>Export des utilisateurs</title> <style> table {{ font-family: arial, sans-serif; border-collapse: separate; border-spacing: 0 1em; width: 100%; }} td, th {{ border-top: 1px solid #dddddd; text-align: left; padding: 8px; }} </style> </head> <body> {} </body> </html> """.format(html) with io.open(os.path.join(self.export_target), "wb") as fout: fout.write(html.encode('utf8')) logger.info("Export finished.") return True
def do_export(self): """Run the actual export code.""" logger.info("Starting export.") export_dir = self.export_target archive_info = get_archive_info(self.export_target) if archive_info["write_mode"] != "": # We are able to write to this archive. if os.path.exists(self.export_target): logger.critical("The specified file already exists, " "I won't overwrite it.") return False export_dir = os.path.join(tempfile.mkdtemp(), archive_info["basename"]) logger.info("Creating dir structure.") try: os.mkdir(export_dir) except OSError: logger.critical("The specified directory already exists, " "I won't overwrite it.") return False files_dir = os.path.join(export_dir, "files") descr_dir = os.path.join(export_dir, "descriptions") os.mkdir(files_dir) os.mkdir(descr_dir) with SessionGen() as session: # Export files. logger.info("Exporting files.") if self.dump_files: for contest_id in self.contests_ids: contest = Contest.get_from_id(contest_id, session) files = contest.enumerate_files(self.skip_submissions, self.skip_user_tests, self.skip_generated) for file_ in files: if not self.safe_get_file(file_, os.path.join(files_dir, file_), os.path.join(descr_dir, file_)): return False # Export data in JSON format. if self.dump_model: logger.info("Exporting data to a JSON file.") # We use strings because they'll be the keys of a JSON # object self.ids = {} self.queue = [] data = dict() for cls, lst in [(Contest, self.contests_ids), (User, self.users_ids), (Task, self.tasks_ids)]: for i in lst: obj = cls.get_from_id(i, session) self.get_id(obj) # Specify the "root" of the data graph data["_objects"] = self.ids.values() while len(self.queue) > 0: obj = self.queue.pop(0) data[self.ids[obj.sa_identity_key]] = \ self.export_object(obj) data["_version"] = model_version with io.open(os.path.join(export_dir, "contest.json"), "wb") as fout: json.dump(data, fout, encoding="utf-8", indent=4, sort_keys=True) # If the admin requested export to file, we do that. if archive_info["write_mode"] != "": archive = tarfile.open(self.export_target, archive_info["write_mode"]) archive.add(export_dir, arcname=archive_info["basename"]) archive.close() rmtree(export_dir) logger.info("Export finished.") return True
class ContestHandler(BaseHandler): """A handler that has a contest attached. Most of the RequestHandler classes in this application will be a child of this class. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.contest_url = None def prepare(self): self.choose_contest() if self.contest.allowed_localizations: lang_codes = filter_language_codes( list(self.available_translations.keys()), self.contest.allowed_localizations) self.available_translations = dict( (k, v) for k, v in self.available_translations.items() if k in lang_codes) super().prepare() if self.is_multi_contest(): self.contest_url = self.url[self.contest.name] else: self.contest_url = self.url # Run render_params() now, not at the beginning of the request, # because we need contest_name self.r_params = self.render_params() def choose_contest(self): """Fill self.contest using contest passed as argument or path. If a contest was specified as argument to CWS, fill self.contest with that; otherwise extract it from the URL path. """ if self.is_multi_contest(): # Choose the contest found in the path argument # see: https://github.com/tornadoweb/tornado/issues/1673 contest_name = self.path_args[0] # Select the correct contest or return an error self.contest = self.sql_session.query(Contest)\ .filter(Contest.name == contest_name).first() if self.contest is None: self.contest = Contest( name=contest_name, description=contest_name) # render_params in this class assumes the contest is loaded, # so we cannot call it without a fully defined contest. Luckily # the one from the base class is enough to display a 404 page. self.r_params = super().render_params() raise tornado.web.HTTPError(404) else: # Select the contest specified on the command line self.contest = Contest.get_from_id( self.service.contest_id, self.sql_session) def get_current_user(self): """Return the currently logged in participation. The name is get_current_user because tornado requires that name. The participation is obtained from one of the possible sources: - if IP autologin is enabled, the remote IP address is matched with the participation IP address; if a match is found, that participation is returned; in case of errors, None is returned; - if username/password authentication is enabled, and the cookie is valid, the corresponding participation is returned, and the cookie is refreshed. After finding the participation, IP login and hidden users restrictions are checked. In case of any error, or of a login by other sources, the cookie is deleted. return (Participation|None): the participation object for the user logged in for the running contest. """ cookie_name = self.contest.name + "_login" cookie = self.get_secure_cookie(cookie_name) try: # In py2 Tornado gives us the IP address as a native binary # string, whereas ipaddress wants text (unicode) strings. ip_address = ipaddress.ip_address(str(self.request.remote_ip)) except ValueError: logger.warning("Invalid IP address provided by Tornado: %s", self.request.remote_ip) return None participation, cookie = authenticate_request( self.sql_session, self.contest, self.timestamp, cookie, ip_address) if cookie is None: self.clear_cookie(cookie_name) elif self.refresh_cookie: self.set_secure_cookie(cookie_name, cookie, expires_days=None) return participation def render_params(self): ret = super().render_params() ret["contest"] = self.contest if self.contest_url is not None: ret["contest_url"] = self.contest_url ret["phase"] = self.contest.phase(self.timestamp) ret["printing_enabled"] = (config.printer is not None) ret["questions_enabled"] = self.contest.allow_questions ret["testing_enabled"] = self.contest.allow_user_tests if self.current_user is not None: participation = self.current_user ret["participation"] = participation ret["user"] = participation.user res = compute_actual_phase( self.timestamp, self.contest.start, self.contest.stop, self.contest.analysis_start if self.contest.analysis_enabled else None, self.contest.analysis_stop if self.contest.analysis_enabled else None, self.contest.per_user_time, participation.starting_time, participation.delay_time, participation.extra_time) ret["actual_phase"], ret["current_phase_begin"], \ ret["current_phase_end"], ret["valid_phase_begin"], \ ret["valid_phase_end"] = res if ret["actual_phase"] == 0: ret["phase"] = 0 # set the timezone used to format timestamps ret["timezone"] = get_timezone(participation.user, self.contest) # some information about token configuration ret["tokens_contest"] = self.contest.token_mode t_tokens = set(t.token_mode for t in self.contest.tasks) if len(t_tokens) == 1: ret["tokens_tasks"] = next(iter(t_tokens)) else: ret["tokens_tasks"] = TOKEN_MODE_MIXED return ret def get_login_url(self): """The login url depends on the contest name, so we can't just use the "login_url" application parameter. """ return self.contest_url() def get_task(self, task_name): """Return the task in the contest with the given name. task_name (str): the name of the task we are interested in. return (Task|None): the corresponding task object, if found. """ return self.sql_session.query(Task) \ .filter(Task.contest == self.contest) \ .filter(Task.name == task_name) \ .one_or_none() def get_submission(self, task, submission_num): """Return the num-th contestant's submission on the given task. task (Task): a task for the contest that is being served. submission_num (str): a positive number, in decimal encoding. return (Submission|None): the submission_num-th submission, in chronological order, that was sent by the currently logged in contestant on the given task (None if not found). """ return self.sql_session.query(Submission) \ .filter(Submission.participation == self.current_user) \ .filter(Submission.task == task) \ .order_by(Submission.timestamp) \ .offset(int(submission_num) - 1) \ .first() def get_user_test(self, task, user_test_num): """Return the num-th contestant's test on the given task. task (Task): a task for the contest that is being served. user_test_num (str): a positive number, in decimal encoding. return (UserTest|None): the user_test_num-th user test, in chronological order, that was sent by the currently logged in contestant on the given task (None if not found). """ return self.sql_session.query(UserTest) \ .filter(UserTest.participation == self.current_user) \ .filter(UserTest.task == task) \ .order_by(UserTest.timestamp) \ .offset(int(user_test_num) - 1) \ .first() def add_notification(self, subject, text, level, text_params=None): subject = self._(subject) text = self._(text) if text_params is not None: text %= text_params self.service.add_notification(self.current_user.user.username, self.timestamp, subject, text, level) def notify_success(self, subject, text, text_params=None): self.add_notification(subject, text, NOTIFICATION_SUCCESS, text_params) def notify_warning(self, subject, text, text_params=None): self.add_notification(subject, text, NOTIFICATION_WARNING, text_params) def notify_error(self, subject, text, text_params=None): self.add_notification(subject, text, NOTIFICATION_ERROR, text_params)
class ContestHandler(BaseHandler): """A handler that has a contest attached. Most of the RequestHandler classes in this application will be a child of this class. """ def prepare(self): super(ContestHandler, self).prepare() self.choose_contest() self._ = self.locale.translate if self.is_multi_contest(): self.contest_url = \ create_url_builder(self.url(self.contest.name)) else: self.contest_url = self.url # Run render_params() now, not at the beginning of the request, # because we need contest_name self.r_params = self.render_params() def choose_contest(self): """Fill self.contest using contest passed as argument or path. If a contest was specified as argument to CWS, fill self.contest with that; otherwise extract it from the URL path. """ if self.is_multi_contest(): # Choose the contest found in the path argument # see: https://github.com/tornadoweb/tornado/issues/1673 contest_name = self.path_args[0] # Select the correct contest or return an error try: self.contest = self.contest_list[contest_name] except KeyError: self.contest = Contest( name=contest_name, description=contest_name) self.r_params = self.render_params() raise tornado.web.HTTPError(404) else: # Select the contest specified on the command line self.contest = Contest.get_from_id( self.application.service.contest_id, self.sql_session) def get_current_user(self): """Return the currently logged in participation. The name is get_current_user because tornado requires that name. The participation is obtained from one of the possible sources: - if IP autologin is enabled, the remote IP address is matched with the participation IP address; if a match is found, that participation is returned; in case of errors, None is returned; - if username/password authentication is enabled, and the cookie is valid, the corresponding participation is returned, and the cookie is refreshed. After finding the participation, IP login and hidden users restrictions are checked. In case of any error, or of a login by other sources, the cookie is deleted. return (Participation|None): the participation object for the user logged in for the running contest. """ cookie_name = self.contest.name + "_login" participation = None if self.contest.ip_autologin: try: participation = self._get_current_user_from_ip() # If the login is IP-based, we delete previous cookies. if participation is not None: self.clear_cookie(cookie_name) except RuntimeError: return None if participation is None \ and self.contest.allow_password_authentication: participation = self._get_current_user_from_cookie() if participation is None: self.clear_cookie(cookie_name) return None # Check if user is using the right IP (or is on the right subnet), # and that is not hidden if hidden users are blocked. ip_login_restricted = \ self.contest.ip_restriction and participation.ip is not None \ and not check_ip(self.request.remote_ip, participation.ip) hidden_user_restricted = \ participation.hidden and self.contest.block_hidden_participations if ip_login_restricted or hidden_user_restricted: self.clear_cookie(cookie_name) participation = None return participation def _get_current_user_from_ip(self): """Return the current participation based on the IP address. return (Participation|None): the only participation matching the remote IP address, or None if no participations could be matched. raise (RuntimeError): if there is more than one participation matching the remote IP address. """ try: # We encode it as a network (i.e., we assign it a /32 or # /128 mask) since we're comparing it for equality with # other networks. remote_ip = ipaddress.ip_network(unicode(self.request.remote_ip)) except ValueError: return None participations = self.sql_session.query(Participation)\ .filter(Participation.contest == self.contest)\ .filter(Participation.ip.any(remote_ip)) # If hidden users are blocked we ignore them completely. if self.contest.block_hidden_participations: participations = participations\ .filter(Participation.hidden.is_(False)) participations = participations.all() if len(participations) == 1: return participations[0] # Having more than participation with the same IP, # is a mistake and should not happen. In such case, # we disallow login for that IP completely, in order to # make sure the problem is noticed. if len(participations) > 1: logger.error("%d participants have IP %s while" "auto-login feature is enabled." % ( len(participations), remote_ip)) raise RuntimeError("More than one participants with the same IP.") def _get_current_user_from_cookie(self): """Return the current participation based on the cookie. If a participation can be extracted, the cookie is refreshed. return (Participation|None): the participation extracted from the cookie, or None if not possible. """ cookie_name = self.contest.name + "_login" if self.get_secure_cookie(cookie_name) is None: return None # Parse cookie. try: cookie = pickle.loads(self.get_secure_cookie(cookie_name)) username = cookie[0] password = cookie[1] last_update = make_datetime(cookie[2]) except: return None # Check if the cookie is expired. if self.timestamp - last_update > \ timedelta(seconds=config.cookie_duration): return None # Load participation from DB and make sure it exists. participation = self.sql_session.query(Participation)\ .join(Participation.user)\ .options(contains_eager(Participation.user))\ .filter(Participation.contest == self.contest)\ .filter(User.username == username)\ .first() if participation is None: return None # Check that the password is correct (if a contest-specific # password is defined, use that instead of the user password). if participation.password is None: correct_password = participation.user.password else: correct_password = participation.password if password != correct_password: return None if self.refresh_cookie: self.set_secure_cookie(cookie_name, pickle.dumps((username, password, make_timestamp())), expires_days=None) return participation def get_user_locale(self): self.langs = self.application.service.langs lang_codes = self.langs.keys() if self.contest.allowed_localizations: lang_codes = filter_language_codes( lang_codes, self.contest.allowed_localizations) # Select the one the user likes most. basic_lang = 'en' if self.contest.allowed_localizations: basic_lang = lang_codes[0].replace("_", "-") http_langs = [lang_code.replace("_", "-") for lang_code in lang_codes] self.browser_lang = parse_accept_header( self.request.headers.get("Accept-Language", ""), LanguageAccept).best_match(http_langs, basic_lang) self.cookie_lang = self.get_cookie("language", None) if self.cookie_lang in http_langs: lang_code = self.cookie_lang else: lang_code = self.browser_lang self.set_header("Content-Language", lang_code) return self.langs[lang_code.replace("-", "_")] @staticmethod def _get_token_status(obj): """Return the status of the tokens for the given object. obj (Contest or Task): an object that has the token_* attributes. return (int): one of 0 (disabled), 1 (enabled/finite) and 2 (enabled/infinite). """ if obj.token_mode == "disabled": return 0 elif obj.token_mode == "finite": return 1 elif obj.token_mode == "infinite": return 2 else: raise RuntimeError("Unknown token_mode value.") def render_params(self): ret = super(ContestHandler, self).render_params() ret["contest"] = self.contest if hasattr(self, "contest_url"): ret["contest_url"] = self.contest_url ret["phase"] = self.contest.phase(self.timestamp) ret["printing_enabled"] = (config.printer is not None) ret["questions_enabled"] = self.contest.allow_questions ret["testing_enabled"] = self.contest.allow_user_tests if self.current_user is not None: participation = self.current_user res = compute_actual_phase( self.timestamp, self.contest.start, self.contest.stop, self.contest.analysis_start if self.contest.analysis_enabled else None, self.contest.analysis_stop if self.contest.analysis_enabled else None, self.contest.per_user_time, participation.starting_time, participation.delay_time, participation.extra_time) ret["actual_phase"], ret["current_phase_begin"], \ ret["current_phase_end"], ret["valid_phase_begin"], \ ret["valid_phase_end"] = res if ret["actual_phase"] == 0: ret["phase"] = 0 # set the timezone used to format timestamps ret["timezone"] = get_timezone(participation.user, self.contest) # some information about token configuration ret["tokens_contest"] = self._get_token_status(self.contest) t_tokens = sum(self._get_token_status(t) for t in self.contest.tasks) if t_tokens == 0: ret["tokens_tasks"] = 0 # all disabled elif t_tokens == 2 * len(self.contest.tasks): ret["tokens_tasks"] = 2 # all infinite else: ret["tokens_tasks"] = 1 # all finite or mixed # TODO Now all language names are shown in the active language. # It would be better to show them in the corresponding one. ret["lang_names"] = {} # Get language codes for allowed localizations lang_codes = self.langs.keys() if len(self.contest.allowed_localizations) > 0: lang_codes = filter_language_codes( lang_codes, self.contest.allowed_localizations) for lang_code, trans in self.langs.iteritems(): language_name = None # Filter lang_codes with allowed localizations if lang_code not in lang_codes: continue try: language_name = translate_language_country_code( lang_code, trans) except ValueError: language_name = translate_language_code( lang_code, trans) ret["lang_names"][lang_code.replace("_", "-")] = language_name ret["cookie_lang"] = self.cookie_lang ret["browser_lang"] = self.browser_lang return ret def get_login_url(self): """The login url depends on the contest name, so we can't just use the "login_url" application parameter. """ return self.contest_url()
def do_reimport(self): """Get the contest from the Loader and merge it.""" with SessionGen() as session: # Load the old contest from the database. old_contest = Contest.get_from_id(self.old_contest_id, session) old_users = dict((x.username, x) for x in old_contest.users) old_tasks = dict((x.name, x) for x in old_contest.tasks) # Load the new contest from the filesystem. new_contest, new_tasks, new_users = self.loader.get_contest() # Updates contest-global settings that are set in new_contest. self._update_columns(old_contest, new_contest) # Do the actual merge: compare all users of the old and of # the new contest and see if we need to create, update or # delete them. Delete only if authorized, fail otherwise. users = set(old_users.keys()) | set(new_users) for username in users: old_user = old_users.get(username, None) if old_user is None: # Create a new user. logger.info("Creating user %s" % username) new_user = self.loader.get_user(username) old_contest.users.append(new_user) elif username in new_users: # Update an existing user. logger.info("Updating user %s" % username) new_user = self.loader.get_user(username) self._update_object(old_user, new_user) else: # Delete an existing user. if self.force: logger.info("Deleting user %s" % username) old_contest.users.remove(old_user) else: logger.critical( "User %s exists in old contest, but " "not in the new one. Use -f to force." % username) return False # The same for tasks. Setting num for tasks requires a bit # of trickery, since we have to avoid triggering a # duplicate key constraint violation while we're messing # with the task order. To do that we just set sufficiently # high number on the first pass and then fix it on a # second pass. tasks = set(old_tasks.keys()) | set(new_tasks) current_num = max(len(old_tasks), len(new_tasks)) for task in tasks: old_task = old_tasks.get(task, None) if old_task is None: # Create a new task. logger.info("Creating task %s" % task) new_task = self.loader.get_task(task) new_task.num = current_num current_num += 1 old_contest.tasks.append(new_task) elif task in new_tasks: # Update an existing task. if self.full or self.loader.has_changed(task): logger.info("Updating task %s" % task) new_task = self.loader.get_task(task) new_task.num = current_num current_num += 1 self._update_object(old_task, new_task) else: logger.info("Task %s has not changed" % task) # Even unchanged tasks should use a temporary number # to avoid duplicate numbers when we fix them. old_task.num = current_num current_num += 1 else: # Delete an existing task. if self.force: logger.info("Deleting task %s" % task) session.delete(old_task) else: logger.critical( "Task %s exists in old contest, but " "not in the new one. Use -f to force." % task) return False session.flush() # And finally we fix the numbers; old_contest must be # refreshed because otherwise SQLAlchemy doesn't get aware # that some tasks may have been deleted tasks_order = dict((name, num) for num, name in enumerate(new_tasks)) session.refresh(old_contest) for task in old_contest.tasks: task.num = tasks_order[task.name] session.commit() logger.info("Reimport finished (contest id: %s)." % self.old_contest_id) return True