Skip to content

Commit

Permalink
TA#66862 [16.0][MIG] project_task_full_text_search (#406)
Browse files Browse the repository at this point in the history
* TA#66862 [16.0][MIG] project_task_full_text_search

---------

Co-authored-by: Majda EL MARIOULI <[email protected]>
  • Loading branch information
lanto-razafindrabe and majouda authored Jul 7, 2024
1 parent 8e1867e commit 647b9a6
Show file tree
Hide file tree
Showing 14 changed files with 472 additions and 0 deletions.
1 change: 1 addition & 0 deletions .docker_files/main/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"depends": [
"project",
"project_task_date_planned",
"project_task_full_text_search",
"project_task_stage_external_mail",
"project_task_type",
],
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ RUN gitoo install-all --conf_file /gitoo.yml --destination "${THIRD_PARTY_ADDONS
USER odoo

COPY project_task_date_planned /mnt/extra-addons/project_task_date_planned
COPY project_task_full_text_search /mnt/extra-addons/project_task_full_text_search
COPY project_task_stage_external_mail /mnt/extra-addons/project_task_stage_external_mail
COPY project_task_type /mnt/extra-addons/project_task_type

Expand Down
73 changes: 73 additions & 0 deletions project_task_full_text_search/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
=============================
Project Task Full Text Search
=============================
This module allows to search a task by its title and description.

When searching a task, a full text search is ran in postgresql.

The order of the searched words has no impact on the result.
The returned tasks are tasks for which the title / description contain every given words
(or similar words).

Usage
-----
* Go to Projects / Tasks
* In the search bar, type something and click on ``Search Full Text for: ...``.

Accents and Upper Cases
-----------------------
The content of a task is indexed without accents and in lower cases.

When searching using the full text search, accents are removed from the searched text
and upper cases are replaced with lower cases.

stemmings
---------
The content search uses stemmings to find similar text.

stemmings are rules applied by postgresql to match text in queries.
These rules depend on the language of the text searched.

Natively, postgresql supports the following languages:

* danish
* dutch
* english
* finnish
* french
* german
* hungarian
* italian
* norwegian
* portuguese
* romanian
* russian
* spanish
* swedish
* turkish

A list of available language can be found using the following SQL query:

`SELECT * FROM pg_catalog.pg_ts_dict;`

Configuration
-------------
By default, the text search language is set to english.

In order to set the search language to a given value (french for example):

* Go to: Settings / Technical / Parameters / System Parameters.
* Create a new entry.
* Set key to `postgresql_full_text_search_language`.
* Set value to `french` (or your prefered language).
* Save the new entry.

That's it, your task are now indexed under the new language.

