odoo13学习---15 CMS网站开发

Posted ly-stranger

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了odoo13学习---15 CMS网站开发相关的知识,希望对你有一定的参考价值。

管理静态资产

  1. web.assets_common:这个资源包包括所有应用程序通用的所有基本实用程序,如JQurey、Underscore.js、FontAwesome等。该资产包用于前端(网站)、后端、销售点、报表等。这一共同的资产加载几乎在Odoo的任何地方。它还包含了boot.js文件,用于Odoo模块系统。
  2. web.assets_backend:这个资源包用于Odoo的后端(ERP部分)。它包含与web客户端、视图、字段小部件、操作管理器等相关的所有代码。
  3. web.assets_frontend:这个资产包用于Odoo的前端(网站部分)。它包含与web站点端应用程序相关的所有代码,如电子商务、博客、在线事件、论坛、实时聊天等等。注意,这个资产包不包含与网站编辑和拖放特性(网站构建器)相关的代码。这背后的原因是,我们不想加载编辑器资产的公共使用的网站。
  4. web_editor.assets_editor和web_editor.summernote:这个资产包包含与网站编辑片段选项和拖放特性(网站构建器)相关的代码。只有当用户拥有网站编辑权限时,它才会被加载到网站中。它也被用于大量的邮件设计。
  5. web.report_assets_common: QWeb报告只是从html生成的PDF文件。此资产加载在报表布局中。

还有其他一些用于特定应用程序的资产包:point_of_sale.assets, survey.survey_assets, mass_mailing.layout, 和 website_slides.slide_embed_assets.

Odoo通过AssetsBundle类管理它的静态资产,它位于/odoo/addons/base/models/assetsbundle.py。资产包不仅组合了多个文件;它还包含了更多的特性。这里是它提供的功能列表:

  • 它结合了多个javascript和CSS文件。
  • 它通过删除文件内容中的注释、额外空格和回车来缩小JavaScript和CSS文件。删除这些额外的数据将减少静态资产的大小并提高页面速度。
  • 它内置了对CSS预处理程序的支持,比如SASS和LESS。这意味着您可以添加SCSS和更少的文件,它们将被自动编译并添加到包中。
  • 如果达到4095规则限制,它会自动分割样式表资产文件。

自定义资产

正如我们所看到的,Odoo对于不同的代码库有不同的资产。为了得到正确的结果,您需要选择正确的资产包,将定制的JavaScript和CSS文件放在其中。例如,如果你正在设计一个网站,你需要把你的文件放在web.assets_frontend。在下一个章节中,您将看到如何在现有的资产包中包含定制的CSS/JavaScript。尽管这种情况很少见,但有时您需要创建一个全新的资产包。您可以创建自己的资产包,正如我们将在下一节中描述的那样。

怎么做呢?

按照以下步骤创建一个自定义资产包:

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>

 

2. 在您想要加载此包的QWeb模板中使用t-call-assets,如下所示:

<template id="some_page">
...
<head>
<t t-call-assets="my_module.my_custom_assets" tjs="false"/>
<t t-call-assets="my_module.my_custom_assets" tcss="false"/>
</head>
...

 

它是如何工作的…

在步骤1中,我们使用外部ID my_custom_assets创建了新的QWeb模板。在这个模板中,您需要列出所有的CSS、SCSS和JavaScript文件。首先,Odoo将编译SCSS文件到CSS,然后,Odoo将结合所有的CSS和JavaScript文件到一个单独的CSS和JavaScript文件。声明资产之后,需要将它们加载到QWeb模板(web页面)中。 

在步骤2中,我们已经在模板中加载了CSS和JavaScript资产。t-csst-js属性仅用于加载样式表或脚本。

在大多数网站开发中,您需要在现有的资产包中添加JavaScript和CSS文件。添加一个新的资产包是非常罕见的。只有当你想开发没有Odoo CMS功能的页面/应用程序时才需要。在下一个章节中,您将了解如何在现有的资产包中添加自定义CSS/JavaScript。

有更多的…

如果您正在使用Odoo中的资产,以下是您需要知道的一些事情

在Odoo中调试JavaScript非常困难,因为AssetBundle会将多个JavaScript文件合并到一个文件中,并且还会缩小它们。

调试JavaScript可能是非常困难的Odoo,因为AssetBundle合并多个JavaScript文件到一个单一的,也缩小了他们。通过使用资产启用developer模式,您可以跳过资产绑定,并且页面将分别加载静态资产,以便您可以轻松地进行调试。通过启用带资产的developer模式,您可以跳过资产绑定,并且页面将分别加载静态资产,以便您可以轻松地进行调试。

 

合并后的资产只生成一次,并存储在ir.attachment模型中。之后,它将从附件中提供。如果您想重新生成资产,您可以从调试选项中进行,如下面的截图所示:

合并后的资产只生成一次并存储在ir中。附件的模型。之后,它将从附件中提供。如果您想重新生成资产,您可以从调试选项中进行,如下面的截图所示:

技术图片

 

 

 

如您所知,Odoo将只生成一次资产。这种行为在开发过程中可能会令人头疼,因为它需要频繁地重新启动服务器。要克服这个问题,可以在命令行中使用dev=xml,它将直接加载资产,因此不需要重新启动服务器。

 


 

扩展CSS和JavaScript的网站

 

在这个章节中,我们将介绍如何添加自定义样式表和JavaScript到网站。

 

准备

 

我们将使用第4章中的my_library模块,创建Odoo附加模块。我们将添加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>

2. 添加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"/>

3.添加一个引用到你的JavaScript文件,如下:

<script src="/my_library/static/src/js/my_library.js" type="text/JavaScript" />

4. 添加一些CSS代码到static/src/ CSS /my_library.css,如下:

body main {
  background: #b9ced8;
}

5. 向static/src/ SCSS my_library.scss添加一些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; }

 6. 添加一些JavaScript代码到static/src/js/my_library.js如下: 

