第5章:最后,一些UI可以玩
现在我们已经创建了新模型及其相应的访问权限,现在是时候与用户界面交互。
在本章的结尾,我们将创建几个菜单以访问默认列表和表单视图。
在第4章:安全性-简介中,我们通过CSV文件添加了数据。在要加载的数据格式简单时CSV很方便。当格式更复杂时(例如,加载视图或电子邮件模板的结构),我们使用XML格式。例如 此帮助字段包含 HTML 标记。虽然可以通过 CSV 文件加载此类数据,但它使用 XML文件比CSV文件方便。
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Define a new lost reason
</p><p>
Use lost reasons to explain why an opportunity is lost.
</p><p>
Some examples of lost reasons: "We don't have people/skill", "Price too high"
</p>
</field>
XML 文件必须添加到与 CSV 文件相同的文件夹中,并在__manifest__.py数据文件的内容也会在安装模块时按顺序加载,因此对 CSV 文件所做的所有注释都适用于 XML 文件。当数据链接到视图时,我们会将它们添加到views文件夹中。
所以,我们的模块结构如下:
在本章中,我们将通过XML文件加载第一个操作和菜单。操作和菜单是数据库中的标准记录。
在Odoo中,用户界面(动作、菜单和视图)主要通过创建以及编写XML文件中定义的记录。一种常见模式是菜单 >动作 > 视图。要访问记录,用户需要浏览多个菜单级别;最深的层级是触发打开记录列表的动作。
动作(action)可以通过三种方式触发操作:
- 通过单击菜单项(链接到特定操作)
- 通过单击视图中的按钮(如果这些按钮连接到操作)
- 作为对象上的上下文操作
在本章中,我们只介绍第一种情况。第二种情况将在后面的章节中介绍,而最后一种情况是 高级主题的重点。在我们的Estate示例中,我们希望将菜单链接到estate.property模型,以便我们能够创建新记录。该操作可以被视为菜单和模型之间的链接。
以下代码是我们的test_model一个最基本动作:
<record id="test_model_action" model="ir.actions.act_window">
<field name="name">Test action</field>
<field name="res_model">test_model</field>
<field name="view_mode">list,form</field>
</record>
id是外部标识符。它可以用来引用记录 (不知道其数据库内标识符)。
model具有固定值ir.actions.act_window(窗口操作(ir.actions.act_window))。
name是操作的名称。
res_model是操作适用的模型。
view_mode是将可用的视图;在本例中,它们是List和Form视图。我们稍后会看到,还可以有其他的视图模式。
下面的代码是简单操作的一个很好的示例。请注意XML数据文件的结构,因为您将在下面的练习中需要它。
<record id="crm_lost_reason_action" model="ir.actions.act_window">
<field name="name">Lost Reasons</field>
<field name="res_model">crm.lost.reason</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Define a new lost reason
</p><p>
Use lost reasons to explain why an opportunity is lost.
</p><p>
Some examples of lost reasons: "We don't have people/skill", "Price too high"
</p>
</field>
</record>
为我们的esteta模块创建以下文件views/estate_property_views.xml
<?xml version="1.0"?>
<odoo>
<record id="estate_property_action" model="ir.actions.act_window">
<field name="name">estate action</field>
<field name="res_model">estate_property</field>
<field name="view_mode">tree, form</field>
</record>
</odoo>
并在__manifest__.py文件中定义它:
{
'name': 'Real Estate',
'version': '1.0',
'description': 'Real Estate',
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
]
}
重新启动服务器,您应该会看到文件加载到日志中。
INFO odoo odoo.modules.loading: loading estate/views/estate_property_views.xml
为了减少声明菜单(ir.ui.menu)并将其连接到相应操作的复杂性,我们可以使用 <menuitem> 快捷方式。
举个test_model_action例子,我们的基本菜单是:
<menuitem id="test_model_menu_action" action="test_model_action"/>
菜单test_model_menu_action将链接到动作test_model_action,而操作链接到模型test_model 。如前所述,该动作可以看作是链接在菜单和模型之间。
但是,菜单始终遵循体系结构,实际上有三个级别的菜单:
1、根菜单,显示在 应用程序切换器 (Odoo社区应用程序切换器是一个下拉菜单)
2、一级菜单,显示在顶部栏中
3、操作菜单
以下图片展示了3个菜单:
定义结构的最简单方法是在XML文件中创建它。test_model_action一个基本的结构:
<menuitem id="test_menu_root" name="Test">
<menuitem id="test_first_level_menu" name="First Level">
<menuitem id="test_model_menu_action" action="test_model_action"/>
</menuitem>
</menuitem>
第三个菜单的名称取自action。
下面为我们的estate.property动作创建3层菜单
创建以下文件views/estate_menus.xml:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="estate_property_menu_root" name="Real Estate">
<menuitem id="real_restate_advertisements_menu" name="Advertisements">
<menuitem id="estate_property_menu_action" action="estate_property_action"/>
</menuitem>
</menuitem>
</odoo>
记得在manifest.py中定义它。
然后修改views/estate_property_views.xml为:
<?xml version="1.0"?>
<odoo>
<record id="estate_property_action" model="ir.actions.act_window">
<field name="name">房屋</field>
<field name="res_model">estate_property</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>
重新启动服务器并刷新浏览器,您现在应该看到菜单,您甚至可以创建您的第一个房地产广告!以下是效果:
我们现在的模型代码是这样的:
class EstateProperty(models.Model):
_name = "estate_property"
_description = "Estate Property"
name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_available = fields.Date()
expected_price = fields.Float(required=True)
selling_price = fields.Float()
bedrooms = fields.Integer()
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection([
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
])
active = fields.Boolean(default=True)
state = fields.Selection([
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("canceled", "Canceled"),
], default="new",copy=False)
到目前为止,我们只在房地产广告中使用了通用视图,但是在大多数情况下,我们希望微调视图。Odoo中有许多微调,通常,第一步是确保:
1、某些字段具有默认值
2、某些字段是只读的
3、复制记录时不会复制某些字段
在我们的房地产商业案例中,我们希望以下内容:
1、销售价格应该是只读的(稍后会自动填写)
2、复制记录时,不应复制可用日期和销售价格
3、默认卧室数应为2
4、默认可用性日期应为3个月
在进一步进行视图设计之前,让我们先回到模型定义。我们看到,一些属性(如 required=True)会影响数据库中的表模式。
接下来,我们向字段添加新属性。
将 Selling price(销售价格)设置为read-only(只读)
防止复制date_available和selling_price的值
date_available = fields.Date(copy=False)
selling_price = fields.Float(readonly=True, copy=False)
重新启动服务器并刷新浏览器。您应该无法设置任何销售价格。
可以为任何字段指定默认值。在字段定义中,添加default=X选项,其中X是 Python 文本值(boolean、integer、 float, string) 或获取模型并返回值的函数:
name = fields.Char(default="Unknown")
last_seen = fields.Datetime("Last Seen", default=fields.Datetime.now)
默认情况下,name字段的值为“Unknown”,而last_seen字段默认值为当前时间。
接下来给我们的模型设置默认值。
添加适当的默认属性,以便:
默认卧室数量为 2
默认可用日期为 3 个月后
代码如下:
from odoo import models, fields, api
from dateutil.relativedelta import relativedelta
class EstateProperty(models.Model):
_name = "estate_property"
_description = "Estate Property"
def _get_default_date_available(self):
# 使用 fields.Date.today() 获取当前日期,并添加三个月
return fields.Date.today() + relativedelta(months=+3)
name = fields.Char(required=True, default="Unknown")
description = fields.Text()
postcode = fields.Char()
date_available = fields.Date(copy=False, default=_get_default_date_available)
expected_price = fields.Float(required=True)
selling_price = fields.Float(readonly=True, copy=False)
bedrooms = fields.Integer()
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection([
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
])
active = fields.Boolean(default=True)
state = fields.Selection([
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("canceled", "Canceled"),
], default="new")
一些字段名称是为预定义行为保留的。当需要相关行为时,应在模型上定义它们。
我们已经将active和state字段添加到estate.property模型中,当把active设置成false时,默认的房屋列表中将不会显示active=false的房屋。
active是保留字段,具有特定行为:当记录active=False时,它将自动从任何搜索中移除。要显示创建的属性,需要专门搜索非活动记录。
第5章:最后,一些UI可以玩