예제 #1
0
    def map_all_tasks(project_id: int, user_id: int):
        """ Marks all tasks on a project as mapped """
        tasks_to_map = Task.query.filter(
            Task.project_id == project_id,
            Task.task_status.notin_([
                TaskStatus.BADIMAGERY.value,
                TaskStatus.MAPPED.value,
                TaskStatus.VALIDATED.value,
            ]),
        ).all()

        for task in tasks_to_map:
            if TaskStatus(task.task_status) not in [
                    TaskStatus.LOCKED_FOR_MAPPING,
                    TaskStatus.LOCKED_FOR_VALIDATION,
            ]:
                # Only lock tasks that are not already locked to avoid double lock issue
                task.lock_task_for_mapping(user_id)

            task.unlock_task(user_id, new_state=TaskStatus.MAPPED)

        # Set counters to fully mapped
        project = ProjectService.get_project_by_id(project_id)
        project.tasks_mapped = project.total_tasks - project.tasks_bad_imagery
        project.save()
예제 #2
0
    def undo_mapping(project_id: int,
                     task_id: int,
                     user_id: int,
                     preferred_locale: str = "en") -> TaskDTO:
        """ Allows a user to Undo the task state they updated """
        task = MappingService.get_task(task_id, project_id)

        if not MappingService._is_task_undoable(user_id, task):
            raise MappingServiceError("Undo not allowed for this user")

        current_state = TaskStatus(task.task_status)
        undo_state = TaskHistory.get_last_status(project_id, task_id, True)

        # Refer to last action for user of it.
        last_action = TaskHistory.get_last_action(project_id, task_id)

        StatsService.update_stats_after_task_state_change(
            project_id, last_action.user_id, current_state, undo_state, "undo")

        task.unlock_task(
            user_id,
            undo_state,
            f"Undo state from {current_state.name} to {undo_state.name}",
            True,
        )

        return task.as_dto_with_instructions(preferred_locale)
    def lock_tasks_for_validation(
            validation_dto: LockForValidationDTO) -> TaskDTOs:
        """
        Lock supplied tasks for validation
        :raises ValidatorServiceError
        """
        # Loop supplied tasks to check they can all be locked for validation
        tasks_to_lock = []
        for task_id in validation_dto.task_ids:
            task = Task.get(task_id, validation_dto.project_id)

            if task is None:
                raise NotFound(f"Task {task_id} not found")

            if TaskStatus(task.task_status) not in [
                    TaskStatus.MAPPED,
                    TaskStatus.INVALIDATED,
                    TaskStatus.BADIMAGERY,
            ]:
                raise ValidatorServiceError(
                    f"Task {task_id} is not MAPPED, BADIMAGERY or INVALIDATED")
            user_can_validate = ValidatorService._user_can_validate_task(
                validation_dto.user_id, task.mapped_by)
            if not user_can_validate:
                raise ValidatorServiceError(
                    "Tasks cannot be validated by the same user who marked task as mapped or badimagery"
                )

            tasks_to_lock.append(task)

        user_can_validate, error_reason = ProjectService.is_user_permitted_to_validate(
            validation_dto.project_id, validation_dto.user_id)

        if not user_can_validate:
            if error_reason == ValidatingNotAllowed.USER_NOT_ACCEPTED_LICENSE:
                raise UserLicenseError(
                    "User must accept license to map this task")
            elif error_reason == ValidatingNotAllowed.USER_ALREADY_HAS_TASK_LOCKED:
                raise ValidatorServiceError("User already has a task locked")
            else:
                raise ValidatorServiceError(
                    f"Validation not allowed because: {error_reason}")

        # Lock all tasks for validation
        dtos = []
        for task in tasks_to_lock:
            task.lock_task_for_validating(validation_dto.user_id)
            dtos.append(
                task.as_dto_with_instructions(validation_dto.preferred_locale))

        task_dtos = TaskDTOs()
        task_dtos.tasks = dtos

        return task_dtos
