「Odoo 基础教程系列」第二篇——从 Todo 应用开始(1)

2018/06/26 Odoo Tutorial

By PickPik

在第一篇教程发布之后差不多一个月的今天,终于完成了第二篇内容,这个发布周期拖得实在是有点太长了,我都觉得不好意思了 :P

从这一篇教程开始,我们将以创建一个 Todo 应用为目标,将 Odoo 中的一些概念和内容逐渐引入进来,然后利用这些知识去完善我们的 Todo 应用。虽然这是个很简单的应用,但是希望大家可以动手一起操作,从最简单的开始上手学习如何使用 Odoo 这个框架。

这篇教程将会带领大家一起创建第一个 Odoo 模块,为模型增加菜单和修改视图,对于刚接触的小伙伴来说可能会有些生涩,不理解也没关系,慢慢接触上手之后会显得容易得多,同时也欢迎大家积极提问,我会竭尽所能去帮助爱学习的小伙伴的。

好了,下面就正式开始,一起动手完成这一次的内容吧 :)

创建模块

我们在 Odoo 源码所在目录的同一层级里创建一个目录 mkdir mymodules,这个目录专门用于存放我们自己创建的模块,和 Odoo 自带的模块区分开来,当然你也可以用其他名字。然后用 odoo-bin 的脚手架功能创建一个空的 Odoo 模块,里面包含了一个完整的项目所需的基本文件和项目结构:

./odoo/odoo-bin scaffold todo mymodules

进去 mymodules/ 看看,是不是多了一个目录 todo/?就是这么简单,我们的第一个 Odoo 模块就创建好了,接下来先对项目结构进行简单的说明,了解一下各个目录底下应该放置些什么内容。

结构说明

下面是我们刚创建的 todo 模块的目录结构,从上到下一一进行说明:

todo
├── __init__.py
├── __manifest__.py
├── controllers
│   ├── __init__.py
│   └── controllers.py
├── demo
│   └── demo.xml
├── models
│   ├── __init__.py
│   └── models.py
├── security
│   └── ir.model.access.csv
└── views
    ├── templates.xml
    └── views.xml

在 Python 中,每一个包(package)都包含一个 __init__.py 文件,而一个 Odoo 的模块,同时也是一个 Python 包,所以我们可以看到,生成的项目文件里已经包含了 __init__.py 这个文件,如果打开这个文件,你会看到里面引入了 controllersmodels 这两个包,稍候我们会讲到这两个,这里先放一放。那么对于这个 __init__.py 文件,我们没什么特殊需求,是可以不用去理会的,就让它静静地躺在那里完成它的使命就好了。

文件 __manifest__.py 用于声明一个 Odoo 模块以及指定它的元数据(metadata),文件里只包含了一个单独的 Python 字典,里面默认只列出了 9 项最基本的配置项,包含了模块(或应用)名,模块的简介和详细介绍,作者和网站,模块的所属分类、版本,还有就是这个模块依赖于其他的哪些 Odoo 模块,需要加载哪些数据文件以及演示数据。除了这里列出的配置项外,还有一些高级配置项,我们这里暂时不需要理会,后面用到之后将会进行详细的说明。

接下来我们先讲一下 demosecurity 这两个目录。前者是用于存放演示数据的,在 __manifest__.py 中就可以看到有引入该目录下的 demo.xml 文件,在使用演示模式时,初始化一些演示数据可以帮我们节省不少的时间;而后者通过名字就知道它的作用是跟安全相关的,目录下只有一个 ir.model.access.csv 文件,里面用于定义不同的角色组对应于不同模型的相关权限,包括读(read),写(write),创建(create)和删除(unlink)权限,拥有相关权限则为 1,反之为 0。我们刚提到了角色组,但是没有发现相关定义的位置,我们只要默认角色组的定义和模型权限定义在同一目录下就可以了,角色组的定义同样也是使用的 .xml 文件,在后面我们会有专门的一篇文章对角色组和权限进行讲解说明。

