Exemple #1
0
    def merge_degrees(self, existing_degrees, new_degrees):
        """
        This method compares degrees of existing education with degrees of new education
        EducationDegree objects looks like this
        {
            'start_month': 11,
            'start_year': 2002,
            'end_year': 2006,
            'degree_type': u'ms',
            'end_month': 12,
            'end_time': datetime.datetime(2006, 12, 1, 0, 0),
            'degree_title': u'M.Sc',
            'gpa_num': 1.5
        }

        We have list of education degrees dict (GtDict) objects that end-user want to add or update in existing
        degrees of a specific candidate. We will loop over new degrees and will compare each new degree object with all
        existing candidate's degree objects and if it matches, we will update existing degree object with new degree
        object (dict) data and will append a tuple (new_degree, existing degree) in degrees list and if it will not
        match, we will append tuple (new_degree, None) in degrees list.
        """
        degrees = []
        for new_degree in new_degrees or []:
            is_same_degree = False
            for existing_degree_obj in existing_degrees or []:
                start_year = new_degree.start_year
                end_year = new_degree.end_year
                # If start year needs to be updated, it cannot be greater than existing end year
                if start_year and not end_year and (
                        start_year > existing_degree_obj.end_year):
                    raise InvalidUsage(
                        'Start year ({}) cannot be greater than end year ({})'.
                        format(start_year, existing_degree_obj.end_year))

                # If end year needs to be updated, it cannot be less than existing start year
                if end_year and not start_year and (
                        end_year < existing_degree_obj.start_year):
                    raise InvalidUsage(
                        'End year ({}) cannot be less than start year ({})'.
                        format(end_year, existing_degree_obj.start_year))

                if existing_degree_obj == new_degree:
                    is_same_degree = True
                    degrees.append((new_degree, existing_degree_obj))
                    track_edits(update_dict=new_degree,
                                table_name='candidate_education_degree',
                                candidate_id=self.existing_candidate.id,
                                user_id=self.existing_candidate.user_id,
                                query_obj=existing_degree_obj)
                    existing_degree_obj.update(**new_degree)
                    break
            if not is_same_degree:
                degrees.append((new_degree, None))
        return degrees
Exemple #2
0
def get_json_if_exist(_request):
    """ Function will ensure data's content-type is JSON, and it isn't empty
    :type _request:  request
    """
    if "application/json" not in _request.content_type:
        raise InvalidUsage("Request body must be a JSON object",
                           custom_error.INVALID_INPUT)
    if not _request.get_data():
        raise InvalidUsage("Request body cannot be empty",
                           custom_error.MISSING_INPUT)
    return _request.get_json()
Exemple #3
0
def validate_id_list(key, values):
    if ',' in values or isinstance(values, list):
        values = values.split(',') if ',' in values else map(str, values)
        for value in values:
            if not value.strip().isdigit():
                raise InvalidUsage("`%s` must be comma separated ids" % key)
        # if multiple values then return as list else single value.
        return values[0] if values.__len__() == 1 else values
    else:
        if not values.strip().isdigit():
            raise InvalidUsage("`%s` must be comma separated ids()" % key)
        return values.strip()
