Ejemplo n.º 1
0
    def run(self, current_user_id: int) -> ImportPrepRsp:
        # Security check
        RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE,
                            self.prj_id)
        # OK
        loaded_files = none_to_empty(self.prj.fileloaded).splitlines()
        logger.info("Previously loaded files: %s", loaded_files)
        self.manage_uploaded()
        # Prepare response
        ret = ImportPrepRsp(source_path=self.source_dir_or_zip)
        self.update_progress(0, "Starting")
        # Unzip or point to source directory
        self.unzip_if_needed()
        ret.source_path = self.source_dir_or_zip
        # Validate files
        logger.info("Analyze TSV Files")
        how, diag, nb_rows = self.do_intra_step_1(loaded_files)
        ret.mappings = how.custom_mapping.as_dict()
        ret.warnings = diag.messages
        ret.errors = diag.errors
        ret.rowcount = nb_rows
        # Resolve users...
        logger.info("Resolve users")
        self.resolve_users(self.session, how.found_users)
        ret.found_users = how.found_users
        # ...and taxonomy
        logger.info("Resolve taxonomy")
        self.resolve_taxa(self.session, how.taxo_found)
        ret.found_taxa = how.taxo_found

        return ret
Ejemplo n.º 2
0
    def do_run(self, current_user_id: int) -> SubsetRsp:
        # Security checks
        RightsBO.user_wants(self.session, current_user_id, Action.READ, self.prj_id)
        RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE, self.dest_prj.projid)
        # OK
        logger.info("Starting subset of '%s'", self.prj.title)
        ret = SubsetRsp()

        self.update_progress(5, "Determining objects to clone")
        self._find_what_to_clone()

        logger.info("Matched %s objects", len(self.to_clone))
        if len(self.to_clone) == 0:
            self.task.taskstate = "Error"
            self.update_progress(10, "No object to include in the subset project")
            ret.errors.append("No object found to clone into subset.")
            return ret

        self._do_clone()
        self.session.commit()

        # Recompute stats and so on
        ProjectBO.do_after_load(self.session, self.dest_prj.projid)
        self.session.commit()
        return ret
Ejemplo n.º 3
0
    def do_run(self, current_user_id: int) -> MergeRsp:
        """
            Run the service, merge the projects.
        :return:
        """
        # Security check
        RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE,
                            self.prj_id)
        RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE,
                            self.src_prj_id)
        # OK
        prj = self.session.query(Project).get(self.prj_id)
        assert prj is not None
        src_prj = self.session.query(Project).get(self.src_prj_id)
        assert src_prj is not None

        logger.info("Validating Merge of '%s'", prj.title)
        ret = MergeRsp()
        errs = self._verify_possible(prj, src_prj)
        ret.errors = errs
        # Exit if errors or dry run
        if self.dry_run or len(errs) > 0:
            return ret

        logger.info("Remaps: %s", self.remap_operations)
        # Go for real if not dry run AND len(errs) == 0
        logger.info("Starting Merge of '%s'", prj.title)
        self._do_merge(prj)
        self.session.commit()

        # Recompute stats and so on
        ProjectBO.do_after_load(self.session, prj_id=self.prj_id)
        self.session.commit()
        return ret
Ejemplo n.º 4
0
    def delete(self, current_user_id: UserIDT, object_ids: ObjectIDListT) -> Tuple[int, int, int, int]:
        """
            Remove from DB all the objects with ID in given list.
        """
        # Security check
        obj_set = EnumeratedObjectSet(self.session, object_ids)
        # Get project IDs for the objects and verify rights
        prj_ids = obj_set.get_projects_ids()
        for a_prj_id in prj_ids:
            RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE, a_prj_id)

        # Prepare & start a remover thread that will run in // with DB queries
        remover = VaultRemover(self.link_src, logger).do_start()
        # Do the deletion itself.
        nb_objs, nb_img_rows, img_files = obj_set.delete(self.CHUNK_SIZE, remover.add_files)

        # Update stats on impacted project(s)
        for prj_id in prj_ids:
            ProjectBO.update_taxo_stats(self.session, prj_id)
            # Stats depend on taxo stats
            ProjectBO.update_stats(self.session, prj_id)

        self.session.commit()
        # Wait for the files handled
        remover.wait_for_done()
        return nb_objs, 0, nb_img_rows, len(img_files)
