第17讲、odoo18可视化操作代码生成模块

1. 模块概述

代码框架生成模块是一个专为Odoo开发者设计的工具,旨在简化模块开发过程中的重复性工作。该模块允许开发者通过定义模型名称和字段,自动生成相应的Python代码、XML视图和CSV权限配置文件,从而大幅提高开发效率。通过这种方式,开发者可以将更多精力集中在业务逻辑实现上,而非基础代码框架的搭建。

该模块的核心功能是根据用户在前端界面中定义的模型名称和字段信息,自动生成三类关键文件:Python模型类定义文件、XML视图定义文件以及CSV权限配置文件。这些文件构成了Odoo模块开发的基础框架,为后续的功能开发奠定了坚实基础。

2. 模块架构

代码框架生成模块采用了标准的Odoo模块结构,主要包含以下目录和文件:

2.1 目录结构

模块的目录结构遵循Odoo标准模块布局,包括:

  • __init__.py:模块初始化文件,导入子模块
  • __manifest__.py:模块清单文件,定义模块的基本信息和依赖
  • models/:存放模型定义文件
    • __init__.py:模型包初始化文件
    • global_module_temp.py:核心模型定义文件,包含代码生成逻辑
  • views/:存放视图定义文件
    • global_module_temp_views.xml:模块主视图定义
  • security/:存放安全相关文件
    • ir.model.access.csv:模型访问权限配置
  • static/:存放静态资源文件
    • description/:模块描述相关资源
      • icon.png:模块图标
      • img.png:模块截图

2.2 核心模型

模块定义了三个核心模型,分别负责不同的功能:

  1. global.module.temp:模型创建模板,是整个模块的主要模型,负责存储模型定义和生成代码
  2. global.temp.field.line:字段列表,用于定义模型的字段信息
  3. temp.fields.selection:选项列表,用于定义selection类型字段的选项

这三个模型之间通过关联字段建立了层级关系,形成了完整的数据结构,用于存储模型定义和生成代码所需的全部信息。

3. 实现原理

代码框架生成模块的实现原理基于Odoo的模型-视图-控制器(MVC)架构,通过定义数据模型、构建用户界面和实现业务逻辑,实现了从模型定义到代码生成的完整流程。

3.1 数据模型设计

3.1.1 主模型 - GlobalModuleTemp

GlobalModuleTemp模型是整个模块的核心,定义了模型创建模板的基本结构和代码生成方法。该模型包含以下主要字段:

  • name:模型逻辑名,如'project.task'
  • class_name:模型类名,如'ProjectTask'
  • description:模型描述,用于生成视图标题和菜单名称
  • field_line_ids:字段列表,一对多关联到global.temp.field.line模型
  • py_binaryxml_binarycsv_binary:生成的代码文件二进制数据
  • py_logsxml_logscsv_logs:生成的代码文本内容

该模型还定义了三个关键方法:

  • action_auto_py():生成Python模型代码
  • action_auto_xml():生成XML视图代码
  • action_auto_csv():生成CSV权限配置代码
  • action_auto_all():调用上述三个方法,一次性生成所有代码

3.1.2 字段模型 - GlobalTempFieldLine

GlobalTempFieldLine模型用于定义模型的字段信息,包含以下主要字段:

  • order_id:关联到主模型global.module.temp
  • name:字段名称
  • field_description:字段描述,用于生成界面标签
  • type:字段类型,使用Odoo标准字段类型列表
  • model_id:关联模型,用于关系型字段(many2one, many2many, one2many)
  • relation_tablecolumn1column2:many2many关系表和列名
  • related_field:one2many关联字段
  • required:是否必填
  • readonly:是否只读
  • selection_ids:selection类型字段的选项列表

3.1.3 选项模型 - TempFieldSelection

TempFieldSelection模型用于定义selection类型字段的选项,包含以下主要字段:

  • field_id:关联到字段模型global.temp.field.line
  • name:选项逻辑值
  • value:选项显示值

3.2 用户界面设计

