def test_update_after_flagging_bad_imagery(self): # Arrange test_project = Project() test_project.tasks_bad_imagery = 0 # Act StatsService._set_counters_after_bad_imagery(test_project) # Assert self.assertEqual(test_project.tasks_bad_imagery, 1)
def create_canned_project() -> Tuple[Project, User]: """ Generates a canned project in the DB to help with integration tests """ test_aoi_geojson = geojson.loads( json.dumps(get_canned_json('test_aoi.json'))) task_feature = geojson.loads( json.dumps(get_canned_json('splittable_task.json'))) test_user = create_canned_user() test_project_dto = DraftProjectDTO() test_project_dto.project_name = 'Test' test_project_dto.user_id = test_user.id test_project_dto.area_of_interest = test_aoi_geojson test_project = Project() test_project.create_draft_project(test_project_dto) test_project.set_project_aoi(test_project_dto) test_project.total_tasks = 1 # Setup test task test_task = Task.from_geojson_feature(1, task_feature) test_task.task_status = TaskStatus.MAPPED.value test_task.mapped_by = test_user.id test_project.tasks.append(test_task) test_project.create() return test_project, test_user
def search_projects( search_dto: ProjectSearchDTO) -> ProjectSearchResultsDTO: """ Searches all projects for matches to the criteria provided by the user """ all_results, paginated_results = ProjectSearchService._filter_projects( search_dto) if paginated_results.total == 0: raise NotFound() features = [] for project in all_results: # This loop creates a geojson feature collection so you can see all active projects on the map properties = { "projectId": project.id, "priority": ProjectPriority(project.priority).name } centroid = project.centroid feature = geojson.Feature(geometry=geojson.loads(project.centroid), properties=properties) features.append(feature) feature_collection = geojson.FeatureCollection(features) dto = ProjectSearchResultsDTO() dto.map_results = feature_collection for project in paginated_results.items: # This loop loads the paginated text results # TODO would be nice to get this for an array rather than individually would be more efficient project_info_dto = ProjectInfo.get_dto_for_locale( project.id, search_dto.preferred_locale, project.default_locale) list_dto = ListSearchResultDTO() list_dto.project_id = project.id list_dto.locale = project_info_dto.locale list_dto.name = project_info_dto.name list_dto.priority = ProjectPriority(project.priority).name list_dto.mapper_level = MappingLevel(project.mapper_level).name list_dto.short_description = project_info_dto.short_description list_dto.organisation_tag = project.organisation_tag list_dto.campaign_tag = project.campaign_tag list_dto.percent_mapped = Project.calculate_tasks_percent( 'mapped', project.total_tasks, project.tasks_mapped, project.tasks_validated, project.tasks_bad_imagery) list_dto.percent_validated = Project.calculate_tasks_percent( 'validated', project.total_tasks, project.tasks_mapped, project.tasks_validated, project.tasks_bad_imagery) list_dto.status = ProjectStatus(project.status).name list_dto.active_mappers = Project.get_active_mappers(project.id) dto.results.append(list_dto) dto.pagination = Pagination(paginated_results) return dto
def test_update_after_mapping_increments_counter(self): # Arrange test_project = Project() test_project.tasks_mapped = 0 test_user = User() test_user.tasks_mapped = 0 # Act StatsService._set_counters_after_mapping(test_project, test_user) # Assert self.assertEqual(test_project.tasks_mapped, 1) self.assertEqual(test_user.tasks_mapped, 1)
def test_update_after_mapping_increments_counter(self): # Arrange test_project = Project() test_project.tasks_mapped = 0 test_user = User() test_user.tasks_mapped = 0 # Act StatsService._update_tasks_stats(test_project, test_user, TaskStatus.READY, TaskStatus.MAPPED) # Assert self.assertEqual(test_project.tasks_mapped, 1) self.assertEqual(test_user.tasks_mapped, 1)
def test_update_after_flagging_bad_imagery(self): # Arrange test_project = Project() test_project.tasks_bad_imagery = 0 test_user = User() test_user.tasks_invalidated = 0 # Act StatsService._update_tasks_stats(test_project, test_user, TaskStatus.READY, TaskStatus.BADIMAGERY) # Assert self.assertEqual(test_project.tasks_bad_imagery, 1)
def _attach_tasks_to_project(draft_project: Project, tasks_geojson): """ Validates then iterates over the array of tasks and attach them to the draft project :param draft_project: Draft project in scope :param tasks_geojson: GeoJSON feature collection of mapping tasks :raises InvalidGeoJson, InvalidData """ tasks = geojson.loads(json.dumps(tasks_geojson)) if type(tasks) is not geojson.FeatureCollection: raise InvalidGeoJson( 'Tasks: Invalid GeoJson must be FeatureCollection') is_valid_geojson = geojson.is_valid(tasks) if is_valid_geojson['valid'] == 'no': raise InvalidGeoJson( f"Tasks: Invalid FeatureCollection - {is_valid_geojson['message']}" ) task_count = 1 for feature in tasks['features']: try: task = Task.from_geojson_feature(task_count, feature) except (InvalidData, InvalidGeoJson) as e: raise e draft_project.tasks.append(task) task_count += 1 task_count -= 1 # Remove last increment before falling out loop draft_project.total_tasks = task_count
def test_update_after_invalidating_bad_imagery_task_sets_counters_correctly(self, last_status): # Arrange test_project = Project() test_project.tasks_bad_imagery = 1 test_user = User() test_user.tasks_invalidated = 0 last_status.return_value = TaskStatus.BADIMAGERY # Act StatsService._set_counters_after_invalidated(1, test_project, test_user) # Assert self.assertEqual(test_project.tasks_bad_imagery, 0) self.assertEqual(test_user.tasks_invalidated, 1)
def test_update_after_invalidating_mapped_task_sets_counters_correctly(self, last_status): # Arrange test_project = Project() test_project.tasks_mapped = 1 test_user = User() test_user.tasks_invalidated = 0 last_status.return_value = TaskStatus.MAPPED # Act StatsService._set_counters_after_invalidated(1, test_project, test_user) # Assert self.assertEqual(test_project.tasks_mapped, 0) self.assertEqual(test_user.tasks_invalidated, 1)
def get_project_by_id(project_id: int) -> Project: project = Project.get(project_id) if project is None: raise NotFound() return project
def test_split_task_helper(self, mock_task_get, mock_task_get_max_task_id_for_project, mock_task_create, mock_task_delete, mock_project_get, mock_project_save, mock_project_tasks, mock_instructions): if self.skip_tests: return # arrange task_stub = Task() task_stub.id = 1 task_stub.project_id = 1 task_stub.task_status = 1 task_stub.locked_by = 1234 task_stub.lock_holder = 1234 task_stub.splittable = True task_stub.x = 1010 task_stub.y = 1399 task_stub.zoom = 11 mock_task_get.return_value = task_stub mock_task_get_max_task_id_for_project.return_value = 1 mock_project_get.return_value = Project() mock_project_tasks.return_value = [task_stub] splitTaskDTO = SplitTaskDTO() splitTaskDTO.user_id = 1234 splitTaskDTO.project_id = 1 splitTaskDTO.task_id = 1 # act result = SplitService.split_task(splitTaskDTO) # assert self.assertEqual(4, len(result.tasks))
def test_update_after_invalidating_validated_task_sets_counters_correctly( self): # Arrange test_project = Project() test_project.tasks_validated = 1 test_user = User() test_user.tasks_invalidated = 0 # Act StatsService._update_tasks_stats(test_project, test_user, TaskStatus.VALIDATED, TaskStatus.INVALIDATED) # Assert self.assertEqual(test_project.tasks_validated, 0) self.assertEqual(test_user.tasks_invalidated, 1)
def test_user_correctly_identified_as_pm(self, mock_user): # Arrange test_proj = Project() test_user = User() test_user.role = UserRole.PROJECT_MANAGER.value mock_user.return_value = test_user # Act / Assert self.assertTrue(UserService.is_user_a_project_manager(123)) self.assertTrue(test_proj)
def create_draft_project(draft_project_dto: DraftProjectDTO) -> int: """ Validates and then persists draft projects in the DB :param draft_project_dto: Draft Project DTO with data from API :raises InvalidGeoJson :returns ID of new draft project """ try: area_of_interest = AreaOfInterest( draft_project_dto.area_of_interest) except InvalidGeoJson as e: raise e draft_project = Project() draft_project.create_draft_project(draft_project_dto, area_of_interest) # if arbitrary_tasks requested, create tasks from aoi otherwise use tasks in DTO if draft_project_dto.has_arbitrary_tasks: tasks = GridService.tasks_from_aoi_features( draft_project_dto.area_of_interest) else: tasks = draft_project_dto.tasks ProjectAdminService._attach_tasks_to_project(draft_project, tasks) draft_project.create() return draft_project.id
def test_get_intersecting_projects(self, get_dto_for_locale, _get_intersecting_projects, get_user_by_username, validate_bbox_area, _make_4326_polygon_from_bbox): if self.skip_tests: return # arrange _make_4326_polygon_from_bbox mock _make_4326_polygon_from_bbox.return_value = Polygon([ (34.68826225820438, -12.59912449955007), (34.68826225820438, -11.57858317689196), (32.50198296132938, -11.57858317689196), (32.50198296132938, -12.59912449955007), (34.68826225820438, -12.59912449955007) ]) # arrange validate_bbox_area mock validate_bbox_area.return_value = True # arrange get_user_by_username mock get_user_by_username.return_value = User(id=3488526) # arrange _get_intersecting_projects mock polygon = json.dumps(get_canned_json('search_bbox_feature.json')) project = Project(id=2274, status=0, default_locale='en', geometry=polygon) projects = [project] _get_intersecting_projects.return_value = projects # arrange get_dto_for_locale mock get_dto_for_locale.return_value = ProjectInfo( name='PEPFAR Kenya: Homa Bay') # arrange dto dto = ProjectSearchBBoxDTO() dto.bbox = map(float, '34.404,-1.034, 34.717,-0.624'.split(',')) dto.preferred_locale = 'en' dto.input_srid = 4326 dto.project_author = 3488526 dto.validate() # arrange expected result expected = json.dumps(get_canned_json('search_bbox_result.json')) # act result = ProjectSearchService.get_projects_geojson(dto) # assert self.assertEqual(str(expected), str(expected))
def test_project_can_be_cloned(self): if self.skip_tests: return # Arrange self.update_project_with_info() # Act original_id = copy.copy(self.test_project.id) cloned_project = Project.clone(original_id, self.test_user.id) self.assertTrue(cloned_project) self.assertEqual(cloned_project.project_info[0].name, 'Thinkwhere Test') # Tidy Up cloned_project.delete() original_project = Project.get( original_id ) # SQLAlchemy is hanging on to a ref to the old project original_project.delete()
def get_project_stats(project_id: int, preferred_locale: str) -> ProjectSummary: """ Gets stats for the specified project """ project = db.session.query(Project.id, Project.status, Project.campaign_tag, Project.total_tasks, Project.tasks_mapped, Project.tasks_validated, Project.tasks_bad_imagery, Project.created, Project.last_updated, Project.default_locale, AreaOfInterest.centroid.ST_AsGeoJSON().label('geojson'))\ .join(AreaOfInterest).filter(Project.id == project_id).one_or_none() pm_project = Project.get_project_summary(project, preferred_locale) return pm_project
def test_split_task_helper(self, mock_task_get, mock_task_get_max_task_id_for_project, mock_task_create, mock_task_delete, mock_project_get, mock_project_save, mock_project_tasks, mock_instructions): if self.skip_tests: return # arrange task_stub = Task() task_stub.id = 1 task_stub.project_id = 1 task_stub.task_status = 1 task_stub.locked_by = 1234 task_stub.lock_holder = 1234 task_stub.is_square = True task_stub.x = 16856 task_stub.y = 17050 task_stub.zoom = 15 task_stub.geometry = shape.from_shape( Polygon([(5.1855468740711421, 7.2970875628719796), (5.1855468740711421, 7.3079847788619219), (5.1965332021941588, 7.3079847788619219), (5.1965332021941588, 7.2970875628719796), (5.1855468740711421, 7.2970875628719796)])) mock_task_get.return_value = task_stub mock_task_get_max_task_id_for_project.return_value = 1 mock_project_get.return_value = Project() mock_project_tasks.return_value = [task_stub] splitTaskDTO = SplitTaskDTO() splitTaskDTO.user_id = 1234 splitTaskDTO.project_id = 1 splitTaskDTO.task_id = 1 # act result = SplitService.split_task(splitTaskDTO) # assert self.assertEqual(4, len(result.tasks))
def transfer_project_to(project_id: int, transfering_user_id: int, username: str): """ Transfers project from old owner (transfering_user_id) to new owner (username) """ project = Project.get(project_id) transfering_user = UserService.get_user_by_id(transfering_user_id) new_owner = UserService.get_user_by_username(username) is_pm = new_owner.role in (UserRole.PROJECT_MANAGER.value, UserRole.ADMIN.value) if not is_pm: raise Exception("User must be a project manager") if transfering_user.role == UserRole.PROJECT_MANAGER.value: if project.author_id == transfering_user_id: project.author_id = new_owner.id project.save() else: raise Exception("Invalid owner_id") elif transfering_user.role == UserRole.ADMIN.value: project.author_id = new_owner.id project.save() else: raise Exception("Normal users cannot transfer projects")
def get_all_campaign_tags(preferred_locale): """ Get all campaign tags""" return Project.get_all_campaign_tag(preferred_locale=preferred_locale)
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
def test_tasks_state_representation(self): # Arrange test_project = Project() test_project.tasks_mapped = 0 test_project.tasks_validated = 0 test_project.tasks_bad_imagery = 0 test_mapper = User() test_mapper.tasks_mapped = 0 test_mapper.tasks_validated = 0 test_mapper.tasks_invalidated = 0 test_validator = User() test_validator.tasks_mapped = 0 test_validator.tasks_validated = 0 test_validator.tasks_invalidated = 0 test_admin = User() test_admin.tasks_mapped = 0 test_admin.tasks_validated = 0 test_admin.tasks_invalidated = 0 # Mapper marks task as mapped StatsService._update_tasks_stats(test_project, test_mapper, TaskStatus.READY, TaskStatus.MAPPED) # Validator marks task as bad imagery StatsService._update_tasks_stats(test_project, test_validator, TaskStatus.MAPPED, TaskStatus.BADIMAGERY) # Admin undos marking task as bad imagery StatsService._update_tasks_stats(test_project, test_admin, TaskStatus.BADIMAGERY, TaskStatus.MAPPED, 'undo') # Validator marks task as invalid StatsService._update_tasks_stats(test_project, test_validator, TaskStatus.MAPPED, TaskStatus.INVALIDATED) # Mapper marks task as mapped StatsService._update_tasks_stats(test_project, test_mapper, TaskStatus.INVALIDATED, TaskStatus.MAPPED) # Admin undos marking task as mapped (test_mapper is given to the function though, as the author of the # last_change - compare with MappingServer.undo_mapping() method) StatsService._update_tasks_stats(test_project, test_mapper, TaskStatus.MAPPED, TaskStatus.INVALIDATED, 'undo') # Mapper marks task as mapped StatsService._update_tasks_stats(test_project, test_mapper, TaskStatus.INVALIDATED, TaskStatus.MAPPED) # Validator marks task as valid StatsService._update_tasks_stats(test_project, test_validator, TaskStatus.MAPPED, TaskStatus.VALIDATED) # Assert self.assertEqual(test_project.tasks_mapped, 0) self.assertEqual(test_project.tasks_validated, 1) self.assertEqual(test_project.tasks_bad_imagery, 0) self.assertEqual(test_mapper.tasks_mapped, 2) self.assertEqual(test_mapper.tasks_validated, 0) self.assertEqual(test_mapper.tasks_invalidated, 0) self.assertEqual(test_validator.tasks_mapped, 0) self.assertEqual(test_validator.tasks_validated, 1) self.assertEqual(test_validator.tasks_invalidated, 1) self.assertEqual(test_admin.tasks_mapped, 0) self.assertEqual(test_admin.tasks_validated, 0) self.assertEqual(test_admin.tasks_invalidated, 0)
def get_projects_for_admin(admin_id: int, preferred_locale: str): """ Get all projects for provided admin """ return Project.get_projects_for_admin(admin_id, preferred_locale)
def get_all_organisation_tags(preferred_locale): """ Get all org tags""" return Project.get_all_organisations_tag(preferred_locale=preferred_locale)
def _set_default_changeset_comment(draft_project: Project): """ Sets the default changesset comment when project created """ default_comment = current_app.config['DEFAULT_CHANGESET_COMMENT'] draft_project.changeset_comment = f'{default_comment}-{draft_project.id}' draft_project.save()
def create_draft_project(draft_project_dto: DraftProjectDTO) -> int: """ Validates and then persists draft projects in the DB :param draft_project_dto: Draft Project DTO with data from API :raises InvalidGeoJson :returns ID of new draft project """ # First things first, we need to validate that the author_id is a PM. issue #1715 if not UserService.is_user_a_project_manager( draft_project_dto.user_id): raise (ProjectAdminServiceError( f'User {UserService.get_user_by_id(draft_project_dto.user_id).username} is not a project manager' )) # If we're cloning we'll copy all the project details from the clone, otherwise create brand new project if draft_project_dto.cloneFromProjectId: draft_project = Project.clone(draft_project_dto.cloneFromProjectId, draft_project_dto.user_id) else: draft_project = Project() draft_project.create_draft_project(draft_project_dto) draft_project.set_project_aoi(draft_project_dto) # if arbitrary_tasks requested, create tasks from aoi otherwise use tasks in DTO if draft_project_dto.has_arbitrary_tasks: tasks = GridService.tasks_from_aoi_features( draft_project_dto.area_of_interest) draft_project.task_creation_mode = TaskCreationMode.ARBITRARY.value else: tasks = draft_project_dto.tasks ProjectAdminService._attach_tasks_to_project(draft_project, tasks) if draft_project_dto.cloneFromProjectId: draft_project.save() # Update the clone else: draft_project.create() # Create the new project draft_project.set_default_changeset_comment() return draft_project.id
def create_draft_project(draft_project_dto: DraftProjectDTO) -> int: """ Validates and then persists draft projects in the DB :param draft_project_dto: Draft Project DTO with data from API :raises InvalidGeoJson :returns ID of new draft project """ # If we're cloning we'll copy all the project details from the clone, otherwise create brand new project if draft_project_dto.cloneFromProjectId: draft_project = Project.clone(draft_project_dto.cloneFromProjectId, draft_project_dto.user_id) else: draft_project = Project() draft_project.create_draft_project(draft_project_dto) draft_project.set_project_aoi(draft_project_dto) # if arbitrary_tasks requested, create tasks from aoi otherwise use tasks in DTO if draft_project_dto.has_arbitrary_tasks: tasks = GridService.tasks_from_aoi_features( draft_project_dto.area_of_interest) draft_project.task_creation_mode = TaskCreationMode.ARBITRARY.value else: tasks = draft_project_dto.tasks ProjectAdminService._attach_tasks_to_project(draft_project, tasks) if draft_project_dto.cloneFromProjectId: draft_project.save() # Update the clone else: draft_project.create() # Create the new project draft_project.set_default_changeset_comment() return draft_project.id