Ejemplo n.º 5
0
    def do_run(self, current_user_id: int) -> ImportRealRsp:
        """
            Do the real job using injected parameters.
            :return:
        """
        # Security check
        RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE,
                            self.prj_id)
        # OK
        loaded_files = none_to_empty(self.prj.fileloaded).splitlines()
        logger.info("Previously loaded files: %s", loaded_files)

        # Save mappings straight away
        self.save_mapping(self.custom_mapping)

        source_bundle = InBundle(
            self.req.source_path,
            Path(self.temp_for_task.data_dir_for(self.task_id)))
        # Configure the import to come, destination
        db_writer = DBWriter(self.session)
        import_where = ImportWhere(
            db_writer, self.vault,
            self.temp_for_task.base_dir_for(self.task_id))
        # Configure the import to come, directives
        import_how = ImportHow(self.prj_id, self.req.update_mode,
                               self.custom_mapping,
                               self.req.skip_existing_objects, loaded_files)
        import_how.taxo_mapping = self.req.taxo_mappings
        import_how.taxo_found = self.req.found_taxa
        import_how.found_users = self.req.found_users
        if self.req.skip_loaded_files:
            import_how.compute_skipped(source_bundle, logger)
        if not self.req.skip_existing_objects:
            with CodeTimer("run: Existing images for %d: " % self.prj_id,
                           logger):
                import_how.objects_and_images_to_skip = Image.fetch_existing_images(
                    self.session, self.prj_id)
        import_how.do_thumbnail_above(int(self.config['THUMBSIZELIMIT']))

        # Do the bulk job of import
        row_count = source_bundle.do_import(import_where, import_how,
                                            self.req.rowcount,
                                            self.report_progress)

        # Update loaded files in DB, removing duplicates
        self.prj.fileloaded = "\n".join(set(import_how.loaded_files))
        self.session.commit()

        # Recompute stats
        ProjectBO.do_after_load(self.session, self.prj_id)
        self.session.commit()

        logger.info("Total of %d rows loaded" % row_count)

        # Prepare response
        ret = ImportRealRsp()
        return ret
Ejemplo n.º 6
0
 def do_run(self, current_user_id: int) -> List[str]:
     # Security check
     RightsBO.user_wants(self.session, current_user_id, Action.READ,
                         self.prj_id)
     # OK
     ret = []
     # TODO: Permissions
     ret.extend(self.check_paths_unicity())
     return ret
Ejemplo n.º 7
0
 def run(self, current_user_id: int) -> ImportRsp:
     """
         Initial run, basically just create the job.
     """
     # Security check
     RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE,
                         self.prj_id)
     # OK, go background straight away
     self.create_job(self.JOB_TYPE, current_user_id)
     ret = ImportRsp(job_id=self.job_id)
     return ret
Ejemplo n.º 8
0
 def run(self, current_user_id: int) -> Optional[SimpleImportRsp]:
     # Security check
     RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE,
                         self.prj_id)
     # Validate values in all cases, dry run or not.
     ret = self._validate()
     if len(ret.errors) > 0:
         return ret
     if not self.dry_run:
         self.create_job(self.JOB_TYPE, current_user_id)
         ret.job_id = self.job_id
     return ret
Ejemplo n.º 9
0
 def do_run(self, current_user_id: int) -> SimpleImportRsp:
     # Security check
     RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE,
                         self.prj_id)
     # OK
     # Validate values in all cases
     ret = self._validate()
     if len(ret.errors) > 0:
         return ret
     if self.task_id != 0:
         if len(ret.errors) == 0:
             ret = self.do_import()
     return ret
