第2章:Odoo模块基础——创建第一个Odoo模块

好,咱们直接动手。这一章我会带你从零创建一个Odoo模块。别怕,其实就几个文件的事。我刚开始学的时候,也被那一堆目录结构吓到过,后来发现——说白了,Odoo模块就是个有规矩的文件夹。

2.1 模块的目录结构

一个标准的Odoo模块长这样:

my_module/
├── __init__.py
├── __manifest__.py
├── models/
│   ├── __init__.py
│   └── my_model.py
├── views/
│   ├── my_model_views.xml
│   └── menu_views.xml
├── security/
│   └── ir.model.access.csv
├── data/
│   └── demo_data.xml
├── static/
│   └── description/
│       └── icon.png
└── tests/
    ├── __init__.py
    └── test_my_model.py

嗯,这里要注意:__init__.py 是Python包的标志,每个子目录下都得有。我见过不少新手漏掉 models 目录下的 __init__.py,结果模块死活加载不上——排查了半天,就少了个空文件。

2.2 理解 manifest.py 文件

这是模块的身份证。Odoo启动时,第一个读的就是它。我个人习惯把 __manifest__.py 叫做「模块说明书」。

{
    'name': "我的第一个模块",
    'version': '1.0',
    'category': 'Uncategorized',
    'summary': '这是一个学习用的模块',
    'description': """
        这个模块用来演示Odoo模块的基础结构。
        包含模型、视图、权限等核心内容。
    """,
    'author': "你的名字",
    'website': "https://your-website.com",
    'depends': ['base'],
    'data': [
        'security/ir.model.access.csv',
        'views/my_model_views.xml',
        'views/menu_views.xml',
    ],
    'demo': [
        'data/demo_data.xml',
    ],
    'installable': True,
    'application': True,
    'auto_install': False,
}

关键字段解读:

  • depends:依赖哪些模块。哪怕你的模块再简单,也得依赖 base。我曾经犯过一个错——把依赖写成了 ['Base'],大写B,结果Odoo报错说找不到模块。大小写敏感,切记。
  • data:模块安装时要加载的XML、CSV文件。顺序很重要,先加载权限文件,再加载视图。
  • demo:演示数据,只在开启演示数据时加载。我建议开发阶段都写上,方便测试。
  • installable:能不能安装。设为False的话,模块列表里都看不到它。

避坑指南: 我曾经把一个模块的 version 写成 '1.0.0',结果升级时Odoo死活不认。后来发现Odoo的版本号格式是 主版本.次版本,比如 17.0.1.0.0。嗯,这个坑踩过一次就记住了。

2.3 模型(Model)与字段(Field)基础

模型就是数据库表,字段就是表的列。Odoo用Python类来定义模型,ORM会自动帮你建表。你想想看,这比直接写SQL爽多了吧?

# models/my_model.py
from odoo import models, fields, api

class LibraryBook(models.Model):
    _name = 'library.book'
    _description = '图书'
    _order = 'name'

    name = fields.Char(string='书名', required=True)
    author = fields.Char(string='作者')
    isbn = fields.Char(string='ISBN号', size=13)
    publication_date = fields.Date(string='出版日期')
    price = fields.Float(string='价格', digits=(10, 2))
    active = fields.Boolean(string='在库', default=True)
    category_id = fields.Many2one('library.category', string='分类')
    reader_ids = fields.Many2many('library.reader', string='借阅者')

这里有几个要点:

  • _name 是模型的唯一标识,用点号分隔。我习惯用 模块名.模型名 的命名方式,比如 library.book
  • _description 是模型的中文描述,会在界面显示。
  • _order 指定默认排序字段,不写的话按ID排序。

字段类型我列个表,方便你查阅:

字段类型 Python类型 说明
Char str 字符串,最长255字符
Text str 长文本,无长度限制
Integer int 整数
Float float 浮点数,可指定精度
Boolean bool 布尔值
Date date 日期
Datetime datetime 日期时间
Many2one int 多对一关系,存关联记录的ID
One2many list 一对多关系,存关联记录的ID列表
Many2many list 多对多关系,通过中间表关联

注意: 字段名不要用Python保留字,比如 typeidclass。我见过有人用 type 做字段名,结果ORM报错说语法错误——改个名就好了。

2.4 视图(View)基础

视图就是界面。Odoo的视图用XML定义,说白了就是告诉系统:这个字段放左边,那个按钮放上面。

先看一个最简单的树形视图(列表视图):

<!-- views/my_model_views.xml -->
<odoo>
    <record id="view_library_book_tree" model="ir.ui.view">
        <field name="name">library.book.tree</field>
        <field name="model">library.book</field>
        <field name="arch" type="xml">
            <tree string="图书列表">
                <field name="name"/>
                <field name="author"/>
                <field name="price"/>
                <field name="active"/>
            </tree>
        </field>
    </record>

    <record id="view_library_book_form" model="ir.ui.view">
        <field name="name">library.book.form</field>
        <field name="model">library.book</field>
        <field name="arch" type="xml">
            <form string="图书">
                <sheet>
                    <group>
                        <field name="name"/>
                        <field name="author"/>
                        <field name="isbn"/>
                        <field name="publication_date"/>
                        <field name="price"/>
                        <field name="category_id"/>
                    </group>
                </sheet>
            </form>
        </field>
    </record>
</odoo>

然后加上菜单,不然用户找不到入口:

<!-- views/menu_views.xml -->
<odoo>
    <menuitem id="menu_library_root" name="图书管理" sequence="10"/>
    
    <menuitem id="menu_library_book" name="图书列表"
              parent="menu_library_root"
              action="action_library_book"
              sequence="10"/>

    <record id="action_library_book" model="ir.actions.act_window">
        <field name="name">图书</field>
        <field name="res_model">library.book</field>
        <field name="view_mode">tree,form</field>
    </record>
</odoo>

个人经验: 我刚开始写视图时,经常忘记在 __manifest__.pydata 列表里加上XML文件。结果模块安装后,菜单都看不到。排查步骤很简单:先看模块有没有报错,再看视图有没有加载。嗯,现在我会在写XML的同时就把manifest更新了。

2.5 权限文件——别漏了它

没有权限文件,你的模型在界面上就是只读的。Odoo的权限用CSV文件定义:

# security/ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_library_book_user,library.book.user,model_library_book,base.group_user,1,1,1,1

这行CSV的意思是:给所有内部用户(base.group_user)对 library.book 模型的全部权限(读、写、创建、删除)。

我曾经踩过的坑: CSV文件的列名不能有空格,而且顺序必须严格按照 id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 来。有一次我手抖把 perm_read 写成了 perm read,中间多了个空格——Odoo直接忽略了这个权限,用户打开模块就报权限错误。

2.6 完整流程:从创建到安装

  1. addons 目录下创建模块文件夹,比如 library
  2. 创建 __init__.py__manifest__.py
  3. 创建 models/ 目录,写模型文件。
  4. 创建 views/ 目录,写视图和菜单。
  5. 创建 security/ 目录,写权限CSV。
  6. 重启Odoo服务,进入「应用」菜单,搜索你的模块,点击安装。

如果一切顺利,你就能在菜单里看到「图书管理」了。如果看不到——别急,先检查Odoo日志,大部分问题日志里都有提示。

好了,这一章的内容就这些。你跟着敲一遍,应该半小时就能跑起来。下一章我们会深入模型的关系字段,讲讲One2many和Many2many怎么用——那可是Odoo的精髓所在。