# -*- coding: utf-8 -*-
# 通用导出优化方案
# 1. 官方切片优化方案 + 维护机导向
# 2. sql快速导出
# 优化静态文件模糊查询范围，提高性能
# Created by HJH (jiahao.huang@yunside.com) @ 2022/4/1
from odoo import api, models, SUPERUSER_ID
from odoo.models import fix_import_export_id_paths, BaseModel
from odoo.addons.base.ir.ir_qweb.assetsbundle import AssetsBundle
import logging
import gc

_logger = logging.getLogger(__name__)


def wrap_get_attachments(self, type):
    """ Return the ir.attachment records for a given bundle. This method takes care of mitigating
    an issue happening when parallel transactions generate the same bundle: while the file is not
    duplicated on the filestore (as it is stored according to its hash), there are multiple
    ir.attachment records referencing the same version of a bundle. As we don't want to source
    multiple time the same bundle in our `to_html` function, we group our ir.attachment records
    by file name and only return the one with the max id for each group.
    """
    url_pattern = '/web/content/%/{0}{1}.{2}'.format(self.name, '.%' if type == 'css' else '', type)
    self.env.cr.execute("""
         SELECT max(id)
           FROM ir_attachment
          WHERE create_uid = %s
            AND url is not null
            AND url like %s
       GROUP BY datas_fname
       ORDER BY datas_fname
     """, [SUPERUSER_ID, url_pattern])
    attachment_ids = [r[0] for r in self.env.cr.fetchall()]
    return self.env['ir.attachment'].sudo().browse(attachment_ids)

AssetsBundle.get_attachments = wrap_get_attachments