Exemple #4
0
    def patch(self, **kwargs):
        """
        Function will update candidate's tag(s)
        Endpoints:
             i. PATCH /v1/candidates/:candidate_id/tags
            ii. PATCH /v1/candidates/:candidate_id/tags/:id
        Example:
            >>> url = 'host/v1/candidates/4/tags' or 'host/v1/candidates/4/tags/57'
            >>> headers = {'Authorization': 'Bearer edo9rdSKN8hYuc1zBWMfLXpXFd4ZbE'}
            >>> data = {"tags": [{"description": "minority"}, {"description": "remote"}]}
            >>> requests.patch(url=url, headers=headers, data=json.dumps(data))
        """
        # Get json data if exists and validate its schema
        body_dict = get_json_data_if_validated(request, tag_schema, False)

        # Description is a required field (must not be empty)
        tags, tag = body_dict.get('tags'), body_dict.get('tag')
        for tag in tags:
            tag['name'] = tag['name'].strip().lower(
            )  # remove whitespaces while validating
            if not tag['name']:
                raise InvalidUsage('Tag name is a required field',
                                   custom_error.MISSING_INPUT)

        # Authenticated user, candidate ID, and tag ID
        authed_user, candidate_id, tag_id = request.user, kwargs[
            'candidate_id'], kwargs.get('id')

        # If tag_id is provided in the url, it is assumed that only one candidate-tag needs to be updated
        if tag_id and len(tags) > 1:
            raise InvalidUsage(
                "Updating multiple Tags via this resource is not permitted.",
                custom_error.INVALID_USAGE)

        # Check for candidate's existence and web-hidden status
        get_candidate_if_validated(authed_user, candidate_id)

        # If tag_id is provided in the url, update only one record
        if tag_id:
            return {
                'updated_tag':
                update_candidate_tag(candidate_id, tag_id, tag['name'].strip())
            }

        # Update tag(s)
        updated_tag_ids = update_candidate_tags(candidate_id=candidate_id,
                                                tags=tags)

        # Update cloud search
        upload_candidate_documents.delay([candidate_id])

        return {'updated_tags': [{'id': tag_id} for tag_id in updated_tag_ids]}
Exemple #5
0
def _add_reference(candidate_id, reference_dict):
    """
    Function will insert a record in CandidateReference.
    Function will check db to prevent adding duplicate records.
    :rtype:  int
    """
    reference_name = reference_dict.get('person_name')

    duplicate_reference_note = CandidateReference.query.filter_by(
        candidate_id=candidate_id,
        person_name=reference_name,
        comments=reference_dict.get('comments')).first()
    if not duplicate_reference_note:
        candidate_reference = CandidateReference(**reference_dict)
        db.session.add(candidate_reference)
        db.session.flush()
        return candidate_reference.id
    else:
        raise InvalidUsage(
            error_message="Reference already exists for candidate",
            error_code=custom_error.REFERENCE_EXISTS,
            additional_error_info={
                'reference_id': duplicate_reference_note.id,
                'candidate_id': candidate_id
            })
Exemple #6
0
def update_candidate_tag(candidate_id, tag_id, tag_name):
    """
    Function will update candidate's tag
    :type candidate_id: int | long
    :type tag_id:       int | long
    :type tag_name      str
    :param tag_name     name of the tag, e.g. 'diligent', 'minority'
    :rtype  dict[int|long]
    """
    tag_obj_from_id = Tag.get(tag_id)
    if not tag_obj_from_id:
        raise NotFoundError("Tag ID: {} is not recognized".format(tag_id),
                            custom_error.TAG_NOT_FOUND)

    # Candidate must already be associated with provided tag_id
    candidate_tag_query = CandidateTag.query.filter_by(
        candidate_id=candidate_id, tag_id=tag_id)
    candidate_tag_object = candidate_tag_query.first()
    if not candidate_tag_object:
        raise InvalidUsage(
            'Candidate (id = {}) is not associated with Tag (id = {})'.format(
                candidate_id, tag_id), custom_error.INVALID_USAGE)

    # If Tag is not found, create it
    tag_object = Tag.get_by_name(tag_name)
    if not tag_object:
        tag_object = Tag(name=tag_name)
        db.session.add(tag_object)
        db.session.flush()

    # Update
    candidate_tag_query.update(
        dict(candidate_id=candidate_id, tag_id=tag_object.id))
    db.session.commit()
    return {'id': tag_object.id}
Exemple #7
0
def validate_encoded_json(value):
    """ This function will validate and decodes a encoded json string """
    try:
        return json.loads(value)
    except Exception as e:
        raise InvalidUsage(
            error_message="Encoded JSON %s couldn't be decoded because: %s" %
            (value, e.message))