下面要讲的是 Odoo 开发中的核心部分 MVC(同时也是大部分 Web 应用开发所采用的经典模式),MVC 分别代表的是 Model(模型)、View(视图)和 Controller(控制器)。有关 MVC 模式具体的概念和相关的知识,这里就不作详细讲解了,希望不了解的同学可以去找找相关的内容学习一下。这里简单说一下它们各自的用途,在 Model 中,我们定义一切和数据相关的东西,例如对应到数据库表字段的模型字段、各种外键关系以及操作数据的逻辑方法等。View 则是负责数据展示的,我们通过编写视图控制需要展示出来的数据以及以什么样的形式展示数据等,并且可以在视图上进行交互。Controller 则是在 Model 和 View 之间,负责响应用户操作,从 Model 中获取数据进行处理并返回到 View 中。

以上就是对一个 Odoo 模块的目录结构的一些简单的说明,另外还有一些目录在初始创建的模块里是没有出现的,但是我们后面会用上,到时候自己手动创建相应的目录即可。

模型

前面一节中我们说到 Model 是用于定义和数据相关内容的地方,现在就来创建第一个模型吧。

首先打开 models/models.py 将里面注释掉的内容删掉,不要修改文件顶部引入的包,然后添加以下代码:

class TodoTask(models.Model):
     _name = 'todo.task'
     _description = '待办事项'

     name = fields.Char('描述', required=True)
     is_done = fields.Boolean('已完成?')

看起来十分简单,第一个模型就这样创建好了 :)

我们创建了一个叫做 TodoTask 的类,它继承自基础模型 models.Model,这也是我们使用 Odoo 开发时最常用的一种用于持久化数据的基础模型,除此之外还有 models.TransientModelmodels.AbstractModel 这两种,在之后接触到我们将会进行讲解,这里暂不涉猎。

先看一下这个模型里带下划线前缀的两个属性:

  • _name - 模型的名称,在外键或者实例化模型对象时会用到,是模型的唯一标识
  • _description - 模型的描述,描述模型的作用,一般情况下不会主动使用到

除了上面列出的两个特殊属性外,还有一些其他属性,例如更改默认显示名称时会用到 _rec_name,继承现有模型时会用到 _inherit 等,这些特殊属性都具有他们各自的用途,但是除了 _name 是定义一个新的模型时必须要有的属性外,其他属性都是可选的。

在 Odoo 的模型定义中,我们使用 fields 进行字段的定义,在上面这个模型里,我们简单地定义了 nameis_done 两个字段,它们的类型分别是 CharBoolean,并且我们指定字段 name 是必填的(添加了 required=True)。如果小伙伴接触过 Django,可能就会说了,Django 的 CharField 是必须指定最大长度 max_length 的,Odoo 的不需要吗?那最大长度是多少呢?对于这个疑问,官方的文档里有这样一句说明:

Basic string field, can be length-limited, usually displayed as a single-line string in clients.

也就是说 Odoo 的字段类型 Char 是不限制长度的,但是通常只是用于单行字符串,如果要存储大量文本内容,还是使用 Text 更合适一些。

不如我们先把模块安装好,看看会不会有什么事情发生,和上一篇教程一样,我们先将 Odoo 服务跑起来,记得先在 Odoo 的源码目录中激活我们的开发环境:

pipenv shell

./odoo-bin --addons-path=addons,../mymodules --db-filter=^demo$ -d demo

需要注意一下的是 --addons-path 中除了有 addons 外,还加上了我们刚创建的模块目录 mymodules,这里使用的是相对路径,不要搞错了哦 :)

接下来我们打开浏览器访问 localhost:8069 然后用超级管理员帐号登录(不记得了?帐号密码都是 admin),登录后在应用列表页面,将搜索框的 Apps 标签去掉,搜索关键词 todo 找到我们刚创建的模块,点击 Install 按钮安装模块。好了,你很快就会发现,页面又倒回了应用列表页面,如果我们再次按前面的步骤搜索我们的模块,你会发现没有了安装按钮,现在显示的是 Installed 已安装,除了这个变化外仿佛一切都没发生过,这是为什么呢?

