예제 #1
0
 def read_group(self,
                domain,
                fields,
                groupby,
                offset=0,
                limit=None,
                orderby=False,
                lazy=True):
     """ Override to set the `inventory_quantity` field if we're in "inventory mode" as well
     as to compute the sum of the `available_quantity` field.
     """
     if 'available_quantity' in fields:
         if 'quantity' not in fields:
             fields.append('quantity')
         if 'reserved_quantity' not in fields:
             fields.append('reserved_quantity')
     result = super(StockQuant, self).read_group(domain,
                                                 fields,
                                                 groupby,
                                                 offset=offset,
                                                 limit=limit,
                                                 orderby=orderby,
                                                 lazy=lazy)
     for group in result:
         if self._is_inventory_mode():
             group['inventory_quantity'] = group.get('quantity', 0)
         if 'available_quantity' in fields:
             group['available_quantity'] = group['quantity'] - group[
                 'reserved_quantity']
     return result
예제 #2
0
    def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
        if all('on_time_rate' not in field for field in fields):
            res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
            return res

        for field in fields:
            if 'on_time_rate' not in field:
                continue

            fields.remove(field)

            agg = field.split(':')[1:]
            if agg and agg[0] != 'sum':
                raise NotImplementedError('Aggregate functions other than \':sum\' are not allowed.')

            qty_total = field.replace('on_time_rate', 'qty_total')
            if qty_total not in fields:
                fields.append(qty_total)
            qty_on_time = field.replace('on_time_rate', 'qty_on_time')
            if qty_on_time not in fields:
                fields.append(qty_on_time)
            break

        res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)

        for group in res:
            if group['qty_total'] == 0:
                on_time_rate = 100
            else:
                on_time_rate = group['qty_on_time'] / group['qty_total'] * 100
            group.update({'on_time_rate': on_time_rate})

        return res
예제 #3
0
    def _generate_order_by(self, order_spec, query):
        '''
        replace sort field, if sort by key.
        replace on sort by prefix, number
        '''
        new_order_spec = order_spec
        if order_spec:
            fields = []
            for order_part in order_spec.split(','):
                order_split = order_part.strip().split(' ')
                order_field = order_split[0].strip()
                order_direction = order_split[1].strip().upper() if len(order_split) == 2 else ''
                fields.append((order_field, order_direction))

            key_field = [x for x in fields if x[0] == 'key']
            if key_field:
                direction = key_field[0][1]
                fields.remove(key_field[0])
                fields.extend([('prefix', direction), ('number', direction)])

            fields = map(lambda x: ' '.join(x), fields)
            new_order_spec = ','.join(fields)

        order_by = super(Task, self)._generate_order_by(new_order_spec, query)
        return order_by
예제 #4
0
    def get_allowed_items(self, params):
        fields = ['id', 'display_name', 'name']
        is_project = False
        domain = [('name', 'ilike', params['search'])]

        if params['model'] == 'project.project':
            fields.append('privacy_visibility')
            fields.append('message_is_follower')
            is_project = True

        if len(self.env['ir.model.fields'].search([
            ('model', '=', params['model']), ('name', '=', 'display_name'),
            ('store', '=', True)
        ])):
            domain = [('display_name', 'ilike', params['search'])]

        sr_items = self.env[params['model']].search_read(domain,
                                                         fields,
                                                         limit=10)
        allowed_items = []

        for sr_i in sr_items:
            if is_project:
                # if sr_i['privacy_visibility'] != 'employees':
                if not sr_i['message_is_follower']:
                    continue

                del sr_i['privacy_visibility']
                del sr_i['message_is_follower']

            allowed_items.append(sr_i)

        return allowed_items
예제 #5
0
 def _address_fields(self):
     """ Returns the list of address fields that are synced from the parent
     when the `use_parent_address` flag is set.
     """
     fields = super(ResPartner, self)._address_fields()
     fields.append('company')
     return fields