odoo.define(‘my_library‘, function (require) {
  var core = require(‘web.core‘);
  alert(core._t(‘Hello world‘));
  return {
    // if you created functionality to export, add it here
  }
});

更新你的模块后,你应该看到Odoo网站有自定义的颜色在菜单,正文,和页脚,和一个有点恼人的Hello world弹出在每个页面加载,如下截图所示:

它是如何工作的…

在Odoo的CMS基础上有一个名为QWeb的XML模板引擎,我们将在下一篇文章中详细讨论它。资产包只是用模板创建的。在步骤1、2和3中,我们通过扩展web.assets_frontend列出了样式表和JavaScript文件。我们选择web.assets_frontend是因为我们想更新网站。这些资产被载入每个网站页面。

在第4步中,我们添加了CSS,用于设置网站的主体背景颜色。

对于CSS/SCSS文件,有时候,顺序很重要。因此,如果您需要覆盖在另一个附加组件中定义的样式,您必须注意您的文件是在您想要修改的原始文件之后加载的。

这可以通过调整视图的优先级字段或直接继承加载项的视图来实现,该视图将引用注入CSS文件。有关详细信息,请参阅第10章后端视图中的更改现有视图-视图继承配方。

在步骤5中,我们添加了基本的SCSS。Odoo内置了对SCSS预处理器的支持。Odoo将自动编译SCSS文件到CSS。在我们的示例中,我们使用了带有一些变量的基本SCSS和函数darken来将$my-text-color调暗15%。SCSS预处理器有大量的其他特性;如果你想了解更多关于SCSS的信息,请参考‘http://sass- lang.com/。

Odoo 12之前的版本使用的是Bootstrap 3,它使用的较少(http://lesscss.org)预处理器。Odoo版本12使用了最新的Bootstrap版本,它是Bootstrap 4 (https://getbootstrap. com/ )。它使用SCSS。因此,如果您使用的是较老的Odoo版本,则需要编写更少的代码而不是SCSS。

在第6步中,我们添加了基本的JavaScript,它只在页面加载时显示警告消息。为了避免JavaScript的排序问题,Odoo使用了一种非常类似RequireJS的机制(http://requirejs.org)。在我们的JavaScript文件中,我们调用了odoo.define(),它需要两个参数,一个是要定义的名称空间,另一个是包含实际实现的函数如果您导出了功能的许多逻辑上不同的部分,那么在不同的函数中定义它们,并在前面加上您的附加组件的名称,并用点分隔,以避免将来的命名冲突。这就是web模块所做的,它定义了web.core和web.data等。

对于第二个参数,定义函数只接收一个参数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 9.0的要求机制。在旧版本中,处理JavaScript的外接程序需要在openerp命名空间中定义与外接程序同名的函数。此函数接收到当前加载实例的引用作为参数,从该参数访问API函数。因此,为了升级现有代码,将其更改为odoo.define子句,并通过require导入必要的对象

 


 

创建或修改模板- QWeb

我们将为在第5章“应用模型”中开发的my_library插件添加网站功能。我们感兴趣的是允许用户浏览图书馆,如果他们以适当的权限登录,使他们能够从网站界面编辑图书详细信息。

 怎么做呢?

我们需要定义以下几个控制器和视图:

1. 在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>

 

2. 在website.layout中,使用oe_structure类添加droppable元素,如下所示:

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

 

3.将代码块附加到网站中。显示图书信息的版面如下:

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

 

4. 添加一个不可编辑的元素到website.layout,如下所示:

  <section class="container mt16" contenteditable="False">
      This is a non-editable text after the list of books.
  </section>

 

5. 在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([]),
            })

在浏览器中打开http://your-server-url:8069/books,您将能够看到图书列表以及作者。通过这段代码,用户可以看到图书列表及其详细信息。给予适当的权限,用户还将能够改变书的细节和一些其他文本。

 它是如何工作的…

首先,我们创建了一个名为books的模板,用于生成显示图书列表所需的HTML。所有代码都包装在带有t-call属性集的t元素中,这使得Odoo使用website.layout模板呈现页面,并将我们的内容插入到模板中。website.layout包括所有必需的实用程序,比如Bootstrap、jQuery、FontAwesome等等。这些实用程序用于设计web页面。默认情况下,它包括所有必要的资产,如Bootstrap、jQuery、FontAwesome等等。website.layout还包括默认的页眉、页脚、代码片段和页面编辑功能。这样,我们就得到了一个完整的Odoo web页面,包括菜单、页脚和页面编辑功能,而不必在所有页面中重复这些代码如果你不使用t-call="website_layout",你将不会得到默认的页眉、页脚和网站编辑功能。

在website.layout中,我们添加了带有QWeb模板属性的HTML来显示图书列表。现在,我们将查看不同的QWeb属性及其用法。在此模板中,您将能够访问从main.py中控制器传递的参数。

Loop

为了处理记录集或可迭代数据类型,您需要一个结构来遍历列表。在QWeb模板中,这可以通过t-foreach元素完成。迭代可以发生在元素中,在这种情况下,它的内容会对t-foreach属性中传递的iterable的每个成员重复,如下所示:

<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-foreacht-as属性放置在任意元素中,此时该元素及其内容将针对迭代中的每个项重复。看看下面的代码块。这将产生与前面例子完全相同的结果:

<p t-foreach="[1, 2, 3, 4, 5]" t-as="num">
  <t t-esc="num"/>
</p>

在我们的示例中,看一下t-call元素的内部,这里发生了实际的内容生成。模板期望使用具有名为books set变量的上下文进行呈现,该变量在t-foreach元素中遍历该模板。t-as属性是强制性的,它将用作迭代器变量的名称,用于访问迭代数据虽然这种构造最常见的用途是遍历记录集,但您可以在任何可迭代的Python对象上使用它。

在t-foreach循环中,您可以访问两个额外的变量,它们的名称派生于附带的t-as属性。正如前面示例中的book一样,我们可以访问book_odd变量,该变量在迭代时包含奇数索引的值为真,偶数索引的值为假。在这个例子中,我们使用这个可以在纸牌中交替使用背景颜色。

