Пример #1
0
def validate_cf_category_and_subcategory_ids(cf_category_id,
                                             domain_id,
                                             cf_subcategory_id=None):
    """
    Function will check whether custom field category and subcategory ids exits in db
    and if the do, do they belong to active user's domain
    :param positive cf_category_id: Custom Field Category Id
    :param positive|None cf_subcategory_id: Custom Field Subcategory Id
    :param positive domain_id: User's domain Id
    """
    category = CustomFieldCategory.query.join(CustomField).filter(
        CustomFieldCategory.id == cf_category_id,
        CustomField.domain_id == domain_id).first()
    if not category:
        raise ForbiddenError(
            "Requested Custom Field Category does not belong to user's domain",
            custom_error.CUSTOM_FIELD_CATEGORY_NOT_FOUND)
    if cf_subcategory_id:
        valid_subcategory = False
        if cf_subcategory_id in [
                category.id for category in category.subcategories
        ]:
            valid_subcategory = True
        if not valid_subcategory:
            raise ForbiddenError(
                "Unauthorized Custom Field Subcategory",
                custom_error.CUSTOM_FIELD_SUBCATEGORY_NOT_FOUND)
Пример #2
0
    def delete(self, **kwargs):
        """
        Function will delete all of candidate's notes if note ID is not provided,
        otherwise it will delete specified candidate note
        Endpoints:
             i. DELETE /v1/candidates/:candidate_id/notes
            ii. DELETE /v1/candidates/:candidate_id/notes/:id
        """
        # Get authenticated user & candidate ID
        authed_user, candidate_id, note_id = request.user, kwargs['candidate_id'], kwargs.get('id')

        # Check if candidate exists & is not web-hidden
        candidate = get_candidate_if_exists(candidate_id)

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

        # Delete candidate's note if note ID is provided, otherwise delete all of candidate's notes
        if note_id:

            # Delete note from DB & update cloud search
            delete_note(candidate_id, note_id)
            upload_candidate_documents.delay([candidate_id])

            return {'candidate_note': {'id': note_id}}
        else:
            # Delete notes from DB & update cloud search
            deleted_notes = delete_notes(candidate)
            upload_candidate_documents.delay([candidate_id])

            return {'candidate_notes': deleted_notes}
Пример #3
0
def get_candidate_if_validated(user, candidate_id):
    """
    Function will return candidate if:
        1. it exists
        2. not archived, and
        3. belongs to user's domain
    :type user: User
    :type candidate_id: int | long
    :rtype: Candidate
    """
    candidate = Candidate.get(candidate_id)
    if not candidate:
        raise NotFoundError(
            error_message='Candidate not found: {}'.format(candidate_id),
            error_code=custom_error.CANDIDATE_NOT_FOUND)
    if candidate.is_archived:
        raise NotFoundError(
            error_message='Candidate not found: {}'.format(candidate_id),
            error_code=custom_error.CANDIDATE_IS_ARCHIVED)

    if user and user.role.name != 'TALENT_ADMIN' and candidate.user.domain_id != user.domain_id:
        raise ForbiddenError("Not authorized",
                             custom_error.CANDIDATE_FORBIDDEN)

    return candidate
Пример #4
0
def get_tags(candidate_id, tag_id=None):
    """
    :type candidate_id:  int|long
    :type tag_id:        int|long
    :return:
    """
    # Return specified tag if tag_id is provided
    if tag_id:
        tag_object = Tag.get(tag_id)
        if not tag_object:
            raise NotFoundError(
                'Tag ID ({}) is not recognized.'.format(tag_id),
                custom_error.TAG_NOT_FOUND)

        # Tag must belong to candidate
        if not CandidateTag.query.filter_by(tag_id=tag_id,
                                            candidate_id=candidate_id).first():
            raise ForbiddenError(
                'Tag (id = {}) does not belong to candidate (id = {})'.format(
                    tag_id, candidate_id), custom_error.TAG_FORBIDDEN)

        return {'name': tag_object.name}

    # Return all of candidate's tags if tag_id is not provided
    candidate_tags = CandidateTag.get_all(candidate_id)
    if not candidate_tags:
        raise NotFoundError('Candidate is not associated with any tags',
                            custom_error.TAG_NOT_FOUND)

    tags = []
    for candidate_tag in candidate_tags:
        tag = Tag.get(candidate_tag.tag_id)
        tags.append(dict(id=tag.id, name=tag.name))

    return {'tags': tags}
