def _sync(self): if self.auto_sync: logger.info("Synchronizing {}".format(self.path)) with chdir(self.path): gitout = "" try: gitout = check_output(["git", "pull"]) except: logger.error("Couldn't sync with repository: " + "{}".format(gitout)) else: logger.info("Finished synchronization: " + "{}".format(gitout)) gitout = "" try: gitout = check_output(["git", "push"]) except: logger.error("Couldn't push to repository: " + "{}".format(gitout)) else: logger.info("Finished pushing: " + "{}".format(gitout))
def get_contest(self): """See docstring in class Loader. """ # Unset stack size limit resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)) self.buildpath = os.path.join(self.path, "build") # We have to avoid copying the folder contest/build # or contest/task/build into contest/build. # For this reason, we ignore all files and directories named "build" # when copying recursively. copyrecursivelyifnecessary((self.path), self.buildpath, set(["build"])) with chdir(self.buildpath): rules = ".rules" if not os.path.exists(rules): os.mkdir(rules) rules = os.path.abspath(rules) self.contestconfig = ContestConfig(rules, os.path.basename(self.path)) self.contestconfig._readconfig("contest-config.py") tasknames = [t.name for t in self.contestconfig.tasks] usernames = [u.username for u in self.contestconfig.users] return self.contestconfig._makecontest(), tasknames, usernames
def build(self): file_cacher = FileCacher(path=os.path.join(self.wdir, ".cache")) with chdir(self.wdir): contestconfig = ContestConfig( os.path.join(self.wdir, ".rules"), "hidden contest", relevant_language=(self.language if self.language != "ALL" else None), ignore_latex=self.no_latex, minimal=self.minimal) copyifnecessary( os.path.join(contestconfig._get_ready_dir(), "contest-template.py"), os.path.join(self.wdir, "c.py")) contestconfig._readconfig("c.py") contestconfig._task(self.task, contestconfig.full_feedback, None, self.minimal, standalone_task=True) if not self.minimal: cdb = contestconfig._makecontest() test_udb = contestconfig._makeuser( contestconfig._mytestuser.username) test_gdb = contestconfig._makegroup( contestconfig._mytestuser.group.name, cdb) # We're not putting the test user on any team for testing # (shouldn't be needed). test_pdb = contestconfig._makeparticipation( contestconfig._mytestuser.username, cdb, test_udb, test_gdb, None) for t in contestconfig.tasks.values(): tdb = t._makedbobject(cdb, file_cacher) t._make_test_submissions(test_pdb, tdb, self.local_test) statements = list(contestconfig.tasks.values())[0]._statements if self.language == "ALL": return [ os.path.abspath(s.file_) for s in list(statements.values()) ] if self.language is not None: if self.language in statements: return os.path.abspath(statements[self.language].file_) else: return None primary_statements = [ s for s in list(statements.values()) if s.primary ] if len(primary_statements) == 0: return None elif len(primary_statements) == 1: return os.path.abspath(primary_statements[0].file_) else: raise Exception("More than one primary statement")
def _task(self, s, feedback, score_mode, minimal, standalone_task=False): """ Add a task to this contest (full version, not accessible from config.py). s (unicode): task name; the task description has to reside in the folder with the same name feedback: type of feedback (one of the variables no_feedback, partial_feedback, full_feedback, restricted_feedback) score_mode: how to calculate the final score (one of SCORE_MODE_MAX, SCORE_MODE_MAX_SUBTASK, SCORE_MODE_MAX_TOKENED_LAST) minimal (bool): only try to compile statement? """ # Check if this task should be ignored if self.onlytask is not None and self.onlytask != s: return if not os.path.isdir(s): raise Exception("No directory found for task {}".format(s)) if s in self.tasks: raise Exception("Task {} specified multiple times".format(s)) with chdir(s): if not os.path.isfile("config.py"): raise Exception("Couldn't find task config file. Make sure it " "is named 'config.py' and located on the " "topmost level of the folder {}" .format(os.getcwd())) with TaskConfig(self, os.path.abspath(".rules"), s, len(self.tasks), feedback, score_mode, ignore_latex=self.ignore_latex, relevant_language=self.relevant_language, minimal=minimal, standalone_task=standalone_task) as taskconfig: for f in self.ontasks: f(taskconfig) taskconfig._readconfig("config.py") taskconfig._printresult() self.tasks[s] = taskconfig if minimal: print_msg("Statement for task {} generated successfully". format(s), success=True) else: print_msg("Task {} loaded completely".format(s), success=True)
def _sync(self): if self.auto_sync: logger.info("Synchronizing {}".format(self.path)) with chdir(self.path): gitout = "" try: gitout = check_output(["git", "pull"]) except: logger.error("Couldn't sync with repository: " + "{}".format(gitout)) else: logger.info("Finished synchronization: " + "{}".format(gitout))
def getlog(self, file_path): #TODO Only do this if it's a git repository #if self.auto_sync: with chdir(self.path): gitout = "" try: #TODO Remove diff info lines gitout = check_output([ "git", "log", '--pretty=format:Date: %ci%n%n %s%n', "-p", "--word-diff=color", file_path ]) except: logger.error("Couldn't get log: " + "{}".format(gitout)) else: gitout = gitout.decode('utf-8') return gitout
def _task(self, s, feedback, minimal): """ Add a task to this contest (full version, not accessible from config.py). s (unicode): task name; the task description has to reside in the folder with the same name feedback: type of feedback (one of the variables no_feedback, partial_feedback, full_feedback, restricted_feedback) minimal (bool): only try to compile statement? """ # Check if this task should be ignored if self.onlytask is not None and self.onlytask != s: return if not os.path.isdir(s): raise Exception("No directory found for task {}".format(s)) if s in self.tasks: raise Exception("Task {} specified multiple times".format(s)) with chdir(s): if not os.path.isfile("config.py"): raise Exception("Couldn't find task config file. Make sure it " "is named 'config.py' and located on the " "topmost level of the folder {}" .format(os.getcwd())) with TaskConfig(self, os.path.abspath(".rules"), s, len(self.tasks), feedback, ignore_latex=self.ignore_latex, minimal=minimal) as taskconfig: for f in self.ontasks: f(taskconfig) taskconfig._readconfig("config.py") taskconfig._printresult() self.tasks[s] = taskconfig if minimal: print_msg("Statement for task {} generated successfully". format(s), success=True) else: print_msg("Task {} loaded completely".format(s), success=True)
def make(self): # Unset stack size limit resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)) if not os.path.exists(os.path.join(self.odir, "contest-config.py")): raise Exception("Directory doesn't contain contest-config.py") self.wdir = os.path.join(self.odir, "build") if self.clean: shutil.rmtree(self.wdir) if not os.path.exists(self.wdir): os.mkdir(self.wdir) # We have to avoid copying the folder contest/build # or contest/task/build into contest/build. # For this reason, we ignore all files and directories named "build" # when copying recursively. copyrecursivelyifnecessary(self.odir, self.wdir, set(["build"])) self.wdir = os.path.abspath(self.wdir) file_cacher = FileCacher(path=os.path.join(self.wdir, ".cache")) try: with chdir(self.wdir): contestconfig = ContestConfig( os.path.join(self.wdir, ".rules"), os.path.basename(self.odir), ignore_latex=self.no_latex, onlytask=self.task) contestconfig._readconfig("contest-config.py") if self.task is not None and len(contestconfig.tasks) == 0: raise Exception("Task {} not found".format(self.task)) cdb = contestconfig._makecontest() test_udb = contestconfig._makeuser( contestconfig._mytestuser.username) test_gdb = contestconfig._makegroup( contestconfig._mytestuser.group.name, cdb) # We're not putting the test user on any team for testing # (shouldn't be needed). test_pdb = contestconfig._makeparticipation( contestconfig._mytestuser.username, cdb, test_udb, test_gdb, None) for t in contestconfig.tasks.values(): tdb = t._makedbobject(cdb, file_cacher) t._make_test_submissions(test_pdb, tdb, self.local_test) finally: file_cacher.destroy_cache()
def build(self): file_cacher = FileCacher(path=os.path.join(self.wdir, ".cache")) try: with chdir(self.wdir): contestconfig = \ ContestConfig(os.path.join(self.wdir, ".rules"), "hidden contest", minimal=self.minimal) copyifnecessary(os.path.join(contestconfig._get_ready_dir(), "contest-template.py"), os.path.join(self.wdir, "c.py")) contestconfig._readconfig("c.py") contestconfig._task( self.task, contestconfig.full_feedback, self.minimal) if not self.minimal: cdb = contestconfig._makecontest() test_udb = contestconfig._makeuser( contestconfig._mytestuser.username) test_gdb = contestconfig._makegroup( contestconfig._mytestuser.group.name, cdb) # We're not putting the test user on any team for testing # (shouldn't be needed). test_pdb = contestconfig._makeparticipation( contestconfig._mytestuser.username, cdb, test_udb, test_gdb, None) for t in contestconfig.tasks.values(): tdb = t._makedbobject(cdb, file_cacher) t._make_test_submissions( test_pdb, tdb, self.local_test) finally: file_cacher.destroy_cache() primary_statements = [s for s in list(list(contestconfig.tasks.values())[ 0]._statements.values()) if s.primary] if len(primary_statements) == 0: return None elif len(primary_statements) == 1: return os.path.abspath(primary_statements[0].file_) else: raise Exception("More than one primary statement")
def commit(self, file_path, file_identifier): #TODO Only do this if it's a git repository #if self.auto_sync: logger.info("Committing {} in {}".format(file_path, self.path)) with chdir(self.path): gitout = "" try: gitout = check_output(["git", "add", file_path]) except: logger.error("Couldn't add file to git staging area: " + "{}".format(gitout)) else: try: gitout = "" #NOTE file_path is relative to self.path, which isn't #necessarily the root of the git repo. So the commit message #might be confusing. gitout = check_output([ "git", "commit", "-o", file_path, "-m", "Changes to " + file_identifier + ", uploaded via GerTranslate web interface", #TODO Provide meaningful commit message and author "--author", "\"GerTranslate <GerTranslate@localhost>\"" ]) except: logger.error("Couldn't commit in repository: " + "{}".format(gitout)) else: logger.info("Committed: " + "{}".format(gitout))
def make_overview_sheets(self): """ Print overview sheets in two versions (with tasks and all contestants of any individual team in one file OR without tasks and all contestants separately) and ZIP them together """ if self.contest.ignore_latex: return teams = {} contestants_with_language = {} overview_sheets_for = {} assert (all(t._feedback_level == FEEDBACK_LEVEL_RESTRICTED for t in self.contest.tasks.values())) assert (all(t.score_mode() == SCORE_MODE_MAX_SUBTASK for t in self.contest.tasks.values())) prefix = "overview-sheets-for" for u in self.contest.users.values(): team = u.team if team not in teams: teams[team] = [] teams[team].append(u) for l in u.primary_statements: if l not in contestants_with_language: contestants_with_language[l] = [] contestants_with_language[l].append(u) if not os.path.exists("overview"): os.mkdir("overview") copyrecursivelyifnecessary( os.path.join(self.contest.wdir, "general"), os.path.join(self.contest.wdir, "overview", "general")) self.contest.supply("contestoverview", def_latex("olympiadyear", self.year)) import csv printingunwanted = dict() requestsfile = "printingrequested.csv" if os.path.exists(requestsfile): with open(requestsfile, encoding='utf-8-sig') as f: reader = csv.DictReader(f, delimiter=',', quotechar='"') for row in reader: c, l = row["Country"], row["Language"] if c not in printingunwanted: printingunwanted[c] = dict() printingunwanted[c]["en"] = row["English"] == "No" printingunwanted[c][l] = row["Own"] == "No" with chdir("overview"): if not os.path.exists(".overviews-per-language"): os.mkdir(".overviews-per-language") with chdir(".overviews-per-language"): for l, users in contestants_with_language.items(): if self.contest.relevant_language and \ self.contest.relevant_language != l: continue self.supply_overview() self.contest._build_supplements_for_key("contestoverview") #Provide access to the BOI logo shutil.copy( os.path.join(os.path.dirname(__file__), "header{}.pdf".format(self.year)), os.path.join(os.getcwd(), "header.pdf")) def do_supply_language(): return self.language_supplement(l) def do_supply_credentials(): return "\n".join("\\printoverviewpage{%s}{%s, %s}{%s}" % \ (u.username, u.lastname, u.firstname, u.password) for u in users) self.contest.supply("lang", do_supply_language) self.contest.supply("credentials", do_supply_credentials) filename = prefix + "-" + l + ".tex" shutil.copy( os.path.join(os.path.dirname(__file__), "overview-template.tex"), filename) self.contest._build_supplements_for_key("credentials") self.contest._build_supplements_for_key("lang") pdf = PdfFileReader(self.contest.compile(filename)) assert (pdf.getNumPages() == len(users)) for i, u in enumerate(users): overview_sheets_for[(u.username, l)] = pdf.getPage(i) self.contest.supplements["lang"].parts.clear() self.contest.supplements["credentials"].parts.clear() def cleardoublepage(stream): if stream.getNumPages() % 2 == 1: stream.addBlankPage() for team, users in teams.items(): if not os.path.exists(team.code): os.mkdir(team.code) with chdir(team.code): hw = PdfFileWriter() for u in users: # Overview sheets ow = PdfFileWriter() for l in u.primary_statements: if self.contest.relevant_language and \ self.contest.relevant_language != l: continue ow.addPage(overview_sheets_for[(u.username, l)]) with open("overview-" + u.username + ".pdf", "wb") as f: ow.write(f) # handout for l in u.primary_statements: if self.contest.relevant_language and \ self.contest.relevant_language != l: continue if printingunwanted \ .get(team.name, dict()) \ .get(l, False): print_msg("Not adding translation to language " "{} for user {} to the handout " "as requested by team {}".format( l, u.username, team.code)) hw.addPage(overview_sheets_for[(u.username, l)]) cleardoublepage(hw) continue hw.addPage(overview_sheets_for[(u.username, l)]) cleardoublepage(hw) for t in sorted(self.contest.tasks.values(), key=lambda x: x.name): if l in t._statements: st = PdfFileReader(t._statements[l].file_) hw.appendPagesFromReader(st) cleardoublepage(hw) with open("handout.pdf", "wb") as f: hw.write(f) job = { "overview-" + u.username + ".pdf": os.path.join(team.code, "overview-" + u.username + ".pdf") for u in users } job["handout.pdf"] = os.path.join(team.code, "handout.pdf") r = ZipRule(os.path.join("..", ".rules"), team.code + "-all.zip", job).ensure()
def run(self): sandbox = LaTeXSandbox(self.file_cacher, name="LaTeX") copyrecursivelyifnecessary(self.wdir, sandbox.get_home_path(), self.ignore, self.ignore_ext, self.do_copy, mode=0o777) stored_cache_path = os.path.join(config.latex_cache_dir, self.party, config.latex_distro) sandbox_cache_path = os.path.join(sandbox.get_home_path(), config.latex_distro) # Create the LaTeX cache directory if it doesn't exist os.makedirs(stored_cache_path, exist_ok=True) # Copy the LaTeX cache directory into the sandbox copyrecursivelyifnecessary(stored_cache_path, sandbox_cache_path, mode=0o777) sandbox.allow_writing_all() relpath = os.path.relpath(os.getcwd(), self.wdir) sandbox.chdir = os.path.join(sandbox.chdir, relpath) depsfile = os.path.join(sandbox.get_home_path(), relpath, ".deps") # Delete the dependencies file before running latex to ensure # that we don't read a left-over dependencies file in the end # (if latex fails to write the dependencies file). deletefile(depsfile) success = sandbox.execute_without_std(self.command, wait=True) # Don't save the result if the sandbox or LaTeX failed. # (We don't reliably detect when a previously missing file was added, # for example when a missing package was installed.) if not success or sandbox.failed(): self.result.badfail = True self.result.log['code'] = sandbox.failed() self.result.log['out'] = sandbox.get_stdout() self.result.log['err'] = sandbox.get_stderr() self.result.add_dependency(self.source) if self.result.log['code']: self.result.log['err'] += "\n\n" + ("#" * 40) + "\n" + \ "SANDBOX: " + sandbox.get_root_path() + "\n" + \ "MESSAGE: " + sandbox.get_human_exit_description() + "\n" + \ "LOG FILE:\n" + sandbox.get_log_file_contents() else: copyifnecessary( os.path.join(sandbox.get_home_path(), relpath, self.output), os.path.join(self.wdir, relpath, self.output)) self.result.add_output(self.output) #Copy potentially changed latex cache copyrecursivelyifnecessary(sandbox_cache_path, stored_cache_path) # Change to the same working directory that lualatex used (which is # inside the sandbox). with chdir(os.path.join(sandbox.get_home_path(), relpath)): readmakefile(".deps", self.result, True) def convert(path): if path.startswith(os.path.join(config.latex_distro, "")): return os.path.join(config.latex_cache_dir, self.party, path) else: return path self.result.dependencies = \ {convert(path): hash for path, hash in self.result.dependencies.items()} sandbox.cleanup(not config.keep_sandbox)
def make_overview_sheets(self, attach_statements=False): """ Print an overview sheet, containing information about all tasks attach_statements (bool): whether we should collect all primary statements for all users and add them to the resp. PDF right after their overview sheet """ if self.contest.ignore_latex: return teams = {} for u in self.contest.users.values(): team_name = u.team if team_name not in teams: teams[team_name] = [] teams[team_name].append(u) if not os.path.exists("overview"): os.mkdir("overview") with chdir("overview"): self.supply_overview() self.contest._build_supplements_for_key("contestoverview") shutil.copy(os.path.join(os.path.dirname(__file__), "logo.eps"), os.path.join(os.getcwd(), "logo.eps")) lang_code = "" user_list = [] def do_supply_language(): return self.language_supplement(lang_code) def do_supply_credentials(): return self.credentials_supplement(user_list, attach_statements) self.contest.supply("lang", do_supply_language) self.contest.supply("credentials", do_supply_credentials) for team, users in teams.items(): filename = "overview-sheet-" + team.name + ".tex" shutil.copy( os.path.join(os.path.dirname(__file__), "overview-template.tex"), filename) # Provide translation-{de,en}.tex shutil.copy( os.path.join(os.path.dirname(__file__), "translation-de.tex"), "translation-de.tex") shutil.copy( os.path.join(os.path.dirname(__file__), "translation-en.tex"), "translation-en.tex") lang_code = str.lower(team.code) if lang_code == "unaffiliated": lang_code = "en" user_list = users self.contest._build_supplements_for_key("credentials") self.contest._build_supplements_for_key("lang") self.contest.compile(filename)
def make_helper(self): # Unset stack size limit INFTY = int(.75 * virtual_memory().total) resource.setrlimit(resource.RLIMIT_STACK, (INFTY, INFTY)) if not os.path.exists(os.path.join(self.odir, "contest-config.py")): raise Exception("Directory doesn't contain contest-config.py") self.wdir = os.path.join(self.odir, "build") if self.clean: shutil.rmtree(self.wdir) if not os.path.exists(self.wdir): os.mkdir(self.wdir) # We have to avoid copying the folder contest/build # or contest/task/build into contest/build. # For this reason, we ignore all files and directories named "build" # when copying recursively. copyrecursivelyifnecessary(self.odir, self.wdir, set(["build"])) self.wdir = os.path.abspath(self.wdir) with chdir(self.wdir): contestconfig = ContestConfig(os.path.join(self.wdir, ".rules"), os.path.basename(self.odir), safe_latex=self.safe_latex) # Read the configuration file and build. contestconfig._readconfig("contest-config.py") with SessionGen() as session: def session_add(k, v): session.add(v) # Variables like udbs, teamdbs, cdb, ... contain objects before # they've been put into the database. # Their counterpars udb1s, teamdb1s, cdb1, ... contain the # objects that are actually in the database (which are copies # of the objects in udbs, ...). # Create users in the database. udbs = [ contestconfig._makeuser(u) for u in contestconfig.users ] udb1s = _update_list_with_key(session.query(User).all(), udbs, lambda u: u.username, preserve_old=True, update_value_fn=update_user, creator_fn=session_add) udbs = {u.username: u for u in udbs} # Create teams in the database. teamdbs = [ contestconfig._maketeam(t) for t in contestconfig.teams ] teamdb1s = _update_list_with_key(session.query(Team).all(), teamdbs, lambda t: t.code, preserve_old=True, update_value_fn=update_team, creator_fn=session_add) teamdbs = {t.code: t for t in teamdbs} # Create contest (including associated user groups) in the database. cdb = contestconfig._makecontest() cdbs = [cdb] cdb1s = _update_list_with_key(session.query(Contest).all(), cdbs, lambda c: c.name, preserve_old=True, update_value_fn=update_contest, creator_fn=session_add) cdb1 = cdb1s[cdb.name] # Set the contest's main group. cdb1.main_group = cdb1.get_group( contestconfig.defaultgroup.name) # Create participations in the database. # Team object for a given user def user_team(u): t = contestconfig.users[u].team if t is None: return None else: return teamdbs[t.code] # Team object in the database for a given user def user_team1(u): t = contestconfig.users[u].team if t is None: return None else: return teamdb1s[t.code] def make_participation(u): gdb = cdb.get_group(contestconfig.users[u].group.name) return contestconfig._makeparticipation( u, cdb, udbs[u], gdb, user_team(u)) pdbs = [make_participation(u) for u in contestconfig.users] pdb1s = _update_list_with_key(cdb1.participations, pdbs, lambda p : p.user.username, preserve_old= \ self.preserve_participations, update_value_fn=update_participation) pdbs = {p.user.username: p for p in pdbs} for username, u in iteritems(pdb1s): if username in contestconfig.users: u.user = udb1s[username] u.group = cdb1.get_group( contestconfig.users[username].group.name) u.team = user_team1(username) # The test user participation. test_pdb = pdbs[contestconfig._mytestuser.username] test_pdb1 = pdb1s[contestconfig._mytestuser.username] # This is an ugly hack to prevent problems when reordering or # adding tasks. Since we delete after adding and updating, # there might otherwise at one point be two tasks with the same # number. for t in cdb1.tasks: t.num += len(contestconfig.tasks) + len(cdb1.tasks) tdbs = [ t._makedbobject(cdb, self.file_cacher) for t in contestconfig.tasks.values() ] tdbs_dict = {t.name: t for t in tdbs} # We only set the active dataset when importing a new task. # Afterwards, the active dataset has to be set using the web # interface. def task_creator(name, v): tdb = tdbs_dict[name] ddb1 = session.query(Dataset) \ .filter(Dataset.task == v) \ .filter(Dataset.description == tdb.active_dataset.description).first() assert ddb1 is not None v.active_dataset = ddb1 tdb1s = _update_list_with_key(cdb1.tasks, tdbs, lambda t: t.name, preserve_old=False, update_value_fn=update_task, creator_fn=task_creator) tdbs = {t.name: t for t in tdbs} if not self.no_test: ans = input("Replace (perhaps not yet existent) test " "submissions? (This will cause workers to crash if " "there are still test submissions pending!) [y/N] ") \ .strip().lower() if ans not in ["y", "yes"]: self.no_test = True sdb1ss = {} if not self.no_test: logger.warning("Replacing test submissions") for t in contestconfig.tasks: tdb = tdbs[t] tdb1 = tdb1s[t] # Mark old test submissions for deletion. sdb1s = session.query(Submission) \ .filter(Submission.participation == test_pdb1) \ .filter(Submission.task == tdb1) \ .filter(Submission.additional_info != None).all() for sdb1 in sdb1s: assert sdb1.is_unit_test() _to_delete.append(sdb1) # Create test submissions in the database. sdbs = contestconfig.tasks[t]._make_test_submissions( test_pdb, tdb, False) sdb1s = [] for sdb in sdbs: sdb1 = _copy(sdb) sdb1.task = tdb1 sdb1.participation = test_pdb1 session.add(sdb1) sdb1s.append(sdb1) sdb1ss[t] = sdb1s for v in _to_delete: if isinstance(v, Task): logger.warning("Removing task {}".format(v.name)) if any(not s.is_unit_test() for s in v.submissions): logger.warning( "There are submissions for task {}.".format( v.name)) if not self.force: logger.error("Aborting. Run with -f to force " "deletion.") return elif isinstance(v, Participation): logger.warning("Removing participation {}".format( v.user.username)) if any(not s.is_unit_test() for s in v.submissions): logger.warning( "There are submissions for participation {}.". format(v.user.username)) if not self.force: logger.error("Aborting. Run with -f to force " "deletion.") return if self.force: logger.warning("Force flace -f set.") ans = input("Proceed? [y/N] ").strip().lower() if ans not in ["y", "yes"]: logger.error("Aborting.") return # Delete marked objects for v in _to_delete: # Participation objects don't have to be explicitly # deleted, they are a part of cdb / cdb1. # TODO We might want to move the handling of # participations to ContestConfig + importing as # for groups. if not isinstance(v, Participation): session.delete(v) session.commit() if not self.no_test: evaluation_service = self.connect_to( ServiceCoord("EvaluationService", 0)) for t in contestconfig.tasks: sdb1s = sdb1ss[t] # Notify EvaluationService of the new test submissions. for sdb1 in sdb1s: evaluation_service.new_submission( submission_id=sdb1.id) evaluation_service.disconnect() logger.info("Import finished (new contest id: %s).", cdb.id if cdb1 is None else cdb1.id) contestconfig.finish()
def make_helper(self): # Unset stack size limit resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)) if not os.path.exists(os.path.join(self.odir, "contest-config.py")): raise Exception("Directory doesn't contain contest-config.py") self.wdir = os.path.join(self.odir, "build") if self.clean: shutil.rmtree(self.wdir) if not os.path.exists(self.wdir): os.mkdir(self.wdir) # We have to avoid copying the folder contest/build # or contest/task/build into contest/build. # For this reason, we ignore all files and directories named "build" # when copying recursively. copyrecursivelyifnecessary(self.odir, self.wdir, set(["build"])) self.wdir = os.path.abspath(self.wdir) with chdir(self.wdir): contestconfig = ContestConfig( os.path.join(self.wdir, ".rules"), os.path.basename(self.odir)) # Read the configuration file and build. contestconfig._readconfig("contest-config.py") with SessionGen() as session: def session_add(k, v): session.add(v) # Variables like udbs, teamdbs, cdb, ... contain objects before # they've been put into the database. # Their counterpars udb1s, teamdb1s, cdb1, ... contain the # objects that are actually in the database (which are copies # of the objects in udbs, ...). # Create users in the database. udbs = [contestconfig._makeuser(u) for u in contestconfig.users] udb1s = _update_list_with_key(session.query(User).all(), udbs, lambda u : u.username, preserve_old=True, update_value_fn=update_user, creator_fn=session_add) udbs = {u.username : u for u in udbs} # Create teams in the database. teamdbs = [contestconfig._maketeam(t) for t in contestconfig.teams] teamdb1s = _update_list_with_key(session.query(Team).all(), teamdbs, lambda t : t.code, preserve_old=True, update_value_fn=update_team, creator_fn=session_add) teamdbs = {t.code : t for t in teamdbs} # Create contest (including associated user groups) in the database. cdb = contestconfig._makecontest() cdbs = [cdb] cdb1s = _update_list_with_key(session.query(Contest).all(), cdbs, lambda c : c.name, preserve_old=True, update_value_fn=update_contest, creator_fn=session_add) cdb1 = cdb1s[cdb.name] # Set the contest's main group. cdb1.main_group = cdb1.get_group(contestconfig.defaultgroup.name) # Create participations in the database. # Team object for a given user def user_team(u): t = contestconfig.users[u].team if t is None: return None else: return teamdbs[t.code] # Team object in the database for a given user def user_team1(u): t = contestconfig.users[u].team if t is None: return None else: return teamdb1s[t.code] def make_participation(u): gdb = cdb.get_group(contestconfig.users[u].group.name) return contestconfig._makeparticipation(u, cdb, udbs[u], gdb, user_team(u)) pdbs = [make_participation(u) for u in contestconfig.users] pdb1s = _update_list_with_key(cdb1.participations, pdbs, lambda p : p.user.username, preserve_old=True, update_value_fn=update_participation) pdbs = {p.user.username : p for p in pdbs} for username, u in iteritems(pdb1s): u.user = udb1s[username] u.group = cdb1.get_group( contestconfig.users[username].group.name) u.team = user_team1(username) # The test user participation. test_pdb = pdbs[contestconfig._mytestuser.username] test_pdb1 = pdb1s[contestconfig._mytestuser.username] # This is an ugly hack to prevent problems when reordering or # adding tasks. Since we delete after adding and updating, # there might otherwise at one point be two tasks with the same # number. for t in cdb1.tasks: t.num += len(contestconfig.tasks) + len(cdb1.tasks) tdbs = [t._makedbobject(cdb, self.file_cacher) for t in contestconfig.tasks.values()] tdbs_dict = {t.name: t for t in tdbs} # We only set the active dataset when importing a new task. # Afterwards, the active dataset has to be set using the web # interface. def task_creator(name, v): tdb = tdbs_dict[name] ddb1 = session.query(Dataset) \ .filter(Dataset.task == v) \ .filter(Dataset.description == tdb.active_dataset.description).first() assert ddb1 is not None v.active_dataset = ddb1 tdb1s = _update_list_with_key(cdb1.tasks, tdbs, lambda t : t.name, preserve_old=False, update_value_fn=update_task, creator_fn=task_creator) tdbs = {t.name : t for t in tdbs} sdb1ss = {} if not self.no_test: logger.warning("Replacing test submissions") for t in contestconfig.tasks: tdb = tdbs[t] tdb1 = tdb1s[t] # Mark old test submissions for deletion. sdb1s = session.query(Submission) \ .filter(Submission.participation == test_pdb1) \ .filter(Submission.task == tdb1) \ .filter(Submission.additional_info != None).all() for sdb1 in sdb1s: assert sdb1.is_unit_test() _to_delete.append(sdb1) # Create test submissions in the database. sdbs = contestconfig.tasks[t]._make_test_submissions( test_pdb, tdb, False) sdb1s = [] for sdb in sdbs: sdb1 = _copy(sdb) sdb1.task = tdb1 sdb1.participation = test_pdb1 session.add(sdb1) sdb1s.append(sdb1) sdb1ss[t] = sdb1s for v in _to_delete: if isinstance(v, Task): logger.warning("Removing task {}" .format(v.name)) if any(not s.is_unit_test() for s in v.submissions): logger.warning( "There are submissions for task {}." .format(v.name)) if not self.force: logger.error("Aborting. Run with -f to force " "deletion.") return elif isinstance(v, Participation): logger.warning("Removing participation {}" .format(v.user.username)) if any(not s.is_unit_test() for s in v.submissions): logger.warning( "There are submissions for participation {}." .format(v.user.username)) if not self.force: logger.error("Aborting. Run with -f to force " "deletion.") return if self.force: logger.warning("Force flace -f set.") ans = input("Proceed? [y/N] ").strip().lower() if ans not in ["y", "yes"]: logger.error("Aborting.") return # Delete marked objects for v in _to_delete: session.delete(v) session.commit() if not self.no_test: evaluation_service = self.connect_to( ServiceCoord("EvaluationService", 0)) for t in contestconfig.tasks: sdb1s = sdb1ss[t] # Notify EvaluationService of the new test submissions. for sdb1 in sdb1s: evaluation_service.new_submission( submission_id=sdb1.id) evaluation_service.disconnect() logger.info("Import finished (new contest id: %s).", cdb.id if cdb1 is None else cdb1.id)