以下是其他可用的变量: 

book_index,它返回迭代中的当前(从零开始)索引。

book_first和book_last:如果这是第一次或最后一次迭代,则book_first和book_last分别为真。

book_value,如果我们遍历的book变量是一个字典,它将包含条目的值;在本例中,book将遍历字典的键。

book_size,它是集合的大小(如果可用)。

book_evenbook_odd根据迭代索引获得真值。

book_parity:迭代时包含偶数索引的偶数值,以及奇数索引的奇数值。

给出的例子是基于我们的例子。在您的例子中,需要用t-as属性处的值替换book。

动态属性(Dynamic attributes)

QWeb template可以动态设置属性值。这可以通过以下三种方式来实现。

第一种方法是通过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"/>

在我们的示例中,我们使用t-attf-class来获得基于索引值的动态背景。

Fields

 h3和div标记使用t字段属性。t-field属性的值必须用于长度为1的记录集,这允许用户在编辑模式下打开网站时更改web页面的内容。当您保存页面时,更新后的值将存储在数据库中。当然,这要经过权限检查,并且只有当前用户对显示的记录具有写权限时才允许。通过一个可选的t-options属性,您可以提供一个要传递给字段呈现器的dictionary选项,包括要使用的小部件目前,后台并没有大量的小部件,所以这里的选择有点有限。例如,如果你想从二进制字段显示一个图像,那么你可以使用像这样的图像小部件:

<span t-field="author.image_small" t-options="{‘widget‘: ‘image‘}"/>

t-field有一些局限性。它只对记录集有效,对<t>元素无效为此,需要使用一些HTML元素,如<span>或<div>。t字段属性还有一个可选属性,即t-esct-esc属性不限于记录集;它也可以用于任何数据类型,但不能在网站中编辑。

 t-esc和t-field之间的另一个区别是,t-field显示基于用户语言的值,而t-esc显示来自数据库的原始值例如,对于在首选项中配置了英语语言并将datetime字段设置为与t字段一起使用的用户,结果将以12/15/2018 17:12:13格式呈现。相反,如果使用t-esc属性,则结果将呈现如下格式:2018-12-15 16:12:13。

Conditionals

注意,显示发布日期的部分由带有t-if属性集的t元素包装。该属性作为Python代码计算,只有当结果是真实值时才呈现元素。在下面的示例中,我们只显示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>

 Setting variables

QWeb模板还能够在模板本身中定义变量。定义模板之后,您可以在后续模板中使用该变量。你可以这样设置变量:

 

<t t-set="my_var" t-value="5 + 1"/>
<t t-esc="my_var"/>

 Subtemplates(子模板)

如果您正在开发一个大型应用程序,管理大型模板可能会很困难。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字段节点加载的数据在默认情况下是可编辑的如果用户更改了这样一个节点中的值并保存了页面,那么这些值也将在后端进行更新。别担心;为了更新记录,用户需要对记录进行写权限。注意,t字段只对记录集有效要显示其他类型的数据,可以使用t-esc。它的工作原理与t-field完全相同,但唯一的区别是t-esc是不可编辑的,可以用于任何类型的数据

 

  • 如果希望在页面上启用代码片段拖放支持,可以使用oe_structure类。在我们的示例中,我们将其添加到模板的顶部。使用oe_structure将启用编辑和代码片段拖放支持。
  • 如果你想在某个块上禁用网站编辑功能,那么你可以使用contenteditable=False属性。这使得元素是只读的。在步骤1中,我们在最后一个<section>标记中使用了这个属性。

  注意通过网站编辑器编辑视图会在此视图上设置noupdate标志。这意味着后续的代码更改将永远不会进入客户的数据库。为了更方便地使用内联编辑并在后续版本中更新HTML代码,创建一个包含语义HTML元素的视图和第二个注入可编辑元素的视图。然后,只有后一个视图是noupdate,您仍然可以更改前一个视图。

  对于这里使用的其他CSS类,请参考Bootstrap文档,链接见本节的“参见”部分。

  在步骤2中,我们声明了渲染模板的路由。如果您注意到,我们在route()中使用了website=True参数,它将在模板中传递一些额外的上下文,如菜单、用户语言、公司等等这将用于网站。布局来呈现菜单和页脚。True参数也支持网站的多语言支持。它还以更好的方式显示异常。

  在函数结束时,我们通过呈现模板返回结果;然后传递模板中使用的所有图书的记录集。有关更新现有路由的更多信息,请参考第14章“Web服务器开发”中的修改现有处理程序菜谱。

有更多的…

  要修改现有模板,可以在模板上使用inherit_id属性,然后使用xpath元素,比如视图继承。例如,我们希望通过继承books模板来显示authors标签附近的作者数量。

我们可以通过以下方式做到这一点:

<!-- inheritance example -->
<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元素是记录元素的简写,它为您在记录上设置一些属性。虽然没有理由不使用template元素的便利,但是您应该知道在幕后发生了什么:元素创建了一个具有qweb类型的ir.ui.view模型的记录。然后,根据模板元素的名称和inherit_id属性,将设置视图记录上的inherit_id字段。

另请参阅

要有效设计QWeb模板,请参考以下几点:

总的来说,Odoo广泛使用了Bootstrap (http://getbootstrap.com),您应该使用它来轻松获得自适应设计。

有关视图继承的详细信息,请参阅第10章后端视图中的更改现有视图-视图继承。

关于控制器的更深入的讨论,请参考第14章“web服务器开发”中的“从网络创建一个可访问的路径并限制对web可访问路径的访问”。

 


 

管理动态路由

   在网站开发项目中,经常需要创建带有动态url的页面。例如,在电子商务中,每个产品都有一个带有不同URL的详细页面。在这个章节中,我们将创建一个web页面来显示图书的详细信息。

  准备

  我们将使用上一个节中的my_library模块。为了使图书细节页面更吸引人,我们需要添加一些新字段。请在library.book模型中添加以下两个新字段并形成视图,如下所示:

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,
            })

