1. 程式人生 > 實用技巧 >odoo13學習---14 Web伺服器開發

odoo13學習---14 Web伺服器開發

使路徑可從網路訪問

我們將瞭解如何讓使用者可以訪問http://yourserver/path1/path2表單的URL。這可以是一個web頁面,也可以是返回要被其他程式使用的任意資料的路徑。在後一種情況下,通常使用JSON格式使用引數並提供資料。

準備

我們希望允許任何使用者查詢完整的圖書列表。此外,我們希望通過JSON請求向程式提供相同的資訊。

怎麼做呢?

我們需要新增控制器,按照慣例將其放入名為controllers的資料夾中:

1.新增一個controller /main.py檔案與HTML版本的頁面,如下所示:

class Main(http.Controller):
    @http.route(
'/my_library/books', type='http', auth='none') def books(self): books = request.env['library.book'].sudo().search([]) html_result = '<html><body><ul>' for book in books: html_result += "<li> %s </li>" % book.name html_result
+= '</ul></body></html>' return html_result

2.新增一個函式以JSON格式提供相同的資訊,如下面的示例所示:

    @http.route('/my_library/books/json', type='json', auth='none')
    def books_json(self):
        records = request.env['library.book'].sudo().search([])
        return records.read(['name'])

3.新增controllers/__init__.py檔案,如下所示:

from . import main

4.將控制器匯入到my_library/__init__.py檔案中,如下所示:

from . import controllers

重啟伺服器後,您可以在瀏覽器中訪問/my_library/books,並獲得一個圖書名稱的平面列表。要測試JSON- rpc部分,必須編寫一個JSON請求。一個簡單的方法是使用下面的命令來接收命令列輸出:

curl -i -X POST -H "Content-Type: application/json" -d "{}"localhost:8069/my_library/books/json

如果此時出現404錯誤,則例項上可能有多個數據庫可用。在這種情況下,Odoo不可能確定哪個資料庫將服務於請求。使用-db-filter='^yourdatabasename$'引數強制Odoo使用你安裝模組的確切資料庫。該路徑現在應該是可訪問的。

它是如何工作的…

這裡有兩個關鍵部分,我們的控制器是來自odoo.http.Controller,和我們使用的方法為內容裝飾著odoo.http.route_繼承odoo.http.Controller註冊控制器與Odoo路由系統類似於模型如何註冊,由odoo.models.Model繼承。同樣,Controller有一個元類來處理這個問題。

通常,外接程式處理的路徑應該以外接程式的名稱開始,以避免名稱衝突。當然,如果您擴充套件了一些附加元件的功能,您將使用該附加元件的名稱。

odoo.http.route

route decorator允許我們告訴Odoo一個方法首先應該是web可訪問的,而第一個引數決定了它在哪個路徑上可訪問。如果使用同一個函式服務多個路徑,還可以傳遞字串列表,而不是字串。type引數預設為http,並確定要服務的請求型別。嚴格地說,JSON是HTTP,將第二個函式宣告為type=' JSON '會使我們的工作變得容易得多,因為Odoo會為我們處理型別轉換。

現在不用擔心auth引數;這將在本章的限制訪問web可訪問路徑配方中討論。

返回值

Odoo對函式返回值的處理是由路由裝飾器的型別引數決定的。對於type='http',我們通常想要傳遞一些HTML,所以第一個函式只是返回一個包含它的字串。另一種方法是使用request.make_response(),它允許您控制要在響應中傳送的頭。因此,為了指示頁面最後更新的時間,我們可以將books()中的最後一行更改為以下程式碼:

return request.make_response(html_result, 
[ (
'Last-modified',
email.utils.formatdate((fields.Datetime.from_string(request.env[
'library.book'].sudo().search([], order='write_date desc', limit=1).write_date) -datetime.datetime(1970, 1, 1)).total_seconds(),usegmt=True)
),
])

