odoo 11 之signup_with_phone模塊分析
signup_with_phone模塊的主要功能是允許用戶用自己的手機號作為註冊登錄賬號,這裏會進行手機號碼格式的嚴格檢查,該模塊依賴odoo自帶的auth_signup註冊模塊。
該項目地址在https://github.com/odooaktiv/SignupWithPhone,不過目前是10.0版本的,稍微修改下也可以在11.0版本下使用。下面分析其具體原理。
第一部分是使用到的XML布局文件
1.auth_signup模塊的auth_signup_login_templates.xml文件
這個文件包含4個模板,對應的是auth_signup.login,auth_signup.fields,auth_signup.signup,auth_signup.reset_password。
我們來一一分析每一個模板的內容,首先是auth_signup.login模板
<template id="auth_signup.login" inherit_id="web.login" name="Sign up - Reset Password"> <xpath expr="//button[@type=‘submit‘]" position="before"> <a t-if="signup_enabled" t-attf-href="/web/signup?{{ keep_query() }}" class="btn btn-link pull-right">Don‘t have an account?</a> <a t-if="reset_password_enabled" t-attf-href="/web/reset_password?{{ keep_query() }}" class="btn btn-link pull-right">Reset Password</a> </xpath> </template>
這個模板繼承了我們的登錄模板web.login,主要功能是找到其中的submit按鈕,在這個按鈕之前放2個鏈接,一個顯示Don‘t have an account,一個顯示Reset Password.
但是這裏的每個鏈接都在class屬性上使用了bootstrap中的pull-right樣式,最終的結果不是顯示在submit按鈕左側,而是右側,如下圖所示
默認情況下這兩個鏈接是不現實的,因為每個鏈接都有個t-if判斷值,odoo系統默認是不設置的,如果需要只能手動設置,設置的位置如下:
第2個模板是auth_signup.fields,此模板會被第三個模板調用
<template id="auth_signup.fields" name="Auth Signup/ResetPassword form fields"> <div class="form-group field-login"> <label for="login" class="control-label">Your Email</label> <input type="text" name="login" t-att-value="login" id="login" class="form-control" autofocus="autofocus" autocapitalize="off" required="required" t-att-readonly="‘readonly‘ if only_passwords else None"/> </div> <div class="form-group field-name"> <label for="name" class="control-label">Your Name</label> <input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="e.g. John Doe" required="required" t-att-readonly="‘readonly‘ if only_passwords else None" t-att-autofocus="‘autofocus‘ if login and not only_passwords else None" /> </div> <div class="form-group field-password"> <label for="password" class="control-label">Password</label> <input type="password" name="password" id="password" class="form-control" required="required" t-att-autofocus="‘autofocus‘ if only_passwords else None"/> </div> <div class="form-group field-confirm_password"> <label for="confirm_password" class="control-label">Confirm Password</label> <input type="password" name="confirm_password" id="confirm_password" class="form-control" required="required"/> </div> </template>
這裏主要設置了註冊時需要填寫的字段,包含Email, Name, Password, Confirm Password 4個input文本。
第3個模板是auth_signup.signup,主要負責將註冊界面組合成一個整體
<template id="auth_signup.signup" name="Sign up login"> <t t-call="web.login_layout"> <form class="oe_signup_form" role="form" method="post" t-if="not message"> <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> <t t-call="auth_signup.fields"> <t t-set="only_passwords" t-value="bool(token and not invalid_token)"/> </t> <p class="alert alert-danger" t-if="error"> <t t-esc="error"/> </p> <input type="hidden" name="redirect" t-att-value="redirect"/> <input type="hidden" name="token" t-att-value="token"/> <div class="clearfix oe_login_buttons"> <a t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link pull-right">Already have an account?</a> <button type="submit" class="btn btn-primary pull-left o_signup_btn"> Sign up</button> </div> </form> </t> </template>
這裏還調用了web.login_layout模板,主要實現頭部的odoo圖標和底部的數據庫連接效果,此處不做詳細分析。其余代碼有3大作用,一是調用模板2 auth_signup.fields,用於顯示
註冊的填寫字段,二是如果填寫不合規範的時候,有錯誤提醒,三是左側顯示‘‘Sign Up按鈕”,右側顯示“Already have an account?”鏈接。
填寫內容不合規範的時候,提示錯誤的信息截圖
第4個模板是auth_signup.reset_password,主要用於給用戶發郵件,進行密碼的重設
<template id="auth_signup.reset_password" name="Reset password"> <t t-call="web.login_layout"> <div t-if="message"> <p class="alert alert-success" t-if="message"> <t t-esc="message"/> </p> <a href="/web/login" class="btn btn-link pull-right">Back to Login</a> </div> <form class="oe_reset_password_form" role="form" method="post" t-if="not message"> <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> <t t-if="token and not invalid_token"> <t t-call="auth_signup.fields"> <t t-set="only_passwords" t-value="1"/> </t> </t> <t t-if="not token"> <div class="form-group field-login"> <label for="login" class="control-label">Your Email</label> <input type="text" name="login" t-att-value="login" id="login" class="form-control" autofocus="autofocus" required="required" autocapitalize="off"/> </div> </t> <p class="alert alert-danger" t-if="error"> <t t-esc="error"/> </p> <input type="hidden" name="redirect" t-att-value="redirect"/> <input type="hidden" name="token" t-att-value="token"/> <div class="clearfix oe_login_buttons"> <a t-if="not token" t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link pull-right">Back to Login</a> <a t-if="invalid_token" href="/web/login" class="btn btn-link pull-right">Back to Login</a> <button type="submit" class="btn btn-primary pull-left">Confirm</button> </div> </form> </t> </template>
這裏像前面的模板3一樣,調用了web.login_layout模板,還有根據一些屬性值來判斷是不是顯示html控件,目前我們不關系這些。這裏的主要代碼在與顯示"Your Email"輸入框,“Confirm”按鈕和“Back to Login”鏈接。
如果有錯誤,顯示提示內容:
到此,auth_signup模塊的xml主要模板全部理完,下面開始signupWithPhone模塊的XML分析。
2.signupWithPhone模塊的auth_signup_inherit.xml分析
這裏主要包含2個模板,change_email_label和auth_signup.fields。
change_email_label模板主要改變登錄界面的Email為Email/Mobile,提示用戶既可以使用郵件也可以是電話
<!-- Change Email label to Email/Mobile in Signin Form. --> <template id="change_email_label" inherit_id="web.login"> <xpath expr="//form/div/label[@for = ‘login‘]" position="replace"> <label for="login" class="control-label">Email/Mobile</label> </xpath> </template>
auth_signup.fields模板主要是對auth_signup中的auth_signup.fields的重寫,多了個Mobile字段,也是我們今天的主要字段。
<!-- override sign up form And add mobile field--> <template id="auth_signup.fields"> <div class="form-group field-login"> <label for="login" class="control-label">Your Email</label> <input type="text" name="login" t-att-value="login" id="login" class="form-control" autofocus="autofocus" autocapitalize="off" t-att-readonly="‘readonly‘ if only_passwords else None" /> </div> <div class="form-group field-name"> <label for="name" class="control-label">Your Name</label> <input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="e.g. John Doe" required="required" t-att-readonly="‘readonly‘ if only_passwords else None" t-att-autofocus="‘autofocus‘ if login and not only_passwords else None" /> </div> <div class="form-group field-mobile"> <label for="mobile" class="control-label">Mobile</label> <input type="text" name="mobile" id="mobile" class="form-control" placeholder="e.g. +919876543210"/> </div> <div class="form-group field-password"> <label for="password" class="control-label">Password</label> <input type="password" name="password" id="password" class="form-control" required="required" t-att-autofocus="‘autofocus‘ if only_passwords else None" /> </div> <div class="form-group field-confirm_password"> <label for="confirm_password" class="control-label">Confirm Password</label> <input type="password" name="confirm_password" id="confirm_password" class="form-control" required="required" /> </div> </template>
效果圖如下
這樣涉及到的XML視圖文件都已分析完畢。
第二部分 原理分析
這裏要解決的一個問題,就是signupWithPhone是怎麽做到可以使用電話號碼作為登錄賬號的呢?
首先在signupWithPhone模塊裏面沒有modles文件夾,也就意味著沒有對原來類或表做任何字段的增加,只能說是Mobile手機字段是存儲到了已有表的某個字段中了,帶著這個疑問我們來看看後臺的註冊代碼。
這裏只有一個AuthSignupController類,繼承於web/controllers/main.py文件中的Home類,雖然是繼承Home這個Controller類,但此處的AuthSignupController和auth_signup模塊中的類名一樣,這裏的類自然是對auth_signup模塊裏類的方法的重寫和擴展。這裏有3個方法,
這裏我們重點關心get_auth_signup_qcontext方法
def get_auth_signup_qcontext(self): """ Shared helper returning the rendering context for signup and reset password. Check Condition If Email not Exists, then Signup with Mobile. """ if request.params.items() and request.params[‘mobile‘]: try: carrier._is_mobile(number_type(phonenumbers.parse(request.params[‘mobile‘]))) except NumberParseException: request.params[‘error‘] = _("Please Enter Valid Mobile Number") if request.params.items() and request.params[‘login‘]: if not tools.single_email_re.match(request.params[‘login‘]): request.params[‘error‘] = _("Please Enter Valid Email") if request.params.items() and request.params[‘mobile‘] and request.params[‘login‘] == ‘‘: request.params[‘login‘] = request.params[‘mobile‘] qcontext = request.params.copy() qcontext.update(self.get_auth_signup_config()) if qcontext.get(‘token‘): try: # retrieve the user info (name, login or email) corresponding to a signup token token_infos = request.env[‘res.partner‘].sudo().signup_retrieve_info(qcontext.get(‘token‘)) for k, v in token_infos.items(): qcontext.setdefault(k, v) except: qcontext[‘error‘] = _("Invalid signup token") qcontext[‘invalid_token‘] = True return qcontext
以上紅色代碼表示,如果註冊了電話號碼,沒有填寫郵件,那麽將會把mobile字段對應的值,賦值給login參數
def do_signup(self, qcontext): """ Override do_signup for Create User & Partner with Extra field Mobile. """ values = { key: qcontext.get(key) for key in (‘login‘, ‘name‘, ‘password‘,‘mobile‘) } assert values.get(‘password‘) == qcontext.get(‘confirm_password‘), "Passwords do not match; please retype them." supported_langs = [lang[‘code‘] for lang in request.env[‘res.lang‘].sudo().search_read([], [‘code‘])] if request.lang in supported_langs: values[‘lang‘] = request.lang self._signup_with_values(qcontext.get(‘token‘), values) request.env.cr.commit()
以上的紅色代碼,抽取了對應的login,name,password,mobile 4個參數的值,當然,此時的login和mobile值是一樣的,如果用戶沒有填寫郵件的話。
這樣signupWithPhone模塊,就完成了使用電話號碼的註冊過程,而不僅僅是只允許用戶使用郵件註冊。
註冊的過程中,auth_signup模塊,而作為管理員在後臺新增用戶的時候,或者使用Oauth2進行登錄的時候,用戶名都沒有進行驗證是否屬於郵件格式,其他都可以作為登錄賬號。
那麽這裏就有一個問題,就是在找回密碼的時候,問題就變得復雜了,分3種情況,第一種是郵件的,第二種是電話號碼的,第三種是非郵件亦不是電話號碼的。對於前2種情況,在重設密碼的時候,可以分別使用發送郵件和發送手機驗證碼的方式實現對密碼的修改,第三種就暫時無法處理。
貌似對於重設密碼的問題,需要單獨開一篇文章來討論了,待續.....
odoo 11 之signup_with_phone模塊分析