├── .gitignore ├── LICENSE.txt ├── README.md ├── __init__.py ├── __manifest__.py ├── controllers ├── __init__.py ├── controllers.py ├── mail.py ├── oauth_login_ext.py └── oauth_signin_3rd.py ├── data └── oauth_provider.xml ├── models ├── __init__.py ├── res_partner.py ├── res_users.py ├── wo_config.py └── wo_confirm_wizard.py ├── rpc └── __init__.py ├── security └── ir.model.access.csv └── views ├── res_users_views.xml ├── wo_config_views.xml ├── wo_confirm_views.xml └── wx_login.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # C extensions 4 | *.so 5 | 6 | 7 | # Mr Developer 8 | .mr.developer.cfg 9 | .project 10 | .pydevproject 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 The Data Incubator 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## WeOdoo 2 | Odoo 快速接入企业微信,快捷使用,基于Oauth2.0安全认证协议,免对接开发配置,支持局域网等内网环境的 Odoo 服务 3 | 4 | 5 | ## 特性 6 | * 账号授权绑定 7 | * PC端扫码登录、企业微信端授权登录,首次自助登录绑定用户 8 | * 可绑定自有的甚至本地的Odoo服务地址,企业微信端自动授权登录绑定的Odoo用户 9 | * 可自由发送通知消息到企业微信 10 | * Odoo单据mail消息自动发送企业微信通知,点开通知直接进入Odoo单据页面 11 | * 实现了Odoo融合到企业微信的移动化办公 12 | * 支持Odoo10、11、12 13 | 14 | 15 | ## 使用 16 | 17 | - 下载Odoo模块wedooo并安装 18 | - 在登录页面点“企业微信登录”按提示安装好企业微信手机端应用 19 | - 将得到的授权应用Key和授权应用Secret填入Odoo的【设置】-【WeOdoo设置】页即可 20 | 21 | ![info](http://oejia.net/files/201811/12123008687.jpeg) 22 | 23 | ![info](http://oejia.net/files/201811/12123138548.jpeg) 24 | 25 | ![info](http://oejia.net/files/201811/12123243069.jpeg) 26 | 27 | 28 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import controllers 4 | from . import models -------------------------------------------------------------------------------- /__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | { 3 | 'name': "WeOdoo", 4 | 'summary': """企业微信快捷使用,免对接""", 5 | 'description': """""", 6 | 'author': 'Oejia', 7 | 'website': 'http://www.oejia.net/', 8 | 'category': '', 9 | 'version': '0.1', 10 | 'depends': ['auth_oauth'], 11 | 'application': True, 12 | 'data': [ 13 | 'security/ir.model.access.csv', 14 | 15 | 'views/wx_login.xml', 16 | 'data/oauth_provider.xml', 17 | 'views/wo_config_views.xml', 18 | 'views/wo_confirm_views.xml', 19 | 'views/res_users_views.xml', 20 | ], 21 | 'qweb': [], 22 | 'demo': [], 23 | } 24 | -------------------------------------------------------------------------------- /controllers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import controllers 4 | from . import oauth_signin_3rd 5 | from . import oauth_login_ext 6 | from . import mail 7 | -------------------------------------------------------------------------------- /controllers/controllers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | import time 4 | import random 5 | import json 6 | import base64 7 | 8 | from odoo import http 9 | from odoo.addons.web.controllers.main import Home, ensure_db 10 | from odoo.addons.web.controllers.main import db_monodb, ensure_db, set_cookie_and_redirect, login_and_redirect 11 | import requests 12 | from odoo.http import request 13 | 14 | 15 | 16 | _logger = logging.getLogger(__name__) 17 | 18 | QR_DICT = {} 19 | 20 | 21 | def gen_id(data): 22 | _now = time.time() 23 | # 回收过期的ID 24 | for k,v in list(QR_DICT.items()): 25 | if _now - v['ts'] > 600: 26 | del QR_DICT[k] 27 | # 生成ID 28 | tm = str(int(_now*100))[-7:] 29 | _id = str(random.randint(1,9)) + tm 30 | QR_DICT[_id] = {'ts':_now, 'state': 'gen', 'data': data} 31 | return _id 32 | 33 | 34 | class SocialLogin(http.Controller): 35 | 36 | 37 | @http.route('/corp/bind', type='http', auth="public", website=True) 38 | def wx_bind(self, **kw): 39 | qr_id = kw.get('qr_id') 40 | redirect = kw.get('redirect', '') 41 | redirect = base64.urlsafe_b64decode(redirect.encode('utf-8')).decode('utf-8') 42 | _info = QR_DICT[qr_id]['data'] 43 | 44 | values = request.params.copy() 45 | if redirect: 46 | values['login_url'] = '/web/login?qr_id=%s&redirect=%s'%(qr_id, redirect) 47 | else: 48 | values['login_url'] = '/web/login?qr_id=%s'%qr_id 49 | values['avatar'] = _info['avatar'] 50 | values['name'] = _info['name'] 51 | request.session['qr_id'] = qr_id 52 | return request.render('weodoo.wx_bind', values) 53 | 54 | -------------------------------------------------------------------------------- /controllers/mail.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import logging 4 | import werkzeug 5 | import werkzeug.utils 6 | 7 | 8 | from odoo import http 9 | from odoo.http import request 10 | from odoo.addons.mail.controllers.main import MailController 11 | 12 | _logger = logging.getLogger(__name__) 13 | 14 | 15 | class MailControllerExt(MailController): 16 | 17 | @http.route() 18 | def mail_action_view(self, **kwargs): 19 | _logger.info('>>> %s'%request.httprequest.url) 20 | _logger.info('>>>mail_action_view %s'%kwargs) 21 | if not request.session.uid: 22 | # X2Z0eXBlPXdv 23 | return werkzeug.utils.redirect('/web/login?_fm=X2Z0eXBlPXdv&redirect=%s'%werkzeug.url_quote_plus(request.httprequest.url), 303) 24 | res = super(MailControllerExt, self).mail_action_view(**kwargs) 25 | return res 26 | -------------------------------------------------------------------------------- /controllers/oauth_login_ext.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import logging 4 | import werkzeug.utils 5 | import werkzeug 6 | import base64 7 | import json 8 | 9 | import odoo 10 | from odoo import http 11 | from odoo.http import request 12 | from odoo.addons import auth_signup 13 | from odoo.addons.auth_oauth.controllers.main import OAuthLogin 14 | 15 | 16 | _logger = logging.getLogger(__name__) 17 | 18 | 19 | class AuthSignupHome(OAuthLogin): 20 | 21 | def _deal_state_r(self, state): 22 | _logger.info('>>> get_state %s'%request.httprequest.url) 23 | _fm = request.params.get('_fm', None) 24 | if _fm: 25 | fragment = base64.urlsafe_b64decode(_fm.encode('utf-8')).decode('utf-8') 26 | fragment = fragment.replace('_ftype=wo', '') 27 | r = werkzeug.url_unquote_plus(state.get('r', '')) 28 | state['r'] = werkzeug.url_quote_plus('%s#%s'%(r, fragment)) 29 | return state 30 | 31 | def _get_auth_link_wo(self, provider=None): 32 | if not provider: 33 | provider = request.env(user=1).ref('weodoo.provider_third') 34 | 35 | return_url = request.httprequest.url_root + 'auth_oauth/signin3rd' 36 | state = self.get_state(provider) 37 | self._deal_state_r(state) 38 | params = dict( 39 | response_type='token', 40 | client_id=provider['client_id'], 41 | redirect_uri=return_url, 42 | scope=provider['scope'], 43 | state=json.dumps(state), 44 | ) 45 | return "%s?%s" % (provider['auth_endpoint'], werkzeug.url_encode(params)) 46 | 47 | def list_providers(self): 48 | providers = super(AuthSignupHome, self).list_providers() 49 | weodoo_provider = request.env(user=1).ref('weodoo.provider_third') 50 | for provider in providers: 51 | if provider['id']==weodoo_provider.id: 52 | provider['auth_link'] = self._get_auth_link_wo(provider) 53 | break 54 | return providers 55 | 56 | 57 | @http.route() 58 | def web_login(self, *args, **kw): 59 | if request.httprequest.method == 'GET': 60 | if request.session.uid and request.params.get('redirect'): 61 | return http.redirect_with_hash(request.params.get('redirect')) 62 | fm = request.params.get('_fm', None) 63 | if not request.session.uid and fm!=None: 64 | fragment = base64.urlsafe_b64decode(fm.encode('utf-8')).decode('utf-8') 65 | if '_ftype=wo' in fragment: 66 | auth_link = self._get_auth_link_wo() 67 | return werkzeug.utils.redirect(auth_link, 303) 68 | 69 | response = super(AuthSignupHome, self).web_login(*args, **kw) 70 | 71 | from .controllers import QR_DICT 72 | qr_id = str(request.session.get('qr_id', ''))#kw.get('qr_id', False) 73 | if qr_id and (request.params['login_success'] or request.session.uid): 74 | from .controllers import QR_DICT 75 | if qr_id in QR_DICT: 76 | qr = QR_DICT[qr_id] 77 | if 1:#qr['state']=='fail' and qr['openid']: 78 | # 绑定当前登录的用户 79 | if request.session.uid: 80 | user = request.env["res.users"].sudo().search(([('id','=',request.session.uid)])) 81 | else: 82 | user = request.env.user 83 | user.write({ 84 | 'oauth_provider_id': qr['data']['oauth_provider_id'], 85 | 'oauth_uid': qr['data']['user_id'], 86 | }) 87 | request.env.cr.commit() 88 | 89 | return response 90 | 91 | @http.route() 92 | def web_client(self, s_action=None, **kw): 93 | res = super(AuthSignupHome, self).web_client(s_action, **kw) 94 | if not request.session.uid: 95 | fm = request.params.get('_fm', None) 96 | if fm!=None: 97 | res = werkzeug.utils.redirect('/web/login?_fm=%s'%fm, 303) 98 | return res 99 | -------------------------------------------------------------------------------- /controllers/oauth_signin_3rd.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import werkzeug 3 | import json 4 | import logging 5 | import base64 6 | 7 | from odoo import http 8 | from odoo.http import request 9 | from odoo.addons.auth_oauth.controllers.main import OAuthController, fragment_to_query_string 10 | from odoo.addons.auth_oauth.controllers.main import OAuthLogin 11 | 12 | from odoo.addons.web.controllers.main import db_monodb, ensure_db, set_cookie_and_redirect, login_and_redirect 13 | from odoo import registry as registry_get 14 | from odoo import api, http, SUPERUSER_ID, _ 15 | 16 | from odoo.exceptions import AccessDenied 17 | 18 | _logger = logging.getLogger(__name__) 19 | 20 | 21 | class OAuthControllerExt(OAuthController): 22 | 23 | #@http.route() 24 | @http.route('/auth_oauth/signin3rd', type='http', auth='none') 25 | @fragment_to_query_string 26 | def signin_3rd(self, **kw): 27 | state = json.loads(kw['state']) 28 | dbname = state['d'] 29 | provider = state['p'] 30 | context = state.get('c', {}) 31 | registry = registry_get(dbname) 32 | with registry.cursor() as cr: 33 | try: 34 | env = api.Environment(cr, SUPERUSER_ID, context) 35 | credentials = env['res.users'].sudo().auth_oauth_third(provider, kw) 36 | cr.commit() 37 | action = state.get('a') 38 | menu = state.get('m') 39 | redirect = werkzeug.url_unquote_plus(state['r']) if state.get('r') else False 40 | url = '/web' 41 | if redirect: 42 | url = redirect 43 | elif action: 44 | url = '/web#action=%s' % action 45 | elif menu: 46 | url = '/web#menu_id=%s' % menu 47 | if credentials[0]==-1: 48 | from .controllers import gen_id 49 | credentials[1]['oauth_provider_id'] = provider 50 | qr_id = gen_id(credentials[1]) 51 | redirect = base64.urlsafe_b64encode(redirect.encode('utf-8')).decode('utf-8') 52 | url = '/corp/bind?qr_id=%s&redirect=%s'%(qr_id, redirect) 53 | else: 54 | return login_and_redirect(*credentials, redirect_url=url) 55 | except AttributeError: 56 | import traceback;traceback.print_exc() 57 | # auth_signup is not installed 58 | _logger.error("auth_signup not installed on database %s: oauth sign up cancelled." % (dbname,)) 59 | url = "/web/login?oauth_error=1" 60 | except AccessDenied: 61 | import traceback;traceback.print_exc() 62 | # oauth credentials not valid, user could be on a temporary session 63 | _logger.info('OAuth2: access denied, redirect to main page in case a valid session exists, without setting cookies') 64 | url = "/web/login?oauth_error=3" 65 | redirect = werkzeug.utils.redirect(url, 303) 66 | redirect.autocorrect_location_header = False 67 | return redirect 68 | except Exception as e: 69 | # signup error 70 | _logger.exception("OAuth2: %s" % str(e)) 71 | url = "/web/login?oauth_error=2" 72 | 73 | return set_cookie_and_redirect(url) 74 | 75 | -------------------------------------------------------------------------------- /data/oauth_provider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 企业微信登录 6 | 1 7 | https://i.calluu.cn/oauth2/auth 8 | userinfo 9 | https://i.calluu.cn/oauth2/tokeninfo 10 | 11 | 企业微信登录 12 | 13 | 14 | 15 | 1 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import res_users 4 | from . import res_partner 5 | from . import wo_config 6 | from . import wo_confirm_wizard 7 | -------------------------------------------------------------------------------- /models/res_partner.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | 4 | from openerp import models, fields, api 5 | 6 | _logger = logging.getLogger(__name__) 7 | 8 | class ResPartner(models.Model): 9 | 10 | _inherit = 'res.partner' 11 | 12 | 13 | wecorp_notify = fields.Boolean('接收企业微信通知', default=True) 14 | 15 | 16 | @api.multi 17 | def _notify(self, message, force_send=False, send_after_commit=True, user_signature=True): 18 | res = super(ResPartner, self)._notify(message, force_send, send_after_commit, user_signature) 19 | self._notify_by_weodoo(message) 20 | return res 21 | 22 | @api.multi 23 | def _notify_by_weodoo(self, message): 24 | _logger.info('>>> _notify_by_weodoo: %s'%str(message)) 25 | base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') 26 | for partner in self: 27 | if partner.get_corp_key() and partner.wecorp_notify and partner.im_status!='1online': 28 | _logger.info('>>> notify by weodoo: %s'%str((message,partner))) 29 | _body = message.body.replace('