這段程式碼將傳送一個last -modified頭以及我們生成的HTML,告訴瀏覽器最後一次修改列表的時間。我們可以從庫的write_date欄位中提取這些資訊。本模型。為了讓前面的程式碼片段發揮作用,你需要在檔案的頂部新增一些匯入,如下所示:

import email
import datetime
from odoo import fields

您還可以手動建立werkzeug的響應物件並返回該物件,但是這樣做不會帶來什麼好處。

手工生成HTML非常適合用於演示,但是千萬不要在生產程式碼中這樣做。始終使用模板,如第16章“Web客戶端開發”中的“建立或修改模板—QWeb”中所示,並通過呼叫request.render()返回模板。

這將免費為您提供本地化,並通過將業務邏輯與表示層分離使您的程式碼更好。此外,模板還提供了在輸出HTML之前轉義資料的函式。前面的程式碼容易受到跨站點指令碼攻擊(例如,如果使用者設法將指令碼標記插入到圖書名稱中)。

對於一個JSON請求,只需返回你想要移交給客戶端的資料結構;Odoo負責序列化。要做到這一點,您應該將自己限制在JSON可序列化的資料型別上,這些資料型別大致是字典、列表、字串、浮點數和整數。

odoo.http.request

請求物件是一個靜態物件,它引用當前處理的請求,其中包含您採取操作所需的所有內容。這裡最重要的方面是request.env屬性,它包含一個與模型的self.env相同的環境物件。這個環境被繫結到當前使用者(在前面的示例中沒有),因為我們使用了auth='none'。缺少使用者也是我們必須在示例程式碼中使用sudo()來呼叫所有模型方法的原因。

如果您習慣了web開發,那麼您會期望會話處理,這是完全正確的。 在OpenERPSession物件,使用request.session(這是相當薄包裝werkzeug的會話物件),和request.session.sid訪問會話ID。儲存會話值,只是把request.session當作字典,如以下示例所示:

request_session['你好']=‘世界’

request.session_get(“你好”)

注意,在會話中儲存資料與使用全域性變數沒有區別。如果有必要的話,請使用它。這通常是多請求操作的情況,比如website_sale模組中的簽出。在這種情況下,tiy sgiykd處理控制器中所有與會話有關的功能,而不是模型中。

有更多的…

route decorator可以有一些額外的引數,以便進一步定製其行為。預設情況下,所有的HTTP方法都是允許的,Odoo將傳遞的引數混合在一起。

使用methods引數,您可以傳遞一個要接受的方法列表,通常是['GET']或['POST']中的一個。

允許cross-origin請求(瀏覽器阻止AJAX和一些其他型別的請求域指令碼從何處載入以外,安全與隱私原因),設定cors引數為 *,允許請求從所有來源,或限制請求URI的來自這個URI。如果這個引數未設定(這是預設值),那麼Access-Control-Allow-Origin標頭檔案就沒有設定,剩下的就是瀏覽器的標準行為了。在我們的示例中,我們可能希望將其設定為/my_module/books/json,以便允許從其他網站提取的指令碼訪問圖書列表。

預設情況下,Odoo通過在每個請求上傳遞一個令牌,來保護某些型別的請求免受稱為“跨站點請求偽造”的攻擊。如果您想關閉它,可以將csrf引數設定為false,但請注意,通常這不是一個好主意。

請參閱以下要點以瞭解更多有關HTTP路由的資訊:

如果在同一個例項上託管多個Odoo資料庫,那麼不同的資料庫可能執行在不同的域上。在這種情況下,您可以使用--db-filter選項,也可以使用https://github.com/OCA/server-tools中的dbfilter_from_header模組,它可以幫助您基於域過濾資料庫。在撰寫本書時,這個模組還沒有遷移到版本12,但在出版時,它可能已經遷移到了版本12。


限制訪問web可訪問路徑

我們將探討Odoo為路由提供的三種身份驗證機制。我們將使用不同的身份驗證機制定義路由,以顯示它們的差異。

準備

在擴充套件上一個菜譜中的程式碼時,我們還將依賴於庫。書的第5章模型,應用程式模型,所以你應該得到它的程式碼,以便繼續。

