def add_task_comment(task_comment: TaskCommentDTO) -> TaskDTO: """ Adds the comment to the task history """ task = Task.get(task_comment.task_id, task_comment.project_id) if task is None: raise MappingServiceError(f"Task {task_comment.task_id} not found") task.set_task_history(TaskAction.COMMENT, task_comment.user_id, task_comment.comment) # Parse comment to see if any users have been @'d MessageService.send_message_after_comment(task_comment.user_id, task_comment.comment, task.id, task_comment.project_id) task.update() return task.as_dto_with_instructions(task_comment.preferred_locale)
def delete_tasks(project_id: int, tasks_ids): # Validate project exists. project = Project.get(project_id) if project is None: raise NotFound({"project": project_id}) tasks = [{"id": i, "obj": Task.get(i, project_id)} for i in tasks_ids] # In case a task is not found. not_found = [t["id"] for t in tasks if t["obj"] is None] if len(not_found) > 0: raise NotFound({"tasks": not_found}) # Delete task one by one. [t["obj"].delete() for t in tasks]
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
def test_split_non_square_task(self, mock_task): # Lock task for mapping task = Task.get(2, self.test_project.id) task.lock_task_for_mapping(self.test_user.id) split_task_dto = SplitTaskDTO() split_task_dto.user_id = self.test_user.id split_task_dto.project_id = self.test_project.id split_task_dto.task_id = 2 # Split tasks expected = geojson.loads( json.dumps(get_canned_json("non_square_split_results.json")) ) result = SplitService._create_split_tasks(task.x, task.y, task.zoom, task) self.assertEqual(str(expected), str(result))
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