[IMP] website_forum,*: populate with forums, posts and comments

* website_slides_forum

Populate a database with different quantities of forums, posts
and comments.The number of replies per question and comments per post
(question or reply) are defined by weights.

We use SQL queries in the '_populate' method to update certain fields
automatically managed by the ORM, as well as the 'last_activity_date' of
posts (questions).
The aim is to give posts, replies and comments varied and consistent
create_dates, as well as a wide variety of authors (create_uid).

Task-3349374
Task-3463533

closes odoo/odoo#132042

Signed-off-by: Florian Charlier (flch) <flch@odoo.com>
This commit is contained in:
Lopes Marc (loma)
2023-10-19 14:47:14 +00:00
parent c2fdf83e0b
commit 893a6e4f8e
8 changed files with 247 additions and 1 deletions
+1
View File
@@ -2,3 +2,4 @@
from . import controllers
from . import models
from . import populate
@@ -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
@@ -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}'))
]
@@ -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
@@ -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
+2 -1
View File
@@ -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
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import forum_forum
@@ -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),
]