diff --git a/cmscontrib/DumpExporter.py b/cmscontrib/DumpExporter.py index d6a06790c7..667af71878 100755 --- a/cmscontrib/DumpExporter.py +++ b/cmscontrib/DumpExporter.py @@ -183,9 +183,6 @@ def __init__( ) self.tasks_ids = [task.id for task in tasks] else: - # FIXME: this is ATM broken, because if you export a contest, you - # then export the users who participated in it and then all of the - # contests those users participated in. self.contests_ids = contest_ids self.users_ids = [] self.tasks_ids = [] @@ -376,6 +373,10 @@ class of the given object), an item for each column property elif isinstance(val, other_cls): data[prp.key] = self.get_id(val) elif isinstance(val, list): + # Shared users and teams must not pull in other contests. + if other_cls is Participation: + val = [i for i in val + if i.contest_id in self.contests_ids] data[prp.key] = list(self.get_id(i) for i in val) elif isinstance(val, dict): data[prp.key] = \ diff --git a/cmscontrib/DumpImporter.py b/cmscontrib/DumpImporter.py index 1096ef56b1..8566dd63eb 100755 --- a/cmscontrib/DumpImporter.py +++ b/cmscontrib/DumpImporter.py @@ -64,6 +64,8 @@ FilenameSchemaArray, Digest, SessionGen, + Admin, + Team, Contest, Submission, SubmissionResult, @@ -262,9 +264,49 @@ def do_import(self): assert self.datas["_version"] == model_version + # Only root contests were selected for export; shared users + # and teams can also reference unrelated contests in old dumps. + contest_keys = {key for key in self.datas["_objects"] + if self.datas[key]["_class"] == "Contest"} + import_keys = set() + queue = list(self.datas["_objects"]) + while queue: + key = queue.pop() + if key in import_keys: + continue + data = self.datas[key] + if ((data["_class"] == "Contest" and key not in contest_keys) + or (data.get("contest") is not None + and data["contest"] not in contest_keys)): + continue + import_keys.add(key) + cls = getattr(class_hook, data["_class"]) + for prp in cls._rel_props: + references = data.get(prp.key) + if isinstance(references, str): + queue.append(references) + elif isinstance(references, list): + queue.extend(references) + elif isinstance(references, dict): + queue.extend(references.values()) + self.objs = dict() + reused_keys = set() + shared_classes = {"Admin": (Admin, "username"), + "User": (User, "username"), + "Team": (Team, "code")} for id_, data in self.datas.items(): - if not id_.startswith("_"): + if id_ in import_keys: + if data["_class"] in shared_classes: + cls, field = shared_classes[data["_class"]] + existing = session.query(cls).filter( + getattr(cls, field) == data[field]).one_or_none() + if existing is not None: + self.objs[id_] = existing + reused_keys.add(id_) + logger.info("Reusing existing %s %s.", + data["_class"], data[field]) + continue self.objs[id_] = self.import_object(data) for k, v in list(self.objs.items()): @@ -289,7 +331,8 @@ def do_import(self): del self.objs[k] for id_, data in self.datas.items(): - if not id_.startswith("_") and id_ in self.objs: + # Preserve existing memberships, credentials and permissions. + if id_ in self.objs and id_ not in reused_keys: self.add_relationships(data, self.objs[id_]) contest_id = list()