Пример #5
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
Пример #6
0
def get_notes(candidate, note_id=None):
    """
    Function will return all of candidate's notes if note_id is not provided, otherwise it
    will return a single note object.
    :type candidate:  Candidate
    :type note_id:  int | long | None
    :rtype: dict | list[dict]
    """
    candidate_id = candidate.id

    # return specified note
    if note_id:

        note = CandidateTextComment.get(note_id)

        # Note ID must be recognized
        if not note:
            raise NotFoundError('Note ID ({}) not recognized'.format(note_id), custom_error.NOTE_NOT_FOUND)

        # Note must belong to the candidate
        if note.candidate_id != candidate_id:
            raise ForbiddenError('Note (id = {}) does not belong to candidate (id = {})'.format(note_id, candidate_id),
                                 custom_error.NOTE_FORBIDDEN)

        return {
            'candidate_note': {
                'id': note_id,
                'candidate_id': candidate_id,
                'owner_user_id': note.owner_user_id,
                'title': note.title,
                'comment': note.comment,
                'added_time': DatetimeUtils.to_utc_str(note.added_time) if note.added_time else None
            }
        }

    # return all of candidate's notes
    else:
        candidate_notes = []
        for note in CandidateTextComment.get_by_candidate_id(candidate_id):
            candidate_notes.append({
                'id': note.id,
                'candidate_id': candidate_id,
                'owner_user_id': note.owner_user_id,
                'title': note.title,
                'comment': note.comment,
                'added_time': DatetimeUtils.to_utc_str(note.added_time) if note.added_time else None
            })

        return {'candidate_notes': candidate_notes}
Пример #7
0
    def get(self, **kwargs):
        """
        Function will return all of candidate's notes if note ID is not provided,
        otherwise it will return specified candidate note
        Endpoints:
             i. GET /v1/candidates/:candidate_id/notes
            ii. GET /v1/candidates/:candidate_id/notes/:id
        """
        # Get authenticated user & candidate ID
        authed_user, candidate_id, note_id = request.user, kwargs['candidate_id'], kwargs.get('id')

        # Check if candidate exists & is web-hidden
        candidate = get_candidate_if_exists(candidate_id)

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

        return get_notes(candidate, note_id)
Пример #8
0
def authenticate_candidate_preference_request(request_object, candidate_id):
    """
    This method will check either user or candidate is authorized to access Candidate Preference EndPoint
    :param request_object: Flask Request Object
    :param candidate_id: Id of candidate
    :return:
    """
    # Ensure Candidate exists & is not web-hidden
    candidate_object = get_candidate_if_validated(request_object.user,
                                                  candidate_id)

    if (request_object.candidate
            and request_object.candidate.id != candidate_id) or (
                request_object.user and request_object.user.domain_id !=
                candidate_object.user.domain_id):
        raise ForbiddenError(
            "You are not authorized to change subscription preference of "
            "candidate: %s" % candidate_id)

    return candidate_object
Пример #9
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
Пример #10
0
def delete_note(candidate_id, note_id):
    """
    Function will delete candidate note.
    Note ID must be recognized & must belong to candidate
    :type candidate_id:  int | long
    :type note_id:  int | long
    """
    note = CandidateTextComment.get(note_id)

    # Note ID must be recognized
    if not note:
        raise NotFoundError('Note ID ({}) not recognized'.format(note_id), custom_error.NOTE_NOT_FOUND)

    # Note must belong to the candidate
    if note.candidate_id != candidate_id:
        raise ForbiddenError('Note (id = {}) does not belong to candidate (id = {})'.format(note_id, candidate_id),
                             custom_error.NOTE_FORBIDDEN)

    db.session.delete(note)
    db.session.commit()
    return note_id
