Packer.Odoo.10---Chapter 2

创建第一个odoo 应用

Odoo遵循传统的MVC模式。我们可以通过创建简单的To-Do 应用来具体介绍分析

  • model :定义了应用的数据结构
  • view: 描述了用户界面(可以理解为前端)
  • controller: 控制器,支持应用的具体业务逻辑

理解applications(应用)跟modules(模块)的区别:
  • Module addons :它是applications的基础,它能为Odoo添加新功能或者改变一个已经存在的模块,它包含了一个名为"mainfest.py"的字典文件,和一些能够实现新功能的文件
  • Applications : 它是将主要功能添加到Odoo的一种方式.例如Odoo中的Accounting or HR,依赖与之相对应对Applications,提供了非常重要的功能。它们在Odoo中的Apps菜单中高亮显示
    简而言之,当你的module十分复杂,为Odoo添加了新的或者非常重要的功能,你可能就需要把它创建为一个Application。当你的module只是为已经存在的Odoo模块增加小变动。就不需要作为一个Application

创建一个module基础架构
  • 我们把新的module放入在custom-addons文件夹中,新建todo_app文件夹,然后在该文件夹中创建manifest.py文件,由于todo_app是python包,需要新建init.py.
    mkdir -p custom-addons/todo_app
    cd custom-addons/todo_app
    touch __init__.py
    vim __manifest__.py
    manifest.py中添加如下代码
{
    'name': 'To-Do Application',  
    'description': 'Manage your personal task',
    'author': 'xer',
    'depends': ['base'],
    'application': True,
    'data': [ ],
}
  • 以上depends这个key对应了一个list用来存放所依赖的模块。例子中的‘base‘ 表示我们新建的todo_app在安装时候会自动把Odoo中的'base'一起安装.list中存放的都是模块的包名,就跟'todo_app'这样.
  • 其他manifest.py中的keys:
  1. summary:module的次要标题
  2. version: 代码module的版本号
  3. license:版权,默认LGPL-3
  4. website:设置一个URL用来查找模块相应的信息
  5. category:module的分类,默认为Uncategorized。目前已存在的categories可以在Settings| User | Groups中找到
  6. installable: 默认为True
  7. auto_install: 如果设置为True 这个模块会自动安装.

添加todo_app到addons path
  • 我们首选需要保证addons path中有我们刚才新建的todo_app的文件路径
    ./odoo-bin -d todo --addons-path='custom-addons,odoo/addons' --save
安装todo_app
  • Apps ==>Update Apps List 然后在搜索框搜索'todo_app' 点击安装即可
image.png
更新module
  • 当我们的Python 代码发生改变时,需要对Odoo进行重启, 因为odoo在启动时只对当前python代码读取一次
  • 当我们的date files更改时候(例如views中添加新的视图) 这时需要在Apps中对module进行upgrading操作.
  • 当python 代码跟data files同时改变时.要重启并upgrading module.
    在这里其实有个更好的方法:
  • 首先 Ctrl+c停止Odoo服务后,命令行输入odoo-bin -d todo -u todo_app
    这里运用了参数-u <需要更新的模块名(list)> 来指定需要更新的模块名.

服务端开发者模式
  • 在odoo10 中有一个新的参数在开启Odoo服务时可以运用 --dev=all
    这个参数有利于加快我们的开发:
  1. 自动的Reload python代码.
  2. 自动的读取xml files中的新定义.避免手动更新

model 层:

  • Models 描述了业务对象,例如 sales order, partner等,一个model 有许多属性(attributes) ,并能在其中定义特殊的业务逻辑。
  • Models 使用Python(目前仍为2.7)语言进行编写。使用了ORM模式可以对数据库直接进行操作。
  • 我们会在todo_app模块中新建一个'to-do tasks' model来对Models进行一步步的深入.这个task会有一个text field 用来描述task的详细情况,还有一个选择框来标记task是否被完成.最后会添加一个按钮(button)来清除那些旧的,已经被完成的tasks.