Ejemplo n.º 10
0
 def do_run(self, current_user_id: int) -> List[str]:
     # Security check
     _user, project = RightsBO.user_wants(self.session, current_user_id,
                                          Action.READ, self.prj_id)
     # OK
     proj_bo = ProjectBO(project).enrich()
     ret = []
     # TODO: Permissions
     ret.append(proj_bo.title)
     ret.append(str(proj_bo.obj_free_cols))
     free_cols_vals = proj_bo.get_all_num_columns_values(self.session)
     acquis_stats: AcquisitionStats = AcquisitionStats("", 0)
     for a_row in free_cols_vals:
         acquis_id, acquis_orig_id, objid, *free_vals = a_row
         free_vals = [
             a_val if a_val is not None else Decimal('nan')
             for a_val in free_vals
         ]
         if acquis_id == acquis_stats.acquis_id:
             # Same acquisition
             pass
         else:
             # New acquisition, close previous one
             self.output_acq(acquis_stats, ret)
             # And start new one
             acquis_stats = AcquisitionStats(acquis_orig_id, acquis_id)
         acquis_stats.add_values(free_vals)
     self.output_acq(acquis_stats, ret)
     return ret
Ejemplo n.º 11
0
    def reset_to_predicted(self, current_user_id: UserIDT, proj_id: ProjectIDT, filters: ProjectFilters) -> None:
        """
            Query the given project with given filters, reset the resulting objects to predicted.
        """
        # Security check
        RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE, proj_id)

        impacted_objs = [r[0] for r in self.query(current_user_id, proj_id, filters)[0]]

        EnumeratedObjectSet(self.session, impacted_objs).reset_to_predicted()

        # Update stats
        ProjectBO.update_taxo_stats(self.session, proj_id)
        # Stats depend on taxo stats
        ProjectBO.update_stats(self.session, proj_id)
        self.session.commit()
Ejemplo n.º 12
0
 def recompute_geo(self, current_user_id: int, prj_id: ProjectIDT) -> None:
     # Security barrier
     _current_user, _project = RightsBO.user_wants(self.session,
                                                   current_user_id,
                                                   Action.ADMINISTRATE,
                                                   prj_id)
     Sample.propagate_geo(self.session, prj_id)
Ejemplo n.º 13
0
    def delete(self, current_user_id: int, prj_id: int,
               only_objects: bool) -> Tuple[int, int, int, int]:
        # Security barrier
        _current_user, _project = RightsBO.user_wants(self.session,
                                                      current_user_id,
                                                      Action.ADMINISTRATE,
                                                      prj_id)
        # Troll-ish way of erasing
        all_object_ids = ProjectBO.get_all_object_ids(self.session,
                                                      prj_id=prj_id)
        # Build a big set
        obj_set = EnumeratedObjectSet(self.session, all_object_ids)

        # Prepare a remover thread that will run in // with DB queries
        remover = VaultRemover(self.link_src, logger).do_start()
        # Do the deletion itself.
        nb_objs, nb_img_rows, img_files = obj_set.delete(
            self.DELETE_CHUNK_SIZE, remover.add_files)

        ProjectBO.delete_object_parents(self.session, prj_id)

        if only_objects:
            # Update stats, should all be 0...
            ProjectBO.update_taxo_stats(self.session, prj_id)
            # Stats depend on taxo stats
            ProjectBO.update_stats(self.session, prj_id)
        else:
            ProjectBO.delete(self.session, prj_id)

        self.session.commit()
        # Wait for the files handled
        remover.wait_for_done()
        return nb_objs, 0, nb_img_rows, len(img_files)
Ejemplo n.º 14
0
 def read_user_stats(self, current_user_id: int,
                     prj_ids: ProjectIDListT) -> List[ProjectUserStats]:
     """
         Read user statistics for these projects.
     """
     # Security barrier
     [RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE, prj_id)
      for prj_id in prj_ids]
     ret = ProjectBO.read_user_stats(self.session, prj_ids)
     return ret
Ejemplo n.º 15
0
 def update_set(self, current_user_id: int, acquisition_ids: AcquisitionIDListT, updates: ColUpdateList):
     # Get project IDs for the acquisitions and verify rights
     acquisition_set = EnumeratedAcquisitionSet(self.session, acquisition_ids)
     prj_ids = acquisition_set.get_projects_ids()
     # All should be in same project, so far
     assert len(prj_ids) == 1, "Too many or no projects for acquisitions: %s" % acquisition_ids
     prj_id = prj_ids[0]
     _user, project = RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE, prj_id)
     assert project  # for mypy
     return acquisition_set.apply_on_all(project, updates)
