class CandidateMostEngagedPipelines(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES) def get(self, **kwargs): """ GET /candidates/<candidate_id>/talent-pipelines?limit=5 Fetch Engagement score of a candidate in each pipeline :return A dictionary containing list of most engaged candidates belonging to a talent-pipeline :rtype: dict """ candidate_id = kwargs.get('id') limit = request.args.get('limit', 5) if not is_number(limit) or int(limit) < 1: raise InvalidUsage("Limit should be a positive integer") candidate = Candidate.query.get(candidate_id) if not candidate: raise NotFoundError(error_message="Candidate with id {} doesn't exist in database".format(candidate_id)) if request.user.role.name != 'TALENT_ADMIN' and candidate.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and candidate belong to different domain") return {'talent_pipelines': top_most_engaged_pipelines_of_candidate(candidate_id, int(limit))}
class TalentPipelineCampaigns(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_CAMPAIGNS) def get(self, **kwargs): """ GET /talent-pipelines/<id>/campaigns?fields=id,subject&page=1&per_page=20 Fetch all campaigns of a talent-pipeline. :return A dictionary containing list of campaigns belonging to a talent-pipeline :rtype: dict """ # Valid talent pipeline talent_pipeline_id = kwargs.get('id') talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain") page = request.args.get('page', DEFAULT_PAGE) per_page = request.args.get('per_page', 20) if not is_number(page) or not is_number(per_page) or int(page) < 1 or int(per_page) < 1: raise InvalidUsage("page and per_page should be positive integers") page = int(page) per_page = int(per_page) # Get the email campaigns include_fields = request.values['fields'].split(',') if request.values.get('fields') else None email_campaigns = talent_pipeline.get_email_campaigns(page=page, per_page=per_page) headers = generate_pagination_headers(talent_pipeline.get_email_campaigns_count(), per_page, page) response = { 'page_number': page, 'email_campaigns_per_page': per_page, 'total_number_of_email_campaigns': talent_pipeline.get_email_campaigns_count(), 'email_campaigns': [email_campaign.to_dict(include_fields) for email_campaign in email_campaigns] } return ApiResponse(response=response, headers=headers, status=200)
class SmartlistCandidates(Resource): decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_SMART_LISTS) def get(self, **kwargs): """ Use this endpoint to retrieve all candidates present in list (smart or dumb list) Input: URL Arguments `smartlist_id` (Required): id of smartlist Accepts (query string parameters): fields :: comma separated values sort_by :: Sort by field limit :: Size of each page page :: Page number or cursor string :return : List of candidates present in list (smart list or dumb list) :rtype: json """ smartlist_id = kwargs.get('smartlist_id') smartlist = Smartlist.query.get(smartlist_id) if not smartlist or smartlist.is_hidden: raise NotFoundError("List id does not exists.") # check whether smartlist belongs to user's domain if request.user.role.name != 'TALENT_ADMIN' and smartlist.user.domain_id != request.user.domain_id: raise ForbiddenError( "Provided list does not belong to user's domain") request_params = dict() request_params['facets'] = request.args.get('facets', '') request_params['fields'] = request.args.get('fields', '') request_params['sort_by'] = request.args.get('sort_by', '') request_params['limit'] = request.args.get('limit', '') request_params['page'] = request.args.get('page', '') return get_smartlist_candidates(smartlist, request.oauth_token, request_params)
class SmartlistResource(Resource): decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_SMART_LISTS) def get(self, **kwargs): """Retrieve list information List must belong to auth user's domain Call this resource from url: /v1/smartlists?page=1&page_size=10 :: to retrieve all the smartlists in user's domain /v1/smartlists/<int:id> :: to get single smartlist example: http://localhost:8008/v1/smartlists/2 Returns: List in following json format { "smartlist": { "total_found": 3, "user_id": 1, "id": 1, "name": "my list" "search_params": {"location": "San Jose, CA"} } } """ list_id = kwargs.get('id') candidate_count = request.args.get('candidate-count', False) if not is_number(candidate_count) or int(candidate_count) not in ( True, False): raise InvalidUsage("`candidate_count` field value can be 0 or 1") auth_user = request.user if list_id: smartlist = Smartlist.query.get(list_id) if not smartlist or smartlist.is_hidden: raise NotFoundError("List id does not exists") # check whether smartlist belongs to user's domain if request.user.role.name != 'TALENT_ADMIN' and smartlist.user.domain_id != auth_user.domain_id: raise ForbiddenError("List does not belong to user's domain") return { 'smartlist': create_smartlist_dict(smartlist, request.oauth_token, int(candidate_count)) } else: # Return all smartlists from user's domain page = request.args.get('page', DEFAULT_PAGE) per_page = request.args.get('per_page', DEFAULT_PAGE_SIZE) total_number_of_smartlists = Smartlist.query.join( Smartlist.user).filter(User.domain_id == auth_user.domain_id, Smartlist.is_hidden == 0).count() if not is_number(page) or not is_number( per_page) or int(page) < 1 or int(per_page) < 1: raise InvalidUsage( "page and per_page should be positive integers") page = int(page) per_page = int(per_page) headers = generate_pagination_headers(total_number_of_smartlists, per_page, page) response = { 'smartlists': get_all_smartlists(auth_user, request.oauth_token, int(page), int(per_page), candidate_count), 'page_number': page, 'smartlists_per_page': per_page, 'total_number_of_smartlists': total_number_of_smartlists } return ApiResponse(response=response, headers=headers, status=200) @require_all_permissions(Permission.PermissionNames.CAN_ADD_SMART_LISTS) def post(self): """ Creates list with search params or with list of candidate ids Input data: json body having following keys "name": Name with which smart list will be created "search_params": search parameters for smart list in dictionary format or "candidate_ids": if not search_params then candidate_ids should be present :return: smartlist id """ auth_user = request.user data = request.get_json(silent=True) if not data: raise InvalidUsage("Received empty request body") # request data must pass through this function, as this will create data in desired format data = validate_and_format_smartlist_post_data(data, auth_user) smartlist = save_smartlist( user_id=auth_user.id, name=data.get('name'), talent_pipeline_id=data.get('talent_pipeline_id'), search_params=data.get('search_params'), candidate_ids=data.get('candidate_ids'), access_token=request.oauth_token) return {'smartlist': {'id': smartlist.id}}, 201 @require_all_permissions(Permission.PermissionNames.CAN_EDIT_SMART_LISTS) def patch(self, **kwargs): """ PATCH list with search params or with list of candidate ids Input data: json body having following keys "name": Name with which smart list will be created "search_params": search parameters for smart list in dictionary format or "candidate_ids": if not search_params then candidate_ids should be present :return: smartlist id """ auth_user = request.user list_id = kwargs.get('id') data = request.get_json(silent=True) if not data: raise InvalidUsage("Received empty request body") if not list_id or not is_number(list_id): raise InvalidUsage( "Either List ID is not provided or It's not an integer") smart_list = Smartlist.query.get(list_id) if not smart_list: raise InvalidUsage("No SmartList exists with id: %s" % list_id) # request data must pass through this function, as this will create data in desired format data = validate_and_format_smartlist_patch_data(data, auth_user) if data.get('name'): smart_list.name = data.get('name') if data.get('talent_pipeline_id'): smart_list.talent_pipeline_id = data.get('talent_pipeline_id') if data.get('search_params'): smart_list.search_params = data.get('search_params') elif data.get('remove_candidate_ids'): SmartlistCandidate.query.filter( SmartlistCandidate.smartlist_id == smart_list.id, SmartlistCandidate.candidate_id.in_( data.get('remove_candidate_ids'))).delete() elif data.get('add_candidate_ids'): for candidate_id in data.get('add_candidate_ids'): row = SmartlistCandidate(smartlist_id=smart_list.id, candidate_id=candidate_id) db.session.add(row) db.session.commit() candidate_ids = data.get('remove_candidate_ids', []) + data.get( 'add_candidate_ids', []) update_candidates_on_cloudsearch(request.oauth_token, candidate_ids) return {'smartlist': {'id': smart_list.id}}, 201 @require_all_permissions(Permission.PermissionNames.CAN_DELETE_SMART_LISTS) def delete(self, **kwargs): """ Deletes (hides) the smartlist :return: Id of deleted smartlist. """ list_id = kwargs.get('id') if not list_id: return InvalidUsage("List id is required for deleting a list") smartlist = Smartlist.query.get(list_id) if not smartlist or smartlist.is_hidden: raise NotFoundError("List id does not exists") if request.user.role.name == 'USER' and smartlist.user.id != request.user.id: raise ForbiddenError( "Logged-in user doesn't have appropriate permissions to delete this smartlist" ) # check whether smartlist belongs to user's domain if request.user.role.name != 'TALENT_ADMIN' and smartlist.user.domain_id != request.user.domain_id: raise ForbiddenError( "Logged-in user doesn't have appropriate permissions to delete this smartlist" ) smartlist_candidate_ids = SmartlistCandidate.query.with_entities( SmartlistCandidate.candidate_id).filter( SmartlistCandidate.smartlist_id == list_id).all() smartlist_candidate_ids = [ smartlist_candidate_id[0] for smartlist_candidate_id in smartlist_candidate_ids ] smartlist.delete() if smartlist_candidate_ids: logger.info( "Candidates %s of SmartList %s are going to be updated in Amazon Cloud " "Search" % (smartlist_candidate_ids, list_id)) update_candidates_on_cloudsearch(request.oauth_token, smartlist_candidate_ids) return {'smartlist': {'id': smartlist.id}}
class TalentPipelineSmartListApi(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_TALENT_PIPELINES) def get(self, **kwargs): """ GET /talent-pipeline/<id>/smart_lists Fetch all smartlists of a talent_pipeline :return A dictionary containing smartlist objects of a talent_pipeline :rtype: dict """ talent_pipeline_id = kwargs.get('id') talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain") page = request.args.get('page', DEFAULT_PAGE) per_page = request.args.get('per_page', DEFAULT_PAGE_SIZE) if not is_number(page) or not is_number(per_page) or int(page) < 1 or int(per_page) < 1: raise InvalidUsage("page and per_page should be positive integers") page = int(page) per_page = int(per_page) total_number_of_smartlists = Smartlist.query.filter_by(talent_pipeline_id=talent_pipeline_id).count() smartlists = Smartlist.query.filter_by(talent_pipeline_id=talent_pipeline_id).order_by( Smartlist.added_time.desc()).paginate(page, per_page, False) smartlists = smartlists.items headers = generate_pagination_headers(total_number_of_smartlists, per_page, page) response = { 'page_number': page, 'smartlists_per_page': per_page, 'total_number_of_smartlists': total_number_of_smartlists, 'smartlists': [smartlist.to_dict() for smartlist in smartlists] } return ApiResponse(response=response, headers=headers, status=200) @require_all_permissions(Permission.PermissionNames.CAN_EDIT_TALENT_PIPELINES) def post(self, **kwargs): """ POST /talent-pipeline/<id>/smartlists Add smartlists to a talent_pipeline Take a JSON dictionary containing smartlist_ids :return A dictionary containing smartlist_ids successfully added to talent_pipeline :rtype: dict """ talent_pipeline_id = kwargs.get('id') talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) posted_data = request.get_json(silent=True) if not posted_data or 'smartlist_ids' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) smartlist_ids = posted_data['smartlist_ids'] # Talent_pool object(s) must be in a list if not isinstance(smartlist_ids, list): raise InvalidUsage("Request body is not properly formatted") if request.user.role.name == 'USER' and talent_pipeline.user.id != request.user.id: raise ForbiddenError("Logged-in user doesn't have appropriate permissions to edit this talent-pipeline") if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain") for smartlist_id in smartlist_ids: if not is_number(smartlist_id): raise InvalidUsage('Smartlist id %s should be an integer'.format(smartlist_id)) else: smartlist_id = int(smartlist_id) smartlist = Smartlist.query.get(smartlist_id) if smartlist.user.domain_id != talent_pipeline.user.domain_id: raise ForbiddenError("Smartlist {} and Talent pipeline {} belong to different domain".format( smartlist_id, talent_pipeline_id)) if smartlist.talent_pipeline_id == talent_pipeline_id: raise InvalidUsage("Smartlist {} already belongs to Talent Pipeline {}".format( smartlist.name, talent_pipeline_id)) if smartlist.talent_pipeline_id: raise ForbiddenError("smartlist {} is already assigned to talent_pipeline {}".format( smartlist.name, smartlist.talent_pipeline_id)) smartlist.talent_pipeline_id = talent_pipeline_id db.session.commit() return { 'smartlist_ids': [int(smartlist_id) for smartlist_id in smartlist_ids] } @require_all_permissions(Permission.PermissionNames.CAN_EDIT_TALENT_PIPELINES) def delete(self, **kwargs): """ DELETE /talent-pipeline/<id>/smartlists Remove smartlists from a talent_pipeline Take a JSON dictionary containing smartlist_ids :return A dictionary containing smartlist_ids successfully removed from a talent_pipeline :rtype: dict """ talent_pipeline_id = kwargs.get('id') talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) posted_data = request.get_json(silent=True) if not posted_data or 'smartlist_ids' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) smartlist_ids = posted_data['smartlist_ids'] # Talent_pool object(s) must be in a list if not isinstance(smartlist_ids, list): raise InvalidUsage("Request body is not properly formatted") if request.user.role.name == 'USER' and talent_pipeline.user.id != request.user.id: raise ForbiddenError("Logged-in user doesn't have appropriate permissions to edit this talent-pipeline") if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain") for smartlist_id in smartlist_ids: if not is_number(smartlist_id): raise InvalidUsage('Smart List id {} should be an integer'.format(smartlist_id)) else: smartlist_id = int(smartlist_id) smartlist = Smartlist.query.get(smartlist_id) if smartlist.talent_pipeline_id != talent_pipeline_id: raise ForbiddenError("smartlist {} doesn't belong to talent_pipeline {}".format( smartlist.name, talent_pipeline_id)) smartlist.talent_pipeline_id = None db.session.commit() return { 'smartlist_ids': [int(smartlist_id) for smartlist_id in smartlist_ids] }
class TalentPipelineCandidates(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_ADD_TALENT_PIPELINES) def post(self, **kwargs): """ POST /talent-pipeline/<id>/candidates Add candidates to a Talent Pipeline :return None :rtype: None """ talent_pipeline_id = kwargs.get('id') talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) posted_data = request.get_json(silent=True) if not posted_data or 'candidate_ids' not in posted_data: raise InvalidUsage("Request body is empty or not provided") candidate_ids = posted_data.get('candidate_ids') candidate_ids = list(set(candidate_ids)) if Candidate.query.filter(Candidate.id.in_(candidate_ids)).count() != len(candidate_ids): raise InvalidUsage("Some of the candidates don't exist in DB") TalentPipelineExcludedCandidates.query.filter( TalentPipelineExcludedCandidates.talent_pipeline_id == talent_pipeline.id, TalentPipelineExcludedCandidates.candidate_id.in_(candidate_ids)).delete(synchronize_session='fetch') added_candidate_ids = [] for candidate_id in candidate_ids: if not TalentPipelineIncludedCandidates.query.filter_by(talent_pipeline_id=talent_pipeline.id, candidate_id=candidate_id).first(): added_candidate_ids.append(candidate_id) db.session.add(TalentPipelineIncludedCandidates(talent_pipeline_id=talent_pipeline.id, candidate_id=candidate_id)) # Track updates made to candidate's records db.session.add(CandidateEdit( user_id=request.user.id, candidate_id=candidate_id, field_id=1801, old_value=None, new_value=talent_pipeline.name, edit_datetime=datetime.utcnow(), is_custom_field=False )) else: db.session.rollback() raise InvalidUsage("Candidate: %s is already included in pipeline: %s" % ( candidate_id, talent_pipeline.id)) db.session.commit() update_candidates_on_cloudsearch(request.oauth_token, candidate_ids) activity_data = { 'name': talent_pipeline.name } candidates = Candidate.query.with_entities(Candidate.id, Candidate.formatted_name).filter( Candidate.id.in_(added_candidate_ids)).all() for candidate_id, formatted_name in candidates: activity_data['formattedName'] = formatted_name add_activity(user_id=request.user.id, activity_type=Activity.MessageIds.PIPELINE_ADD_CANDIDATE, source_table=Candidate.__tablename__, source_id=candidate_id, params=activity_data) return '', 204 @require_all_permissions(Permission.PermissionNames.CAN_ADD_TALENT_PIPELINES) def delete(self, **kwargs): """ DELETE /talent-pipeline/<id>/candidates Add candidates to a Talent Pipeline :return None :rtype: None """ talent_pipeline_id = kwargs.get('id') talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) posted_data = request.get_json(silent=True) if not posted_data or 'candidate_ids' not in posted_data: raise InvalidUsage("Request body is empty or not provided") candidate_ids = posted_data.get('candidate_ids') candidate_ids = list(set(candidate_ids)) if Candidate.query.filter(Candidate.id.in_(candidate_ids)).count() != len(candidate_ids): raise InvalidUsage("Some of the candidates don't exist in DB") TalentPipelineIncludedCandidates.query.filter( TalentPipelineIncludedCandidates.talent_pipeline_id == talent_pipeline.id, TalentPipelineIncludedCandidates.candidate_id.in_(candidate_ids)).delete(synchronize_session='fetch') removed_candidate_ids = [] for candidate_id in candidate_ids: if not TalentPipelineExcludedCandidates.query.filter_by(talent_pipeline_id=talent_pipeline.id, candidate_id=candidate_id).first(): removed_candidate_ids.append(candidate_id) db.session.add(TalentPipelineExcludedCandidates(talent_pipeline_id=talent_pipeline.id, candidate_id=candidate_id)) # Track updates made to candidate's records db.session.add(CandidateEdit( user_id=request.user.id, candidate_id=candidate_id, field_id=1801, old_value=talent_pipeline.name, new_value=None, # inferring deletion edit_datetime=datetime.utcnow(), is_custom_field=False )) else: db.session.rollback() raise InvalidUsage("Candidate: %s is not included in pipeline: %s" % (candidate_id, talent_pipeline.id)) db.session.commit() update_candidates_on_cloudsearch(request.oauth_token, candidate_ids) activity_data = { 'name': talent_pipeline.name } candidates = Candidate.query.with_entities(Candidate.id, Candidate.formatted_name).filter( Candidate.id.in_(removed_candidate_ids)).all() for candidate_id, formatted_name in candidates: activity_data['formattedName'] = formatted_name add_activity(user_id=request.user.id, activity_type=Activity.MessageIds.PIPELINE_REMOVE_CANDIDATE, source_table=Candidate.__tablename__, source_id=candidate_id, params=activity_data) return '', 204 @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES) def get(self, **kwargs): """ GET /talent-pipeline/<id>/candidates Fetch all candidates of a talent-pipeline Query String: {'fields': 'id,email', 'sort_by': 'match' 'limit':10, 'page': 2} :return A dictionary containing list of candidates belonging to a talent-pipeline :rtype: dict """ talent_pipeline_id = kwargs.get('id') talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain") request_params = dict() request_params['facets'] = request.args.get('facets', '') request_params['fields'] = request.args.get('fields', '') request_params['sort_by'] = request.args.get('sort_by', '') request_params['limit'] = request.args.get('limit', '') request_params['page'] = request.args.get('page', '') return get_candidates_of_talent_pipeline(talent_pipeline, request.oauth_token, request_params)
class TalentPipelineApi(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_TALENT_PIPELINES) def get(self, **kwargs): """ GET /talent-pipelines/<id> Fetch talent-pipeline object GET /talent-pipelines Fetch all talent-pipelines objects of domain of logged-in user :return A dictionary containing talent-pipeline's basic info or a dictionary containing all talent-pipelines of a domain :rtype: dict """ talent_pipeline_id = kwargs.get('id') interval_in_days = request.args.get('interval', 30) candidate_count = request.args.get('candidate-count', False) email_campaign_count = request.args.get('email-campaign-count', False) if not is_number(candidate_count) or int(candidate_count) not in (True, False): raise InvalidUsage("`candidate_count` field value can be 0 or 1") if not is_number(email_campaign_count) or int(email_campaign_count) not in (True, False): raise InvalidUsage("`email_campaign_count` field value can be 0 or 1") if not is_number(interval_in_days) or int(interval_in_days) < 0: raise InvalidUsage("Value of interval should be positive integer") if talent_pipeline_id: talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain") if not candidate_count: talent_pipeline_dict = talent_pipeline.to_dict(email_campaign_count=email_campaign_count) else: talent_pipeline_dict = talent_pipeline.to_dict(email_campaign_count=email_campaign_count, include_candidate_count=True, get_candidate_count=get_talent_pipeline_stat_for_given_day) talent_pipeline_dict.update({'engagement_score': get_pipeline_engagement_score(talent_pipeline_id)}) return {'talent_pipeline': talent_pipeline_dict} else: sort_by = request.args.get('sort_by', 'added_time') sort_type = request.args.get('sort_type', 'DESC') search_keyword = request.args.get('search', '').strip() owner_user_id = request.args.get('user_id', '') is_hidden = request.args.get('is_hidden', 0) from_date = request.args.get('from_date', EPOCH_TIME_STRING) to_date = request.args.get('to_date', datetime.utcnow().isoformat()) page = request.args.get('page', DEFAULT_PAGE) per_page = request.args.get('per_page', DEFAULT_PAGE_SIZE) if not is_number(is_hidden) or int(is_hidden) not in (0, 1): raise InvalidUsage('`is_hidden` can be either 0 or 1') if not is_number(page) or not is_number(per_page) or int(page) < 1 or int(per_page) < 1: raise InvalidUsage("page and per_page should be positive integers") page = int(page) per_page = int(per_page) if owner_user_id and is_number(owner_user_id) and not User.query.get(int(owner_user_id)): raise InvalidUsage("User: (%s) doesn't exist in system") if sort_by not in ('added_time', 'name', 'engagement_score', 'candidate_count'): raise InvalidUsage('Value of sort parameter is not valid') try: from_date = parser.parse(from_date).replace(tzinfo=None) to_date = parser.parse(to_date).replace(tzinfo=None) except Exception as e: raise InvalidUsage("from_date or to_date is not properly formatted: %s" % e) talent_pipelines_query = TalentPipeline.query.join(User).filter( TalentPipeline.is_hidden == is_hidden, User.domain_id == request.user.domain_id, from_date <= TalentPipeline.added_time, TalentPipeline.added_time <= to_date, or_( TalentPipeline.name.ilike('%' + search_keyword + '%'), TalentPipeline.description.ilike('%' + search_keyword + '%'))) if owner_user_id: talent_pipelines_query = talent_pipelines_query.filter(User.id == int(owner_user_id)) total_number_of_talent_pipelines = talent_pipelines_query.count() if sort_by not in ("engagement_score", "candidate_count"): if sort_by == 'added_time': sort_attribute = TalentPipeline.added_time else: sort_attribute = TalentPipeline.name talent_pipelines = talent_pipelines_query.order_by( sort_attribute.asc() if sort_type == 'ASC' else sort_attribute.desc()).paginate(page, per_page, False) talent_pipelines = talent_pipelines.items else: talent_pipelines = talent_pipelines_query.all() if candidate_count or sort_by == "candidate_count": talent_pipelines_data = [ talent_pipeline.to_dict(include_cached_candidate_count=True, email_campaign_count=email_campaign_count, get_candidate_count=get_talent_pipeline_stat_for_given_day) for talent_pipeline in talent_pipelines] else: talent_pipelines_data = [ talent_pipeline.to_dict(email_campaign_count=email_campaign_count) for talent_pipeline in talent_pipelines] for talent_pipeline_data in talent_pipelines_data: talent_pipeline_data['engagement_score'] = get_pipeline_engagement_score(talent_pipeline_data['id']) if sort_by in ("engagement_score", "candidate_count"): sort_by = 'total_candidates' if sort_by == "candidate_count" else "engagement_score" talent_pipelines_data = sorted( talent_pipelines_data, key=lambda talent_pipeline_data: talent_pipeline_data[sort_by], reverse=(False if sort_type == 'ASC' else True)) talent_pipelines_data = talent_pipelines_data[(page - 1) * per_page:page * per_page] headers = generate_pagination_headers(total_number_of_talent_pipelines, per_page, page) response = dict( talent_pipelines=talent_pipelines_data, page_number=page, talent_pipelines_per_page=per_page, total_number_of_talent_pipelines=total_number_of_talent_pipelines ) return ApiResponse(response=response, headers=headers, status=200) @require_all_permissions(Permission.PermissionNames.CAN_DELETE_TALENT_PIPELINES) def delete(self, **kwargs): """ DELETE /talent-pipelines/<id> Remove talent-pipeline from Database :return A dictionary containing deleted talent-pipeline's id :rtype: dict """ talent_pipeline_id = kwargs.get('id') if not talent_pipeline_id: raise InvalidUsage("A valid talent_pipeline_id should be provided") talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) if request.user.role.name == 'USER' and talent_pipeline.user.id != request.user.id: raise ForbiddenError("Logged-in user doesn't have appropriate permissions to delete this talent-pipeline") if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain") talent_pipeline.is_hidden = 1 for smartlist in Smartlist.query.filter(Smartlist.talent_pipeline_id == talent_pipeline_id): smartlist.is_hidden = 1 db.session.commit() return { 'talent_pipeline': {'id': talent_pipeline_id} } @require_all_permissions(Permission.PermissionNames.CAN_ADD_TALENT_PIPELINES) def post(self, **kwargs): """ POST /talent-pipelines Add new talent-pipelines to Database input: {'talent_pipelines': [talent_pipeline_dict1, talent_pipeline_dict2, talent_pipeline_dict3, ... ]} Take a JSON dictionary containing array of TalentPipeline dictionaries A single talent-pipelines dict must contain pipelines's name, date_needed, talent_pool_id :return A dictionary containing ids of added talent-pipelines :rtype: dict """ posted_data = request.get_json(silent=True) if not posted_data or 'talent_pipelines' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) talent_pipelines = posted_data['talent_pipelines'] # TalentPipeline object(s) must be in a list if not isinstance(talent_pipelines, list): raise InvalidUsage("Request body is not properly formatted") talent_pipeline_objects = [] for talent_pipeline in talent_pipelines: name = talent_pipeline.get('name', '') description = talent_pipeline.get('description', '') positions = talent_pipeline.get('positions', 1) date_needed = talent_pipeline.get('date_needed', '') talent_pool_id = talent_pipeline.get('talent_pool_id', '') search_params = talent_pipeline.get('search_params', dict()) if not name or not date_needed or not talent_pool_id: raise InvalidUsage("A valid name, date_needed, talent_pool_id should be provided to " "create a new talent-pipeline") if TalentPipeline.query.join(TalentPipeline.user).filter(and_( TalentPipeline.name == name, User.domain_id == request.user.domain_id)).first(): raise InvalidUsage( "Talent pipeline with name {} already exists in domain {}".format(name, request.user.domain_id)) try: date_needed = parser.parse(date_needed) except Exception as e: raise InvalidUsage("Date_needed is not valid as: {}".format(e.message)) if not is_number(positions) or not int(positions) > 0: raise InvalidUsage("Number of positions should be integer and greater than zero") if not is_number(talent_pool_id): raise InvalidUsage("talent_pool_id should be an integer") talent_pool_id = int(talent_pool_id) talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError("Talent pool with id {} doesn't exist in database".format(talent_pool_id)) if talent_pool.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and given talent-pool belong to different domain") if search_params: if not isinstance(search_params, dict): raise InvalidUsage("search_params is not provided in valid format") # Put into params dict for key in search_params: if key not in TALENT_PIPELINE_SEARCH_PARAMS and not key.startswith('cf-'): raise NotFoundError("Key[{}] is invalid".format(key)) search_params = json.dumps(search_params) if search_params else None talent_pipeline = TalentPipeline(name=name, description=description, positions=positions, date_needed=date_needed, user_id=request.user.id, talent_pool_id=talent_pool_id, search_params=search_params) db.session.add(talent_pipeline) talent_pipeline_objects.append(talent_pipeline) db.session.commit() return { 'talent_pipelines': [talent_pipeline_object.id for talent_pipeline_object in talent_pipeline_objects] } @require_all_permissions(Permission.PermissionNames.CAN_EDIT_TALENT_PIPELINES) def put(self, **kwargs): """ PUT /talent-pipelines/<id> Edit existing talent-pipeline Take a JSON dictionary containing TalentPipeline dictionary :return A dictionary containing id of edited talent-pipeline :rtype: dict """ talent_pipeline_id = kwargs.get('id') if not talent_pipeline_id: raise InvalidUsage("A valid talent_pipeline_id should be provided") talent_pipeline = TalentPipeline.query.get(talent_pipeline_id) if not talent_pipeline: raise NotFoundError("Talent pipeline with id {} doesn't exist in database".format(talent_pipeline_id)) if request.user.role.name == 'USER' and talent_pipeline.user.id != request.user.id: raise ForbiddenError("Logged-in user doesn't have appropriate permissions to edit this talent-pipeline") if request.user.role.name != 'TALENT_ADMIN' and talent_pipeline.user.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and talent_pipeline belong to different domain") posted_data = request.get_json(silent=True) if not posted_data or 'talent_pipeline' not in posted_data: raise InvalidUsage("Request body is empty or not provided") posted_data = posted_data['talent_pipeline'] name = posted_data.get('name') description = posted_data.get('description', '') positions = posted_data.get('positions', '') date_needed = posted_data.get('date_needed', '') talent_pool_id = posted_data.get('talent_pool_id', '') is_hidden = posted_data.get('is_hidden', 0) search_params = posted_data.get('search_params', dict()) if name is not None: if name: if TalentPipeline.query.join(TalentPipeline.user).filter( and_(TalentPipeline.name == name, User.domain_id == request.user.domain_id)).first(): raise InvalidUsage("Talent pipeline with name {} already exists in domain {}".format( name, request.user.domain_id)) talent_pipeline.name = name else: raise InvalidUsage("Name cannot be an empty string") if date_needed: try: parser.parse(date_needed) except Exception as e: raise InvalidUsage("Date_needed is not valid as: {}".format(e.message)) if parser.parse(date_needed) < datetime.utcnow(): raise InvalidUsage("Date_needed {} cannot be before current date".format(date_needed)) talent_pipeline.date_needed = date_needed if positions: if not is_number(positions) or not int(positions) > 0: raise InvalidUsage("Number of positions should be integer and greater than zero") talent_pipeline.positions = positions if description: talent_pipeline.description = description if talent_pool_id: if not is_number(talent_pool_id): raise InvalidUsage("talent_pool_id should be an integer") talent_pool_id = int(talent_pool_id) talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError("Talent pool with id {} doesn't exist in database".format(talent_pool_id)) if talent_pool.domain_id != request.user.domain_id: raise ForbiddenError("Logged-in user and given talent-pool belong to different domain") talent_pipeline.talent_pool_id = talent_pool_id if search_params: if not isinstance(search_params, dict): raise InvalidUsage("search_params is not provided in valid format") # Put into params dict for key in search_params: if key not in TALENT_PIPELINE_SEARCH_PARAMS and not key.startswith('cf-'): raise NotFoundError("Key[{}] is invalid".format(key)) talent_pipeline.search_params = json.dumps(search_params) if not is_number(is_hidden) or (int(is_hidden) not in (0, 1)): raise InvalidUsage("Possible vaues of `is_hidden` are 0 and 1") talent_pipeline.is_hidden = int(is_hidden) for smartlist in Smartlist.query.filter(Smartlist.talent_pipeline_id == talent_pipeline_id): smartlist.is_hidden = int(is_hidden) db.session.commit() return { 'talent_pipeline': {'id': talent_pipeline.id} }
class TalentPipelinesOfTalentPools(Resource): # Access token decorator decorators = [require_oauth()] # 'SELF' is for readability. It means this endpoint will be accessible to any user @require_all_permissions(Permission.PermissionNames.CAN_GET_TALENT_POOLS) def get(self, **kwargs): """ GET /talent-pools/<id>/talent-pipelines Fetch all talent-pipelines of a Talent-Pool :return A dictionary containing all talent-pipelines of a Talent Pool :rtype: dict """ talent_pool_id = kwargs.get('id') talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError( "Talent pool with id {} doesn't exist in database".format( talent_pool_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id: raise ForbiddenError( "Talent pool and logged in user belong to different domains") talent_pool_group = TalentPoolGroup.query.filter_by( user_group_id=request.user.user_group_id, talent_pool_id=talent_pool_id).first() if request.user.role.name == 'USER' and not talent_pool_group: raise ForbiddenError( "User {} doesn't have appropriate permissions to " "get talent-pipelines".format(request.user.id)) page = request.args.get('page', DEFAULT_PAGE) per_page = request.args.get('per_page', DEFAULT_PAGE_SIZE) if not is_number(page) or not is_number( per_page) or int(page) < 1 or int(per_page) < 1: raise InvalidUsage("page and per_page should be positive integers") page = int(page) per_page = int(per_page) talent_pipelines_query = TalentPipeline.query.filter_by( talent_pool_id=talent_pool_id) talent_pipelines = talent_pipelines_query.paginate( page, per_page, False) talent_pipelines = talent_pipelines.items headers = generate_pagination_headers(talent_pipelines_query.count(), per_page, page) response = { 'talent_pipelines': [ talent_pipeline.to_dict(True, get_stats_generic_function) for talent_pipeline in talent_pipelines ] } return ApiResponse(response=response, headers=headers, status=200)
class TalentPoolCandidateApi(Resource): # Access token decorator decorators = [require_oauth()] @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES) def get(self, **kwargs): """ GET /talent-pools/<id>/candidates Fetch Candidate Statistics of Talent-Pool :return A dictionary containing Candidate Statistics of a Talent Pool :rtype: dict """ talent_pool_id = kwargs.get('id') talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError( "Talent pool with id {} doesn't exist in database".format( talent_pool_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id: raise ForbiddenError( "Talent pool and logged in user belong to different domains") talent_pool_group = TalentPoolGroup.query.filter_by( user_group_id=request.user.user_group_id, talent_pool_id=talent_pool_id).first() if request.user.role.name == 'USER' and not talent_pool_group: raise ForbiddenError( "User {} doesn't have appropriate permissions to get " "candidates".format(request.user.id)) request_params = dict() request_params['facets'] = request.args.get('facets', '') request_params['fields'] = request.args.get('fields', '') request_params['sort_by'] = request.args.get('sort_by', '') request_params['limit'] = request.args.get('limit', '') request_params['page'] = request.args.get('page', '') search_candidates_response = get_candidates_of_talent_pool( talent_pool, request.oauth_token, request_params) # To be backwards-compatible, for now, we add talent_pool_candidates to top level dict search_candidates_response['talent_pool_candidates'] = { 'name': talent_pool.name, 'total_found': search_candidates_response.get('total_found') } return search_candidates_response # 'SELF' is for readability. It means this endpoint will be accessible to any user @require_all_permissions(Permission.PermissionNames.CAN_ADD_CANDIDATES) def post(self, **kwargs): """ POST /talent-pools/<id>/candidates Add input candidates in talent-pool input: {'talent_pool_candidates': [talent_pool_candidate_id1, talent_pool_candidate_id2, ... ]} :return A dictionary containing candidate_ids which have been added successfully :rtype: dict """ talent_pool_id = kwargs.get('id') posted_data = request.get_json(silent=True) if not posted_data or 'talent_pool_candidates' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) talent_pool_candidate_ids = posted_data['talent_pool_candidates'] # Talent_pool object(s) must be in a list if not isinstance(talent_pool_candidate_ids, list): raise InvalidUsage("Request body is not properly formatted") talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError( "Talent pool with id {} doesn't exist in database".format( talent_pool_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id: raise ForbiddenError( "Talent pool and logged-in user belong to different domains") talent_pool_group = TalentPoolGroup.query.filter_by( user_group_id=request.user.user_group_id, talent_pool_id=talent_pool_id).first() if request.user.role.name == 'USER' and not talent_pool_group: raise ForbiddenError( "User {} doesn't have appropriate permissions to add " "candidates".format(request.user.id)) try: talent_pool_candidate_ids = [ int(talent_pool_candidate_id) for talent_pool_candidate_id in talent_pool_candidate_ids ] except: raise InvalidUsage("All candidate ids should be integer") # Candidates which already exist in a given talent-pool already_existing_candidates_in_talent_pool = TalentPoolCandidate.query.filter( and_( TalentPoolCandidate.talent_pool_id == talent_pool_id, TalentPoolCandidate.candidate_id.in_( talent_pool_candidate_ids))).all() # No candidate should already exist in a given talent-pool if len(already_existing_candidates_in_talent_pool) > 0: raise InvalidUsage( "Candidate {} already exists in talent-pool {}".format( already_existing_candidates_in_talent_pool[0].id, talent_pool_id)) # Candidates with input candidate ids exist in database or not for talent_pool_candidate_id in talent_pool_candidate_ids: talent_pool_candidate = Candidate.query.get( talent_pool_candidate_id) if not talent_pool_candidate: raise NotFoundError( "Candidate with id {} doesn't exist in database".format( talent_pool_candidate_id)) if talent_pool_candidate.user.domain_id != talent_pool.domain_id: raise ForbiddenError( "Talent Pool {} and Candidate {} belong to different domain" .format(talent_pool.id, talent_pool_candidate.id)) db.session.add( TalentPoolCandidate(talent_pool_id=talent_pool_id, candidate_id=talent_pool_candidate_id)) db.session.commit() try: # Update Candidate Documents in Amazon Cloud Search headers = { 'Authorization': request.oauth_token, 'Content-Type': 'application/json' } response = requests.post( CandidateApiUrl.CANDIDATES_DOCUMENTS_URI, headers=headers, data=json.dumps({'candidate_ids': talent_pool_candidate_ids})) if response.status_code != 204: raise Exception("Status Code: {} Response: {}".format( response.status_code, response.json())) except Exception as e: raise InvalidUsage( "Couldn't update Candidate Documents in Amazon Cloud " "Search. Because: {}".format(e.message)) return { 'added_talent_pool_candidates': [ int(talent_pool_candidate_id) for talent_pool_candidate_id in talent_pool_candidate_ids ] } @require_all_permissions(Permission.PermissionNames.CAN_DELETE_CANDIDATES) def delete(self, **kwargs): """ DELETE /talent-pools/<id>/candidates Remove input candidates from talent-pool input: {'talent_pool_candidates': [talent_pool_candidate_id1, talent_pool_candidate_id2, ... ]} :return A dictionary containing candidate_ids which have been removed successfully :rtype: dict """ talent_pool_id = kwargs.get('id') posted_data = request.get_json(silent=True) if not posted_data or 'talent_pool_candidates' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) talent_pool_candidate_ids = posted_data['talent_pool_candidates'] # Talent_pool object(s) must be in a list if not isinstance(talent_pool_candidate_ids, list): raise InvalidUsage("Request body is not properly formatted") talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError( "Talent pool with id {} doesn't exist in database".format( talent_pool_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id: raise ForbiddenError( "Talent pool and logged in user belong to different domains") talent_pool_group = TalentPoolGroup.query.filter_by( user_group_id=request.user.user_group_id, talent_pool_id=talent_pool_id).first() if request.user.role.name == 'USER' and not talent_pool_group: raise ForbiddenError( "User {} doesn't have appropriate permissions to " "remove candidates".format(request.user.id)) for talent_pool_candidate_id in talent_pool_candidate_ids: if not is_number(talent_pool_candidate_id): raise InvalidUsage( 'Candidate id {} should be an integer'.format( talent_pool_candidate_id)) else: talent_pool_candidate_id = int(talent_pool_candidate_id) talent_pool_candidate = TalentPoolCandidate.query.filter_by( candidate_id=talent_pool_candidate_id, talent_pool_id=talent_pool_id).first() if not talent_pool_candidate: raise NotFoundError( "Candidate {} doesn't belong to talent-pool {}".format( talent_pool_candidate_id, talent_pool_id)) else: db.session.delete(talent_pool_candidate) db.session.commit() try: # Update Candidate Documents in Amazon Cloud Search headers = { 'Authorization': request.oauth_token, 'Content-Type': 'application/json' } response = requests.post( CandidateApiUrl.CANDIDATES_DOCUMENTS_URI, headers=headers, data=json.dumps({'candidate_ids': talent_pool_candidate_ids})) if response.status_code != 204: raise Exception("Status Code: {} Response: {}".format( response.status_code, response.json())) except Exception as e: raise InvalidUsage( "Couldn't update Candidate Documents in Amazon Cloud Search. Because: {}" .format(e.message)) return { 'talent_pool_candidates': [ int(talent_pool_candidate_id) for talent_pool_candidate_id in talent_pool_candidate_ids ] }
class TalentPoolGroupApi(Resource): # Access token decorator decorators = [require_oauth()] # 'SELF' is for readability. It means this endpoint will be accessible to any user @require_all_permissions(Permission.PermissionNames.CAN_GET_DOMAIN_GROUPS) def get(self, **kwargs): """ GET /groups/<group_id>/talent_pools Fetch all talent-pool objects of given user group :return A dictionary containing all talent-pools of a user group :rtype: dict """ user_group_id = kwargs.get('group_id') user_group = UserGroup.query.get(user_group_id) if not user_group: raise NotFoundError( "User group with id {} doesn't exist".format(user_group_id)) if request.user.role.name != 'TALENT_ADMIN' and user_group.domain_id != request.user.domain_id: raise ForbiddenError( "Logged-in user belongs to different domain as given user group" ) talent_pool_ids = [ talent_pool_group.talent_pool_id for talent_pool_group in TalentPoolGroup.query.filter_by( user_group_id=user_group_id).all() ] talent_pools = TalentPool.query.filter( TalentPool.id.in_(talent_pool_ids)).all() return { 'talent_pools': [{ 'id': talent_pool.id, 'name': talent_pool.name, 'description': talent_pool.description, 'domain_id': talent_pool.domain_id, 'user_id': talent_pool.user_id } for talent_pool in talent_pools] } @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAIN_GROUPS) def delete(self, **kwargs): """ DELETE /groups/<group_id>/talent_pools Remove given input of talent-pool ids from user group input: {'talent_pools': [talent_pool_id1, talent_pool_id2, talent_pool_id3, ... ]} :return A dictionary containing talent-pool ids which have been removed successfully :rtype: dict """ user_group_id = kwargs.get('group_id') posted_data = request.get_json(silent=True) if not posted_data or 'talent_pools' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) talent_pool_ids = posted_data['talent_pools'] # Talent_pool object(s) must be in a list if not isinstance(talent_pool_ids, list): raise InvalidUsage("Request body is not properly formatted") user_group = UserGroup.query.get(user_group_id) if not user_group: raise NotFoundError( "User group with id {} doesn't exist".format(user_group_id)) if request.user.role.name != 'TALENT_ADMIN' and request.user.domain_id != user_group.domain_id: raise ForbiddenError( "Logged-in user and given user-group belong to different domains" ) for talent_pool_id in talent_pool_ids: if not is_number(talent_pool_id): raise InvalidUsage( 'Talent pool id {} should be an integer'.format( talent_pool_id)) else: talent_pool_id = int(talent_pool_id) talent_pool_group = TalentPoolGroup.query.filter_by( user_group_id=user_group_id, talent_pool_id=talent_pool_id).first() if not talent_pool_group: raise NotFoundError( "Talent pool {} doesn't belong to group {}".format( talent_pool_id, user_group_id)) else: db.session.delete(talent_pool_group) db.session.commit() return { 'talent_pools': [int(talent_pool_id) for talent_pool_id in talent_pool_ids] } @require_all_permissions(Permission.PermissionNames.CAN_EDIT_DOMAIN_GROUPS) def post(self, **kwargs): """ POST /groups/<group_id>/talent_pools Add talent-pools to user_group input: {'talent_pools': [talent_pool_id1, talent_pool_id2, talent_pool_id3, ... ]} :return A dictionary containing talent-pool ids which have been added successfully :rtype: dict """ user_group_id = kwargs.get('group_id') posted_data = request.get_json(silent=True) if not posted_data or 'talent_pools' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) talent_pool_ids = posted_data['talent_pools'] # Talent_pool object(s) must be in a list if not isinstance(talent_pool_ids, list): raise InvalidUsage("Request body is not properly formatted") user_group = UserGroup.query.get(user_group_id) if not user_group: raise NotFoundError( "User group with id {} doesn't exist".format(user_group_id)) if request.user.role.name != 'TALENT_ADMIN' and request.user.domain_id != user_group.domain_id: raise ForbiddenError( "Logged-in user and given user-group belong to different domains" ) for talent_pool_id in talent_pool_ids: if not is_number(talent_pool_id): raise InvalidUsage( 'Talent pool id {} should be an integer'.format( talent_pool_id)) else: talent_pool_id = int(talent_pool_id) if TalentPoolGroup.query.filter_by( user_group_id=user_group_id, talent_pool_id=talent_pool_id).first(): raise InvalidUsage( "Talent pool {} already belongs to user group {}".format( talent_pool_id, user_group_id)) talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError( "Talent pool {} doesn't exist in database".format( talent_pool_id)) if user_group.domain_id != talent_pool.domain_id: raise ForbiddenError( "Talent pool {} and user_group {} belong to different " "domain".format(talent_pool.name, user_group.name)) db.session.add( TalentPoolGroup(talent_pool_id=talent_pool_id, user_group_id=user_group_id)) db.session.commit() return { 'added_talent_pools': [int(talent_pool_id) for talent_pool_id in talent_pool_ids] }
class TalentPoolApi(Resource): # Access token decorator decorators = [require_oauth()] # 'SELF' is for readability. It means this endpoint will be accessible to any user @require_all_permissions(Permission.PermissionNames.CAN_GET_TALENT_POOLS) def get(self, **kwargs): """ GET /talent-pools/<id> Fetch talent-pool object GET /talent-pools?domain_id=1 Fetch all talent-pool objects of domain of logged-in user :return A dictionary containing talent-pool basic info or a dictionary containing all talent-pools of a domain :rtype: dict """ talent_pool_id = kwargs.get('id') # Getting a single talent-pool if talent_pool_id: talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError( "Talent pool with id {} doesn't exist in database".format( talent_pool_id)) if request.user.role.name != 'TALENT_ADMIN' and talent_pool.domain_id != request.user.domain_id: raise ForbiddenError( "User {} is not authorized to get talent-pool's info". format(request.user.id)) talent_pool_group = TalentPoolGroup.query.filter_by( user_group_id=request.user.user_group_id, talent_pool_id=talent_pool_id).all() if request.user.role.name == 'USER' and not talent_pool_group: raise ForbiddenError( "User {} doesn't have appropriate permissions to get " "talent-pools's info".format(request.user.id)) return { 'talent_pool': { 'id': talent_pool.id, 'name': talent_pool.name, 'description': talent_pool.description, 'domain_id': talent_pool.domain_id, 'user_id': talent_pool.user_id, 'added_time': talent_pool.added_time.isoformat(), 'updated_time': talent_pool.updated_time.isoformat() } } # Getting all talent-pools of logged-in user's domain else: domain_id = request.user.domain_id # Get all Talent Pools of any domain if user is `TALENT_ADMIN` if request.user.role.name == 'TALENT_ADMIN' and request.args.get( 'domain_id'): domain_id = request.args.get('domain_id') if not is_number(domain_id) or not Domain.query.get( int(domain_id)): raise InvalidUsage("Invalid Domain Id is provided") talent_pools = TalentPool.query.filter_by( domain_id=int(domain_id)).all() return { 'talent_pools': [{ 'id': talent_pool.id, 'name': talent_pool.name, 'description': talent_pool.description, 'user_id': talent_pool.user_id, 'added_time': talent_pool.added_time.isoformat(), 'updated_time': talent_pool.updated_time.isoformat(), 'accessible_to_user_group_ids': [ talent_pool_group.user_group_id for talent_pool_group in TalentPoolGroup.query. filter_by(talent_pool_id=talent_pool.id).all() ] } for talent_pool in talent_pools] } @require_all_permissions(Permission.PermissionNames.CAN_EDIT_TALENT_POOLS) def put(self, **kwargs): """ PUT /talent-pools/<id> Modify an already existing talent-pool input: {'name': 'facebook-recruiting', 'description': ''} :return {'talent_pool': {'id': talent_pool_id}} :rtype: dict """ talent_pool_id = kwargs.get('id') if not talent_pool_id: raise InvalidUsage("A valid talent_pool_id should be provided") talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError( "Talent pool with id {} doesn't exist in database".format( talent_pool_id)) posted_data = request.get_json(silent=True) if not posted_data or 'talent_pool' not in posted_data: raise InvalidUsage("Request body is empty or not provided") posted_data = posted_data['talent_pool'] # posted_data must be in a dict if not isinstance(posted_data, dict): raise InvalidUsage("Request body is not properly formatted") if request.user.role.name != 'TALENT_ADMIN' and request.user.domain_id != talent_pool.domain_id: raise ForbiddenError( "User {} is not authorized to edit talent-pool's info".format( request.user.id)) name = posted_data.get('name') description = posted_data.get('description') if not name and not description: raise InvalidUsage( "Neither modified name nor description is provided") if name and not TalentPool.query.filter_by( name=name, domain_id=talent_pool.domain_id).all(): talent_pool.name = name elif name: raise InvalidUsage( "Talent pool {} already exists in domain {}".format( name, talent_pool.domain_id)) if description: talent_pool.description = description db.session.commit() return {'talent_pool': {'id': talent_pool.id}} @require_all_permissions(Permission.PermissionNames.CAN_DELETE_TALENT_POOLS ) def delete(self, **kwargs): """ DELETE /talent-pools/<id> Delete an already existing talent-pool :return {'delete_talent_pool': {'id': talent_pool_id}} :rtype: dict """ talent_pool_id = kwargs.get('id') if not talent_pool_id: raise InvalidUsage("A valid talent_pool_id should be provided") talent_pool = TalentPool.query.get(talent_pool_id) if not talent_pool: raise NotFoundError( "Talent pool with id {} doesn't exist in database".format( talent_pool_id)) if request.user.role.name != 'TALENT_ADMIN' and request.user.domain_id != talent_pool.domain_id: raise ForbiddenError( "User {} is not authorized to delete a talent-pool".format( request.user.id)) talent_pool_candidate_ids = map( lambda talent_pool_candidate: talent_pool_candidate[0], TalentPoolCandidate.query.with_entities( TalentPoolCandidate.candidate_id).filter_by( talent_pool_id=talent_pool_id).all()) talent_pool.delete() if talent_pool_candidate_ids: try: # Update Candidate Documents in Amazon Cloud Search headers = { 'Authorization': request.oauth_token, 'Content-Type': 'application/json' } response = requests.post( CandidateApiUrl.CANDIDATES_DOCUMENTS_URI, headers=headers, data=json.dumps( {'candidate_ids': talent_pool_candidate_ids})) if response.status_code != 204: raise Exception("Status Code: {} Response: {}".format( response.status_code, response.json())) except Exception as e: raise InvalidUsage( "Couldn't update Candidate Documents in Amazon Cloud " "Search. Because: {}".format(e.message)) return {'talent_pool': {'id': talent_pool.id}} @require_all_permissions(Permission.PermissionNames.CAN_ADD_TALENT_POOLS) def post(self, **kwargs): """ POST /talent-pools Create new empty talent pools input: {'talent_pools': [talent_pool_dict1, talent_pool_dict2, talent_pool_dict3, ... ]} Take a JSON dictionary containing array of TalentPool dictionaries A single talent-pool dict must contain pool's name :return A dictionary containing talent_pools_ids :rtype: dict """ posted_data = request.get_json(silent=True) if not posted_data or 'talent_pools' not in posted_data: raise InvalidUsage("Request body is empty or not provided") # Save user object(s) talent_pools = posted_data['talent_pools'] # Talent_pool object(s) must be in a list if not isinstance(talent_pools, list): raise InvalidUsage("Request body is not properly formatted") talent_pool_objects = [] for talent_pool in talent_pools: name = talent_pool.get('name', '').strip() description = talent_pool.get('description', '').strip() user_object = User.query.get( talent_pool.get('user_id', request.user.id)) if not user_object: raise InvalidUsage( "User with id {} doesn't exist in Database".format( user_object.id)) if request.user.role.name != 'TALENT_ADMIN' and user_object.domain_id != request.user.domain_id: raise UnauthorizedError( "User {} doesn't have appropriate permission to " "add TalentPool".format(request.user.id)) if not name: raise InvalidUsage( "A valid name should be provided to create a talent-pool") if name and TalentPool.query.filter_by( name=name, domain_id=user_object.domain_id).all(): raise InvalidUsage( "Talent pool '{}' already exists in domain {}".format( name, user_object.domain_id)) # Add TalentPool talent_pool_object = TalentPool(name=name, description=description, domain_id=user_object.domain_id, user_id=user_object.id) talent_pool_objects.append(talent_pool_object) db.session.add(talent_pool_object) db.session.flush() # Add TalentPoolGroup to associate new TalentPool with existing UserGroup db.session.add( TalentPoolGroup(talent_pool_id=talent_pool_object.id, user_group_id=user_object.user_group_id)) db.session.commit() for talent_pool in talent_pool_objects: hash = hashlib.md5() hash.update(str(talent_pool.id)) talent_pool.simple_hash = hash.hexdigest()[:8] pool_saved = False while not pool_saved: try: db.session.add(talent_pool) db.session.commit() pool_saved = True except IntegrityError: hash.update(random_word(8)) return { 'talent_pools': [ talent_pool_object.id for talent_pool_object in talent_pool_objects ] }