Odoo 14开发者指南第十三章 Web服务端开发

Odoo Alan 3年前 (2021-03-07) 5577次浏览 0个评论 扫描二维码

全书完整目录请见:Odoo 14开发者指南(Cookbook)第四版

我们将在本章中介绍Odoo网页服务端部分的基础知识。注意本章中所讲解的为基础部分,有关更高阶的功能,请参见第十四章 CMS网站开发

所有的Odoo网页请求都是由Python库werkzeug来进行处理的。虽然werkzeug的复杂部分多隐藏在Odoo便捷的封装器中,学习其底层的运行机制也会非常的有帮助。

本章中,我们将讲解如下内容:

  • 让路径在网络中可访问
  • 限制线上路径的访问
  • 使用传递给handler的参数
  • 修改已有handler
  • 提供对静态资源的访问

技术准备

学习本章要求安装有在线Odoo平台。

本章中使用的所有代码可通过GitHub仓库进行下载:https://github.com/alanhou/odoo14-cookbook/tree/main/Chapter13。

让路径在网络中可访问

本节中我们学习如何让http://yourserver/path1/path2这样的URL可由用户访问。这既有可能是一个网页,也有可能是返回供其它程序使用数据的路径。后一种情况中,我们通常会使用JSON格式来接收参数并提供数据。

准备工作

我们将使用library.book 模型,可参见第四章 应用模型,因此如果你还没有按该章进行操作,请通过GitHub仓库获取相关代码以便能按照本章示例进行操作。

我们希望允许任何用户查询完整的图书列表。此外,我们希望通过JSON请求对程序提供同样的信息。

如何实现…

我们需要添加控制器,按惯例放在一个名为controllers的文件夹中:

  1. 添加带有我们页面HTML内容的controllers/main.py文件,如下:
  2. 添加一个函数来以JSON格式提供相同的信息,如下例所示:
  3. 添加controllers/__init__.py文件,如下:
  4. 在my_library/__init__.py文件中导入controllers,如下:

在重启服务之后,可以在浏览器中访问/my_library/books并获得书名的列表。要进行JSON-RPC的测试,需要构造一个JSON请求。简单的实现方式是通过使用如下命令在命令行中接收输出:

如果此时得到404报错,可能是在实例中有不止一个数据库。这种情况下,Odoo无法决定使用哪个数据库对请求提供服务。

使用–db-filter=’^yourdatabasename$’参数来强制Odoo使用安装模块所在的具体数据库。现在该路径应该就可以访问了。

运行原理…

这里的两个关键部分是我们的控制器通过odoo.http.Controller获取,并且用于提供内容服务的方法由odoo.http.route进行装饰。继承 odoo.http.Controller以通过Odoo路由系统注册该控制器,与继承odoo.models.Model注册模型的方式相似。同时Controller有一个处理这一注册的元类。

通常由插件所处理的路径以插件名开头,以避免名称的冲突。当然,如果你继承一些插件功能,会使用这个插件名。

odoo.http.route

route装饰器让我们首先告诉Odoo某一方法可通过web访问,第一个参数决定可以访问哪个路径。除了可传递字符串,也可以传递字符串列表,这样相同的函数可以为多个路径提供服务。

type参数默认为http,决定所提供服务对应的请求类型。严格意义上说,JSON是HTTP,声明第二个函数为type=’json’会让事情变得很轻松,因为接下来Odoo会替我们处理类型转换。

现在先不用担心auth参数,会在本章的限制网络可访问路径的访问一节中进行讲解。

返回值

Odoo中函数的返回值可由route装饰器的type参数来决定。对于type=’http’,我们通常希望传送一些HTML,因此第一个函数只是返回包含 HTML 的字符串。一种代替方案是使用request.make_response(),可控制在响应中发送的headers。因此要表明页面最后更新的时间,可以在books()中的修改最后一行为如下代码:

代码发送一个Last-modified头及所生成的 HTML,告诉浏览器列表最后一次修改的时间。我们可以从library.book 模型的write_date字段中提取这一信息。

为让前面代码段可以运行,我们需要在文件的顶部添加一些导入语句,如下:

也可以手动创建一个werkzeug的Response对象并返回,但费这番功夫收获甚微。