Exemple #8
0
    def get(self):
        """
        Search candidates based on the given filter criteria
        """
        # Authenticated user
        authed_user = request.user

        body_dict = request.get_json(silent=True)
        if body_dict:  # In case req-body is empty
            try:
                validate(instance=body_dict,
                         schema=candidates_resource_schema_get)
            except ValidationError as e:
                raise InvalidUsage(error_message=e.message,
                                   error_code=custom_error.INVALID_INPUT)

            candidate_ids = body_dict.get('candidate_ids')

            # Candidate IDs must belong to user's domain
            if not do_candidates_belong_to_users_domain(
                    authed_user, candidate_ids):
                raise ForbiddenError('Not authorized',
                                     custom_error.CANDIDATE_FORBIDDEN)

            retrieved_candidates = []
            for candidate_id in candidate_ids:
                # Check for candidate's existence and web-hidden status
                candidate = get_candidate_if_exists(candidate_id)
                retrieved_candidates.append(
                    fetch_candidate_info(candidate=candidate))

            return {'candidates': retrieved_candidates}

        else:
            request_vars = validate_and_format_data(request.args)

            if 'smartlist_ids' in request_vars:
                request_vars['search_params_list'] = []
                smartlist_search_params_list = get_search_params_of_smartlists(
                    request_vars.get('smartlist_ids'))
                for search_params in smartlist_search_params_list:
                    request_vars['search_params_list'].append(
                        validate_and_format_data(search_params))

            # Get domain_id from auth_user
            domain_id = request.user.domain_id
            limit = request_vars.get('limit')
            search_limit = int(limit) if limit else 15
            count_only = True if 'count_only' in request.args.get(
                'fields', '') else False
            facets = request.args.get('facets', '')

            # If limit is not requested then the Search limit would be taken as 15, the default value
            candidate_search_results = search_candidates(
                domain_id, request_vars, facets, search_limit, count_only)

            return candidate_search_results
Exemple #9
0
def validate_sort_by(key, value):
    # If sorting is present, modify it according to cloudsearch sorting variables.
    try:
        sort_by = SORTING_FIELDS_AND_CORRESPONDING_VALUES_IN_CLOUDSEARCH[value]
    except KeyError:
        raise InvalidUsage(
            error_message="sort_by `%s` is not correct input for sorting." %
            value,
            error_code=400)
    return sort_by
Exemple #10
0
def update_candidate_tags(candidate_id, tags):
    """
    Function will update candidate's tag(s)
    :type candidate_id:  int | long
    :type tags:          list[dict]
    :rtype: list[int|long]
    """
    created_tag_ids, updated_tag_ids = [], []
    for tag in tags:
        tag_name, tag_id = tag['name'], tag.get('id')
        tag_object = Tag.get_by_name(tag_name)

        # If Tag is not found, create it
        if not tag_object:
            tag_object = Tag(name=tag_name)
            db.session.add(tag_object)
            db.session.flush()

        # Data for updating candidate's tag
        update_dict = dict(candidate_id=candidate_id, tag_id=tag_object.id)

        if not tag_id:
            raise InvalidUsage('Tag ID is required for updating',
                               custom_error.INVALID_USAGE)

        tag_obj_from_id = Tag.get(tag_id)
        if not tag_obj_from_id:
            raise NotFoundError("Tag ID: {} is not recognized".format(tag_id),
                                custom_error.TAG_NOT_FOUND)

        # Candidate must already be associated with provided tag_id
        candidate_tag_query = CandidateTag.query.filter_by(
            candidate_id=candidate_id, tag_id=tag_id)
        if not candidate_tag_query.first():
            raise InvalidUsage(
                'Candidate (id = {}) is not associated with Tag (id = {})'.
                format(candidate_id, tag_id), custom_error.INVALID_USAGE)
        # Update
        candidate_tag_query.update(update_dict)
        updated_tag_ids.append(tag_object.id)

    db.session.commit()
    return updated_tag_ids
Exemple #11
0
def update_reference_phone(reference_id, reference_phone_dict):
    """
    Function will update Reference Phone
    """
    reference_phone_query = ReferencePhone.query.filter_by(
        reference_id=reference_id)
    if not reference_phone_query.first():
        raise InvalidUsage("Unable to update. Reference phone does not exist.")

    reference_phone_dict.update(reference_id=reference_id)
    reference_phone_query.update(reference_phone_dict)
    return