2. templates.xml中添加一个新的图书详细信息模板,如下所示:

<!-- Book Detail Page -->
<template id="book_detail" name="Books Detail">
    <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>

 3. 在book list模板中添加一个按钮,如下所示。此按钮将重定向到图书详情网页:

<template id="books">
    <t t-call="website.layout">
        <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>
                        <a t-attf-href="/books/#{book.id}" class="btn btn-primary btn-sm">
                            <i class="fa fa-book"/>
                                Book Detail
                        </a>
                    </div>
                </div>
            </t>
        </div>

        <section class="container mt16" contenteditable="False">
            This is a non-editable text after the list of books.
        </section>
    </t>
</template>

 它是如何工作的…

  在第一步中,我们为图书详细信息页面创建了一个动态路径。在这个路径中,我们添加了<model("library.book"):book>接受带有整数的url,比如/books/1。Odoo认为这个整数是库的ID当这个URL被访问时,Odoo获取一个记录集并将它作为参数传递给函数因此,当从浏览器访问/books/1时,函数library_book_detail()中的book参数将拥有该库的一个记录集ID为1的图书模型。我们传递了这个图书记录集,并呈现了一个名为my_library.book_detail的新模板。

  在第二步中,我们创建了一个名为book_detail的新QWeb模板来呈现图书详细信息页面。这很简单,是使用引导结构创建的。如果您选中,我们已经在详细页面中添加了html_description。字段html_description有一个HTML类型的字段,因此您可以在字段中存储HTML数据。Odoo自动添加代码片段拖放支持的HTML类型的字段。所以,现在我们可以在book details页面中使用片段了。HTML字段中的代码片段存储在一本书的记录中,因此您可以为不同的图书设计不同的内容。

   在最后一步中,我们添加了一个带有锚标记的链接,这样访问者就可以被重定向到图书详细信息页面。

有更多的…

  Odoo使用werkzeug处理HTTP请求。Odoo在werkzeug周围增加了一个薄薄的包装纸,方便处理路线。您在最后一个示例中看到了<model(“library_book”):book>路由。这是Odoo自己的实现,但是它也支持所有来自werkzeug路由的特性。因此,你可以这样使用路由:

/page/<int:page> 接受整型值

/page/<any(about, help):page_name> 接受选择的值

/pages/<page> 接受字符串

/pages/<category>/<int:page> 接受多个值

路由有很多变体,您可以在http://werkzeug.pocoo.org/docs/0.14/routing/上了解到。

 


 向用户提供代码片段 

  网站设计者在网站编辑模式中提供构建块,它可以在页面上。本节将介绍如何在内部提供自己的块(称为片段)。 

准备 

  对于这个章节,我们将使用上一个章节中的my_library模块。 

怎么做呢? 

  一个代码片段实际上就是一个注入到插入块栏中的qwebview,它是由QWeb view本身定义的。遵循以下步骤: 

1. 添加名为views/snippets.xml的文件,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<odoo>
  <!-- Assets for JS file added step 4 -->
  <template id="assets_frontend" inherit_id="web.assets_frontend">
    <xpath expr="." position="inside">
      <script src="/my_library/static/src/js/snippets.js" type="text/JavaScript" />
    </xpath>
  </template>
<!-- Step 2 and 3 comes here -->
</odoo>

 2. 为代码片段添加一个模板,如下所示:

<template id="book_snippet">
  <section class="book_list">
    <div class="container">
      <h2>Latest books</h2>
      <table class="table book_snippet">
        <tr>
          <th>Name</th>
          <th>Release date</th>
        </tr>
      </table>
    </div>
  </section>
</template>

3.继承代码片段模板并附加代码片段和选项,如下所示:

<template id="book_snippets_options" inherit_id="website.snippets">
  <xpath expr="//div[@id=‘snippet_feature‘]/div[hasclass(‘o_panel_body‘ )]" position="inside">
    <t t-snippet="my_library.book_snippet" tthumbnail="/my_library/static/description/icon.png"/>
  </xpath>
  <xpath expr="//div[@id=‘snippet_options‘]" position="inside">
    <!-- add snippet options here -->
  </xpath>
</template>

4. 在继承的代码片段模板中添加代码片段选项,如下所示:

<div data-js="book_count" data-selector="section.book_list">
  <div class="dropdown-submenu">
    <a tabindex="-2" href="#" class="dropdown-item">
      <i class="fa fa-book"/> Number of books
    </a>
    <div class="dropdown-menu" role="menu">
      <a href="#" class="dropdown-item" data-selectcount="3"> 3 </a>
      <a href="#" class="dropdown-item" data-selectcount="5"> 5 </a>
      <a href="#" class="dropdown-item" data-selectcount="10"> 10 </a>
      <a href="#" class="dropdown-item" data-selectcount="15"> 15 </a>
    </div>
  </div>
</div>

<div data-selector=".book_snippet">   <div class="dropdown-submenu">     <a tabindex="-2" href="#" class="dropdown-item">       <i class="fa fa-columns"/> Table Style     </a>     <div class="dropdown-menu" role="menu">       <a href="#" class="dropdown-item" data-toggleclass="table-bordered">         Bordered
      </a>       <a href="#" class="dropdown-item" data-toggleclass="table-dark">         Dark       </a>       <a href="#" class="dropdown-item" data-toggleclass="table-striped">         Striped
      </a>
    </div>   </div> </div>

5. 添加新文件/my_library/static/src/js/snippets.js,并添加JavaScript代码来填充我们的代码片段,如下所示:

odoo.define(‘my_library.snippets‘, function (require) {
  "use strict";
  var rpc = require(‘web.rpc‘);
  var Animation = require(‘website.content.snippets.animation‘);
  var options = require(‘web_editor.snippets.options‘);
// Add snippet option and animation JavaScript here
});

