【odoo14】第十四章、CMS网站开发

第十四章、CMS网站开发**
Odoo有一个功能齐全的内容管理系统(CMS)。经过拖放功能,你的最终用户能够在几分钟内设计一个页面,可是在Odoo CMS中开发一个新功能或构建块就不是那么简单了。在本章中,您将探索Odoo的前台开发。您将学习如何建立网页。您还将学习如何建立用户能够在页面上拖放的构建块。进阶内容,如Urchin跟踪模块(UTMs),搜索引擎优化(SEO),多网站,GeoIP,和网站地图也涵盖在这一章。简而言之,您将了解开发交互式网站所需的全部内容。javascript

重要信息
全部的Odoo CMS功能都是经过website和web_editor模块实现的。若是您想了解CMS在内部是如何工做的,请查看这两个模块。你能够在这里找到代码在行动视频:http://bit.ly/2UH0eMM。css

本章将涵盖以下内容:html

  1. 管理静态资源
  2. 为网站添加CSS及JavaScript
  3. 建立或修改QWeb模板
  4. 配置动态路由
  5. 为用户提供静态代码片断
  6. 为用户提供动态代码片断
  7. 获取网站用户输入的数据
  8. 管理SEO配置项
  9. 管理站点地图
  10. 获取访客的国家信息
  11. 跟踪营销活动
  12. 管理多网站
  13. 重定向老的URL
  14. 发布网站

管理静态资源

现代网站包含了大量的JavaScript和CSS文件。当页面加载到浏览器中时,这些静态文件向服务器发出单独的请求。请求次数越多,网站速度越慢。为了不这个问题,大多数网站经过组合多个文件来提供静态资产。市场上有一些工具能够管理这类东西,可是Odoo有本身的实现来管理静态资产。前端

什么是资源包以及有哪些资源?

在Odoo中,静态资产管理并不像在其余应用中那么简单。Odoo有不少不一样的应用程序和代码库。不一样的Odoo应用程序有不一样的用途和ui。这些应用程序不共享公共代码,因此在某些状况下,咱们想加载一些资产,但咱们不想对全部状况都这样作。在页面上加载没必要要的静态资产不是一个好作法。为了不在全部应用程序中加载额外的资源,Odoo使用了资源包的概念。资产包的工做是将全部JavaScript和CSS组合在一个文件中,并经过最小化它来减小其大小。Odoo代码库中有资产包,不一样的代码库也有不一样的资产包。
如下是Odoo中使用的不一样资产包:java

  • 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_assetspython

Odoo经过AssetBundle类来管理它的静态资产
,它位于/odoo/addons/base/models/assetsbundle.py。如今,AssetBundle不只能够组合多个文件;它还有更多的功能。如下是它提供的特性列表:git

  • 它结合了多个JavaScript和CSS文件。
  • 它经过从文件内容中删除注释、额外空格和回车来减小JavaScript和CSS文件。删除这些额外的数据将减小静态资产的大小,并提升页面加载速度。
  • 它内置了对CSS预处理器的支持,好比SCSS和LESS。这意味着您能够添加SCSS和更少的文件,它们将被自动编译并添加到包中。

自定义资源

正如咱们所看到的,Odoo针对不一样的代码库有不一样的资产。要得到正确的结果,您须要选择正确的资产包,将定制的JavaScript和CSS文件放入其中。例如,若是你正在设计一个网站,你须要把你的文件加载到web.assets_frontend。虽然这种状况不多见,但有时您须要建立一个全新的资产包。您能够建立本身的资产包,咱们将在下一节中进行描述。github

步骤

  1. 建立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>
  1. 使用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属性只用于加载样式表或脚本。web

重要信息
在大多数网站开发中,您须要将JavaScript和CSS文件添加到现有的资产包中。添加新的资产包是很是罕见的。只有当你想开发没有Odoo CMS功能的页面/应用程序时才须要它。在下一个菜谱中,您将学习如何将自定义CSS/JavaScript添加到现有的资产包中。数据库

更多

在Odoo中调试JavaScript很是困难,由于AssetBundle会将多个JavaScript文件合并到一个文件中,并将其最小化。经过使用资产启用developer模式,您能够跳过资产绑定,页面将单独加载静态资产,这样您就能够轻松调试。
组合资产生成一次并存储在ir中。附件的模型。在那以后,它们从附件中被送达。若是你想从新生成资产,你能够经过调试选项,以下图所示:

小贴士
正如你所知,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'],
...

步骤

  1. 添加一个名为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>
  1. 添加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"/>
  1. 添加一个引用到你的JavaScript文件,以下所示:
<script src="/my_library/static/src/js/my_library.js" type="text/javascript" />
  1. 在静态/src/ CSS /my_library中添加一些CSS代码。css,以下:
body main {
    background: #b9ced8;
}
  1. 在静态/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;
}
  1. 在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模板引入。在步骤一、二、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存储库。

步骤

  1. 在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([]),
        })
  1. 在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>
  1. 在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>
  1. 在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>
  1. 在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模板。
步骤二、三、四、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。在本例中,咱们使用这个方法来为咱们的卡片设置交替的背景颜色。
如下是其余可用的变量:

  • 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有一些限制。它仅做用于数据集且不能用于元素。你须要使用诸如

的HTML元素。t-esc与t-field相似,但它并不局限于数据集,而且可用于各类类型的字段,可是它不可编辑。 另外一个不一样点是,t-field是会根据用户语言调整展现值的。而t-esc展现的数据库中的原始值。例如,对于英语用户,经过t-field展现datetime字段的值时,将展现12/15/2018 17:12:13格式。而使用t-esc的时候,将展现2018-12-15 17:12:13(若当地时区与UTC时区不一样,则时间也会不一样哦)

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中,咱们在

设置了该属性。

小贴士
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模板的参考以下:

  • 总的来讲,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()

步骤

  1. 在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, })
  1. 添加模板:
<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>
  1. 添加按钮,导航到图书的详细页面:
<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。你能够在页面上拖放功能块,能够编辑图片及标题。

  1. 添加新文件views/snippets.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Step 2 and 3 comes here -->
</odoo>
  1. 添加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>
  1. 将代码段添加到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>
  1. 添加封面及缩略图/my_library/ static/src/img。

原理

静态代码段其实就是HTML代码的区块。步骤1,咱们建立了QWeb的模板。在HTML中,咱们使用了Bootstrap的架构。静态代码段可经过拖拽的形式加载到页面上。通常而言,在代码段中使用section元素及Bootstrap类将会很是方便,由于odoo为咱们提供了开箱即用的页面、背景、尺寸的编辑功能。
步骤2,咱们在代码列表中注册咱们的代码段。可经过继承website.snippets实现。在网站的编辑器中,将被分为不一样的区域。在咱们的例子中,咱们可经过xpath注册代码段。为了展现咱们的代码段,可经过 标签加t-snippet属性实现。t-snippet属性值是XML ID值。咱们还可使用t-thumbnail用于展现代码区的缩略图。

小贴士
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步中,咱们没有使用它们,由于咱们容许将代码片断放在内容能够到达的任何地方。

为用户提供动态代码片断

本节,咱们将学习如何建立动态代码片断。

准备

步骤

  1. 在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>
  1. 注册代码片断并添加选项改变代码行为:
<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>
  1. 在图书片添加选项:
<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>
  1. 添加/static/src/snippets.js文件
odoo.define('book.dynamic.snippet', function (require) {
    'use strict';
    var publicWidget = require('web.public.widget');
    // Add step 5 here
});
  1. 添加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)
                    ));
            });
        });
    },
});
  1. 添加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实例运行多个网站并展现不一样的内容。

准备

步骤

  1. 在library.book模型中添加继承website.multi.mixin
class LibraryBook(models.Model):
	_name = 'library.book'
	_inherit = ['website.seo.metadata', 'website.multi.mixin']
  1. 在图书的form视图下新增website_id
<group>
	<field name="author_ids" widget="many2many_tags"/> 
	<field name="website_id"/>
</group>
  1. 管理/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),
        })
  1. 导入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。

  1. 激活开发者模式
  2. 打开 Website|Configuration|Redirects。
  3. 点击 新建 。
  4. 输入新旧URL。
  5. 选择Action的值301 Moved permanently。
  6. 保存记录。

原理

页面重定向很简单;它只是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'

步骤

  1. 在Library.book模型中添加引用website.published.mixin
class LibraryBook(models.Model):
	_name = 'library.book'
	_description = 'Library Book'
	_inherit = ['website.seo.metadata','website. published.mixin']
  1. 添加新文件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>
  1. 更新模块

要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也能够提供一种方法。如下步骤显示如何将重定向按钮添加到书本的窗体视图:

  1. 在library.book计算书籍URL的模型:
@api.depends('name')
def _compute_website_url(self):
	for book in self:
		book.website_url = '/books/%s' % (slug(book))
  1. 添加剧定向按钮
<sheet>
	<div class="oe_button_box" name="button_box">
		<field name="is_published" widget="website_redirect_button"/>
	</div>

添加按钮后,您将可以在书本的窗体视图中看到该按钮,单击它,您将被重定向到书本的详细信息页面。