Пример #11
0
    def get(self, **kwargs):
        """
        Endpoints:
             i. GET /v1/candidates/:candidate_id/references
            ii. GET /v1/candidates/:candidate_id/references/:id
        """
        # Get authenticated user, candidate ID, and reference ID
        authed_user, candidate_id, reference_id = request.user, kwargs[
            'candidate_id'], kwargs.get('id')

        # Check if candidate exists & is web-hidden
        candidate = get_candidate_if_validated(authed_user, candidate_id)

        if reference_id:
            # Reference ID must be recognized
            reference = CandidateReference.get(reference_id)
            if not reference:
                raise NotFoundError(
                    "Reference ID ({}) not recognized.".format(reference_id),
                    custom_error.REFERENCE_NOT_FOUND)

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

            return dict(candidate_reference=dict(
                id=reference_id,
                name=reference.person_name,
                position_title=reference.position_title,
                comments=reference.comments,
                reference_email=get_reference_emails(reference_id),
                reference_phone=get_reference_phones(reference_id),
                reference_web_address=get_reference_web_addresses(
                    reference_id)))

        return {'candidate_references': get_references(candidate)}
Пример #12
0
    def delete(self, **kwargs):
        """
        Endpoints:
             i. DELETE /v1/candidates/:candidate_id/references
            ii. DELETE /v1/candidates/:candidate_id/references/:id
        :return
            {'candidate_reference': {'id': int}}                        If a single reference was deleted, OR
            {'candidate_references': [{'id': int}, {'id': int}, ...]}   If all references were deleted
            status code: 200
        """
        # Get authenticated user, candidate ID, and reference ID
        authed_user, candidate_id, reference_id = request.user, kwargs[
            'candidate_id'], kwargs.get('id')

        # Check if candidate exists & is web-hidden
        candidate = get_candidate_if_validated(authed_user, candidate_id)

        if reference_id:  # Delete specified reference
            candidate_reference = CandidateReference.get_by_id(reference_id)
            if not candidate_reference:  # Reference must be recognized
                raise NotFoundError(
                    "Candidate reference ({}) not found.".format(reference_id),
                    custom_error.REFERENCE_NOT_FOUND)

            if candidate_reference.candidate_id != candidate_id:  # reference must belong to candidate
                raise ForbiddenError("Not authorized",
                                     custom_error.REFERENCE_FORBIDDEN)

            # Delete candidate reference and return its ID
            return {
                'candidate_reference': delete_reference(candidate_reference)
            }

        else:  # Delete all of candidate's references
            return {
                'candidate_references':
                delete_all_references(candidate.references)
            }
Пример #13
0
    def post(self, **kwargs):
        """
        Endpoint:  POST /v1/candidates/:candidate_id/notes
        Function will add candidate's note(s) to database
        """
        # Validate and retrieve json data
        body_dict = get_json_data_if_validated(request, notes_schema)

        # Get authenticated user & Candidate ID
        authed_user, candidate_id = request.user, kwargs['candidate_id']

        # Check if candidate exists & is not web-hidden
        get_candidate_if_exists(candidate_id)

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

        note_ids = add_notes(candidate_id=candidate_id, user_id=authed_user.id, data=body_dict['notes'])

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

        return {'candidate_notes': [{'id': note_id} for note_id in note_ids]}, requests.codes.CREATED