怎麼做呢?

在controller /main.py中定義處理程式:

1.新增一個顯示所有書籍的路徑,如下面的例子所示:

    @http.route('/my_library/all-books', type='http', auth='none')
    def all_books(self):
        books = request.env['library.book'].sudo().search([])
        html_result = '<html><body><ul>'
        for book in books:
            html_result += "<li> %s </li>" % book.name
        html_result += '</ul></body></html>'
        return html_result

2.新增一個路徑,以顯示所有圖書,並指出當前使用者編寫了哪些圖書(如果有的話)。如下例所示:

    @http.route('/my_library/all-books/mark-mine', type='http', auth='public')
    def all_books_mark_mine(self):
        books = request.env['library.book'].sudo().search([])
        html_result = '<html><body><ul>'
        for book in books:
            if request.env.user.partner_id.id in book.author_ids.ids:
                html_result += "<li> <b>%s</b> </li>" % book.name
            else:
                html_result += "<li> %s </li>" % book.name
        html_result += '</ul></body></html>'
        return html_result

3.新增一個顯示當前使用者的圖書的路徑,如下所示:

    @http.route('/my_library/all-books/mine', type='http', auth='user')
    def all_books_mine(self):
        books = request.env['library.book'].search([
            ('author_ids', 'in', request.env.user.partner_id.ids),
        ])
        html_result = '<html><body><ul>'
        for book in books:
            html_result += "<li> %s </li>" % book.name
        html_result += '</ul></body></html>'
        return html_result

使用此程式碼,對於未經身份驗證的使用者,/my_library/all-books和/my_library/allbooks/標記-mine路徑看起來是相同的,而登入使用者在後一個路徑上可以看到他們的圖書以粗體顯示。未經身份驗證的使用者根本無法訪問/my_library/allbooks/ mine路徑。如果您嘗試在沒有經過身份驗證的情況下訪問它,那麼您將被重定向到登入螢幕。

它是如何工作的…

身份驗證方法之間的區別基本上可以從request.env.user的內容中看出。

對於auth='none',使用者記錄總是空的,即使通過身份驗證的使用者正在訪問該路徑。如果希望提供對使用者沒有依賴關係的內容,或者希望在伺服器範圍的模組中提供與資料庫無關的功能,請使用此功能。

auth='public'將未經身份驗證的使用者的使用者記錄設定為XML ID base.public_user的特殊使用者,將經過身份驗證的使用者的使用者記錄設定為使用者的記錄。如果您希望同時為未經過身份驗證的使用者和經過身份驗證的使用者提供功能,而經過身份驗證的使用者可以獲得一些額外功能,這是正確的選擇,如前面的程式碼所示。

使用auth='user'來確保只有經過身份驗證的使用者才能訪問您提供的內容。使用此方法,您可以確保request.env.user指向一個現有使用者。

有更多的…

身份驗證方法的魔力發生在基本外接程式的ir.http模型中。不管你傳遞給路徑中的auth引數的是什麼值,Odoo都會搜尋一個名為_auth_method_<yourvalue>的函式。因此,您可以通過繼承它並宣告一個負責所選身份驗證方法的方法來輕鬆地對其進行定製。

作為示例,我們將提供一種名為base_group_user的身份驗證方法,它只對當前登入的使用者是base.group_user組的一部分進行授權,如下面的示例所示:

from odoo import exceptions, http, models
from odoo.http import request
class IrHttp(models.Model):   _inherit = 'ir.http'   def _auth_method_base_group_user(self):     self._auth_method_user()     if not request.env.user.has_group('base.group_user'):       raise exceptions.AccessDenied()

現在您可以在decorator中說auth='base_group_user',並確保執行此路由處理程式的使用者是該組的成員。通過一個小技巧,您可以將其擴充套件到auth='groups(xmlid1,…)';它的實現留給讀者作為練習,但是包含在GitHub儲存庫示例程式碼Chapter14/r2_paths_auth/my_library/models/sample_auth_http.py中。

from odoo import exceptions, models
from odoo.http import request


