diff --git a/hr_linkedin_recruitment/README.rst b/hr_linkedin_recruitment/README.rst new file mode 100644 index 000000000..0a574a3b7 --- /dev/null +++ b/hr_linkedin_recruitment/README.rst @@ -0,0 +1,41 @@ +========================================== + Basic HR-LinkedIn Connector v10 +========================================== + +The module integrates LinkedIn with Odoo HR Recruitment. + * Share Job Opening Posts With LinkedIn Page. + * Automate Odoo HRMS with LinkedIn. + + +========================================== + Advanced HR-LinkedIn Connector v10 +========================================== + +The module integrates LinkedIn with Odoo HR Recruitment. + * Share Job Opening Posts With LinkedIn Page. + * Retrieve Post Comments and Likes. + * Retrieve Candidates Profile Data From LinkedIn. + * Profile URL of Candidates. + * Automate Odoo HRMS with LinkedIn. + + +Contributor +----------- +* `Nilmar Shereef `__ + +Company +------- +* `Cybrosys Technologies `__ + +Contact +------- +* Mail Contact : odoo@cybrosys.com +* Mail Contact : shereef@cybrosys.in +* Mail Contact : info@cybrosys.com + +Further information +=================== +HTML Description: ``__ +Technical instructions: ``__ + +Tested on Odoo 10.0 ffba5c688ff74a0630f9f70be1d7760a43a7deba diff --git a/hr_linkedin_recruitment/__init__.py b/hr_linkedin_recruitment/__init__.py new file mode 100644 index 000000000..c555c50ec --- /dev/null +++ b/hr_linkedin_recruitment/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Author: Nilmar Shereef () +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### +from . import models + diff --git a/hr_linkedin_recruitment/__manifest__.py b/hr_linkedin_recruitment/__manifest__.py new file mode 100644 index 000000000..7c69b76f7 --- /dev/null +++ b/hr_linkedin_recruitment/__manifest__.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Author: Nilmar Shereef () +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### +{ + 'name': 'HR-LinkedIn Integration', + 'summary': "Integrates LinkedIn with HR Recruitment", + 'description': "Basic module for LnkedIn-HR Recruitment connector", + 'category': 'Generic Modules/Human Resources', + 'version': "10.0.1.0.0", + 'depends': ['hr_recruitment', 'auth_oauth'], + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'data': [ + 'data/auth_linkedin_data.xml', + 'views/recruitment_config_settings.xml', + 'views/hr_job_linkedin.xml', + 'views/oauth_view.xml', + ], + 'external_dependencies': + { + 'python': ['mechanize', 'linkedin'], + }, + 'images': ['static/description/banner.jpg'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/hr_linkedin_recruitment/data/auth_linkedin_data.xml b/hr_linkedin_recruitment/data/auth_linkedin_data.xml new file mode 100644 index 000000000..564832f14 --- /dev/null +++ b/hr_linkedin_recruitment/data/auth_linkedin_data.xml @@ -0,0 +1,14 @@ + + + + + LinkedIn + https://www.linkedin.com/oauth/v2/authorization + r_basicprofile r_emailaddress w_share + https://api.linkedin.com/v1/people/~ + https://api.linkedin.com/v1/people/~ + fa fa-linkedin-square + Share post with LinkedIn + + + diff --git a/hr_linkedin_recruitment/doc/requirment.txt b/hr_linkedin_recruitment/doc/requirment.txt new file mode 100644 index 000000000..ef092c744 --- /dev/null +++ b/hr_linkedin_recruitment/doc/requirment.txt @@ -0,0 +1,5 @@ +Odoo integration module "hr_linkedin_recruitment" depends on the several external python package. +Please ensure that listed packaged has installed in your system: + +* linkedin +* mechanize \ No newline at end of file diff --git a/hr_linkedin_recruitment/models/__init__.py b/hr_linkedin_recruitment/models/__init__.py new file mode 100644 index 000000000..81806be4b --- /dev/null +++ b/hr_linkedin_recruitment/models/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Author: Nilmar Shereef () +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### +from . import mechanize_op +from . import recruitment_config +from . import hr_job +from . import auth_outh diff --git a/hr_linkedin_recruitment/models/auth_outh.py b/hr_linkedin_recruitment/models/auth_outh.py new file mode 100644 index 000000000..e95b08e8f --- /dev/null +++ b/hr_linkedin_recruitment/models/auth_outh.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Author: Nilmar Shereef () +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### +from odoo import models, fields, api + + +class OAuthProviderLinkedin(models.Model): + + _inherit = 'auth.oauth.provider' + + """ Adding client_secret field because some apps likes twitter, + linkedIn are using this value for its API operations """ + + client_secret = fields.Char(string='Client Secret', help="Only need LinkedIn, Twitter etc..") diff --git a/hr_linkedin_recruitment/models/hr_job.py b/hr_linkedin_recruitment/models/hr_job.py new file mode 100644 index 000000000..8a5045139 --- /dev/null +++ b/hr_linkedin_recruitment/models/hr_job.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Author: Nilmar Shereef () +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### +try: + import mechanize + from linkedin import linkedin + +except ImportError: + _logger.error('Odoo module hr_linkedin_recruitment depends on the several external python package' + 'Please read the doc/requirement.txt file inside the module.') + +import requests +import json +import urlparse +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError +from mechanize_op import MechanizeRedirectHandler + + +class HrJobShare(models.Model): + _inherit = 'hr.job' + + update_key = fields.Char(string='Update Key', readonly=True) + + @api.multi + def share_linkedin(self): + """ Button function for sharing post """ + credential_dict = self.get_authorize() + access_token = credential_dict['access_token'] + li_credential = credential_dict['li_credential'] + + share_data = { + "visibility": {"code": "anyone"}, + "comment": self.description + } + + # URLS + has_access_url = 'https://api.linkedin.com/v1/companies/%s/relation-to-viewer/is-company-share-enabled?format=json'%(li_credential['page_id']) + page_share_url = 'https://api.linkedin.com/v1/companies/%s/shares?format=json'%(li_credential['page_id']) + + response = self.has_acces_request('GET', has_access_url, access_token) + access_response_text = response.json() + if access_response_text: + response = self.share_request('POST', page_share_url, access_token, data=json.dumps(share_data)) + share_response_text = response.json() + share_response_code = response.status_code + if share_response_code == 201: + self.update_key = share_response_text['updateKey'] + + + def share_request(self, method, page_share_url, access_token, data): + """ Function will return UPDATED KEY , [201] if sharing is OK """ + headers = {'x-li-format': 'json', 'Content-Type': 'application/json'} + params = {} + params.update({'oauth2_access_token': access_token}) + kw = dict(data=data, params=params, headers=headers, timeout=60) + req_response = requests.request(method.upper(), page_share_url, **kw) + return req_response + + def get_authorize(self): + """ Supporting function for authenticating operations """ + li_credential = {} + linkedin_auth_provider = self.env.ref('hr_linkedin_recruitment.provider_linkedin') + if linkedin_auth_provider.client_id and linkedin_auth_provider.client_secret: + li_credential['api_key'] = linkedin_auth_provider.client_id + li_credential['secret_key'] = linkedin_auth_provider.client_secret + else: + raise ValidationError(_('LinkedIn Access Credentials are empty.!\n' + 'Please fill up in Auth Provider form.')) + + if self.env['ir.values'].get_default('hr.recruitment.config.settings', 'company_page_id'): + li_credential['page_id'] = self.env['ir.values'].get_default('hr.recruitment.config.settings', + 'company_page_id') + else: + raise exceptions.Warning(_('Please fill up company page ID in LinkedIn Credential settings.')) + if self.env['ir.values'].get_default('hr.recruitment.config.settings', 'li_username'): + li_credential['un'] = self.env['ir.values'].get_default('hr.recruitment.config.settings', 'li_username') + else: + raise exceptions.Warning(_('Please fill up username in LinkedIn Credential settings.')) + if self.env['ir.values'].get_default('hr.recruitment.config.settings', 'li_password'): + li_credential['pw'] = self.env['ir.values'].get_default('hr.recruitment.config.settings', 'li_password') + else: + raise exceptions.Warning(_('Please fill up password in LinkedIn Credential settings.')) + + # Browser Data Posting And Signing + br = mechanize.Browser() + br.set_cookiejar(mechanize.CookieJar()) + br.handler_classes['_redirect'] = MechanizeRedirectHandler + br.set_handle_redirect(True) + br.set_handle_robots(False) + return_uri = 'http://0.0.0.0:8010' + li_permissions = ['r_basicprofile', 'r_emailaddress', 'w_share', 'rw_company_admin'] + auth = linkedin.LinkedInAuthentication(li_credential['api_key'], + li_credential['secret_key'], + return_uri, + li_permissions) + br.open(auth.authorization_url) + br.select_form(nr=0) + + br.form['session_key'] = li_credential['un'] + br.form['session_password'] = li_credential['pw'] + r = br.submit() + auth.authorization_code = urlparse.parse_qs(urlparse.urlsplit(r.geturl()).query)['code'] + li_suit_credent = {} + li_suit_credent['access_token'] = str(auth.get_access_token().access_token) + li_suit_credent['li_credential'] = li_credential + return li_suit_credent diff --git a/hr_linkedin_recruitment/models/mechanize_op.py b/hr_linkedin_recruitment/models/mechanize_op.py new file mode 100644 index 000000000..545ba6d45 --- /dev/null +++ b/hr_linkedin_recruitment/models/mechanize_op.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Author: Nilmar Shereef () +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### +try: + import mechanize +except ImportError: + _logger.error('Odoo module hr_linkedin_recruitment depends on the several external python package' + 'Please read the doc/requirement.txt file inside the module.') +import re +from mechanize import _response +from mechanize import _rfc3986 + + +class MechanizeRedirectHandler(mechanize.HTTPRedirectHandler): + def http_error_302(self, req, fp, code, msg, headers): + # Code from mechanize._urllib2_fork.HTTPRedirectHandler: + if 'location' in headers: + newurl = headers.getheaders('location')[0] + elif 'uri' in headers: + newurl = headers.getheaders('uri')[0] + else: + return + newurl = _rfc3986.clean_url(newurl, "latin-1") + newurl = _rfc3986.urljoin(req.get_full_url(), newurl) + + new = self.redirect_request(req, fp, code, msg, headers, newurl) + if new is None: + return + + if hasattr(req, 'redirect_dict'): + visited = new.redirect_dict = req.redirect_dict + if (visited.get(newurl, 0) >= self.max_repeats or + len(visited) >= self.max_redirections): + raise HTTPError(req.get_full_url(), code, + self.inf_msg + msg, headers, fp) + else: + visited = new.redirect_dict = req.redirect_dict = {} + visited[newurl] = visited.get(newurl, 0) + 1 + + fp.read() + fp.close() + + # If the redirected URL doesn't match + new_url = new.get_full_url() + if not re.search('^http(?:s)?\:\/\/.*www\.linkedin\.com', new_url): + return _response.make_response('', headers.items(), new_url, 200, 'OK') + else: + return self.parent.open(new) + + http_error_301 = http_error_303 = http_error_307 = http_error_302 + http_error_refresh = http_error_302 diff --git a/hr_linkedin_recruitment/models/recruitment_config.py b/hr_linkedin_recruitment/models/recruitment_config.py new file mode 100644 index 000000000..3b67640b0 --- /dev/null +++ b/hr_linkedin_recruitment/models/recruitment_config.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +################################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Author: Nilmar Shereef () +# Copyright (C) 2018-TODAY Cybrosys Technologies (). +# +# This program is free software: you can modify +# it under the terms of the GNU Affero General Public License (AGPL) as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +################################################################################### +from odoo import models, fields, api + + +class RecruitmentSettings(models.TransientModel): + _inherit = 'hr.recruitment.config.settings' + + company_page_id = fields.Char(string="Company Page ID", help="") + li_username = fields.Char(string="User Name", help="") + li_password = fields.Char(string="Password", help="") + + + @api.multi + def set_linkedin_page_id(self): + """ Set function for LinkedIn credentials """ + return self.env['ir.values'].sudo().set_default( + 'hr.recruitment.config.settings', 'company_page_id', self.company_page_id) + + @api.multi + def set_linkedin_username(self): + """ Set function for LinkedIn credentials """ + return self.env['ir.values'].sudo().set_default( + 'hr.recruitment.config.settings', 'li_username', self.li_username) + + @api.multi + def set_linkedin_pw(self): + """ Set function for LinkedIn credentials """ + return self.env['ir.values'].sudo().set_default( + 'hr.recruitment.config.settings', 'li_password', self.li_password) + + + diff --git a/hr_linkedin_recruitment/static/description/banner.jpg b/hr_linkedin_recruitment/static/description/banner.jpg new file mode 100644 index 000000000..d496d0059 Binary files /dev/null and b/hr_linkedin_recruitment/static/description/banner.jpg differ diff --git a/hr_linkedin_recruitment/static/description/config.png b/hr_linkedin_recruitment/static/description/config.png new file mode 100644 index 000000000..e0d970cd8 Binary files /dev/null and b/hr_linkedin_recruitment/static/description/config.png differ diff --git a/hr_linkedin_recruitment/static/description/cybro_logo.png b/hr_linkedin_recruitment/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/hr_linkedin_recruitment/static/description/cybro_logo.png differ diff --git a/hr_linkedin_recruitment/static/description/icon.png b/hr_linkedin_recruitment/static/description/icon.png new file mode 100644 index 000000000..0945ec499 Binary files /dev/null and b/hr_linkedin_recruitment/static/description/icon.png differ diff --git a/hr_linkedin_recruitment/static/description/index.html b/hr_linkedin_recruitment/static/description/index.html new file mode 100644 index 000000000..fdaef4734 --- /dev/null +++ b/hr_linkedin_recruitment/static/description/index.html @@ -0,0 +1,125 @@ +
+
+ + +
+

