├── README.md ├── 11.md ├── 13.md ├── 2.md ├── 16.md ├── 6.md ├── 7.md ├── 21.md ├── 3.md ├── 10.md ├── 23.md └── 1.md /README.md: -------------------------------------------------------------------------------- 1 | # Odoo 14开发者指南(第四版) 2 | 3 | > Odoo 12开发者指南请点击[这里](https://github.com/iTranslateX/odoo-cookbook/tree/v12) 4 | 5 | Odoo 14开发者指南(Cookbook)第四版,是一套提供了各类开发场景的完整资源,帮助读者通过Odoo框架构建复杂商业应用。不论你是希望自定义已有模块、新建模块,还是自定义网站或后台web客户端(JS),本书都讲解到了Odoo开发的方方面面。 6 | 7 | 在最新的发行版中,强大的Odoo框架为快速应用开发提供了广泛的功能。这本新的Odoo开发指南有助于读者探索Odoo 14中的新功能并学习如何从零开始使用这些功能开发Odoo应用。读者还将学习到Odoo 14中新的网站概念并一睹Odoo的新web客户端框架OWL(Odoo Web Library的简称)。 8 | 9 | 在完成安装后,就开始通过真实世界中的案例来探索Odoo框架。然后从基础创建一个新的Odoo模块并逐步了解高级的框架概念。读者还将学习到如何修改已有应用,如销售点应用(PoS)。这本书不只限于后端开发,还包含用于新建视图和微件的高级JavaScript章节。接下来你还将学习到网站开发并通过学习性能优化、调试和自动化测试成为一名合格的Odoo开发者。最后,我们会讲解一些高级概念,如多网站、应用内购买(IAP)、Odoo.sh、IoT Box(物联网盒子)以及部署相关知识。 10 | 11 | 使用Odoo CMS的动态构建代码块可以创建出美观的网站;进而学习到一些高级概念,如缓存、预加载和调试;通过新的OWL框架修改后台JavaScript组件和POS;通过远程过程调用(RPC)来连接和访问Odoo中的任意对象;通过Odoo.sh管理、部署并测试Odoo实例;配置IoT Box添加及升级POS硬件,并了解如何实现IAP服务。 12 | 13 | 在本书的最后,读者可以掌握到构建迷人Odoo应用所需的所有知识,并且会对开发的最佳实践了然于胸,这对于使用Odoo框架也会非常有益。 14 | 15 | ## 本书面向的读者 16 | 17 | 本书对于新入门或希望通过Odoo框架开发出高效的商业应用的有经验的Odoo开发者均适用。具备Python和JavaScript的基础知识在学习本书时将获益更多。 18 | 19 | ## 本书的主要内容 20 | 21 | [第一章 安装Odoo开发环境](1.md):讲解如何为Odoo创建开发环境、启动Odoo、创建配置文件以及启用Odoo开发者工具。 22 | 23 | [第二章 管理Odoo服务端实例](2.md):提供安装来自GitHub插件的一些有用贴士以及在实例中组织源代码的讲解。 24 | 25 | [第三章 创建Odoo插件模块](3.md):讲解Odoo插件模块的结构并提供从零开始创建一个简单的Odoo模块的操作指南分解。 26 | 27 | [第四章 应用模型](4.md):聚焦于Odoo模型结构,并讲解所有字段类型及它们的属性。本章还包含通过继承模块来扩展已有数据库结构的相关技巧。 28 | 29 | [第五章 基本服务端开发](5.md):讲解Odoo中执行增删改查(CRUD)操作的各类框架方法。本章还包含继承和扩展已有方法的各种方式。 30 | 31 | [第六章 管理模块数据](6.md):展示如何和模块代码一并进行数据的迁移。还讲解在新发行版中插件修改了数据模型时如何编写迁移脚本。 32 | 33 | [第七章 调试模块](7.md):提供调试Odoo代码的不同策略并介绍了Python调试器。本章包含在开发者模式下运行Odoo的一些技巧。 34 | 35 | [第八章 高级服务端开发技巧](8.md):讲解ORM框架更高级的课题。对于开发向导、SQL视图、安装钩子(hook)、on-change方法等非常有用。本章还讲解了如何在数据库中执行原生SQL查询。 36 | 37 | [第九章 后端视图](9.md):讲解如何为数据模型编写业务视图以及如何在视图中调用服务端方法。涵盖了常用视图(列表、表单和搜索视图),以及一些更为复杂的视图(看板、图形、日历、透视表等)。 38 | 39 | [第十章 权限安全](10.md):讲解如何在Odoo实例中指定谁可以执行什么操作,方式有创建安全组、编写访问控制列表定义在给定模型中每个组可执行的操作,在必要时还可以编写记录级的规则。 40 | 41 | [第十一章 国际化](11.md):展示Odoo中语言翻译的原理。还讲解如何安装多语言及导入/导出所翻译的词语。 42 | 43 | [第十二章 自动化、工作流、Email和打印件](12.md):描绘了Odoo中为记录实现业务流程的不同工具。还展示了如何使用服务端动作和自动化规则来对业务规则进行支持。本章还讲解可生成动态PDF文档的QWeb报告。 44 | 45 | [第十三章 Web服务端开发](13.md):涵盖Odoo web服务端的核心内容。展示了如何创建自定义URL路由来在指定URL上提供数据服务,以及如何对这些URL进行访问控制。 46 | 47 | [第十四章 CMS网站开发](14.md):讲解如何使用Odoo管理网站。还展示了如何创建和修改美观的网页和QWeb模板。本章还包含如何创建带选项的动态网页构建代码块。它包含一些管理 SEO、用户表单、UTM追踪、网站地图和获取访客地理信息的独立小节。本章还强调了Odoo中最新的多站点概念。 48 | 49 | [第十五章 网页客户端开发](15.md):深入到Odoo的JavaScript部分。涵盖了如何创建新字段微件以及对服务端发送RPC调用。还包含如何从零开始创建全新的视图。读者还将学习到如何创建操作向导。 50 | 51 | [第十六章 Odoo Web Library (OWL)](16.md),介绍名为OWL的新客户端框架。涵盖了OWL组件的生命周期。还包含从零创建字段微件的小节。 52 | 53 | [第十七章 Odoo的应用内购买](17.md):涵盖有关Odoo最新的应用内购买(IAP)概念的所有内容。本章中会学习到如何为IAP创建客户端和服务模块。读者还将学习到如何创建IAP账户并从终端用户提取IAP款项。 54 | 55 | [第十八章 自动化测试用例](18.md):包含如何编写和执行自动化测试用例。这包括服务端、客户端测试用例。本章还包含导览测试用例以及对失败的测试用例设置headless Chrome来获取视频。 56 | 57 | [第十九章 使用Odoo.sh管理、部署和测试](19.md):讲解如何通过PaaS平台Odoo.sh来管理、部署和测试Odoo实例。还涉及到如何管理各类实例,如生产、预发布和部署阶段。本章还包含针对Odoo.sh的各种配置选项。 58 | 59 | [第二十章 Odoo中的远程过程调用(RPC)](20.md):涵盖从外部应用连接Odoo实例的不同方式。本章教你如何通过XML-RPC、JSON-RPC和odoorpc库连接Odoo 以及从Odoo实例访问数据。 60 | 61 | [第二十一章 性能优化](21.md):讲解用于获取Odoo中性能提升的不同概念和模式。本章包含预提取、ORM缓存和代码性能测试来监测性能问题的概念。 62 | 63 | [第二十二章 POS(销售点)](22.md):涵盖 POS 应用的自定义。包含对用户界面、添加新动作按钮、修改业务流和扩展客户菜单的自定义。 64 | 65 | [第二十三章 在Odoo中管理Email](23.md):讲解如何在Odoo中管理email和chatter工具。通过配置邮件服务器开始,然后讲解Odoo框架的邮件API。本章还涵盖Jinja2和QWeb邮件模板、表单视图、字段日志和活动的聊天工具。 66 | 67 | [第二十四章 管理IoT盒子](24.md):给出了最新的IoT盒子硬件的重点讲解。本章涵盖如何配置、访问和调试IoT盒子。还包含一个集成IoT盒子到你的自定义插件的示范。 -------------------------------------------------------------------------------- /11.md: -------------------------------------------------------------------------------- 1 | # 第十一章 国际化 2 | 3 | 全书完整目录请见:[Odoo 14开发者指南(Cookbook)第四版](README.md) 4 | 5 | Odoo支持多语言并允许不同的用户根据自己的方便选用不同的语言。这通过Odoo内置的i18n功能实现。通过字符串翻译,Odoo还支持日期的数字格式及时间格式化等。 6 | 7 | 本章中,我们将学习如何在Odoo中启用多语言以及如何在自定义模块中添加翻译文件。在掌握这些新功能时会有助于提升Odoo的用户体验。 8 | 9 | 本章中,我们将讲解如下内容: 10 | 11 | - 安装语言及配置用户首选项 12 | - 配置语言相关设置 13 | - 通过网页客户端在用户界面翻译文本 14 | - 将翻译字符串导出到文件 15 | - 使用`gettext`工具来简化翻译 16 | - 将翻译文件导入到Odoo中 17 | - 对网站修改自定义URL语言代码 18 | 19 | 这里的很多操作可以通过网页客户端用户界面或命令行来完成。在可以使用的地方,我们会讲解两种选项的使用方法。 20 | 21 | ## 安装语言及配置用户首选项 22 | 23 | Odoo已预置了本地化,也就是说它支持多语言和本地化设置,如日期和数字格式。 24 | 25 | 初次安装时,仅能使用默认语言英语。要对用户开放其它语言和地点,需要先安装这些语言。本节中,我们将学习如何设置用户首选项以及如何进行应用。 26 | 27 | ### 如何实现... 28 | 29 | 启用开发者模式并按照如下步骤在Odoo实例中安装新的语言: 30 | 31 | 1. 访问Settings > Translations > Load a Translation菜单项。这里有如下图所示的Add Language链接。点击该链接会打开一个加载语言的对话框: 32 | ![图11.1 – 通用设置中的语言选项](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021030809040060.png) 33 | 图11.1 – 通用设置中的语言选项 34 | 35 | 2. 选择想要加载的语言: 36 | ![图11.2 – 加载语言的对话框](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021030809071950.png) 37 | 图11.2 – 加载语言的对话框 38 | 39 | 3. 点击Add按钮会加载所选中的语言,会打开如下所示的确认对话框: 40 | ![图11.3 – 显示语言加载的对话框](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021030809102084.png) 41 | 图11.3 – 显示语言加载的对话框 42 | 43 | 4. 新语言也可以通过命令行来安装。与上一步同等的命令如下: 44 | 45 | ``` 46 | $ ./odoo-bin -d mydb --load-language=zh_CN 47 | ``` 48 | 49 | 5. 要设置用户使用的语言,访问Settings > Users & Companies > Users,在用户表单的首选项(Preferences)标签中设置语言(Language)字段的值。在同一界面还可以设置用户的时区: 50 | ![图11.4 – 设置语言的用户表单](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021030809180197.png) 51 | 图11.4 – 设置语言的用户表单 52 | 53 | 用户也可以通过Preferences菜单项来进行这些配置。点击网页端窗口右上角的用户名即可。 54 | 55 | ### 运行原理... 56 | 57 | 用户可以有自己的语言和时区首选项。语言设置用于将用户界面的文本翻译为所选的语言并按本地习惯应用浮点和货币字段。 58 | 59 | 在语言可供用户选择之前,它必须通过**Add language**选项进行过安装。可用的语言列表在开发者模式下可通过Settings > Translations > Languages菜单项进行查看。带有active标记的语言是已安装的。 60 | 61 | 每个Odoo插件模块负责其自身的翻译资源,位于i18n子目录中。每种语言的数据应放在 .po文件中。在我们的示例中,西班牙语的翻译文件通过es_ES.po(中文zh_CN.po)数据文件进行加载。 62 | 63 | Odoo还支持基本语言(base language)的概念。例如,如有西班牙语的es.po文件和墨西哥西班牙语的es_MX.po 文件,那么es.po会作为es_MX.po的基本语言。在安装西语的时候,两个数据文件都会被加载,先安装基本语言,再安装指定的语言。因此,具体语言的翻译文件仅需包含针对语言变体部分的字符串,在本例中即为墨西哥西班牙语。 64 | 65 | `i18n`子目录中还应包含 `.pot`文件,提供一个翻译的模板并包含所有的可翻译字符串。本章中*将翻译字符串导出到文件*一节讲解了如何导出可翻译字符串生成该文件。 66 | 67 | 在安装一种语言时,对应的资源从所有已安装的插件模块中载入并在Translated Terms模型中保存。它的数据可以在 Settings > Translations > Application Terms > Translated Terms 菜单项(注意仅在开发者模式下可见)中进行查看(和编辑)。 68 | 69 | 已安装语言的翻译文件还会在新插件模块安装或已有插件模块升级时进行载入。 70 | 71 | ### 扩展知识... 72 | 73 | 通过再次点击语言中的刷新图标即可在无需升级插件模块的情况下重新载入翻译文件。如果升级了翻译文件而又不想经历升级模块(及其所有依赖)的麻烦时可以使用这种方法。 74 | 75 | 如未勾选 Overwrite Existing Terms复选框,仅加载新翻译的字符串。因此,修改的已翻译字符串不会被加载。如果你希望已存在的翻译也被加载则勾选复选框,这样会覆盖当前已加载的翻译。注意这样在有人通过用户界面手动修改翻译时可能会导致潜在问题。 76 | 77 | 存在前面的复选框是因为我们可以通过进入Settings > Translations > Application Terms > Translated Terms菜单项或在调试菜单中使用Technical Translation快捷选项来编辑指定的翻译。以这种方式添加或修改的翻译不会被覆盖,除在重新加载语言时勾选了Overwrite Existing Terms。 78 | 79 | 同时插件模块可以在i18n_extra子文件夹中带有额外的翻译文件。首先, 下载了i18n子目录中的 .po文件。然后Odoo ORM对基本语言下载文件,然后对变种语言进行下载。按照这个顺序,下载i18n_extra子目录中的.po文件,首先会下载基础语言,然后下载语言变种。最后加载的字符串翻译即为当前使用的。 80 | 81 | ## 配置语言相关设置 82 | 83 | 语言和它们的变种(如`es_MX`为墨西哥西班牙语)也提供本地化设置,如日期和数字格式。 84 | 85 | 它们都有相应的默认值,只要用户使用正确的语言,本地化设置就应该是正确的。 86 | 87 | 但是你可能会想要修改语言的设置。比如,你可能会偏向于在用户界面中使用默认的英语,但又希望修改美式英语的默认日期和数字格式匹配自己的要求。 88 | 89 | ### 准备工作 90 | 91 | 我们需要启用开发者模式。如未启用请参照[第一章 安装Odoo开发环境](1.md)*激活Odoo开发者工具*一节进行激活。 92 | 93 | ### 如何实现... 94 | 95 | 按照如下步骤来修改语言的本地化设置: 96 | 97 | 1. 选择Settings > Translations > Languages菜单项来检查所安装的语言和它们的设置。点击所安装的一种语言会打开带有相应设置的表单: 98 | ![图11.5 – 配置语言设置的表单](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021031314133831.png) 99 | 图11.5 – 配置语言设置的表单 100 | 2. 编辑语言设置。修改日期为ISO格式,将日期格式修改为%Y-%m-%d.。要修改数字格式来使用逗号作为十进制分隔符,修改对应的十进制分隔符和千分位分隔符。 101 | 102 | ### 运行原理... 103 | 104 | 在登录并创建新的Odoo用户会话时,用户首选项中勾选了用户语言并在`lang`上下文键中进行了设置。然后使用它来格式化对应的输出 - 源文本翻译为用户语言,日期和数字以语言的当前本地化设置进行格式化。 105 | 106 | ### 扩展知识... 107 | 108 | 服务端进程可修改动作所运行的上下文。例如,要获取根据美式英语格式设置了日期格式的记录集,不依赖当前用户的语言首选项,可以进行如下操作: 109 | 110 | ``` 111 | en_records = self.with_context(lang='en_US').search([]) 112 | ``` 113 | 114 | 更多详情,请参见[第八章 高级服务端开发技巧](8.md)中的*使用变更的上下文调用方法*一节。 115 | 116 | ## 通过网页客户端在用户界面翻译文本 117 | 118 | 进行翻译最简单的方式是使用由网页客户端提供的翻译功能。这些翻译字符串在数据库中存储,稍后可导出为`.po`文件,要么在插件模块中包含,要么之后进行手动导入。 119 | 120 | 文本字段可带有可翻译内容,也就是说它们的值可以依赖于当前用户的语言。我们还将学习如何在这些字段中设置依赖于语言的值。 121 | 122 | ### 准备工作 123 | 124 | 我们需要启用开发者模式。如未启用请参照[第一章 安装Odoo开发环境](1.md)中*激活Odoo开发者工具*一节进行激活。 125 | 126 | ### 如何实现... 127 | 128 | 我们使用用户组作为示例来演示如何通过网页客户端翻译词汇: 129 | 130 | 1. 导航至想要进行翻译的页面。本例我们通过Settings > Users & Companies > Groups菜单项打开用户组的视图。 131 | 2. 在顶部菜单栏中,点击调试菜单图标并选择Technical Translation选项: 132 | ![图11.6 – 在当前视频中打开翻译选项](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021031314285569.png) 133 | 图11.6 – 在当前视频中打开翻译选项 134 | 3. 会显示针对该视图的可翻译词语的列表。编辑某行的Translation Value来修改(或添加)其翻译文本。如果要查找某个具体的源字符串,可使用所列出的过滤器来精简出所显示的文本: 135 | ![图11.7 – 视图的翻译词语](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021031314350797.png) 136 | 图11.7 – 视图的翻译词语 137 | 组名是可翻译字段。我们来将记录值翻译为所安装的其它语言。 138 | 4. 再次导航至User Groups菜单项,在表单视图中打开其中一条组记录,并点击Edit: 139 | ![图11.8 – 字段值的翻译](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021031314403966.png) 140 | 图11.8 – 字段值的翻译 141 | 5. 注意在Name字段的最右端有一个特殊的图标。这表示它是一个可翻译字段。点击该图标打开带有各个安装语言的翻译列表。这样我们可以对这些语言设置翻译。 142 | 143 | ### 运行原理... 144 | 145 | 在ir.translation模型的数据表中进行存储。调试菜单下的Technical Translation选项包含对这些词汇的快速访问,与当前所选择的视图保持对应。 146 | 147 | 类似地,带有可翻译内容的模型字段可包含一个访问已安装语言列表的图标并为每种语言设置相应的值。 148 | 149 | 此外,可以使用Translations > Application Terms > Translated Terms菜单项来从Settings顶部菜单访问翻译词汇。这里,可以看到所有针对我们实例的可用词汇。应使用数据过滤器来定位想要找到的词汇。 150 | 151 | ### 扩展知识... 152 | 153 | Translated Terms菜单项的旁边可以看到一个Generate Missing Term选项。选择它会显示一个对话窗口,包含所安装的语言,然后会从已安装的插件模块中提取可翻译字符串,将新增的词句添加到Translated Terms表格中。与本章*将翻译字符串导出到文件*一节异曲同工。 154 | 155 | 在修改一些模型或视图后这会非常实用。这么做会添加新字符串,这样我们就可以对它们进行翻译。 156 | 157 | 它也可用于从默认en_US语言中获取字符串。然后我们可以利用翻译词句来替换原始英语文本为更适合终端用户具体业务的词汇。 158 | 159 | > 📝重要:在主网站语言外的语言中编辑QWeb视图时,会发现只能修改字符串。这是因为在其它语言中,实际上只能通过Odoo的国际化机制向节点的文本内容添加翻译。 160 | 161 | ## 将翻译字符串导出到文件 162 | 163 | 可导出带有或不带有翻译文本所选语言。可在模块中包含i18n数据,或者稍后通过文本编辑器或是专业工具来执行翻译。 164 | 165 | 我们将演示如何使用标准mail模块来进行导出,读者可以修改为自己想要进行导出的模块。 166 | 167 | ### 准备工作 168 | 169 | 我们需要启用开发者模式。如未启用请参照[第一章 安装Odoo开发环境](1.md)中*激活Odoo开发者工具*一节进行激活。 170 | 171 | ### 如何实现... 172 | 173 | 按照如下步骤来导出mail插件模块的翻译词句: 174 | 175 | 1. 在网页客户端用户界面的Settings顶级菜单中选择Translations > Import/Export > Export Translation菜单项。 176 | 177 | 2. 在Export Translations对话框中,选择所要导出翻译的语言、文件格式以及要导出的模块。要导出翻译模板文件,从Language下拉列表中选择New Language (Empty translation template)。推荐使用 .po格式并且一次只导出一个插件模块 - 本例中为Discuss模块(mail是Discuss应用的技术名称): 178 | ![图11.9 – 导出翻译词句对话框](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021042514035815.png)图11.9 – 导出翻译词句对话框 179 | 180 | 3. 完成导出后,会显示一个新的窗口,包含一个下载文件的链接以及一些建议。 181 | 182 | 4. 通过Odoo命令行界面导出mail插件模块的翻译模板文件,键入如下命令: 183 | 184 | ``` 185 | $ ./odoo-bin -d mydb --i18n-export=mail.po --modules=mail 186 | $ mv mail.po ./addons/mail/i18n/mail.pot 187 | ``` 188 | 189 | 5. 若要通过Odoo命令行界面导出某种语言的翻译模板文件 - 本例中为针对西语的es_ES,键入如下命令: 190 | 191 | ``` 192 | $ ./odoo-bin -d mydb --i18n-export=es_ES.po --modules=mail --language=es_ES 193 | $ mv es_ES.po ./addons/mail/i18n 194 | ``` 195 | 196 | ### 运行原理... 197 | 198 | Export Translation功能完成两件事:从目标模块导出可翻译字符串,在ir.translation模型中添加新字符串,然后通过翻译词句创建一个文件。可通过网页客户端和命令行界面来进行实现。 199 | 200 | 在通过网页导出时,我们可以选择导出空翻译模板,即带有待翻译字符串和空白翻译的文件,或选择导出一种语言,文件中包含待翻译字符串,及所选语言的翻译内容。 201 | 202 | 可以选择的文件格式有CSV, PO和TGZ。TGZ文件格式导出一个包含PO或POT文件的/i18n/目录结构的压缩文件。 203 | 204 | CSV格式可用于通过Excel执行翻译,但在插件模块中所使用的格式是PO文件。它们应放在i18n子目录中。然后在相应的语言安装时自动加载。在导出这些PO文件时,我们应一次仅导出一个模块。PO文件也是一些翻译工具如Poedit所支持的流行格式。 205 | 206 | 翻译还可以直接通过命令行使用--i18nexport选项进行导出。本节展示如何导出模板文件和已翻译语言文件。 207 | 208 | 本节的第4步中,我们导出了一个模板文件。--i18n-export 选项需要传入导出路径和文件名。记住文件的扩展名要求为CSV, PO或TGZ。该选项需要一个-d参数,用于指定所使用的数据库。还需要--modules选项来指定要导出的插件模块。注意这里无需使用--stop-after-init选项,因为导出命令在完成后自动返回命令行。 209 | 210 | 这会导出一个模板文件。Odoo模块导出的模板应放在i18n文件夹中,扩展名为 .pot。在对模块完成导出操作后,我们通常会将导出的PO文件移到模块的i18n目录中,使用名称.pot。 211 | 212 | 在第5步中,还使用到了--language选项。通过它,导出的不是空翻译文件,而是所选语言的已翻译词句。一种情况是使用Technical Translation功能来通过网页客户端用户界面来执行一些翻译,然后将它们一并导出至模块中。 213 | 214 | ### 扩展知识... 215 | 216 | 视图和模型定义中的文本字符串会自动提取供翻译。对于模型,会提取_description属性、字段名(string属性)、帮助文本和选择字段选项以及模型约束(_constraints和_sql_constraints)的用户文本。 217 | 218 | Python或JavaScript代码中需翻译的文本字符串不会进行自动检查,因此代码应标识出这些字符串,通过下划线函数内封装这些字符串。 219 | 220 | 在模块的Python文件中,应确保使用如下语句进行导入: 221 | 222 | ``` 223 | from odoo import _ 224 | ``` 225 | 226 | 然后它在用到要翻译的文本时可以这样使用: 227 | 228 | ``` 229 | _('Hello World') 230 | ``` 231 | 232 | 对于那些要使用上下文信息的字符串,我们应使用Python的字符串内插,如下所示: 233 | 234 | ``` 235 | _('Hello %s') % 'World' 236 | ``` 237 | 238 | 注意插值应放在翻译函数的外面。例如,_("Hello %s" % 'World')是错误的。字符串插值最好应为字符串拼接,这样每个界面文本也仅是一个翻译字符串。 239 | 240 | 对Selection字段要保持警惕!如果对字段定义传递了明确的值列表,显示的字符串会自动标记为翻译。而如果传递一个返回值列表的方法,显示字符串必须显式地标记供翻译。 241 | 242 | 对于手动翻译工作,可以使用任何文本编辑器,但使用特别支持PO文件语法的编辑器会减少格式上的错误,让任务变得轻松。这类编辑器如下所示: 243 | 244 | - [POEDIT](https://poedit.net/) 245 | - [Emacs (PO模式)](https://www.gnu.org/software/gettext/manual/html_node/PO-Mode.html): 246 | - [Lokalize](http://i18n.kde.org/tools) 247 | - [Gtranslator](https://wiki.gnome.org/Apps/Gtranslator): 248 | 249 | ## 使用gettext工具来简化翻译 250 | 251 | PO文件格式是gettext国际化和本地化系统的一部分,它在类Unix系统中经常使用。该系统包含简化翻译任务的一些工具。 252 | 253 | 本节演示如何使用这些工具来辅助翻译插件模块。我们要对自定义模块使用它,所以[第三章 创建Odoo插件模块](3.md)中创建的my_library是一个不错的对象。但是,请随意替换为你自己手边的其它自定义模块,将本节中的my_library替换为相应模块即可。 254 | 255 | ### 如何实现... 256 | 257 | 要通过命令行来管理翻译,假定Odoo的安装位置为~/odoo-work/odoo,执行如下步骤: 258 | 259 | 1. 创建一个目标语言如西语翻译词语的汇总。如果我们的汇总文件名为odoo_es.po,应编写如下代码: 260 | 261 | ``` 262 | $ cd ~/odoo-work/odoo # 使用你Odoo安装的路径 263 | $ find ./ -name es_ES.po | xargs msgcat --use-first | msgattrib -- translated --no-fuzzy -o ./odoo_es.po 264 | ``` 265 | 266 | 2. 通过Odoo命令行界面导出该插件模块的翻译模板文件并将其放在模块中相应位置: 267 | 268 | ``` 269 | $ ./odoo-bin -d mydb --i18n-export=my_module.po --modules=my_module 270 | $ mv my_module.po ./addons/my_module/i18n/my_module.pot 271 | ``` 272 | 273 | 3. 如果针对目标语言还没有翻译文件,创建一个PO翻译文件,复用我们在汇总中已发现和翻译的词语: 274 | 275 | ``` 276 | $ msgmerge --compendium ./odoo_es.po -o ./addons/my_module/i18n/es_ES.po \ 277 | /dev/null ./addons/my_module/i18n/my_module.pot 278 | ``` 279 | 280 | 4. 如果翻译文件存在,在汇总中添加翻译: 281 | 282 | ``` 283 | $ mv ./addons/my_module/i18n/es_ES.po /tmp/my_module_es_old.po 284 | $ msgmerge --compendium ./odoo_es.po -o ./addons/my_module/i18n/es_ES.po \ 285 | /tmp/my_module_es_old.po ./addons/my_module/i18n/my_module.pot 286 | $ rm /tmp/my_module_es_old.po 287 | ``` 288 | 289 | 5. 使用如下命令查看PO文件中未翻译的词语: 290 | 291 | ``` 292 | $ msgattrib --untranslated ./addons/my_module/i18n/es_ES.po 293 | ``` 294 | 295 | 6. 使用你喜欢的编辑器完成翻译。 296 | 297 | ### 运行原理... 298 | 299 | 第1步使用gettext工具中的命令来创建所选择语言的翻译汇总(Compendium) - 本例中为西语。通过在Odoo代码中查找所有的es_ES.po文件并将其传递给msgcat命令来进行实现。我们使用 --use-first标记来避免翻译的冲突(在Odoo代码中已存在一些翻译)。结果会传递给msgattrib过滤器。我们使用--translated选项来过滤掉未翻译的词条,使用--no-fuzzy选项来移除模糊翻译。然后我们将结果保存到odoo_es.po中。 300 | 301 | 前一部分的第2步使用--i18n-export选项调用odoo.py。我们需要对命令行指定数据库,虽然在配置文件中已指定过数据库,还有--modules选项,接一个要导出翻译的以逗号分隔的模块列表。 302 | 303 | 在gettext的世界中,模糊翻译是对源字符串通过msgmerge命令(或其它工具)使用邻近匹配所自动创建的。我们需要在汇总中避免使用。 304 | 305 | 第3步通过在汇总中查找到的已有翻译值新建一个翻译文件。msgmerge命令配合--compendium使用来在汇总文件中查找msgid,匹配那些在第2步中生成的翻译模板文件中的内容。结果保存在es_ES.po 文件中。 306 | 307 | 如果插件已有包含希望持久化翻译的.po文件,应对其重命名并将 /dev/null参数替换为该文件。需要有重命名这一步,以避免在输入和输出中使用相同的文件。 308 | 309 | ### 扩展知识... 310 | 311 | 本小节只涉及到GNU gettext工具集中丰富工具的冰山一角。完整的讲解不是本书的范畴。如果读者兴趣,GNU gettext的文档包含大量的有关对PO操作的信息,参见http://www.gnu.org/software/gettext/manual/gettext.html。 312 | 313 | ## 将翻译文件导入到Odoo中 314 | 315 | 加载翻译的常用做法是把PO文件放在模块的i18n子目录内。在插件模块安装或升级时,会加载翻译文件并添加新翻译的字符串。 316 | 317 | 但是,有些情况下我们希望直接导入翻译文件。本节中我们会学习如何加载翻译文件,既可以通过网页客户端也可以通过命令行。 318 | 319 | ### 准备工作 320 | 321 | 我们需要启用开发者模式。如未启用请参照[第一章 安装Odoo开发环境](1.md)中*激活Odoo开发者工具*一节进行激活。我们还会需要一个po翻译文件,例如myfile.po文件,在本节中我们将进行导入操作。 322 | 323 | ### 如何实现... 324 | 325 | 按照如下步骤来导入翻译词句: 326 | 327 | 1. 在网页客户端用户界面中,通过Settings顶部菜单,选择Translations > Import/Export > Import Translation菜单项。 328 | 329 | 2. 在Import Translations对话框中,填写语言名称和代码,并选择要导入的文件。最后,点击Import按钮来执行导入: 330 | ![图11.10 – 导入翻译文件对话框](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021042514530869.png)图11.10 – 导入翻译文件对话框 331 | 332 | 3. 要通过Odoo命令行界面导入翻译文件,我们必须将它放至服务端的插件路径中,然后执行导入: 333 | 334 | ``` 335 | $ mv myfile.po ./addons/ 336 | $ ./odoo.py -d mydb --i18n-import="myfile.po" --lang=es_ES 337 | ``` 338 | 339 | ### 运行原理... 340 | 341 | Import Translation接受一个PO或CSV文件,并将翻译字符串导入到ir.translation数据表中。 342 | 343 | 网页客户端功能中要求填写语言名,但它并没有在导入过程中使用到。还有一个重写选项。如果勾选,会强制导入所有的翻译字符串,即便这些字符串已经存在,在这个过程中会进行覆盖。 344 | 345 | 在命令行中,导入可通过--i18n-import选项来实现。必须提供该文件相对插件路径目录的路径,-d和--language (或-l)也是必传的。还可以通过在命令行中添加--i18n-overwrite 选项来实现重写。注意我们在这里没有使用--stop-after-init选项。不需要使用,因为导入动作在完成时会停止服务。 346 | 347 | ## 对网站修改自定义URL语言代码 348 | 349 | Odoo对网站应用也支持多语言。在网中,当前语言标记为语言字符串。本节中,我们学习如何在URL中修改语言代码。 350 | 351 | ### 准备工作 352 | 353 | 在学习本节前,请确保安装了website模块并且对网站启用了多语言。 354 | 355 | ### 如何实现... 356 | 357 | 按照如下步骤来修改语言的URL代码: 358 | 359 | 1. 通过Settings > Translations > Languages菜单项打开语言列表。点击一个已安装语言会打开如下的表单: 360 | ![图11.11 – 网站语言URL代码](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021042515035216.png) 361 | 图11.11 – 网站语言URL代码 362 | 2. 这里可以看到URL Code字段。设置为所希望的值。不要添加空格或特殊字符。 363 | 364 | 配置完后,就可以对网站测试看结果了。打开首页并修改语言,就会在URL中看到自定义的语言代码。 365 | 366 | ### 运行原理... 367 | 368 | Odoo通过URL路径来识别网站的语言。例如[www.odoo.com/fr_FR](http://www.odoo.com/fr_FR)用于法语,[www.odoo.com/es_ES](http://www.odoo.com/es_ES)用于西班牙语。这里URL中的fr_FR和es_ES部分是语言的ISO代码,在Odoo中用于监测请求的语言。但有时我们希望将语言设置为对用户更友好的方式。这时就可以更新URL Code字段。一旦做了修改,Odoo网站会使用URL Code的值来识别语言。例如,可以将法语的URL Code设置为 fr,那么[www.odoo.com/fr_FR](http://www.odoo.com/fr_FR)就会转化为[www.odoo.com/fr](http://www.odoo.com/fr)。 369 | 370 | > 📝注:在生产环境中修改URL code不会有问题,Odoo会将ISO语言代码的URL跳转至自定义URL。 -------------------------------------------------------------------------------- /13.md: -------------------------------------------------------------------------------- 1 | # 第十三章 Web服务端开发 2 | 3 | 全书完整目录请见:[Odoo 14开发者指南(Cookbook)第四版](README.md) 4 | 5 | 我们将在本章中介绍Odoo网页服务端部分的基础知识。注意本章中所讲解的为基础部分,有关更高阶的功能,请参见[第十四章 CMS网站开发](14.md)。 6 | 7 | 所有的Odoo网页请求都是由Python库[werkzeug](http://werkzeug.pocoo.org)来进行处理的。虽然werkzeug的复杂部分多隐藏在Odoo便捷的封装器中,学习其底层的运行机制也会非常的有帮助。 8 | 9 | 本章中,我们将讲解如下内容: 10 | 11 | - 让路径在网络中可访问 12 | - 限制线上路径的访问 13 | - 使用传递给handler的参数 14 | - 修改已有handler 15 | - 提供对静态资源的访问 16 | 17 | ## 技术准备 18 | 19 | 学习本章要求安装有在线Odoo平台。 20 | 21 | 本章中使用的所有代码可通过GitHub仓库进行下载:https://github.com/alanhou/odoo14-cookbook/tree/main/Chapter13。 22 | 23 | ## 让路径在网络中可访问 24 | 25 | 本节中我们学习如何让http://yourserver/path1/path2这样的URL可由用户访问。这既有可能是一个网页,也有可能是返回供其它程序使用数据的路径。后一种情况中,我们通常会使用JSON格式来接收参数并提供数据。 26 | 27 | ### 准备工作 28 | 29 | 我们将使用library.book 模型,可参见[第四章 应用模型](4.md),因此如果你还没有按该章进行操作,请通过[GitHub仓库](https://github.com/alanhou/odoo14-cookbook/tree/main/Chapter13/00_initial_module)获取相关代码以便能按照本章示例进行操作。 30 | 31 | 我们希望允许任何用户查询完整的图书列表。此外,我们希望通过JSON请求对程序提供同样的信息。 32 | 33 | ### 如何实现... 34 | 35 | 我们需要添加控制器,按惯例放在一个名为controllers的文件夹中: 36 | 37 | 1. 添加带有我们页面HTML内容的controllers/main.py文件,如下: 38 | 39 | ``` 40 | from odoo import http 41 | from odoo.http import request 42 | class Main(http.Controller): 43 | @http.route('/my_library/books', type='http', auth='none') 44 | def books(self): 45 | books = request.env['library.book'].sudo().search([]) 46 | html_result = '
    ' 47 | for book in books: 48 | html_result += "
  • %s
  • " % book.name 49 | html_result += '
