1. 程式人生 > >odoo 11 之signup_with_phone模塊分析

odoo 11 之signup_with_phone模塊分析

phone cap 輸入 文件中 模板 item XP AR 效果圖

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模塊分析