Интерфейс мастера odoo отображает список с флажками и получает выбранные данные.
Интерфейс мастера odoo отображает список с флажками и получает выбранные данные.

среда практики

Odoo 14.0-20221212 (Community Edition)

Описание требования

Как показано ниже (не скриншот реального интерфейса проекта, используется только для ознакомления с темой этой статьи), откройте страницу сведений о записи (представление формы), нажмите кнопку (кнопка «Выбрать предложения» на рисунке), откроется мастер. появится всплывающий интерфейс и отобразит записи списка во встроенном древовидном представлении (вкладка «Предложения») на странице сведений в интерфейсе мастера, а также поддержит флажки для выбора целевых записей и последующего выполнения целевых операций.

Страница сведений принадлежит МодельEstateProperty

Язык кода:javascript
копировать
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)

OffersTabСтраницаTreeсписок принадлежит МодельEstatePropertyOffer

Язык кода:javascript
копировать
class EstatePropertyOffer(models.Model):
    _name = 'estate.property.offer'
    _description = 'estate property offer'
    
    # ... немного
    property_id = fields.Many2one('estate.property', required=True)

Реализация кода

Организационная структура кода

Чтобы лучше представить тему этой статьи, ниже приведена общая организационная структура файлов проекта (чтобы всем было понятнее, сохраняются только ключевые файлы).

Язык кода:javascript
копировать
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и повторно использовать все существующие механизмы,Имеет следующие особенности:

  • wizardЗаписывать不是永久из;Они автоматически меняются сданныебиблиотекаудалить.Вот почему их называют переходными(transient)。
  • wizardДоступен через поля отношений.(many2oneилиmany2many)引用常规ЗаписыватьилиwizardЗаписывать,Но обычные рекорды не могут пройтиmany2oneссылка на полеwizardЗаписывать

Подробный код

Примечание. Чтобы более четко выразить тему этой статьи, некоторые коды в файле кода опущены.

реализация мастера
odoo14\custom\estate\wizards\demo_wizard.py
Версия реализации 1
Язык кода:javascript
копировать
#!/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данные格式для:

Язык кода:javascript
копировать
{'uuid':{'recordID1':{'data': {}, 'context':{}}, 'recordID2': {'data': {}, 'context':{}}}}
Язык кода:javascript
копировать
{'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}}}}
Версия реализации 2
Язык кода:javascript
копировать
#!/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
Язык кода:javascript
копировать
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from . import demo_wizard
odoo14\custom\estate\__init__.py
Язык кода:javascript
копировать
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from . import models
from . import wizards
odoo14\custom\estate\wizards\demo_wizard_views.xml
Версия реализации 1

переписыватьсяdemo_wizard.pyВерсия реализации 1

Язык кода:javascript
копировать
<?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>

проиллюстрировать:

Язык кода:javascript
копировать
<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Поле,следующее:

Язык кода:javascript
копировать
        <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

Версия реализации 2

переписыватьсяdemo_wizard.pyВерсия реализации 2

Язык кода:javascript
копировать
<?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
Язык кода:javascript
копировать
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函数определение问题。

Язык кода:javascript
копировать
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 будет выдано, если оно будет определено повторно.

Язык кода:javascript
копировать
odoo.define('web.ListRenderer', function (require) {
    "use strict";
    //...немного,То же, что и приведенный выше код
    //odoo.__DEBUG__['services']['web.ListRenderer'] = ListRenderer;
    вернуть ListRenderer;
});

Позже автор обнаружил,Можно использоватьincludeзаменятьextendметод修改现有изweb.ListRenderer,следующее

Язык кода:javascript
копировать
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
Версия реализации 1

дляdemo_wizard_views.xmlВерсия реализации 1 использование

Язык кода:javascript
копировать
function disableActionConfirmButton(){ // Кнопка отключения
    $("button[name='action_confirm']").attr("disabled", true);
}

function enableActionConfirmButton(){ // кнопка включения
    $("button[name='action_confirm']").attr("disabled", false);
}

Дизайн здесь такой,При выполнении операции с флажком сначала отключите кнопку и не разрешайте операции подтверждения, поскольку запрос, вызванный выполнением флажка, может быть выполнен не так быстро, и данные внешнего интерфейса могут не быть полностью переданы на серверную часть. Если вы выполните операцию в это время, это может привести к неожиданным результатам. Итак, дождитесь завершения запроса, прежде чем включать кнопку.

Версия реализации 2

дляdemo_wizard_views.xmlВерсия реализации 2 использования

Язык кода:javascript
копировать
функция 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характеристики,в противном случае,При обновлении приложения будет сообщено об ошибке.

Язык кода:javascript
копировать
<?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.

Язык кода:javascript
копировать
<?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.

Язык кода:javascript
копировать
#!/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
Язык кода:javascript
копировать
<?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" Установить подсветку кнопок

boy illustration
Учебное пособие по Jetpack Compose для начинающих, базовые элементы управления и макет
boy illustration
Код js веб-страницы, фон частицы, код спецэффектов
boy illustration
【новый! Суперподробное】Полное руководство по свойствам компонентов Figma.
boy illustration
🎉Обязательно к прочтению новичкам: полное руководство по написанию мини-программ WeChat с использованием программного обеспечения Cursor.
boy illustration
[Забавный проект Docker] VoceChat — еще одно приложение для мгновенного чата (IM)! Может быть встроен в любую веб-страницу!
boy illustration
Как реализовать переход по странице в HTML (html переходит на указанную страницу)
boy illustration
Как решить проблему зависания и низкой скорости при установке зависимостей с помощью npm. Существуют ли доступные источники npm, которые могут решить эту проблему?
boy illustration
Серия From Zero to Fun: Uni-App WeChat Payment Practice WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа
boy illustration
Серия uni-app: uni.navigateЧтобы передать скачок значения
boy illustration
Апплет WeChat настраивает верхнюю панель навигации и адаптируется к различным моделям.
boy illustration
JS-время конвертации
boy illustration
Обеспечьте бесперебойную работу ChromeDriver 125: советы по решению проблемы chromedriver.exe не найдены
boy illustration
Поле комментария, щелчок мышью, специальные эффекты, js-код
boy illustration
Объект массива перемещения объекта JS
boy illustration
Как открыть разрешение на позиционирование апплета WeChat_Как использовать WeChat для определения местонахождения друзей
boy illustration
Я даю вам два набора из 18 простых в использовании фонов холста Power BI, так что вам больше не придется возиться с цветами!
boy illustration
Получить текущее время в js_Как динамически отображать дату и время в js
boy illustration
Вам необходимо изучить сочетания клавиш vsCode для форматирования и организации кода, чтобы вам больше не приходилось настраивать формат вручную.
boy illustration
У ChatGPT большое обновление. Всего за 45 минут пресс-конференция показывает, что OpenAI сделал еще один шаг вперед.
boy illustration
Copilot облачной разработки — упрощение разработки
boy illustration
Микросборка xChatGPT с низким кодом, создание апплета чат-бота с искусственным интеллектом за пять шагов
boy illustration
CUDA Out of Memory: идеальное решение проблемы нехватки памяти CUDA
boy illustration
Анализ кластеризации отдельных ячеек, который должен освоить каждый&MarkerгенетическийВизуализация
boy illustration
vLLM: мощный инструмент для ускорения вывода ИИ
boy illustration
CodeGeeX: мощный инструмент генерации кода искусственного интеллекта, который можно использовать бесплатно в дополнение к второму пилоту.
boy illustration
Машинное обучение Реальный бой LightGBM + настройка параметров случайного поиска: точность 96,67%
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция без кодирования и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
LM Studio для создания локальных больших моделей
boy illustration
Как определить количество слоев и нейронов скрытых слоев нейронной сети?
boy illustration
[Отслеживание целей] Подробное объяснение ByteTrack и детали кода