Before this commit searched terms were the ones entered by the end-user. After this commit if the searched term is not found then the search is performed on a resembling term. The similarity is obtained from a Levenshtein distance combined with ratios of common and different letters. By default the dictionary against which the search term is matched is based on the content of the search fields containing a word that starts with the first letter of the search term. If the `pg_trgm` Postgresql extension is installed the dictionary is built from matches using the `<%%` operator (similar to the `word_similarity` function). Several approaches were benchmarked during development, those results are available through the task record. task-2379555 https://github.com/odoo/odoo/pull/65871 Part-of: odoo/odoo#65871
159 lines
6.1 KiB
Python
159 lines
6.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import odoo.modules
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
def is_initialized(cr):
|
|
""" Check if a database has been initialized for the ORM.
|
|
|
|
The database can be initialized with the 'initialize' function below.
|
|
|
|
"""
|
|
return odoo.tools.table_exists(cr, 'ir_module_module')
|
|
|
|
def initialize(cr):
|
|
""" Initialize a database with for the ORM.
|
|
|
|
This executes base/data/base_data.sql, creates the ir_module_categories
|
|
(taken from each module descriptor file), and creates the ir_module_module
|
|
and ir_model_data entries.
|
|
|
|
"""
|
|
f = odoo.modules.get_module_resource('base', 'data', 'base_data.sql')
|
|
if not f:
|
|
m = "File not found: 'base.sql' (provided by module 'base')."
|
|
_logger.critical(m)
|
|
raise IOError(m)
|
|
|
|
with odoo.tools.misc.file_open(f) as base_sql_file:
|
|
cr.execute(base_sql_file.read())
|
|
|
|
for i in odoo.modules.get_modules():
|
|
mod_path = odoo.modules.get_module_path(i)
|
|
if not mod_path:
|
|
continue
|
|
|
|
# This will raise an exception if no/unreadable descriptor file.
|
|
info = odoo.modules.load_information_from_description_file(i)
|
|
|
|
if not info:
|
|
continue
|
|
categories = info['category'].split('/')
|
|
category_id = create_categories(cr, categories)
|
|
|
|
if info['installable']:
|
|
state = 'uninstalled'
|
|
else:
|
|
state = 'uninstallable'
|
|
|
|
cr.execute('INSERT INTO ir_module_module \
|
|
(author, website, name, shortdesc, description, \
|
|
category_id, auto_install, state, web, license, application, icon, sequence, summary) \
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', (
|
|
info['author'],
|
|
info['website'], i, info['name'],
|
|
info['description'], category_id,
|
|
info['auto_install'] is not False, state,
|
|
info['web'],
|
|
info['license'],
|
|
info['application'], info['icon'],
|
|
info['sequence'], info['summary']))
|
|
id = cr.fetchone()[0]
|
|
cr.execute('INSERT INTO ir_model_data \
|
|
(name,model,module, res_id, noupdate) VALUES (%s,%s,%s,%s,%s)', (
|
|
'module_'+i, 'ir.module.module', 'base', id, True))
|
|
dependencies = info['depends']
|
|
for d in dependencies:
|
|
cr.execute(
|
|
'INSERT INTO ir_module_module_dependency (module_id, name, auto_install_required)'
|
|
' VALUES (%s, %s, %s)',
|
|
(id, d, d in (info['auto_install'] or ()))
|
|
)
|
|
|
|
# Install recursively all auto-installing modules
|
|
while True:
|
|
# this selects all the auto_install modules whose auto_install_required
|
|
# deps are marked as to install
|
|
cr.execute("""
|
|
SELECT m.name FROM ir_module_module m
|
|
WHERE m.auto_install
|
|
AND state != 'to install'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM ir_module_module_dependency d
|
|
JOIN ir_module_module mdep ON (d.name = mdep.name)
|
|
WHERE d.module_id = m.id
|
|
AND d.auto_install_required
|
|
AND mdep.state != 'to install'
|
|
)""")
|
|
to_auto_install = [x[0] for x in cr.fetchall()]
|
|
# however if the module has non-required deps we need to install
|
|
# those, so merge-in the modules which have a dependen*t* which is
|
|
# *either* to_install or in to_auto_install and merge it in?
|
|
cr.execute("""
|
|
SELECT d.name FROM ir_module_module_dependency d
|
|
JOIN ir_module_module m ON (d.module_id = m.id)
|
|
JOIN ir_module_module mdep ON (d.name = mdep.name)
|
|
WHERE (m.state = 'to install' OR m.name = any(%s))
|
|
-- don't re-mark marked modules
|
|
AND NOT (mdep.state = 'to install' OR mdep.name = any(%s))
|
|
""", [to_auto_install, to_auto_install])
|
|
to_auto_install.extend(x[0] for x in cr.fetchall())
|
|
|
|
if not to_auto_install: break
|
|
cr.execute("""UPDATE ir_module_module SET state='to install' WHERE name in %s""", (tuple(to_auto_install),))
|
|
|
|
def create_categories(cr, categories):
|
|
""" Create the ir_module_category entries for some categories.
|
|
|
|
categories is a list of strings forming a single category with its
|
|
parent categories, like ['Grand Parent', 'Parent', 'Child'].
|
|
|
|
Return the database id of the (last) category.
|
|
|
|
"""
|
|
p_id = None
|
|
category = []
|
|
while categories:
|
|
category.append(categories[0])
|
|
xml_id = 'module_category_' + ('_'.join(x.lower() for x in category)).replace('&', 'and').replace(' ', '_')
|
|
# search via xml_id (because some categories are renamed)
|
|
cr.execute("SELECT res_id FROM ir_model_data WHERE name=%s AND module=%s AND model=%s",
|
|
(xml_id, "base", "ir.module.category"))
|
|
|
|
c_id = cr.fetchone()
|
|
if not c_id:
|
|
cr.execute('INSERT INTO ir_module_category \
|
|
(name, parent_id) \
|
|
VALUES (%s, %s) RETURNING id', (categories[0], p_id))
|
|
c_id = cr.fetchone()[0]
|
|
cr.execute('INSERT INTO ir_model_data (module, name, res_id, model, noupdate) \
|
|
VALUES (%s, %s, %s, %s, %s)', ('base', xml_id, c_id, 'ir.module.category', True))
|
|
else:
|
|
c_id = c_id[0]
|
|
p_id = c_id
|
|
categories = categories[1:]
|
|
return p_id
|
|
|
|
def has_unaccent(cr):
|
|
""" Test if the database has an unaccent function.
|
|
|
|
The unaccent is supposed to be provided by the PostgreSQL unaccent contrib
|
|
module but any similar function will be picked by OpenERP.
|
|
|
|
"""
|
|
cr.execute("SELECT proname FROM pg_proc WHERE proname='unaccent'")
|
|
return len(cr.fetchall()) > 0
|
|
|
|
def has_trigram(cr):
|
|
""" Test if the database has the a word_similarity function.
|
|
|
|
The word_similarity is supposed to be provided by the PostgreSQL built-in
|
|
pg_trgm module but any similar function will be picked by Odoo.
|
|
|
|
"""
|
|
cr.execute("SELECT proname FROM pg_proc WHERE proname='word_similarity'")
|
|
return len(cr.fetchall()) > 0
|