Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support having 2 tasks with the same name on DB #998

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cms/db/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ class Task(Base):
# Short name and long human readable title of the task.
name = Column(
Codename,
nullable=False,
unique=True)
nullable=False)
title = Column(
Unicode,
nullable=False)
Expand Down
45 changes: 26 additions & 19 deletions cmscontrib/AddStatement.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Contest Management System - http://cms-dev.github.io/
# Copyright © 2016 Myungwoo Chun <[email protected]>
# Copyright © 2016 Stefano Maggiolo <[email protected]>
# Copyright © 2018 William Di Luigi <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
Expand Down Expand Up @@ -35,31 +36,29 @@
import os

from cms import utf8_decoder
from cms.db import SessionGen, Statement, Task
from cms.db import SessionGen, Statement
from cms.db.filecacher import FileCacher
from cmscontrib.importing import ImportDataError, task_from_db


logger = logging.getLogger(__name__)


def add_statement(task_name, language_code, statement_file, overwrite):
def add_statement(task_name, language_code, statement_file,
overwrite, task_id=None):
logger.info("Adding the statement(language: %s) of task %s "
"in the database.", language_code, task_name)

if not os.path.exists(statement_file):
logger.error("Statement file (path: %s) does not exist.",
statement_file)
return False
raise ImportDataError("Statement file (path: %s) does not exist."
% statement_file)

if not statement_file.endswith(".pdf"):
logger.error("Statement file should be a pdf file.")
return False
raise ImportDataError("Statement file should be a pdf file.")

with SessionGen() as session:
task = session.query(Task)\
.filter(Task.name == task_name).first()
if not task:
logger.error("No task named %s", task_name)
return False
task = task_from_db(session, task_name, task_id)

try:
file_cacher = FileCacher()
digest = file_cacher.put_file_from_path(
Expand All @@ -78,15 +77,14 @@ def add_statement(task_name, language_code, statement_file, overwrite):
session.delete(arr[0])
session.commit()
else:
logger.error("A statement with given language already exists. "
"Not overwriting.")
return False
raise ImportDataError("A statement with given language "
"already exists. Not overwriting.")

statement = Statement(language_code, digest, task=task)
session.add(statement)
session.commit()

logger.info("Statement added.")
return True


def main():
Expand All @@ -100,16 +98,25 @@ def main():
help="language code of statement, e.g. en")
parser.add_argument("statement_file", action="store", type=utf8_decoder,
help="absolute/relative path of statement file")
parser.add_argument("-t", "--task-id", action="store", type=int,
help="optional task ID used for disambiguation")
parser.add_argument("-o", "--overwrite", dest="overwrite",
action="store_true",
help="overwrite existing statement")
parser.set_defaults(overwrite=False)

args = parser.parse_args()

success = add_statement(args.task_name, args.language_code,
args.statement_file, args.overwrite)
return 0 if success is True else 1
try:
add_statement(
args.task_name, args.language_code,
args.statement_file, args.overwrite, args.task_id)
except ImportDataError as e:
logger.error(str(e))
logger.info("Error while importing, no changes were made.")
return 1

return 0


if __name__ == "__main__":
Expand Down
5 changes: 3 additions & 2 deletions cmscontrib/AddSubmission.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ def add_submission(contest_id, username, task_name, timestamp, files):
.filter(Task.name == task_name)\
.first()
if task is None:
logging.critical("Unable to find task `%s'.", task_name)
logging.critical("Unable to find task `%s' in the contest.",
task_name)
return False