模块的用户界面通过XML视图定义,主要包括:

  1. 列表视图:显示已创建的模型模板列表
  2. 表单视图:用于创建和编辑模型模板,包含以下主要部分:
    • 基本信息区域:输入模型名称、类名和描述
    • 代码文件区域:显示生成的代码文件下载链接
    • 代码预览区域:显示生成的代码内容
    • 字段列表区域:管理模型字段定义

表单视图还包含一个"生成全部文件"按钮,点击后会调用action_auto_all()方法,生成所有代码文件。

3.3 代码生成逻辑

代码生成是该模块的核心功能,分为三个部分:

3.3.1 Python代码生成

Python代码生成通过action_auto_py()方法实现,主要步骤如下:

  1. 生成模型类定义头部,包括导入语句、类定义和基本属性
  2. 根据字段列表生成字段定义代码,针对不同类型的字段使用不同的模板
  3. 将生成的代码保存到模型的py_logs字段和py_binary字段

代码生成过程中使用了字段模板字典,为不同类型的字段定义了不同的代码模板,如:

field_template = {
    "many2one": "    {name} = fields.Many2one('{model}', string='{desc}', readonly={readonly}, required={required})",
    "char": "    {name} = fields.Char(string='{desc}', readonly={readonly}, required={required})",
    # 其他字段类型...
}

通过这种模板化的方式,可以根据字段类型和属性生成相应的字段定义代码。

3.3.2 XML代码生成

XML代码生成通过action_auto_xml()方法实现,主要步骤如下:

  1. 生成XML文件头部
  2. 生成列表视图定义,包括字段列表
  3. 生成表单视图定义,包括字段分组
  4. 生成动作定义,用于打开视图
  5. 生成菜单定义,用于导航
  6. 生成XML文件尾部
  7. 将生成的代码保存到模型的xml_logs字段和xml_binary字段

XML代码生成过程中,会根据模型名称和字段列表动态构建视图结构,确保生成的视图能够正确显示模型数据。

3.3.3 CSV代码生成

CSV代码生成通过action_auto_csv()方法实现,主要步骤如下:

  1. 构造CSV数据,包括权限记录的各个字段
  2. 使用Python的csv模块将数据格式化为CSV字符串
  3. 将生成的代码保存到模型的csv_logs字段和csv_binary字段

CSV代码生成相对简单,主要是创建一条默认的访问权限记录,允许普通用户对该模型进行读、写、创建和删除操作。

4. 使用流程

使用代码框架生成模块的典型流程如下:

4.1 创建模型模板

  1. 进入Odoo后台,点击"模型创建模版"菜单
  2. 点击"创建"按钮,新建一个模型模板
  3. 填写基本信息:
    • 模型类名:如"ProjectTask"
    • 模型逻辑名:如"project.task"
    • 模型描述:如"项目任务"
  4. 点击保存

4.2 定义模型字段

  1. 在模型模板表单的"字段列表"标签页中,点击"添加一行"
  2. 填写字段信息:
    • 字段名称:如"name"
    • 字段描述:如"名称"
    • 字段类型:从下拉列表中选择,如"char"
    • 根据字段类型,填写相应的附加信息:
      • 关系型字段:选择关联模型
      • many2many字段:填写关系表和列名
      • one2many字段:填写关联字段
      • selection字段:添加选项
    • 设置字段属性:必填、只读等
  3. 点击保存
  4. 重复上述步骤,添加更多字段

4.3 生成代码

  1. 完成字段定义后,点击表单顶部的"生成全部文件"按钮
  2. 系统会自动生成Python代码、XML视图代码和CSV权限代码
  3. 生成的代码会显示在表单的相应区域,可以直接查看
  4. 生成的代码文件可以通过表单中的下载链接下载

4.4 使用生成的代码

  1. 下载生成的代码文件
  2. 将文件放置到Odoo模块的相应目录中:
    • Python代码放入models目录
    • XML视图代码放入views目录
    • CSV权限代码放入security目录
  3. 更新模块清单文件__manifest__.py,添加新文件的路径
  4. 重启Odoo服务并更新模块

5. 技术特点

代码框架生成模块具有以下技术特点:

5.1 模板化代码生成

模块采用模板化的方式生成代码,为不同类型的字段和视图元素定义了相应的代码模板,通过填充模板参数生成最终代码。这种方式使代码生成过程更加灵活和可维护。

