Files
odoo_source/addons/project/tests/test_project_subtasks.py
T
c5dd03ffee [FIX] project: review UX and UI in Project app
This commit reviews the UX and UI in Project app and improves the usability of the
new features recently added (subtasks list in kanban card, state field replacing
kanban_state field, etc).

The following changes are made in this commit:

- swap of user_id icon in the kanban box and the removal of the allow_unassign field and setting
- change the ordering of the fields in the subtask list
- make small layout changes in the project.task and project.project kanban cards
- remove the "lock-icon private" sub-title of private tasks, replace it with a
  little lock icon on the bottom right icons of the kanban card.
- remove the break tag in the project.task kanban card that was unecessary
  given the new display and margin style settings
- lock icon is bigger
- kanban icons are better aligned
- state is as big as avatar
- remove the 'remaining hours on SO' field
- deadline field will be optional and hidden by default in project.task list view
- remove the rating field
- add stage field as optional in project.task list views
- priority and state fields will no longer be optional
- stage_id will be copied when duplicating a task, except when the task is
  generated through the recurrence
- remove the tooltip of the tag_ids field
- When duplicating a task having sub-tasks, '(copy)' is no longer
  added to the name of the sub-tasks of this task.
- project.task kanban view:
  * (+ x tasks) mention next to the name is removed
  * The caret is replaced with 'fa-check-square-o x/y'
    which will represent the number of sub-tasks closed
    compared to the total number of sub-tasks
  * Only open subtasks are displayed
  * When changing the state of a sub-task to a closing one,
    the sub-task is muted and removed from the list on the view reload
  * The name of the parent task on the kanban card of sub-tasks is
    displayed except when viewing the sub-tasks of a particular task
    through the sub-tasks stat button
  * project.project kanban view: the fa-check-square-o icon of
    milestones is replaced with fa-flag-o
  * project.task kanban card: the fa-play and fa-pause icons
    are moved on the right of the remaining hours widge.
  * Allow users to edit the stage_id in batch from the list view of tasks
    if all of the selected tasks are part of the same project.
  * the state will have the same size as the avatar
  * change the opacity of the tasks that are closed
  * state is at the right of subtask list
  * the striked should be replaced by the opacity on the kanban card

Enterprise PR: odoo/enterprise#38132

Task-3229873

closes odoo/odoo#116628

X-original-commit: 09b5d5843096d27b5a4ab0f603ad44bdb2e74723
Related: odoo/upgrade#4478
Related: odoo/enterprise#38771
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
Co-authored-by: Panagiotis Kyriakou <paky@odoo.com>
Co-authored-by: Bastien (bvdn) <bvdn@odoo.com>
2023-04-03 17:07:21 +02:00

257 lines
14 KiB
Python