Ejemplo n.º 16
0
 def update_set(self, current_user_id: UserIDT, sample_ids: SampleIDListT, updates: ColUpdateList):
     # Get project IDs for the samples and verify rights
     sample_set = EnumeratedSampleSet(self.session, sample_ids)
     prj_ids = sample_set.get_projects_ids()
     # All should be in same project, so far
     assert len(prj_ids) == 1, "Too many or no projects for samples: %s" % sample_ids
     prj_id = prj_ids[0]
     _user, project = RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE, prj_id)
     assert project  # for mypy
     return sample_set.apply_on_all(project, updates)
Ejemplo n.º 17
0
 def search(self, current_user_id: Optional[UserIDT], project_id: ProjectIDT) -> List[AcquisitionBO]:
     # Security check
     if current_user_id is None:
         project = RightsBO.anonymous_wants(self.ro_session, Action.READ, project_id)
     else:
         _user, project = RightsBO.user_wants(self.session, current_user_id, Action.READ, project_id)
     acquisition_set = DescribedAcquisitionSet(self.ro_session, project_id)
     # mappings = ProjectMapping().load_from_project(project)
     # ret.map_free_columns(mappings.sample_mappings)
     return acquisition_set.list()
Ejemplo n.º 18
0
 def run(self, current_user_id: int) -> ExportRsp:
     """
         Initial run, basically just do security check and create the job.
     """
     _user, _project = RightsBO.user_wants(self.session, current_user_id,
                                           Action.READ, self.req.project_id)
     # OK, go background straight away
     self.create_job(self.JOB_TYPE, current_user_id)
     ret = ExportRsp(job_id=self.job_id)
     return ret
Ejemplo n.º 19
0
 def query(self, current_user_id: Optional[int], acquisition_id: AcquisitionIDT) -> Optional[AcquisitionBO]:
     ret = AcquisitionBO(self.ro_session, acquisition_id)
     if not ret.exists():
         return None
     assert ret.acquis is not None
     # Security check
     if current_user_id is None:
         project = RightsBO.anonymous_wants(self.ro_session, Action.READ, ret.acquis.sample.projid)
     else:
         _user, project = RightsBO.user_wants(self.session, current_user_id, Action.READ, ret.acquis.sample.projid)
     mappings = ProjectMapping().load_from_project(project)
     ret.map_free_columns(mappings.acquisition_mappings)
     return ret
Ejemplo n.º 20
0
 def read_taxo_stats(self, current_user_id: Optional[UserIDT],
                     sample_ids: SampleIDListT) -> List[SampleTaxoStats]:
     # Get project IDs for the samples and verify rights
     sample_set = EnumeratedSampleSet(self.ro_session, sample_ids)
     project_ids = sample_set.get_projects_ids()
     # Security check
     if current_user_id is None:
         [RightsBO.anonymous_wants(self.ro_session, Action.READ, project_id)
          for project_id in project_ids]
     else:
         [RightsBO.user_wants(self.session, current_user_id, Action.READ, project_id)
          for project_id in project_ids]
     return sample_set.read_taxo_stats()
Ejemplo n.º 21
0
 def search(self, current_user_id: Optional[UserIDT],
            project_ids: ProjectIDListT,
            orig_id_pattern: str) -> List[SampleBO]:
     # Security check
     if current_user_id is None:
         [RightsBO.anonymous_wants(self.ro_session, Action.READ, project_id)
          for project_id in project_ids]
     else:
         [RightsBO.user_wants(self.session, current_user_id, Action.READ, project_id)
          for project_id in project_ids]
     sample_set = DescribedSampleSet(self.ro_session, project_ids, orig_id_pattern)
     # mappings = ProjectMapping().load_from_project(project)
     # ret.map_free_columns(mappings.sample_mappings)
     return sample_set.list()
Ejemplo n.º 22
0
 def query(self, current_user_id: Optional[UserIDT], sample_id: SampleIDT) -> Optional[SampleBO]:
     ret = SampleBO(self.ro_session, sample_id)
     if not ret.exists():
         return None
     assert ret.sample is not None
     assert ret.sample.projid is not None  # TODO: Why need this?
     # Security check
     if current_user_id is None:
         project = RightsBO.anonymous_wants(self.ro_session, Action.READ, ret.sample.projid)
     else:
         _user, project = RightsBO.user_wants(self.session, current_user_id, Action.READ, ret.sample.projid)
     mappings = ProjectMapping().load_from_project(project)
     ret.map_free_columns(mappings.sample_mappings)
     return ret