class IrHttp(models.AbstractModel):
    _inherit = 'ir.http'

    @classmethod
    def _auth_method_base_group_user(cls):
        cls._auth_method_user()
        if not request.env.user.has_group('base.group_user'):
            raise exceptions.AccessDenied()

    # this is for the exercise
    @classmethod
    def _auth_method_groups(cls, group_xmlids=None):
        cls._auth_method_user()
        if not any(map(request.env.user.has_group, group_xmlids or [])):
            raise exceptions.AccessDenied()


    # the controller will be like this add this in main.py

    @http.route('/my_module/all-books/group_user', type='http',
                auth='base_group_user')
    def all_books_mine_base_group_user(self):
        # your code
        return ...

    # this is for the exercise
    @http.route('/my_module/all-books/groups', type='http',
                auth='groups(base.group_no_one)')
    def all_books_mine_groups(self):
        # your code
        return ...


使用傳遞給處理程式的引數

能夠顯示內容很好,但最好是顯示使用者輸入的結果。接收此輸入並對其作出反應的不同方法。與前面的一樣,我們將使用library_book模型。

怎麼做呢?

首先,我們將新增一個路由,它需要一個帶有圖書ID的傳統引數來顯示有關圖書的一些細節。然後,我們將做同樣的事情,但是我們將把我們的引數合併到路徑本身:

1.新增一個需要圖書ID作為引數的路徑,如下面的示例所示:

@http.route('/my_library/book_details', type='http', auth='none')
    def book_details(self, book_id):
        record = request.env['library.book'].sudo().browse(int(book_id))
        return u'<html><body><h1>%s</h1>Authors: %s' % (record.name,u', '.join(record.author_ids.mapped('name')) or 'none', )

如果你把瀏覽器指向/my_module/book_details?book_id=1,您應該會看到ID為1的書的詳細頁面。如果不存在,您將收到一個錯誤頁面。

2.新增一個路徑,我們可以在路徑中傳遞圖書的ID,如下所示:

    @http.route("/my_library/book_details/<model('library.book'):book>", type='http', auth='none')
    def book_details_in_path(self, book):
        return self.book_details(book.id)

第二個處理程式允許您轉到/my_module/book_details/1並檢視相同的頁面。

它是如何工作的…

預設情況下,Odoo(實際上是werkzeug)混合了GET和POST引數,並將它們作為關鍵字引數傳遞給處理程式。

因此,只需簡單地將函式宣告為期望一個名為book_id的引數,就可以將這個引數引入為GET (URL中的引數)或POST(通常通過<form>傳遞)。並將處理程式作為操作屬性)。

如果我們沒有為該引數新增預設值,那麼如果您嘗試在沒有設定該引數的情況下訪問該路徑,執行時將引發錯誤。

第二個例子利用了這樣一個事實:在werkzeug環境中,大多數路徑都是虛擬的。因此,我們可以簡單地將路徑定義為包含一些輸入。

在本例中,我們說希望將library.book的ID作為路徑的最後一個元件。冒號後的名稱是關鍵字引數的名稱。我們的函式將使用此引數作為關鍵字引數被呼叫。在這裡,Odoo負責查詢這個ID並提供一個瀏覽記錄,當然,只有當訪問這個路徑的使用者具有適當的許可權時,它才工作。假設圖書是一個瀏覽記錄,我們可以簡單地通過將book.id作為book_id引數傳遞來迴圈第一個示例的函式,從而給出相同的內容。

有更多的…

在路徑中定義引數是werkzeug提供的一項功能,稱為轉換器。Odoo添加了模型轉換器,它還定義了接受逗號分隔的id列表的轉換器模型,並將包含這些id的記錄集傳遞給處理程式。

轉換器的美妙之處在於,執行時將引數強制為期望的型別,而您只能使用正常的關鍵字引數。它們是作為字串交付的,您必須自己處理必要的型別轉換,如第一個示例所示。