5.2 完整的字段类型支持

模块支持Odoo中所有标准字段类型,包括基本类型(char、integer、float等)和关系型字段(many2one、many2many、one2many),以及特殊类型(selection、html等)。对于每种字段类型,都提供了相应的参数配置选项。

5.3 二进制文件下载

模块将生成的代码保存为二进制文件,并提供下载链接,方便用户获取生成的代码文件。同时,也在界面上显示代码内容,便于用户预览和修改。

5.4 动态视图生成

模块根据模型定义动态生成XML视图代码,包括列表视图、表单视图、动作和菜单。生成的视图代码结构清晰,符合Odoo的标准视图定义格式。

5.5 权限配置生成

模块自动生成CSV权限配置文件,为新模型创建默认的访问权限记录,确保用户可以正常访问和操作该模型。

6. 完整实现代码

以下是模块的完整实现代码,包括所有关键文件:

6.1 模块清单文件 - __manifest__.py

# -*- coding: utf-8 -*-
{
    'name': '代码框架生成模块',
    'summary': """
           根据模型名称 生成代码,其中python代码中 生成 类名,xml生成
           表单视图,列表视图,动作 csv生成默认权限
           根据字段值控制form表单编辑与创建按钮
     """,
    'description': """
            根据模型名称 生成代码,其中python代码中 生成 类名,xml生成
            表单视图,列表视图,动作 csv生成默认权限
            根据字段值控制form表单编辑与创建按钮
    """,
    'author': 'melon',
    'website': "http://www.yourcompany.com",
    'license': 'LGPL-3',
    'version': '18.0',
    'category': 'Tools',
    'depends': ['base'],
    "images": ["static/description/img.png"],
    'data': [
        'security/ir.model.access.csv',
        'views/global_module_temp_views.xml'
    ],
    'assets': {
        'web.assets_backend': [
        ],
        'web.assets_qweb': [
        ],
    },
    'images': [
    ],
    'installable': True,
    'auto_install': False,
    'application': False,
}

6.2 模块初始化文件 - __init__.py

# -*- coding: utf-8 -*-
from . import models

6.3 模型包初始化文件 - models/__init__.py

from . import global_module_temp

6.4 核心模型定义文件 - models/global_module_temp.py

# -*- coding: utf-8 -*-
from odoo import models, fields, api
import base64
import csv
import io
import os
import lxml.etree as ET
import base64
import black

FIELD_TYPES = [(key, key) for key in sorted(fields.Field.by_type)]


