Выделение строк в Many2one поле в Odoo

Недавно появилась задача раскрасить строки в поле типа Many2one в Odoo, так как у меня есть историчность записей и при выборе необхдимо разделять активные и не активыне записи по цветам. К сожалению, стандартного способа я не нашел и поэтому начала искать варианты как это сделать.

Исследование исходников.

Первым делом я полез смотреть как работает виджет для данного типа поля. Его реализацию я нашел в файле /addons/web/static/src/js/views/form_relational_widgets.js.

Его верхнеуровневая реализация выглядит так:

Описание реализации виджета Many2one

Как видно из листинга данный виджет реализуется ввиде расширения minix класса, состоящего из common.CompletionFieldMixin и common.ReinitializeFieldMixin.

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

Описание реализации ReinitializeFieldMixin

Так как метод start отрабоатывает сразу при отрисовке объекта, то получается следующая последовательность вызовов:

ReinitializeFieldMixin.start
    FieldMany2One.initialize_field
        ReinitializeFieldMixin.initialize_field
            FieldMany2One.initialize_content
                FieldMany2One.render_editable

Функция render_editable отвечает за отрисовку элемента.

В ней я нашел следующий кусок, отвечающий за автокомплит (jqueryui):

Кусок с автокомплитом из render_editable

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

Отладка

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

Следовательно интересующая нас функциональность находится где-то в функции get_search_result и если посмотеть то она вызывается из CompletionFieldMixin и имеет следующий интересный кусок:

Получение данных get_search_result

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

Реализация метода name_search выглядит так:

Реализация name_search

Получается что данный метод просто вызывает у модели метод name_search, который в свою очередь возвращает id и имя объекта.

Результат анализа

Из всего выше описанного получается следующее решение:

  1. Написать метод подобный name_search, который будет возвращать кроме id и имя объекта, еще и имя класса для расскраски
  2. Переопределить метод 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;
}

После чего установить или обновить модуль.

Заключение

В итоге получился виджет который выводит следующее:

Кусок с автокомплитом из render_editable

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

 
comments powered by Disqus