第十四章、CMS網站開發**
Odoo有一個功能齊全的內容管理系統(CMS)。經過拖放功能,你的最終用戶能夠在幾分鐘內設計一個頁面,可是在Odoo CMS中開發一個新功能或構建塊就不是那麼簡單了。在本章中,您將探索Odoo的前臺開發。您將學習如何建立網頁。您還將學習如何建立用戶能夠在頁面上拖放的構建塊。進階內容,如Urchin跟蹤模塊(UTMs),搜索引擎優化(SEO),多網站,GeoIP,和網站地圖也涵蓋在這一章。簡而言之,您將瞭解開發交互式網站所需的全部內容。javascript
重要信息
全部的Odoo CMS功能都是經過website和web_editor模塊實現的。若是您想了解CMS在內部是如何工做的,請查看這兩個模塊。你能夠在這裏找到代碼在行動視頻:http://bit.ly/2UH0eMM。css
本章將涵蓋以下內容:html
現代網站包含了大量的JavaScript和CSS文件。當頁面加載到瀏覽器中時,這些靜態文件向服務器發出單獨的請求。請求次數越多,網站速度越慢。爲了不這個問題,大多數網站經過組合多個文件來提供靜態資產。市場上有一些工具能夠管理這類東西,可是Odoo有本身的實現來管理靜態資產。前端
在Odoo中,靜態資產管理並不像在其餘應用中那麼簡單。Odoo有不少不一樣的應用程序和代碼庫。不一樣的Odoo應用程序有不一樣的用途和ui。這些應用程序不共享公共代碼,因此在某些狀況下,咱們想加載一些資產,但咱們不想對全部狀況都這樣作。在頁面上加載沒必要要的靜態資產不是一個好作法。爲了不在全部應用程序中加載額外的資源,Odoo使用了資源包的概念。資產包的工做是將全部JavaScript和CSS組合在一個文件中,並經過最小化它來減小其大小。Odoo代碼庫中有資產包,不一樣的代碼庫也有不一樣的資產包。
如下是Odoo中使用的不一樣資產包:java
重要信息
還有一些其餘用於特定應用的資產包:point_ of_sale.assets, survey.survey_assets, mass_mailing. layout, and website_slides.slide_embed_assetspython
Odoo經過AssetBundle類來管理它的靜態資產
,它位於/odoo/addons/base/models/assetsbundle.py。如今,AssetBundle不只能夠組合多個文件;它還有更多的功能。如下是它提供的特性列表:git
正如咱們所看到的,Odoo針對不一樣的代碼庫有不一樣的資產。要得到正確的結果,您須要選擇正確的資產包,將定製的JavaScript和CSS文件放入其中。例如,若是你正在設計一個網站,你須要把你的文件加載到web.assets_frontend。雖然這種狀況不多見,但有時您須要建立一個全新的資產包。您能夠建立本身的資產包,咱們將在下一節中進行描述。github
<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>
<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屬性只用於加載樣式表或腳本。web
重要信息
在大多數網站開發中,您須要將JavaScript和CSS文件添加到現有的資產包中。添加新的資產包是很是罕見的。只有當你想開發沒有Odoo CMS功能的頁面/應用程序時才須要它。在下一個菜譜中,您將學習如何將自定義CSS/JavaScript添加到現有的資產包中。數據庫
在Odoo中調試JavaScript很是困難,由於AssetBundle會將多個JavaScript文件合併到一個文件中,並將其最小化。經過使用資產啓用developer模式,您能夠跳過資產綁定,頁面將單獨加載靜態資產,這樣您就能夠輕鬆調試。
組合資產生成一次並存儲在ir中。附件的模型。在那以後,它們從附件中被送達。若是你想從新生成資產,你能夠經過調試選項,以下圖所示:
小貼士
正如你所知,odoo只會產生一次資產。這是一個頭痛問題,由於它須要頻繁重啓服務器。爲了解決這個問題,您能夠在命令行中使用dev=xml,這將直接加載資產,所以不須要從新啓動服務器。
在本節,咱們將介紹如何向網站添加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'], ...
<odoo> <template id="assets_frontend" inherit_id="web.assets_frontend"> <xpath expr="." position="inside"> <!-- points 2 & 3 go here /--> </xpath> </template> </odoo>
<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"/>
<script src="/my_library/static/src/js/my_library.js" type="text/javascript" />
body main { background: #b9ced8; }
$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; }
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模板引入。在步驟一、二、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。
咱們將在第四章「應用模型」中開發的my_library附加組件中添加網站功能。咱們感興趣的是容許用戶瀏覽圖書館,若是他們以適當的權限登陸,容許他們從網站界面編輯圖書詳細信息。
本節,咱們將使用來自https://github.com/ PacktPublishing/oodoo-14-developing-cookbook-fourth-edition /tree/master/Chapter14/00_initial_module/my_library目錄的my_library,該目錄來自本書的GitHub存儲庫。
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([]), })
<?xml version="1.0" encoding="utf-8"?> <odoo> <template id="books"> <t t-call="website.layout"> <!-- Add page elements here --> </t> </template> </odoo>
<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>
<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>
<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模板。
步驟二、三、四、5,咱們創造了一個名爲Books的模板,用於生成用於展現圖書的HTML的代碼。代碼由t元素包裹,並經過t-call屬性調用website.layout模板,odoo將渲染website.layout模板,並將咱們生成的HTML代碼插入其中。website.layout包含必要的文件,好比Bootstrap、JQery、Font Awesome等。這些文件用於設計web頁面。website.layout還包含了默認的頭部、尾部、代碼塊及頁面編輯功能。這樣,咱們獲得一個完整的Odoo網頁與菜單,頁腳,頁面編輯功能,而沒必要重複代碼在全部頁面。
步驟三、四、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。在本例中,咱們使用這個方法來爲咱們的卡片設置交替的背景顏色。
如下是其餘可用的變量:
重要小貼士
這裏的示例基於咱們的場景。在本例中,您須要用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"/>
h3和div標籤使用t-field屬性。t-field的值必須是長度爲1的數據集。這能夠在頁面以編輯模式打開的時候可編輯。當用戶保存後修改的值可更新到數據庫。固然,當前用戶必須具有訪問權限才能夠哦。經過t-options屬性,你能夠將一個字典傳遞給字段渲染器,包括想要使用的widget。目前,後端尚未大量的小部件集合,因此這裏的選擇有點有限。例如,你想展現一個圖片,可以下:
<span t-field="author.image_small" t-options="{'widget': 'image'}"/>
t-field有一些限制。它僅做用於數據集且不能用於元素。你須要使用諸如或
注意,顯示出版日期的部分由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>
用戶能夠在編輯模式下直接修改記錄內容。經過t-field加載的數據默認是可編輯的。
若是你想配置可編輯、可拖拽的元素,那麼可將元素的class配置爲oe_structure。在咱們的例子中,咱們在頂層模板添加了該類。
若是你想禁用網站某個區域的編輯功能,可設置contenteditable=False屬性。步驟5中,咱們在
小貼士
To make the page multi-website-compatible, when you edit a page/view through the website editor, Odoo will create a separate copy of the page for that website. This means that subsequent code updates will never make it to the edited website page. In order to also get the ease of use of inline editing and the possibility of updating your HTML code in subsequent releases, create one view that contains the semantic HTML elements and a second one that injects editable elements. Then, only the latter view will be copied, and you can still have updates for the parent view.
對於這裏使用的其餘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模板的參考以下:
在網站開發項目中,咱們常常須要建立動態的路由。好比,在電商中,每個商品都有詳細的頁面且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()
@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路由的全部特性。所以,您能夠這樣使用路由:
odoo網站編輯器提供了幾種編輯功能區的方式,可拖拽可編輯。本節將介紹如何構建本身的功能區。這些功能區稱爲代碼段。有幾種類型的代碼段,一般可分爲靜態和動態。靜態代碼段是固定的,除非用戶主動修改。動態區域是依賴於數據庫數據變化的。本節咱們將介紹如何建立靜態代碼段。
代碼段實際上是將被注入到添加模塊區域的QWeb視圖。咱們將建立一個展現圖書的image和圖書的title。你能夠在頁面上拖放功能塊,能夠編輯圖片及標題。
<?xml version="1.0" encoding="UTF-8"?> <odoo> <!-- Step 2 and 3 comes here --> </odoo>
<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>
<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>
靜態代碼段其實就是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步中,咱們沒有使用它們,由於咱們容許將代碼片斷放在內容能夠到達的任何地方。
本節,咱們將學習如何建立動態代碼片斷。
<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>
odoo.define('book.dynamic.snippet', function (require) { 'use strict'; var publicWidget = require('web.public.widget'); // Add step 5 here });
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) )); }); }); }, });
<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)
odoo支持同一個odoo實例運行多個網站並展現不一樣的內容。
class LibraryBook(models.Model): _name = 'library.book' _inherit = ['website.seo.metadata', 'website.multi.mixin']
<group> <field name="author_ids" widget="many2many_tags"/> <field name="website_id"/> </group>
@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), })
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搜索中添加了域,以下所示:
當咱們遷移網站的時候,須要將老的URL重定向到新的URL。好的重定向,可讓SEO依舊指向新的URL。本節,咱們將介紹重定向相關知識。
在咱們老的網站,/library將展現圖書列表。而my_library模塊的/books也是展現圖書列表。所以咱們能夠將/library指向/books。
頁面重定向很簡單;它只是HTTP協議的一部分。在咱們的示例中,咱們將/庫移到了/圖書。咱們使用了301移動永久重定向進行重定向。如下是Odoo中提供的全部重定向選項:
重定向規則窗體上還有幾個字段。其中之一是Active字段,若是您想不時啓用/禁用規則,可使用該字段。第二個重要領域是網站。當您使用多網站功能而且但願將重定向規則僅限於一個網站時,將使用「網站」字段。可是,默認狀況下,該規則將應用於全部網站。
在業務流中,有時須要容許或撤消對公共用戶的頁面訪問。其中一個例子是電子商務產品,您須要根據可用性發布或取消發佈產品。在本節中,咱們將看到如何爲公共用戶發佈和取消發佈圖書記錄。
提醒
請將路由中的auth='user'調整爲auth='public'
class LibraryBook(models.Model): _name = 'library.book' _description = 'Library Book' _inherit = ['website.seo.metadata','website. published.mixin']
<?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也能夠提供一種方法。如下步驟顯示如何將重定向按鈕添加到書本的窗體視圖:
@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>
添加按鈕後,您將可以在書本的窗體視圖中看到該按鈕,單擊它,您將被重定向到書本的詳細信息頁面。