summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOdoo's Mergebot <xmo+roboto@odoo.com>2020-06-17 15:32:22 +0200
committerGitHub <noreply@github.com>2020-06-17 15:32:22 +0200
commiteb5ebc804f0a75d11ff07bc4f7baf66dbcd10bd4 (patch)
treebfdccdc563c470eb4a2d109eb8c84278428b2ca5
parent93479ad005bd69b269a662003e70163d0f405e05 (diff)
parent2b10b3d75ec2d3924f0513c43ebf97ddf2ba6e7c (diff)
[IMP] project: populate
New populate feature (#48479) now allows to define model generation methods to populate the database with model records for performance testing. This PR aims to implement database population for the `project` App. Furthermore, to reduce population time, a first performance improvement is applied to create `project.task` records in batch. -- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr closes odoo/odoo#50405 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
-rw-r--r--addons/project/__init__.py1
-rw-r--r--addons/project/models/project.py45
-rw-r--r--addons/project/populate/__init__.py1
-rw-r--r--addons/project/populate/project.py102
4 files changed, 131 insertions, 18 deletions
diff --git a/addons/project/__init__.py b/addons/project/__init__.py
index 4e9b019bd7a..e35155177e6 100644
--- a/addons/project/__init__.py
+++ b/addons/project/__init__.py
@@ -5,3 +5,4 @@ from . import controllers
from . import models
from . import report
from . import wizard
+from . import populate
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index b2cb1b25a9a..70c5f58f4dc 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -735,7 +735,7 @@ class Task(models.Model):
self.company_id = self.project_id.company_id
else:
self.stage_id = False
-
+
@api.onchange('company_id')
def _onchange_task_company(self):
if self.project_id.company_id != self.company_id:
@@ -805,23 +805,32 @@ class Task(models.Model):
# CRUD overrides
# ------------------------------------------------
- @api.model
- def create(self, vals):
- # context: no_log, because subtype already handle this
- context = dict(self.env.context)
- stage_id = vals.get('stage_id') or context.get('default_stage_id') or False
- # for default stage
- if vals.get('project_id') and not context.get('default_project_id'):
- context['default_project_id'] = vals.get('project_id')
- # user_id change: update date_assign
- if vals.get('user_id'):
- vals['date_assign'] = fields.Datetime.now()
- # Stage change: Update date_end if folded stage and date_last_stage_update
- if stage_id:
- vals.update(self.update_date_end(stage_id))
- vals['date_last_stage_update'] = fields.Datetime.now()
- task = super(Task, self.with_context(context)).create(vals)
- return task
+ @api.model_create_multi
+ def create(self, vals_list):
+ default_stage = dict()
+ for vals in vals_list:
+ project_id = vals.get('project_id') or self.env.context.get('default_project_id')
+ if project_id and not "company_id" in vals:
+ vals["company_id"] = self.env["project.project"].browse(
+ project_id
+ ).company_id.id or self.env.company.id
+ if project_id and "stage_id" not in vals:
+ # 1) Allows keeping the batch creation of tasks
+ # 2) Ensure the defaults are correct (and computed once by project),
+ # by using default get (instead of _get_default_stage_id or _stage_find),
+ if project_id not in default_stage:
+ default_stage[project_id] = self.with_context(
+ default_project_id=project_id
+ ).default_get(['stage_id']).get('stage_id')
+ vals["stage_id"] = default_stage[project_id]
+ # user_id change: update date_assign
+ if vals.get('user_id'):
+ vals['date_assign'] = fields.Datetime.now()
+ # Stage change: Update date_end if folded stage and date_last_stage_update
+ if vals.get('stage_id'):
+ vals.update(self.update_date_end(vals['stage_id']))
+ vals['date_last_stage_update'] = fields.Datetime.now()
+ return super().create(vals_list)
def write(self, vals):
now = fields.Datetime.now()
diff --git a/addons/project/populate/__init__.py b/addons/project/populate/__init__.py
new file mode 100644
index 00000000000..351a3ad34b5
--- /dev/null
+++ b/addons/project/populate/__init__.py
@@ -0,0 +1 @@
+from . import project
diff --git a/addons/project/populate/project.py b/addons/project/populate/project.py
new file mode 100644
index 00000000000..73728e7f6e8
--- /dev/null
+++ b/addons/project/populate/project.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import logging
+import collections
+
+from odoo import models
+from odoo.tools import populate
+
+_logger = logging.getLogger(__name__)
+
+class ProjectStage(models.Model):
+ _inherit = "project.task.type"
+ _populate_sizes = {"small": 10, "medium": 50, "large": 500}
+
+ def _populate_factories(self):
+ return [
+ ("name", populate.constant('stage_{counter}')),
+ ("sequence", populate.randomize([False] + [i for i in range(1, 101)])),
+ ("description", populate.constant('project_stage_description_{counter}')),
+ ("active", populate.randomize([True, False], [0.8, 0.2])),
+ ("fold", populate.randomize([True, False], [0.9, 0.1]))
+ ]
+
+class ProjectProject(models.Model):
+ _inherit = "project.project"
+ _populate_sizes = {"small": 10, "medium": 50, "large": 1000}
+ _populate_dependencies = ["res.company", "project.task.type"]
+
+ def _populate_factories(self):
+ company_ids = self.env.registry.populated_models["res.company"]
+ stage_ids = self.env.registry.populated_models["project.task.type"]
+
+ def get_company_id(random, **kwargs):
+ return random.choice(company_ids)
+ # user_ids from company.user_ids ?
+ # Also add a partner_ids on res_company ?
+
+ def get_stage_ids(random, **kwargs):
+ return [
+ (6, 0, [
+ random.choice(stage_ids)
+ for i in range(random.choice([j for j in range(1, 10)]))
+ ])
+ ]
+
+ return [
+ ("name", populate.constant('project_{counter}')),
+ ("sequence", populate.randomize([False] + [i for i in range(1, 101)])),
+ ("active", populate.randomize([True, False], [0.8, 0.2])),
+ ("company_id", populate.compute(get_company_id)),
+ ("type_ids", populate.compute(get_stage_ids)),
+ ('color', populate.randomize([False] + [i for i in range(1, 7)])),
+ # TODO user_id but what about multi-company coherence ??
+ ]
+
+
+class ProjectTask(models.Model):
+ _inherit = "project.task"
+ _populate_sizes = {"small": 500, "medium": 5000, "large": 50000}
+ _populate_dependencies = ["project.project"]
+
+ def _populate_factories(self):
+ project_ids = self.env.registry.populated_models["project.project"]
+ stage_ids = self.env.registry.populated_models["project.task.type"]
+ def get_project_id(random, **kwargs):
+ return random.choice([False, False, False] + project_ids)
+ def get_stage_id(random, **kwargs):
+ return random.choice([False, False] + stage_ids)
+ return [
+ ("name", populate.constant('project_task_{counter}')),
+ ("sequence", populate.randomize([False] + [i for i in range(1, 101)])),
+ ("active", populate.randomize([True, False], [0.8, 0.2])),
+ ("color", populate.randomize([False] + [i for i in range(1, 7)])),
+ ("kanban_state", populate.randomize(['normal', 'done', 'blocked'])),
+ ("project_id", populate.compute(get_project_id)),
+ ("stage_id", populate.compute(get_stage_id)),
+ ]
+
+ def _populate(self, size):
+ records = super()._populate(size)
+ # set parent_ids
+ self._populate_set_children_tasks(records, size)
+ return records
+
+ def _populate_set_children_tasks(self, tasks, size):
+ _logger.info('Setting parent tasks')
+ rand = populate.Random('project.task+children_generator')
+ parents = self.env["project.task"]
+ for task in tasks:
+ if not rand.getrandbits(4):
+ parents |= task
+ parent_ids = parents.ids
+ tasks -= parents
+ parent_childs = collections.defaultdict(lambda: self.env['project.task'])
+ for count, task in enumerate(tasks):
+ if not rand.getrandbits(4):
+ parent_childs[rand.choice(parent_ids)] |= task
+
+ for count, (parent, childs) in enumerate(parent_childs.items()):
+ if (count + 1) % 100 == 0:
+ _logger.info('Setting parent: %s/%s', count + 1, len(parent_childs))
+ childs.write({'parent_id': parent})