6. 添加如下选项来决定我们想要在代码片段中显示多少本书:

options.registry.book_count = options.Class.extend({
  selectCount: function (previewMode, value, $opt) {
    var table = this.$target.find(‘table‘);
    var oldClass = table.attr(‘class‘);
    var newTable = $(‘<table><tr><th>Name</th><th>Release date</th></tr></table>);
    newTable.attr(‘class‘, oldClass);
    newTable.attr(
‘data-rows‘, value);     table.replaceWith(newTable);     this._refreshAnimations();   } });

 7. 添加动画来获取图书数据并显示在页面中,如下图所示:

Animation.registry.book_snippet = Animation.Class.extend({
  selector: ‘.book_snippet‘,
  start: function () {
      var self = this;
      var rows = this.$el.data().rows || 5;
      this.$el.find(‘td‘).parents(‘tr‘).remove();
      rpc.query({
        model: ‘library.book‘, method: ‘search_read‘,
        domain: [], fields: [‘name‘, ‘date_release‘],
        orderBy: [{name: ‘date_release‘, asc: false}],
        limit: rows,
      }).then(function (data) {
        _.each(data, function (book) {
          self.$el.append(
            $(‘<tr />‘).append(
              $(‘<td />‘).text(book.name),
              $(‘<td />‘).text(book.date_release)
          ));
      });
    });
  },
});

  在更新模块之后,将向您提供一个名为Latest books的新代码片段,其中有一个选项可以更改最近添加的图书数量。我们还添加了更改表设计的选项,可以在单击表时显示。

 它是如何工作的…

  在第一步中,我们添加了一个新的XML文件snipptes.xml,并在资源中添加了一个JS文件。代码片段只是没有website.layout的QWeb模板。

  在第二步中,我们为该书创建了book_snippet模板。一般来说,使用section元素和引导类是一个好主意,因为对于它们,Odoo的编辑器提供了编辑、背景和调整大小的控制。在代码片段主体中,我们刚刚添加了标题和带有标题的表。我们想把最新的书陈列在我们的箱子里。最新的图书列表不是固定的,每次新书出版时,该列表都会更改。因此,我们希望动态地将图书详细信息插入到表中。在接下来的几个步骤中,我们将添加JavaScript来获取最新书籍列表并将它们添加到表中。

  在第三步中,我们继承了website.snippet模板来添加代码片段及其选项。第一个xpath将book_snippet添加到feature snippet部分。要添加代码片段,需要使用t-snippet属性。您还需要添加t-thumbnail属性,它将是代码片段的缩略图的URL。使用position属性确定将在哪个部分显示代码片段。我们的选择是//div[@id=‘snippet_feature‘]/div[@class=‘o_panel_body‘],这将它放在features部分中。使用snippet_structuresnippet_contentsnippet_effect的id,您可以将代码片段放在其他相应的部分中。

  在第二个xpath中,我们为代码片段添加了选项。通过步骤4在这个xpath中添加了两个选项。第一个选项用于选择要在表中显示的图书数量。代码片段选项有各种类型。在本例中,我们使用了data-js="book_count"的自定义选项。当使用data-js选项时,需要在options_registry中使用属性的值注册它当用户更改选项时,它将调用已注册选项中的函数,然后您需要在元素上设置选项值。当页面被重新加载时,渲染函数(Odoo动画框架)会使用这些值。

  您可以在步骤6中检查这一点,其中我们添加了选项,如options.registry.book_count。在函数体中,我们在this.$target中获得一个代码片段元素,函数体中的其余内容是用基本的JQuery语法编写的。

  我们还在步骤4中添加了一个代码片段选项,用于更改表样式。对于这个选项,我们使用了data-toggle-class属性。当用户点击属性data-toggle-class的选项时,Odoo将切换属性值中给出的类。这种类型的选项不需要JavaScript代码。在Odoo中还有一种称为data-select-class的选项,它一次只支持一个类我们在示例中没有使用这个,但您可以测试它。

  如果您注意到,data-selector属性包含一个JQuery选择器,用于确定要显示选项的哪个元素。在本例中,第一个选项列表是在整个容器被选中时显示的,而第二个选项列表是在表被选中时显示的。

  在步骤6和步骤7中,我们在Animation.registry中添加了book_snippet,它将从数据库中获取图书数据,并在snippet主体中追加表行。它使用代码片段动画框架在每次加载代码片段时执行代码。我们使用它来查询要呈现给用户的当前图书列表。这里的关键属性是已定义的选择器,它指示框架在有匹配选择器的元素时运行我们的类。在内部,我们使用data-rows选项,它是从options.registry.book.count添加的,用于确定需要显示多少行。

  我们的章节中给出的示例用于创建动态片段。如果您不需要动态代码片段,而只想添加静态内容,那么您可以直接在代码片段中添加所有内容。不需要为静态片段添加JavaScript。

  在前面的示例中,我们创建了一个动态代码片段。如果您不需要动态代码片段,而只想添加静态内容,那么您可以直接在代码片段中添加所有内容。在这种情况下,将不需要额外的JavaScript。

有更多的…

  在这种情况下,将不需要额外的JavaScript。Odoo的编辑器提供了很多现成的选项和控制,对于静态代码片段来说,它们已经足够了。

  你可以在website/views/snippets.xml找到所有现有的片段和选项。Snippet选项还支持data-exclude、data-drop-near和data-drop-in属性,这些属性决定在将Snippet拖出Snippet栏时可以将其放置在何处。这些也是JQuery选择器,在本节的第3步中,我们没有使用它们,因为我们允许将代码片段放到内容可以放到的任何地方。

 


从用户那里获取输入

  在网站开发中,通常需要创建表单来获取网站用户(访问者)的输入。在这个章节中,我们将在页面中创建一个HTML表单来报告与书籍相关的问题。

准备

  对于这个章节,我们将使用上一个章节中的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()
  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()

在book表单视图中添加一个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>
...

