def lock_tasks_for_validation( validation_dto: LockForValidationDTO) -> TaskDTOs: """ Lock supplied tasks for validation :raises ValidatatorServiceError """ # 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.VALIDATED, TaskStatus.BADIMAGERY ]: raise ValidatatorServiceError( f'Task {task_id} is not MAPPED, BADIMAGERY or VALIDATED') if not ValidatorService._user_can_validate_task( validation_dto.user_id, task.mapped_by): raise ValidatatorServiceError( f'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') else: raise ValidatatorServiceError( f'Mapping not allowed because: {error_reason.name}') # 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
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): return True return False
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 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) StatsService.set_counters_after_undo(project_id, user_id, current_state, undo_state) 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 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) != TaskStatus.LOCKED_FOR_MAPPING: # 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 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()
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 ValidatatorServiceError, NotFound :param project_id: :param unlock_tasks: List of tasks to be unlocked :param user_id: :return: List of Tasks :raises ValidatatorServiceError :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 ValidatatorServiceError( f'Task {unlock_task.task_id} is not LOCKED_FOR_VALIDATION') if task.locked_by != user_id: raise ValidatatorServiceError( '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
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()
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 - splittable (splittable property is True) :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() # check it's splittable if not original_task.splittable: raise SplitServiceError('Task is not splittable') # 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) 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_dto = [] for new_task_geojson in new_tasks_geojson: # 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_task.update() new_tasks_dto.append( new_task.as_dto_with_instructions( split_task_dto.preferred_locale)) # delete original task from the database original_task.delete() # 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