# -*- coding: utf-8 -*-
from odoo import Command
from odoo.addons.project.tests.test_project_base import TestProjectCommon
from odoo.tests import tagged
from odoo.tests.common import Form
@tagged('-at_install', 'post_install')
class TestProjectSubtasks(TestProjectCommon):
def test_task_display_project_with_default_form(self):
"""
Create a task in the default task form should take the project set in the form or the default project in the context
"""
with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form:
task_form.name = 'Test Task 1'
task_form.project_id = self.project_pigs
task = task_form.save()
self.assertEqual(task.project_id, self.project_pigs, "The project should be assigned.")
with Form(self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_pigs.id})) as task_form:
task_form.name = 'Test Task 2'
task = task_form.save()
self.assertEqual(task.project_id, self.project_pigs, "The project should be assigned from the default project.")
def test_task_display_project_with_task_form2(self):
"""
Create a task in the task form 2 should take the project set in the form or the default project in the context
"""
with Form(self.env['project.task'].with_context({'tracking_disable': True}), view="project.view_task_form2") as task_form:
task_form.name = 'Test Task 1'
task_form.project_id = self.project_pigs
task = task_form.save()
self.assertEqual(task.project_id, self.project_pigs, "The project should be assigned.")
with Form(self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_pigs.id}), view="project.view_task_form2") as task_form:
task_form.name = 'Test Task 2'
task = task_form.save()
self.assertEqual(task.project_id, self.project_pigs, "The project should be assigned from the default project.")
def test_task_display_project_with_quick_create_task_form(self):
"""
Create a task in the quick create form should take the default project in the context
"""
task_form = Form(self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_pigs.id}), view="project.quick_create_task_form")
task_form.display_name = 'Test Task 2'
task = task_form.save()
self.assertEqual(task.project_id, self.project_pigs, "The project should be assigned from the default project.")
def test_task_display_project_with_any_task_form(self):
"""
Create a task in any form should take the default project in the context
"""
form_views = self.env['ir.ui.view'].search([('model', '=', 'project.task'), ('type', '=', 'form')])
for form_view in form_views:
task_form = Form(self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_pigs.id, 'default_name': 'Test Task 1', 'default_display_name': 'Test Task 1'}), view=form_view)
# Some views have the `name` field invisible
# As the goal is simply to test the default project field and not the name, we can skip setting the name
# in the view and set it using `default_name` instead
# Quick create form use display_name and for the same goal, we can add default_display_name for that form
task = task_form.save()
self.assertEqual(task.project_id, self.project_pigs, "The project should be assigned from the default project, form_view name : %s." % form_view.name)
def test_subtask_project(self):
"""
1) Create a subtask
- Shouldn't have a project set.
2) Set project on subtask
- Should not change parent project
- Project should be correct
3) Reset the project to False
- Project should be correct
4) Change parent task project
- Project should stay false
5) Set project on subtask and change parent task project
- Project should be the one set by the user
6) Remove parent task:
- The project id should remain unchanged
7) Remove project id then parent id:
- Project should be removed
- Parent should be removed
"""
# 1)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.new() as child_task_form:
child_task_form.name = 'Test Subtask 1'
self.assertFalse(self.task_1.child_ids.project_id, "The project should be False by default.")
# 2)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.edit(0) as child_task_form:
child_task_form.project_id = self.project_goats
self.assertEqual(self.task_1.project_id, self.project_pigs, "Changing the project of a subtask should not change parent project")
self.assertEqual(self.task_1.child_ids.project_id, self.project_goats, "Display Project of the task should be well assigned")
self.assertEqual(self.task_1.child_ids.project_id, self.project_goats, "Changing display project id on a subtask should change project id")
# 3)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.edit(0) as child_task_form:
child_task_form.project_id = self.env['project.project']
self.assertFalse(self.task_1.child_ids.project_id, "The project of the subtask should be False")
# 4)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
task_form.project_id = self.project_goats
self.assertEqual(self.task_1.project_id, self.project_goats, "Parent project should change.")
self.assertFalse(self.task_1.child_ids.project_id, "The project of the subtask should stay False")
# 5)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.edit(0) as child_task_form:
child_task_form.project_id = self.project_goats
task_form.project_id = self.project_pigs
self.assertEqual(self.task_1.project_id, self.project_pigs, "Parent project should change back.")
self.assertEqual(self.task_1.child_ids.project_id, self.project_goats, "The project of the subtask should have the one set by the user")
# Debug mode required for `parent_id` to be visible in the view
with self.debug_mode():
# 6)
with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form:
subtask_form.parent_id = self.env['project.task']
orphan_subtask = subtask_form.save()
self.assertEqual(orphan_subtask.project_id, self.project_goats, "The project of the orphan task should stay the same even if it no longer has a parent task")
self.assertFalse(orphan_subtask.parent_id, "Parent should be false")
# 7)
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.new() as child_task_form:
child_task_form.name = 'Test Subtask 1'
child_task_form.project_id = self.project_goats
with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form:
subtask_form.project_id = self.env['project.project']
subtask_form.parent_id = self.env['project.task']
orphan_subtask = subtask_form.save()
self.assertFalse(orphan_subtask.project_id, "The project should be removed as expected.")
self.assertFalse(orphan_subtask.project_id, "The Parent should be removed as expected.")
def test_subtask_stage(self):
"""
The stage of the new child must be the default one of the project
"""
stage_a = self.env['project.task.type'].create({'name': 'a', 'sequence': 1})
stage_b = self.env['project.task.type'].create({'name': 'b', 'sequence': 10})
self.project_pigs.type_ids |= stage_a
self.project_pigs.type_ids |= stage_b
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.new() as child_task_form:
child_task_form.name = 'Test Subtask 1'
self.assertFalse(self.task_1.child_ids.stage_id, "No stage should be set on the subtask since no project is set by default.")
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.edit(0) as subtask_form:
subtask_form.project_id = task_form.project_id
self.assertEqual(self.task_1.child_ids.stage_id, stage_a, "The stage of the child task should be the default one of the project.")
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
task_form.stage_id = stage_b
self.assertEqual(self.task_1.child_ids.stage_id, stage_a, "The stage of the child task should remain the same while changing parent task stage.")
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
task_form.child_ids.remove(index=0)
with task_form.child_ids.new() as child_task_form:
child_task_form.name = 'Test Subtask 2'
child_task_form.project_id = task_form.project_id
self.assertEqual(self.task_1.child_ids.stage_id, stage_a, "The stage of the child task should be the default one of the project even if parent stage id is different.")
with Form(self.task_1.with_context({'tracking_disable': True})) as task_form:
with task_form.child_ids.edit(0) as child_task_form:
child_task_form.project_id = self.project_goats
self.assertEqual(self.task_1.child_ids.stage_id.name, "New", "The stage of the child task should be the default one of the display project id, once set.")
def test_copy_project_with_subtasks(self):
self.env['project.task'].with_context({'mail_create_nolog': True}).create({
'name': 'Parent Task',
'project_id': self.project_goats.id,
'child_ids': [
Command.create({'name': 'child 1'}),
Command.create({'name': 'child 2', 'project_id': self.project_goats.id}),
Command.create({'name': 'child 3', 'project_id': self.project_pigs.id}),
Command.create({'name': 'child 4 with subtask', 'child_ids': [Command.create({'name': 'child 5'}), Command.create({'name': 'child 6 with project', 'project_id': self.project_goats.id})]}),
Command.create({'name': 'child archived', 'active': False}),
],
})
task_count_with_subtasks_including_archived = 8
task_count_in_project_pigs = self.project_pigs.task_count
self.project_goats._compute_task_count() # recompute without archived tasks and subtasks
task_count_in_project_goats = self.project_goats.task_count
project_goats_duplicated = self.project_goats.copy()
self.project_pigs._compute_task_count() # retrigger since a new task should be added in the project after the duplication of Project Goats
def dfs(task):
# ABGH: i used dfs to avoid visiting a task 2 times as it can be a direct task for the project and a subtask for another task like child 6
visited[task.id] = True
total_count = 1
for child_id in task.child_ids:
if child_id.id not in visited:
total_count += dfs(child_id)
return total_count
visited = {}
tasks_copied_count = 0
for task in project_goats_duplicated.tasks:
if not task.id in visited:
tasks_copied_count += dfs(task)
self.assertEqual(
tasks_copied_count,
task_count_with_subtasks_including_archived - 1,
'The number of duplicated tasks (subtasks included) should be equal to the number of all tasks (with active subtasks included) of both projects, '
'that is only the active subtasks are duplicated.')
self.assertEqual(self.project_goats.task_count, task_count_in_project_goats, 'The number of tasks should be the same before and after the duplication of this project.')
self.assertEqual(self.project_pigs.task_count, task_count_in_project_pigs + 1, 'The project pigs should an additional task after the duplication of the project goats.')
def test_subtask_creation_with_form(self):
"""
1) test the creation of sub-tasks through the notebook
2) set a parent task on an existing task
3) test the creation of sub-sub-tasks
4) check the correct nb of sub-tasks is displayed in the 'sub-tasks' stat button and on the parent task kanban card
5) sub-tasks should be copied when the parent task is duplicated
"""
task_form = Form(self.task_1.with_context({'tracking_disable': True}))
with task_form.child_ids.new() as child_task_form:
child_task_form.name = 'Test Subtask 1'
child_task_form.project_id = task_form.project_id
task = task_form.save()
child_subtask = self.task_1.child_ids[0]
with Form(child_subtask.with_context(tracking_disable=True)) as subtask_form:
with subtask_form.child_ids.new() as child_subtask_form:
child_subtask_form.name = 'Test Subtask 2'
self.assertFalse(child_subtask_form.project_id)
self.assertEqual(task.subtask_count, 1, "Parent task should have 1 children")
task_2 = task.copy()
self.assertEqual(task_2.subtask_count, 1, "If the parent task is duplicated then the sub task should be copied")