Пример #14
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
Пример #15
0
    def delete(self, **kwargs):
        """
        Endpoints:
             i. DELETE /v1/candidates/:candidate_id/custom_fields
            ii. DELETE /v1/candidates/:candidate_id/custom_fields/:id
        Depending on the endpoint requested, function will delete all of Candidate's
        custom fields or just a single one.
        """
        # Get authenticated user, candidate_id, and can_cf_id (CandidateCustomField.id)
        authed_user, candidate_id, can_cf_id = request.user, kwargs[
            'candidate_id'], kwargs.get('id')

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

        if can_cf_id:  # Delete specified custom field
            candidate_custom_field = CandidateCustomField.get_by_id(can_cf_id)
            if not candidate_custom_field:
                raise NotFoundError(
                    'Candidate custom field not found: {}'.format(can_cf_id),
                    custom_error.CUSTOM_FIELD_NOT_FOUND)

            # Custom fields must belong to user's domain
            custom_field_id = candidate_custom_field.custom_field_id
            if not is_custom_field_authorized(authed_user.domain_id,
                                              [custom_field_id]):
                raise ForbiddenError('Not authorized',
                                     custom_error.CUSTOM_FIELD_FORBIDDEN)

            # Custom Field must belong to candidate
            if candidate_custom_field.candidate_id != candidate_id:
                raise ForbiddenError(
                    "Candidate custom field ({}) does not belong to candidate ({})"
                    .format(can_cf_id,
                            candidate_id), custom_error.CUSTOM_FIELD_FORBIDDEN)

            db.session.delete(candidate_custom_field)

            # Track change
            db.session.add(
                CandidateEdit(
                    user_id=authed_user.id,
                    candidate_id=candidate_id,
                    field_id=CandidateEdit.field_dict['candidate_custom_field']
                    ['value'],
                    old_value=candidate_custom_field.value,
                    new_value=None,
                    edit_datetime=datetime.datetime.utcnow(),
                    is_custom_field=True))

        else:  # Delete all of Candidate's custom fields
            for ccf in CandidateCustomField.get_candidate_custom_fields(
                    candidate_id):  # type: CandidateCustomField
                db.session.delete(ccf)

                # Track change
                db.session.add(
                    CandidateEdit(
                        user_id=authed_user.id,
                        candidate_id=candidate_id,
                        field_id=CandidateEdit.
                        field_dict['candidate_custom_field']['value'],
                        old_value=ccf.value,
                        new_value=None,
                        edit_datetime=datetime.datetime.utcnow(),
                        is_custom_field=True))

        db.session.commit()

        # Update cloud search
        upload_candidate_documents.delay([candidate_id])
        return '', 204
Пример #16
0
    def get(self, **kwargs):
        """
        Endpoints:
             i. GET /v1/candidates/:candidate_id/custom_fields
            ii. GET /v1/candidates/:candidate_id/custom_fields/:id
        Depending on the endpoint requested, function will return all of Candidate's
        custom fields or just a single one.
        """
        # Get authenticated user, candidate_id, and can_cf_id
        authed_user, candidate_id, can_cf_id = request.user, kwargs[
            'candidate_id'], kwargs.get('id')

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

        if can_cf_id:  # Retrieve specified custom field
            candidate_custom_field = CandidateCustomField.get_by_id(can_cf_id)
            if not candidate_custom_field:
                raise NotFoundError(
                    'Candidate custom field not found: {}'.format(can_cf_id),
                    custom_error.CUSTOM_FIELD_NOT_FOUND)

            # Custom field must belong to user's domain
            custom_field_id = candidate_custom_field.custom_field_id
            if not is_custom_field_authorized(authed_user.domain_id,
                                              [custom_field_id]):
                raise ForbiddenError('Not authorized',
                                     custom_error.CUSTOM_FIELD_FORBIDDEN)

            # Custom Field must belong to candidate
            if candidate_custom_field.candidate_id != candidate_id:
                raise ForbiddenError(
                    "Candidate custom field ({}) does not belong to candidate ({})"
                    .format(can_cf_id,
                            candidate_id), custom_error.CUSTOM_FIELD_FORBIDDEN)

            return {
                'candidate_custom_field': {
                    'id':
                    can_cf_id,
                    'custom_field_id':
                    custom_field_id,
                    'value':
                    candidate_custom_field.value,
                    'created_at_datetime':
                    candidate_custom_field.added_time.isoformat()
                }
            }

        else:
            # Custom fields must belong user's domain
            return {
                'candidate_custom_fields': [{
                    'id':
                    ccf.id,
                    'custom_field_id':
                    ccf.custom_field_id,
                    'value':
                    ccf.value,
                    'created_at_datetime':
                    ccf.added_time.isoformat()
                } for ccf in CandidateCustomField.get_candidate_custom_fields(
                    candidate_id)]
            }