Exemple #12
0
    def post(self):
        """
        Upload Candidate Documents to Amazon Cloud Search
        """

        requested_data = request.get_json(silent=True)
        if not requested_data or 'candidate_ids' not in requested_data:
            raise InvalidUsage(
                error_message="Request body is empty or invalid")

        upload_candidate_documents.delay(requested_data.get('candidate_ids'))

        return '', 204
Exemple #13
0
def validate_fields(key, value):
    # If `fields` are present, validate and modify `fields` values according to cloudsearch supported return field names.
    fields = [field.strip() for field in value.split(',') if field.strip()]
    try:
        fields = ','.join([
            RETURN_FIELDS_AND_CORRESPONDING_VALUES_IN_CLOUDSEARCH[field]
            for field in fields
        ])
    except KeyError:
        raise InvalidUsage(
            error_message="Field name `%s` is not correct `return field` name"
            % fields,
            error_code=400)
    return fields
Exemple #14
0
    def delete(self):
        """
        Delete Candidate Documents from Amazon Cloud Search
        """

        requested_data = request.get_json(silent=True)
        if not requested_data or 'candidate_ids' not in requested_data:
            raise InvalidUsage(
                error_message="Request body is empty or invalid")

        delete_candidate_documents(
            candidate_ids=requested_data.get('candidate_ids'))

        return '', 204
Exemple #15
0
def update_reference_email(reference_id, reference_email_dict):
    """
    Function will update reference-email's info
    """
    # Reference Email must already exist
    reference_email_query = ReferenceEmail.query.filter_by(
        reference_id=reference_id)
    if not reference_email_query.first():
        raise InvalidUsage("Unable to update. Reference email does not exist.",
                           custom_error.REFERENCE_NOT_FOUND)

    reference_email_dict.update(reference_id=reference_id)
    reference_email_query.update(reference_email_dict)
    return
Exemple #16
0
def update_reference_web_address(reference_id, reference_web_address_dict):
    """
    Function will update Reference Web Address
    """
    # ReferenceWebAddress must already exist
    reference_web_query = ReferenceWebAddress.query.filter_by(
        reference_id=reference_id)
    if not reference_web_query.first():
        raise InvalidUsage(
            "Unable to update. Reference web address does not exist.")

    reference_web_address_dict.update(reference_id=reference_id)
    reference_web_query.update(reference_web_address_dict)
    return
Exemple #17
0
def get_json_data_if_validated(request_body, json_schema, format_checker=True):
    """
    Function will compare requested json data with provided json schema
    :type request_body:  request
    :type json_schema:  dict
    :param format_checker:  If True, specified formats will need to be validated, e.g. datetime
    :return:  JSON data if validation passes
    """
    try:
        body_dict = get_json_if_exist(request_body)
        if format_checker:
            validate(instance=body_dict,
                     schema=json_schema,
                     format_checker=FormatChecker())
        else:
            validate(instance=body_dict, schema=json_schema)
    except ValidationError as e:
        raise InvalidUsage('JSON schema validation error: {}'.format(e),
                           custom_error.INVALID_INPUT)
    return body_dict
Exemple #18
0
def update_reference(candidate_id, reference_id, reference_dict):
    """
    Function will validate and update Candidate Reference
    """
    candidate_reference_query = CandidateReference.query.filter_by(
        id=reference_id)
    candidate_reference_obj = candidate_reference_query.first()

    # Reference ID must be recognized
    if not candidate_reference_obj:
        raise InvalidUsage(
            "Reference ID ({}) not recognized".format(reference_id))

    # CandidateReference must belong to specified candidate
    if candidate_reference_obj.candidate_id != candidate_id:
        raise ForbiddenError(
            "Reference (id={}) does not belong to candidate (id={})".format(
                reference_id, candidate_id))

    candidate_reference_query.update(reference_dict)
    return