📝重要信息:手动生成HTML对于演示非常好,但在生产环境的代码中则不应这么做。保持使用模板,如我们在第十五章 网页客户端开发中的创建或更改模板 – QWeb一节中所演示的,并通过调用request.render()返回。这样我们可以从容地进行本地化并让代码通过将展示层与业务逻辑分离而更优雅。同时,模板为我们提供函数在输出 HTML 之前转义数据。前面的代码会容易遭受跨站脚本攻击(比如用户可能会把脚本标签放到书名中)。

对于JSON请求,只需返回希望交给客户端的数据结构,Odoo会做序列化。这时,应限定所返回的数据类型可进行JSON序列化,通常意味着要使用字典、列表、字符串、浮点型和整型。

odoo.http.request

request对象是引用当前处理请求的静态对像,包含所有执行需要的内容。这里最重要的是request.env属性,包含一个与模型中self.env相同的Environment对象。环境与当前用户绑定,在前例中并不存在,因为我们使用了auth=’none’。缺少用户也是我们使用sudo()来在示例代码中调用模型方法的原因。

如果习惯于web开发,则会倾向进行会话处理,这是绝对正确的。使用OpenERPSession对象(是对werkzeug的Session对象的轻微封装)的request.session,以及用request.session.sid来访问会话ID。存储会话值,只需将request.session作为字典处理,如以下示例代码所示:

📝重要信息:注意在会话中存储数据与使用全局变量是相同的。仅在必要时才使用它。通常对于多请求动作是需要的,如website_sale模块中的结账。

扩展知识…

route装饰器可带有其它的参数来进一步自定义其行为。默认允许所有的HTTP方法,并且Odoo将所有传递的参数组装在一起。使用methods参数,我们可以传递一个可接受的方法列表,通常是[‘GET’] 或[‘POST’]。

要允许跨域请求的话(出于安全和隐私考虑,浏览器阻止对脚本所加载域名以外域名的AJAX和其它类型的请求),可设置cors参数为 * 来允许来自所有域名的请求,或设置一个URI来限定请求为来自该URI的请求。如果未设置这个参数,这也是默认情况,即未设置Access-Control-Allow-Origin头,采取浏览器的默认行为。在本例中,我们可能会希望在/my_module/books/json中进行设置,来允许从其它网站拉取的脚本访问图书列表。

默认,Odoo通过对每个请求传递token来保护一些类型的请求免受称为跨站请求伪造(CSRF)的攻击。如果想要关闭,设置csrf参数为False,但应注意这通常不是一个好的想法。

其它内容

参见以下各点来了解有关HTTP路由的更多知识:

  • 如果在同一个实例中托管多个Odoo数据库,那么不同的数据库可能运行在不同的域名中。这时我们可以使用–db-filter选项或使用https://github.com/OCA/server-tools的dbfilter_from_header模块,它有助于按域名过滤数据库。在写本书时这个模块还没有迁移到版本14,但在本书出版时应该已经迁移了。
  • 要学习如何使用模板来实现模块化,请参见本章中的修改已有handler一节。

限制线上路径的访问

我们将在本节中探讨Odoo为路由所提供的三种验证机制。并使用不同的验证机制来定义路由,展示它们之间的不同之处。

准备工作

我们将对前一节的代码继续进行扩展,还会依赖第四章 应用模型中的library.book模型,所以请准备好相关代码再继续下面的学习。

如何实现…

在controllers/main.py中定义handler:

  1. 添加显示所有图书的路径,如下例所示:
  2. 添加一个显示所有图书的路径并表明哪个是由当前用户所著的。参见如下示例代码:
  3. 添加显示当前用户图书的路径,如下:

通过这段代码,/my_library/all-books和/my_library/allbooks/mark-mine路径对未验证用户所显示内容相同,但登录用户会在后一个路径中看到自己的书以粗体显示。对未验证用户/my_library/allbooks/mine路径完全不可访问。如果未验证依然访问该路径,会被重定向到登录页面进行登录。

运行原理…

验证方法之间的不同基本上通过request.env.user的内容可判断到。

对于auth=’none’哪怕是已验证用户在访问路径时用户记录也是空的。使用这一个验证的场景是所响应的内容对用户不存在依赖,或者是在服务端模块中提供与数据库无关的功能。