예제 #6
0
파일: models.py 프로젝트: Alfa-90/OCB
    def search_panel_select_range(self, field_name):
        """
        Return possible values of the field field_name (case select="one")
        and the parent field (if any) used to hierarchize them.

        :param field_name: the name of a many2one category field
        :return: {
            'parent_field': parent field on the comodel of field, or False
            'values': array of dictionaries containing some info on the records
                        available on the comodel of the field 'field_name'.
                        The display name (and possibly parent_field) are fetched.
        }
        """
        field = self._fields[field_name]
        supported_types = ['many2one']
        if field.type not in supported_types:
            raise UserError(
                _('Only types %(supported_types)s are supported for category (found type %(field_type)s)'
                  ) % ({
                      'supported_types': supported_types,
                      'field_type': field.type
                  }))

        Comodel = self.env[field.comodel_name]
        fields = ['display_name']
        parent_name = Comodel._parent_name if Comodel._parent_name in Comodel._fields else False
        if parent_name:
            fields.append(parent_name)
        return {
            'parent_field':
            parent_name,
            'values':
            Comodel.with_context(hierarchical_naming=False).search_read(
                [], fields),
        }
예제 #7
0
 def read(self, fields=None, load='_classic_read'):
     if fields == [
             'name', 'sequence', 'parent_id', 'action', 'web_icon',
             'web_icon_data'
     ]:
         fields.append('x_menu_category_id')
     return super(IrUiMenu, self).read(fields, load)
예제 #8
0
파일: models.py 프로젝트: essamcis/silver
    def read(self, fields=None, load='_classic_read'):
        """ Override to explicitely call check_access_rule, that is not called
            by the ORM. It instead directly fetches ir.rules and apply them. """
        if fields and 'ks_msg_edit' not in fields:
            fields.append('ks_msg_edit')

        return super(KsChatDelete, self).read(fields=fields, load=load)
예제 #9
0
    def _get_print_field(self):
        fields = []
        static_properties_obj = self.env['product.properties.static']
        dinamic_properties_obj = self.env['product.properties.category']
        for categ in dinamic_properties_obj.search([]):
            for line in categ.lines_ids:
                for g in line.with_context(lang="en"):
                    fields.append('print_' +
                                  g.name.name.lower().replace(" ", "_") +
                                  "_%d" % g.name.id)
        for record in self.print_properties:
            for g in filter(
                    lambda r: r not in static_properties_obj.ignore_fields(),
                    static_properties_obj.with_context(lang="en")._fields):
                setattr(self, 'print_' + g.lower().replace(" ", "_"),
                        record.print)
            for field in fields:
                parts = field.split("_")
                if record.name == parts[1] and record.name.id == int(parts[2]):
                    setattr(self, field, record.print)

# @api.multi
# def read(self, fields=None, load='_classic_read'):
#static_properties_obj = self.env['product.properties.static']
#dinamic_properties_obj = self.env['product.properties.category']
#pfields = []
#for categ in dinamic_properties_obj.search([]):
#  for line in categ.lines_ids:
#     for g in line.with_context(lang="en"):
#        pfields.append('print_' + g.name.name.lower().replace(" ", "_")+"_%d" % g.name.id)
#fields1 = fields or list(self.fields_get())
# #_logger.info("FIELDS %s" % list(self.fields_get()))
        other_fields = {}
예제 #10
0
파일: models.py 프로젝트: elementgreen/odoo
    def search_panel_select_range(self, field_name):
        """
        Return possible values of the field field_name (case select="one")
        and the parent field (if any) used to hierarchize them.

        :param field_name: the name of a many2one category field
        :return: {
            'parent_field': parent field on the comodel of field, or False
            'values': array of dictionaries containing some info on the records
                        available on the comodel of the field 'field_name'.
                        The display name (and possibly parent_field) are fetched.
        }
        """
        field = self._fields[field_name]
        supported_types = ['many2one']
        if field.type not in supported_types:
            raise UserError(_('Only types %(supported_types)s are supported for category (found type %(field_type)s)') % ({
                            'supported_types': supported_types, 'field_type': field.type}))

        Comodel = self.env[field.comodel_name]
        fields = ['display_name']
        parent_name = Comodel._parent_name if Comodel._parent_name in Comodel._fields else False
        if parent_name:
            fields.append(parent_name)
        return {
            'parent_field': parent_name,
            'values': Comodel.with_context(hierarchical_naming=False).search_read([], fields),
        }
예제 #11
0
 def _get_eshop_fields(self):
     fields = super()._get_eshop_fields()
     for field in fields:
         if "image" in field:
             fields.remove(field)
     fields.append("image_write_date")
     fields.append("image_write_date_hash")
     return fields
