Search code examples
pythonpostgresqlormodooodoo-14

How to get record in true alphabetically ( case insensitive ) sort order in odoo?


My use-case is to list the records in a tree or kanban view by name but it should be case insensitive. Current state Eg. I have four records like

Apple
Orange
apple
Banana

In default the ordering is:

Apple
Banana
Orange
apple

And the expected ordering is:

Apple
apple
Banana
Orange

How to achieve this? Is there any ORM method to do this?


Solution

  • When Odoo searches for records, it will attempt to construct an appropriate ORDER BY clause based on order_spec, which must be a comma-separated list of valid field names, optionally followed by an ASC or DESC direction.

    The order by clause is computed based on _order attribute and can be overridden by the order parameter in the search function.

    You can override the _generate_order_by_inner function to make it possible to pass function names used in SQL queries directly in _order attribute (field_name:function) for char fields.

    Example:

    class Fruits(models.Model):
        _name = 'fruit.fruit'
        _order = 'name:lower'
    
        name = fields.Char()
    
        @api.model
        def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=False, seen=None):
            if seen is None:
                seen = set()
            self._check_qorder(order_spec)
    
            order_by_elements = []
            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 ''
                if reverse_direction:
                    order_direction = 'ASC' if order_direction == 'DESC' else 'DESC'
                do_reverse = order_direction == 'DESC'
                # ------------------------------------------------------------------
                func_split = order_field.strip().split(':')
                order_field = func_split[0].strip()
                func = func_split[1].strip().upper() if len(func_split) == 2 else ''
                # ------------------------------------------------------------------
                field = self._fields.get(order_field)
                if not field:
                    raise ValueError("Invalid field %r on model %r" % (order_field, self._name))
                
                if order_field == 'id':
                    order_by_elements.append('"%s"."%s" %s' % (alias, order_field, order_direction))
                else:
                    if field.inherited:
                        field = field.base_field
                    if field.store and field.type == 'many2one':
                        key = (field.model_name, field.comodel_name, order_field)
                        if key not in seen:
                            seen.add(key)
                            order_by_elements += self._generate_m2o_order_by(alias, order_field, query, do_reverse, seen)
                    elif field.store and field.column_type:
                        qualifield_name = self._inherits_join_calc(alias, order_field, query)
                        if field.type == 'boolean':
                            qualifield_name = "COALESCE(%s, false)" % qualifield_name
                        # ------------------------------------------------------
                        if func and field.type == 'char':
                            qualifield_name = "%s(%s)" % (func, qualifield_name)
                        # ------------------------------------------------------
                        order_by_elements.append("%s %s" % (qualifield_name, order_direction))
                    else:
                        _logger.warning("Model %r cannot be sorted on field %r (not a column)", self._name, order_field)
                        continue  # ignore non-readable or "non-joinable" fields
    
            return order_by_elements
    

    You can also patch the models.BaseModel._generate_order_by_inner to use the same logic for any char field.

    Example:

    @api.model
    def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=False, seen=None):
        ...
    
    models.BaseModel._generate_order_by_inner = _generate_order_by_inner
    

    To override the sort order in any view, you have just to include:

    if field.type == 'char':
        qualifield_name = "lower(%s)" % (qualifield_name,)
    

    Without redefining the order.