elements = set(task.submission_format)
Expand Down Expand Up @@ -163,7 +164,7 @@ def main():
parser = argparse.ArgumentParser(
description="Adds a submission to a contest in CMS.")
parser.add_argument("-c", "--contest-id", action="store", type=int,
help="id of contest where to add the user")
help="id of contest where to add the submission")
parser.add_argument("-f", "--file", action="append", type=utf8_decoder,
help="in the form <name>:<file>, where name is the "
"name as required by CMS, and file is the name of "
Expand Down
43 changes: 24 additions & 19 deletions cmscontrib/AddTestcases.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Contest Management System - http://cms-dev.github.io/
# Copyright © 2016 Peyman Jabbarzade Ganje <[email protected]>
# Copyright © 2016 Stefano Maggiolo <[email protected]>
# Copyright © 2018 William Di Luigi <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
Expand Down Expand Up @@ -36,40 +37,36 @@
import re

from cms import utf8_decoder
from cms.db import Contest, Dataset, SessionGen, Task
from cms.db import Contest, Dataset, SessionGen
from cms.db.filecacher import FileCacher
from cmscommon.importers import import_testcases_from_zipfile
from cmscontrib.importing import ImportDataError, task_from_db


logger = logging.getLogger(__name__)


def add_testcases(archive, input_template, output_template,
task_name, dataset_description=None, contest_name=None,
public=False, overwrite=False):
def add_testcases(archive, input_template, output_template, task_name,
dataset_description=None, contest_name=None, public=False,
overwrite=False, task_id=None):
with SessionGen() as session:
task = session.query(Task)\
.filter(Task.name == task_name).first()
if not task:
logger.error("No task called %s found." % task_name)
return False
task = task_from_db(session, task_name, task_id)

dataset = task.active_dataset
if dataset_description is not None:
dataset = session.query(Dataset)\
.filter(Dataset.task_id == task.id)\
.filter(Dataset.description == dataset_description)\
.first()
if not dataset:
logger.error("No dataset called %s found."
% dataset_description)
return False
raise ImportDataError("No dataset called `%s' found."
% dataset_description)
if contest_name is not None:
contest = session.query(Contest)\
.filter(Contest.name == contest_name).first()
if task.contest != contest:
logger.error("%s is not in %s" %
(task_name, contest_name))
return False
raise ImportDataError("%s is not in %s" %
(task_name, contest_name))

file_cacher = FileCacher()

Expand Down Expand Up @@ -110,14 +107,22 @@ def main():
help="if testcases can overwrite existing testcases")
parser.add_argument("-c", "--contest_name", action="store",
help="contest which testcases will be attached to")
parser.add_argument("-t", "--task-id", action="store", type=int,
help="optional task ID used for disambiguation")
parser.add_argument("-d", "--dataset_description", action="store",
help="dataset testcases will be attached to")
args = parser.parse_args()

success = add_testcases(
args.file, args.inputtemplate, args.outputtemplate,
args.task_name, args.dataset_description, args.contest_name,
args.public, args.overwrite)
try:
success = add_testcases(
args.file, args.inputtemplate, args.outputtemplate,
args.task_name, args.dataset_description, args.contest_name,
args.public, args.overwrite, args.task_id)
except ImportDataError as e:
logger.error(str(e))
logger.info("Error while importing, no changes were made.")
return 1

return 0 if success is True else 1


Expand Down
108 changes: 68 additions & 40 deletions cmscontrib/ImportContest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Copyright © 2010-2018 Stefano Maggiolo <[email protected]>
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2013 Luca Wehrstedt <[email protected]>
# Copyright © 2014-2015 William Di Luigi <[email protected]>
# Copyright © 2014-2018 William Di Luigi <[email protected]>
# Copyright © 2015-2016 Luca Chiodini <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -190,60 +190,88 @@ def _task_to_db(self, session, contest, tasknum, taskname):
return (Task): the task in the DB.

raise (ImportDataError): in case of one of these errors:
- if the task is not in the DB and user did not ask to import it;
- if the loader cannot load the task;
- if the task is already in the DB, attached to another contest.
- if the user did not ask to import the task AND one of:
- the task is not in the DB and;
- the task is in the DB but it's attached to another contest;
- the task exists in more than one version (we would need the
ID of every single "problematic" task if we wanted to decide
which ones to attach, so for now this is not supported).

