diff --git a/addons/website_forum/__init__.py b/addons/website_forum/__init__.py index aa4d0fd63a9..4bf600d50ff 100644 --- a/addons/website_forum/__init__.py +++ b/addons/website_forum/__init__.py @@ -2,3 +2,4 @@ from . import controllers from . import models +from . import populate diff --git a/addons/website_forum/populate/__init__.py b/addons/website_forum/populate/__init__.py new file mode 100644 index 00000000000..ccf52d64ebd --- /dev/null +++ b/addons/website_forum/populate/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import forum_forum +from . import forum_post +from . import mail_message diff --git a/addons/website_forum/populate/forum_forum.py b/addons/website_forum/populate/forum_forum.py new file mode 100644 index 00000000000..65ebe65f9ab --- /dev/null +++ b/addons/website_forum/populate/forum_forum.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models +from odoo.tools import populate + +class Forum(models.Model): + _inherit = 'forum.forum' + _populate_sizes = {'small': 1, 'medium': 3, 'large': 10} + + def _populate_factories(self): + return [ + ('name', populate.constant('Forum_{counter}')), + ('description', populate.constant('This is forum number {counter}')) + ] diff --git a/addons/website_forum/populate/forum_post.py b/addons/website_forum/populate/forum_post.py new file mode 100644 index 00000000000..18d090ccc1a --- /dev/null +++ b/addons/website_forum/populate/forum_post.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +from odoo import fields, models +from odoo.tools import populate + +QA_WEIGHTS = {0: 25, 1: 35, 2: 20, 3: 10, 4: 4, 5: 3, 6: 2, 7: 1} +_logger = logging.getLogger(__name__) + +class Post(models.Model): + _inherit = 'forum.post' + # Include an additional average of 2 post answers for each given size + # e.g.: 100 posts as populated_model_records = ~300 actual forum.posts records + _populate_sizes = {'small': 100, 'medium': 1000, 'large': 90000} + _populate_dependencies = ['forum.forum', 'res.users'] + + def _populate_factories(self): + forum_ids = self.env.registry.populated_models['forum.forum'] + hours = [_ for _ in range(1, 49)] + random = populate.Random('forum_posts') + + def create_answers(values=None, **kwargs): + """Create random number of answers + We set the `last_activity_date` to convert it to `create_date` in `_populate` + as the ORM prevents setting these. + """ + return [ + fields.Command.create({ + 'name': f"reply to {values['name']}", + 'forum_id': values['forum_id'], + 'content': 'Answer content', + 'last_activity_date': fields.Datetime.add(values['last_activity_date'], hours=random.choice(hours)), + }) + for _ in range(random.choices(*zip(*QA_WEIGHTS.items()))[0]) + ] + + def get_last_activity_date(iterator, *args): + days = [_ for _ in range(3, 93)] + now = fields.Datetime.now() + + for values in iterator: + values.update(last_activity_date=fields.Datetime.subtract(now, days=random.choice(days))) + yield values + + return [ + ('forum_id', populate.randomize(forum_ids)), + ('name', populate.constant('post_{counter}')), + ('last_activity_date', get_last_activity_date), # Must be before call to 'create_answers' + ('child_ids', populate.compute(create_answers)), + ] + + def _populate(self, size): + records = super()._populate(size) + user_ids = self.env.registry.populated_models['res.users'] + + # Overwrite auto-fields: use last_activity_date to update create date + _logger.info('forum.post: update create date and uid') + question_ids = tuple(records.ids) + query = """ + SELECT setseed(0.5); + UPDATE forum_post + SET create_date = last_activity_date, + create_uid = floor(random() * (%(max_value)s - %(min_value)s + 1) + %(min_value)s) + WHERE id in %(question_ids)s or parent_id in %(question_ids)s + """ + self.env.cr.execute(query, {'question_ids': question_ids, 'min_value': user_ids[0], 'max_value': user_ids[-1]}) + + _logger.info('forum.post: update last_activity_date of questions with answers') + query = """ + WITH latest_answer AS( + SELECT parent_id, max(last_activity_date) as answer_date + FROM forum_post + WHERE parent_id in %(question_ids)s + GROUP BY parent_id + ) + UPDATE forum_post fp + SET last_activity_date = latest_answer.answer_date + FROM latest_answer + WHERE fp.id = latest_answer.parent_id + """ + self.env.cr.execute(query, {'question_ids': question_ids}) + + return records diff --git a/addons/website_forum/populate/mail_message.py b/addons/website_forum/populate/mail_message.py new file mode 100644 index 00000000000..4d03ebe80bf --- /dev/null +++ b/addons/website_forum/populate/mail_message.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +from odoo import fields, models +from odoo.tools import populate + +CP_WEIGHTS = {1: 35, 2: 30, 3: 25, 4: 10} +_logger = logging.getLogger(__name__) + +class Message(models.Model): + _inherit = 'mail.message' + _populate_dependencies = ['forum.post'] + _populate_sizes = {'small': 100, 'medium': 1000, 'large': 90000} + + def _populate_factories(self): + comment_subtype = self.env.ref('mail.mt_comment') + random = populate.Random('comments_on_forum_posts') + + def get_author_id(iterator, *args): + user_ids = self.env.registry.populated_models['res.users'] + author_ids = self.env['res.users'].search_read([('id', 'in', user_ids)], ['partner_id']) + + for values in iterator: + author_id = random.choice(author_ids) + values.update(author_id=author_id['partner_id'][0]) + yield values + + def get_res_id_and_date(iterator, *args): + """Randomly assign messages to some populated posts and answers. + This makes sure these questions and answers get one to four comments. + Also define a date that is more recent than the post/answer's create_date + """ + question_ids = self.env.registry.populated_models['forum.post'] + posts = self.env['forum.post'].search_read(['|', ('id', 'in', question_ids), ('parent_id', 'in', question_ids)], ['id', 'create_date']) + sample_size = int(len(question_ids) * 0.7) + res_ids = random.sample(posts, sample_size) + nb_comments = 0 + idx = 0 + post_to_comment_id = None + hours = [_ for _ in range(1, 24)] + + for values in iterator: + if nb_comments == 0: + nb_comments = random.choices(*zip(*CP_WEIGHTS.items()))[0] + idx += 1 + post_to_comment_id = res_ids[idx]['id'] + nb_comments -= 1 + values.update({ + 'date': fields.Datetime.add(res_ids[idx]['create_date'], hours=random.choice(hours)), + 'res_id': post_to_comment_id + }) + yield values + + return [ + ('author_id', get_author_id), + ('body', populate.constant('message_body_{counter}')), + ('message_type', populate.constant('comment')), + ('model', populate.constant('forum.post')), + ('_res_id_and_date', get_res_id_and_date), + ('subtype_id', populate.constant(comment_subtype.id)), + ] + + def _populate(self, size): + records = super()._populate(size) + + _logger.info('mail.message: update comments create date and uid') + _logger.info('forum.post: update last_activity_date for posts with comments and/or commented answers') + query = """ + WITH comment_author AS( + SELECT mm.id, mm.author_id, ru.id as user_id, ru.partner_id + FROM mail_message mm + JOIN res_users ru + ON mm.author_id = ru.partner_id + WHERE mm.id in %(comment_ids)s + ), + updated_comments as ( + UPDATE mail_message mm + SET create_date = date, + create_uid = ca.user_id + FROM comment_author ca + WHERE mm.id = ca.id + RETURNING res_id as post_id, create_date as comment_date + ), + max_comment_dates AS ( + SELECT post_id, max(comment_date) as last_comment_date + FROM updated_comments + GROUP BY post_id + ), + updated_posts AS ( + UPDATE forum_post fp + SET last_activity_date = CASE --on questions, answer could be more recent + WHEN fp.parent_id IS NOT NULL THEN greatest(last_activity_date, last_comment_date) + ELSE last_comment_date END + FROM max_comment_dates + WHERE max_comment_dates.post_id = fp.id + RETURNING fp.id as post_id, fp.last_activity_date as last_activity_date, fp.parent_id as parent_id + ) + UPDATE forum_post fp + SET last_activity_date = greatest(fp.last_activity_date, up.last_activity_date) + FROM updated_posts up + WHERE up.parent_id = fp.id + """ + self.env.cr.execute(query, {'comment_ids': tuple(records.ids)}) + + return records diff --git a/addons/website_slides_forum/__init__.py b/addons/website_slides_forum/__init__.py index 8c67f893b37..ca9d16e5409 100644 --- a/addons/website_slides_forum/__init__.py +++ b/addons/website_slides_forum/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import models from . import controllers +from . import models +from . import populate diff --git a/addons/website_slides_forum/populate/__init__.py b/addons/website_slides_forum/populate/__init__.py new file mode 100644 index 00000000000..2efe347cd8d --- /dev/null +++ b/addons/website_slides_forum/populate/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import forum_forum diff --git a/addons/website_slides_forum/populate/forum_forum.py b/addons/website_slides_forum/populate/forum_forum.py new file mode 100644 index 00000000000..58e329d5919 --- /dev/null +++ b/addons/website_slides_forum/populate/forum_forum.py @@ -0,0 +1,30 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from itertools import zip_longest + +from odoo import models +from odoo.addons.website_slides.populate.slide_channel import SlideChannel + + +class SlidesForum(models.Model): + _inherit = 'forum.forum' + + @property + def _populate_sizes(self): + return {size: count + SlideChannel._populate_sizes[size] for size, count in super()._populate_sizes.items()} + + @property + def _populate_dependencies(self): + return super()._populate_dependencies + ['slide.channel'] + + def _populate_factories(self): + def link_course(iterator, *args, **kwargs): + courses = self.env['slide.channel'].browse(self.env.registry.populated_models['slide.channel']) + for values, course in zip_longest(iterator, courses): + if course: + values.update(slide_channel_ids=course, name=f"{course.name}'s Forum") + yield values + + return super()._populate_factories() + [ + ('_name_and_course', link_course), + ]