class JDBase(models.AbstractModel):

    _inherit = 'base'

    def __export_xml_id(self):
        """ Return a valid xml_id for the record ``self``. """
        if not self._is_an_ordinary_table():
            raise Exception(
                "You can not export the column ID of model %s, because the "
                "table %s is not an ordinary table."
                % (self._name, self._table))
        ir_model_data = self.sudo().env['ir.model.data']
        data = ir_model_data.search([('model', '=', self._name), ('res_id', '=', self.id)])
        if data:
            if data[0].module:
                return '%s.%s' % (data[0].module, data[0].name)
            else:
                return data[0].name
        else:
            postfix = 0
            name = '%s_%s' % (self._table, self.id)
            while ir_model_data.search([('module', '=', '__export__'), ('name', '=', name)]):
                postfix += 1
                name = '%s_%s_%s' % (self._table, self.id, postfix)
            ir_model_data.create({
                'model': self._name,
                'res_id': self.id,
                'module': '__export__',
                'name': name,
            })
            return '__export__.' + name

    # _export_rows 内存导出方法
    # 使用官方优化方案增强 
    # 将记录集切分为1000条记录一片，导出一片后清理缓存
    @api.multi
    def _export_rows(self, fields, _is_toplevel_call=True):
        """ Export fields of the records in ``self``.

            :param fields: list of lists of fields to traverse
            :return: list of lists of corresponding values
        """
        import_compatible = self.env.context.get('import_compat', True)
        lines = []

        def splittor(rs):
            """ Splits the self recordset in batches of 1000 (to avoid
            entire-recordset-prefetch-effects) & removes the previous batch
            from the cache after it's been iterated in full
            """
            for idx in range(0, len(rs), 1000):
                sub = rs[idx:idx+1000]
                for rec in sub:
                    yield rec
                rs.invalidate_cache(ids=sub.ids)
                gc.collect()
        if not _is_toplevel_call:
            splittor = lambda rs: rs

        for record in splittor(self):
            # main line of record, initially empty
            current = [''] * len(fields)
            lines.append(current)
            # current length of lines
            lines_num = len(lines)

            # list of primary fields followed by secondary field(s)
            primary_done = []

            # process column by column
            for i, path in enumerate(fields):
                if not path:
                    continue

                name = path[0]
                if name in primary_done:
                    continue

                if name == '.id':
                    current[i] = str(record.id)
                elif name == 'id':
                    current[i] = record.__export_xml_id()
                else:
                    field = record._fields[name]
                    value = record[name]

                    # this part could be simpler, but it has to be done this way
                    # in order to reproduce the former behavior
                    if not isinstance(value, BaseModel):
                        current[i] = field.convert_to_export(value, record)
                    else:
                        primary_done.append(name)

                        # in import_compat mode, m2m should always be exported as
                        # a comma-separated list of xids in a single cell
                        if import_compatible and field.type == 'many2many' and len(path) > 1 and path[1] == 'id':
                            xml_ids = [r.__export_xml_id() for r in value]
                            current[i] = ','.join(xml_ids) or False
                            continue

                        # recursively export the fields that follow name; use
                        # 'display_name' where no subfield is exported
                        fields2 = [(p[1:] or ['display_name'] if p and p[0] == name else [])
                                   for p in fields]
                        lines2 = value._export_rows(fields2, _is_toplevel_call=False)
                        if lines2:
                            # merge first line with record's main line
                            for j, val in enumerate(lines2[0]):
                                if val or isinstance(val, bool):
                                    current[j] = val
                            # append the other lines at the end
                            lines += lines2[1:]
                        else:
                            # 关联字段取值为空 False -> ''
                            # current[i] = False
                            current[i] = ''
        
            # 后处理: 存在明细字段导出时，冗余表头字段
            # len(lines) > lines_num: 存在下穿的明细字段 one2many/many2one
            if len(lines) > lines_num:
                detail_lines = lines[lines_num:]
                for dl in detail_lines:
                    for i, path in enumerate(fields):
                        if len(path) == 1 and (_is_toplevel_call or self._fields[path[0] if path[0] != '.id' else 'id'].type not in ('one2many', 'many2many')):
                            dl[i] = current[i]
                        elif len(path) == 2 and self._fields[path[0]].type == 'many2one' and \
                            self.env[self._fields[path[0]].comodel_name]._fields[path[1] if path[1] != '.id' else 'id'].type not in ('one2many', 'many2many'):
                                dl[i] = current[i]
                        elif len(path) == 3 and self._fields[path[0]].type == 'many2one' and \
                            self.env[self._fields[path[0]].comodel_name]._fields[path[1]].type == 'many2one' and \
                                self.env[self.env[self._fields[path[0]].comodel_name]._fields[path[1]].comodel_name]._fields[path[2] if path[2] != '.id' else 'id'].type not in ('one2many', 'many2many'):
                                    dl[i] = current[i]

        return lines

    # _export_rows_by_ids sql导出方法
    @api.model
    def _export_rows_by_ids(self, ids, fields):
        """ 
            Export fields of the records with ``ids``
        :params:
            :param ids: list of ids of records to export
            :param fields: list of lists of fields to traverse  [[id], [name], [company_id, name], [company_id, parent_id, name], ...]
        :return: list of dicts of corresponding values
        """
        lines = []
        self_store = []        
        rel_store, rrel_store = {}, {}

        # 将需要导出的字段区分为本模型字段、下穿一层以及下穿两层的字段
        self._extract_fields(fields, self_store, rel_store, rrel_store)
        # generate export sql 生成 sql
        sql = self._generate_export_sql(ids, self_store, rel_store, rrel_store)
        _logger.info('======== Export sql ========')
        _logger.info(sql)

        if sql:
            cr = self.env.cr
            cr.execute(sql)
            lines = cr.dictfetchall() or []
        return lines

    # _extract_fields 将需要导出的字段区分为本模型字段、下穿一层以及下穿两层的字段 
    # 同时要验证字段是否为 store, non_store 的字段略过
    @api.model
    def _extract_fields(self, fields, self_store, rel_store, rrel_store):
        """
            Optimize 
            Extract fields for export optimization
        :params:
            :params fields: a list of list of fields to export  [[id], [name], [company_id, name], [company_id, parent_id, name], ...]
            :params self_store: a list of stored fields of self [id, name, ...]
            :params rel_store: a dict of list of stored fields of self.relational(one2many, many2many, many2one)    {'company_id': [name, ...], ...}
            :params rrel_store: a dict of list of stored fields of self.relational.relational   {'company_id/parent_id': [name, ...], ...}
        :returns:
        """
        for field in fields:
            if not field:
                continue
            if len(field) == 0:
                continue
            if len(field) == 1:     # 当前模型字段，生成 self_store[] 
                # self.a, self.b, self.c ==> [a, b, c]
                name = field[0]
                # 导出时前端传入的字段名 .id 实际为 id 字段，id 实际为外部 id
                if name == '.id':
                    self_store.append(u'id')
                else:
                    field = self._fields[name]
                    if field.store:
                        self_store.append(field.name)
            elif len(field) == 2:   # 下穿一层，生成 rel_store{}, 键为 field[0] 即关联字段，值为同一关联字段下穿的字段数组 
                # self.a_id.a, self.a_id.b, self.a_id.c ==> {a_id: [a, b, c]}
                name = field[0]
                # fields.Many2one, Many2many, One2many, comodel_name字段都为关联的模型名
                fm = self._fields[name].comodel_name
                if field[1] == '.id':
                    if rel_store.get(name):
                        rel_store[name].append(u'id')
                    else:
                        rel_store[name] = [u'id']
                elif field[1] == 'id':
                    # [[company_id], [id]] 实际为 company_id 的外部 id，在前端表现为 组织 而不是 组织/ID([[company_id], [.id]])
                    # sql 快速导出不导出外部 id（未进行过导出的记录的外部 ID 需要计算）
                    # 上级模型字段 self_store[] 增加 company_id 字段，下一步生成 sql 时将识别为 company_id/name
                    self_store.append(field[0])
                else:
                    fk = self.env[fm]._fields[field[1]]
                    if fk.store:
                        if rel_store.get(name):
                            rel_store[name].append(field[1])
                        else:
                            rel_store[name] = [field[1]]
            else:                   # 下穿两层，生成 rrel_store{}, 键为 field[0]/field[1], 值为 field[0] 下穿到 field[1] 再往下下穿的字段数组
                # self.a_id.b_id.a, self.a_id.b_id.b, self.a_id.b_id.c   ==> {a_id/b_id: [a, b, c]}
                name = field[0]
                # fields.Many2one, Many2many, One2many, comodel_name字段都为关联的模型名
                fm = self._fields[name].comodel_name
                fk = self.env[fm]._fields[field[1]]
                fm = fk.comodel_name
                key = u'' + field[0] + '/' + field[1]
                if field[2] == '.id':
                    if rrel_store.get(key):
                        rrel_store[key].append(u'id')
                    else:
                        rrel_store[key] = [u'id']
                elif field[2] == 'id':
                    # 外部 ID 同样增加到上级模型字段中
                    if rel_store.get(field[0]):
                        rel_store[field[0]].append(field[1])
                    else:
                        rel_store[field[0]] = [field[1]]
                else:
                    fk = self.env[fm]._fields[field[2]]
                    if fk.store:
                        if rrel_store.get(key):
                            rrel_store[key].append(field[2])
                        else:
                            rrel_store[key] = [field[2]]

    # _generate_export_sql 生成导出 sql
    @api.model
    def _generate_export_sql(self, ids, self_store, rel_store, rrel_store):
        """
            Optimize
            Sql generator method for export optimization
        :params:
            :params ids: a list of record ids
            :params self_store: a list of stored fields of self [id, name, ...]
            :params rel_store: a dict of list of stored fields of self.relational(one2many, many2many, many2one)    {'company_id': [name, ...], ...}
            :params rrel_store: a dict of list of stored fields of self.relational.relational   {'company_id/parent_id': [name, ...], ...}
        :returns:
            :rtype: str
        """
        if self_store == [] and rel_store == {} and rrel_store == {}:
            return ''
        tables = {}     # tables: {表别名: 计数器}  别名重复时，向末尾添加计数值
        alias = {}      # alias: {id: 本模型表别名, a_id: 下穿1模型关联表别名, a_id/b_id: 下穿2模型关联表别名} 
                        # 一个关联字段对应一个表（many2many中间表不需要记录）
                        # 必须记录关联字段对应的表别名，否则下穿2层时，上层表别名丢失
        sql = """"""
        select_sql = """ SELECT """
        self_alias = self._get_table_alias(self._table, tables) # 本模型表别名
        from_sql = """ FROM %s %s """ % (self._table, self_alias)
        alias.update({
            'id': self_alias
        })
        for field in self_store:
            # self.field
            # use field as field alias
            field_type = self._fields[field].type
            if field_type not in ('one2many', 'many2many', 'many2one'):
                # 基础字段
                # 直接使用 field 作为 字段别名，除了 id 字段用 .id 做别名（适配导出规则）
                # 字段别名将与前端传入的字段名一致
                field_alias = field if field != 'id' else '.id'
                if field_type != 'selection':
                    select_sql += """ %s.%s AS "%s", """ % (self_alias, field, field_alias)
                else:   # selection 字段显示中文
                    selection = self._fields[field].selection
                    case_sql = """ CASE """
                    for val_str in selection:
                        case_sql += """ WHEN %s.%s = '%s' THEN '%s' """ % (self_alias, field, val_str[0], val_str[1])
                    case_sql += """ ELSE 'FALSE' END AS "%s", """ % (field_alias, )
                    select_sql += case_sql
            else:
                # 关联字段代表导出下层关联模型的外部 ID, sql 快速导出中用 name 代替
                # 传到下层字段 rel_store 中, 字段名为 ''
                if rel_store.get(field):
                    rel_store[field].append('')
                else:
                    rel_store.update({
                        field: ['']
                    })

        for key in rel_store.keys():
            # rel_store {'a_id': ['a', 'b']}
            # self.a_id.a
            items = rel_store[key]
            field = self._fields[key]                   # field
            comodel_name = field.comodel_name           # name of comodel
            comodel = self.env[comodel_name]._table     # name of comodel table
            if not alias.get(key):
                coalias = self._get_table_alias(comodel, tables)    # alias of comodel table
                alias.update({
                    key: coalias
                })
            coalias = alias[key]

            # 下穿一层 key 为关联字段，一定为 many2one, one2many, many2many 其中一种
            if field.type == 'many2one':
                # Many2one
                # many2one 关联字段即为 key
                from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id """ % (comodel, coalias, self_alias, key, coalias)
            elif field.type == 'one2many':
                # One2many
                # inverse_name: one2many 中与表头关联的字段（parent_id）
                inverse_key = field.inverse_name
                from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id """ % (comodel, coalias, coalias, inverse_key, self_alias)
            elif field.type == 'many2many':
                # Many2many
                relation = field.relation  # relation: 中间表
                column1 = field.column1    # colum1: left_id ==> self
                column2 = field.column2    # column2: right_id ==> comodel
                relation_alias = self._get_table_alias(relation, tables)
                # 中间表别名不需要保存
                from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id
                                LEFT JOIN %s %s ON %s.%s = %s.id """ % (relation, relation_alias, relation_alias, column1, self_alias, 
                                                                        comodel, coalias, relation_alias, column2, coalias)

            for item in items:
                # self.key.item
                # use key/item as field alias
                comodel = self.env[comodel_name]
                if item == '':
                    # '' 字段，从 self_store 抛到 rel_store, 导出 comodel 的外部 ID
                    # 如果 comodel 中存在 name 字段，且 name 字段为 store, 则导出 comodel.name 
                    # comodel 中不存在 name 字段，或 name 字段 non_store, 则导出空值
                    # field alias 为 key/id (与前端字段名规则一致)
                    if 'name' in comodel._fields and comodel._fields['name'].store:
                        select_sql += """ %s.%s AS "%s", """ % (coalias, 'name', key + '/id')
                    else:
                        select_sql += """ '' AS "%s", """ % (key + '/id',)
                else:
                    field_type = comodel._fields[item].type
                    # 使用 key/item(a_id/a) 作为字段别名，除了 id 字段用 key/.id 做别名（适配导出规则）
                    # 字段别名将与前端传入的字段名一致
                    key_alias = key + '/' + item if item != 'id' else key + '/.id'
                    if field_type not in ('one2mnay', 'many2one', 'many2many'):
                        # 基础字段
                        if field_type != 'selection':
                            select_sql += """ %s.%s AS "%s", """ % (coalias, item, key_alias)
                        else:   # selection 字段显示中文
                            selection = comodel._fields[item].selection
                            case_sql = """ CASE """
                            for val_str in selection:
                                case_sql += """ WHEN %s.%s = '%s' THEN '%s' """ % (coalias, item, val_str[0], val_str[1])
                            case_sql += """ ELSE 'FALSE' END AS "%s", """ % (key_alias, )
                            select_sql += case_sql
                    else:
                        # 关联字段代表导出下层关联模型的外部 ID, sql 快速导出中用 name 代替
                        # 传到下层字段 rrel_store 中, 字段名为 ''
                        if rrel_store.get(key_alias):
                            rrel_store[key_alias].append('')
                        else:
                            rrel_store.update({
                                key_alias: ['']
                            })

        for k in rrel_store.keys():
            # self.a_id.b_id.a
            items = rrel_store[k]
            key = k.split('/')  # k 为 a_id/b_id
            comodel_name = self._fields[key[0]].comodel_name
            comodel1 = self.env[comodel_name]._table
            
            if not alias.get(key[0]):
                # key: a_id/b_id, alias 中找不到 a_id 对应的关联表别名
                # a_id 关联的表还没 join
                # 需要先 join 上级关联表
                coalias1 = self._get_table_alias(comodel1, tables)
                alias.update({
                    key[0]: coalias1
                })
                field = self._fields[key[0]]
                if field.type == 'many2one':
                    # Many2one
                    from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id """ % (comodel1, coalias1, self_alias, key[0], coalias1)
                elif field.type == 'one2many':
                    # One2many
                    # inverse_name: one2many 中与表头关联的字段（parent_id）
                    inverse_key = field.inverse_name
                    from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id """ % (comodel1, coalias1, coalias1, inverse_key, self_alias)
                elif field.type == 'many2many':
                    # Many2many
                    relation = field.relation  # relation: 中间表
                    column1 = field.column1    # colum1: left_id ==> self
                    column2 = field.column2    # column2: right_id ==> comodel1
                    relation_alias = self._get_table_alias(relation, tables)
                    # 中间表别名不需要保存
                    from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id
                                    LEFT JOIN %s %s ON %s.%s = %s.id """ % (relation, relation_alias, relation_alias, column1, self_alias, 
                                                                            comodel1, coalias1, relation_alias, column2, coalias1) 
            
            field = self.env[comodel_name]._fields[key[1]]
            comodel_name = field.comodel_name
            comodel = self.env[comodel_name]
            comodel2 = comodel._table
            coalias2 = self._get_table_alias(comodel2, tables)
            alias.update({
                k: coalias2
            })
            # 从 alias 中获取上级关联表别名
            coalias1 = alias[key[0]]

            if field.type == 'many2one':
                # Many2one
                from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id """ % (comodel2, coalias2, coalias1, key[1], coalias2)
            elif field.type == 'one2many':
                # One2many
                # inverse_name: one2many 中与表头关联的字段（parent_id）
                inverse_key = field.inverse_name
                from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id """ % (comodel2, coalias2, coalias2, inverse_key, coalias1)
            elif field.type == 'many2many':
                # Many2many
                relation = field.relation  # relation: 中间表
                column1 = field.column1    # colum1: left_id ==> comodel1
                column2 = field.column2    # column2: right_id ==> comodel2
                relation_alias = self._get_table_alias(relation, tables)
                # 中间表别名不需要保存
                from_sql ++ """ LEFT JOIN %s %s ON %s.%s = %s.id 
                                LEFT JOIN %s %s ON %s.%s = %s.id """ % (relation, relation_alias, relation_alias, column1, coalias1,
                                                                        comodel2, coalias2, relation_alias, column2, coalias2)

            for item in items:
                # self.key[0].key[1].item
                # use key[0]/key[1]/item as field alias
                if item == '':
                    # '' 字段，从 rel_store 抛到 rrel_store, 导出 comodel 的外部 ID
                    # 如果 comodel 中存在 name 字段，且 name 字段为 store, 则导出 comodel.name 
                    # comodel 中不存在 name 字段，或 name 字段 non_store, 则导出空值
                    # field alias 为 key[0]/key[1]/id (与前端字段名规则一致)
                    if 'name' in comodel._fields and comodel._fields['name'].store:
                        select_sql += """ %s.%s AS "%s", """ % (coalias2, 'name', k + '/id')
                    else:
                        select_sql += """ '' AS "%s", """ % (k + '/id',)
                else:
                    # 使用 k/item(a_id/b_id/a) 作为字段别名，除了 id 字段用 k/.id 做别名（适配导出规则）
                    # 字段别名将与前端传入的字段名一致
                    key_alias = k + '/' + item if item != 'id' else k + '/.id'
                    field = comodel._fields[item]
                    if field.type not in ('one2many', 'many2many', 'many2one'):
                        # 基础字段
                        if field.type != 'selection':
                            select_sql += """ %s.%s AS "%s", """ % (coalias2, item, key_alias)
                        else:   # selection 显示中文
                            selection = comodel._fields[item].selection
                            case_sql = """ CASE """
                            for val_str in selection:
                                case_sql += """ WHEN %s.%s = '%s' THEN '%s' """ % (coalias2, item, val_str[0], val_str[1])
                            case_sql += """ ELSE 'FALSE' END AS "%s", """ % (key_alias, )
                            select_sql += case_sql
                    else:
                        # 出现self.a_id.b_id.c_id
                        # 下穿的最后一级选择了一个关联字段 => 导出 c_id 关联模型 post_model 的外部 ID
                        # 如果 post_model 中存在 name 字段，且 name 字段为 store, 则导出 comodel.name 
                        # post_model 中不存在 name 字段，或 name 字段 non_store, 则导出空值
                        post_model = self.env[field.comodel_name]
                        coalias = self._get_table_alias(post_model._table, tables)
                        if 'name' in post_model._fields and post_model._fields['name'].store:
                            if field.type == 'many2one':
                                # Many2one
                                from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id """ % (post_model._table, coalias, coalias2, item, coalias)   
                            elif field.type == 'one2many':
                                # One2many
                                # inverse_name: one2many 中与表头关联的字段（parent_id）
                                inverse_key = field.inverse_name
                                from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id """ % (post_model._table, coalias, coalias, inverse_key, coalias2)
                            elif field.type == 'many2many':
                                # Many2many
                                relation = field.relation  # relation: 中间表
                                column1 = field.column1    # colum1: left_id ==> comodel2
                                column2 = field.column2    # column2: right_id ==> post_model
                                relation_alias = self._get_table_alias(relation, tables)
                                from_sql += """ LEFT JOIN %s %s ON %s.%s = %s.id 
                                                LEFT JOIN %s %s ON %s.%s = %s.id """ % (relation, relation_alias, relation_alias, column1, coalias2,
                                                                                        post_model._table, coalias, relation_alias, column2, coalias)
                            select_sql += """ %s.%s AS "%s", """ % (coalias, 'name', key_alias)
                        else:
                            select_sql += """ '' AS "%s", """ % (key_alias)
        
        where_sql = """ WHERE %s.id = ANY(ARRAY%s)""" % (self_alias, ids)
        # order_sql is optional
        order_sql = """ ORDER BY %s.id """ % (self_alias,)
        # remove excessive ', ' in select_sql
        # 去掉 select_sql 中多余的 ', '
        select_sql = select_sql[:-2] + ' '
        sql += select_sql + from_sql + where_sql + order_sql
        return sql

    # _get_table_alias 获取表别名方法
    @api.model
    def _get_table_alias(self, table, tables):
        """
            Optimize
            Method to get alias of table
        :params:
            :params table: table name of model
            :params tables: a dict of alias and count pairs
        :returns:
            :rtype: str
        """
        """
            ``table``:  model._table
            ``tables``: {alias: count}
        """
        # res_company => rc, rc1, rc2, ...
        alias = "".join(t[0] for t in table.split('_'))
        if tables.get(alias) or tables.get(alias) == 0:
            # tables.get(alias) = 0 == False
            # 别名已被使用，添加 postfix 计数标识，避免重复，postfix ++, tables 记录别名 postfix
            postfix = tables[alias] + 1
            tables.update({
                alias: postfix
            })
            alias += str(postfix)
        else:
            tables.update({
                alias: 0
            })
        return alias
    
    # export_data_by_ids 参考原方法 export_data 
    # 通过传入的记录 id 做sql导出
    @api.model
    def export_data_by_ids(self, ids, fields_to_export, raw_data=False):
        """ Export fields for selected objects

            :param fields_to_export: list of fields
            :param raw_data: True to return value in native Python type
            :rtype: dictionary with a *datas* matrix

            This method is used when optimizing data export via client menu
        """
        fields_to_export = map(fix_import_export_id_paths, fields_to_export)
        if raw_data:
            self = self.with_context(export_raw_data=True)
        return {'datas': self._export_rows_by_ids(ids, fields_to_export)}