內建的werkzeug轉換器包括int、float和string,但也包括更復雜的轉換器,如path、any或uuid。您可以在http://werkzeug.pocoo.org/docs/0.11/routing/#builtin-converters上查詢它們的語義。

另請參閱

如果你想了解更多關於HTTP路由,請參考以下幾點:Odoo的自定義轉換器在ir_http.py的基本模組中定義,並在ir.http的_get_converters類方法中註冊。作為練習,您可以建立自己的轉換器,它允許您訪問/my_library/book_details/Odoo+cookbook頁面來接收這本書的詳細資訊(如果您之前將它新增到您的庫中)。


修改現有的處理程式

當你安裝網站模組時,/website/info路徑顯示一些關於你的Odoo例項的資訊。在此菜譜中,我們將覆蓋此內容,以更改此資訊頁面的佈局,並更改所顯示的內容。

準備

安裝網站模組並檢查/website/info路徑。在此菜譜中,我們將更新/website/info路徑以提供更多資訊。

怎麼做呢?

我們必須修改現有的模板並覆蓋現有的處理程式。我們可以這樣做:

1.在一個名為views/templates的檔案中覆蓋qweb模板。xml,因為

如下:

2.重寫一個名為controllers/main的檔案中的處理程式。,如下例所示:

現在,在訪問info頁面時,我們只會在表中看到經過篩選的已安裝應用程式列表,而不是原始的定義列表。

它是如何工作的…

在第一步中,我們覆蓋了現有的QWeb模板。為了找出它是哪一個,您必須查閱原始處理程式的程式碼。通常,這將以類似於下面的程式碼結束,它告訴您需要重寫template_name:

return request_render('template_name', values)

在我們的例子中,處理程式使用了一個名為website_info的模板,但是這個模板立即被另一個名為website_show_website_info的模板擴充套件,因此覆蓋這個模板更方便。這裡,我們用一個表替換了顯示已安裝應用程式的定義列表。有關QWeb繼承如何工作的詳細資訊,請參閱第16章Web客戶端開發。

為了覆蓋處理程式方法,我們必須標識定義處理程式的類,在本例中是odoo_addons_website_controllers_main_Website。我們需要匯入類以便能夠從它繼承。現在,我們可以覆蓋該方法並更改傳遞給響應的資料。注意,為了簡潔起見,這裡被覆蓋的處理程式返回的是一個響應物件,而不是前面菜譜所返回的HTML字串。此物件包含對要使用的模板的引用和模板可訪問的值,但僅在請求的最後才計算它。

通常,有三種方法來改變一個現有的處理程式:

如果它使用QWeb模板,最簡單的更改方法是覆蓋模板。這是佈局變化和小邏輯變化的正確選擇。

QWeb模板獲得傳遞的上下文,該上下文在響應中作為qcontext成員可用。這通常是一個字典,您可以根據需要在其中新增或刪除值。在前面的例子中,我們只過濾了網站上的應用程式列表。

如果處理程式接收引數,您還可以對這些引數進行預處理,以便覆蓋的處理程式按您希望的方式執行。

有更多的…

如前所述,控制器的繼承與模型繼承的工作方式略有不同;實際上,您需要對基類進行引用,並在其上使用Python繼承。

不要忘記用@http_route裝飾器裝飾你的新處理器;Odoo使用它作為一個標記,用於將哪些方法暴露給網路層。

如果省略了decorator,則實際上使處理程式的路徑不可訪問。

@http_route裝飾器本身的行為類似於欄位宣告:您沒有設定的每個值都將派生自您正在重寫的函式的裝飾器,因此我們不必重複我們不想更改的值。

在從你覆蓋的函式接收到一個響應物件後,你可以做的不僅僅是改變QWeb上下文:

您可以通過操作response_headers來新增或刪除HTTP headers

如果您想呈現一個完全不同的模板,您可以覆蓋response_template

要首先檢測響應是否基於QWeb,請查詢response_is_qweb

通過呼叫response_render()可以得到結果HTML程式碼

另請參閱

QWeb模板的細節將在第16章Web客戶端開發中解釋。