Пример #1
0
class CandidateDocuments(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_ADD_CANDIDATES)
    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

    @require_all_permissions(Permission.PermissionNames.CAN_DELETE_CANDIDATES)
    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
Пример #2
0
class CandidateEditResource(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    def get(self, **kwargs):
        """
        Endpoint: GET /v1/candidates/:id/edits
        Function will return requested Candidate with all of its edits.

        Usage:
            >>> url = 'host/v1/candidates/4/edits'
            >>> headers = {'Authorization': 'Bearer 7UupqClvkNv4payNUPzZmUerW9Wwd5'}
            >>> requests.get(url=url, headers=headers)
            <Response [200]>

        :return: {
                    'candidate': [
                        {
                            'id': 4,
                            'edits': [
                                {
                                    'user_id': 45,
                                    'field': 'first_name',
                                    'old_value': 'john',
                                    'new_value': 'jon',
                                    'is_custom_field': False,
                                    'edit_action': 'updated',
                                    'edit_datetime': '2016-03-02T08:44:55+00:00'
                                },
                                {
                                    'user_id': 45,
                                    'field': 'middle_name',
                                    'old_value': 'eleven',
                                    'new_value': None,
                                    'is_custom_field': False,
                                    'edit_action': 'deleted',
                                    'edit_datetime': '2016-04-02T08:44:55+00:00'
                                }
                            ]
                        }
                    ]
                }
        """
        # Get authenticated user & candidate_id
        authed_user, candidate_id = request.user, kwargs.get('id')

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

        return {
            'candidate': {
                'id': candidate_id,
                'edits': fetch_candidate_edits(candidate_id)
            }
        }
Пример #3
0
class CandidateStatusesResources(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    def get(self):
        """
        Function will create candidate's status(es)
        Usage:
            >>> headers = {"Authorization": "Bearer {access_token}"}
            >>> requests.get(url="/v1/candidate_statuses", headers=headers)
            <Response [200]>
        :return:    {"statuses": [{"id": 1, "description": "New", "Notes": "newly added candidate"}, {...}]}
        """
        return {
            "statuses": [{
                "id": status.id,
                "description": status.description,
                "notes": status.notes
            } for status in CandidateStatus.get_all()]
        }
Пример #4
0
class CandidateNotesResource(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_ADD_CANDIDATE_NOTES)
    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

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATE_NOTES)
    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)

    @require_all_permissions(Permission.PermissionNames.CAN_DELETE_CANDIDATE_NOTES)
    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}
Пример #5
0
class CandidateReferencesResource(Resource):
    """
    Resource for all CRUD operations pertaining to candidate's references
    """
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_CANDIDATES)
    def post(self, **kwargs):
        """
        Endpoint:   POST /v1/candidates/:candidate_id/references
        Function will create candidate's reference's data

        Usage:
            >>> url = 'host/v1/candidates/4/references'
            >>> headers = {'Authorization': 'Bearer {access_token}', 'content-type': 'application/json'}
            >>> data =
                        {
                            "candidate_references": [
                                {
                                    "name": "Colonel Sanders",
                                    "position_title": "manager",
                                    "comments": "adept in problem solving",
                                    "reference_email": {
                                        "address": "*****@*****.**",
                                        "label": "Primary",
                                        "is_default": true
                                    },
                                    "reference_phone": {
                                        "value": "+14055689944",
                                        "label": "Mobile",
                                        "is_default": true
                                    },
                                    "reference_web_address": {
                                        "url": "http://www.kfc.com",
                                        "description": "best way to reach me is via this website"
                                    }
                                }
                            ]
                        }
            >>> requests.post(url=url, headers=headers, data=json.dumps(data))
            <Response [201]>

        :return     {'candidate_references': [{'id': int}, {'id': int}, ...]}
        """
        # Get json data if exists and validate its schema
        body_dict = get_json_data_if_validated(request, references_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_validated(authed_user, candidate_id)

        created_reference_ids = create_or_update_references(
            candidate_id=candidate_id,
            references=body_dict['candidate_references'],
            is_creating=True)
        return {
            'candidate_references': [{
                'id': reference_id
            } for reference_id in created_reference_ids]
        }, requests.codes.CREATED

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    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)}

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_CANDIDATES)
    def patch(self, **kwargs):
        """
        Function will update candidate's references' information
        Candidate Reference ID must be provided for a successful response
        Endpoints:
             i. PATCH /v1/candidates/:candidate_id/references
            ii. PATCH /v1/candidates/:candidate_id/references/:id
        """
        # Get json data if provided and passed schema validation
        body_dict = get_json_data_if_validated(request, references_schema)

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

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

        updated_reference_ids = create_or_update_references(
            candidate_id=candidate_id,
            references=body_dict['candidate_references'],
            is_updating=True,
            reference_id_from_url=reference_id_from_url)
        return {
            'updated_candidate_references': [{
                'id': reference_id
            } for reference_id in updated_reference_ids]
        }

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_CANDIDATES)
    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)
            }