예제 #12
0
 def default_get(self, fields):
     res = super(ResPartnerIdNumber, self).default_get(fields)
     # It seems to be a bug in native odoo that the field partner_id
     # is not in the fields list by default. A workaround is required
     # to force this.
     if "default_partner_id" in self._context and "partner_id" not in fields:
         fields.append("partner_id")
         res["partner_id"] = self._context.get("default_partner_id")
     return res
예제 #13
0
 def onchange(self, values, field_name, field_onchange):
     """ Fix this issue https://github.com/ingadhoc/account-payment/issues/189
     """
     fields = []
     for field in field_onchange.keys():
         if field.startswith(('to_pay_move_line_ids.')):
             fields.append(field)
     for field in fields:
         del field_onchange[field]
     return super().onchange(values, field_name, field_onchange)
예제 #14
0
 def _get_invoice_line_key_cols(self):
     fields = [
         'name', 'origin', 'discount', 'invoice_line_tax_ids', 'price_unit',
         'product_id', 'account_id', 'account_analytic_id', 'uom_id',
         'sale_line_ids'
     ]
     for field in ['analytics_id']:
         if field in self.env['account.invoice.line']._fields:
             fields.append(field)
     return fields
예제 #15
0
 def _sms_get_partner_fields(self):
     """ This method returns the fields to use to find the contact to link
     whensending an SMS. Having partner is not necessary, having only phone
     number fields is possible. However it gives more flexibility to
     notifications management when having partners. """
     fields = []
     if hasattr(self, 'partner_id'):
         fields.append('partner_id')
     if hasattr(self, 'partner_ids'):
         fields.append('partner_ids')
     return fields
예제 #16
0
 def onchange(self, values, field_name, field_onchange):
     """Necesitamos hacer esto porque los onchange que agregan lineas,
     cuando se va a guardar el registro, terminan creando registros.
     """
     fields = []
     for field in field_onchange.keys():
         if field.startswith(('move_line_ids.')):
             fields.append(field)
     for field in fields:
         del field_onchange[field]
     return super().onchange(values, field_name, field_onchange)
예제 #17
0
 def check_update_fields(self, vals):
     """this method is call from the write methods of selected model whose records is updated
     example: product.product and product.template"""
     if self.id:
         fields = []
         for f in self.elastic_index_id.field_ids:
             fields.append(f.name)
         intersectionSet = set(vals).intersection(fields)
     else:
         intersectionSet = False
     return intersectionSet
예제 #18
0
    def _convert_import_data(self, fields, options):
        data, fields = super(BaseImport,
                             self)._convert_import_data(fields, options)
        if self._context.get('import_order_line'):
            import_field = options.get('import_field')
            order_id = options.get('order_id')
            if import_field and order_id:
                fields.append(import_field)
                for row in data:
                    row.append(order_id)

        return data, fields
예제 #19
0
    def search_read(self,
                    domain=None,
                    fields=None,
                    offset=0,
                    limit=None,
                    order=None):
        if not fields:
            fields = []
        fields.append('editable')

        return super(Meeting, self).search_read(domain, fields, offset, limit,
                                                order)
예제 #20
0
 def _get_fields(self, fields=None, model=None):
     for field in model.field_id:
         if field.ttype != 'one2many':
             if field.is_report:
                 fields.append(field.id)
         else:
             model = self.env['ir.model'].search(
                 [('model', '=', field.relation)], limit=1)
             if model.is_report and field.relation != self.report_id.model_id.model:
                 self._get_fields(fields=fields if fields else [],
                                  model=model)
     return fields
예제 #21
0
    def get_cn23(self, record, values, package):
        article = []
        for line in package.quant_ids:
            price = 1
            if record.sale_id:
                price = record.env["sale.order.line"].search(
                    [('order_id', '=', record.sale_id.id),
                     ('product_id', '=', line.product_id.id)],
                    limit=1).price_unit or line.product_id.lst_price
            product = {
                "description": line.product_id.name,
                "quantity": int(line.quantity),
                "weight": line.product_id.weight,
                "value": price,
                "hsCode": line.product_id.hs_code,
                # todo : gestion des pays origine
                "originCountry": 'FR',
            }
            article.append(product)
        cn23 = {
            "includeCustomsDeclarations": 1,
            "contents": {
                "article": article,
                "category": {
                    "value": 3
                }
            },
        }
        values["letter"]["customsDeclarations"] = cn23

        # set delivery price if not set
        if record.sale_id:
            delivery_price = record.get_delivery_price()
            values["letter"]["service"]["totalAmount"] = int(delivery_price *
                                                             100)

        values["letter"]["service"]["returnTypeChoice"] = 3

        field = {
            'key': 'EORI',
            'value': record.env.company.phi_eori_number,
        }
        custom_fields = []
        fields = []
        custom_fields.append(field)
        fields.append(field)
        fields = {
            "customField": custom_fields,
            "field": custom_fields,
        }
        values["fields"] = fields
        return values