Ejemplo n.º 23
0
 def query(self, current_user_id: Optional[int], object_id: ObjectIDT) -> Optional[ObjectBO]:
     ret = ObjectBO(self.ro_session, object_id)
     if not ret.exists():
         return None
     # Security check
     projid = ret.header.acquisition.sample.projid
     if current_user_id is None:
         project = RightsBO.anonymous_wants(self.session, Action.READ, projid)
     else:
         _user, project = RightsBO.user_wants(self.session, current_user_id, Action.READ, projid)
     assert project is not None
     mappings = ProjectMapping().load_from_project(project)
     ret.map_free_columns(mappings.object_mappings)
     return ret
Ejemplo n.º 24
0
 def query(self, current_user_id: Optional[UserIDT], prj_id: int,
           for_managing: bool) -> ProjectBO:
     if current_user_id is None:
         RightsBO.anonymous_wants(self.session, Action.READ, prj_id)
         highest_right = ""
     else:
         current_user, project = RightsBO.user_wants(
             self.session, current_user_id,
             Action.ADMINISTRATE if for_managing else Action.READ, prj_id)
         highest_right = RightsBO.highest_right_on(current_user, prj_id)
     ret = ProjectBOSet.get_one(self.session, prj_id)
     assert ret is not None
     ret.highest_right = highest_right
     return ret
Ejemplo n.º 25
0
    def parents_by_id(self, current_user_id: UserIDT, object_ids: ObjectIDListT) -> ObjectIDWithParentsListT:
        """
            Query the given IDs, return parents.
        """
        # Security check
        obj_set = EnumeratedObjectSet(self.session, object_ids)
        # Get project IDs for the objects and verify rights
        prj_ids = obj_set.get_projects_ids()
        for a_prj_id in prj_ids:
            RightsBO.user_wants(self.session, current_user_id, Action.READ, a_prj_id)

        sql = """
    SELECT obh.objid, acq.acquisid, sam.sampleid, sam.projid
      FROM obj_head obh
      JOIN acquisitions acq on acq.acquisid = obh.acquisid 
      JOIN samples sam on sam.sampleid = acq.acq_sample_id 
     WHERE obh.objid = any (:ids) """
        params = {"ids": object_ids}

        res: ResultProxy = self.session.execute(sql, params)
        ids = [(objid, acquisid, sampleid, projid)
               for objid, acquisid, sampleid, projid in res]
        return ids  # type:ignore
Ejemplo n.º 26
0
 def _the_project_for(self, current_user_id: UserIDT, target_ids: ObjectIDListT, action: Action) \
         -> Tuple[EnumeratedObjectSet, Project]:
     """
         Check _the_ single project for an object set, with the given right.
     """
     # Get project IDs for the objects and verify rights
     object_set = EnumeratedObjectSet(self.session, target_ids)
     prj_ids = object_set.get_projects_ids()
     # All should be in same project, so far
     assert len(prj_ids) == 1, "Too many or no projects for objects: %s" % target_ids
     prj_id = prj_ids[0]
     _user, project = RightsBO.user_wants(self.session, current_user_id, action, prj_id)
     assert project  # for mypy
     return object_set, project
Ejemplo n.º 27
0
    def revert_to_history(self, current_user_id: UserIDT, proj_id: ProjectIDT,
                          filters: ProjectFilters, dry_run: bool,
                          target: Optional[int]) -> Tuple[List[HistoricalLastClassif], ClassifSetInfoT]:
        """
            Revert to classification history the given set, if dry_run then only simulate.
        """
        # Security check
        RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE, proj_id)

        # Get target objects
        impacted_objs = [r[0] for r in self.query(current_user_id, proj_id, filters)[0]]
        obj_set = EnumeratedObjectSet(self.session, impacted_objs)

        # We don't revert to a previous version in history from same annotator
        but_not_by: Optional[int] = None
        but_not_by_str = filters.get('filt_last_annot', None)
        if but_not_by_str is not None:
            try:
                but_not_by = int(but_not_by_str)
            except ValueError:
                pass
        if dry_run:
            # Return information on what to do
            impact = obj_set.evaluate_revert_to_history(target, but_not_by)
            # And names for display
            classifs = TaxonomyBO.names_with_parent_for(self.session, self.collect_classif(impact))
        else:
            # Do the real thing
            impact = obj_set.revert_to_history(target, but_not_by)
            classifs = {}
            # Update stats
            ProjectBO.update_taxo_stats(self.session, proj_id)
            # Stats depend on taxo stats
            ProjectBO.update_stats(self.session, proj_id)
            self.session.commit()
        # Give feedback
        return impact, classifs