Exemple #19
0
    def post(self, **kwargs):
        """
        Function will create tags
        Note: "description" is a required field
        Endpoint:  POST /v1/candidates/:candidate_id/tags
        Docs: http://docs.candidatetags.apiary.io/#reference/tags/tags-collections-resource/create-tags
        Example:
            >>> url = 'host/v1/candidates/4/tags'
            >>> headers = {'Authorization': 'Bearer edo9rdSKN8hYuc1zBWMfLXpXFd4ZbE'}
            >>> data = {"tags": [{"description": "python"}]}
            >>> requests.post(url=url, headers=headers, data=json.dumps(data))
        :return:  {'tags': [{'id': int}, {'id': int}, ...]}
        """
        # Get json data if exists and validate its schema
        body_dict = get_json_data_if_validated(request, tag_schema, False)

        # Description is a required field (must not be empty)
        for tag in body_dict['tags']:
            tag['name'] = tag['name'].strip().lower(
            )  # remove whitespaces while validating
            if not tag['name']:
                raise InvalidUsage('Tag name is a required field',
                                   custom_error.MISSING_INPUT)

        # Authenticated user & candidate ID
        authed_user, candidate_id = request.user, kwargs['candidate_id']

        # Check for candidate's existence and web-hidden status
        get_candidate_if_validated(authed_user, candidate_id)

        # Create tags
        created_tag_ids = create_tags(candidate_id=candidate_id,
                                      tags=body_dict['tags'])

        # Update cloud search
        upload_candidate_documents.delay([candidate_id])

        return {'tags': [{'id': tag_id} for tag_id in created_tag_ids]}, 201
Exemple #20
0
def convert_date(key, value):
    """
    Convert the given date into cloudsearch's desired format and return.
    Raise error if input date string is not matching the "MM/DD/YYYY" format.
    """
    if value:
        try:
            formatted_date = parse(value)
            # If only date is given without any time (21-06-2016 or 21/06/2016)
            if re.match(r'\d+[-/]\d+[-/]\d+$', value):
                if key == "date_from":
                    formatted_date = formatted_date.replace(hour=0,
                                                            minute=0,
                                                            second=0)
                else:
                    formatted_date = formatted_date.replace(hour=23,
                                                            minute=59,
                                                            second=59)
        except ValueError:
            raise InvalidUsage(
                "Field `%s` contains incorrect date format. "
                "Date format should be MM/DD/YYYY (eg. 06/10/2016)" % key)
        return formatted_date.isoformat(
        ) + 'Z'  # format it as required by cloudsearch.
Exemple #21
0
def add_notes(candidate_id, user_id, data):
    """
    Function will insert candidate notes into the db and return their IDs
    Notes must have a comment.
    :type candidate_id:  int|long
    :type data:  list[dict]
    :rtype:  list[int]
    """
    created_note_ids = []

    for note in data:

        # Normalize value
        title = normalize_value(note.get('title')) if note.get('title') else None
        comments = normalize_value(note.get('comment'))

        # Notes must have a comment
        if not comments:
            raise InvalidUsage('Note must have a comment', custom_error.INVALID_USAGE)

        notes_dict = dict(
            candidate_id=candidate_id,
            owner_user_id=user_id,
            title=title,
            comment=comments,
            added_time=datetime.utcnow()
        )

        notes_dict = dict((k, v) for k, v in notes_dict.iteritems() if v is not None)
        note_obj = CandidateTextComment(**notes_dict)
        db.session.add(note_obj)
        db.session.flush()
        created_note_ids.append(note_obj.id)

    db.session.commit()
    return created_note_ids