Contributors
------------
* Numigi (tm) and all its contributors (https://bit.ly/numigiens)

More information
----------------
* Meet us at https://bit.ly/numigi-com
4 changes: 4 additions & 0 deletions project_task_full_text_search/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import models
20 changes: 20 additions & 0 deletions project_task_full_text_search/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

{
"name": "Project Task Full Text Search",
"version": "16.0.1.0.0",
"author": "Numigi",
"maintainer": "Numigi",
"website": "https://bit.ly/numigi-com",
"license": "LGPL-3",
"category": "Project",
"depends": ["project"],
"external_dependencies": {
"python": ["unidecode"],
},
"data": [
"views/project_task.xml",
],
"installable": True,
}
48 changes: 48 additions & 0 deletions project_task_full_text_search/i18n/fr.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_task_full_text_search
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-06-16 11:39-0400\n"
"PO-Revision-Date: 2022-06-16 11:42-0400\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"X-Generator: Poedit 2.0.6\n"

#. module: project_task_full_text_search
#: model:ir.model.fields,field_description:project_task_full_text_search.field_project_task__full_text_search
msgid "Full Text"
msgstr "Plein texte"

#. module: project_task_full_text_search
#: model:ir.model.fields,field_description:project_task_full_text_search.field_project_task__full_text_content
msgid "Indexed Content"
msgstr "Contenu indexé"

#. module: project_task_full_text_search
#: model:ir.model,name:project_task_full_text_search.model_ir_config_parameter
msgid "System Parameter"
msgstr ""

#. module: project_task_full_text_search
#: model:ir.model,name:project_task_full_text_search.model_project_task
msgid "Task"
msgstr "Tâche"

#. module: project_task_full_text_search
#: code:addons/project_task_full_text_search/models/project_task.py:44
#, python-format
msgid ""
"The operator {operator} is not supported for full text search on tasks. You "
"may use the equal operator instead."
msgstr ""
"L'opérateur {operator} n'est pas supporté pour la recherche plein texte sur "
"les tâches. Vous pouvez toutefois utiliser l'opérateur d'égalité."
7 changes: 7 additions & 0 deletions project_task_full_text_search/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import (
ir_config_parameter,
project_task,
)
34 changes: 34 additions & 0 deletions project_task_full_text_search/models/ir_config_parameter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import api, models

FULL_TEXT_SEARCH_PARAM_KEY = "postgresql_full_text_search_language"


class IrConfigParameterWithFullTextSearchParamKey(models.Model):
"""Automatically update the gin index on tasks when the full text search language is set."""

_inherit = "ir.config_parameter"

@api.model
def create(self, vals):
param = super().create(vals)
should_update_index = param.key == FULL_TEXT_SEARCH_PARAM_KEY
if should_update_index:
self.env["project.task"]._setup_content_index()
return param

def write(self, vals):
super().write(vals)
should_update_index = any(p.key == FULL_TEXT_SEARCH_PARAM_KEY for p in self)
if should_update_index:
self.env["project.task"]._setup_content_index()
return True

def unlink(self):
should_update_index = any(p.key == FULL_TEXT_SEARCH_PARAM_KEY for p in self)
super().unlink()
if should_update_index:
self.env["project.task"]._setup_content_index()
return True
124 changes: 124 additions & 0 deletions project_task_full_text_search/models/project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import logging

from unidecode import unidecode

from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from .ir_config_parameter import FULL_TEXT_SEARCH_PARAM_KEY

_logger = logging.getLogger(__name__)

FULL_TEXT_SEARCH_INDEX_NAME = "project_task_full_text_search_idx"


class ProjectTaskWithFullTextSearch(models.Model):
"""Add full test search to tasks."""

_inherit = "project.task"

full_text_search = fields.Text(
"Full Text",
compute=lambda self: None,
search="_search_full_text_content",
)

full_text_content = fields.Text(
"Indexed Content",
compute="_compute_full_text_content",
store=True,
)

@api.depends("name", "description")
def _compute_full_text_content(self):
for task in self:
text = " ".join((task.name, task.description or ""))
text_with_no_accent = unidecode(text).lower()
task.full_text_content = text_with_no_accent

def _search_full_text_content(self, operator, value):
if operator != "=":
raise ValidationError(
_(
"The operator {operator} is not supported for full text search on tasks. "
"You may use the equal operator instead."
).format(operator=operator)
)
ids = self._find_tasks_from_full_text_content(value) if value else []
return [("id", "in", ids)]

def _find_tasks_from_full_text_content(self, searched_text):
"""Find tasks from the given searched text.
:param searched_text: the text to search in task contents.
"""
lang = self._get_full_text_content_language() or "pg_catalog.english"
words_to_search = unidecode(searched_text).lower().split(" ")
self.env.cr.execute(
"""
SELECT id FROM project_task
WHERE to_tsvector(%(lang)s, full_text_content) @@ plainto_tsquery(
%(lang)s, %(words)s);
""",
{
"lang": lang,
"words": " & ".join(words_to_search),
},
)

return [r[0] for r in self.env.cr.fetchall()]

def _get_full_text_content_language(self):
"""Get the language used for full text search on tasks.
sudo() is required because the variable is required for searching content.
"""
return (
self.env["ir.config_parameter"].sudo().get_param(FULL_TEXT_SEARCH_PARAM_KEY)
)


class ProjectTaskWithFullTextSearchIndex(models.Model):
"""Add a full text search index tasks.
The index is optional for searching content from tasks.
It only increases the spead of the full text search.
"""

_inherit = "project.task"

def _setup_content_index(self):
"""Setup a gin index on task content for the given language.
:param pg_language_name: the name of the postgresql dictionnary to
use for the the gin index.
"""
_logger.info("Droping the full text search index on project_task if it exists.")
query = "DROP INDEX IF EXISTS {index_name}".format(
index_name=FULL_TEXT_SEARCH_INDEX_NAME
)
self.env.cr.execute(query)

lang = self._get_full_text_content_language()

if lang:
_logger.info(
"Creating the full text search index on project_task "
"with stemmings for the language {lang}.".format(lang=lang)
)
query = """
CREATE INDEX {index_name} ON project_task
USING gin (to_tsvector(%(lang)s, full_text_content));
""".format(
index_name=FULL_TEXT_SEARCH_INDEX_NAME
)
self.env.cr.execute(query, {"lang": lang})
else:
_logger.info(
"The system parameter {param_key} is not defined. "
"The full text search index will not be created on project_task.".format(
param_key=FULL_TEXT_SEARCH_PARAM_KEY
)
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions project_task_full_text_search/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import (
test_config_parameter,
test_project_task,
)
Loading

0 comments on commit 647b9a6

Please sign in to comment.