因为我们没有入口,找不到可以打开我们的 Todo 应用的地方,接下来我们就一起创建第一个入口——菜单。

创建菜单

光有模型是当然不够的,就好象一幢大楼,钢筋骨架都搭起来了,然后把墙给封死了,没有门我们进不去呀!

刚好像说了个不怎么恰当的比喻,不如直接动手,创建一个菜单更实际。在模块里的 views 目录下创建一个 menus.xml 文件,然后输入以下内容:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data>
        <!-- 主菜单定义 -->
        <menuitem id="menu_todo" name="Todo"/>
        <!-- 菜单动作定义 -->
        <record id="action_todo_task" model="ir.actions.act_window">
            <field name="name">待办事项</field>
            <field name="res_model">todo.task</field>
            <field name="view_type">form</field>
            <field name="view_mode">tree,form</field>
            <field name="target">current</field>
        </record>
        <!-- 子菜单定义 -->
        <menuitem action="action_todo_task" id="submenu_todo_task" name="待办事项"
                  parent="menu_todo" sequence="10"/>
    </data>
</odoo>

上面各部分内容我都写上了备注,大家一看就应该知道它们各自是干什么的了。在 Odoo 中定义一个菜单,使用的是 menuitem,然后我们需要为菜单指定属性 idname,前者是这个菜单的唯一标识,而后者则是这个菜单所显示的名字。

然后我们定义了一个菜单动作,也就是点击一个菜单时所要执行的动作,Odoo 中动作的类型分为几个类型,从 model 这个属性可以看出来一个动作是属于什么类型的,act_window 表示我们定义的这个动作是和窗口有关的,例如打开一个弹窗或者一个列表页面,都可以通过窗口动作实现。其他类型的动作我们先放一放,后面会有专门的一篇文章用于讲述动作相关的内容,我们先简单地了解一下定义一个窗口动作有哪些要素。

和定义菜单一样,必不可少的会有 id 这一属性,以后我们将默认所有在 .xml 文件中定义的数据,都需要带有 id 属性,毕竟这是我们能够通过代码找到它们的唯一标识了。这里我们用了 record 标签,包裹了一系列的 field 来定义我们的动作,除了可以定义动作外,包括前面提到的菜单在内的一切具有实际模型的对应的数据,我们都可以这样定义,最大的区别就在于 model 这个属性所指定的值,它对应到我们定义的数据是来自哪个模型的,被包裹在里面的 field 则对应到一个模型的各个字段。刚开始可能不太明白,不过不要紧,后面会一直使用到,很快就能掌握的了。

最后我们定义了一个子菜单,可以看到和最开始定义的菜单不一样的是,我们这里多了 3 个属性,分别是 actionparentsequence。其中 action 的值是我们定义的动作的 id,表示点击这个菜单,就执行对应 id 的动作,parent 指定的值是我们定义的第一个菜单的 id,表示当前菜单是指定菜单的子菜单,sequence 用于指定当前菜单的位置,如果我们在主菜单下有多个子菜单,我们可以通过这个属性去指定各个子菜单排列的先后顺序。

关于菜单和动作,暂时就先到这里,最后不要忘了把文件 menus.xml 放到 __manifest__.pydata 列表中,不然我们定义的内容是不会被加载的哦:

# 现在应该是这样的
'data': [
    # 'security/ir.model.access.csv',
    'views/views.xml',
    'views/templates.xml',
    'views/menus.xml',
],

好咧,菜单有了,也就有了入口,现在我们刷新页面看看有没有什么变化,好像还是一样没有什么不一样?当然了,因为我们修改了模块里的文件内容,这些变动并没有被加载,所以我们需要先升级模块。一样的步骤,先找到我们的 Todo 应用模块,然后点击卡片进入表单页面,可以看到有个 Upgrade 按钮,我们点击这个按钮把模块升级一下,然后你就会发现,我们终于看到了期待已久的菜单和 Todo 应用的列表页面啦~