Exemple #22
0
def create_or_update_references(candidate_id,
                                references,
                                is_creating=False,
                                is_updating=False,
                                reference_id_from_url=None):
    """
    Function will insert candidate's references' information into db.
    References' information must include: comments.
    References' information may include: reference-email dict & reference-phone dict.
    Empty data will not be added to db
    Duplicate records will not be added to db
    :type candidate_id: int|long
    :type references:  list[dict]
    :type is_creating: bool
    :type is_updating: bool
    :type reference_id_from_url: int | long
    :rtype:  list[int]
    """
    created_or_updated_reference_ids = []
    for reference in references:

        candidate_reference_dict = dict(
            person_name=reference.get('name'),
            position_title=reference.get('position_title'),
            comments=reference.get('comments'))

        # Strip each value & remove keys with empty values
        candidate_reference_dict = purge_dict(candidate_reference_dict)

        # Prevent inserting empty records in db
        if not candidate_reference_dict:
            continue

        reference_id = reference_id_from_url or reference.get('id')
        if not reference_id and is_updating:
            raise InvalidUsage("Reference ID is required for updating",
                               custom_error.INVALID_USAGE)

        candidate_reference_dict.update(resume_id=candidate_id,
                                        candidate_id=candidate_id)

        if is_creating:  # Add
            reference_id = _add_reference(candidate_id,
                                          candidate_reference_dict)
        elif is_updating:  # Update
            update_reference(candidate_id, reference_id,
                             candidate_reference_dict)

        reference_email = reference.get('reference_email')
        reference_phone = reference.get('reference_phone')
        reference_web_address = reference.get('reference_web_address')

        if reference_email:  # add reference's email info
            default_label = EmailLabel.PRIMARY_DESCRIPTION
            email_label = default_label if not reference_email.get(
                'label') else reference_email['label'].strip().title()
            value = reference_email['address'].strip() if reference_email.get(
                'address') else None
            reference_email_dict = dict(
                email_label_id=EmailLabel.email_label_id_from_email_label(
                    email_label) if value else None,
                is_default=reference_email.get('is_default') or True
                if value else None,
                value=value)
            # Remove keys with empty values
            reference_email_dict = purge_dict(reference_email_dict,
                                              strip=False)

            if reference_email_dict and is_creating:  # Add
                add_reference_email(reference_id, reference_email_dict)
            elif reference_email_dict and is_updating:  # Update
                update_reference_email(reference_id, reference_email_dict)

        if reference_phone:  # add reference's phone info if provided
            default_label = PhoneLabel.DEFAULT_LABEL
            phone_label = default_label if not reference_phone.get(
                'label') else reference_phone['label'].strip().title()
            value = reference_phone['value'].strip() if reference_phone.get(
                'value') else None
            phone_number_dict = format_phone_number(value) if value else None
            reference_phone_dict = dict(
                phone_label_id=PhoneLabel.phone_label_id_from_phone_label(
                    phone_label) if phone_number_dict else None,
                is_default=reference_phone.get('is_default') or True
                if phone_number_dict else None,
                value=phone_number_dict.get('formatted_number')
                if phone_number_dict else None,
                extension=phone_number_dict.get('extension')
                if phone_number_dict else None)
            # Remove keys with empty values
            reference_phone_dict = purge_dict(reference_phone_dict,
                                              strip=False)

            if reference_phone_dict and is_creating:  # Add
                add_reference_phone(reference_id, reference_phone_dict)
            elif reference_phone_dict and is_updating:  # Update
                update_reference_phone(reference_id, reference_phone_dict)

        if reference_web_address:
            reference_web_address_dict = dict(
                url=reference_web_address.get('url'),
                description=reference_web_address.get('description'),
            )
            # Remove keys with empty values & strip each value
            reference_web_address_dict = purge_dict(reference_web_address_dict)

            if reference_web_address_dict and is_creating:  # Add
                add_reference_web_address(reference_id,
                                          reference_web_address_dict)
            elif reference_web_address_dict and is_updating:  # Update
                update_reference_web_address(reference_id,
                                             reference_web_address_dict)

        db.session.commit()  # Commit transactions to db
        created_or_updated_reference_ids.append(reference_id)

    return created_or_updated_reference_ids