创建一个data 模块:
  • 根据Odoo的常规做法,我们需要一个models文件夹来存放用python编写的models.py文件。但在这个简单的例子中,我们不遵循这个规范,直接把todo_model.py放在todo_app文件夹中
  • 在todo_model.py 中添加相应的python代码:
# -*- coding: utf-8 -*-
from odoo import models, fields
class TodoTask(models.Model):
   _name = 'todo.task'
   _description = "To-do Task"
   name = fields.Char('Description', required=True)
   is_done = fields.Boolean('Done')
   active = fields.Boolean('Active?', default=True)
  • 注意点:
    1. 需要从odoo中导入models,fields 对象
    2. 创建一个TodoTask类,该类继承自models.Model
    3. _name属性:用来定义我们新添加的这个类的名字.可以看做是当Odoo需要关联TodoTask类时的标识符。
    4. _description属性:这个属性不是必须的。用来描models的记录。
    5. 最后三行内容具体描述了我们的model的具体字段,其中nameactive是特殊字段。
      name:用作于记录的名称。(在与其他models有关联关系时候,如Many2one,Many2many时显示在关联models上的名称。)。
      active:只有当记录中的active字段为true时,该记录才能在页面视图中显示。
      最后,在init.py中添加如下代码
      from . import todo_model
      整体结构图
      --custom-addons
      ----todo_app
      -------manifest.py
      -------init.py
      -------todo_models.py
      • 这里扩展下,'_rec_name'属性:默认关联显示名称为name对应的fields.但'_rec_name'属性可以根据需要把要显示在关联models上的名字定义为想要的fields的名字.

重启Odoo服务,由于我们还没定义菜单,需要进入Settings | Technical | Database Structure | Models.使用'todo.task'来搜索我们刚才定义的model.点击查询结果来查看.

image.png
  • Odoo会在创建model数据表时自动加入4个字段
    1. id: 可认为是每个model record的主键
    2. create_date 和create_uid :什么时候创建,哪个用户创建
    3. write_date and write_uid: 确认最后的修改时间和修改的用户
    4. __last_update:没有实际存贮在数据库中,使用在并发检查上

添加自动测试

Odoo有2种测试方法:

  1. YAML
  2. Python的测试类(Unittest2).
下面主要介绍第二种测试方法:
  • test代码名字需要用'test_'作为开头,并且需要在tests/init.py导入.但是tests文件夹不需要在modules中的init.py导入.因为Odoo会自己搜索测试代码.
  • 在todo_app文件夹中新建tests文件夹,在tests中新建init.py.
  • init.py中添加代码:
    from . import test_todo
  • 创建test_todo.py文件,添加代码
from odoo.tests.common import TransactionCase
class TestTodo(TransactionCase):
    def test_create(self):
        'Create a simple Todo'
        Todo = self.env['todo.task']
        task = Todo.create({'name': 'Test Task'})
        self.assertEqual(task.is_done, False)
  • 运行测试代码
    ./odoo-bin -d todo -i todo_app --test-enable

view层

  • view可以认为是用户跟Odoo内部数据直接进行联系的一个交互界面(通过html前端页面来展示数据,通过form表单来提交用户输入数据到Odoo数据库)
  • Odoo中的Views是通过xml来编写的。Odoo中的xml 文件一般都存放在views这个子文件夹中。我们首先来进行对menu items进行编写。(menu可以看做是一个动作,点击后能渲染视图在前端显示)
添加 menu 主题
  • 既然我们已经有todo_models来存放我们的数据,那现在我们就需要让用户能够通过menu来与之交互.
  • 创建'views/todo_menu.xml' 来定义menu.添加如下xml 代码
<?xml version="1.0"?>
<odoo>
   <act_window id="action_todo_task"
               name="To-do Task"
               res_model="todo.task"
               view_mode="tree,form"/>
 <!--menuitem -->
   <menuitem id="menu_todo_task"
             name="Todos"
             action="action_todo_task"/>