예제 #4
0
 def get_task_locked_by_user(project_id: int, task_id: int,
                             user_id: int) -> Task:
     """
     Returns task specified by project id and task id if found and locked for mapping by user
     :raises: MappingServiceError
     """
     task = MappingService.get_task(task_id, project_id)
     if task is None:
         raise MappingServiceError(f"Task {task_id} not found")
     current_state = TaskStatus(task.task_status)
     if current_state != TaskStatus.LOCKED_FOR_MAPPING:
         raise MappingServiceError(
             "Status must be LOCKED_FOR_MAPPING to unlock")
     if task.locked_by != user_id:
         raise MappingServiceError(
             "Attempting to unlock a task owned by another user")
     return task
    def get_tasks_locked_by_user(project_id: int, unlock_tasks, user_id: int):
        """
        Returns tasks specified by project id and unlock_tasks list if found and locked for validation by user,
        otherwise raises ValidatorServiceError, NotFound
        :param project_id:
        :param unlock_tasks: List of tasks to be unlocked
        :param user_id:
        :return: List of Tasks
        :raises ValidatorServiceError
        :raises NotFound
        """
        tasks_to_unlock = []
        # Loop supplied tasks to check they can all be unlocked
        for unlock_task in unlock_tasks:
            task = Task.get(unlock_task.task_id, project_id)

            if task is None:
                raise NotFound(f"Task {unlock_task.task_id} not found")

            current_state = TaskStatus(task.task_status)
            if current_state != TaskStatus.LOCKED_FOR_VALIDATION:
                raise ValidatorServiceError(
                    f"Task {unlock_task.task_id} is not LOCKED_FOR_VALIDATION"
                )

            if task.locked_by != user_id:
                raise ValidatorServiceError(
                    "Attempting to unlock a task owned by another user"
                )

            if hasattr(unlock_task, "status"):
                # we know what status we ate going to be setting to on unlock
                new_status = TaskStatus[unlock_task.status]
            else:
                new_status = None

            tasks_to_unlock.append(
                dict(
                    task=task,
                    new_state=new_status,
                    comment=unlock_task.comment,
                    issues=unlock_task.issues,
                )
            )

        return tasks_to_unlock
예제 #6
0
    def _is_task_undoable(logged_in_user_id: int, task: Task) -> bool:
        """ Determines if the current task status can be undone by the logged in user """
        # Test to see if user can undo status on this task
        if logged_in_user_id and TaskStatus(task.task_status) not in [
                TaskStatus.LOCKED_FOR_MAPPING,
                TaskStatus.LOCKED_FOR_VALIDATION,
                TaskStatus.READY,
        ]:

            last_action = TaskHistory.get_last_action(task.project_id, task.id)

            # User requesting task made the last change, so they are allowed to undo it.
            if last_action.user_id == int(
                    logged_in_user_id
            ) or ProjectService.is_user_permitted_to_validate(
                    task.project_id, logged_in_user_id):
                return True

        return False
    def invalidate_all_tasks(project_id: int, user_id: int):
        """ Invalidates all mapped tasks on a project"""
        mapped_tasks = Task.query.filter(
            Task.project_id == project_id,
            ~Task.task_status.in_(
                [TaskStatus.READY.value, TaskStatus.BADIMAGERY.value]),
        ).all()
        for task in mapped_tasks:
            if TaskStatus(task.task_status) not in [
                    TaskStatus.LOCKED_FOR_MAPPING,
                    TaskStatus.LOCKED_FOR_VALIDATION,
            ]:
                # Only lock tasks that are not already locked to avoid double lock issue.
                task.lock_task_for_validating(user_id)
            task.unlock_task(user_id, new_state=TaskStatus.INVALIDATED)

        # Reset counters
        project = ProjectService.get_project_by_id(project_id)
        project.tasks_mapped = 0
        project.tasks_validated = 0
        project.save()
    def validate_all_tasks(project_id: int, user_id: int):
        """ Validates all mapped tasks on a project"""
        tasks_to_validate = Task.query.filter(
            Task.project_id == project_id,
            Task.task_status != TaskStatus.BADIMAGERY.value,
        ).all()

        for task in tasks_to_validate:
            task.mapped_by = task.mapped_by or user_id  # Ensure we set mapped by value
            if TaskStatus(task.task_status) not in [
                    TaskStatus.LOCKED_FOR_MAPPING,
                    TaskStatus.LOCKED_FOR_VALIDATION,
            ]:
                # Only lock tasks that are not already locked to avoid double lock issue
                task.lock_task_for_validating(user_id)

            task.unlock_task(user_id, new_state=TaskStatus.VALIDATED)

        # Set counters to fully mapped and validated
        project = ProjectService.get_project_by_id(project_id)
        project.tasks_mapped = project.total_tasks - project.tasks_bad_imagery
        project.tasks_validated = project.total_tasks
        project.save()