' 50 | return html_result 51 | ``` 52 | 53 | 2. 添加一个函数来以JSON格式提供相同的信息,如下例所示: 54 | 55 | ``` 56 | @http.route('/my_library/books/json', type='json', auth='none') 57 | def books_json(self): 58 | records = request.env['library.book'].sudo().search([]) 59 | return records.read(['name']) 60 | ``` 61 | 62 | 3. 添加controllers/__init__.py文件,如下: 63 | 64 | ``` 65 | from . import main 66 | ``` 67 | 68 | 4. 在my_library/__init__.py文件中导入controllers,如下: 69 | 70 | ``` 71 | from . import controllers 72 | ``` 73 | 74 | 在重启服务之后,可以在浏览器中访问/my_library/books并获得书名的列表。要进行JSON-RPC的测试,需要构造一个JSON请求。简单的实现方式是通过使用如下命令在命令行中接收输出: 75 | 76 | ``` 77 | curl -i -X POST -H "Content-Type: application/json" -d "{}" localhost:8069/my_library/books/json 78 | ``` 79 | 80 | 如果此时得到404报错,可能是在实例中有不止一个数据库。这种情况下,Odoo无法决定使用哪个数据库对请求提供服务。 81 | 82 | 使用--db-filter='^yourdatabasename$'参数来强制Odoo使用安装模块所在的具体数据库。现在该路径应该就可以访问了。 83 | 84 | ### 运行原理... 85 | 86 | 这里的两个关键部分是我们的控制器通过odoo.http.Controller获取,并且用于提供内容服务的方法由odoo.http.route进行装饰。继承 odoo.http.Controller以通过Odoo路由系统注册该控制器,与继承odoo.models.Model注册模型的方式相似。同时Controller有一个处理这一注册的元类。 87 | 88 | 通常由插件所处理的路径以插件名开头,以避免名称的冲突。当然,如果你继承一些插件功能,会使用这个插件名。 89 | 90 | #### odoo.http.route 91 | 92 | route装饰器让我们首先告诉Odoo某一方法可通过web访问,第一个参数决定可以访问哪个路径。除了可传递字符串,也可以传递字符串列表,这样相同的函数可以为多个路径提供服务。 93 | 94 | type参数默认为http,决定所提供服务对应的请求类型。严格意义上说,JSON是HTTP,声明第二个函数为type='json'会让事情变得很轻松,因为接下来Odoo会替我们处理类型转换。 95 | 96 | 现在先不用担心auth参数,会在本章的*限制网络可访问路径的访问*一节中进行讲解。 97 | 98 | #### 返回值 99 | 100 | Odoo中函数的返回值可由route装饰器的type参数来决定。对于type='http',我们通常希望传送一些HTML,因此第一个函数只是返回包含 HTML 的字符串。一种代替方案是使用request.make_response(),可控制在响应中发送的headers。因此要表明页面最后更新的时间,可以在books()中的修改最后一行为如下代码: 101 | 102 | ``` 103 | return request.make_response( 104 | html_result, headers=[ 105 | ('Last-modified', email.utils.formatdate( 106 | ( 107 | fields.Datetime.from_string( 108 | request.env['library.book'].sudo() 109 | .search([], order='write_date desc', limit=1) 110 | .write_date) - 111 | datetime.datetime(1970, 1, 1) 112 | ).total_seconds(), 113 | usegmt=True)), 114 | ]) 115 | ``` 116 | 117 | 代码发送一个Last-modified头及所生成的 HTML,告诉浏览器列表最后一次修改的时间。我们可以从library.book 模型的write_date字段中提取这一信息。 118 | 119 | 为让前面代码段可以运行,我们需要在文件的顶部添加一些导入语句,如下: 120 | 121 | ``` 122 | import email 123 | import datetime 124 | from odoo import fields 125 | ``` 126 | 127 | 也可以手动创建一个werkzeug的Response对象并返回,但费这番功夫收获甚微。 128 | 129 | > 📝**重要信息**:手动生成HTML对于演示非常好,但在生产环境的代码中则不应这么做。保持使用模板,如我们在[第十五章 网页客户端开发](15.md)中的*创建或更改模板 - QWeb*一节中所演示的,并通过调用request.render()返回。这样我们可以从容地进行本地化并让代码通过将展示层与业务逻辑分离而更优雅。同时,模板为我们提供函数在输出 HTML 之前转义数据。前面的代码会容易遭受跨站脚本攻击(比如用户可能会把脚本标签放到书名中)。 130 | 131 | 对于JSON请求,只需返回希望交给客户端的数据结构,Odoo会做序列化。这时,应限定所返回的数据类型可进行JSON序列化,通常意味着要使用字典、列表、字符串、浮点型和整型。 132 | 133 | #### odoo.http.request 134 | 135 | **request**对象是引用当前处理请求的静态对像,包含所有执行需要的内容。这里最重要的是request.env属性,包含一个与模型中self.env相同的Environment对象。环境与当前用户绑定,在前例中并不存在,因为我们使用了auth='none'。缺少用户也是我们使用sudo()来在示例代码中调用模型方法的原因。 136 | 137 | 如果习惯于web开发,则会倾向进行会话处理,这是绝对正确的。使用OpenERPSession对象(是对werkzeug的Session对象的轻微封装)的request.session,以及用request.session.sid来访问会话ID。存储会话值,只需将request.session作为字典处理,如以下示例代码所示: 138 | 139 | ``` 140 | request.session['hello'] = 'world' 141 | request.session.get('hello') 142 | ``` 143 | 144 | > 📝**重要信息**:注意在会话中存储数据与使用全局变量是相同的。仅在必要时才使用它。通常对于多请求动作是需要的,如website_sale模块中的结账。 145 | 146 | ### 扩展知识... 147 | 148 | route装饰器可带有其它的参数来进一步自定义其行为。默认允许所有的HTTP方法,并且Odoo将所有传递的参数组装在一起。使用methods参数,我们可以传递一个可接受的方法列表,通常是['GET'] 或['POST']。 149 | 150 | 要允许跨域请求的话(出于安全和隐私考虑,浏览器阻止对脚本所加载域名以外域名的AJAX和其它类型的请求),可设置cors参数为 * 来允许来自所有域名的请求,或设置一个URI来限定请求为来自该URI的请求。如果未设置这个参数,这也是默认情况,即未设置Access-Control-Allow-Origin头,采取浏览器的默认行为。在本例中,我们可能会希望在/my_module/books/json中进行设置,来允许从其它网站拉取的脚本访问图书列表。 151 | 152 | 默认,Odoo通过对每个请求传递token来保护一些类型的请求免受称为跨站请求伪造(CSRF)的攻击。如果想要关闭,设置csrf参数为False,但应注意这通常不是一个好的想法。 153 | 154 | ### 其它内容 155 | 156 | 参见以下各点来了解有关HTTP路由的更多知识: 157 | 158 | - 如果在同一个实例中托管多个Odoo数据库,那么不同的数据库可能运行在不同的域名中。这时我们可以使用--db-filter选项或使用https://github.com/OCA/server-tools的dbfilter_from_header模块,它有助于按域名过滤数据库。在写本书时这个模块还没有迁移到版本14,但在本书出版时应该已经迁移了。 159 | - 要学习如何使用模板来实现模块化,请参见本章中的*修改已有handler*一节。 160 | 161 | ## 限制线上路径的访问 162 | 163 | 我们将在本节中探讨Odoo为路由所提供的三种验证机制。并使用不同的验证机制来定义路由,展示它们之间的不同之处。 164 | 165 | ### 准备工作 166 | 167 | 我们将对前一节的代码继续进行扩展,还会依赖[第四章 应用模型](4.md)中的library.book模型,所以请准备好相关代码再继续下面的学习。 168 | 169 | ### 如何实现... 170 | 171 | 在controllers/main.py中定义handler: 172 | 173 | 1. 添加显示所有图书的路径,如下例所示: 174 | 175 | ``` 176 | @http.route('/my_library/all-books', type='http', auth='none') 177 | def all_books(self): 178 | books = request.env['library.book'].sudo().search([]) 179 | html_result = '
    ' 180 | for book in books: 181 | html_result += "
  • %s
  • " % book.name 182 | html_result += '