auth=’public’的值将未验证用户设置为一个带有XML ID base.public_user的特殊用户,已验证用户设置为用户自己的记录。对于所提供的功能同时针对未验证和已验证用户而已验证用户又具有一些额外的功能时应选择它,前面的代码中已经演示。

使用auth=’user’来确保仅已验证用户才能访问所提供的内容。通过这个方法,我们可以确保request.env.user指向已有用户。

扩展知识…

验证方法的逻辑位于base插件的 ir.http模型中。不论在路由的auth参数中传递什么值,Odoo搜索该模型中名为_auth_method_<yourvalue>的函数,这样可以通过继承它并声明处理所选验证方法来进行自定义。

作为示例,我们将提供一个名为base_group_user的验证方法,仅针对属于base.group_user组的当前登录用户,如下例所示:

现在可以在装饰器中使用auth=’base_group_user’,并确保运行这个路由handler的用户是该组的成员。使用一点技巧,我们还可以将其扩展为auth=’groups(xmlid1,…)’,这一实现留作读者练习,可参见GitHub仓库中的示例代码Chapter13/r2_paths_auth/my_library/models/sample_auth_http.py。

使用传递给handler的参数

能够显示内容自然很棒,但能够根据用户输入显示内容则更佳。本节将演示接收输入和做出响应的不同方式。如同前一小节,我们将使用library.book模型。

如何实现…

首先,我们将添加一个接收传统参数图书 ID来显示其详情的路由。然后,我们使用将参数嵌入路径内的方式来实现同样的功能:

  1. 添加一个接收图书ID参数的路径,如下例所示:
  2. 添加一个我们可以传递图书ID的路径,如下:

如果在浏览器中访问 /my_library/book_details?book_id=1,应该会看到ID为1的图书的详情页。如不存在,会收到一个报错页面。

报错内容:The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

第二个handler允许我们访问/my_library/book_details/1并浏览到相同的内容。

译者注: 请注意原书中为auth=’none’,这会出现psycopg2.ProgrammingError: can’t adapt type ‘RequestUID’报错,因为这里我们用到了模型数据,同时也请检查代码是否为最新的稳定版。

运行原理…

默认,Odoo(实际上是werkzeug)合并了GET和POST参数并将它们通过关键词参数传递给handler。因此,仅需声明接收参数book_id的函数,我们以GET(URL中的参数)或POST(通过以是action属性指定handler的<form>元素传递)来引入该参数 。如果没有对该参数添加默认值,运行时会在未设置参数就进行访问时抛出错误。

第二个示例利用了werkzeug环境中大多数是虚拟路径的这一点。因此我们可以定义路径包含一些输入内容。在本例中,我们指定了library.book的ID为路径的最后一个组成部分。冒号后为关键词参数的名称。我们的函数会以关键词传递参数并调用。这里,Odoo处理对这一ID的调用并分发一条浏览记录,当然要在访问路径的用户具有相应权限时才能使用。假定book是一条浏览记录,我们可以传递book.id 来作为book_id参数重复利用第一个示例的函数,给出相同的内容。

扩展知识…

在路径中定义参数是werkzeug所带的一个功能,称为转换器(converter)。模型转换器由Odoo添加,它还定义接收逗号分隔ID列表的转换器模型,并传递包含这些 ID 的记录集给我们的handler。

转换器的优美之处在于runtime强制参数为所需类型,而使用普通关键字参数则需要自己处理。这些以字符串进行分发,而你需要像第一个示例一样自己处理必要的类型转换。

内置的werkzeug转换器包含 int, float和string,以及更为复杂的path, any或uuid等类型。可以在https://werkzeug.palletsprojects.com/en/1.0.x/中查看它们的语法。

其它内容

如果希望学习更多的HTTP路径,参见如下各点:

  • Odoo的自定义转换器在base模块中的ir_http.py中定义并在ir.http的_get_converters类方法中注册。作为练习,读者可以创建自己的转换器,它允许你访问/my_library/book_details/Odoo+cookbook页面来接收图书的详情(如果之前在library中进行了添加的话)。
  • 如果想要学习更多在该路径上的表单提交的话,请参见第十四章 CMS网站开发中的从网站用户获取输入一节。

修改已有handler

在安装website模块时,/website/info path显示有关Odoo实例的一些信息。本节中,我们将重载它来修改信息页面的布局,并修改其显示的内容。

准备工作

