Недавно появилась задача раскрасить строки в поле типа Many2one в Odoo, так как у меня есть историчность записей и при выборе необхдимо разделять активные и не активыне записи по цветам. К сожалению, стандартного способа я не нашел и поэтому начала искать варианты как это сделать.
Исследование исходников.
Первым делом я полез смотреть как работает виджет для данного типа поля. Его реализацию я нашел в файле /addons/web/static/src/js/views/form_relational_widgets.js
.
Его верхнеуровневая реализация выглядит так:

Как видно из листинга данный виджет реализуется ввиде расширения minix класса, состоящего из common.CompletionFieldMixin
и common.ReinitializeFieldMixin
.
В на первый взгялд в классе CompletionFieldMixin ничего интересного нет, а вот во втором можно увитедь следующие функции:

Так как метод start
отрабоатывает сразу при отрисовке объекта, то получается следующая последовательность вызовов:
ReinitializeFieldMixin.start
FieldMany2One.initialize_field
ReinitializeFieldMixin.initialize_field
FieldMany2One.initialize_content
FieldMany2One.render_editable
Функция render_editable
отвечает за отрисовку элемента.
В ней я нашел следующий кусок, отвечающий за автокомплит (jqueryui):

После этого я встал отладчиком и посмотрел что происходит на 278 строке:

Отсюда видно, что в данных уже содержатся строки не приходящие с серевра (например: “Искать еще”), а также видно что у них есть атрибут classname
с произвольным классом оформления.
Следовательно интересующая нас функциональность находится где-то в функции get_search_result
и если посмотеть то она вызывается из CompletionFieldMixin
и имеет следующий интересный кусок:

Здесь видно, что сначала определяется dataset для нужной модели, затем у него запускает name_search
, который судя по всему отвечает за получение данных с сервера, и после того как данные придут к ним добавляются произвольные записи.
Реализация метода name_search выглядит так:

Получается что данный метод просто вызывает у модели метод name_search
, который в свою очередь возвращает id и имя объекта.
Результат анализа
Из всего выше описанного получается следующее решение:
- Написать метод подобный name_search, который будет возвращать кроме id и имя объекта, еще и имя класса для расскраски
- Переопределить метод get_search_result, для корректной обработки такого ответа
Реализация
Расширенный метод поиска
Напишем тестовую модель следующего содержания:
from odoo import models, fields, api
class TestModel(models.Model)
name = fields.Char("Название")
is_active = fields.Boolean("Активно")
@api.model
def name_search_cond(self, name='', args=None, limit=100):
args = list(args or [])
args += [(self._rec_name, 'ilike', name)]
filters = self.env['test.model'].search(args, limit=limit)
res = []
for r in filters_bnso:
css_class_name = u'active' if r.is_active > now else u'inactive'
res.append((r.id, r.name, css_class_name))
return res
Чтобы функция была видна через json-rpc нужно установить ей декоратор @api.model
Кастомизация js виджета
После того, как метод для поиска задан, расширим стандарный виджет следующим способом:
odoo.define('many2one_color', function (require) {
'use strict';
var core = require('web.core');
var data = require('web.data');
var FieldMany2One = core.form_widget_registry.get('many2one');
var _t = core._t;
var FieldMany2OneColor = FieldMany2One.extend({
get_search_result: function(search_val) {
console.log("extend widget")
var self = this;
var dataset = new data.DataSet(this, this.field.relation, self.build_context());
this.last_query = search_val;
var exclusion_domain = [], ids_blacklist = this.get_search_blacklist();
if (!_(ids_blacklist).isEmpty()) {
exclusion_domain.push(['id', 'not in', ids_blacklist]);
}
return this.orderer.add(dataset.call('name_search_cond', {
name: search_val || '',
args: new data.CompoundDomain(self.build_domain(), exclusion_domain) || false,
limit: this.limit + 1
})
).then(function(_data) {
self.last_search = _data;
// possible selections for the m2o
var values = _.map(_data, function(x) {
x[1] = x[1].split("\n")[0];
return {
label: _.str.escapeHTML(x[1].trim()) || data.noDisplayContent,
classname: x[2],
value: x[1],
name: x[1],
id: x[0],
};
});
// стандартный код из get_search_result
});
},
});
core.form_widget_registry.add('many2one_color', FieldMany2OneColor)
});
В данном случае я просто скопировал код из метода get_search_result
и заменил в нем вызов dataset.name_searh
на dataset.call
, который дергает мою функцию.
Кроме того я добавил атрибут classname
в обработку ответа от сервера с результатом поиска.
Далее остается только добавить нужные цвета в css:
.inactive {
background: #f89e9e;
}
.active {
background: #acfaac;
}
После чего установить или обновить модуль.
Заключение
В итоге получился виджет который выводит следующее:

Такое решение может пригодится если вам бы хотелось разделять данные визуально в выпадающем списке поля в Odoo.
comments powered by Disqus