Exemple #23
0
    def get(self, **kwargs):
        """
        Function will return Pipelines for which given candidate is part of.
        :rtype:  dict[list[dict]]
        Usage:
            >>> requests.get('host/v1/candidates/:candidate_id/pipelines')
            <Response [200]>
        """
        # Authenticated user & candidate ID
        authed_user, candidate_id = request.user, kwargs['candidate_id']

        # Ensure candidate exists and belongs to user's domain
        get_candidate_if_validated(user=authed_user, candidate_id=candidate_id)

        # Maximum number of Talent Pipeline objects used for searching.
        # This is to prevent client from waiting too long for a response
        max_requests = request.args.get('max_requests', 30)

        is_hidden = request.args.get('is_hidden', 0)
        if not is_number(is_hidden) or int(is_hidden) not in (0, 1):
            raise InvalidUsage('`is_hidden` can be either 0 or 1')

        # Candidate's talent pool ID
        candidate_talent_pool_ids = [
            tp.talent_pool_id for tp in TalentPoolCandidate.query.filter_by(
                candidate_id=candidate_id).all()
        ]

        added_pipelines = TalentPipelineIncludedCandidates.query.filter_by(
            candidate_id=candidate_id).all()
        added_pipelines = map(lambda x: x.talent_pipeline, added_pipelines)
        logger.info('added_pipelines are:{}. candidate_id:{}'.format(
            len(added_pipelines), candidate_id))

        removed_pipeline_ids = map(
            lambda x: x[0],
            TalentPipelineExcludedCandidates.query.with_entities(
                TalentPipelineExcludedCandidates.talent_pipeline_id).filter_by(
                    candidate_id=candidate_id).all())

        # Get User-domain's 30 most recent talent pipelines in order of added time
        talent_pipelines = TalentPipeline.query.join(User).filter(
            TalentPipeline.is_hidden == is_hidden,
            TalentPipeline.talent_pool_id.in_(candidate_talent_pool_ids),
            TalentPipeline.id.notin_(removed_pipeline_ids)).order_by(
                TalentPipeline.added_time.desc()).limit(max_requests).all()

        logger.info(
            'Going for CS for {} talent_pipelines for candidate_id:{}'.format(
                len(talent_pipelines), candidate_id))

        # Use Search API to retrieve candidate's domain-pipeline inclusion
        found_talent_pipelines = []
        futures = []

        for talent_pipeline in talent_pipelines:
            search_params = talent_pipeline.search_params
            if search_params:
                search_future = search_candidates_from_params(
                    search_params=format_search_params(
                        talent_pipeline.search_params),
                    access_token=request.oauth_token,
                    url_args='?id={}&talent_pool_id={}'.format(
                        candidate_id, talent_pipeline.talent_pool_id),
                    facets='none')
                search_future.talent_pipeline = talent_pipeline
                search_future.search_params = search_params
                futures.append(search_future)
        # Wait for all the futures to complete
        completed_futures = wait(futures)
        for completed_future in completed_futures[0]:
            if completed_future._result.ok:
                search_response = completed_future._result.json()
                if search_response.get('candidates'):
                    found_talent_pipelines.append(
                        completed_future.talent_pipeline)
                logger.info(
                    "\ncandidate_id: {}\ntalent_pipeline_id: {}\nsearch_params: {}\nsearch_response: {}"
                    .format(candidate_id, completed_future.talent_pipeline.id,
                            completed_future.search_params, search_response))
            else:
                logger.error(
                    "Couldn't get candidates from Search API because %s" %
                    completed_future._result.text)

        result = []

        found_talent_pipelines += added_pipelines
        found_talent_pipelines = list(set(found_talent_pipelines))

        found_talent_pipelines = sorted(
            found_talent_pipelines,
            key=lambda talent_pipeline: talent_pipeline.added_time,
            reverse=True)

        logger.info("\nFound {} talent_pipelines:{}".format(
            len(found_talent_pipelines), found_talent_pipelines))

        if found_talent_pipelines:
            pipeline_engagements = top_most_engaged_pipelines_of_candidate(
                candidate_id)
            for talent_pipeline in found_talent_pipelines:
                result.append({
                    "id":
                    talent_pipeline.id,
                    "candidate_id":
                    candidate_id,
                    "name":
                    talent_pipeline.name,
                    "description":
                    talent_pipeline.description,
                    "open_positions":
                    talent_pipeline.positions,
                    "pipeline_engagement":
                    pipeline_engagements.get(int(talent_pipeline.id), None),
                    "datetime_needed":
                    str(talent_pipeline.date_needed),
                    'is_hidden':
                    talent_pipeline.is_hidden,
                    "user_id":
                    talent_pipeline.user_id,
                    "added_datetime":
                    str(talent_pipeline.added_time)
                })

        return {'candidate_pipelines': result}