在ir.model.access.csv文件中为新 book.issue模型添加访问权限,如下: 

acl_book_issues,library.book_issue,model_book_issue,group_librarian,1,1,1,1

  我们已经为图书问题添加了一个新模型,现在,我们将添加一个带有HTML表单的新模板。

怎么做呢? 

  按照以下步骤为问题页面创建一个新的路由和模板页面:  

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表单的模板,如下所示:

<template id="books_issue_form" name="Book Issues Form">
    <t t-call="website.layout">
        <div class="container mt32">
<!--第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步--> <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> </div> </t> </template>

3.为页面添加条件标题,如下所示:(第2步中的粗体)

<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>提交问题如下: (第2步粗体) 

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

 更新模块并打开/books/submit_issues URL。从这个页面,您可以提交这本书的问题。提交后,您可以在后台将它们签入相应的图书表单视图。

 它是如何工作的…

  在此章节的第1步中,我们创建了一个提交图书问题的路径。函数中的**post参数将接受URL中的所有查询参数您还将在**post参数中获得提交的表单数据。在我们的示例中,我们使用了相同的控制器来显示页面并提交问题。如果我们发现数据后,我们将创建一个新的问题book.issue模型,然后将用户重定向到问题页面提交的查询参数,所以用户可以看到提交承认问题,因此可以提交另一个问题,如果他/她想要的。  

  注意,我们使用sudo()来创建一个图书发行记录,因为普通用户(访问者)没有创建新的图书发行记录的访问权限。尽管如果用户从web页面提交了一个问题,仍然需要创建图书发行记录。这是使用sudo()的一个实际示例。 

   在步骤2中,我们已经为问题页面创建了模板。

  在步骤3中,我们添加了条件标题。成功标头将在提交问题后显示。

  在步骤4中,我们添加了带有三个字段的<form>: csrf_token、图书选择和问题描述。最后两个字段用于获取网站用户的输入。但是,csrf_token用于避免跨站点请求伪造(CSRF)攻击如果不在表单中使用它,用户将无法提交表单。当您提交表单时,您将在第1步的books_issues()方法中获得作为**post参数的提交数据。

  在某些情况下,如果您想禁用csrf验证,您可以在路由中使用csrf=False,如下所示:

@http.route(/url, type=http,auth="user",website=True, csrf=False )

 有更多的…

  如果你想,你可以使用单独的路由页面和post数据,你可以在表单中添加操作如下:

...
<form action="/my_url" method="post">
...

  此外,您可以通过在路由中添加方法参数来限制get请求,如下所示:

@http.route(/my_url, type=http, method=POST auth="user",website=True)

 管理搜索引擎优化(SEO)选项

  Odoo为模板(页面)提供内置的SEO支持。然而,有些模板在多个url中使用。例如,在在线商店中,产品页面使用相同的模板和不同的产品数据呈现。对于这类情况,我们希望每个URL都有单独的SEO选项。

准备

  对于这个章节,我们将使用上一个章节中的my_library模块。我们将为每个图书详细信息页面存储单独的SEO数据。在开发此章节之前,您应该在不同的图书页面中测试SEO选项。你可以从顶部的“提升”下拉菜单中得到一个SEO对话框,如下图所示:

技术图片

 

 

   如果您在不同的图书详细信息页面中测试SEO选项,您将注意到更改一个图书页面中的SEO数据将反映在所有图书页面上。我们将在这个章节中解决这个问题。

怎么做呢?

  为每本书管理单独的SEO选项,遵循以下步骤:

  1. 在library_book模型中继承SEO元数据mixin,如下所示:

class LibraryBook(models.Model):
    _name = library.book
    _description = Library Book
    _inherit = [website.seo.metadata]


    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_ids = fields.One2many(book.issue, book_id)

    def _default_website_meta(self):
        res = super(LibraryBook, self)._default_website_meta()
        res[default_opengraph][og:image] = self.env[website].image_url(self, image)
        res[default_twitter][twitter:image] = self.env[website].image_url(self, image)
        return res

2. 在图书详细信息路由中以main_object的形式传递图书对象,如下所示:

    @http.route(/books/<model("library.book"):book>, type=http, auth="user", website=True, sitemap=sitemap_books)
    def library_book_detail(self, book):
        return request.render(
            my_library.book_detail, {
                book: book,
                main_object: book
            })

更新模块和改变不同的图书页面的SEO。它可以通过优化SEO选项来改变。现在,您将能够管理每本书单独的SEO细节。

它是如何工作的…

  要在模型的每个记录上启用SEO,您需要在模型中继承website.seo.metadata这将在library.book模型中添加一些字段和方法。网站将使用这些字段和方法为每本书存储单独的数据。

  如果你想看到SEO mixin的字段和方法,在/addons/website/models/website.py文件中搜索website.seo.metadata模型。

  所有与SEO相关的代码都写在website.layout中,它从传入main_object的记录集中获取所有SEO元信息。因此,在第2步中,我们传递了一个带有main_object键的book对象,这样网站布局就会从这本书中获得所有SEO信息。如果您没有从控制器传递main_object,那么模板记录集将作为main_object传递,这就是为什么您在所有书籍中获得相同的SEO数据的原因。

有更多的…

  Odoo v12增加了对开放GraphTwitter共享的元标签的支持。如果您想在页面中添加您的自定义meta标签,您可以在添加SEO mixin之后覆盖_default_website_meta()


 管理网站的网站地图

  一个网站的网站地图对任何一个网站都是至关重要的。搜索引擎将使用网站网站地图来索引网站的页面。在这个章节中,我们将在网站地图中添加图书详细信息页面。

准备……

  对于这个章节,我们将使用上一个章节中的my_library模块。如果你想在Odoo中检查当前的站点地图,请在浏览器中打开<your_odoo_server_url> /sitemap_xml。这将没有书的URL。

怎么做呢?

  按照以下步骤将图书页面添加到sitemap.xml:

  1. 导入main.py中给定的方法,如下所示:  