class GlobalModuleTemp(models.Model):
    _name = "global.module.temp"
    _description = "模型创建模版"

    name = fields.Char('模型逻辑名', required=True)
    class_name = fields.Char('模型类名', required=True)
    description = fields.Char('模型描述', required=True)
    field_line_ids = fields.One2many('global.temp.field.line', 'order_id', string='字段列表')
    # py文件
    xml_binary = fields.Binary('XML代码文件', help="生成的XML代码文件", readonly=True)
    xml_binary_name = fields.Char('File Name')

    # xml文件
    py_binary = fields.Binary('Python代码文件', help="生成的Python代码文件", readonly=True)
    py_binary_name = fields.Char('File Name')

    # csv文件
    csv_binary = fields.Binary('csv代码文件', help="生成的csv代码文件", readonly=True)
    csv_binary_name = fields.Char('File Name')

    # 生成代码log
    py_logs = fields.Text('Python代码')
    xml_logs = fields.Text('XML代码')
    csv_logs = fields.Text('CSV代码')

    def action_auto_all(self):
        """调用三个函数"""
        self.action_auto_py()
        self.action_auto_xml()
        self.action_auto_csv()

    def action_auto_py(self):
        self.py_binary = False
        self.py_binary_name = False

        name_str = str(self.name).replace('.', '_')

        # **生成 Python 代码**
        all_data = f"""# -*- coding: utf-8 -*-
from odoo import models, fields

class {self.class_name}(models.Model):
    _name = '{self.name}'
    _description = '{self.description}'
"""

        field_template = {
            "many2one": "    {name} = fields.Many2one('{model}', string='{desc}', readonly={readonly}, required={required})",
            "many2many": "    {name} = fields.Many2many('{model}', '{relation}', '{col1}', '{col2}', string='{desc}', readonly={readonly}, required={required})",
            "one2many": "    {name} = fields.One2many('{model}', '{related_field}', string='{desc}', readonly={readonly}, required={required})",
            "char": "    {name} = fields.Char(string='{desc}', readonly={readonly}, required={required})",
            "integer": "    {name} = fields.Integer(string='{desc}', readonly={readonly}, required={required})",
            "float": "    {name} = fields.Float(string='{desc}', readonly={readonly}, required={required})",
            "boolean": "    {name} = fields.Boolean(string='{desc}', readonly={readonly}, required={required})",
            "selection": "    {name} = fields.Selection(selection={selection}, string='{desc}', readonly={readonly}, required={required})",
            "date": "    {name} = fields.Date(string='{desc}', readonly={readonly}, required={required})",
            "datetime": "    {name} = fields.Datetime(string='{desc}', readonly={readonly}, required={required})",
            "text": "    {name} = fields.Text(string='{desc}', readonly={readonly}, required={required})",
            "binary": "    {name} = fields.Binary(string='{desc}', readonly={readonly}, required={required})",
            "html": "    {name} = fields.Html(string='{desc}', readonly={readonly}, required={required})"
        }

        for line in self.field_line_ids:
            params = {
                "name": line.name,
                "desc": line.field_description,
                "readonly": line.readonly,
                "required": line.required
            }

            if line.type in field_template:
                if line.type == "selection":
                    params["selection"] = [(i.name, i.value) for i in line.selection_ids]
                elif line.type in ["many2one", "one2many"]:
                    params["model"] = line.model_id.model
                elif line.type == "many2many":
                    params.update({
                        "model": line.model_id.model,
                        "relation": line.relation_table,
                        "col1": line.column1,
                        "col2": line.column2
                    })
                elif line.type == "one2many":
                    params["related_field"] = line.related_field

                all_data += field_template[line.type].format(**params) + "\n"

        # **存储到 Odoo**
        self.py_logs = all_data
        self.py_binary_name = f"{name_str}.py"
        self.py_binary = base64.b64encode(all_data.encode('utf-8')).decode('utf-8')

    def action_auto_xml(self):
        self.xml_binary = False
        self.xml_binary_name = False

        name_str = str(self.name).replace('.', '_')

        head_data = """<?xml version="1.0" encoding="utf-8"?>
        <odoo>
        """
        fields_data = "".join([f'\n                <field name="{line.name}"/>' for line in self.field_line_ids])

        list_data = f"""
        <record id="{name_str}_list_view" model="ir.ui.view">
            <field name="name">{self.name}.list</field>
            <field name="model">{self.name}</field>
            <field name="arch" type="xml">
                <list string="{self.description}">{fields_data}
                </list>
            </field>
        </record>"""

        form_data = f"""
        <record id="{name_str}_form_view" model="ir.ui.view">
            <field name="name">{self.name}.form</field>
            <field name="model">{self.name}</field>
            <field name="arch" type="xml">
                <form string="{self.description}">
                    <sheet>
                        <group>{fields_data}
                        </group>
                    </sheet>
                </form>
            </field>
        </record>"""

        action_data = f"""
        <record id="action_{name_str}_view" model="ir.actions.act_window">
            <field name="name">{self.description}</field>
            <field name="res_model">{self.name}</field>
            <field name="view_mode">list,form</field>
        </record>"""

        menu_data = f"""
        <menuitem id="menu_{name_str}_root"
            name="{self.description}"
            sequence="1"
            action="action_{name_str}_view"/>"""

        foot_data = "</odoo>"

        all_data = head_data + list_data + form_data + action_data + menu_data + foot_data

        # **存储到 Odoo**
        self.xml_logs = all_data
        self.xml_binary_name = f"{name_str}_views.xml"
        self.xml_binary = base64.b64encode(all_data.encode('utf-8')).decode('utf-8')

    def action_auto_csv(self):
        self.csv_binary = False
        self.csv_binary_name = False
        name_str = self.name.replace('.', '_')

        # **构造 CSV 数据**
        csv_data = [
            ["id", "name", "model_id:id", "group_id:id", "perm_read", "perm_write", "perm_create", "perm_unlink"],
            [f"access_{name_str}", name_str, f"model_{name_str}", "base.group_user", 1, 1, 1, 1]
        ]

        # **格式化 CSV**
        csv_buffer = io.StringIO()
        csv_writer = csv.writer(csv_buffer, quoting=csv.QUOTE_MINIMAL)
        csv_writer.writerows(csv_data)
        all_data = csv_buffer.getvalue()

        # **存储到 Odoo**
        self.csv_logs = all_data
        self.csv_binary_name = "ir.model.access.csv"
        self.csv_binary = base64.b64encode(all_data.encode('utf-8')).decode('utf-8')


