开发一个模块 zerone_books

创建自定义模块

在自定义文件目录 custom_addons中创建 zerone_books模块

1
python odoo-bin scaffold zerone_books custom_addons

建立数据模型

新建编辑 zerone_book.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from odoo import api, fields, models, _


class ZeroneBook(models.Model):
    _name = "zerone.book"
    _description = "Zerone Books"
    _order = "code desc"

    _inherit = ['image.mixin']

    image_1920 = fields.Image(string="图书图片")
    name = fields.Char(string="图书名称", required=True)
    code = fields.Char(string="图书番号", copy=False, help="图书的管理番号")
    isbn = fields.Char(string="ISBN", copy=False)
    author = fields.Char(string="作者")
    pages = fields.Integer(string="字数")
    publish_date = fields.Date(string="出版日期")
    publisher = fields.Char(string="出版社")
    price = fields.Float(string="定价", digits=(7, 2))
    description = fields.Text(string="概要", help="""本图书的概要説明""")
    binding_type = fields.Selection(
        [("common", "普通"), ("hardcover", "硬封套")],
        string="图书形式", index=True, default='common'
    )
    e_link = fields.Html(string="电子版号")
    borrowed = fields.Boolean(string="货出中", default=False)
    date_last_borrowed = fields.Datetime("最后的货出时刻", index=True)

    shelf_id = fields.Many2one('zerone.shelf', string='书架')
    tags_ids = fields.Many2many("zerone.tags", string="标签")

    @api.depends('isbn', 'name')
    def name_get(self):
        result = []
        for book in self:
            result.append((book.id, '%s(%s)' % (book.name, book.isbn)))
        return result

    def action_borrow(self):
        self.borrowed = True
        self.date_last_borrowed = fields.Datetime.now()

    def action_return(self):
        self.borrowed = False
        self.date_last_borrowed = None

新建编辑 zerone_shelf.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from odoo import api, fields, models, _


class ZeroneShelf(models.Model):
    _name = "zerone.shelf"
    _description = "Zerone Shelf"

    name = fields.Char("书架名称")
    capacity = fields.Integer("书架容量")
    capacity_rate = fields.Float(string="图书放置比", compute="_compute_rate")

    book_ids = fields.One2many('zerone.book', 'shelf_id', string="图书")

    @api.depends('capacity', 'book_ids')
    def _compute_rate(self):
        for record in self:
            if not record.capacity:
                record.capacity_rate = 0.0
            else:
                record.capacity_rate = 100.0 * len(record.book_ids) / record.capacity

新建编辑 zerone_tags.py

1
2
3
4
5
6
7
8
from odoo import api, fields, models, _


class ZeroneTags(models.Model):
    _name = "zerone.tags"
    _description = "Zerone Tags"

    name = fields.Char("标签名称", size=10)

配置model目录下init.py文件

1
2
3
4
5
# -*- coding: utf-8 -*-

from . import zerone_book
from . import zerone_shelf
from . import zerone_tags

构建视图

新建编辑 zerone_books.xml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data>
        <!--Menu-->
        <!-- Top Menu Item -->
        <menuitem name="Books" id="menu_books_root" sequence="50" groups="base.group_user"
                  web_icon="zerone_books,static/description/icon.png"/>
        <!-- Middle Menu -->
        <menuitem id="menu_books" name='图书' parent="menu_books_root" sequence="15"/>
        <!-- Child Menu 子菜单名称 -->
        <menuitem action="action_zerone_books"
                  id="menu_zerone_books"
                  name="图书管理"
                  parent="menu_books"
                  sequence="20"
        />

        <!--Search View-->
        <record id="view_zerone_books_search" model="ir.ui.view">
            <field name="name">zerone.book.search</field>
            <field name="model">zerone.book</field>
            <field name="arch" type="xml">
<!--                ===========================-->
                <search string="Books">
                <!-- 在搜索栏中输入文字后会出现的候补的自定义 -->
                    <field name="code"/>
                    <field name="name"/>
                    <field name="author"/>
                <!-- 作为候选添加到分组的下拉菜单中的内容 -->
                    <group expand="1" string="Group By">
                        <filter string="作者" name="author_group" context="{'group_by':'author'}"/>
                <!--name="name_group"过滤器的名称。如果和上面field的name名重合的话,显示可能会出现错误,所以必须避免。-->
                        <filter string="图书名" name="name_group" context="{'group_by':'name'}"/>
                    </group>
                </search>
