第十四章、CMS網站開發**
Odoo有一個功能齊全的內容管理系統(CMS)。通過拖放功能,你的最終用戶可以在幾分鍾內設計一個頁面,但是在Odoo CMS中開發一個新功能或構建塊就不是那么簡單了。在本章中,您將探索Odoo的前台開發。您將學習如何創建網頁。您還將學習如何創建用戶可以在頁面上拖放的構建塊。進階內容,如Urchin跟蹤模塊(UTMs),搜索引擎優化(SEO),多網站,GeoIP,和網站地圖也涵蓋在這一章。簡而言之,您將了解開發交互式網站所需的所有內容。
重要信息
所有的Odoo CMS功能都是通過website和web_editor模塊實現的。如果您想了解CMS在內部是如何工作的,請查看這兩個模塊。你可以在這里找到代碼在行動視頻:http://bit.ly/2UH0eMM。
本章將涵蓋如下內容:
- 管理靜態資源
- 為網站添加CSS及JavaScript
- 創建或修改QWeb模板
- 配置動態路由
- 為用戶提供靜態代碼片段
- 為用戶提供動態代碼片段
- 獲取網站用戶輸入的數據
- 管理SEO配置項
- 管理站點地圖
- 獲取訪客的國家信息
- 跟蹤營銷活動
- 管理多網站
- 重定向老的URL
- 發布網站
管理靜態資源
現代網站包含了大量的JavaScript和CSS文件。當頁面加載到瀏覽器中時,這些靜態文件向服務器發出單獨的請求。請求次數越多,網站速度越慢。為了避免這個問題,大多數網站通過組合多個文件來提供靜態資產。市場上有一些工具可以管理這類東西,但是Odoo有自己的實現來管理靜態資產。
什么是資源包以及有哪些資源?
在Odoo中,靜態資產管理並不像在其他應用中那么簡單。Odoo有很多不同的應用程序和代碼庫。不同的Odoo應用程序有不同的用途和ui。這些應用程序不共享公共代碼,所以在某些情況下,我們想加載一些資產,但我們不想對所有情況都這樣做。在頁面上加載不必要的靜態資產不是一個好做法。為了避免在所有應用程序中加載額外的資源,Odoo使用了資源包的概念。資產包的工作是將所有JavaScript和CSS組合在一個文件中,並通過最小化它來減少其大小。Odoo代碼庫中有資產包,不同的代碼庫也有不同的資產包。
以下是Odoo中使用的不同資產包:
- web.assets_common: 這個資產包包括所有應用程序通用的所有基本實用程序,如jQuery、Underscore.js、Font Awesome等。該資產包用於前端(網站)、后端、銷售點、報告等。這個公共資產在Odoo幾乎無處不在。它還包含用於Odoo模塊系統的boot.js文件。
- web.assets_backend: 這個資產包用於Odoo(企業資源規划(ERP)部分)的后端。它包含與web客戶機、視圖、字段小部件、操作管理器等相關的所有代碼。
- web.assets_frontend|website.assets_frontend: 這個資產包用於Odoo的前端(網站部分)。它包含了所有相關的代碼到網站端應用程序,如電子商務、博客、在線事件、論壇、實時聊天等。注意,這個資產包不包含與網站編輯或拖放特性(網站構建器)相關的代碼。這背后的原因是,我們不想加載編輯器資產的公共使用的網站。
- website.assets_editor|web_editor.summernote: 這個資產包包含與網站編輯片段選項和拖放功能(網站構建器)相關的代碼。只有當用戶擁有編輯權限時,才會在網站上加載它。它也被用於群發郵件的設計者。
- web.report_assets_common: QWeb報告只是從HTML生成的PDF文件。該資產被加載到報告布局中。
重要信息
還有一些其他用於特定應用的資產包:point_ of_sale.assets, survey.survey_assets, mass_mailing. layout, and website_slides.slide_embed_assets
Odoo通過AssetBundle類來管理它的靜態資產
,它位於/odoo/addons/base/models/assetsbundle.py。現在,AssetBundle不僅可以組合多個文件;它還有更多的功能。以下是它提供的特性列表:
- 它結合了多個JavaScript和CSS文件。
- 它通過從文件內容中刪除注釋、額外空格和回車來減少JavaScript和CSS文件。刪除這些額外的數據將減少靜態資產的大小,並提高頁面加載速度。
- 它內置了對CSS預處理器的支持,比如SCSS和LESS。這意味着您可以添加SCSS和更少的文件,它們將被自動編譯並添加到包中。
自定義資源
正如我們所看到的,Odoo針對不同的代碼庫有不同的資產。要獲得正確的結果,您需要選擇正確的資產包,將定制的JavaScript和CSS文件放入其中。例如,如果你正在設計一個網站,你需要把你的文件加載到web.assets_frontend。雖然這種情況很少見,但有時您需要創建一個全新的資產包。您可以創建自己的資產包,我們將在下一節中進行描述。
步驟
- 創建QWeb模板並添加JavaScript、CSS或SCSS文件,如下所示:
<template id="my_custom_assets" name="My Custom Assets">
<link rel="stylesheet" type="text/scss" href="/my_module/static/src/scss/my_scss.scss"/>
<link rel="stylesheet" type="text/css" href="/my_module/static/src/scss/my_css.css"/>
<script type="text/JavaScript" src="/my_module/static/src/js/widgets/my_ JavaScript.js"/>
</template>
- 使用t-call-assets在QWeb模板中,你想加載這個包,如下:
<template id="some_page">
...
<head>
<t t-call-assets="my_module.my_custom_assets" t-js="false"/>
<t t-call-assets="my_module.my_custom_assets" t-css="false"/>
</head>
...
原理
步驟1,我們使用my_custom_assets外部ID創建了新的QWeb模板。在這個模板中,您需要列出所有的CSS、SCSS和JavaScript文件。首先,Odoo會將SCSS文件編譯成CSS,然后將所有CSS和JavaScript文件合並成一個單獨的CSS和JavaScript文件。
步驟2,我們已經在模板中加載了CSS和JavaScript資源。t-css和t-js屬性只用於加載樣式表或腳本。
重要信息
在大多數網站開發中,您需要將JavaScript和CSS文件添加到現有的資產包中。添加新的資產包是非常罕見的。只有當你想開發沒有Odoo CMS功能的頁面/應用程序時才需要它。在下一個章節中,您將學習如何將自定義CSS/JavaScript添加到現有的資產包中。
更多
在Odoo中調試JavaScript非常困難,因為AssetBundle會將多個JavaScript文件合並到一個文件中,並將其最小化。通過使用資產啟用developer模式,您可以跳過資產綁定,頁面將單獨加載靜態資產,這樣您就可以輕松調試。
組合資產生成一次並存儲在ir中。附件的模型。在那之后,它們從附件中被送達。如果你想重新生成資產,你可以通過調試選項,如下圖所示:
小貼士
odoo默認在啟動的時候會生成一次資源,如果中途修改了資源的內容,那么就需要重啟才可以生效。為了方便開發,我們可以在啟動odoo實例的時候添加dev=xml,這將直接加載資產,而不需要重新啟動服務器。
為網站添加CSS及JavaScript
在本節,我們將介紹如何向網站添加CSS和JavaScript。
准備
我們使用第三章,創建odoo模塊,中的my_library模塊。你可以從 [GitHub](https://github.com/PacktPublishing/Odoo-14-Development-Cookbook-Fourth- Edition/tree/master/Chapter14/00_initial_module/my_library) 下載。我們將添加CSS、SCSS和JavaScript文件,這些文件將修改網站。因為我們正在修改網站,我們將需要添加網站作為依賴。像這樣修改清單文件:
...
'depends': ['base', 'website'],
...
步驟
- 添加一個名為views/templates.xml的文件,並添加一個空視圖覆蓋,如以下(不要忘記在__manifest__.py中列出文件):
<odoo>
<template id="assets_frontend" inherit_id="web.assets_frontend">
<xpath expr="." position="inside">
<!-- points 2 & 3 go here /-->
</xpath>
</template>
</odoo>
- 添加CSS和SCSS文件的引用,如下所示:
<link href="/my_library/static/src/css/my_library.css" rel="stylesheet" type="text/css"/>
<link href="/my_library/static/src/scss/my_library.scss" rel="stylesheet" type="text/scss"/>
- 添加一個引用到你的JavaScript文件,如下所示:
<script src="/my_library/static/src/js/my_library.js" type="text/javascript" />
- 在靜態/src/ CSS /my_library中添加一些CSS代碼。css,如下:
body main {
background: #b9ced8;
}
- 在靜態/src/ SCSS /my_library中添加一些SCSS代碼。scss,如下所示:
$my-bg-color: #1C2529;
$my-text-color: #D3F4FF;
nav.navbar {
background-color: $my-bg-color !important;
.navbar-nav .nav-link span {
color: darken($my-text-color, 15);
font-weight: 600;
}
}
footer.o_footer {
background-color: $my-bg-color !important;
color: $my-text-color;
}
- 在static/src/js/my_library.js中添加一些JavaScript代碼,如下所示:
odoo.define('my_library', function (require) {
var core = require('web.core');
alert(core._t('Hello world'));
return {
// if you created functionality to export, add ithere
}
});
更新模塊后,你應該看到Odoo網站在菜單、正文和頁腳有自定義顏色,並且在每個頁面加載時都有一個Hello World彈出窗口,如下圖所示:
原理
odoo的CMS依賴於名為QWeb的XML模板引擎,我們將在下一節中詳細介紹。資源包通過QWeb模板引入。在步驟1、2、3中,我們擴展了web.assets_frontend文件加載樣式及js文件。我們選擇web.assets_frontend是因為每一個網頁都會加載這些文件。
步驟4,我們添加了CSS文件,用於設置網站的背景顏色。
小貼士
對於CSS/SCSS文件,有時順序很重要。因此,如果您需要覆蓋在另一個附加組件中定義的樣式,則必須確保您的文件在您想要修改的原始文件之后加載。這可以通過調整視圖的優先級字段或直接繼承附加組件的視圖來實現,該視圖將引用注入到CSS文件中。詳細信息,請參閱第9章“后端視圖”中的“更改現有視圖-視圖繼承配方”。
步驟5,我們添加了SCSS文件。odoo支持SCSS的預處理程序,將自動將SCSS編譯為CSS文件。在我們的例子中,我們設置了幾個變量及使用了darken的函數(可將$my-text-color變暗15%)。SCSS預處理器還有很多其他功能;如果你想了解更多關於SCSS的信息,請參考http://sass-lang.com/。
步驟6,我們添加了js文件,用於在頁面加載完后彈框。為了避免JavaScript的排序問題,Odoo使用了一種非常類似於RequireJS的機制。在我們的JavaScript文件中,我們調用了odoo.define(),它需要兩個參數:您想要定義的名稱空間和包含實際實現的函數。如果您正在開發一個廣泛使用JavaScript的復雜產品,那么您可以將代碼划分為邏輯上不同的部分,並在不同的函數中定義它們。這將非常有用,因為您可以通過require導入函數來重用它們。此外,要定義模塊的命名空間,請添加附加組件的名稱,將其作為前綴,並用點分隔,以避免將來的命名沖突。如web模塊下的,web.core和web.data。
對於第二個參數,definition函數只接收一個參數require,這個函數可以用來獲取對其他模塊中定義的JavaScript名稱空間的引用。在所有與Odoo的交互中使用這個,並且永遠不要依賴全局Odoo對象。
然后,您自己的函數可以返回一個對象,該對象指向您希望為其他附加組件提供的引用,或者如果沒有此類引用,則不指向任何引用。如果你已經從你的函數返回了一些引用,你可以在另一個函數中使用它們,如下面的例子所示:
odoo.define('my_module', function (require) {
var test = {
key1: 'value1',
key2: 'value2'
};
var square = function (number) {
return 2 * 2;
};
return {
test: test,
square: square
}
});
// In another file
odoo.define('another_module', function (require) {
var my_module = require('my_module');
console.log(my_module.test.key1);
console.log('square of 5 is', my_module.square(5));
});
更多
為了提高性能,Odoo只在前端加載最少的JavaScript。一旦頁面被完全加載,資源中的所有其他JavaScript將被惰性加載,並且最小的可用資源擁有web.assets_frontend_minimal_js ID。
創建或修改QWeb模板
我們將在第四章“應用模型”中開發的my_library附加組件中添加網站功能。我們感興趣的是允許用戶瀏覽圖書館,如果他們以適當的權限登錄,允許他們從網站界面編輯圖書詳細信息。
准備
本節,我們將使用來自https://github.com/ PacktPublishing/oodoo-14-developing-cookbook-fourth-edition /tree/master/Chapter14/00_initial_module/my_library目錄的my_library,該目錄來自本書的GitHub存儲庫。
步驟
- 在controllers/main.py中添加展示圖書列表的控制器,如下所示:
from odoo import http
from odoo.http import request
class Main(http.Controller):
@http.route('/books', type='http', auth="user", website=True)
def library_books(self):
return request.render('my_library.books', {
'books': request.env['library.book'].search([]),
})
- 在views/templates.xml中添加最小模板(確保您已經在清單中添加了views/templates.xml文件):
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="books">
<t t-call="website.layout">
<!-- Add page elements here -->
</t>
</template>
</odoo>
- 在website.layout,添加可拖拽元素(class="oe_structure"),如下:
<div class="oe_structure">
<section class="pt32 pb32 bg-secondary oe_custom_bg">
<div class="container text-center">
<h1> Editable text and supports drag and drop.</h1>
</div>
</section>
</div>
- 在website.layout中添加代碼塊,以展示圖書的信息,如下:
<div class="container">
<t t-foreach="books" t-as="book">
<div t-attf-class="card mt-3 #{'bg-info' if book_ odd else ''}">
<div class="card-body" id="card_body">
<h3 t-field="book.name"/>
<t t-if="book.date_release">
<div t-field="book.date_release" class="text-muted"/>
</t>
<b class="mt8"> Authors </b>
<ul>
<li t-foreach="book.author_ids" t-as="author">
<span t-esc="author.name" />
</li>
</ul>
</div>
</div>
</t>
</div>
- 在website.layout中添加不可編輯的元素:
<section class="container mt16" contenteditable="False"> This is a non-editable text after the list of books. </section>
在瀏覽器中打開http://your-server-url:8069/books,您將能夠看到圖書列表和作者。通過這段代碼,用戶可以看到圖書及其詳細信息的列表。如果有適當的權限,用戶還可以更改圖書細節和其他文本。
原理
步驟1,我們有一個路由處理器接收用戶自定義參數。這些參數將從處理器傳遞給QWeb模板。
步驟2、3、4、5,我們創造了一個名為Books的模板,用於生成用於展示圖書的HTML的代碼。代碼由t元素包裹,並通過t-call屬性調用website.layout模板,odoo將渲染website.layout模板,並將我們生成的HTML代碼插入其中。website.layout包含必要的文件,比如Bootstrap、JQery、Font Awesome等。這些文件用於設計web頁面。website.layout還包含了默認的頭部、尾部、代碼塊及頁面編輯功能。這樣,我們得到一個完整的Odoo網頁與菜單,頁腳,頁面編輯功能,而不必重復代碼在所有頁面。
步驟3、4、5,我們再website.layout中添加了HTML代碼及QWeb模板的屬性。HTML將展示圖書的列表。一些常用的QWeb屬性及他們用法如下:
循環
要處理記錄集或可迭代數據類型,你需要一個機構循環遍歷列表,t-foreach,單個元素通過t元素實現。如下:
<t t-foreach="[1, 2, 3, 4, 5]" t-as="num">
<p><t t-esc="num"/></p>
</t>
渲染后結果如下:
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
你可以在任意元素中使用t-foreach及t-as屬性。這時,在迭代器中,這個元素及其內容將會重復渲染。如下將生成上面一樣的HTML代碼:
<p t-foreach="[1, 2, 3, 4, 5]" t-as="num">
<t t-esc="num"/>
</p>
在t-foreach循環中,還有幾個額外的變量,變量名將根據t-as設置的value組合而來。如前面的t-as=book的例子,book-odd變量將在迭代次數為奇數時為True,偶數時為False。在本例中,我們使用這個方法來為我們的卡片設置交替的背景顏色。
以下是其他可用的變量:
- book_index: 將返回當前迭代的序號(0開始)
- book_first、book_last: 如果迭代號第一個或者最后一個時為True
- book_value: 如果book是一個字段,那么將返回他的值,本案例中將返回字典所有的鍵值。
- book_size: 返回列表的大小。
- book_even、book_odd: 偶數、基數為True
- book_parity: 在迭代時,偶數索引包含偶數值,奇數索引包含奇數值。
重要小貼士
這里的示例基於我們的場景。在本例中,您需要用t-as屬性的給定值替換book。
動態屬性
QWeb模板可以動態設置屬性值。這可以通過以下三種方式實現。
第一種方法是通過t-att-$attr_name。在模板呈現時,創建了一個屬性$attr_name;它的值可以是任何有效的Python表達式。這是通過當前上下文計算的,結果設置為屬性的值,如下所示:
<div t-att-total="10 + 5 + 5"/>
渲染后為:
<div total="20"></div>
第二種方法是通過t-attf-$attr_name。這與前面的選項類似。唯一的區別是只有字符串之間的{{..}}和#{…}會被計算。主要用於計算類,如下例所示:
<t t-foreach="['info', 'danger', 'warning']" t-as="color">
<div t-attf-class="alert alert-#{color}">
Simple bootstrap alert
</div>
</t>
渲染后為:
<div class="alert alert-info">
Simple bootstrap alert
</div>
<div class="alert alert-danger">
Simple bootstrap alert
</div>
<div class="alert alert-warning">
Simple bootstrap alert
</div>
第三種方法是通過t-att=mapping屬性。該選項在將呈現字典數據的模板轉換為屬性和值之后接受字典。看看下面的例子:
<div t-att="{'id': 'my_el_id', 'class': 'alert alert- danger'}"/>
渲染后如下:
<div id="my_el_id" class="alert alert-danger"/>
Fields
h3和div標簽使用t-field屬性。t-field的值必須是長度為1的數據集。這可以在頁面以編輯模式打開的時候可編輯。當用戶保存后修改的值可更新到數據庫。當然,當前用戶必須具備訪問權限才可以哦。通過t-options屬性,你可以將一個字典傳遞給字段渲染器,包括想要使用的widget。目前,后端還沒有大量的小部件集合,所以這里的選擇有點有限。例如,你想展示一個圖片,可如下:
<span t-field="author.image_small" t-options="{'widget': 'image'}"/>
t-field有一些限制。它僅作用於數據集且不能作用於
Conditionals 條件
注意,顯示出版日期的部分由t元素包裝,t-if屬性設置。此屬性計算規則符合python的代碼邏輯,元素只有在判斷條件為true的時候才進行渲染。如下的例子,只有設置了出版日期的時候顯示div。然而,在復雜的邏輯下,還需要用到t-elif和t-else,如下:
<div t-if="state == 'new'">
Text will be added of state is new.
</div>
<div t-elif="state == 'progress'">
Text will be added of state is progress.
</div>
<div t-else="">
Text will be added for all other stages.
</div>
設置變量
QWeb模板還能夠在模板本身中定義變量。定義模板之后,可以在后續模板中使用該變量。你可以這樣設置變量:
<t t-set="my_var" t-value="5 + 1"/>
<t t-esc="my_var"/>
子模板
如果您正在開發一個大型應用程序,管理大型模板可能會很困難。QWeb模板支持子模板,因此您可以將大型模板划分為較小的子模板,並且可以在多個模板中重用它們。對於子模板,你可以使用t-call屬性,就像下面這個例子:
<template id="first_template">
<div> Test Template </div>
</template>
<template id="second_template">
<t t-call="first_template"/>
</template>
Inline editing 內聯編輯
對於一些通過t-field方式加載的數據,可以編輯模式下進行修改。同時,為元素添加class oe_structure時,可以允許用戶對該元素進行修改及拖拽。若需要禁用網站某個區域的編輯功能,可設置contenteditable=False屬性。步驟5中,我們在
小貼士
為了確保頁面在多網站可用,當我們通過網站編輯器編輯網頁的時候,其實odoo為我們創建了一個單獨的副本。因此當我們對某些代碼進行更新的時候,對於我們已經修改過的代碼是不生效的。為了兼顧內聯編輯的易用性及方便在后續進行代碼更新的時候同時生效,我們可以創建一個包含html元素的視圖以及一個可注入編輯元素的視圖。這樣,只有后面一個視圖會被復制一份新的,但是我們還可以同時更新父級視圖。
對於這里使用的其他CSS類,請參考Bootstrap的文檔。
在步驟1中,我們已經聲明了渲染模板的路由。如果您注意到,我們在route()中使用了website=True參數,它將在模板中傳遞一些額外的上下文,如菜單、用戶語言、公司等等。這將在網站上使用統一的布局,用於呈現菜單和頁腳。參數website=True也將啟用網站的多語言功能。
在函數末尾,我們返回了渲染的模板。
更多
我們可以通過inherit_id繼承已有的模板,並通過xpath定位修改的位置實現對現有模板的調整。例如,我們想在Authors標簽旁展示作者的數量,可以通過如下方式實現:
<template id="books_ids_inh" inherit_id="my_library.books">
<xpath expr="//div[@id='card_body']/b" position="replace">
<b class="mt8"> Authors (<t t-esc="len(book.author_ ids)"/>) </b>
</xpath>
</template>
QWeb模板其實是qweb類型的普通視圖。template標簽是帶有特定屬性record元素的縮寫。后台其實創建了一個ir.ui.view模型qweb類型的新紀錄。通過tempalte標簽的name及inherit_id屬性,可以設置記錄的inherit_id字段。
在下一節中,我們將學習如何管理動態路由。
參考
關於QWeb模板的參考如下:
- 總的來說,Odoo廣泛使用Bootstrap,您應該使用它來輕松地獲得自適應設計。
- 有關視圖繼承的詳細信息,請參閱第9章后端視圖。
- 更深入理解控制器,可參考第十三章的"配置url及添加訪問控制"章節。
- 關於更新已有路由的內容,可參考第十三章"調整已有路由"章節。
配置動態路由
在網站開發項目中,我們經常需要創建動態的路由。比如,在電商中,每一個商品都有詳細的頁面且URL不同。在本節中,我們將展示每本書的詳細內容。
准備
我們會使用之前的my_library模塊。為了使每本書頁面更吸引人,我們將添加一些字段。如下:
class LibraryBook(models.Model):
_name = 'library.book'
name = fields.Char('Title', required=True)
date_release = fields.Date('Release Date')
author_ids = fields.Many2many('res.partner', string='Authors')
image = fields.Binary(attachment=True)
html_description = fields.Html()
步驟
- 在main.py添加新路由
@http.route('/books/<model("library.book"):book>', type='http', auth="user", website=True)
def library_book_detail(self, book):
return request.render( 'my_library.book_detail', {'book': book, })
- 添加模板:
<template id="book_detail" name="Books Details">
<t t-call="website.layout">
<div class="container">
<div class="row mt16">
<div class="col-5">
<span t-field="book.image" t-options="{'widget': 'image','class':'mx-auto d-block img-thumbnail'}">
</div>
<div class="offset-1 col-6">
<h1 t-field="book.name" />
<t t-if="book.date_release">
<div t-field="book.date_release" class="text-muted" />
</t>
<b class="mt8">Authors</b>
<ul>
<li t-foreach="book.author_ids" t-as="author">
<span t-esc="author.name">
</li>
</ul>
</div>
</div>
</div>
<div t-field="book.html_description"/>
</t>
</template>
- 添加按鈕,導航到圖書的詳細頁面:
<div t-attf-class="card mt24 #{'bg-light' if book_odd else ''}">
<div class="card-body">
<h3 t-field="book.name" />
<t t-if="book.date_release">
<div t-field="book.date_release" class="text-muted">
</t>
<b class="mt8">Authors</b>
<ul>
<li t-foreach="book.author_ids" t-as="author">
<span t-esc="author.name"/>
</li>
</ul>
<a t-attf-href="/books/#{book.id}" class="btn btn-primary btn-sm">
<i class="fa fa-book"/>Book Detail
</a>
</div>
</div>
原理
步驟1,我們創建了動態路由。其中<model("library.book"):book>,如/books/1。odoo將自動將ID為1的library.book賦值給book。
步驟2,我們新建了一個展示圖書詳細頁面的QWeb模板。其中html_description字段是html類型的值。odoo將自動添加可拖拽的代碼到html類型的值。
步驟3,添加了到每本書的鏈接。
小貼士
模型路由還支持域過濾。例如,如果要基於某個條件限制某些書籍,可以按如下方式將域傳遞到路由:
/books/<model("library.book", "[(name','!=', 'Book 1')]"):team>/submit
這將限制名為"Book 1"的圖書。
更多
Odoo使用werkzeug來處理HTTP請求。Odoo在werkzeug周圍添加了一個薄薄的包裝,以方便處理路由。上面的例子中<model("library.book"):book>。這是Odoo自己的實現,但是它也支持werkzeug路由的所有特性。因此,您可以這樣使用路由:
- /page/int:page 接受整數值。
- /page/<any(about, help):page_name>:接受選擇值
- /pages/
接受字符串。 - /pages/<category>/<int:page>: 接受多個參數
更多詳細內容可參考 http://
werkzeug.pocoo.org/docs/0.14/routing/.
為用戶提供靜態代碼片段
odoo網站編輯器提供了幾種編輯功能區的方式,可拖拽可編輯。本節將介紹如何構建自己的功能區。這些功能區稱為代碼段。有幾種類型的代碼段,通常可分為靜態和動態。靜態代碼段是固定的,除非用戶主動修改。動態區域是依賴於數據庫數據變化的。本節我們將介紹如何創建靜態代碼段。
准備
步驟
代碼段其實是將被注入到添加模塊區域的QWeb視圖。我們將創建一個展示圖書的image和圖書的title。你可以在頁面上拖放功能塊,可以編輯圖片及標題。
- 添加新文件views/snippets.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Step 2 and 3 comes here -->
</odoo>
- 添加QWeb視圖如下:
<template id="snippet_book_cover" name="Book Cover">
<section class="pt-3 pb-3">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6 pt16 pb16">
<h1>Odoo 12 Development Cookbook</h1>
<p>
Learn with Odoo development
quicky with examples
</p>
<a class="btn btn-primary" href="#"> Book Details
</a>
</div>
<div class="col-lg-6 pt16 pb16">
<img src="/my_library/static/src/img/cover.jpeg" class="mx-auto img-thumbnail w-50 img img-fluid shadow"/>
</div>
</div>
</div>
</section>
</template>
- 將代碼段添加到website.snippets
<template id="book_snippets_options" inherit_id="website. snippets">
<xpath expr="//div[@id='snippet_structure']/ div[hasclass('o_panel_body')]" position="inside">
<t t-snippet="my_library.snippet_book_cover" t-thumbnail="/my_library/static/src/img/s_book_thumb.png"/>
</xpath>
</template>
- 添加封面及縮略圖/my_library/ static/src/img。
原理
靜態代碼段其實就是HTML代碼的區塊。步驟1,我們創建了QWeb的模板。在HTML中,我們使用了Bootstrap的架構。靜態代碼段可通過拖拽的形式加載到頁面上。一般而言,在代碼段中使用section元素及Bootstrap類將會非常方便,因為odoo為我們提供了開箱即用的頁面、背景、尺寸的編輯功能。
步驟2,我們在代碼列表中注冊我們的代碼段。可通過繼承website.snippets實現。在網站的編輯器中,將被分為不同的區域。在我們的例子中,我們可通過xpath注冊代碼段。為了展示我們的代碼段,可通過
小貼士
website.snippets模板包含了所有的默認代碼段,你可以在/addons/website/views/snippets/snippets.xml詳細了解。
當你使用合適的Bootstrap架構時,odoo將自動添加一些默認的選項。比如,在我們的例子中,你可以設置背景色,背景圖,寬度,高度等。在/addons/website/views/snippets/snippets.xml中可以看到全部的選項。下一節,我們將了解如何添加我們自己的可選項。
步驟3,我們已經在結構塊下面列出了我們的代碼片段。更新模塊后,就可以拖放代碼段了。在步驟4中,我們剛剛為代碼段縮略圖添加了一個圖像。
更多
在這種情況下,不需要額外的JavaScript。Odoo的編輯器提供了很多開箱即用的選項和控件,它們對於靜態代碼段來說已經足夠了。您將在 website/views/snippets.xml中找到所有現有的代碼段和選項。
Snippet選項還支持data exclude、data drop near和data-drop-in屬性,這些屬性決定了將代碼段從代碼段欄中拖出時可以將其放置在何處。這些也是jQuery選擇器,在這個方法的第3步中,我們沒有使用它們,因為我們允許將代碼片段放在內容可以到達的任何地方。
為用戶提供動態代碼片段
本節,我們將學習如何創建動態代碼片段。
准備
步驟
- 在views/snippets.xml添加QWeb模板
<template id="snippet_book_dynamic" name="Latest Books">
<section class="book_list">
<div class="container">
<h2>Latest books</h2>
<table class="table book_snippet table-striped" data-number-of-books="5">
<tr>
<th>Name</th>
<th>Release date</th>
</tr>
</table>
</div>
</section>
</template>
- 注冊代碼片段並添加選項改變代碼行為:
<template id="book_snippets_options" inherit_id="website.snippets">
<!-- register snippet -->
<xpath expr="//div[@id='snippet_structure']/div[hasclass('o_panel_body')]" position="inside">
<t t-snippet="my_library.snippet_book_dynamic" t-thumbnail="/my_library/static/src/img/s_ list.png"/>
</xpath>
<xpath expr="//div[@id='snippet_options']" position="inside">
<!—Add step 3 here -->
</xpath>
</template>
- 在圖書片添加選項:
<div data-selector=".book_snippet">
<we-select string="Table Style">
<we-button data-select-class="table-striped">
Striped
</we-button>
<we-button data-select-class="table-dark">
Dark
</we-button>
<we-button data-select-class="table-bordered">
Bordered
</we-button>
</we-select>
<we-button-group string="No of Books" data-attribute-name="numberOfBooks">
<we-button data-select-data-attribute="5">
5
</we-button>
<we-button data-select-data-attribute="10">
10
</we-button>
<we-button data-select-data-attribute="15">
15
</we-button>
</we-button-group>
</div>
- 添加/static/src/snippets.js文件
odoo.define('book.dynamic.snippet', function (require) {
'use strict';
var publicWidget = require('web.public.widget');
// Add step 5 here
});
- 添加public小部件渲染book代碼片段:
publicWidget.registry.books = publicWidget.Widget.extend({
selector: '.book_snippet',
disabledInEditableMode: false,
start: function () {
var self = this;
var rows = this.$el[0].dataset.numberOfBooks || '5';
this.$el.find('td').parents('tr').remove();
this._rpc({
model: 'library.book',
method: 'search_read',
domain: [],
fields: ['name', 'date_release'],
orderBy: [{
name: 'date_release',
asc: false
}],
limit: parseInt(rows)
}).then(function (data) {
_.each(data, function (book) {
self.$el.append(
$('<tr />').append(
$('<td />').text(book.name),
$('<td />').text(book.date_release)
));
});
});
},
});
- 添加js文件
<template id="assets_frontend" inherit_id="website.assets_frontend">
<xpath expr="." position="inside">
<script src="/my_library/static/src/js/ snippets.js" type="text/javascript" />
</xpath>
</template>
更新模塊,我們新增了名為Latest books的代碼段,提供了一個可選擇展示最新添加幾本書的選項。
原理
步驟1,我們添加了QWeb模板,包含了table的基礎架構,並動態生成圖書的行。
步驟2,我們注冊了動態代碼段,我們添加了改變代碼行為的自定義的選項。我們添加的第一個選項是選擇Table樣式。第二個選項是圖書的數量。我們使用<we-select>和<we-button-group>標簽。這些標簽提供了不同的GUI展示。<we-select>標簽將展示一個下拉選項,<we-button-group>將作為按鈕組供用戶選擇。還有幾個其他的GUI選項,<we-checkbox>和<we-colorpicker>。你可以在 /addons/website/views/snippets/snippets.xml 查看更多GUI選項。
如果仔細觀察這些選項,就會發現選項按鈕有data-select-class和data-select-data-attribute屬性。這將讓Odoo知道當用戶選擇一個選項時要更改哪個屬性。data-select- class將在用戶選擇該選項時設置元素的class屬性,而data-select-data-attribute將設置元素的自定義屬性和值。注意,它將使用data-attribute-name的值來設置屬性。
現在,我們已經添加了代碼片段和代碼片段選項。如果此時拖放代碼片段,則只會看到表頭和代碼片段選項。更改snippet選項將更改表樣式,但還沒有圖書數據。為此,我們需要編寫一些JavaScript代碼來獲取數據並將其顯示在表中。在步驟3中,我們已經添加了JavaScript代碼,用於在表中呈現圖書數據。要將JavaScript對象映射到HTML元素,Odoo使用PublicWidget。現在,可以通過require('web.public.widget')模塊獲得PublicWidget。使用PublicWidget的關鍵屬性是選擇器屬性。在selector屬性中,您需要使用元素的CSS選擇器,Odoo將自動將元素與PublicWidget綁定。您可以訪問$el屬性中的相關元素。除了_rpc之外,其余的代碼都是基本的JavaScript和jQuery。_rpc方法用於發出網絡請求並獲取圖書數據。我們將在第15章“Web客戶端開發”的服務器配方的RPC調用中學習更多關於_rpc方法的知識。
更多
如果您想創建自己的代碼片段選項,可以在代碼片段選項上使用t-js選項。之后,您需要在JavaScript代碼中定義自己的選項。詳細內容可參見 addons/website/static/src/js/editor/snippets.options.js
獲取網站用戶輸入的數據
在網站開發模式下,我們經常需要獲取用戶輸入。本節,我們將為用戶創建一個針對圖書反饋問題的html 表格。
准備
本節,我們使用my_library模塊,我們需要一個新的模型存儲問題信息。
1. 在library.book模型中添加字段及book.issues模型,如下:
class LibraryBook(models.Model):
_name = 'library.book'
name = fields.Char('Title', required=True)
date_release = fields.Date('Release Date')
author_ids = fields.Many2many('res.partner', string='Authors')
image = fields.Binary(attachment=True)
html_description = fields.Html()
book_issue_id = fields.One2many('book.issue', 'book_id')
class LibraryBookIssues(models.Model):
_name = 'book.issue'
book_id = fields.Many2one('library.book', required=True)
submitted_by = fields.Many2one('res.users')
isuue_description = fields.Text()
2. 在圖書form視圖中添加book_issues_id字段:
<group string="Book Issues">
<field name="book_issue_id" nolabel="1">
<tree>
<field name="create_date"/> <field name="submitted_by"/>
<field name="isuue_description"/>
</tree>
</field>
</group>
3. 添加book.issue的訪問記錄
acl_book_issues,library.book_issue,model_book_issue,group_librarian,1,1,1,1
步驟
1. 在main.py添加路由
@http.route("/books/submit_issues", type="http", auth="user", website=True)
def books_issues(self, **post):
if post.get("book_id"):
book_id = int(post.get("book_id"))
issue_description = post.get("issue_description")
request.env["book.issue"].sudo().create(
{
"book_id": book_id,
"issue_description": issue_description,
"submitted_by": request.env.user.id,
}
)
return request.redirect("/books/submit_ issues?submitted=1")
return request.render(
"my_library.books_issue_form",
{
"books": request.env["library.book"].search([]),
"submitted": post.get("submitted", False),
},
)
2. 添HTML form:
<template id="books_issue_form" name="Book Issues Form">
<t t-call="website.layout">
<div class="container mt32">
<!-- add the page elements here(step 3 and 4)-->
</div>
</t>
</template>
3. 為頁面添加條件頭,如下所示:
<t t-if="submitted">
<h3 class="alert alert-success mt16 mb16">
<i class="fa fa-thumbs-up"/>
Book submitted successfully
</h3>
<h1> Report the another book issue </h1>
</t>
<t t-else="">
<h1> Report the book issue </h1>
</t>
4. 添加<form>
<div class="row mt16">
<div class="col-6">
<form method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="form-group">
<label>Select Book</label>
<select class="form-control" name="book_id">
<t t-foreach="books" t-as="book">
<option t-att-value="book.id">
<t t-esc="book.name"/>
</option>
</t>
</select>
</div>
<div class="form-group">
<label>Issue Description</label>
<textarea name="issue_description" class="form-control" placeholder="e.g. pages are missing"/>
</div>
<button type="submit" class="btn btn-primary">
Submit
</button>
</form>
</div>
</div>
原理
步驟1,我們創建了一個提交圖書問題的路徑。函數中的post參數將接受URL中的所有查詢參數。您還將在post參數中獲得提交的表單數據。在我們的示例中,我們使用了相同的控制器來顯示頁面並提交問題。如果我們在post中找到數據,我們將創建一個問題記錄,然后用提交的查詢參數將用戶重定向到問題頁面,這樣用戶就可以看到確認問題已經提交,因此如果他/她想提交另一個問題,就可以提交另一個問題。
小貼士
我們使用sudo()創建圖書發行記錄,因為普通用戶(訪問者)沒有創建新的圖書發行記錄的訪問權限。盡管如果用戶從web頁面提交了一個問題,則有必要創建圖書問題記錄。這是sudo()用法的一個實際示例。
步驟2,我們已經為issue頁面創建了模板。在步驟3中,我們已經添加了條件頭文件。提交問題后,將顯示success頭。
步驟4,我們添加了<form>,其中包含三個字段:csrf_token、圖書選擇和問題描述。最后兩個字段用於從網站用戶獲取輸入。然而,csrf_token被用來避免跨站請求偽造(CSRF)攻擊。如果你不在表單中使用它,用戶就不能提交表單。當您提交表單時,您將在步驟1的books_issues()方法中獲得提交的數據作為**post參數。
小貼士
禁用csrf,可設置csrf=False
更多
我們可以為form單獨指定post地址
<form action="/my_url" method="post">
並新增路由
@http.route('/my_url', type='http', method='POST', auth='user', website=True)
管理SEO配置項
管理站點地圖
獲取訪客的國家信息
跟蹤營銷活動
管理多網站
odoo支持同一個odoo實例運行多個網站並展示不同的內容。
准備
步驟
- 在library.book模型中添加繼承website.multi.mixin
class LibraryBook(models.Model):
_name = 'library.book'
_inherit = ['website.seo.metadata', 'website.multi.mixin']
- 在圖書的form視圖下新增website_id
<group>
<field name="author_ids" widget="many2many_tags"/>
<field name="website_id"/>
</group>
- 管理/books路由
@http.route('/books', type='http', auth="user", website=True)
def library_books(self, **post):
domain = ['|', ('restrict_country_ids', '=', False), ('restrict_country_ids', 'not in', [country_id])]
domain += request.website.website_domain()
return request.render( 'my_library.books', {
'books': request.env['library.book']. search(domain),
})
- 導入werkzeug並調整圖書的細節,並限制另一個網站的訪問
import werkzeug
...
@http.route('/books/<model("library.book"):book>', type='http', auth='user', website=True, sitemap=sitemap_books)
def library_book_detail(self, book, **post):
if not book.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
return request.render('my_library.book_detail',{'book':book, 'main_object': book})
···
更新模塊。為不同的圖書設置不同的網站。現在,打開/books,可以看到圖書的列表。然后修改網站,再次檢查圖書列表。如下:
原理
步驟1,我們引入了website.multi.mixin類,可用於管理網站。mixin類將添加website_id字段,可用於當前記錄用於哪個網站。
步驟2,添加視圖。
步驟3,我們修改了用於查找書籍列表的域。request.website.website_domain()將返回篩選出非網站書籍的域。
小貼士
請注意,有些記錄沒有設置任何網站id。這些記錄將在所有網站上顯示。這意味着,如果某本書上沒有“網站id”字段,則該書將顯示在所有網站上。
然后,我們在web搜索中添加了域,如下所示:
- 步驟4,我們限制了圖書訪問。如果這本書不適合當前的網站,那么我們將提出一個找不到的錯誤。can_access_from_current_website()方法將返回值True(如果書籍記錄用於當前活動的網站),如果書籍記錄用於其他網站,則返回值False。
- 我們在路由控制器中添加了**post。這是因為如果沒有配置**post,/books和/books/model:library.book:book將無法接受參數。他們也會產生一個錯誤,而切換網站從網站切換器,所以我們添加了它。通常,在每個控制器中添加**post是一種好的做法,這樣它們就可以處理查詢參數。
重定向老的URL
當我們遷移網站的時候,需要將老的URL重定向到新的URL。好的重定向,可以讓SEO依舊指向新的URL。本節,我們將介紹重定向相關知識。
准備
步驟
在我們老的網站,/library將展示圖書列表。而my_library模塊的/books也是展示圖書列表。因此我們可以將/library指向/books。
- 激活開發者模式
- 打開 Website|Configuration|Redirects。
- 點擊 新建 。
- 輸入新舊URL。
- 選擇Action的值301 Moved permanently。
- 保存記錄。
原理
頁面重定向很簡單;它只是HTTP協議的一部分。在我們的示例中,我們將/庫移到了/圖書。我們使用了301移動永久重定向進行重定向。以下是Odoo中提供的所有重定向選項:
- 404NotFound: 如果要為頁提供404notfound響應,則使用此選項。注意,對於這樣的請求,Odoo將顯示默認的404頁面。
- 301 Moved temporarily: 此選項將舊URL永久重定向到新URL。這種類型的重定向將把搜索引擎優化排名移動到一個新的頁面。
- 302 Moved temporarily: 此選項將舊URL臨時重定向到新URL。當您需要在有限的時間內重定向URL時,請使用此選項。這種類型的重定向不會將SEO排名移動到新頁面。
- 308 Redirect/Rewrite: 一個有趣的選擇-有了這個,你將能夠改變/重寫現有的Odoo網址到新的。在這個方法中,這將允許我們將舊的/庫URL重寫為新的/圖書URL。因此,我們不需要使用/library的301永久移動規則重定向舊的URL。
重定向規則窗體上還有幾個字段。其中之一是Active字段,如果您想不時啟用/禁用規則,可以使用該字段。第二個重要領域是網站。當您使用多網站功能並且希望將重定向規則僅限於一個網站時,將使用“網站”字段。但是,默認情況下,該規則將應用於所有網站。
發布網站
在業務流中,有時需要允許或撤消對公共用戶的頁面訪問。其中一個例子是電子商務產品,您需要根據可用性發布或取消發布產品。在本節中,我們將看到如何為公共用戶發布和取消發布圖書記錄。
准備
提醒
請將路由中的auth='user'調整為auth='public'
步驟
- 在Library.book模型中添加引用website.published.mixin
class LibraryBook(models.Model):
_name = 'library.book'
_description = 'Library Book'
_inherit = ['website.seo.metadata','website. published.mixin']
- 添加新文件my_library/security/rules.xml,並添加新紀錄如下:
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="books_rule_portal_public" model="ir. rule">
<field name="name">
Portal/Public user: read published books
</field>
<field name="model_id" ref="my_library.model_library_book"/>
<field name="groups" eval="[(4, ref('base.group_portal')),(4, ref('base.group_public'))]"/>
<field name="domain_force"> [('website_published','=', True)]
</field>
<field name="perm_read" eval="True"/>
</record>
</odoo>
- 更新模塊
要publish/unpublish 圖書,可以使用圖書詳細信息頁面的前一屏幕截圖中顯示的切換。
原理
Odoo提供了一個現成的mixin來處理記錄的發布管理。它為你做了大部分工作。你只需要添加website.published.mixin你的模特。在步驟1中,我們添加了網站.published.mixin我們的圖書模型。這將添加發布和取消發布圖書所需的所有字段和方法。一旦您將這個mixin添加到books模型中,您將能夠看到在book detail頁面上切換狀態的按鈕,如上圖所示。
小貼士
我們正在從book details路由發送一個book record作為主對象。否則,您將無法在“書本詳細信息”頁上看到“發布/取消發布”按鈕。
添加mixin將在圖書的詳細信息頁面上顯示publish/unpublish按鈕,但不會限制公共用戶訪問它。為此,我們需要添加一個記錄規則。在步驟2中,我們添加了一個記錄規則來限制對未出版書籍的訪問。如果您想了解有關記錄規則的更多信息,請參閱第10章“安全訪問”。
更多
publish mixin將啟用網站上的“發布/取消發布”按鈕。但是如果您想在后端表單視圖上顯示重定向按鈕,publishmixin也可以提供一種方法。以下步驟顯示如何將重定向按鈕添加到書本的窗體視圖:
- 在library.book計算書籍URL的模型:
@api.depends('name')
def _compute_website_url(self):
for book in self:
book.website_url = '/books/%s' % (slug(book))
- 添加重定向按鈕
<sheet>
<div class="oe_button_box" name="button_box">
<field name="is_published" widget="website_redirect_button"/>
</div>
添加按鈕后,您將能夠在書本的窗體視圖中看到該按鈕,單擊它,您將被重定向到書本的詳細信息頁面。