예제 #9
0
    def split_task(split_task_dto: SplitTaskDTO) -> TaskDTOs:
        """
        Replaces a task square with 4 smaller tasks at the next OSM tile grid zoom level
        Validates that task is:
         - locked for mapping by current user
        :param split_task_dto:
        :return: new tasks in a DTO
        """
        # get the task to be split
        original_task = Task.get(split_task_dto.task_id,
                                 split_task_dto.project_id)
        if original_task is None:
            raise NotFound()

        original_geometry = shape.to_shape(original_task.geometry)

        # check its locked for mapping by the current user
        if TaskStatus(
                original_task.task_status) != TaskStatus.LOCKED_FOR_MAPPING:
            raise SplitServiceError(
                "Status must be LOCKED_FOR_MAPPING to split")

        if original_task.locked_by != split_task_dto.user_id:
            raise SplitServiceError(
                "Attempting to split a task owned by another user")

        # create new geometries from the task geometry
        try:
            new_tasks_geojson = SplitService._create_split_tasks(
                original_task.x, original_task.y, original_task.zoom,
                original_task)
        except Exception as e:
            raise SplitServiceError(f"Error splitting task{str(e)}")

        # create new tasks from the new geojson
        i = Task.get_max_task_id_for_project(split_task_dto.project_id)
        new_tasks = []
        new_tasks_dto = []
        for new_task_geojson in new_tasks_geojson:
            # Sanity check: ensure the new task geometry intersects the original task geometry
            new_geometry = shapely_shape(new_task_geojson.geometry)
            if not new_geometry.intersects(original_geometry):
                raise InvalidGeoJson(
                    "New split task does not intersect original task")

            # insert new tasks into database
            i = i + 1
            new_task = Task.from_geojson_feature(i, new_task_geojson)
            new_task.project_id = split_task_dto.project_id
            new_task.task_status = TaskStatus.READY.value
            new_task.create()
            new_task.task_history.extend(original_task.copy_task_history())
            if new_task.task_history:
                new_task.clear_task_lock()  # since we just copied the lock
            new_task.set_task_history(TaskAction.STATE_CHANGE,
                                      split_task_dto.user_id, None,
                                      TaskStatus.SPLIT)
            new_task.set_task_history(TaskAction.STATE_CHANGE,
                                      split_task_dto.user_id, None,
                                      TaskStatus.READY)
            new_task.task_status = TaskStatus.READY.value
            new_tasks.append(new_task)
            new_task.update()
            new_tasks_dto.append(
                new_task.as_dto_with_instructions(
                    split_task_dto.preferred_locale))

        # delete original task from the database
        try:
            original_task.delete()
        except Exception:
            db.session.rollback()
            # Ensure the new tasks are cleaned up
            for new_task in new_tasks:
                new_task.delete()
            db.session.commit()
            raise

        # update project task counts
        project = Project.get(split_task_dto.project_id)
        project.total_tasks = project.tasks.count()
        # update bad imagery because we may have split a bad imagery tile
        project.tasks_bad_imagery = project.tasks.filter(
            Task.task_status == TaskStatus.BADIMAGERY.value).count()
        project.save()

        # return the new tasks in a DTO
        task_dtos = TaskDTOs()
        task_dtos.tasks = new_tasks_dto
        return task_dtos