예제 #22
0
 def _prepare_user_group_ids_from_default_get(self):
     ResGroups = self.env["res.groups"]
     ResUsers = self.env["res.users"]
     res1 = ResGroups.get_groups_by_application()
     fields = []
     for item in res1:
         if item[1] == "boolean":
             for group in item[2]:
                 fields.append(name_boolean_group(group.id))
         elif item[1] == "selection":
             fields.append(name_selection_groups(item[2].ids))
     res2 = ResUsers.default_get(fields)
     return res2["groups_id"][0][2]
 def _prepare_user_group_ids_from_default_get(self):
     group_obj = self.env['res.groups']
     user_obj = self.env['res.users']
     res1 = group_obj.get_groups_by_application()
     fields = []
     for item in res1:
         if item[1] == 'boolean':
             for group in item[2]:
                 fields.append(name_boolean_group(group.id))
         elif item[1] == 'selection':
             fields.append(name_selection_groups(item[2].ids))
     res2 = user_obj.default_get(fields)
     return res2['groups_id'][0][2]
예제 #24
0
    def _get_bindings(self, model_name, debug=False):
        """ Retrieve the list of actions bound to the given model.

           :return: a dict mapping binding types to a list of dict describing
                    actions, where the latter is given by calling the method
                    ``read`` on the action record.
        """
        cr = self.env.cr
        IrModelAccess = self.env['ir.model.access']

        # discard unauthorized actions, and read action definitions
        result = defaultdict(list)
        user_groups = self.env.user.groups_id
        if not debug:
            user_groups -= self.env.ref('base.group_no_one')

        self.flush()
        cr.execute(
            """
            SELECT a.id, a.type, a.binding_type
              FROM ir_actions a
              JOIN ir_model m ON a.binding_model_id = m.id
             WHERE m.model = %s
          ORDER BY a.id
        """, [model_name])
        for action_id, action_model, binding_type in cr.fetchall():
            try:
                action = self.env[action_model].sudo().browse(action_id)
                action_groups = getattr(action, 'groups_id', ())
                action_model = getattr(action, 'res_model', False)
                if action_groups and not action_groups & user_groups:
                    # the user may not perform this action
                    continue
                if action_model and not IrModelAccess.check(
                        action_model, mode='read', raise_exception=False):
                    # the user won't be able to read records
                    continue
                fields = ['name', 'binding_view_types']
                if 'sequence' in action._fields:
                    fields.append('sequence')
                result[binding_type].append(action.read(fields)[0])
            except (AccessError, MissingError):
                continue

        # sort actions by their sequence if sequence available
        if result.get('action'):
            result['action'] = sorted(result['action'],
                                      key=lambda vals: vals.get('sequence', 0))
        return result
 def _form_fieldsets(self):
     fields = [
         {
             'id': 'flyers',
             'fields': ['flyer_number']
         },
     ]
     if not self.large_picture:
         fields.append({
             'id': 'picture',
             'description': _(
                 "Please upload a large image of good quality which "
                 "will be used to be printed on your material."),
             'fields': ['large_picture']
         })