' 183 | return html_result 184 | ``` 185 | 186 | 2. 添加一个显示所有图书的路径并表明哪个是由当前用户所著的。参见如下示例代码: 187 | 188 | ``` 189 | @http.route('/my_library/all-books/mark-mine', type='http', auth='public') 190 | def all_books_mark_mine(self): 191 | books = request.env['library.book'].sudo().search([]) 192 | html_result = '
    ' 193 | for book in books: 194 | if request.env.user.partner_id.id in 195 | book.author_ids.ids: 196 | html_result += "
  • %s
  • " % 197 | book.name 198 | else: 199 | html_result += "
  • %s
  • " % book.name 200 | html_result += '
' 201 | return html_result 202 | ``` 203 | 204 | 3. 添加显示当前用户图书的路径,如下: 205 | 206 | ``` 207 | @http.route('/my_library/all-books/mine', type='http', auth='user') 208 | def all_books_mine(self): 209 | books = request.env['library.book'].search([ 210 | ('author_ids', 'in', request.env.user.partner_id.ids), 211 | ]) 212 | html_result = '
    ' 213 | for book in books: 214 | html_result += "
  • %s
  • " % book.name 215 | html_result += '
' 216 | return html_result 217 | ``` 218 | 219 | 通过这段代码,/my_library/all-books和/my_library/allbooks/mark-mine路径对未验证用户所显示内容相同,但登录用户会在后一个路径中看到自己的书以粗体显示。对未验证用户/my_library/allbooks/mine路径完全不可访问。如果未验证依然访问该路径,会被重定向到登录页面进行登录。 220 | 221 | ### 运行原理... 222 | 223 | 验证方法之间的不同基本上通过request.env.user的内容可判断到。 224 | 225 | 对于auth='none'哪怕是已验证用户在访问路径时用户记录也是空的。使用这一个验证的场景是所响应的内容对用户不存在依赖,或者是在服务端模块中提供与数据库无关的功能。 226 | 227 | auth='public'的值将未验证用户设置为一个带有XML ID base.public_user的特殊用户,已验证用户设置为用户自己的记录。对于所提供的功能同时针对未验证和已验证用户而已验证用户又具有一些额外的功能时应选择它,前面的代码中已经演示。 228 | 229 | 使用auth='user'来确保仅已验证用户才能访问所提供的内容。通过这个方法,我们可以确保request.env.user指向已有用户。 230 | 231 | ### 扩展知识... 232 | 233 | 验证方法的逻辑位于base插件的 ir.http模型中。不论在路由的auth参数中传递什么值,Odoo搜索该模型中名为**_auth_method_**的函数,这样可以通过继承它并声明处理所选验证方法来进行自定义。 234 | 235 | 作为示例,我们将提供一个名为base_group_user的验证方法,仅针对属于base.group_user组的当前登录用户,如下例所示: 236 | 237 | ``` 238 | from odoo import exceptions, http, models 239 | from odoo.http import request 240 | class IrHttp(models.Model): 241 | _inherit = 'ir.http' 242 | def _auth_method_base_group_user(self): 243 | self._auth_method_user() 244 | if not request.env.user.has_group('base.group_user'): 245 | raise exceptions.AccessDenied() 246 | ``` 247 | 248 | 现在可以在装饰器中使用auth='base_group_user',并确保运行这个路由handler的用户是该组的成员。使用一点技巧,我们还可以将其扩展为auth='groups(xmlid1,...)',这一实现留作读者练习,可参见GitHub仓库中的示例代码Chapter13/r2_paths_auth/my_library/models/sample_auth_http.py。 249 | 250 | ## 使用传递给handler的参数 251 | 252 | 能够显示内容自然很棒,但能够根据用户输入显示内容则更佳。本节将演示接收输入和做出响应的不同方式。如同前一小节,我们将使用library.book模型。 253 | 254 | ### 如何实现... 255 | 256 | 首先,我们将添加一个接收传统参数图书 ID来显示其详情的路由。然后,我们使用将参数嵌入路径内的方式来实现同样的功能: 257 | 258 | 1. 添加一个接收图书ID参数的路径,如下例所示: 259 | 260 | ``` 261 | @http.route('/my_library/book_details', type='http', auth='none') 262 | def book_details(self, book_id): 263 | record = request.env['library.book'].sudo().browse(int(book_id)) 264 | return u'