HR Team, Keep smiling for the below reasons:

+
    +
  •    Share Recruitment Post to LInkedIn from Odoo.
  • +
  •    Enlarge Your Audience Range.
  • +
+
+ +
+
+ +
+
+
+

+ This integration module makes your HR recruitment better. + You can share your posts directly to company page of LinkedIn with authorized login.

+
+
+
+ +
+
+
+

Setup a LinkedIn App

+

..with proper information..

+
+
+
+ +
+
+
+
+ +
+
+
+

Fill-up App Credentials

+

..activate developer mode & fill-up API keys..

+
+
+
+ +
+
+
+
+ +
+
+
+

Configuration of LinkedIn Account Details

+

..Username, Password and Page ID..

+
+
+
+ +
+
+
+
+ +
+
+
+

Share Job Requirement With LinkedIn Page

+

..take right candidates from right place..

+
+
+
+ +
+
+
+
+ +
+
+
+

Shared Post at LinkedIn Page

+

..take right candidates from right place..

+
+
+
+ +
+
+
+
+ +
+

Need Any Help?

+ +
+ + + diff --git a/hr_linkedin_recruitment/static/description/job_form_b.png b/hr_linkedin_recruitment/static/description/job_form_b.png new file mode 100644 index 000000000..2b2c7fa4d Binary files /dev/null and b/hr_linkedin_recruitment/static/description/job_form_b.png differ diff --git a/hr_linkedin_recruitment/static/description/linedin_form.png b/hr_linkedin_recruitment/static/description/linedin_form.png new file mode 100644 index 000000000..2e34fd4d9 Binary files /dev/null and b/hr_linkedin_recruitment/static/description/linedin_form.png differ diff --git a/hr_linkedin_recruitment/static/description/linkedin_app.png b/hr_linkedin_recruitment/static/description/linkedin_app.png new file mode 100644 index 000000000..3c2224399 Binary files /dev/null and b/hr_linkedin_recruitment/static/description/linkedin_app.png differ diff --git a/hr_linkedin_recruitment/static/description/linkedin_banner.jpg b/hr_linkedin_recruitment/static/description/linkedin_banner.jpg new file mode 100644 index 000000000..7c1d12802 Binary files /dev/null and b/hr_linkedin_recruitment/static/description/linkedin_banner.jpg differ diff --git a/hr_linkedin_recruitment/static/description/provider.png b/hr_linkedin_recruitment/static/description/provider.png new file mode 100644 index 000000000..f004e7357 Binary files /dev/null and b/hr_linkedin_recruitment/static/description/provider.png differ diff --git a/hr_linkedin_recruitment/views/hr_job_linkedin.xml b/hr_linkedin_recruitment/views/hr_job_linkedin.xml new file mode 100644 index 000000000..4c58e7ea0 --- /dev/null +++ b/hr_linkedin_recruitment/views/hr_job_linkedin.xml @@ -0,0 +1,19 @@ + + + + + + hr.job.linkedin.form + hr.job + + + +