"""
task_loader = self.loader.get_task_loader(taskname)
task = session.query(Task).filter(Task.name == taskname).first()

if task is None:
# Task is not in the DB; if the user asked us to import it, we do
# so, otherwise we return an error.

if not self.import_tasks:
raise ImportDataError(
"Task \"%s\" not found in database. "
"Use --import-task to import it." % taskname)
tasks = session.query(Task).filter(Task.name == taskname).all()

if self.import_tasks:
task = task_loader.get_task(get_statement=not self.no_statements)
if task is None:
raise ImportDataError(
"Could not import task \"%s\"." % taskname)

session.add(task)

elif not task_loader.task_has_changed():
# Task is in the DB and has not changed, nothing to do.
logger.info("Task \"%s\" data has not changed.", taskname)

elif self.update_tasks:
# Task is in the DB, but has changed, and the user asked us to
# update it. We do so.
new_task = task_loader.get_task(
get_statement=not self.no_statements)
if new_task is None:
else:
if len(tasks) == 0:
raise ImportDataError(
"Could not reimport task \"%s\"." % taskname)
logger.info("Task \"%s\" data has changed, updating it.", taskname)
update_task(task, new_task, get_statements=not self.no_statements)
"Task \"%s\" not found in database. Consider using "
"--import-task to import it." % taskname)
if len(tasks) > 1:
raise ImportDataError(
"Multiple tasks with name \"%s\". You should either "
"delete the ambiguous tasks, or consider using the "
"--import-tasks argument with this script." % taskname)

else:
# Task is in the DB, has changed, and the user didn't ask to update
# it; we just show a warning.
logger.warning("Not updating task \"%s\", even if it has changed. "
"Use --update-tasks to update it.", taskname)

# Finally we tie the task to the contest, if it is not already used
# elsewhere.
if task.contest is not None and task.contest.name != contest.name:
raise ImportDataError(
"Task \"%s\" is already tied to contest \"%s\"."
% (taskname, task.contest.name))
# Only one task is found, proceed
task = tasks[0]

if task.contest is not None:
raise ImportDataError(
"The task with name \"%s\" is already attached to a "
"different contest. Deattach it, or consider using the "
"--import-tasks argument with this script." % taskname)

# The task exists, is unique, and is not attached.

# We will assume it's the same task that the user wants, though
# it could also be a previously created (and completely different)
# task that just happens to have the same name. So, let's warn
# the user.
logger.warning(
"A task with name \"%s\" was found and it's not attached to a "
"contest yet. It is recommended to double-check if this is "
"REALLY the task intended to be imported in the contest, and "
"not some previously-imported task that happens to have the "
"same name as this new one." % taskname)

# Proceed using that task.

if task_loader.task_has_changed():
if self.update_tasks:
# Task is in the DB, has changed on disk, and the user
# asked us to update it. We do so.
new_task = task_loader.get_task(
get_statement=not self.no_statements)
if new_task is None:
raise ImportDataError(
"Could not reimport task \"%s\"." % taskname)
logger.info("Task \"%s\" data has changed, updating it.",
taskname)
update_task(task, new_task,
get_statements=not self.no_statements)
else:
# Task is in the DB, has changed on disk, and the user
# didn't ask to update it; We just warn the user.
logger.warning(
"Not updating task \"%s\", even if it has changed on "
"disk. Consider using --update-tasks to update it, or "
"--import-task to import it anew.", taskname)
else:
# Task is in the DB and has not changed, nothing to do.
logger.info("Task \"%s\" data has not changed.", taskname)

# Finally we tie the task to the contest.
task.num = tasknum
task.contest = contest

return task

@staticmethod
Expand Down Expand Up @@ -374,7 +402,7 @@ def main():
parser.add_argument(
"-i", "--import-tasks",
action="store_true",
help="import tasks if they do not exist"
help="import tasks anew, ignoring what exists in the database"
)
parser.add_argument(
"-u", "--update-contest",
Expand Down
Loading