</odoo>
  • The user interface,包括menu 跟actions 都是存储在数据库表中的。而我们的xml 文件可以看做用来把这些数据库中的表展示出来在用户界面上体现。前面的代码描述添加到Odoo中的2条records。
    • <act_window> :定义了客户端的窗口动作,会通过tree视图跟form视图来打开我的定义的todo.task
    • <menuitem>:定义了顶部的(menu)菜单栏,该菜单栏绑定了一个名为'action_todo_task'的action.
  • 所有的元素中包含id 这个属性, 这个id属性十分重要,被称为XML ID:它用于唯一标识模块内部的每个数据元素,并且能够被其他元素所引用到.在我们的例子中,<menuitem>元素需要与action联系起来,所以需要把<act_window>的id关联到<menuitem>的action中
  • 添加xml文件到'manifest.py':
    'data' : ['views/todo_menu.xml'],
    更新todo_app模块,发现我们的菜单栏中已经有了'Todos'这个菜单选项
    image.png

提升我们的视图
添加一个form视图
  • 所有的视图都存储在数据库中,在'ir.ui.view'这个model中.添加一个view到module,我们在XML文件中声明<record>这个元素,当模块被安装后这个<record>中的数据就被加载进数据库中。
  • 添加views/todo_view.xml文件,添加如下代码
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
    <!--<form view>-->
    <record id="view_form_todo_task" model="ir.ui.view">
        <field name="name">To-do Task Form</field>
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <form string="To-do Task">
          <group>
                    <field name="name"/>
                    <field name="is_done"/>
                    <field name="active" readonly="1"/>
                    </group>
            </form>
        </field>
    </record>
</odoo>
  • 之后别忘记添加到'manifest.py'中的'data'中.这个'todo_view.xml'文件在'ir.ui.view' model中添加了一条标识为'view_form_todo_task'的记录.
    注意:这边的话其实添加的所谓标识是添加在'ir.model.data' 表中作为一个外部ID来与'ir.ui.view'进行mapping关系的建立.而不是直接添加到'ir.ui.view'表中

  • 可以通过psql查询数据库
    select name from ir_ui_view where name='To-do Task Form' 来得到.(注意,在psql中,所有Odoo的数据表名称的.都用_代替了.因为'view_form_todo_task'是添加在'ir.model.data'中,所以查询'ir_ui_view'无法得到外部ID).

  • 在上述<record>代码中,最为重要的属性是'arch'.它包含了需要的视图,例子中就定义了视图为<form>.

  • 接下来的三段定义是把我们todo_models中的fields呈现在form视图中.其中的'active'字段属性我们设置为了只读


业务文档form视图
  • 对于文档模型,Odoo有一个专门的模仿成一页纸形的展示风格。这个视图包含了2个元素:
    • <header> :可以在其中添加<button>按钮
    • <sheet>: (让form好看一点)
    image.png
添加动作按钮(action buttons)
  • form表单中可以定义buttons来执行相应的actions,这些buttons可以能够打开一个新的窗口或者运行一个定义在models中的python方法.
  • buttons能定义在form中任何地方, 但是对于文档格式的forms来说,建议存放在<header>中
  • 对于我们的todo_app应用,我们添加2个buttons来运行我们定义在'todo.task' model中的2个方法
 <header>
       <button name="do_toggle_done" type="object" string="Toggle Done" class="oe_highlight"/>
        <button name="do_clear_done" type="object"  string="Clear All Done"/>
