def update(cls, company: str, project_id: str, **fields): with TimingContext("mongo", "projects_update"): project = Project.get_for_writing(company=company, id=project_id) if not project: raise errors.bad_request.InvalidProjectId(id=project_id) new_name = fields.pop("name", None) if new_name: new_name, new_location = _validate_project_name(new_name) old_name, old_location = _validate_project_name(project.name) if new_location != old_location: raise errors.bad_request.CannotUpdateProjectLocation( name=new_name) fields["name"] = new_name fields["last_update"] = datetime.utcnow() updated = project.update(upsert=False, **fields) if new_name: old_name = project.name project.name = new_name children = _get_sub_projects([project.id], _only=("id", "name", "path"))[project.id] _update_subproject_names(project=project, children=children, old_name=old_name) return updated
def validate( cls, task: Task, validate_models=True, validate_parent=True, validate_project=True, ): """ Validate task properties according to the flag Task project is always checked for being writable in order to disable the modification of public projects """ if (validate_parent and task.parent and not task.parent.startswith(deleted_prefix) and not Task.get(company=task.company, id=task.parent, _only=("id", ), include_public=True)): raise errors.bad_request.InvalidTaskId("invalid parent", parent=task.parent) if task.project: project = Project.get_for_writing(company=task.company, id=task.project) if validate_project and not project: raise errors.bad_request.InvalidProjectId(id=task.project) if validate_models: cls.validate_input_models(task)
def delete_project( company: str, project_id: str, force: bool, delete_contents: bool ) -> Tuple[DeleteProjectResult, Set[str]]: project = Project.get_for_writing( company=company, id=project_id, _only=("id", "path") ) if not project: raise errors.bad_request.InvalidProjectId(id=project_id) project_ids = _ids_with_children([project_id]) if not force: for cls, error in ( (Task, errors.bad_request.ProjectHasTasks), (Model, errors.bad_request.ProjectHasModels), ): non_archived = cls.objects( project__in=project_ids, system_tags__nin=[EntityVisibility.archived.value], ).only("id") if non_archived: raise error("use force=true to delete", id=project_id) if not delete_contents: with TimingContext("mongo", "update_children"): for cls in (Model, Task): updated_count = cls.objects(project__in=project_ids).update( project=None ) res = DeleteProjectResult(disassociated_tasks=updated_count) else: deleted_models, model_urls = _delete_models(projects=project_ids) deleted_tasks, event_urls, artifact_urls = _delete_tasks( company=company, projects=project_ids ) res = DeleteProjectResult( deleted_tasks=deleted_tasks, deleted_models=deleted_models, urls=TaskUrls( model_urls=list(model_urls), event_urls=list(event_urls), artifact_urls=list(artifact_urls), ), ) affected = {*project_ids, *(project.path or [])} res.deleted = Project.objects(id__in=project_ids).delete() return res, affected
def move_project(cls, company: str, user: str, project_id: str, new_location: str) -> Tuple[int, Set[str]]: """ Move project with its sub projects from its current location to the target one. If the target location does not exist then it will be created. If it exists then it should be writable. The source location should be writable too. Return the number of moved projects + set of all the affected project ids """ with TimingContext("mongo", "move_project"): project = Project.get(company, project_id) old_parent_id = project.parent old_parent = (Project.get_for_writing(company=project.company, id=old_parent_id) if old_parent_id else None) children = _get_sub_projects([project.id], _only=("id", "name", "path"))[project.id] cls.validate_projects_depth( projects=[project, *children], old_parent_depth=len(project.path), new_parent_depth=_get_project_depth(new_location), ) new_parent = _ensure_project(company=company, user=user, name=new_location) new_parent_id = new_parent.id if new_parent else None if old_parent_id == new_parent_id: raise errors.bad_request.ProjectSourceAndDestinationAreTheSame( location=new_parent.name if new_parent else "") if (new_parent and project_id == new_parent.id or project_id in new_parent.path): raise errors.bad_request.ProjectCannotBeMovedUnderItself( project=project_id, parent=new_parent.id) moved = _reposition_project_with_children(project, children=children, parent=new_parent) now = datetime.utcnow() affected = set() for p in filter(None, (old_parent, new_parent)): p.update(last_update=now) affected.update({p.id, *(p.path or [])}) return moved, affected
def validate_project_delete(company: str, project_id: str): project = Project.get_for_writing( company=company, id=project_id, _only=("id", "path") ) if not project: raise errors.bad_request.InvalidProjectId(id=project_id) project_ids = _ids_with_children([project_id]) ret = {} for cls in (Task, Model): ret[f"{cls.__name__.lower()}s"] = cls.objects( project__in=project_ids, ).count() for cls in (Task, Model): ret[f"non_archived_{cls.__name__.lower()}s"] = cls.objects( project__in=project_ids, system_tags__nin=[EntityVisibility.archived.value], ).count() return ret