예제 #26
0
 def find_field_belong_model(nodes):
     fields = []
     for node in nodes:
         attr = {}
         if node.tag == 'field':
             attr.update(node.attrib)
             if node.find('tree'):
                 attr.update(node.find('tree').attrib)
             fields.append(attr)
             continue
         if node.getchildren():
             field = find_field_belong_model(node)
             fields.extend(field)
             continue
     return fields
    def search_read(self,
                    domain=None,
                    fields=None,
                    offset=0,
                    limit=None,
                    order=None):
        """
        Performs a ``search()`` followed by a ``read()``.

        :param domain: Search domain, see ``args`` parameter in ``search()``. Defaults to an empty domain that will match all records.
        :param fields: List of fields to read, see ``fields`` parameter in ``read()``. Defaults to all fields.
        :param offset: Number of records to skip, see ``offset`` parameter in ``search()``. Defaults to 0.
        :param limit: Maximum number of records to return, see ``limit`` parameter in ``search()``. Defaults to no limit.
        :param order: Columns to sort result, see ``order`` parameter in ``search()``. Defaults to no sort.
        :return: List of dictionaries containing the asked fields.
        :rtype: List of dictionaries.

        """
        records = self.search(domain or [],
                              offset=offset,
                              limit=limit,
                              order=order)
        if not records:
            return []

        if fields and fields == ['id']:
            # shortcut read if we only want the ids
            return [{'id': record.id} for record in records]

        # read() ignores active_test, but it would forward it to any downstream search call
        # (e.g. for x2m or function fields), and this is not the desired behavior, the flag
        # was presumably only meant for the main search().
        # TODO: Move this to read() directly?
        if 'active_test' in self._context:
            context = dict(self._context)
            del context['active_test']
            records = records.with_context(context)

        fields.append('editable')
        result = records.read(fields)
        if len(result) <= 1:
            return result

        # reorder read
        index = {vals['id']: vals for vals in result}
        return [index[record.id] for record in records if record.id in index]
 def _form_fieldsets(self):
     fields = [
         {
             "id": "flyers",
             "fields": ["flyer_number", "form_id"]
         },
     ]
     if not self.large_picture:
         fields.append({
             "id":
             "picture",
             "description":
             _("Please upload a large image of good quality which "
               "will be used to be printed on your material."),
             "fields": ["large_picture"],
         })
     return fields
예제 #29
0
    def yodlee_add_update_provider_account(self, values, site_id, name=None):
        # Setting values entered by user into json
        fields = []
        if type(values) != list:
            # most call to /providerAccounts only needs a dict with id, value keys
            # only exception is for type: questionAndAnswer which require a special format
            # the case is handle in javascript and js pass a dict as values instead of a list
            # in that particular case
            data = json.dumps({'loginForm': values})
        else:
            for element in values:
                if element.get('required', True) == 'false' and element['value'] == '':
                    raise UserError(_('Please fill all required fields'))
                if element['value'] != '':
                    fields.append({'id': element['field_id'], 'value': element['value']})
            data = json.dumps({'field': fields}) if len(fields) > 0 else []
        params = {'providerId': site_id}
        # If we have an id, it means that provider_account already exists and that it is an update
        if len(self) > 0 and self.id:
            params = {'providerAccountIds': self.provider_account_identifier}
            resp_json = self.yodlee_fetch('/providerAccounts', params, data, 'PUT')
            return self.id
        else:
            resp_json = self.yodlee_fetch('/providerAccounts', params, data, 'POST')
            refresh_info = resp_json.get('providerAccount', {}).get('refreshInfo')
            provider_account_identifier = resp_json.get('providerAccount', {}).get('id')
            vals = {'name': name or 'Online institution', 
                    'provider_account_identifier': provider_account_identifier,
                    'provider_identifier': site_id,
                    'status': refresh_info.get('status'),
                    'yodlee_additional_status': refresh_info.get('additionalStatus'),
                    'status_code': refresh_info.get('statusCode'),
                    'message': refresh_info.get('statusMessage'),
                    'action_required': refresh_info.get('actionRequired', False),
                    'last_refresh': self.convert_date_from_yodlee(refresh_info.get('lastRefreshed')),
                    'yodlee_last_attempted_refresh': self.convert_date_from_yodlee(refresh_info.get('lastRefreshAttempt')),
                    'provider_type': 'yodlee',
                    }

            # We create a new object if there isn't one with the same provider_account_identifier
            new_provider_account = self.search([('provider_account_identifier', '=', provider_account_identifier), ('company_id', '=', self.env.user.company_id.id)], limit=1)
            if len(new_provider_account) == 0:
                with self.pool.cursor() as cr:
                    new_provider_account = self.with_env(self.env(cr=cr)).create(vals)
            return new_provider_account.id