Ejemplo n.º 28
0
 def query_history(self, current_user_id: Optional[int], object_id: ObjectIDT) \
         -> List[HistoricalClassification]:
     the_obj = ObjectBO(self.ro_session, object_id)
     if not the_obj.exists():
         return []
     # Security check
     # TODO: dup code
     projid = the_obj.header.acquisition.sample.projid
     if current_user_id is None:
         RightsBO.anonymous_wants(self.ro_session, Action.READ, projid)
     else:
         _user, project = RightsBO.user_wants(self.session, current_user_id, Action.READ, projid)
         assert project is not None
     ret = the_obj.get_history()
     return ret
Ejemplo n.º 29
0
    def summary(self, current_user_id: Optional[UserIDT], proj_id: ProjectIDT, filters: ProjectFilters,
                only_total: bool) -> Tuple[int, Optional[int], Optional[int], Optional[int]]:
        """
            Query the given project with given filters, return classification summary, or just grand total if
            only_total is set.
        """
        # Security check
        if current_user_id is None:
            RightsBO.anonymous_wants(self.session, Action.READ, proj_id)
            # Anonymous can only see validated objects
            # TODO: Dup code
            # noinspection PyTypeHints
            filters.statusfilter = "V"  # type:ignore
            user_id = -1
        else:
            user, _project = RightsBO.user_wants(self.session, current_user_id, Action.READ, proj_id)
            user_id = user.id

        # Prepare a where clause and parameters from filter
        object_set: DescribedObjectSet = DescribedObjectSet(self.session, proj_id, filters)
        from_, where, params = object_set.get_sql(user_id)
        sql = """
    SET LOCAL enable_seqscan=FALSE;
    SELECT COUNT(*) nbr"""
        if only_total:
            sql += """, NULL nbr_v, NULL nbr_d, NULL nbr_p"""
        else:
            sql += """, 
           COUNT(CASE WHEN obh.classif_qual = 'V' THEN 1 END) nbr_v,
           COUNT(CASE WHEN obh.classif_qual = 'D' THEN 1 END) nbr_d, 
           COUNT(CASE WHEN obh.classif_qual = 'P' THEN 1 END) nbr_p"""
        sql += """
      FROM """ + from_.get_sql() + " " + where.get_sql()

        with CodeTimer("summary: V/D/P for %d using %s " % (proj_id, sql), logger):
            res: ResultProxy = self.session.execute(sql, params)

        nbr: int
        nbr_v: Optional[int]
        nbr_d: Optional[int]
        nbr_p: Optional[int]
        nbr, nbr_v, nbr_d, nbr_p = res.first()  # type:ignore
        return nbr, nbr_v, nbr_d, nbr_p
Ejemplo n.º 30
0
 def create(self, current_user_id: int,
            req: CreateProjectReq) -> Union[int, str]:
     """
         Create a project, eventually as a shallow copy of another.
     """
     if req.clone_of_id:
         # Cloning a project needs only manager rights on the origin
         current_user, prj = RightsBO.user_wants(self.session, current_user_id, Action.ADMINISTRATE, req.clone_of_id)
         if prj is None:
             return "Project to clone not found"
         prj = clone_of(prj)
     else:
         current_user = RightsBO.user_wants_create_project(self.session, current_user_id)
         prj = Project()
     prj.title = req.title
     prj.status = ANNOTATE_STATUS
     prj.visible = req.visible
     self.session.add(prj)
     self.session.flush()  # to get the project ID
     # Add the manage privilege
     RightsBO.grant(self.session, current_user, Action.ADMINISTRATE, prj)
     self.session.commit()
     return prj.projid