Odoo 14.0-20221212 (Community Edition)
Как показано ниже (не скриншот реального интерфейса проекта, используется только для ознакомления с темой этой статьи), откройте страницу сведений о записи (представление формы), нажмите кнопку (кнопка «Выбрать предложения» на рисунке), откроется мастер. появится всплывающий интерфейс и отобразит записи списка во встроенном древовидном представлении (вкладка «Предложения») на странице сведений в интерфейсе мастера, а также поддержит флажки для выбора целевых записей и последующего выполнения целевых операций.
Страница сведений принадлежит МодельEstateProperty
class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'estate property table'
# ... немного
offer_ids = fields.One2many("estate.property.offer", "property_id", string="PropertyOffer")
def action_do_something(self, args):
# do something
print(args)
Offers
TabСтраницаTreeсписок принадлежит МодельEstatePropertyOffer
class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'estate property offer'
# ... немного
property_id = fields.Many2one('estate.property', required=True)
Чтобы лучше представить тему этой статьи, ниже приведена общая организационная структура файлов проекта (чтобы всем было понятнее, сохраняются только ключевые файлы).
odoo14
├─custom
│ ├─estate
│ │ │ __init__.py
│ │ │ __manifest__.py
│ │ │
│ │ ├─models
│ │ │ estate_property.py
│ │ │ estate_property_offer.py
│ │ │ __init__.py
│ │ │
│ │ ├─security
│ │ │ ir.model.access.csv
│ │ │
│ │ ├─static
│ │ │ │
│ │ │ └─src
│ │ │ │
│ │ │ └─js
│ │ │ list_renderer.js
│ │ │
│ │ ├─views
│ │ │ estate_property_offer_views.xml
│ │ │ estate_property_views.xml
│ │ │ webclient_templates.xml
│ │ │
│ │ └─wizards
│ │ demo_wizard.py
│ │ demo_wizard_views.xml
│ │ __init__.py
│ │
├─odoo
│ │ api.py
│ │ exceptions.py
│ │ ...немного
│ │ __init__.py
│ │
│ ├─addons
│ │ │ __init__.py
│ ...немного
...немного
wizard(волшебник)Общайтесь с пользователями с помощью динамических описаний форм.(или диалоговое окно)интерактивная сессия。волшебник只是一个继承TransientModel
скорее, чемmodel
из Модель。TransientModel
расширение классаModel
и повторно использовать все существующие механизмы,Имеет следующие особенности:
many2one
илиmany2many
)引用常规ЗаписыватьилиwizardЗаписывать,Но обычные рекорды не могут пройтиmany2one
ссылка на полеwizardЗаписывать
Примечание. Чтобы более четко выразить тему этой статьи, некоторые коды в файле кода опущены.
odoo14\custom\estate\wizards\demo_wizard.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import logging
from odoo import models,fields,api
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class DemoWizard(models.TransientModel):
_name = 'demo.wizard'
_description = 'demo wizard'
property_id = fields.Many2one('estate.property', string='property')
offer_ids = fields.One2many(related='property_id.offer_ids')
def action_confirm(self):
'''После выбора записи нажмите кнопку подтверждения, чтобы выполнить операцию'''
#### Обработайте полученные данные соответствующим образом по мере необходимости.
# ... 获取данные,коднемного(假设获取изданные存放在 data переменная)
record_ids = []
for id, value_dict in data.items():
record_ids.append(value_dict.get('data', {}).get('id'))
if not record_ids:
raise UserError('Пожалуйста, выберите запись')
self.property_id.action_do_something(record_ids)
return True
@api.model
def action_select_records_via_checkbox(self, args):
'''Операция срабатывает при выборе записи через флажок интерфейса окна мастера
@params: args для словаря
'''
# ...存储收到изданные(Предположим, что мы храним толькоdata部分изданные),коднемного
return True # Обратите внимание: если выполнение прошло успешно, вам необходимо сотрудничать с внешней реализацией и вернуть True.
@api.model
def default_get(self, fields_list):
'''Получить мастера Настройки интерфейса окна по умолчанию, включая список записей #Поскольку используется модификатор @api.model, self представляет собой пустой набор записей, поэтому self.fieldName нельзя передать = value Присвоить значение '''
res = super(DemoWizard, self).default_get(fields_list)
record_ids = self.env.context.get('active_ids') # Получить текущий список идентификаторов записей (список идентификаторов записей, к которому принадлежит текущая страница сведений о записи) # self.env.context.get('active_id') # Получить текущий идентификатор записи
property = self.env['estate.property'].browse(record_ids)
res['property_id'] = property.id
offer_ids = property.offer_ids.mapped('id')
res['offer_ids'] = [(6, 0, offer_ids)]
return res
проиллюстрировать:
Обратите внимание, что атрибуты класса нельзя использовать для получения данных, поскольку атрибуты класса являются общими для всех объектов и будут влиять друг на друга и вызывать путаницу в данных.
action_select_records_via_checkbox
函数接收изargs
параметр,его типдля словаря, имеет формуниже,вf412cde5-1e5b-408c-8fc0-1841b9f9e4de
дляUUID,Для веб-использования,данные, используемые для различения различных операций страницы,'estate.property.offer_3'
для Для веб-использованияиз ЗаписыватьID,'data'
键值代表Записыватьизданные,Чтоid
键值代表Записывать在данныебиблиотекаиз主键id,context
键值代表Записыватьиз上下文。arg
данные格式для:
{'uuid':{'recordID1':{'data': {}, 'context':{}}, 'recordID2': {'data': {}, 'context':{}}}}
{'f412cde5-1e5b-408c-8fc0-1841b9f9e4de': {'estate.property.offer_3': {'data': {'price': 30000, 'partner_id': {'context': {}, 'count': 0, 'data': {'display_name': 'Azure Interior, Brandon Freeman', 'id': 26}, 'domain': [], 'fields': {'display_name': {'type': 'char'}, 'id': {'type': 'integer'}}, 'id': 'res.partner_4', 'limit': 1, 'model': 'res.partner', 'offset': -1, 'ref': 26, 'res_ids': [], 'specialData': {}, 'type': 'record', 'res_id': 26}, 'validity': 7, 'date_deadline': '2022-12-30', 'status': 'Accepted', 'id': 21}, 'context': {'lang': 'en_US', 'tz': 'Europe/Brussels', 'uid': 2, 'allowed_company_ids': [1], 'params': {'action': 85, 'cids': 1, 'id': 41, 'menu_id': 70, 'model': 'estate.property', 'view_type': 'form'}, 'active_model': 'estate.property', 'active_id': 41, 'active_ids': [41], 'property_pk_id': 41}}}}
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import uuid
import logging
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError, MissingError
_logger = logging.getLogger(__name__)
class DemoWizard(models.TransientModel):
_name = 'demo.wizard'
_description = 'demo wizard'
property_id = fields.Many2one('estate.property', string='property')
property_pk_id = fields.Integer(related='property_id.id') # Используется для получения свойства в action_confirm.
offer_ids = fields.One2many(related='property_id.offer_ids')
@api.model
def action_confirm(self, data:dict):
'''После выбора записи нажмите кнопку подтверждения, чтобы выполнить операцию'''
#### Обработайте полученные данные соответствующим образом по мере необходимости.
record_ids = []
for id, value_dict in data.items():
record_ids.append(value_dict.get('data', {}).get('id'))
if not record_ids:
raise UserError('Пожалуйста, выберите запись')
property_pk_id = None
for id, value_dict in data.items():
property_pk_id = value_dict.get('context', {}).get('property_pk_id')
break
if not property_pk_id:
raise ValidationError('do something fail')
property = self.env['estate.property'].browse([property_pk_id]) # Обратите внимание, что здесь его больше нельзя получить через self.property_id.
if property.exists():
property.action_do_something(record_ids)
else:
raise MissingError('do something ошибка: текущая запись свойства (id=%s) не существует' % property_pk_id)
return True
@api.model
def default_get(self, fields_list):
'''Получить мастера Настройки интерфейса окна по умолчанию, включая список записей'''
res = super(DemoWizard, self).default_get(fields_list)
record_ids = self.env.context.get('active_ids')
property = self.env['estate.property'].browse(record_ids)
res['property_id'] = property.id
res['property_pk_id'] = property.id
offer_ids = property.offer_ids.mapped('id')
res['offer_ids'] = [(6, 0, offer_ids)]
return res
odoo14\custom\estate\wizards\__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from . import demo_wizard
odoo14\custom\estate\__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from . import models
from . import wizards
odoo14\custom\estate\wizards\demo_wizard_views.xml
переписыватьсяdemo_wizard.py
Версия реализации 1
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="demo_wizard_view_form" model="ir.ui.view">
<field name="name">demo.wizard.form</field>
<field name="model">demo.wizard</field>
<field name="arch" type="xml">
<form>
<field name="offer_ids">
<tree hasCheckBoxes="true" modelName="demo.wizard" modelMethod="action_select_records_via_checkbox" jsMethodOnModelMethodDone="enableActionConfirmButton()" jsMethodOnToggleCheckbox="disableActionConfirmButton()">
<field name="price" string="Price"/>
<field name="partner_id" string="partner ID"/>
<field name="validity" string="Validity(days)"/>
<field name="date_deadline" string="Deadline"/>
<button name="action_accept_offer" string="" type="object" icon="fa-check" attrs="{'invisible': [('status', 'in', ['Accepted','Refused'])]}"/>
<button name="action_refuse_offer" string="" type="object" icon="fa-times" attrs="{'invisible': [('status', 'in', ['Accepted','Refused'])]}"/>
<field name="status" string="Status"/>
</tree>
</field>
<footer>
<button name="action_confirm" type="object" string="Подтвердить(сделать something you want)" class="oe_highlight"/>
<button строка="Отмена" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_demo_wizard" model="ir.actions.act_window">
<field name="name">Выбиратьoffers</field>
<field name="res_model">demo.wizard</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>
проиллюстрировать:
<tree hasCheckBoxes="true" modelName="demo.wizard" modelMethod="action_select_records_via_checkbox" jsMethodOnModelMethodDone="enableActionConfirmButton()" jsMethodOnToggleCheckbox="disableActionConfirmButton()">
hasCheckBoxes
настраивать"true"
,Показать флажок。ниже属性皆在hasCheckBoxes
для"true"
из情况下起作用。modelName
При установке флажка списка,Название модели, к которому необходимо получить доступ,Нужно сотрудничатьmodelMethod
Использование метода,Оба незаменимы. НеобязательныйmodelMethod
При установке флажка списка,Метод модели, который необходимо вызвать,Этот метод собирает список отмеченных записей. Необязательный.jsMethodOnModelMethodDone
определениеmodelMethod
метод执行完成后,需要调用изjavascriptметод(Обратите внимание, что параметры включены. Если параметров нет, это записывается как.()
, имеет формуjsMethod()
)。Необязательный。jsMethodOnToggleCheckbox
определение При установке флажка списка需要调用изjavascriptметод,СравниватьmodelMethod
Расставьте приоритеты выполнения(Обратите внимание, что параметры включены. Если параметров нет, это записывается как.()
, имеет формуjsMethod()
)。Необязательный。以上параметр同下文saveSelectionsToSessionStorage
Параметры могут сосуществовать одновременно
Если вам нужно привязать действие к действию указанной модели, указанному представлению,Можно найти вir.actions.act_window
определениесерединадобавить вbinding_model_id
иbinding_view_types
Поле,следующее:
<record id="action_demo_wizard" model="ir.actions.act_window">
<field name="name">Выбиратьoffers</field>
<field name="res_model">demo.wizard</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<!-- Добавить меню действий -->
<field name="binding_model_id" ref="estate.model_estate_property"/>
<field name="binding_view_types">form</field>
</record>
Эффект следующий
Эталонное соединение:https://www.odoo.com/documentation/14.0/zh_CN/developer/reference/addons/actions.html
переписыватьсяdemo_wizard.py
Версия реализации 2
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="demo_wizard_view_form" model="ir.ui.view">
<field name="name">demo.wizard.form</field>
<field name="model">demo.wizard</field>
<field name="arch" type="xml">
<form>
<field name="property_pk_id" invisible="1"/>
<field name="offer_ids" context="{'property_pk_id': property_pk_id}">
<tree string="List" hasCheckBoxes="true" saveSelectionsToSessionStorage="true">
<field name="price" string="Price"/>
<field name="partner_id" string="partner ID"/>
<field name="validity" string="Validity(days)"/>
<field name="date_deadline" string="Deadline"/>
<button name="action_accept_offer" string="" type="object" icon="fa-check" attrs="{'invisible': [('status', 'in', ['Accepted','Refused'])]}"/>
<button name="action_refuse_offer" string="" type="object" icon="fa-times" attrs="{'invisible': [('status', 'in', ['Accepted','Refused'])]}"/>
<field name="status" string="Status"/>
</tree>
</field>
<footer>
<button name="action_confirm" onclick="do_confirm_action('demo.wizard','action_confirm')" string="Подтвердить(сделать something you want)" class="oe_highlight"/>
<button строка="Отмена" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_demo_wizard" model="ir.actions.act_window">
<field name="name">Выбиратьoffers</field>
<field name="res_model">demo.wizard</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>
проиллюстрировать:
saveSelectionsToSessionStorage
для"true"
означает, что при установке флажка,Воля当前Выбиратьиз Записывать存到浏览器sessionStorage
середина,Необязательныйodoo14\custom\estate\security\ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
# ...немного
access_demo_wizard_model,access_demo_wizard_model,model_demo_wizard,base.group_user,1,1,1,1
Уведомление:wizard
Модель Также необходимо добавить Модель访问权限配置из
Общая идея — через наследование.web.ListRenderer
实现自определениеListRenderer,Затем осуществляется отображение флажка и проверка данных.
odoo14\custom\estate\static\src\js\list_renderer.js
Уведомление:Изтак Воляuuid
函数определение在list_renderer.js
середина,Это сделано для того, чтобы избежать проблем с последовательной загрузкой js.,может вызвать загрузкуlist_renderer.js
не найден когдаuuid
函数определение问题。
function uuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
}
odoo.define('estate.ListRenderer', function (require) {
"use strict";
var ListRenderer = require('web.ListRenderer');
ListRenderer = ListRenderer.extend({
init: function (parent, state, params) {
this._super.apply(this, arguments);
this.hasCheckBoxes = false;
if ('hasCheckBoxes' in params.arch.attrs && params.arch.attrs['hasCheckBoxes']) {
this.objectID = uuid();
$(this).attr('id', this.objectID);
this.hasCheckBoxes = true;
this.hasSelectors = true;
this.records = {}; // Хранить текущие записи интерфейса
this.recordsSelected = {}; // Сохранить выбранные записи
this.modelName = undefined; // Определяет модель, к которой необходимо получить доступ при нажатии на флажок списка.
this.modelMethod = undefined; // Определите метод модели, который необходимо вызывать при установке флажка в списке.
this.jsMethodOnModelMethodDone = undefined; // Определите метод javascript, который необходимо вызвать после выполнения метода modelMethod.
this.jsMethodOnToggleCheckbox = undefined; // Определите метод javascript, который необходимо вызывать при установке флажка списка, который будет выполняться перед modelMethod.
if ('modelName' in params.arch.attrs && params.arch.attrs['modelName']) {
this.modelName = params.arch.attrs['modelName'];
}
if ('modelMethod' in params.arch.attrs && params.arch.attrs['modelMethod']) {
this.modelMethod = params.arch.attrs['modelMethod'];
}
if ('jsMethodOnModelMethodDone' in params.arch.attrs && params.arch.attrs['jsMethodOnModelMethodDone']){
this.jsMethodOnModelMethodDone = params.arch.attrs['jsMethodOnModelMethodDone'];
}
if ('jsMethodOnToggleCheckbox' in params.arch.attrs && params.arch.attrs['jsMethodOnToggleCheckbox']) {
this.jsMethodOnToggleCheckbox = params.arch.attrs['jsMethodOnToggleCheckbox'];
}
if ('saveSelectionsToSessionStorage' in params.arch.attrs && params.arch.attrs['saveSelectionsToSessionStorage']) {
this.saveSelectionsToSessionStorage = params.arch.attrs['saveSelectionsToSessionStorage'];
}
}
},
// _onToggleSelection: function (ev) {
// Эта функция вызывается при нажатии флажка «Выбрать/отменить выбор всех» в заголовке списка.
// this._super.apply(this, arguments);
// },
_onToggleCheckbox: function (ev) {
if (this.hasCheckBoxes) {
var classOfEvTarget = $(ev.target).attr('class');
/* cstom-control-input Просто нажмите на флажок ввода,
custom-control custom-checkbox Просто нажмите на родительский элемент div ввода флажка.
o_list_record_selector Щелкните за пределами флажка родительского элемента вышеуказанного div*/
if (['custom-control custom-checkbox', 'custom-control-input', 'o_list_record_selector'].includes(classOfEvTarget)){
if (this.jsMethodOnToggleCheckbox) {
eval(this.jsMethodOnToggleCheckbox)
}
var id = $(ev.currentTarget).closest('tr').data('id'); // 'custom-control-input' == classOfEvTarget
var checked = !this.$(ev.currentTarget).find('input').prop('checked') // Узнать, установлен ли флажок 'custom-control-input' != classOfEvTarget
if ('custom-control-input' == classOfEvTarget) {
checked = this.$(ev.currentTarget).find('input').prop('checked')
}
if (id == undefined) {
if (checked == true) { // Выбрать все
this.recordsSelected = JSON.parse(JSON.stringify(this.records));
} else { // Отмена Выбрать все
this.recordsSelected = {};
}
} else {
if (checked == true) { // Проверить одну запись
this.recordsSelected[id] = this.records[id];
} else { // Отмена Проверить одну запись
delete this.recordsSelected[id];
}
}
if (this.saveSelectionsToSessionStorage) {
window.sessionStorage[this.objectID] = JSON.stringify(this.recordsSelected);
}
// Запросите метод Модель через rpc, который используется для передачи записей, проверяемых интерфейсом data.
if (this.modelName && this.modelMethod) {
self = this;
this._rpc({
model: this.modelName,
method: this.modelMethod,
args: [this.recordsSelected],
}).then(function (res) {
if (self.jsMethodOnModelMethodDone) {
eval(self.jsMethodOnModelMethodDone);
}
});
}
}
}
this._super.apply(this, arguments);
},
_renderRow: function (record) {
// Строки визуализируются при открытии страницы списка, а визуализированные записи сохраняются в это время.
if (this.hasCheckBoxes) {
this.records[record.id] = {'data': record.data, 'context': record.context};
}
return this._super.apply(this, arguments);
}
});
odoo.__DEBUG__['services']['web.ListRenderer'] = ListRenderer; //Перезаписываем исходный сервис ListRender
});
На практике была опробована следующая схема реализации. Представление указывает тот же идентификатор службы. web.ListRenderer
来覆盖框架自带изweb.ListRenderer
определение,Эта реализация может быть выполнена только в не-Debug
В режиме работает нормально,и приведет к невозможности открытияDebug
модель,odoo.define
实现середина会对服务是否重复определение做判断,Исключение JavaScript будет выдано, если оно будет определено повторно.
odoo.define('web.ListRenderer', function (require) {
"use strict";
//...немного,То же, что и приведенный выше код
//odoo.__DEBUG__['services']['web.ListRenderer'] = ListRenderer;
вернуть ListRenderer;
});
Позже автор обнаружил,Можно использоватьinclude
заменятьextend
метод修改现有изweb.ListRenderer
,следующее
odoo.define('estate.ListRenderer', function (require) {
"use strict";
var ListRenderer = require('web.ListRenderer');
ListRenderer = ListRenderer.include({//...немного,同上述код});
// odoo.__DEBUG__['services']['web.ListRenderer'] = ListRenderer; //Нет необходимости добавлять эту строку кода
});
odoo14\custom\estate\static\src\js\demo_wizard_views.js
дляdemo_wizard_views.xml
Версия реализации 1 использование
function disableActionConfirmButton(){ // Кнопка отключения
$("button[name='action_confirm']").attr("disabled", true);
}
function enableActionConfirmButton(){ // кнопка включения
$("button[name='action_confirm']").attr("disabled", false);
}
Дизайн здесь такой,При выполнении операции с флажком сначала отключите кнопку и не разрешайте операции подтверждения, поскольку запрос, вызванный выполнением флажка, может быть выполнен не так быстро, и данные внешнего интерфейса могут не быть полностью переданы на серверную часть. Если вы выполните операцию в это время, это может привести к неожиданным результатам. Итак, дождитесь завершения запроса, прежде чем включать кнопку.
дляdemo_wizard_views.xml
Версия реализации 2 использования
функция do_confirm_action(modelName, modelMethod, context){
$("button[name='action_confirm']").attr("disabled", true); // После нажатия кнопки,Кнопка отключениясостояние,Сравнивать较重复点击导致重复发送请求
var wizard_dialog = $(event.currentTarget.offsetParent.parentElement.parentElement);
var dataUUID = $(event.currentTarget.parentElement.parentElement.parentElement.parentElement).find('div.o_list_view').prop('id');
var rpc = odoo.__DEBUG__.services['web.rpc'];
rpc.query({
model: modelName,
method: modelMethod,
args: [JSON.parse(window.sessionStorage.getItem(dataUUID) || '{}')]
}).then(function (res) if (res == true) {
wizard_dialog.css('display', 'none'); // Скрыть диалоговое окно
window.sessionStorage.removeItem(dataUUID);
} else {
$("button[name='action_confirm']").attr("disabled", false);
}
}).catch(function (err) {
$("button[name='action_confirm']").attr("disabled", false);
});
}
odoo14\odoo\addons\base\rng\tree_view.rng
Необязательныйдействовать。если хотитеhasCheckBoxes
,modelName
,modelMethod
и т. д. также работает на невстроенныхtreeвид,Вам нужно отредактировать файл,добавить вhasCheckBoxes
,modelName
,modelMethod
характеристики,в противном случае,При обновлении приложения будет сообщено об ошибке.
<?xml version="1.0" encoding="UTF-8"?>
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0"
xmlns:a="http://relaxng.org/ns/annotation/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<!-- ...Содержимое здесь сохранено. -->
<rng:define name="tree">
<rng:element name="tree">
<!-- ...Содержимое здесь сохранено. -->
<rng:optional><rng:attribute name="decoration-warning"/></rng:optional>
<rng:optional><rng:attribute name="banner_route"/></rng:optional>
<rng:optional><rng:attribute name="sample"/></rng:optional>
<!--在此处добавить в新属性>
<rng:optional><rng:attribute name="hasCheckBoxes"/></rng:optional>
<rng:optional><rng:attribute name="modelName"/></rng:optional>
<rng:optional><rng:attribute name="modelMethod"/></rng:optional>
<rng:optional><rng:attribute name="jsMethodOnModelMethodDone"/></rng:optional>
<rng:optional><rng:attribute name="jsMethodOnToggleCheckbox"/></rng:optional>
<rng:optional><rng:attribute name="saveSelectionsToSessionStorage"/></rng:optional>
<!-- ...Содержимое здесь сохранено. -->
</rng:element>
</rng:define>
<!-- ...Содержимое здесь сохранено. -->
</rng:grammar>
odoo14\custom\estate\views\webclient_templates.xml
Используется для загрузки пользовательских JS.
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_common" inherit_id="web.assets_common" name="Backend Assets (used in backend interface)">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/estate/static/src/js/list_renderer.js"></script>
<script type="text/javascript" src="/estate/static/src/js/demo_wizard_views.js"></script>
</xpath>
</template>
</odoo>
odoo14\custom\estate\__manifest__.py
Загрузка пользовательских файлов шаблонов для загрузки пользовательских файлов JS.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
{
'name': 'estate',
'depends': ['base'],
'data':[
'views/webclient_templates.xml',
'security/ir.model.access.csv',
#...немного
'wizards/demo_wizard_views.xml'
'views/estate_property_views.xml',
'views/estate_property_offer_views.xml',
]
}
odoo14\custom\estate\views\estate_property_views.xml
<?xml version="1.0"?>
<odoo>
<!--...немного-->
<record id="estate_property_view_form" model="ir.ui.view">
<field name="name">estate.property.form</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="estate property form">
<header>
<button name="%(action_demo_wizard)d"
type="action"
string="Выбрать предложения" class="oe_highlight"/>
<!--...немного-->
</header>
<sheet>
<!--...немного-->
<notebook>
<!--...немного-->
<page string="Offers">
<field name="offer_ids" attrs="{'readonly': [('state', 'in', ['Offer Accepted','Sold','Canceled'])]}"/>
</page>
<!--...немного-->
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>
проиллюстрировать:class="oe_highlight"
Установить подсветку кнопок