','').replace('

','') 30 | _content = u'%s\n%s'%(message.subject, _body) if message.subject else _body 31 | _head = u'%s 发送到 %s'%(message.author_id.name, message.record_name) 32 | try: 33 | message_content = u'%s:%s'%(_head,_content) 34 | partner.send_corp_msg({"mtype": "card", "title": _head, 'description': _content, 'url': '%s/mail/view?message_id=%s'%(base_url, message.id)}) 35 | except: 36 | import traceback;traceback.print_exc() 37 | 38 | def send_corp_msg(self, msg): 39 | from ..rpc import send_msg 40 | send_msg(self.env, [self.user_ids[0].oauth_uid], msg) 41 | 42 | def get_corp_key(self): 43 | if self.user_ids: 44 | return self.user_ids[0].oauth_uid 45 | -------------------------------------------------------------------------------- /models/res_users.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import werkzeug 3 | import json 4 | try: 5 | import urlparse 6 | except: 7 | from urllib.parse import urlparse 8 | try: 9 | import urllib2 10 | except: 11 | from urllib import request as urllib2 12 | import logging 13 | 14 | from odoo import models, fields, api 15 | from odoo.exceptions import AccessDenied 16 | 17 | 18 | _logger = logging.getLogger(__name__) 19 | 20 | class ResUsers(models.Model): 21 | 22 | _inherit = 'res.users' 23 | 24 | 25 | @api.model 26 | def _auth_oauth_signin_third(self, provider, validation, params): 27 | oauth_user = self.search([("oauth_uid", "=", validation['user_id']), ('oauth_provider_id', '=', provider)]) 28 | if not oauth_user: 29 | return -1 30 | else: 31 | return self._auth_oauth_signin(provider, validation, params) 32 | 33 | @api.model 34 | def auth_oauth_third(self, provider, params): 35 | # Advice by Google (to avoid Confused Deputy Problem) 36 | # if validation.audience != OUR_CLIENT_ID: 37 | # abort() 38 | # else: 39 | # continue with the process 40 | access_token = params.get('access_token') 41 | validation = self._auth_oauth_validate(provider, access_token) 42 | # required check 43 | if not validation.get('user_id'): 44 | # Workaround: facebook does not send 'user_id' in Open Graph Api 45 | if validation.get('id'): 46 | validation['user_id'] = validation['id'] 47 | else: 48 | raise AccessDenied() 49 | 50 | # retrieve and sign in user 51 | login = self._auth_oauth_signin_third(provider, validation, params) 52 | if login==-1: 53 | return login, validation 54 | if not login: 55 | raise AccessDenied() 56 | # return user credentials 57 | return (self.env.cr.dbname, login, access_token) 58 | 59 | 60 | def is_available(self): 61 | return self.oauth_uid or super(ResUsers, self).is_available() 62 | 63 | def send_corp_text(self, text): 64 | msg = { 65 | "mtype": "text", 66 | "content": text, 67 | } 68 | self.partner_id.send_corp_msg(msg) 69 | 70 | @api.multi 71 | def send_corp_text_confirm(self): 72 | self.ensure_one() 73 | 74 | new_context = dict(self._context) or {} 75 | new_context['default_model'] = 'res.users' 76 | new_context['default_method'] = 'send_corp_text' 77 | new_context['record_ids'] = self.id 78 | return { 79 | 'name': u'发送企业微信消息', 80 | 'type': 'ir.actions.act_window', 81 | 'res_model': 'wo.confirm', 82 | 'res_id': None, 83 | 'view_mode': 'form', 84 | 'view_type': 'form', 85 | 'context': new_context, 86 | 'view_id': self.env.ref('weodoo.wo_confirm_view_form_send').id, 87 | 'target': 'new' 88 | } 89 | -------------------------------------------------------------------------------- /models/wo_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from odoo import models, fields, api 4 | 5 | 6 | class WeOdooConfig(models.Model): 7 | 8 | _name = 'wo.config' 9 | _description = u'WeOdoo设置' 10 | 11 | oauth_client_key = fields.Char('授权应用Key') 12 | oauth_client_secret = fields.Char('授权应用Secret') 13 | enable_wx_notify = fields.Boolean('启用企业微信通知', default=True) 14 | 15 | @api.multi 16 | def name_get(self): 17 | return [(obj.id, "WeOdoo 设置") for obj in self] 18 | 19 | 20 | @api.multi 21 | def write(self, vals): 22 | if "oauth_client_key" in vals: 23 | vals["oauth_client_key"] = vals["oauth_client_key"].lstrip().rstrip() 24 | result = super(WeOdooConfig, self).write(vals) 25 | third_provider = self.env.ref('weodoo.provider_third') 26 | if "oauth_client_key" in vals: 27 | third_provider.write({"client_id": vals["oauth_client_key"]}) 28 | return result 29 | -------------------------------------------------------------------------------- /models/wo_confirm_wizard.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from odoo import models, fields, api 4 | 5 | 6 | class WxConfirm(models.TransientModel): 7 | 8 | _name = 'wo.confirm' 9 | _description = u'确认' 10 | 11 | info = fields.Text("信息") 12 | model = fields.Char('模型') 13 | method = fields.Char('方法') 14 | 15 | api.multi 16 | def execute(self): 17 | self.ensure_one() 18 | active_ids = self._context.get('record_ids') 19 | rs = self.env[self.model].browse(active_ids) 20 | ret = getattr(rs, self.method)() 21 | return ret 22 | 23 | api.multi 24 | def execute_with_info(self): 25 | self.ensure_one() 26 | active_ids = self._context.get('record_ids') 27 | rs = self.env[self.model].browse(active_ids) 28 | ret = getattr(rs, self.method)(self.info) 29 | return ret 30 | -------------------------------------------------------------------------------- /rpc/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | import requests 4 | 5 | _logger = logging.getLogger(__name__) 6 | 7 | 8 | oauth_client_cache = {} 9 | 10 | def send_msg(env, slug_list, msg): 11 | if env.cr.dbname not in oauth_client_cache: 12 | obj = env.ref('weodoo.weodoo_config_default') 13 | oauth_client_cache[env.cr.dbname] = { 14 | "id": obj.id, 15 | "oauth_key": obj.oauth_client_key, 16 | "oauth_secret": obj.oauth_client_secret, 17 | } 18 | oauth_client = oauth_client_cache[env.cr.dbname] 19 | 20 | mtype = msg["mtype"] 21 | if mtype=="text": 22 | data = { 23 | "oauth_key": oauth_client["oauth_key"], 24 | "oauth_secret": oauth_client["oauth_secret"], 25 | "slug": ','.join(slug_list), 26 | "content": msg["content"], 27 | } 28 | url = "https://i.calluu.cn/auth3rd/send_text" 29 | ret = requests.post(url, data) 30 | _logger.info(ret) 31 | elif mtype=='card': 32 | data = { 33 | "oauth_key": oauth_client["oauth_key"], 34 | "oauth_secret": oauth_client["oauth_secret"], 35 | "slug": ','.join(slug_list), 36 | "title": msg["title"], 37 | "description": msg["description"], 38 | "url": msg["url"], 39 | "btntxt": msg.get("btntxt", "详情"), 40 | } 41 | url = "https://i.calluu.cn/auth3rd/send_card" 42 | ret = requests.post(url, data) 43 | _logger.info(ret) 44 | elif mtype=='image': 45 | pass 46 | elif mtype=='voice': 47 | pass 48 | -------------------------------------------------------------------------------- /security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | 3 | access_wo_config_group_system,wo_config.group_system,model_wo_config,base.group_system,1,1,0,0 -------------------------------------------------------------------------------- /views/res_users_views.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | res.users.form.inherit_weodoo 7 | res.users 8 | 9 | form 10 | 11 | 12 |