class GlobalTempFieldLine(models.Model):
    _name = "global.temp.field.line"
    _description = "字段列表"

    order_id = fields.Many2one('global.module.temp', string='模型创建模版')
    name = fields.Char('字段名称')
    field_description = fields.Char('字段描述')
    model_id = fields.Many2one('ir.model', string='关联模型')
    related_field = fields.Char(string='关联字段')
    help = fields.Text(string='字段帮助', translate=True)
    type = fields.Selection(selection=FIELD_TYPES, string='字段类型', required=True)
    relation_table = fields.Char('关系表')
    column1 = fields.Char(string='列1')
    column2 = fields.Char(string="列2")
    required = fields.Boolean('必填')
    readonly = fields.Boolean('只读')
    selection_ids = fields.One2many("temp.fields.selection", "field_id", string="选项")


class TempFieldSelection(models.Model):
    _name = "temp.fields.selection"
    _description = "Selection选项"

    field_id = fields.Many2one("global.temp.field.line", string='模型创建模版')
    name = fields.Char('逻辑值')
    value = fields.Char('显示值')

6.5 视图定义文件 - views/global_module_temp_views.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <record id="view_global_module_temp_list" model="ir.ui.view">
        <field name="name">模型创建模版</field>
        <field name="model">global.module.temp</field>
        <field name="arch" type="xml">
            <list>
                <field name="name"/>
                <field name="description"/>
            </list>
        </field>
    </record>

    <record id="view_global_module_temp_form" model="ir.ui.view">
        <field name="name">模型创建模版</field>
        <field name="model">global.module.temp</field>
        <field name="arch" type="xml">
            <form>
                <header>
                    <button name="action_auto_all" string="生成全部文件" type="object" class="oe_highlight"/>
                </header>
                <sheet>
                    <group>
                        <group>
                            <field name="class_name"/>
                            <field name="name"/>
                        </group>
                        <group>
                            <field name="description"/>
                        </group>
                    </group>
                    <group col="3" string="代码文件">
                        <group>
                            <field name="py_binary_name" invisible="1"/>
                            <field name="py_binary" filename="py_binary_name"/>
                        </group>
                        <group>
                            <field name="xml_binary_name" invisible="1"/>
                            <field name="xml_binary" filename="xml_binary_name"/>
                        </group>
                        <group>
                            <field name="csv_binary_name" invisible="1"/>
                            <field name="csv_binary" filename="csv_binary_name"/>
                        </group>
                    </group>
                    <group string="Python代码">
                        <field name="py_logs" nolabel="1" widget="code"/>
                    </group>
                    <group string="xml代码">
                        <field name="xml_logs" nolabel="1" widget="code"/>
                    </group>
                    <group string="csv代码">
                        <field name="csv_logs" nolabel="1" widget="code"/>
                    </group>
                    <notebook>
                        <page string="字段列表">
                            <field name="field_line_ids">
                                <form string="字段列表">
                                    <sheet>
                                        <group>
                                            <group>
                                                <field name="name" required="1"/>
                                                <field name="field_description" required="1"/>
                                                <field name="type" required="1"/>
                                                <field name="model_id"
                                                       invisible="type not in ['many2one', 'many2many', 'one2many']"/>
                                                <field name="relation_table" invisible="type != 'many2many'"/>
                                                <field name="column1" invisible="type != 'many2many'"/>
                                                <field name="column2" invisible="type != 'many2many'"/>
                                                <field name="related_field" invisible="type != 'one2many'"/>
                                            </group>
                                            <group>
                                                <field name="help"/>
                                                <field name="required"/>
                                                <field name="readonly"/>
                                            </group>
                                        </group>
                                        <notebook  invisible="type != 'selection'">
                                            <page string="选项">
                                                <field name="selection_ids">
                                                    <list editable="bottom">
                                                        <field name="name" required="1"/>
                                                        <field name="value" required="1"/>
                                                    </list>
                                                </field>
                                            </page>
                                        </notebook>
                                    </sheet>
                                </form>
                                <list>
                                    <field name="name" required="1"/>
                                    <field name="field_description" required="1"/>
                                    <field name="type" required="1"/>
                                    <field name="required"/>
                                    <field name="readonly"/>
                                </list>
                            </field>
                        </page>
                    </notebook>
                </sheet>
            </form>
        </field>
    </record>

    <record id="action_global_module_temp" model="ir.actions.act_window">
        <field name="name">模型创建模版</field>
        <field name="res_model">global.module.temp</field>
        <field name="view_mode">list,form</field>
    </record>

    <menuitem
            id="menu_global_module_temp_main"
            name="模型创建模版"
            web_icon="code_generator_template,static/description/icon.png"
            sequence="1"/>

    <menuitem
            id="menu_global_module_temp"
            name="模型创建模版"
            parent="menu_global_module_temp_main"
            action="action_global_module_temp"
            groups="base.group_user"/>