%s

Authors: %s' % ( 265 | record.name, 266 | u', '.join(record.author_ids.mapped('name')) or 'none', 267 | ) 268 | ``` 269 | 270 | 2. 添加一个我们可以传递图书ID的路径,如下: 271 | 272 | ``` 273 | @http.route("/my_library/book_details/", type='http', auth='public') 274 | def book_details_in_path(self, book): 275 | return self.book_details(book.id) 276 | ``` 277 | 278 | 如果在浏览器中访问 /my_library/book_details?book_id=1,应该会看到ID为1的图书的详情页。如不存在,会收到一个报错页面。 279 | 280 | > 报错内容: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. 281 | 282 | 第二个handler允许我们访问/my_library/book_details/1并浏览到相同的内容。 283 | 284 | > **译者注:** 请注意原书中为auth='none',这会出现psycopg2.ProgrammingError: can't adapt type 'RequestUID'报错,因为这里我们用到了模型数据,同时也请检查代码是否为最新的稳定版。 285 | 286 | ### 运行原理... 287 | 288 | 默认,Odoo(实际上是werkzeug)合并了GET和POST参数并将它们通过关键词参数传递给handler。因此,仅需声明接收参数book_id的函数,我们以GET(URL中的参数)或POST(通过以是action属性指定handler的
元素传递)来引入该参数 。如果没有对该参数添加默认值,运行时会在未设置参数就进行访问时抛出错误。 289 | 290 | 第二个示例利用了werkzeug环境中大多数是虚拟路径的这一点。因此我们可以定义路径包含一些输入内容。在本例中,我们指定了library.book的ID为路径的最后一个组成部分。冒号后为关键词参数的名称。我们的函数会以关键词传递参数并调用。这里,Odoo处理对这一ID的调用并分发一条浏览记录,当然要在访问路径的用户具有相应权限时才能使用。假定book是一条浏览记录,我们可以传递book.id 来作为book_id参数重复利用第一个示例的函数,给出相同的内容。 291 | 292 | ### 扩展知识... 293 | 294 | 在路径中定义参数是werkzeug所带的一个功能,称为转换器(converter)。模型转换器由Odoo添加,它还定义接收逗号分隔ID列表的转换器模型,并传递包含这些 ID 的记录集给我们的handler。 295 | 296 | 转换器的优美之处在于runtime强制参数为所需类型,而使用普通关键字参数则需要自己处理。这些以字符串进行分发,而你需要像第一个示例一样自己处理必要的类型转换。 297 | 298 | 内置的werkzeug转换器包含 int, float和string,以及更为复杂的path, any或uuid等类型。可以在https://werkzeug.palletsprojects.com/en/1.0.x/中查看它们的语法。 299 | 300 | ### 其它内容 301 | 302 | 如果希望学习更多的HTTP路径,参见如下各点: 303 | 304 | - Odoo的自定义转换器在base模块中的ir_http.py中定义并在ir.http的_get_converters类方法中注册。作为练习,读者可以创建自己的转换器,它允许你访问/my_library/book_details/Odoo+cookbook页面来接收图书的详情(如果之前在library中进行了添加的话)。 305 | - 如果想要学习更多在该路径上的表单提交的话,请参见[第十四章 CMS网站开发](14.md)中的*从网站用户获取输入*一节。 306 | 307 | ## 修改已有handler 308 | 309 | 在安装website模块时,/website/info path显示有关Odoo实例的一些信息。本节中,我们将重载它来修改信息页面的布局,并修改其显示的内容。 310 | 311 | ### 准备工作 312 | 313 | 安装website模块并查看/website/info路径。本节中,我们更新/website/info路由来提供更多的信息。 314 | 315 | ### 如何实现... 316 | 317 | 我们需要调整已有模板并重载原有的handler。可以像这样实现: 318 | 319 | 1. 在views/templates.xml文件中重载qweb模板,如下: 320 | 321 | ``` 322 | 323 | 324 | 338 | 339 | ``` 340 | 341 | 2. 在controllers/main.py文件中重载handler,如下例所示: 342 | 343 | ``` 344 | from odoo import http 345 | from odoo.addons.website.controllers.main import Website 346 | 347 | class WebsiteInfo(Website): 348 | @http.route() 349 | def website_info(self): 350 | result = super(WebsiteInfo, self).website_info() 351 | result.qcontext['apps'] = 352 | result.qcontext['apps'].filtered( 353 | lambda x: x.name != 'website' 354 | ) 355 | return result 356 | ``` 357 | 358 | 此时在访问info页时,我们将只能看到过滤后表中已安装应用的列表,与原始定义列表形成对照。 359 | 360 | 安装前效果: 361 | 362 | ![img](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021052102290968.png) 363 | 364 | 安装后的效果: 365 | 366 | ![img](https://i.cdnl.ink/homepage/wp-content/uploads/2021/03/2021052102301012.png) 367 | 368 | ### 运行原理... 369 | 370 | 第一步中,我们重载了已有QWeb模板。要找出是哪一个,我们需要查看原始handler的代码。通常,会找到有类似下面这行的内容,告诉我们需要重载template.name: 371 | 372 | ``` 373 | return request.render('template.name', values) 374 | ``` 375 | 376 | 本例中,handler使用了website_info模块,但随即由另一个模板website.show_website_info进行扩展,因而重载它会非常方便。这里,我们将显示已安装应用的定义列表替换为表格。有关QWeb继承原理的更多详情,请参见[第十五章 网页客户端开发](15.md)。 377 | 378 | 为重载handler方法,我们必须识别出定义handler的类,本例中即为odoo.addons.website.controllers.main.Website。我们需要导入该类来对其继承。现在,我们可以重载方法并修改传递给响应的数据。注意重载的handler在这里出于简洁考虑返回的是一个Response对象而不是像前一小节中那样的HTML字符串。这个对象包含一个对待使用模板的引用,以及模板可访问的值,但它仅在请求的最终运行。 379 | 380 | 通常,有三种方式可修改已有handler: 381 | 382 | - 如果使用QWeb模板,最简单的修改方式是重载该模板。对于布局修改和小的逻辑变动这种方法更适合。 383 | - QWeb获取一个所传递的上下文,在响应中以qcontext成员存在。通常是一个可以添加或删除值来满足需要的字典。在前例中,我们过滤了应用列表来仅针对website。 384 | - 如果handler接收到参数,也可以进行预处理,来让所重载的handler按照所希望的方式运行。 385 | 386 | ### 扩展知识... 387 | 388 | 如前一部分中所学,控制器的继承与模型继承稍有不同,实际上需要一个base类引用并对其使用Python继承。 389 | 390 | 不要忘记通过@http.route装饰器来装饰新的handler;Odoo使用它作为标记,标记哪些方法在网络层中暴露。如果你省去了该装饰器,实际上会让handler的路径不可访问。 391 | 392 | @http.route装饰器本身与字段声明的行为相似,未设置的值会从所重载函数的装饰器中获取,这样对不希望修改的值就无需重复指定。 393 | 394 | 在从所重载的函数中接收到response对象后,不仅仅是能修改QWeb上下文: 395 | 396 | - 我们可以通过操作response.headers来添加或删除HTTP头部 397 | - 如果希望渲染完全不同的模板,可以覆盖response.template 398 | - 要查看response是否是基于QWeb,使用response.is_qweb进行查询 399 | - 通过调用esponse.render()可获取结果HTML代码 400 | 401 | ### 其它内容 402 | 403 | - 有关QWeb的详细内容会在[第十五章 网页客户端开发](15.md)中进行讲解 404 | 405 | ## 提供对静态资源的访问 406 | 407 | 网页中包含多种类型的静态资源,如图片、视频、CSS等。本节中,我们学习如何管理模块的静态资源。 408 | 409 | ### 准备工作 410 | 411 | 本节中,我们会在页面中显示图片。所以请准备好一张图片。同时需要用到前面小节中的**my_library**模块。 412 | 413 | ### 如何实现... 414 | 415 | 按照如下步骤来在页面中显示图片: 416 | 417 | 1. 在**/my_library/static/src/img**目录中添加图片。 418 | 419 | 2. 在 420 | 421 | controller 422 | 423 | 中定义一个新路由,将图片URL替换为你自己的: 424 | 425 | ``` 426 | @http.route('/demo_page', type='http', auth='none') 427 | def demo_page(self): 428 | image_url = '/my_library/static/src/image/bug.jpg' 429 | html_result = """ 430 | 431 | 432 | 433 | """ % image_url 434 | return html_result 435 | ``` 436 | 437 | 重启服务、更新模块以应用修改。现在访问**/demo_page**查看页面中的图片。 438 | 439 | ### 运行原理... 440 | 441 | **/static**文件夹下放的文件都视作静态资源,可以对外访问。本例中,我们将图片放到了**/static/src/img**目录下。可以将静态资源放到static目录下的任意位置,但有一个基于文件类型的推荐目录结构: 442 | 443 | - **/static/src/img** 为图片目录。 444 | - **/static/src/css** 为CSS文件目录。 445 | - **/static/src/scss** 为SCSS文件目录。 446 | - **/static/src/fonts** 为字体文件目录。 447 | - **/static/src/js** 为JavaScript文件目录。 448 | - **/static/src/xml** 为客户端QWeb模板所用的XML文件目录。 449 | - **/static/lib** 为外部库文件目录。 450 | 451 | 本例中,我们在页面中显示了一张图片。也可以直接通过**/my_library/static/src/image/bug.jpg**访问该图片。 452 | 453 | 本节中,我们在页面中显示了一个静态资源(图片)并学习了各种静态资源的推荐目录。还有更简单的展示页面内容和静态资源的方法,我们在下一章中进行学习。 -------------------------------------------------------------------------------- /2.md: -------------------------------------------------------------------------------- 1 | # 第二章 管理Odoo服务端实例 2 | 3 | 全书完整目录请见:[Odoo 14开发者指南(Cookbook)第四版](README.md) 4 | 5 | 在[第一章 安装Odoo开发环境](1.md)中,我们学习了如何使用源码中所带的标准核心插件配置Odoo实例。本章主要讲解如何对Odoo实例添加非核心或自定义插件。在Odoo中,可以通过多个目录加载插件。此外,推荐使用单独的目录加载第三方插件或自定义的插件,以避免与Odoo核心模块产生冲突。甚至Odoo企业版也是一种类型的插件目录,需要像加载普通插件目录那样对其进行加载。 6 | 7 | 本章中,我们将讲解如下内容: 8 | 9 | - 配置插件路径 10 | - 标准化你的实例目录布局 11 | - 安装及升级本地插件模块 12 | - 通过GitHub安装插件模块 13 | - 对插件应用修改 14 | - 应用及尝试建议的拉取请求 15 | 16 | > 📝**有关用语** 17 | > 18 | > 本书中,我们会交叉使用插件(**add-on**)、模块(**module**)、应用(**app**)或插件模块(**add-on module**)。它们都是指可通过用户界面在Odoo中安装的Odoo应用或扩展应用。 19 | 20 | ## 配置插件路径 21 | 22 | 通过addons_path参数的配置,可以在 Odoo 中加载自己的插件模块。在Odoo初始化一个新数据库时,它会在addons_path配置参数中给定的这些目录中搜索插件模块。addons_path会在这些目录中搜索潜在的插件模块。 23 | 24 | addons_path中所列出的目录预期应包含子目录,每个子目录是一个插件模块。在数据库初始化完成后,将能够安装这些目录中所给出的模块。 25 | 26 | ### 准备工作 27 | 28 | 这一部分假定你已经准备好了实例并生成了配置文件,如在[第一章 安装Odoo开发环境](1.md)*在一个文件中存储实例配置*一节所描述。Odoo的源码存放在~/odoo-dev/odoo中,而配置文件存放在~/odoo-dev/myodoo.cfg中。 29 | 30 | ### 如何配置... 31 | 32 | 按如下步骤在实例的addons_path中添加~/odoo-dev/local-addons目录: 33 | 34 | 1. 编辑你的实例配置文件,即 ~/odoo-dev/myodoo.cfg。 35 | 36 | 2. 定位到以addons_path =开头的一行,默认应该会看到如下内容: 37 | 38 | ``` 39 | addons_path = ~/odoo-dev/odoo/addons 40 | ``` 41 | 42 | **译者注:**当前默认生成的配置文件中为绝对路径 43 | 44 | 3. 修改该行,添加一个逗号(英文半角),并接你想想要添加为addons_path的目录名称,如以下代码所示: 45 | 46 | ``` 47 | addons_path = ~/odoo-dev/odoo/addons,~/odoo-dev/local-addons 48 | ``` 49 | 50 | 4. 在终端中重启实例 51 | 52 | ``` 53 | $ ~/odoo-dev/odoo/odoo-bin -c my-instance.cfg 54 | ``` 55 | 56 | ### 运行原理... 57 | 58 | 在重启 Odoo 时,会读取配置文件。addons_path变量的值应为一个逗号分隔的目录列表。可接受相对路径,但它们是相对于当前工作目录的,因此应在配置文件中尽量避免。 59 | 60 | 至此,我们仅在Odoo中列出了插件目录,但~/odoo-dev/local-addons中尚不存在插件模块。即使在该目录中新增了插件模块,Odoo也不会在用户界面中显示这一模块。为此,你需要执行一个额外的操作,在下一部分*更新插件模块列表*中会进行讲解。 61 | 62 | > 📝这背后的原因是在初始化新数据库时,Odoo在可用模块中自动列举了自定义模块,但如若在数据库初始化之后新增模块,就需要像*更新插件模块列表*一节中那样手动更新可用模块列表。 63 | 64 | ### 扩展知识... 65 | 66 | 在首次调用 odoo-bin脚本来初始化新数据库时,可以传递一个带逗号分隔目录列表的--addons-path命令行参数。这会以所提供插件路径中所找到的所有插件来初始化可用插件模块列表。这么做时,要显式地包含基础插件目录(odoo/odoo/addons)以及核心插件目录(odoo/addons)。与前面稍有不同的是本地插件目录不能为空(**译者注:**请先阅读下面的小贴士),它必须要至少包含一个子目录,并包含插件模块的最小化结构。 67 | 68 | 在[第三章 创建Odoo插件模块](3.md)中,我们会来看如何编写你自己的模块。同时,这里有一个生成内容来满足Odoo要求的快捷版黑科技: 69 | 70 | ``` 71 | $ mkdir -p ~/odoo-dev/local-addons/dummy 72 | $ touch ~/odoo-dev/local-addons/dummy/__init__.py 73 | $ echo '{"name": "dummy", "installable": False}' > \ 74 | ~/odoo-dev/local-addons/dummy/__manifest__.py 75 | ``` 76 | 77 | 你可以使用--save选项来保存路径至配置文件中: 78 | 79 | ``` 80 | $ odoo/odoo-bin -d mydatabase \ 81 | --addons-path="odoo/odoo/addons,odoo/addons,~/odoo-dev/local-addons" \ 82 | --save -c ~/odoo-dev/my-instance.cfg --stop-after-init 83 | ``` 84 | 85 | 本例中,使用相对路径不会有问题,因为它们会在配置文件中转化为绝对路径。 86 | 87 | > **📝注:**因为Odoo仅当从命令行中设置路径时在插件路径的目录中查看插件,而不是在从配置文件中加载路径的时候,dummy已不再必要。因此,你可以删除它(或保留到你确定不需要新建一个配置文件时)。 88 | 89 | ## 标准化你的实例目录布局 90 | 91 | 我们推荐你在开发和生产环境都使用相似的目录布局。这一标准化会在你要执行运维时体现出用处,它也会缓解你日常工作的压力。 92 | 93 | 这一部分创建将相似生命周期或相似用途的文件分组放在标准化子目录中的目录结构。 94 | 95 | > 📝仅在希望以相似的文件结构管理开发和生产环境时才需要学习本节。如果不需要,可以跳过本节。 96 | 97 | 此外,无奇妙严格按照本节中相同的目录结构。请自由按照自己的需求来调整这一结构。 98 | 99 | ### 如何标准化... 100 | 101 | 创建所推荐实例布局,需要执行如下步骤: 102 | 103 | 1. 为每个实例创建一个目录: 104 | 105 | ``` 106 | $ mkdir ~/odoo-dev/projectname 107 | $ cd ~/odoo-dev/projectname 108 | ``` 109 | 110 | 2. 在名为env/的子目录中创建一个Python虚拟环境对象: 111 | 112 | ``` 113 | $ python3 -m venv env 114 | ``` 115 | 116 | 3. 创建一些子目录,如下: 117 | 118 | ``` 119 | $ mkdir src local bin filestore logs 120 | ``` 121 | 122 | 这些子目录的功能如下: 123 | 124 | - src/:包含Odoo本身的一个拷贝,以及一些第三方插件项目(我们在下一步中添加了Odoo源码) 125 | - local/:用于保存你针对具体实例的插件 126 | - bin/:包含各类帮助可执行shell脚本 127 | - filestore/:用于文件存储 128 | - logs/(可选):用于存储服务日志文件 129 | 130 | 4. 克隆Odoo并安装所需依赖包(参见 131 | 132 | 第一章 安装Odoo开发环境 133 | 134 | 获取更多内容): 135 | 136 | ``` 137 | $ git clone -b 14.0 --single-branch --depth 1 https://github.com/odoo/odoo.git src/odoo 138 | $ env/bin/pip3 install -r src/odoo/requirements.txt 139 | ``` 140 | 141 | 5. 以bin/odoo保存如下shell脚本: 142 | 143 | ``` 144 | #!/bin/sh 145 | ROOT=$(dirname $0)/.. 146 | PYTHON=$ROOT/env/bin/python3 147 | ODOO=$ROOT/src/odoo/odoo-bin 148 | $PYTHON $ODOO -c $ROOT/projectname.cfg "$@" 149 | exit $? 150 | ``` 151 | 152 | 6. 让该脚本可执行: 153 | 154 | ``` 155 | $ chmod +x bin/odoo 156 | ``` 157 | 158 | 7. 创建一个空的本地模块dummy: 159 | 160 | ``` 161 | $ mkdir -p local/dummy 162 | $ touch local/dummy/__init__.py 163 | $ echo '{"name": "dummy", "installable": False}' >\ 164 | local/dummy/__manifest__.py 165 | ``` 166 | 167 | 8. 为你的实例生成配置文件: 168 | 169 | ``` 170 | $ bin/odoo --stop-after-init --save \ 171 | --addons-path src/odoo/odoo/addons,src/odoo/addons,local \ 172 | --data-dir filestore 173 | ``` 174 | 175 | 9. 添加一个.gitignore文件,用于告诉GitHub排除这些给定目录,这样Git在提交代码时就会忽略掉这些目录,例如 filestore/, env/, logs/和src/: 176 | 177 | ``` 178 | # dotfiles, with exceptions: 179 | .* 180 | !.gitignore 181 | # python compiled files 182 | *.py[co] 183 | # emacs backup files 184 | *~ 185 | # not tracked subdirectories 186 | /env/ 187 | /src/ 188 | /filestore/ 189 | /logs/ 190 | ``` 191 | 192 | 10. 为这个实例创建一个Git仓库并将已添加的文件添加到Git中: 193 | 194 | ``` 195 | $ git init 196 | $ git add . 197 | $ git commit -m "initial version of projectname" 198 | ``` 199 | 200 | ### 运行原理... 201 | 202 | 我们生成了一个有明确标签目录和独立角色的干净目录结构。我们使用了不同的目录来存储如下内容: 203 | 204 | - 由其它人所维护的代码(src/中) 205 | - 本地相关的具体代码 206 | - 实例的文件存储(filestore) 207 | 208 | 通过为每个项目建一个virtualenv环境,我们可以确保该项目的依赖文件不会与其它项目的依赖产生冲突,这些项目你可能运行着不同的Odoo版本或使用了不同的第三方插件模块,这将需要不同版本的Python依赖。当然也会带来一部分磁盘空间的开销。 209 | 210 | 以类似的方式,通过为我们不同的项目使用不同的Odoo拷贝以及第三方插件模块,我们可以让每个项目单独的进行演化并仅在需要时在这些实例上安装更新,因此也减少了引入回退的风险。 211 | 212 | bin/odoo允许我们不用记住各个路径或激活虚拟环境就可以运行服务。这还为我们设置了配置文件。你可以在其中添加其它脚本来协助日常工作。例如,可以添加一个脚本来检查运行实例所需的第三方项目。 213 | 214 | 有关配置文件,我们仅展示了这里需要设置的最小化选项,但很明显可以做更多设置,例如数据库名、数据库过滤器或项目所监听的端口。有关这一话题的更多信息,请参见[第一章 安装Odoo开发环境](1.md)。 215 | 216 | 最后,通过在Git仓库中管理所有这些,在不同的电脑上复制这一设置及在团队中分享开发内容变得相当容易。 217 | 218 | > **📝**加速贴士 219 | > 220 | > 要加速项目的创建,可以创建一个包含空结构的模板仓库,并为每个新项目复制(fork)该仓库。这会省却你重新输入bin/odoo脚本、.gitignore及其它所需模板文件(持续集成配置、README.md、ChangeLog等等)所花费的时间。 221 | 222 | ### 参见内容 223 | 224 | 如果你喜欢这种方法,我们建议你尝试[第三章 服务器部署](3.md)中的使用 Docker 运行 Odoo 一部分的内容。 225 | 226 | ### 扩展知识... 227 | 228 | 复杂模块的开发要求有各类配置选项,在想要尝试任何配置选项时都会要更新配置文件。更新配置文件常常是一件头痛的事,避免它的一种方式是通过命令行传递所有配置选项,如下: 229 | 230 | 1. 手动激活虚拟环境: 231 | 232 | ``` 233 | $ source env/bin/activate 234 | ``` 235 | 236 | 2. 进入Odoo源代码目录: 237 | 238 | ``` 239 | $ cd src/odoo 240 | ``` 241 | 242 | 3. 运行服务: 243 | 244 | ``` 245 | ./odoo-bin --addons-path=addons,../../local -d test-14 -i account,sale,purchase --log-level=debug 246 | ``` 247 | 248 | 第3步中,我们直接通过命令行传递了一些参数。第一个是--addons-path,它加载Odoo的核心插件目录addons,以及你自己的插件目录local,在其中你可以放自己的插件模块。选项-d会使用test-14数据库或者在该数据库不存在时新建一个数据库。选项-i 会安装会计、销售和采购模块。接着,我们传递了log-level选项来将日志级别提升为debug,这样日志中会显示更多的信息。 249 | 250 | > 📝通过使用命令行,你可以快速地修改配置选项。也可以在Terminal中查看实时日志。所有可用选项可参见[第一章 安装Odoo开发环境](1.md),或使用-help命令来查看所有的选项及各个选项的描述。 251 | 252 | ## 安装并升级本地插件模块 253 | 254 | Odoo 功能的核心来自于它的插件模块。Odoo自带的插件是你所拥有的财富,同时你也可以从应用商店下载一些插件模块或者自己写插件。 255 | 256 | 这一节中,我们将展示如何通过网页界面及命令行来安装和升级插件模块。 257 | 258 | 对这些操作使用命令行的主要好处有可以同时作用于一个以上的插件以及在安装或升级的过程中可以清晰地浏览到服务端日志,对于开发模式或编写脚本安装实例时都非常有用。 259 | 260 | ### 准备工作 261 | 262 | 确保你有一个运行中的 Odoo 实例,且数据库已初始化、插件路径已进行恰当地设置。在这一部分中,我们将安装/升级一些插件模块。 263 | 264 | ### 如何安装升级... 265 | 266 | 安装或升级插件有两种方法-可以使用网页界面或命令行。 267 | 268 | #### 通过网页界面 269 | 270 | 可按照如下步骤来使用网页界面安装新的插件模块到数据库中: 271 | 272 | 1. 使用管理员账户连接实例并打开Apps菜单 273 | [![Apps 页面](https://alanhou.org/homepage/wp-content/uploads/2019/05/2019050906002399.jpg)](https://alanhou.org/homepage/wp-content/uploads/2019/05/2019050906002399.jpg)图2.1 – Odoo应用列表 274 | 2. 使用搜索框来定位你想要安装的插件。这里有一些帮助你完成该任务的操作指南: 275 | - 激活Not Installed过滤器 276 | - 如果你要查找一个具体的功能插件而不是广泛的功能插件,删除Apps过滤器 277 | - 在搜索框中输入模块名的一部分并使用它来作为模块过滤器 278 | - 你会发现使用列表视图可以阅读到更多的信息 279 | 3. 点击卡片中模块名下的Install按钮。 280 | 281 | 注意有些Odoo插件模块具有外部Python依赖,如果你的系统中未安装该Python依赖,那么 Odoo 会中止安装并显示如下的对话框: 282 | 283 | [![pyldap 安装依赖](https://alanhou.org/homepage/wp-content/uploads/2019/05/2019050906125210.jpg)](https://alanhou.org/homepage/wp-content/uploads/2019/05/2019050906125210.jpg)图2.2 – 外部库依赖的警告 284 | **译者注:**按正常安装不会出现一错误,需通过 pip uninstall pyldap 才能复现这一错误 285 | 286 | 修复这一问题,仅需在你的系统中安装相关的Python依赖即可。 287 | 288 | 要升级已安装到数据库的模块,使用如下步骤: 289 | 290 | 1. 使用管理员账户连接到实例 291 | 2. 打开Apps菜单 292 | 3. 点击Apps: 293 | ![图2.3 – Odoo应用列表](https://i.cdnl.ink/homepage/wp-content/uploads/2020/12/2020122609040440-scaled.jpg) 294 | 图2.3 – Odoo应用列表 295 | 4. 使用搜索框来定位你所安装的插件。有如下的小贴士: 296 | - 激活Installed过滤器 297 | - 如果你要查找一个具体的功能插件而不是广义的功能插件,删除Apps过滤器 298 | - 在搜索框中输入部分插件模块的名称并按下 Enter 来使用它作为模块过滤器。例如,输入CRM并按下 Enter 来搜索CRM应用 299 | - 你会发现使用列表视图可以阅读到更多的信息 300 | 5. 点击卡片右上角的的三个点,然后点击Upgrade选项: 301 | 302 | ![图2.4 – 升级模块的下拉链接 ](https://i.cdnl.ink/homepage/wp-content/uploads/2020/12/2020122609084049-scaled.jpg) 303 | 304 | 图2.4 – 升级模块的下拉链接 305 | 306 | 激活开发者模式来查看模块的技术名称。如果你不知道如何激活开发者模式,请参见[第一章 安装Odoo开发环境](1.md): 307 | 308 | [![查看模块技术名称](https://alanhou.org/homepage/wp-content/uploads/2019/05/2019050906302261.jpg)](https://alanhou.org/homepage/wp-content/uploads/2019/05/2019050906302261.jpg) 309 | 310 | 图2.5 – 应用的技术名称 311 | 312 | 在激活开发者模式之后,它会以红色显示模块的技术名称。如果你使用的是Odoo社区版,会看到一些带有Upgrade按钮的应用。这些是Odoo企业版的应用,要想安装/使用它们,需要购买一个证书。 313 | 314 | #### 通过命令行 315 | 316 | 在数据库中安装新插件,可执行如下步骤: 317 | 318 | 1. 查找插件的名称。这是包含__manifest__.py文件的目录名,不带前面的路径。 319 | 320 | 2. 停止实例。如果你在操作生产数据库,请进行备份。 321 | 322 | 3. 运行如下命令: 323 | 324 | ``` 325 | $ odoo/odoo-bin -c instance.cfg -d dbname -i addon1,addon2 --stop-after-init 326 | ``` 327 | 328 | 如果在配置文件中进行过设置可以省略掉-d dbname。 329 | **译者注:**请将addon1,addon2替换为你所要安装的插件名 330 | 331 | 4. 重启实例 332 | 333 | 升级数据库中已安装的插件,可执行如下步骤: 334 | 335 | 1. 查找待更新的插件模块名称。这是包含__manifest__.py文件的目录名,不带前面的路径。 336 | 337 | 2. 停止实例。如果你在操作生产数据库,请进行备份。 338 | 339 | 3. 运行如下命令: 340 | 341 | ``` 342 | $ odoo/odoo-bin -c instance.cfg -d dbname - u addon1 --stop-after-init 343 | ``` 344 | 345 | 如果在配置文件中进行过设置可以省略掉-d dbname。 346 | 347 | 4. 重启实例 348 | 349 | ### 运行原理... 350 | 351 | 插件模块的安装和升级是两个紧密关联的操作,但有一些重要的区别,在下面两部分中进行了强调: 352 | 353 | #### 插件安装 354 | 355 | 在安装插件时,Odoo以提供的名称检查它的可用插件列表中未安装插件。它还会检查该插件的依赖,并且如果有依赖的话,它会在安装插件前递归安装这些依赖。 356 | 357 | 单个模块的安装包含如下步骤: 358 | 359 | 1. 如果存在,运行插件preinit钩子 360 | 2. 从Python源代码中加载模型定义并在必要时更新数据库结构(参见[第四章 应用模型](4.md)了解更多信息) 361 | 3. 加载插件的数据文件并在必要时更新数据库内容(参见[第六章 管理模块数据](6.md)了解更多信息) 362 | 4. 如果实例中启用了演示数据则安装插件演示数据 363 | 5. 如果存在,运行插件postinit钩子 364 | 6. 运行对插件视图定义的验证 365 | 7. 如果启用了演示数据及测试,运行该插件的测试(参见[第十八章 自动化测试用例](18.md)了解更多信息) 366 | 8. 在数据库中更新模块状态 367 | 9. 从插件的翻译文件中更新数据库中的翻译(参见[第十一章 国际化](11.md)了解更多信息) 368 | 369 | > 📝preinit和postinit钩子分别使用pre_init_hook和post_init_hook键名在__manifest__.py文件中定义。这些钩子用于在插件模块的安装之前及之后触发Python函数。参见[第三章 创建Odoo插件模块](3.md)了解更多有关 init 钩子的知识。 370 | 371 | #### 插件升级 372 | 373 | 升级插件时,Odoo以给定的名称在可用的插件模块列表中检查已安装插件。它还会检查该插件的反向依赖(即依赖于所升级插件的那些插件)。如果存在,则也会对它们进行递归升级。 374 | 375 | 单个插件模块的升级过程包含如下步骤: 376 | 377 | 1. 如果存在,先运行插件模块的预迁移步骤(参见[第六章 管理模块数据](6.md)了解更多信息) 378 | 2. 从Python源码中加载模型定义并在必要时更新数据库结构(参见[第四章 应用模型](4.md)了解更多信息) 379 | 3. 加载插件的数据文件并在必要时更新数据库内容(参见[第六章 管理模块数据](6.md)了解更多信息) 380 | 4. 如果实例中启用了演示数据更新插件演示数据 381 | 5. 如果模块有任何迁移方法的话,运行插件模块的后置迁移步骤(参见[第六章 管理模块数据](6.md)了解更多信息) 382 | 6. 运行对插件视图定义的验证 383 | 7. 如果启用了演示数据并启用了测试,运行该插件的测试(参见[第十八章 自动化测试用例](18.md)了解更多信息) 384 | 8. 在数据库中更新模块状态 385 | 9. 从插件的翻译文件中更新数据库中的翻译(参见[第十一章 国际化](11.md)了解更多信息) 386 | 387 | > 📝注意更新未安装的插件模块时什么也不会做。但是安装已安装的插件模块会重新安装该插件,这会通过一些包含数据的数据文件产生一些预期外的问题,这些文件应由用户进行更新而非在常规的模块升级处理时进行更新(参见[第六章 管理模块数据](6.md)中使用noupdate和forcecreate标记部分的内容)。通过用户界面不存在错误的风险,但通过命令行时则有可能发生。 388 | 389 | ### 扩展知识... 390 | 391 | 要当心依赖的处理。假定有一个实例你想要对其安装sale、sale_stock和sale_specific插件,sale_specific依赖于sale_stock,而sale_stock依赖于sale。要安装这三者,你只需要安装sale_specific,因为它会递归安装sale_stock和sale这两个依赖。要升级这三者,需要升级sale,因为这样会递归升级其反向依赖,sale_stock和sale_specific。 392 | 393 | 管理依赖另一个比较搞的地方是在你向已经安装了一个版本的插件添加依赖的时候。我们继续通过前例来理解这一问题。想像一下在sale_specific中添加了一个对stock_dropshipping的依赖。更新sale_specific插件不会自动安装新的依赖,也不会要求安装sale_specific。在这种情况下,你会收到非常糟糕的错误消息,因为插件的Python代码没有成功加载,而插件的数据和模型表则已存在于数据库中。要解决这一问题,你需要停止该实例并手动安装新的依赖。 394 | 395 | ## 从GitHub安装插件模块 396 | 397 | GitHub是第三方插件一个很好的来源。很多Odoo合作伙伴使用GitHub来分享他们内部维护的插件,而Odoo社区联盟(OCA)在GitHub上共同维护着几百个插件。在你开始编写自己的插件之前,确保查看是否已有可直接使用的插件或者作为初始以继续扩展的插件。 398 | 399 | 这一部分向你展示如何从GitHub上克隆OCA的partner-contact项目并让其中所包含的插件模块在我们实例中可用。 400 | 401 | ### 准备工作 402 | 403 | 假设你希望对客户(partner) 表单添加新的字段。默认Odoo客户模型不包含gender字段。如果要添加gender字段,需要新建一个模块。所幸邮件列表中有人告诉你有partner_contact_gender这么一个插件模块,由OCA作为partner-contact项目的一部分进行维护。 404 | 405 | 本部分中所使用的路径反映了我们在*标准化你的实例目录布局*一节中所推荐的布局。 406 | 407 | ### 如何安装... 408 | 409 | 按照如下步骤来安装partner_contact_gender: 410 | 411 | 1. 进入项目目录: 412 | 413 | ``` 414 | $ cd ~/odoo-dev/my-odoo/src 415 | ``` 416 | 417 | 2. 在src/目录中克隆partner-contact项目的14.0分支: 418 | 419 | ``` 420 | $ git clone --branch 14.0 \ 421 | https://github.com/OCA/partner-contact.git src/partner-contact 422 | ``` 423 | 424 | 3. 修改插件路径来包含该目录并更新你的实例中的插件列表(参见本章中的 425 | 426 | 配置插件路径 427 | 428 | 和 429 | 430 | 更新插件模块列表 431 | 432 | 小节)。instance.cfg中的addons_path一行应该是这样的: 433 | 434 | ``` 435 | addons_path = ~/odoo-dev/my-odoo/src/odoo/odoo/addons, \ 436 | ~/odoo-dev/my-odoo/src/odoo/addons, \ 437 | ~/odoo-dev/my-odoo/src/, \ 438 | ~/odoo-dev/local-addons 439 | ``` 440 | 441 | 4. 安装partner_contact_gender插件(如果你不知道如何安装该模块,参见前面的小节,*安装并升级本地插件模块*) 442 | 443 | ### 运行原理... 444 | 445 | Odoo社区联盟的所有代码仓库都将他们自己的插件放在单独的子目录中,这与Odoo对插件路径中目录的要求是一致的。因此,只需复制某处的仓库并将其添加到插件路径中就够了。 446 | 447 | ### 扩展知识... 448 | 449 | 有些维护者遵循不同的方法,每个插件模块一个仓库,放在仓库的根目录下。这种情况下,需要新建一个目录,在这个目录中添加插件路径并克隆所需维护者的插件到该目录中。记住在每次添加一个新仓库克隆时要更新插件模块列表。 450 | 451 | ## 对插件应用修改 452 | 453 | GitHub上可用的大部分插件需要进行修改并且不遵循Odoo对其稳定发行版所强制的规则。它们可能进行漏洞修复或改善,包含你提交的问题或功能请求,这些修改可能会带来数据库模式的修改或数据文件和视图中的更新。这一部分讲解如何安装升级后的版本。 454 | 455 | ### 准备工作 456 | 457 | 假定你对partner_contact_gender报告了一个问题并收到通知说该问题已在partner-contact项目14.0分支的最近一次修订中得以解决。这种情况下,你可以使用最新版本来更新实例。 458 | 459 | ### 如何修改... 460 | 461 | 要对来自GitHub的插件进行源的变更,需执行如下步骤: 462 | 463 | 1. 停止使用该插件的实例。 464 | 465 | 2. 如果是生产实例请进行备份(参见[第一章 安装Odoo开发环境](1.md)中*管理Odoo服务端数据库*一节)。 466 | 467 | 3. 进入克隆了partner-contact的目录: 468 | 469 | ``` 470 | $ cd ~/odoo-dev/my-odoo/src/partner-contact 471 | ``` 472 | 473 | 4. 为该项目创建一个本地标签,这样万一出现了崩溃还可以进行回退: 474 | 475 | ``` 476 | $ git checkout 14.0 477 | $ git tag 14.0-before-update-$(date --iso) 478 | ``` 479 | 480 | 5. 获取源码的最新版本: 481 | 482 | ``` 483 | $ git pull --ff-only 484 | ``` 485 | 486 | 6. 在数据库中更新partner_contact_gender插件(参见*安装并升级本地插件模块*一节) 487 | 488 | 7. 重启实例 489 | 490 | ### 运行原理... 491 | 492 | 通常,插件模块的开发者不时会发布插件的最新版本。这一更新一般包含漏洞修复及新功能。这里,我们将获取一个插件的新版本并在我们的实例中更新它。 493 | 494 | 如果git pull --ff-only失败的话,可以使用如下命令回退到前一个版本: 495 | 496 | ``` 497 | $ git reset --hard 14.0-before-update-$(date --iso) 498 | ``` 499 | 500 | 然后,可以尝试git pull(不添加--ff-only),它会产生一个合并,但这表示你对插件做了本地修改。 501 | 502 | ### 其它内容... 503 | 504 | 如果更新这一步崩溃了,参见[第一章 安装Odoo开发环境](1.md)*从源码更新Odoo*一节获取恢复的操作指南。记住要保持首先在生产数据库的拷贝上进行测试。 505 | 506 | ## 应用及尝试建议的拉取请求 507 | 508 | 在GitHub的世界中,拉取请求(PR)是由开发者所提交的请求,这样项目维护人员可以添加一些新的开发。比如一个 PR 可能包含漏洞修复或新功能。这些请求在拉取到主分支之前会进行审核和测试。 509 | 510 | 这一部分讲解如何对你的 Odoo 项目应用一个PR来测试漏洞修复的改进。 511 | 512 | ### 准备工作 513 | 514 | 在前一节中,假定你对partner_contact_gender 报告了一个问题并收到一条通知在拉取请求中问题已修复,尚未合并到项目的14.0分支中。开发人员要求你验证PR #123中的修复状况。你需要使用这一分支更新一个测试实例。 515 | 516 | 不应在生产数据库直接使用该分支,因此先创建一个带有生产数据库拷贝的测试环境(参见[第一章 安装Odoo开发环境](1.md))。 517 | 518 | ### 如何操作... 519 | 520 | 应用并测试一个插件的GitHub拉取请求,需要执行如下步骤: 521 | 522 | 1. 停止实例 523 | 524 | 2. 进入partner-contact所被克隆的目录: 525 | 526 | ``` 527 | $ cd ~/odoo-dev/my-odoo/src/partner-contact 528 | ``` 529 | 530 | 3. 为该项目创建一个本地标签,这样万一出现崩溃时你可以回退: 531 | 532 | ``` 533 | $ git checkout 14.0 534 | $ git tag 14.0-before-update-$(date --iso) 535 | ``` 536 | 537 | 4. 拉取pull请求的分支。这么做最容易的方式是使用PR编号,在开发者与你沟通时你应该可以看到。在本例中,这个拉取请求编号是123: 538 | 539 | ``` 540 | $ git pull origin pull/123/head 541 | ``` 542 | 543 | 5. 在你的数据库中更新partner_contact_gender1插件模块并重启该实例(如果你不知道如何更新该模块的话请参见*安装并升级本地插件模块*一节) 544 | 545 | 6. 测试该更新 - 尝试重现问题,或测试你想要的功能。 546 | 547 | 如果这不能运行,在GitHub的PR页面进行评论,说明你做了什么以及什么不能运行,这样开发者可以更新这个拉取请求。 548 | 549 | 如果它没有问题,也在PR页面说下;这是PR验证流程中非常重要的一部分;这会加速主分支中的合并。 550 | 551 | ### 运行原理... 552 | 553 | 我们在使用一个GitHub功能,使用pull/nnnn/head分支名称来通过编号进行拉取请求的拉取,其中nnnn是PR的编号。Git pull命令会合并远程分支到我们的分支,在我们基础代码中应用修改。在这之后,我们更新插件模块、对其测试并向作者报告修改是成功或是失败。 554 | 555 | ### 扩展知识... 556 | 557 | 如果你想要同步测试它们,你可以针对相同仓库的不同拉取请求重复本节中的第4步。如果你对结果很满意,可以创建一个分支来保留对应用了改变的结果的引用: 558 | 559 | ``` 560 | $ git checkout -b 14.0-custom 561 | ``` 562 | 563 | 使用一个不同的分支会有助于记住你没有使用GitHub的版本,而是一个自定义的版本。 564 | 565 | > 📝git branch命令可用于列出你仓库中的所有本地分支。 566 | 567 | 从这开始,如果需要应用来自GitHub中14.0分支的最近一个审核版本,需要在拉取时不使用--ff-only: 568 | 569 | ``` 570 | $ git pull origin 14.0 571 | ``` -------------------------------------------------------------------------------- /16.md: -------------------------------------------------------------------------------- 1 | # 第十六章 Odoo Web Library (OWL) 2 | 3 | 全书完整目录请见:[Odoo 14开发者指南(Cookbook)第四版](README.md) 4 | 5 | Odoo v14引入了全新的JavaScript框架,名之为OWL (全称Odoo Web Library)。OWL是一个基于组件的框架,结构上使用QWeb模板。OWL与Odoo传统的我微件系统相比非常快速,并且引入了大量的功能,包括钩子、响应式、子组件自动实例化等等。本章中,我们将学习如何使用OWL组件生成交互UI元素。我们会通过OWL组件开始讲解,接着学习组件的生命周期。最后,我们会为视图新建整体上字段微件。本章将包含如下小节: 6 | 7 | - 创建OWL组件 8 | - 在OWL组件中管理用户动作 9 | - 将OWL组件变为响应式 10 | - 掌握OWL组件的生命周期 11 | - 在表单视图中添加OWL字段 12 | 13 | > 📝注:你可能会问以下的问题:Odoo为什么不使用那些知名的JavaScript框架,如React.js或Vue.js?可参见[Odoo 14全新前端框架 OWL(Odoo Web Library)官方文档中文版](https://alanhou.org/odoo-14-owl/)了解更多有关OWL框架的知识。 14 | 15 | ## 技术准备 16 | 17 | OWL组件使用ES6类进行定义。本章中,我们会使用一些ES6语法。同时,老浏览器不支持某些ES6语法,所以请确保使用Chrome或Firefox的最新版本。本章中的代码参见 [GitHub](https://github.com/alanhou/odoo14-cookbook/tree/main/Chapter16)。 18 | 19 | ## 创建OWL组件 20 | 21 | 本节的主要目标是学习OWL组件的基础知识。我们会创建一个最小化的OWL组件并将其加入到Odoo网页客户端中。本节中我们创建一个带有文本的小横栏组件。 22 | 23 | ### 准备工作 24 | 25 | 学习本需要用到带有基础字段和视图的my_library模块。可以在GitHub仓库的Chapter16/00_initial_module目录中找到这个基础my_library模块。 26 | 27 | ### 如何实现... 28 | 29 | 我们来对Odoo的网页端添加一个小的横栏。执行如下步骤来为Odoo客户端添加第一个组件: 30 | 31 | 1. 添加一个JavaScript文件/my_library/static/src/js/component.js,定义新模块命名空间: 32 | 33 | ``` 34 | odoo.define('my.component', function (require) { 35 | "use strict"; 36 | // 在这里放第3-5步中的代码 37 | }); 38 | ``` 39 | 40 | 2. 添加XML文件/my_library/views/templates.xml file,并在资源中加载JavaScript组件如下: 41 | 42 | ``` 43 |