Skip to content

Commit

Permalink
[MIG][16.0] project_task_id_in_display_name
Browse files Browse the repository at this point in the history
  • Loading branch information
rivo2302 committed Nov 22, 2024
1 parent ec7b6c0 commit 7ce905b
Show file tree
Hide file tree
Showing 15 changed files with 283 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 @@ -14,6 +14,7 @@
"project",
"project_task_date_planned",
"project_task_full_text_search",
"project_task_id_in_display_name",
"project_type_advanced",
"project_default_task_stage",
"project_stage_allow_timesheet",
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ USER odoo
COPY project_stage_allow_timesheet mnt/extra-addons/project_stage_allow_timesheet
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_id_in_display_name /mnt/extra-addons/project_task_id_in_display_name
COPY project_type_advanced /mnt/extra-addons/project_type_advanced
COPY project_default_task_stage /mnt/extra-addons/project_default_task_stage

Expand Down
12 changes: 12 additions & 0 deletions project_task_id_in_display_name/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Project Task ID In Display Name
===============================
This module displays the ID of a task in its displayed name.

Searching a task by its ID
--------------------------
When searching a task in kanban view, you may enter its ID instead of its name.
The system will return the task if the entered expression matches its ID.

Contributors
------------
* Numigi (tm) and all its contributors (https://bit.ly/numigiens)
6 changes: 6 additions & 0 deletions project_task_id_in_display_name/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# © 2022 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import models, controllers

Check notice on line 5 in project_task_id_in_display_name/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_task_id_in_display_name/__init__.py#L5

'.models' imported but unused (F401)
from .init_hook import post_init_hook

Check notice on line 6 in project_task_id_in_display_name/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_task_id_in_display_name/__init__.py#L6

'.init_hook.post_init_hook' imported but unused (F401)
20 changes: 20 additions & 0 deletions project_task_id_in_display_name/__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).


{

Check warning on line 5 in project_task_id_in_display_name/__manifest__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_task_id_in_display_name/__manifest__.py#L5

Statement seems to have no effect
"name": "Project Task ID In Display Name",
"version": "16.0.1.0.0",
"author": "Numigi",
"maintainer": "Numigi",
"website": "https://bit.ly/numigi-com",
"license": "LGPL-3",
"category": "Project",
"summary": "Add the ID of a task to its displayed name.",
"depends": ["project"],
"data": [
"views/project_task_views.xml",
],
"installable": True,
"post_init_hook": "post_init_hook",
}
4 changes: 4 additions & 0 deletions project_task_id_in_display_name/controllers/__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 portal

Check notice on line 4 in project_task_id_in_display_name/controllers/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_task_id_in_display_name/controllers/__init__.py#L4

'.portal' imported but unused (F401)
39 changes: 39 additions & 0 deletions project_task_id_in_display_name/controllers/portal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 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 urllib.parse

from odoo import http
from odoo.addons.project.controllers.portal import CustomerPortal


class ProjectPortalWithSearchTaskByID(CustomerPortal):
"""
Allow the portal user to find a task directly by the task id.
If the task with the given id is found,
the user is redirected to the task form.
"""

@http.route(
["/my/tasks", "/my/tasks/page/<int:page>"],
type="http",
auth="user",
website=True,
)
def portal_my_tasks(self, search=None, **kw):
is_searching_by_task_id = isinstance(search, str) and search.strip().isdigit()

if is_searching_by_task_id:
task_id = search.strip()
task = http.request.env["project.task"].search(
[("id_string", "=", task_id)], limit=1
)
if task:
query = urllib.parse.urlencode(dict(kw))
redirect_url = "/my/task/{task_id}?{query}".format(
task_id=task.id, query=query
)
return http.request.redirect(redirect_url)

return super().portal_my_tasks(search=search, **kw)
21 changes: 21 additions & 0 deletions project_task_id_in_display_name/init_hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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 odoo import SUPERUSER_ID
from odoo.api import Environment

_logger = logging.getLogger(__name__)


def post_init_hook(cr, pool):
setup_task_id_string(cr)


def setup_task_id_string(cr):
"""Setup the field id_string on all tasks."""
_logger.info("Setting field id_string on model project.task.")
env = Environment(cr, SUPERUSER_ID, {})
for task in env["project.task"].with_context(active_test=False).search([]):
task.id_string = str(task.id)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# 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.addons.project_task_id_in_display_name.init_hook import setup_task_id_string


def migrate(cr, version):
setup_task_id_string(cr)
4 changes: 4 additions & 0 deletions project_task_id_in_display_name/models/__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 project_task