</odoo>

6.6 权限配置文件 - security/ir.model.access.csv

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_global_module_temp,global_module_temp,model_global_module_temp,base.group_user,1,1,1,1
access_global_temp_field_line,global_temp_field_line,model_global_temp_field_line,base.group_user,1,1,1,1
access_temp_fields_selection,temp_fields_selection,model_temp_fields_selection,base.group_user,1,1,1,1

6.7 README文件 - README.md

功能描述:代码框架生成模块
=================================================================================
字段:模型名称,xmld代码,py代码,csv代码按钮:生成代码
逻辑:根据模型名称 生成代码,其中python代码中 生成 类名,xml生成 表单视图,列表视图,动作 csv生成默认权限
=================================================================================

7. 扩展与优化建议

虽然当前版本的代码框架生成模块已经能够满足基本需求,但仍有一些可能的扩展和优化方向:

7.1 支持更多视图类型

当前版本仅支持生成列表视图和表单视图,可以扩展支持更多视图类型,如看板视图、日历视图、图表视图等,以满足不同场景的需求。

7.2 增强字段属性配置

可以增加更多字段属性的配置选项,如默认值、索引、翻译等,使生成的字段定义更加完整和灵活。

7.3 支持模型继承

Odoo中模型继承是一个重要特性,可以扩展模块支持模型继承的代码生成,包括继承已有模型和扩展已有字段。

7.4 代码格式化与优化

可以集成代码格式化工具(如black),对生成的代码进行格式化和优化,提高代码质量和可读性。

7.5 模块打包与导出

可以增加将生成的代码打包为完整Odoo模块的功能,包括自动生成__init__.py__manifest__.py等必要文件,并将整个模块打包为zip文件供用户下载。

8. 总结

代码框架生成模块是一个强大的开发工具,通过简化模型定义和代码生成过程,大幅提高了Odoo模块开发的效率。该模块采用模板化的代码生成方式,支持全面的字段类型和属性配置,生成结构清晰、符合标准的Python代码、XML视图和CSV权限配置。

通过使用该模块,开发者可以将更多精力集中在业务逻辑实现上,减少在基础代码框架搭建上的重复工作。同时,模块的设计也为后续扩展提供了良好的基础,可以根据需求不断增强其功能和适用范围。

总的来说,代码框架生成模块是Odoo开发者工具箱中的一个有价值的补充,能够显著提升开发效率和代码质量,特别适合需要频繁创建新模块的开发团队使用。

posted @ 2025-06-02 21:19  何双新  阅读(189)  评论(0)    收藏  举报