<!--                ===============================-->
            </field>
        </record>
        <!--Tree View-->
        <record id="view_zerone_books_tree" model="ir.ui.view">
            <field name="name">zerone.book.tree</field>
            <field name="model">zerone.book</field>
            <field name="arch" type="xml">
                <tree string="Books">
                <!-- editable='bottom'的属性添加到tree标签上后tree的列表就可以直接编辑,但是无法打开窗体 -->
                    <field name="code"/>
                    <field name="name"/>
                    <field name="author"/>
                    <field name="isbn"/>
                    <field name="pages"/>
                    <field name="publish_date"/>
                    <field name="publisher"/>
                    <field name="price"/>
                    <field name="binding_type"/>
                    <field name="borrowed"/>
                    <field name="date_last_borrowed"/>
                </tree>
            </field>
        </record>
        <!--Form View-->
        <record id="view_zerone_books_form" model="ir.ui.view">
            <field name="name">zerone.book.form</field>
            <field name="model">zerone.book</field>
            <field name="arch" type="xml">
                <form string="Books">
                    <header>
                    <!-- 设置按钮。如果borrowed用True或False显示不同的按钮→按钮被按下则变更borrowed的值 -->
                        <button name="action_borrow" type="object" string="货出" class="btn-primary"
                                attrs="{'invisible': [('borrowed', '=', True)]}"/>
                        <button name="action_return" type="object" string="返却" class="btn-success"
                                attrs="{'invisible': [('borrowed', '=', False)]}"/>
                    </header>
                    <sheet string="5Books">
                        <group col="4">
                            <field name="image_1920"/>
                            <field name="name"/>
                            <field name="code"/>
                            <field name="author"/>
                            <field name="isbn"/>
                            <field name="pages"/>
                            <field name="publish_date"/>
                            <field name="publisher"/>
                            <field name="price"/>
                            <field name="binding_type"/>
                            <field name="borrowed"/>
                            <field name="date_last_borrowed"/>
                            <field name="description" colspan="4"/>
                            <field name='e_link' colspan="4"/>
                        </group>
                    </sheet>
                </form>
            </field>
        </record>
                    <!-- kanban view -->
        <record id="view_zerone_books_kanban" model="ir.ui.view">
            <field name="name">Books Kanban</field>
            <field name="model">zerone.book</field>
            <field name="arch" type="xml">
                <kanban create="false" class="o_modules_kanban">
                <!-- kanban视图中声明要使用的字段 -->
                    <field name="id"/>
                    <field name="image_1920"/>
                    <field name="name"/>
                    <field name="code"/>
                    <field name="isbn"/>
                    <field name="author"/>
                    <field name="publisher"/>
                    <field name="publish_date"/>
                    <field name="borrowed"/>
                    <templates>
                        <t t-name="kanban-box">
                            <div class="oe_module_vignette">
                                <t t-set="v_borrowed" t-value="record.borrowed.raw_value == true"/>

                                <!-- 部分 可展开的菜单 -->
                                <div class="o_dropdown_kanban dropdown" tabindex="-1">
                                    <a class="dropdown-toggle o-no-caret btn" data-toggle="dropdown"
                                    data-display="static" href="#" role="button" aria-label="Dropdown menu"
                                    title="Dropdown menu">
                                        <span class="fa fa-ellipsis-v"/>
                                    </a>
                                    <div class="dropdown-menu" role="menu" aria-labelledby="dLabel">
                                        <!-- type="edit" 当存在type属性,且值为edit时,点击这个下拉项,会进入表单编辑状态 -->
                                        <a type="edit" class="dropdown-item">编辑</a>
                                        <!-- type="delete" 当存在type属性,且值为delete时,点击这个下拉项,会提示是否删除这条记录 -->
                                        <a type="delete" class="dropdown-item">删除</a>
                                        <!-- 调用模型后台方法 调用后台方法action_return,设为未被借阅 这里使用的自定义的方式所以type='object'-->
                                        <a t-if="v_borrowed == true" name="action_return" type="object"
                                        role="menuitem" class="dropdown-item">设为未被借阅
                                        </a>
                                        <!-- 调用模型后台方法 调用后台方法action_borrow,设为被借阅 -->
                                        <a t-if="v_borrowed == false" name="action_borrow" type="object"
                                        role="menuitem" class="dropdown-item">设为已被借阅
                                        </a>
                                    </div>
                                </div>

                                <!-- 部分 kanban的内容 -->
                                <!-- 行眼睛图像的设置:t-att-src指定图像的位置 -->
                                <img role="img" t-att-alt="record.name.value" t-att-title="record.name.raw_value"
                                    style="float:left;max-height:127px;"
                                    t-att-src="kanban_image('zerone.book', 'image_128', record.id.raw_value)"/>
                                <div class="oe_module_desc" style="float:left;padding-left:15px;">
                                    <h4 class="oe_module_name" style="font-size:13px;" >
                                        <field name="name"/>
                                        <code>
                                            <field name="author"/>
                                        </code>
                                    </h4>
                                    <p class="oe_isbn">
                                        <field name="isbn"/>
                                    </p>
                                    <p class="oe_publish_info">
                                        <field name="publisher"/>
                                        <field name="publish_date"/>
                                    </p>
                                    <div class="oe_module_action">
                                        <!-- 调用后台方法展示形式2 -->
                                        <button type="object" class="btn btn-primary btn-sm"
                                                name="action_return"
                                                t-if="record.borrowed.raw_value == true">设为未被借阅
                                        </button>
                                        <button type="object" class="btn btn-primary btn-sm"
                                                name="action_borrow"
                                                t-if="record.borrowed.raw_value == false">设为已被借阅
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </t>
                    </templates>
                </kanban>
            </field>
        </record>
        <!--action-->
        <record id="action_zerone_books" model="ir.actions.act_window">
            <!-- 视图中的名称 -->
            <field name="name">图书管理(action 视图中的显示)</field>
            <!-- 这是橱窗里的动作 -->
            <field name="type">ir.actions.act_window</field>
            <!-- 和模型联系起来 -->
            <field name="res_model">zerone.book</field>
            <!-- 此动作定义了显示什么样的视图 -->
            <field name="view_mode">tree,form,kanban</field>
            <!-- 这个域设置了通过对最初显示内容进行过滤来限定显示的功能 -->
            <!-- 例:<field name="domain">[('author','=','金庸')]</field> -->
            <!-- <field name="domain">[('author','=','金庸')]</field> -->
            <field name="domain">[]</field>
            <!-- 这里是search栏的地方关于从最初开始就设定了的group化的默认设定 -->
            <!-- 这里的命名方法很特殊,从search default search default-开始,在后面放入检索视图的某filter的name -->
            <field name="context">{}</field>
            <!-- 'search_default_author_group': 1, 'search_default_name_group':1 输入到上述context -->
            <!-- 这里的ref必须加入search view的id -->
            <field name="search_view_id" ref="view_zerone_books_search"/>
        </record>
    </data>