Exemple #24
0
def validate_is_digit(key, value):
    if not value.isdigit():
        raise InvalidUsage("`%s` should be a whole number" % key, 400)
    return value
Exemple #25
0
def validate_is_number(key, value):
    if not is_number(value):
        raise InvalidUsage("`%s` should be a numeric value" % key, 400)
    return value
Exemple #26
0
    def post(self, **kwargs):
        """
        Endpoints:  POST /v1/candidates/:candidate_id/custom_fields
        Usage:
            >>> headers = {"Authorization": "Bearer access_token", "content-type": "application/json"}
            >>> data = {"candidate_custom_fields": [{"custom_field_id": 547, "value": "scripted"}]}
            >>> requests.post(url="hots/v1/candidates/4/custom_fields", headers=headers, data=json.dumps(data))
            <Response [201]>
        :return  {'candidate_custom_fields': [{'id': int}, {'id': int}, ...]}
        """
        # Validate data
        body_dict = get_json_data_if_validated(request, ccf_schema)

        # Get authenticated user and candidate ID
        authed_user, candidate_id = request.user, kwargs['candidate_id']

        # Candidate must exists and must belong to user's domain
        candidate = get_candidate_if_validated(authed_user, candidate_id)

        created_candidate_custom_field_ids = [
        ]  # aggregate created CandidateCustomField IDs
        candidate_custom_fields = body_dict['candidate_custom_fields']

        for candidate_custom_field in candidate_custom_fields:

            # Custom field value(s) must not be empty
            values = filter(None, [value.strip() for value in (candidate_custom_field.get('values') or []) if value]) \
                     or [candidate_custom_field['value'].strip()]
            if not values:
                raise InvalidUsage("Custom field value must be provided.",
                                   custom_error.INVALID_USAGE)

            # Custom Field must be recognized
            custom_field_id = candidate_custom_field['custom_field_id']
            custom_field = CustomField.get_by_id(custom_field_id)
            if not custom_field:
                raise NotFoundError(
                    "Custom field ID ({}) not recognized".format(
                        custom_field_id), custom_error.CUSTOM_FIELD_NOT_FOUND)

            # Custom Field must belong to user's domain
            if custom_field.domain_id != candidate.user.domain_id:
                raise ForbiddenError(
                    "Custom field ID ({}) does not belong to user ({})".format(
                        custom_field_id, authed_user.id),
                    custom_error.CUSTOM_FIELD_FORBIDDEN)

            custom_field_dict = dict(values=values,
                                     custom_field_id=custom_field_id)

            for value in custom_field_dict.get('values'):

                custom_field_id = candidate_custom_field.get('custom_field_id')

                # Prevent duplicate insertions
                if not does_candidate_cf_exist(candidate, custom_field_id,
                                               value):

                    added_time = datetime.datetime.utcnow()

                    candidate_custom_field = CandidateCustomField(
                        candidate_id=candidate_id,
                        custom_field_id=custom_field_id,
                        value=value,
                        added_time=added_time)
                    db.session.add(candidate_custom_field)
                    db.session.flush()

                    created_candidate_custom_field_ids.append(
                        candidate_custom_field.id)

        db.session.commit()
        upload_candidate_documents.delay([candidate_id])
        return {
            'candidate_custom_fields': [{
                'id': custom_field_id
            } for custom_field_id in created_candidate_custom_field_ids]
        }, http_status_codes.CREATED