待办事项列表页

我们的 Todo 应用已经初具原型了,点击 Create 按钮,创建一些数据试试看吧 :)

还记得在创建模型的时候给字段 name 加上了一个 required=True 的属性吗?在创建记录的表单页我们可以看到,这个字段是淡紫色背景的,这说明这一项是必填的内容,如果不填写,点击 Save 按钮,就会发现标签显示的「描述」变成了红色,输入框也被红色边框包裹起来了,并且可以看到在窗口右上角出现了一个警告提示。

待办事项表单页警告

在创建好一条待办事项的记录后,点击 Save 保存,如果想要继续创建,可以直接在表单页上点击 Create 继续创建新的记录,也可以返回列表页查看全部的待办事项记录。如果完成了一个待办事项,我们点击对应的记录,进入到表单页面,然后点击 Edit 编辑按钮,然后勾选「已完成?」表示我们已经完成了这个任务。

想要删除掉一些记录该怎么办?可以在列表页先勾选需要删除的记录,然后点击上面的 Action 菜单,可以看到 Delete 这个选项,点击后会询问是否要删除,点击 Ok 确认就可以删除啦~除了列表页上可以删除记录,还能打开一条待办事项进入表单页面,然后打开 Action 菜单执行删除操作。

创建视图

在创建菜单之后,我们直接就看到了相应的列表和表单页面,可是我们并没有创建任何相关的内容,这是 Odoo 自动帮我们做了处理。如果一个模型没有创建对应的视图(View),Odoo 就会根据默认的规则进行显示。

可是默认在列表页只显示了一个 name 字段,我们想要知道一个事项是否完成了,并不能直接在列表页上知道,需要一个个点击进去查看,如果有很多的记录,这可就太蛋疼了!所以我们还是需要自己创建一个列表视图,把是否已完成显示在列表页上。

打开 views/views.xml,把里面注释掉的内容都删掉,然后添加以下内容:

<odoo>
    <data>
        <record id="todo_task_view_tree" model="ir.ui.view">
            <field name="name">todo.task.view_tree</field>
            <field name="model">todo.task</field>
            <field name="type">tree</field>
            <field name="arch" type="xml">
                <tree string="Todo">
                    <field name="name"/>
                    <field name="is_done"/>
                </tree>
            </field>
        </record>
    </data>
</odoo>

创建视图相关的内容,我们需要指定属性 model="ir.ui.view",然后还有两个关键的地方是 type 的值,如果创建的是列表视图,则填写 tree,如果是表单视图则是 form,除了这两种视图外还有其他类型的视图,我们暂时不会接触到。然后是 arch 的值,里面的内容决定了我们的视图长什么样,列表视图需要用 <tree></tree> 包裹起来,表单视图则是 <form></form>,包裹的内容具会有所不同,这里我们只需要在列表视图中将要显示的字段列举出来即可。

完成这一切之后,升级一下模块,然后看看效果如何。

修改后的列表页

可以看到列表里多了一列「已完成?」,这样我们就可以一目了然,知道哪些事项是已经完成了的,然后就可以直接勾选将他们删除掉啦 XD

源码下载

从这一篇教程开始,我会把所有代码提交到 Github 的仓库中,并且我会保留相应的文件修改的 commits 以供追溯,大家可以通过查看 commits 看到各个文件的变动,如果发现什么问题,也可以直接提 issue 寻求帮助。

仓库地址:Odoo-Tutorial-Demo


知识共享许可协议

声明:本站的所有文章,都采用知识共享署名-非商业性使用-禁止演绎 3.0 中国大陆许可协议进行许可。

注意:若未作说明,则本文为「TNK」原创。转载务必注明出处

本文永久地址:https://ruterly.com/2018/04/15/Odoo-basic-tutorial-02/

Search

    Table of Contents