</odoo>

项目配置

编辑 __manifest__.py

1
2
3
4
5
6
7
8
'data': [
    'security/ir.model.access.csv',
    'views/views.xml',
    'views/templates.xml',
    'views/zerone_books.xml'
],
'application': True,  # 将模块变成一个应用,可以在应用中搜索到
'sequence': 1,  # 将应用/模块排在所有应用/模块的第一位

权限配置

编辑 ir.model.access.csv 文件

1
2
3
4
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_zreone_book_user,zerone_book_user,model_zerone_book,base.group_user,1,1,1,1
access_zerone_shelf_user,zerone_shelf_user,model_zerone_shelf,base.group_user,1,1,1,1
access_zerone_tags_user,zerone_tags_user,model_zerone_tags,base.group_user,1,1,1,1
  • id:权限id,默认规则是access_模块名_类名
  • name:权限名,默认规则是模块名_类名
  • model_id:id:固定写法,规则是model_模块名,其它地方引用权限会用这个id
  • group_id:id:组id,这里的base.group_user是系统内置组,即员工.员工组,创建帐户时,默认属于这个组。所以给这个组赋权限,相当于给新帐户的默认权限。
  • perm_read,perm_write,perm_create,perm_unlink:对应读、写、增加、删除权限,1是有权限,0是无权限,具体根据需要来设置权限