from odoo.addons.http_routing.models.ir_http import slug
from odoo.addons.website.models.ir_http import sitemap_qs2dom

  2. 在main.py中添加sitemap_books方法,如下所示:

class Main(http.Controller):
    @http.route(/books, type=http, auth="user", website=True)
    def library_books(self):
        country_id = False
        country_code = request.session.geoip and request.session.geoip.get(country_code) or False
        if country_code:
            country_ids = request.env[res.country].sudo().search([(code, =, country_code)])
            if country_ids:
                country_id = country_ids[0].id
        domain = [|, (restrict_country_ids, =, False), (restrict_country_ids, not in, [country_id])]
        return request.render(
            my_library.books, {
                books: request.env[library.book].search([]),
            })

    def sitemap_books(env, rule, qs):
        Books = env[library.book]
        dom = sitemap_qs2dom(qs, /books, Books._rec_name)
        for f in Books.search(dom):
            loc = /books/%s % slug(f)
            if not qs or qs.lower() in loc:
                yield {loc: loc}

    @http.route(/books/<model("library.book"):book>, type=http, auth="user", website=True, sitemap=sitemap_books)
    def library_book_detail(self, book):
        return request.render(
            my_library.book_detail, {
                book: book,
                main_object: book
            })

    @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)
        })

  3.在图书的详细路由中添加sitemap_books函数引用,如下所示:

...
@http.route(/books/<model("library.book"):book>,type=http, auth="user", website=True,sitemap=sitemap_books)
def library_book_detail(self, book):
...

  更新模块以应用更改。sitemap.xml文件被生成并存储在附件中。然后,它每隔几个小时再生一次。要查看我们的更改,您需要从附件中删除sitemap文件。为此,请访问设置|技术|数据库结构|附件,搜索站点地图,并删除该文件。现在,在浏览器中访问/sitemap.xml URL,您将在站点地图中看到该书的页面。

它是如何工作的…

  在第一步中,我们导入了一些必需的函数。slug用于根据一个记录名称生成一个干净的、用户友好的URL。sitemap_qs2dom用于根据路由和查询字符串生成域。

  在第2步中,我们创建了一个Python生成器函数sitemap_books()。每当生成站点地图时,都会调用此函数。在调用期间,它将接收三个参数——env Odoo环境、rule路由规则和qs查询字符串。在函数中,我们已经用sitemap_qs2dom生成了一个域。然后,使用生成的域搜索图书记录,通过slug()方法生成位置。使用slug,您将获得一个用户友好的URL,如/books/ oddoo -12-development-cookbook-1,而不是books/1。

  在第3步中,我们将sitemap_books()函数引用传递给带有关键字站点地图的路由。


 

获取游客的国家信息

 

  Odoo CMS内置了对GeoIP的支持。在实时环境中,您可以基于IP跟踪访问者的国家。在这个章节中,我们将根据访问者的IP地址获得访问者的国家。

准备……

  对于这个章节,我们将使用上一个章节中的my_library模块。在这个章节中,我们将根据访问者的国家在网页中隐藏一些书籍。您需要下载GeoIP数据库。之后,您需要从cli选项中传递数据库位置,如下所示:

./odoo-bin -c config_file --geoip-db=location_of_geoip_DB

  1. 在library.book模型中添加restrict_country_ids m2m字段,如下所示:

class LibraryBook(models.Model):
    _name = library.book
    _description = Library Book
    _inherit = [website.seo.metadata]
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_ids = fields.One2many(book.issue, book_id) restrict_country_ids = fields.Many2many(res.country)
......

  2. 在library.books模型的表单视图中添加一个restrict_country_ids字段,如下所示:

...
<group>
<field name="date_release"/>
<field name="restrict_country_ids" widget="many2many_tags"/>
</group>
...

  3.更新/books控制器以根据国家限制图书,如下所示:

class Main(http.Controller):
    @http.route(/books, type=http, auth="user", website=True)
    def library_books(self):
        country_id = False
        country_code = request.session.geoip and request.session.geoip.get(country_code) or False
        if country_code:
            country_ids = request.env[res.country].sudo().search([(code, =, country_code)])
            if country_ids:
                country_id = country_ids[0].id
        domain = [|, (restrict_country_ids, =, False), (restrict_country_ids, not in, [country_id])]
        return request.render(
            my_library.books, {
                books: request.env[library.book].search([]),
            })
......

  更新模块以应用更改。在图书的受限国家字段中添加您的国家,以及access /book。这将不会显示列表中的受限制书籍。

  警告:此章节不适用于本地服务器。它将需要一个托管服务器,因为在本地机器中,您将获得本地IP,这与任何国家都没有关系。

  您还需要正确配置nginx

它是如何工作的…

  在第一步中,我们在library.book模型中添加了一个新的restricted_country_ids many2many类型字段。如果网站访问者来自受限国家,我们将把书藏起来。 

  在步骤2中,我们刚刚在图书的表单视图中添加了一个restricted_country_ids字段。如果GeoIP和NGINX配置正确,Odoo会在request.session.geoip添加GeoIP信息,然后你可以从中获得国家代码。 

  在第三步中,我们从GeoIP获取了国家代码,然后根据country_code获取了国家的记录集。在获得访问者的国家信息后,我们根据受限制的国家使用域名过滤图书。

  如果你没有真正的服务器,你想要测试它,你可以在控制器中添加一个默认的国家代码,像这样:country_code = request.session.geoip和request.session.geoip.get(‘country_code‘)或‘ IN‘


 

跟踪营销活动 

  在任何业务或服务中,熟悉投资回报(ROI)是非常重要的。ROI用于评估投资的效率。广告投资可以通过Urchin跟踪模块(UTM)代码进行跟踪。UTM代码是一个可以添加到URL中的小字符串。此UTM代码将帮助您跟踪活动、资源和媒体。