Пример #6
0
class CandidateTagResource(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_CANDIDATES)
    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

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    def get(self, **kwargs):
        """
        Function will retrieve tag(s)
        Endpoints:
             i. GET /v1/candidates/:candidate_id/tags
            ii. GET /v1/candidates/:candidate_id/tags/:id
        Example:
            >>> url = 'host/v1/candidates/4/tags' or 'host/v1/candidates/4/tags/57'
            >>> headers = {'Authorization': 'Bearer edo9rdSKN8hYuc1zBWMfLXpXFd4ZbE'}
            >>> requests.get(url=url, headers=headers)
        If tag ID is not provided in the url, all of candidate's tags will be returned
            :return:  {'tags': [{'id': int, 'description': string}, {'id': int, 'description': string}, ...]}
        If tag ID is provided in the url, a single candidate tag will be returned
            :return: {'tag': [{'description': string}]}
        """
        # Authenticated user, candidate ID, and tag ID
        authed_user, candidate_id, tag_id = request.user, kwargs[
            'candidate_id'], kwargs.get('id')

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

        # Retrieve tag(s)
        return get_tags(candidate_id=candidate_id, tag_id=tag_id)

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_CANDIDATES)
    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]}

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_CANDIDATES)
    def delete(self, **kwargs):
        """
        Function will delete candidate's tag(s)
        Endpoints:
             i. DELETE /v1/candidates/:candidate_id/tags
            ii. DELETE /v1/candidates/:candidate_id/tags/:id
        Example:
            >>> url = 'host/v1/candidates/4/tags' or 'host/v1/candidates/4/tags/57'
            >>> headers = {'Authorization': 'Bearer edo9rdSKN8hYuc1zBWMfLXpXFd4ZbE'}
            >>> requests.delete(url=url, headers=headers)
        """
        # Authenticated user, candidate ID, and tag ID
        authed_user, candidate_id, tag_id = request.user, kwargs[
            'candidate_id'], kwargs.get('id')

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

        # Delete specified tag
        if tag_id:

            # Delete
            deleted_tag_id = delete_tag(candidate_id=candidate_id,
                                        tag_id=tag_id)

            # Update cloud search
            upload_candidate_documents([candidate_id])

            return {'deleted_tag': deleted_tag_id}

        # Delete all of candidate's tags
        deleted_tag_ids = delete_tags(candidate_id)

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

        # Delete all of candidate's tags
        return {'deleted_tags': deleted_tag_ids}
Пример #7
0
class CandidateCustomFieldResource(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_CANDIDATES)
    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

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    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)]
            }

    @require_all_permissions(Permission.PermissionNames.CAN_EDIT_CANDIDATES)
    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
Пример #8
0
class CandidatePipelineResource(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    @time_me(logger=logger, api='candidate_pipeline_inclusion')
    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}
Пример #9
0
class CandidateSearch(Resource):
    decorators = [require_oauth()]

    @require_all_permissions(Permission.PermissionNames.CAN_GET_CANDIDATES)
    @time_me(logger=logger, api='candidate-search')
    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