Check notice on line 4 in project_task_id_in_display_name/models/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_task_id_in_display_name/models/__init__.py#L4

'.project_task' imported but unused (F401)
47 changes: 47 additions & 0 deletions project_task_id_in_display_name/models/project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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, fields, models


class ProjectTask(models.Model):
"""
Add a field to allow searching a task by its ID.
Odoo does not allow to properly search an integer value from a search bar.
This results in an exceptions because Odoo sends the searched value
right to the database without checking if the given string only contains digits.
This is why we copy the id value into a varchar column.
"""

_inherit = "project.task"

id_string = fields.Char("ID (String)", readonly=True)

def name_get(self):
return [(t.id, t._get_complete_name()) for t in self]

def _get_complete_name(self):
"""Get the complete name of the task.
:return: a string containing the id and the name of the task.
"""
return "[{id}] {name}".format(id=self.id, name=self.name)

@api.model
def name_search(self, name, args=None, operator="ilike", limit=100):
args = args or []
tasks = self.browse()

if name and name.isdigit():
tasks = self.search([("id", "=", int(name))] + args, limit=limit)

if not tasks:
tasks = self.search([("name", operator, name)] + args, limit=limit)

return tasks.name_get()

@api.model
def create(self, vals):
task = super().create(vals)
task.id_string = str(task.id)
return task
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions project_task_id_in_display_name/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 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_project_task
38 changes: 38 additions & 0 deletions project_task_id_in_display_name/tests/test_project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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.tests.common import TransactionCase


class TestProjectTaskWithIdInDisplayName(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.task_a = cls.env["project.task"].create({"name": "Task A"})
cls.task_b = cls.env["project.task"].create({"name": "Task B"})

def _search_tasks_by_name(self, name):
"""Search tasks given a string to search for.
:param name: the name of the task to search for
:return: a recordset of project.task
"""
ids = [t[0] for t in self.env["project.task"].name_search(name)]
return self.env["project.task"].browse(ids)

def test_task_search_by_name(self):
tasks = self._search_tasks_by_name("Task A")
self.assertIn(self.task_a, tasks)
self.assertNotIn(self.task_b, tasks)

def test_task_search_by_id(self):
tasks = self._search_tasks_by_name(str(self.task_a.id))
self.assertIn(self.task_a, tasks)
self.assertNotIn(self.task_b, tasks)

def test_display_name(self):
expected_name = "[{id}] {name}".format(id=self.task_a.id, name=self.task_a.name)
self.assertEqual(self.task_a.display_name, expected_name)

def test_compute_id_string(self):
self.assertEqual(self.task_a.id_string, str(self.task_a.id))
77 changes: 77 additions & 0 deletions project_task_id_in_display_name/views/project_task_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>

<record id="view_task_form2" model="ir.ui.view">
<field name="name">Task Form With Display Name In Read Mode</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="display_name" class="oe_read_only"/>
</field>
<field name="name" position="attributes">
<attribute name="class">oe_edit_only</attribute>
</field>
</field>
</record>

<record id="view_task_kanban" model="ir.ui.view">
<field name="name">Task Kanban With Display Name Instead Of Name</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_kanban"/>
<field name="arch" type="xml">
<xpath expr="//strong[hasclass('o_kanban_record_title')]/s/field[@name='name']" position="before">
<field name="display_name"/>
</xpath>
<xpath expr="//strong[hasclass('o_kanban_record_title')]/s/field[@name='name']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//strong[hasclass('o_kanban_record_title')]/t/field[@name='name']" position="before">
<field name="display_name"/>
</xpath>
<xpath expr="//strong[hasclass('o_kanban_record_title')]/t/field[@name='name']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>
</record>

<record id="view_task_tree2" model="ir.ui.view">
<field name="name">Task List: Display Name Instead Of Name</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_tree2"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="display_name"/>
</field>
<field name="name" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>

<record id="project_task_view_tree_activity" model="ir.ui.view">
<field name="name">Task Next Activities List: Display Name Instead Of Name</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.project_task_view_tree_activity"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="display_name"/>
</field>
<field name="name" position="attributes">
<attribute name="invisible">1</attribute>
</field>
</field>
</record>

<record id="view_task_search_form" model="ir.ui.view">
<field name="name">Task Search With ID Searched In Name</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_search_form"/>
<field name="arch" type="xml">
<field name="name" position="attributes">
<attribute name="filter_domain">['|', ('id_string', '=', self), ('name', 'ilike', self)]</attribute>
</field>
</field>
</record>

</odoo>

0 comments on commit 7ce905b

Please sign in to comment.