准备

  对于这个章节,我们将使用上一个章节中的my_library模块。Odoo内置了对UTMs的支持。对于我们的库应用程序,我们没有任何使用UTMs的实际案例。但是,在这个章节中,我们将在my_library中的/books/submit_issues生成的问题中添加UTM。

怎么做呢?

  按照以下步骤链接由/books/submit_issues URL上的web页面生成的图书问题中的UTMs:

1. 在manifest.py的depends部分添加一个utm模块,如下:

depends: [base, website, utm],

2. 在书中继承utm.mixin。发行模式如下:

class LibraryBookIssues(models.Model):
_name = book.issue
_inherit = [utm.mixin]
book_id = fields.Many2one(library.book, required=True)
submitted_by = fields.Many2one(res.users)
issue_description = fields.Text()

3.在book_issue_ids字段的树形视图中添加一个campaign_id字段,如下所示:

...
<group string="Book Issues">
<field name="book_issue_ids" nolabel="1">
<tree name="Book isuues">
<field name="create_date"/>
<field name="submitted_by"/>
<field name="issue_description"/>
<field name="campaign_id"/>
</tree>
</field>
</group>
...

  更新模块以应用更改。要测试UTM,您需要执行以下步骤:

  •   在Odoo中,UTM是基于cookie处理的,而一些浏览器不支持本地主机中的cookie,因此如果要用本地主机测试它,请使用http://127.0.0.1:8069访问实例。
  •   默认情况下,UTM跟踪对销售人员是被阻止的。因此,要测试UTM特性,您需要使用门户用户登录。
  •   现在,像这样打开URL: http://127.0.0.1:8069/books/submit_issues?utm_campaign=sale.
  •   提交图书问题并在后端检查图书问题。这将在图书的表单视图中显示活动。

它是如何工作的…

  在第一步中,我们在book.issue模型中继承了utm.mixin将向book.issue模型添加以下字段:

  campaign_id: utm.campaign模型中的Many2one字段。这是用来跟踪不同的活动,如夏季和圣诞节特辑。

  source_id: utm.source模型中的Many2one字段。这是用来跟踪不同的来源,如搜索引擎和其他域。

 

  medium_id: utm.medium模型中的Many2one字段。这是用来跟踪不同的媒体,如明信片,电子邮件,或横幅广告。

   要跟踪活动、媒体和来源,你需要在营销媒体中共享一个URL,如下:your_url?utm_campaign=campaign_name&utm_medium=medium_name&utm_source=source_name

  如果访问者从任何营销媒体访问您的网站,那么当在网站页面上创建记录时,campaign_id、source_id和medium_id字段将自动填充。

  在我们的示例中,我们只跟踪了campaign_id,但是还可以添加source_id和medium_id。

  注意:在我们的测试示例中,我们使用了campaign_id=sale。sale是模型utm_campaign中记录的名称。默认情况下,utm模块会添加一些活动、媒体和来源的记录。唱片销售就是其中之一。如果你想创建一个新的活动,媒体和源代码,你可以在开发者模式下访问Link Tracker > UTMs菜单。

 


 

管理多个网站 

  在v12中,Odoo增加了对多个网站的支持。这意味着同一个Odoo实例可以在多个域上运行,也可以在显示不同记录时运行。 

准备 

  对于这个章节,我们将使用上一个章节中的my_library模块。在这个章节中,我们将会隐藏基于网站的书籍。 

怎么做呢? 

  按照以下步骤,使网上网站-多网站兼容: 

  1. 在library.book模型中添加website.multi.mixin,如下所示:

class LibraryBook(models.Model):
_name = library.book
_inherit = [website.seo.metadata, website.multi.mixin]
...

  2. 在图书表单视图中添加website_id,如下所示:

...
<group>
<field name="author_ids" widget="many2many_tags"/>
<field name="website_id"/>
</group>
...

  3.修改/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),
})
...

  4. 导入werkzeug,修改book details控制器,限制从另一个网站访问图书,如下:

...
@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 URL并检查图书列表。在这之后,改变网站和检查图书列表。对于测试,您可以从“网站切换器”下拉菜单中更改网站。请参考下面的截图:

技术图片

 

   您还可以尝试直接从URL(如/books/1)访问图书详细信息。如果一本书不是来自该网站,它将显示为404。

它是如何工作的…

  在第一步中,我们添加了website.multi.mixin。这个mixin添加了一个基本实用程序来处理模型中的多个网站。这个mixin在模型中添加website_id字段。该字段用于确定记录用于哪个网站。 

  在第2步中,我们在图书的表单视图中添加了website_id字段,因此图书将根据网站进行过滤。 

  在步骤3中,我们修改了用于查找图书列表的域。request.website.website_domain()将返回一个域,该域将过滤掉不来自该网站的图书。

  请注意,有些记录没有设置任何website_id。这些记录将在所有网站上显示。这意味着,如果某本书没有website_id字段,那么该图书将显示在所有网站上。

  然后,我们在web搜索中添加域名,如下: 

  在步骤4中,我们限制了图书访问。如果这本书不是为当前的网站,那么我们将提出一个未发现的错误。can_access_from_current_website()方法将返回值True,如果图书记录用于当前活动的网站,则返回值为True;如果图书记录用于另一个网站,则返回值为False。

  如果勾选,我们已经在两个控制器中添加了**post。这是因为没有它,**post /books和/books/<model:library_book:book>;将不接受查询参数。当从网站切换器切换网站时,它也会产生一个错误,所以我们添加了它。通常,在每个控制器中添加**post是一个很好的实践,这样它就可以处理查询参数。

 

 

 

 

 



















以上是关于odoo13学习---15 CMS网站开发的主要内容,如果未能解决你的问题,请参考以下文章

odoo13学习---16 Web客户端开发

odoo14和Odoo15的区别

odoo开发学习 -- odoo13 Docker镜像制作

odoo开发学习 -- odoo13 Docker镜像制作

pycharm添加Odoo代码片段

《odoo快速入门与实战》的在线开发在 13版 与 11版 的问题与解决