</header>
  • 我们定义的buttons包含了下面4个属性
    1.string:显示为button在页面上的名字
    2.type : 动作的类型(这里的object可以理解为调用了我们todo_model.py中的TodoTask这个类创建的object)
    3.name: action的标识符(我们代码中的name可以理解为在TodoTask中定义的do_toggle_done方法.
    4.class:这是一个可选选项来运用CSS样式.(我们的oe_highlight表示为把这个button设置为高亮显示)

使用<group>来组织form视图
  • <group> 标签可以让我们把form中的内容组织起来。在一个<group>中在放入<group>,就像<group><group></group></group>这样,能够在外部的<group>的内容中增加两个纵列(其实可以这样理解,一个<group>就是把我们field的字段分为左右两列,左边是field的名字,右边是field要输入的.)
image.png
  • 我们建议在<group>元素中添加一个name属性以便以后更好的扩展我们的<group>
     <sheet>
              <group name="top">
                      <group name="left">
                      <field name="name"/>
                  </group>
                    <group name="right">
                     <field name="is_done"/>
                     <field name="active" readonly="1"/>
                 </group>
              </group>
        </sheet>
    

最后,我们的form视图代码如下:

            <form string="To-do Task">
                <header>
                    <button name="do_toggle_done" type="object"
                            string="Toggle Done" class="oe_highlight"/>
                    <button name="do_clear_done" type="object"
                            string="Clear All Done"/>
                </header>
                <sheet>
                <group name="top">
                    <group name="left">
                    <field name="name"/>
                </group>
                    <group name="right">
                        <field name="is_done"/>
                        <field name="active" readonly="1"/>
                    </group>
                </group>
                </sheet>>
            </form>

展示效果:

image.png

添加列举(list)跟搜索(search)视图
  • 当需要列举一个model中的数据时,我们就需要使用<tree>视图。Tree视图能够展示结构化的层级关系,(如linux下的tree命令)在Odoo中,我们通常使用Tree视图来展示清晰的数据列表。
  • 我们在todo_view.xml中添加如下的tree视图定义代码:
    <!--tree view-->
    <record id="view_tree_todo_task" model="ir.ui.view">
        <field name="name">To-do Task tree</field>
        <field name="model">todo.task</field>
        <field name="arch" type="xml">
            <tree colors="decoration-muted:is_done==True">
                <field name="name"/>
                <field name="is_done"/>
            </tree>
        </field>
    </record>
  • 以上的定义主要在列举数据时只显示两列nameis_done.我们在这里设置了一个bootstrap的CSS样式,当task的记录中is_doneTrue时,记录显示为灰色.(需要安装Odoo的'website'模块才能使用bootstrap).
  • Odoo的右上角有一个搜索框,我们可以定义一个<search>视图来为这个搜索框添加可选的过滤条件。
  • 下面构造我们的<search> 视图代码:
    <!--search view-->
    <record id="view_filter_todo_task" model="ir.ui.view">
    <field name="name">To-do Task Filter</field>
    <field name="model">todo.task</field>
    <field name="arch" type="xml">
        <search>
            <field name="name"/>
            <filter string="Not Done"
                    domain="[('is_done','=',False)]"/>
            <filter string="Done"
                    domain="[('is_done','!=',False)]"/>
        </search>
    </field>
    </record>
  • 可以看到在<search>标签中,可以定义<filter>这个标签,使用domain属性来实现过滤条件.

逻辑层

  • 现在我们需要添加业务逻辑来实现我们的buttons:使用python在我们的todo_models.py中编写与业务相关的python methods.
添加业务逻辑
  • 编辑我们在models文件夹中的'todo_model.py'文件.首先,我们需要import 新的API
    from odoo import models, fields, api
  • 我们的Toggle Donebutton逻辑十分简单,就是用来转换我们创立的task中的'Is_Done'这个flag.我们使用@api.multi这个装饰器来表示对多条records的逻辑修改.self代表一个recordset.我们可以通过遍历self来达到遍历recordset从来得到需要的每条record.
  • TodoTask 类中,添加如下python代码:
@api.multi
def do_toggle_done(self):
      for task in self:
            task.is_done = not task.is_done
      return True
  • 上面的代码实现的逻辑是:遍历所有的'to-do task' 记录,然后修改每条记录的'is_done' field, 反转它的值.在最后我们return True是因为Odoo客户端使用XML-RPC来调用我们的'do_toggle_done'方法,而这个协议不支持客户端方法返回None值。
  • Clear All Done方法: 找到所有'is_done'的值为True的记录,把它们的active属性设置为False以达到让该条记录不在页面显示的功能.通常来说,放在form视图中的button都是用来操作当前选中的记录,在我们的这个例子中,我们想要让这个button能够影响所有的records.
@api.model
 def do_clear_done(self):
        dones = self.search([('is_done', '=',True)])
        dones.write({'active': False})
        return True
  • 上面的代码中. @api.model装饰器装饰的方法中,self变量代表了当前的model,我们把所有is_done = True的recordset存放在变量dones中.然后再把它们的active 字段设置为False.
  • search方法是Odoo提供的一个API方法,返回符合条件的records. 这些条件使用Odoo的domain规则.是一个tuple组成的list.
  • write方法能够对recordset直接使用.传入的参数是一个'dict'.
添加测试
  • 我们需要为我们的业务逻辑添加测试,在我们以前编写的测试类'tests/test_todo.py' ,添加如下测试方法test_create()方法:
 def test_create(self):
        # Test Toggle Done
        task.do_toggle_done()
        self.assertTrue(task.is_done)
        # Test Clear Done
        Todo.do_clear_done()
        self.assertFalse(task.active)

使用./odoo-bin -d todo -i todo_app --test-enable

设置模块访问权限(access security)

当我们加载我们的模块时,记录中会有一条warning message:

The model todo.task has no access rules, consider adding one.

  • 这条信息说明了我们的todo_app模块没有设置访问权限规则,除了超级管理员以外其他的用户不能使用我们的模块.
  • 另一个问题是我们需要让不同的用户拥有自己私有的to-do tasks.
测试访问权限
  • 实际上,我们在前面编写的测试代码不应该成功,但我们的管理员(admin)用户身份导致了测试类完整运行。现在,我们使用Demo来代替我们的admin用户。
  • 编辑'tests/test_todo.py'文件,我们新添加set Up这个方法:
    def setUp(self,*args,**kwargs):
        result = super(TestTodo, self).setUp(*args, **kwargs)
        user_demo = self.env.ref('base.user_demo')
        self.env = self.env(user=user_demo)
        return result
  • 函数定义的第一行,我们调用了父类中的set Up方法.接下来就是对调用测试方法的用户的更改,上述代码中,我们改变了测试环境,把demo用户代替了admin
  • 接下来,我们在测试类中导入了断言异常的处理函数.
    from odoo.exceptions import AccessError
  • 在test 类中添加一个新的测试方法:
    def test_record_rule(self):
        'Test per user record rules'
        Todo = self.env['todo.task']
        task = Todo.sudo().create({'name': 'Admin Task'})
        with self.assertRaises(AccessError):
            Todo.browse([task.id]).name
  1. 因为我们现在的env 已经使用Demo用户,我们使用sudo()方法(可以理解为linux下的sudo)来切换到admin的上下文环境创立一个name为Admin Task的task。这么做的目的是创建一个不能被Demo所获取的to-do task.
  2. 当我们尝试使用Demo用户去获取上文创建的task时,会有一个AccessError异常被抛出.
  • 注意:
    此时,当我们尝试运行刚才编写的test时,会发生错误,因为我们还没有设置权限设置.

添加访问控制

访问Settings | Technical | Security| Access Controls List:

image.png

这些信息是有模块提供然后加载到odoo中的'ir.model.access'模块,接下来,我们就在我们的model中添加所有权限给员工(employee)这个分组.注:员工这个分组是非常普通的.

  • 我们可以通过使用CSV文件来设置权限.新建'security/ir.model.access.csv'文件,添加如下内容
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_todo_task_group_user,todo.task.user,model_todo_task,base.group_user,1,1,1,1

CSV文件中的列名:
我们定义的CSV文件中,第一行的那些名字可以看做是书写的参照规则:

  • id :外部标识,在我们的模块中应该是独一无二的
  • name :是一个描述标题,作为一个信息的展示,最好能保持唯一性.Odoo官方模块通常使用modelname.group来进行取名.我们的todo_app中,使用了'todo.task.user'
  • model_id: 这是我们要赋予权限的model的外部标识,通常来说,Odoo会通过ORM对models使用自动的取名.对于我们的todo.task来说,这个外面id是'model_todo_task'.
  • group_id: 标识了需要赋予权限的用户组.Odoo中重要的用户组一般都是base模块提供的,而我们需要的Employee的id为base.group_user
  • perm: 4个,'read','write','create','unlink'.分别代表了对读,写,创建,删除的4个操作的允许与否.
    创建了security/ir.model.access.csv文件后还需要把其路径导入到manifest.py的'data'列表中
    'data':[ 'security/ir.model.access.csv', ... ],
  • 现在,更新我们的模块,warning message就会消失了,我们登录Demo账户也能发现能够访问我们的todo_app模块。运行测试类时只有'test_record_rule'方法仍会失败

Row-level的访问规则

Setting | Technical | Record Rules
记录规则被定义在'ir.rule' model中,跟往常一样,我们需要提供一个特殊的名字,记录规则作用在的具体的model以及需要定义的domain过滤规则来进行权限约束。

  • 通常,规则被适用于特殊的安全用户组,在我们的例子中,我们还是会使用员工(Employees)用户组,如果不定义规则要适用的用户组,Odoo就会认为是一个全局规则(Global rules).全局规则是特殊的存在因为它们无法被普通的规则覆盖。
  • 为了添加记录规则,我们创建'security/todo_access_rules.xml'文件,添加如下代码
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
    <data noupdate="1">
        <record id="todo_task_user_rule" model="ir.rule">
            <field name="name">ToDo_rule</field>
            <field name="model_id" ref="model_todo_task"/>
            <field name="domain_force">[('create_uid','=',user.id)]</field>
            <field name="groups" eval="[(4,ref('base.group_user'))]"/>
        </record>
    </data>
</odoo>
  • 上面代码中,注意noupdate='1'属性,这个属性意味着当模块被升级时,我们的data并不会发生改变.通常在开发时我们需要多次调整数据,所以可以把它的值设为'0'.
  • 在<groups>字段中,我们发现有一个特殊的表达式,这是一个一对多的关系字段,在我们的例子中(4,x)这个tuple 表明把x添加到记录中,在这里,x代表了员工用户组,验证id为 base.group_user.
  • 最后别忘记把我们刚新建的xml文件放入manifest.py的'data'中.
  • 下面,我们来运行我们编写的测试类,可以发现全部
    测试通过.
添加一个module的图标
  • 添加一个icon图标让我们的module看起来好看点.
    把它放入module目录下的static/description目录中即可.
    在todo_app目录路径下,简单的几句linux代码:
    mkdir -p static/description
    cp 我们自己的icon图标 static/description
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 继承-扩展原有的应用 Odoo中,有一个非常重要的特色,不用直接修改底层对象就能为我们的模块添加新的功能。这个特色...
    F4A3阅读 636评论 0 1
  • 结构化应用数据 在前面的章节中,我们基本接触了建造Odoo后台应用的所有层面。下面我们就要具体分析‘model,v...
    F4A3阅读 524评论 0 0
  • 视图层-设计用户界面 这一章节我们会学习用来构建用户界面的视图层。 视图跟控件 context跟domian 使用...
    F4A3阅读 928评论 0 3
  • ORM应用逻辑-业务处理 前面的章节我们学习了利于Odoo的视图来构建用户前端界面。本章介绍Odoo的后台业务逻辑...
    F4A3阅读 774评论 0 3
  • 文|你这磨人的小妖精 有时候朋友之于我们,在生活中比爱情还重要。 001/// 昨天下午,朋友猫儿打来电话,我眼睛...
    小妖的幺阅读 1,671评论 5 17