From 9052aa386d8fcaf97515d48d7ef881a2fb62b259 Mon Sep 17 00:00:00 2001 From: les-adhoc Date: Thu, 18 Jun 2026 16:13:53 +0000 Subject: [PATCH 1/2] [FIX] project_ux: keep typed task title when selecting project on quick-create The quick-create form (My Tasks, project kanban) binds the "Task Title" input to display_name, which is only inversed into `name` on save. Because this module makes display_name depend on project_id.show_task_id (to append the task id), selecting/changing the project invalidates and recomputes display_name from the still-empty `name`, wiping the title the user just typed. Restore the typed value in the onchange result so it survives the project change, across all creation flows (quick-create and normal form). Task: 69540 --- project_ux/models/project_task.py | 18 ++++++++++++++++++ project_ux/tests/test_project_task.py | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/project_ux/models/project_task.py b/project_ux/models/project_task.py index 7b44724d..5e23329e 100644 --- a/project_ux/models/project_task.py +++ b/project_ux/models/project_task.py @@ -79,6 +79,24 @@ def _compute_display_name(self): if task.project_id.show_task_id and task.id and task.display_name: task.display_name = f"{task.display_name} (#{task.id})" + def onchange(self, values, field_names, fields_spec): + result = super().onchange(values, field_names, fields_spec) + # In the quick-create (My Tasks, project kanban) the "Task Title" input + # is bound to display_name and is only inversed into `name` on save. + # Because this module makes display_name depend on project_id (to append + # the task id), changing the project recomputes display_name from the + # still-empty `name`, blanking the title the user just typed. Restore the + # typed value in the onchange result so it survives the project change. + typed_title = values.get("display_name") + if ( + "project_id" in (field_names or []) + and typed_title + and not values.get("name") + and not result.get("value", {}).get("display_name") + ): + result.setdefault("value", {})["display_name"] = typed_title + return result + @api.model def _search_display_name(self, operator, value): domain = super()._search_display_name(operator, value) diff --git a/project_ux/tests/test_project_task.py b/project_ux/tests/test_project_task.py index be4e4477..24f9a75f 100644 --- a/project_ux/tests/test_project_task.py +++ b/project_ux/tests/test_project_task.py @@ -1,3 +1,4 @@ +from odoo.tests import Form from odoo.tests.common import TransactionCase @@ -94,3 +95,24 @@ def test_name_search_does_not_find_task_by_id_when_disabled_on_project(self): result_ids = [task_id for task_id, __ in self.Task.name_search(str(self.test_task.id), limit=20)] self.assertNotIn(self.test_task.id, result_ids) + + def _check_quick_create_keeps_title(self, title): + """Quick-create binds the "Task Title" to display_name (not name). + Selecting/changing the project must not wipe a title already typed. + """ + with Form(self.Task, view="project.quick_create_task_form") as task_form: + task_form.display_name = title + task_form.project_id = self.test_project + self.assertEqual(task_form.display_name, title) + task = task_form.record + self.assertEqual(task.name, title) + self.assertEqual(task.project_id, self.test_project) + + def test_quick_create_keeps_title_when_selecting_project(self): + self._check_quick_create_keeps_title("My Title") + + def test_quick_create_keeps_title_when_project_shows_task_id(self): + # The title must survive the project onchange even though display_name + # depends on project_id.show_task_id. + self.test_project.show_task_id = True + self._check_quick_create_keeps_title("Another Title") From 27b6afff1631e6d9a67c500a3629a81852ffda11 Mon Sep 17 00:00:00 2001 From: les-adhoc Date: Thu, 18 Jun 2026 16:13:53 +0000 Subject: [PATCH 2/2] [IMP] project_ux: open project form after creating it from the New modal Creating a project from Project > New redirected to the task pipeline (the "No stages yet..." Kanban stages config) before the project was set up. Point the "Create project" button of the New modal to the standard get_formview_action so it lands on the project's own form, where the user can finish configuring it (manager, customer, dates, settings). Stages remain configurable from the "Tasks Stages" tab. Scoped to that modal only, so the shared action_view_tasks (used by the kanban cards) is untouched. Task: 69540 --- project_ux/tests/__init__.py | 1 + project_ux/tests/test_project_project.py | 28 ++++++++++++++++++++++ project_ux/views/project_project_views.xml | 15 ++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 project_ux/tests/test_project_project.py diff --git a/project_ux/tests/__init__.py b/project_ux/tests/__init__.py index 7a25d85b..41f2124c 100644 --- a/project_ux/tests/__init__.py +++ b/project_ux/tests/__init__.py @@ -1,2 +1,3 @@ from . import test_project_task from . import test_project_task_type +from . import test_project_project diff --git a/project_ux/tests/test_project_project.py b/project_ux/tests/test_project_project.py new file mode 100644 index 00000000..99921169 --- /dev/null +++ b/project_ux/tests/test_project_project.py @@ -0,0 +1,28 @@ +from odoo.tests.common import TransactionCase + + +class TestProjectProject(TransactionCase): + def setUp(self): + super().setUp() + self.Project = self.env["project.project"] + + def test_create_project_modal_button_opens_form(self): + """The "Create project" button of the New modal must open the project + form (via the standard get_formview_action), not the task pipeline + (action_view_tasks). + """ + view = self.env.ref("project.project_project_view_form_simplified_footer") + combined_arch = view.get_combined_arch() + self.assertIn('name="get_formview_action"', combined_arch) + self.assertNotIn('name="action_view_tasks"', combined_arch) + + def test_get_formview_action_lands_on_project_form(self): + """get_formview_action opens the created project's own form.""" + project = self.Project.create({"name": "New Project"}) + + action = project.get_formview_action() + + self.assertEqual(action["res_model"], "project.project") + self.assertEqual(action["res_id"], project.id) + self.assertEqual(action["target"], "current") + self.assertTrue(any(view_type == "form" for __, view_type in action["views"])) diff --git a/project_ux/views/project_project_views.xml b/project_ux/views/project_project_views.xml index 8bbc9cac..3d19129f 100644 --- a/project_ux/views/project_project_views.xml +++ b/project_ux/views/project_project_views.xml @@ -60,4 +60,19 @@ + + + project.project.view.form.simplified.open.form + project.project + + + + get_formview_action + + + +