安装website模块并查看/website/info路径。本节中,我们更新/website/info路由来提供更多的信息。

如何实现…

我们需要调整已有模板并重载原有的handler。可以像这样实现:

  1. 在views/templates.xml文件中重载qweb模板,如下:
  2. 在controllers/main.py文件中重载handler,如下例所示:

此时在访问info页时,我们将只能看到过滤后表中已安装应用的列表,与原始定义列表形成对照。

安装前效果:

Odoo 14开发者指南第十三章 Web服务端开发

安装后的效果:

Odoo 14开发者指南第十三章 Web服务端开发

运行原理…

第一步中,我们重载了已有QWeb模板。要找出是哪一个,我们需要查看原始handler的代码。通常,会找到有类似下面这行的内容,告诉我们需要重载template.name:

本例中,handler使用了website_info模块,但随即由另一个模板website.show_website_info进行扩展,因而重载它会非常方便。这里,我们将显示已安装应用的定义列表替换为表格。有关QWeb继承原理的更多详情,请参见第十五章 网页客户端开发

为重载handler方法,我们必须识别出定义handler的类,本例中即为odoo.addons.website.controllers.main.Website。我们需要导入该类来对其继承。现在,我们可以重载方法并修改传递给响应的数据。注意重载的handler在这里出于简洁考虑返回的是一个Response对象而不是像前一小节中那样的HTML字符串。这个对象包含一个对待使用模板的引用,以及模板可访问的值,但它仅在请求的最终运行。

通常,有三种方式可修改已有handler:

  • 如果使用QWeb模板,最简单的修改方式是重载该模板。对于布局修改和小的逻辑变动这种方法更适合。
  • QWeb获取一个所传递的上下文,在响应中以qcontext成员存在。通常是一个可以添加或删除值来满足需要的字典。在前例中,我们过滤了应用列表来仅针对website。
  • 如果handler接收到参数,也可以进行预处理,来让所重载的handler按照所希望的方式运行。

扩展知识…

如前一部分中所学,控制器的继承与模型继承稍有不同,实际上需要一个base类引用并对其使用Python继承。

不要忘记通过@http.route装饰器来装饰新的handler;Odoo使用它作为标记,标记哪些方法在网络层中暴露。如果你省去了该装饰器,实际上会让handler的路径不可访问。

@http.route装饰器本身与字段声明的行为相似,未设置的值会从所重载函数的装饰器中获取,这样对不希望修改的值就无需重复指定。

在从所重载的函数中接收到response对象后,不仅仅是能修改QWeb上下文:

  • 我们可以通过操作response.headers来添加或删除HTTP头部
  • 如果希望渲染完全不同的模板,可以覆盖response.template
  • 要查看response是否是基于QWeb,使用response.is_qweb进行查询
  • 通过调用esponse.render()可获取结果HTML代码

其它内容

提供对静态资源的访问

网页中包含多种类型的静态资源,如图片、视频、CSS等。本节中,我们学习如何管理模块的静态资源。

准备工作

本节中,我们会在页面中显示图片。所以请准备好一张图片。同时需要用到前面小节中的my_library模块。

如何实现…

按照如下步骤来在页面中显示图片:

  1. /my_library/static/src/img目录中添加图片。
  2. controller中定义一个新路由,将图片URL替换为你自己的:

重启服务、更新模块以应用修改。现在访问/demo_page查看页面中的图片。

运行原理…

/static文件夹下放的文件都视作静态资源,可以对外访问。本例中,我们将图片放到了/static/src/img目录下。可以将静态资源放到static目录下的任意位置,但有一个基于文件类型的推荐目录结构:

  • /static/src/img 为图片目录。
  • /static/src/css 为CSS文件目录。
  • /static/src/scss 为SCSS文件目录。
  • /static/src/fonts 为字体文件目录。
  • /static/src/js 为JavaScript文件目录。
  • /static/src/xml 为客户端QWeb模板所用的XML文件目录。
  • /static/lib 为外部库文件目录。

本例中,我们在页面中显示了一张图片。也可以直接通过/my_library/static/src/image/bug.jpg访问该图片。

本节中,我们在页面中显示了一个静态资源(图片)并学习了各种静态资源的推荐目录。还有更简单的展示页面内容和静态资源的方法,我们在下一章中进行学习。

喜欢 (6)
[]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址