예제 #30
0
    def import_order_line_data(self):
        product_tmpl_obj = self.env['product.template']
        order_line_obj = self.env['purchase.order.line']
        product_obj = self.env['product.product']
        if self.import_option == 'csv':
            data = base64.b64decode(self.file)
            file_input = cStringIO.StringIO(data)
            file_input.seek(0)
            reader = csv.reader(file_input,
                                delimiter=',',
                                lineterminator='\r\n')
            reader_info = []
            try:
                reader_info.extend(reader)
            except Exception:
                raise exceptions.Warning(_("Not a valid file!"))
            fields = []
            for i in range(1, len(reader_info)):
                try:
                    field = map(str, reader_info[i])
                    fields.append(field)

                except ValueError:
                    raise exceptions.Warning(
                        _("Dont Use Character only use numbers"))
            self.create_order_line(fields, product_tmpl_obj, order_line_obj,
                                   product_obj)
        else:
            fp = tempfile.NamedTemporaryFile(suffix=".xlsx")
            fp.write(binascii.a2b_base64(self.file))
            fp.seek(0)
            workbook = xlrd.open_workbook(fp.name)
            sheet = workbook.sheet_by_index(0)
            fields = []
            for row_no in range(1, sheet.nrows):
                # print ".........row_no ", row_no
                field = map(lambda row: row.value, sheet.row(row_no))
                if field:
                    fields.append(field)
            self.create_order_line(fields, product_tmpl_obj, order_line_obj,
                                   product_obj)
    def read_group(self,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   orderby=False,
                   lazy=True):
        if 'on_time_rate' not in fields:
            res = super().read_group(domain,
                                     fields,
                                     groupby,
                                     offset=offset,
                                     limit=limit,
                                     orderby=orderby,
                                     lazy=lazy)
            return res

        fields.remove('on_time_rate')
        if 'qty_total' not in fields:
            fields.append('qty_total')
        if 'qty_on_time' not in fields:
            fields.append('qty_on_time')
        res = super().read_group(domain,
                                 fields,
                                 groupby,
                                 offset=offset,
                                 limit=limit,
                                 orderby=orderby,
                                 lazy=lazy)
        for group in res:
            if group['qty_total'] == 0:
                on_time_rate = 100
            else:
                on_time_rate = group['qty_on_time'] / group['qty_total'] * 100
            group.update({'on_time_rate': on_time_rate})

        return res
예제 #32
0
파일: main.py 프로젝트: datenbetrieb/odoo
    def products_autocomplete(self, term, options={}, **kwargs):
        """
        Returns list of products according to the term and product options

        Params:
            term (str): search term written by the user
            options (dict)
                - 'limit' (int), default to 5: number of products to consider
                - 'display_description' (bool), default to True
                - 'display_price' (bool), default to True
                - 'order' (str)
                - 'max_nb_chars' (int): max number of characters for the
                                        description if returned

        Returns:
            dict (or False if no result)
                - 'products' (list): products (only their needed field values)
                        note: the prices will be strings properly formatted and
                        already containing the currency
                - 'products_count' (int): the number of products in the database
                        that matched the search query
        """
        ProductTemplate = request.env['product.template']

        display_description = options.get('display_description', True)
        display_price = options.get('display_price', True)
        order = self._get_search_order(options)
        max_nb_chars = options.get('max_nb_chars', 999)

        category = options.get('category')
        attrib_values = options.get('attrib_values')

        domain = self._get_search_domain(term, category, attrib_values, display_description)
        products = ProductTemplate.search(
            domain,
            limit=min(20, options.get('limit', 5)),
            order=order
        )

        fields = ['id', 'name', 'website_url']
        if display_description:
            fields.append('description_sale')

        res = {
            'products': products.read(fields),
            'products_count': ProductTemplate.search_count(domain),
        }

        if display_description:
            for res_product in res['products']:
                desc = res_product['description_sale']
                if desc and len(desc) > max_nb_chars:
                    res_product['description_sale'] = "%s..." % desc[:(max_nb_chars - 3)]

        if display_price:
            FieldMonetary = request.env['ir.qweb.field.monetary']
            monetary_options = {
                'display_currency': request.website.get_current_pricelist().currency_id,
            }
            for res_product, product in zip(res['products'], products):
                combination_info = product._get_combination_info(only_template=True)
                res_product.update(combination_info)
                res_product['list_price'] = FieldMonetary.value_to_html(res_product['list_price'], monetary_options)
                res_product['price'] = FieldMonetary.value_to_html(res_product['price'], monetary_options)

        return res