forked from Mapan/odoo17e
266 lines
12 KiB
Python
266 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import models
|
|
from odoo.tools import populate
|
|
from odoo.addons.base.models.ir_qweb_fields import nl2br
|
|
from odoo.addons.knowledge.populate import tools
|
|
|
|
|
|
class KnowledgeArticle(models.Model):
|
|
_inherit = 'knowledge.article'
|
|
_populate_dependencies = [
|
|
'res.users', # membership management
|
|
]
|
|
# be careful that size only defines the amount of ROOT articles
|
|
# there will be a lot more articles in the end, counting children
|
|
# see underlying docstring in '_prepare_children_articles'
|
|
# manual testing gives around 3000-4000 generated articles for "medium" size
|
|
# (giving a bigger size also increases the amount of res.users we have for members config)
|
|
# /!\ using "large" can take a very long time as we can't take advantage of the batching
|
|
# because of parent / children relationship, everything is committed in one singular batch
|
|
_populate_sizes = {
|
|
'small': 5, # will result in ~1.500
|
|
'medium': 20, # will result in ~5.000 records
|
|
'large': 100, # will result in ~27.000 records
|
|
}
|
|
|
|
def _populate(self, size):
|
|
# create some dummy portal users as res.users populate only creates internal users
|
|
self.env['res.users'].create([{
|
|
'login': 'portal_user_knowledge_%i' % i,
|
|
'email': 'portaluserknowledge%i@example.com' % i,
|
|
'name': 'Portal User %i' % i,
|
|
'groups_id': [
|
|
(6, 0, [self.env.ref('base.group_portal').id]),
|
|
]
|
|
} for i in range(self.env['res.users']._populate_sizes[size])])
|
|
|
|
return super()._populate(size)
|
|
|
|
def _populate_factories(self, depth=0):
|
|
internal_partner_ids = self.env['res.users'].search([
|
|
('share', '=', False),
|
|
]).partner_id.ids
|
|
shared_partner_ids = self.env['res.users'].search([
|
|
('share', '=', True),
|
|
]).partner_id.ids
|
|
|
|
return self._populate_article_factories(depth, internal_partner_ids, shared_partner_ids)
|
|
|
|
def _populate_article_factories(self, depth, internal_partner_ids, shared_partner_ids):
|
|
random = populate.Random('articles')
|
|
if depth == 0:
|
|
names = populate.iterate(tools._a_title_root)
|
|
elif depth == 1:
|
|
names = populate.iterate(tools._a_title_top_level)
|
|
elif depth == 2:
|
|
names = populate.iterate(tools._a_title_leaf)
|
|
else:
|
|
names = populate.randomize(tools._a_title_low_level, seed=random.randint(1, 100))
|
|
|
|
return {
|
|
('name', names),
|
|
('body', populate.randomize([
|
|
nl2br('%s' % tuple(tools.a_body_content_lorem_ipsum[:1])),
|
|
nl2br('%s %s' % tuple(tools.a_body_content_lorem_ipsum[:2])),
|
|
nl2br('%s %s %s' % tuple(tools.a_body_content_lorem_ipsum[:3])),
|
|
])),
|
|
('child_ids', populate.compute(lambda *args, **kwargs: self._prepare_children_articles(depth, internal_partner_ids, shared_partner_ids))),
|
|
('icon', populate.randomize(['🗒️', '🤖', '⭐', '🚀', '🎉', '☕', '🏆', '🛫', '💰', '📫'])),
|
|
('internal_permission', populate.compute(lambda *args, **kwargs: self._generate_internal_permission(depth))),
|
|
('is_locked', populate.randomize([True, False], [0.02, 0.98], seed=random.randint(1, 100))),
|
|
('full_width', populate.randomize([True, False], [0.2, 0.8], seed=random.randint(1, 100))),
|
|
('article_member_ids', populate.compute(lambda random, values, **kwargs: self._prepare_member_ids(depth, values, internal_partner_ids, shared_partner_ids))),
|
|
('is_locked', populate.randomize([True, False], [0.02, 0.98])),
|
|
('full_width', populate.randomize([True, False], [0.2, 0.8])),
|
|
# TODO add article items / fill some properties fields
|
|
('favorite_ids', populate.compute(lambda values, *args, **kwargs: self._prepare_favorites(values, internal_partner_ids, shared_partner_ids))),
|
|
}
|
|
|
|
def _prepare_favorites(self, values, internal_partner_ids, shared_partner_ids):
|
|
"""In Knowledge we offer the possibility for a user to set articles as favorites.
|
|
To handle this in the model we have a table `knowledge.article.favorite` that stores every
|
|
favorited article for each user.
|
|
|
|
Every article has a chance to be the favorite of a user but it is more likely that user will add
|
|
a root as a favorite than a child.
|
|
Thus to prepare the favorites we will need some information:
|
|
* Will the article be favorited by a user ?
|
|
* Which user wants to add the article to its favorite ?
|
|
|
|
Of course we do not want a lot of articles being favorited so the initial check will reject many of the attempts,
|
|
but if we want to add some favorite_ids to an article then we will check how many users will add the article as
|
|
favorite.
|
|
This is how we will populate the table."""
|
|
random = populate.Random('favorites')
|
|
|
|
article_member_ids = values.get('article_member_ids', False)
|
|
internal_permission = values.get('internal_permission', False)
|
|
# 2% of the articles can be set as favorites
|
|
can_be_favorite = random.choices([True, False], weights=[0.02, 0.98], k=1)[0] and (
|
|
internal_permission or article_member_ids)
|
|
if not can_be_favorite:
|
|
return []
|
|
|
|
admin_partner_id = self.env.ref('base.user_admin').partner_id.id
|
|
|
|
# sampling some partners
|
|
internals = random.sample(internal_partner_ids, k=min(random.randint(2, 10), len(internal_partner_ids)))
|
|
externals = random.sample(shared_partner_ids, k=min(random.randint(2, 10), len(shared_partner_ids)))
|
|
# 50% chance to force admin into the mix (easier for testing)
|
|
if admin_partner_id not in internals and random.choices([True, False], weights=[0.5, 0.5], k=1)[0]:
|
|
internals.append(admin_partner_id)
|
|
|
|
# If there are article members, check their permissions, otherwise, all are favorites
|
|
all_partners = internals + externals
|
|
favorite_partner_ids, partners_to_check_access = ([], all_partners) if article_member_ids else (all_partners, [])
|
|
|
|
for partner_id in partners_to_check_access:
|
|
member_access = next(
|
|
(
|
|
member[2]['permission']
|
|
for member in article_member_ids
|
|
if member[2]['partner_id'] == partner_id
|
|
),
|
|
False
|
|
)
|
|
|
|
if member_access == 'none':
|
|
continue # specified no access for this partner -> pass
|
|
|
|
if internal_permission == 'none' and not member_access:
|
|
continue # internal permission denies access -> pass
|
|
|
|
favorite_partner_ids.append(partner_id)
|
|
|
|
# sadly the knowledge.article.favorite model needs user_id, so we have to fetch them
|
|
linked_users = self.env['res.users'].search([('partner_id', 'in', favorite_partner_ids)])
|
|
user_per_partner = {
|
|
user.partner_id.id: user
|
|
for user in linked_users
|
|
}
|
|
|
|
return [(0, 0, {
|
|
'user_id': user_per_partner[partner_id].id
|
|
}) for partner_id in favorite_partner_ids]
|
|
|
|
def _prepare_children_articles(self, depth, internal_partner_ids, shared_partner_ids):
|
|
""" As knowledge.article is a bit meaningless without a parent / children configuration,
|
|
this methods aims to fill-up child_ids recursively.
|
|
|
|
As the regular populate only allows to specify the 'total amount of records' we want to
|
|
create, and it does not handle parent / children relationship, we apply a specific logic for
|
|
children articles.
|
|
|
|
The idea is to always have children for root articles and then lower the chances of generating
|
|
children as you go into higher 'depth', with a maximum of 5 levels in total.
|
|
The amount of children is chosen randomly between 2 and 10.
|
|
|
|
The code that generates values re-uses the '_populate_factories' method and increases the depth
|
|
every time we loop. """
|
|
random = populate.Random('childrenarticles')
|
|
|
|
if depth > 4 or random.randint(1, depth + 1) != depth + 1:
|
|
# higher depth means lower chance of having children articles
|
|
# depth of 0 -> 100% (randint(1,1) needs to equal 1)
|
|
# depth of 1 -> 50% (randint(1,2) needs to equal 2)
|
|
# depth of 2 -> 33% (randint(1,2,3) needs to equal 3)
|
|
# ...
|
|
return []
|
|
|
|
record_count = 0
|
|
create_values = []
|
|
field_generators = self._populate_article_factories(depth + 1, internal_partner_ids, shared_partner_ids)
|
|
|
|
generator = populate.chain_factories(field_generators, self._name)
|
|
for _i in range(random.randint(2, 10)):
|
|
values = next(generator)
|
|
values.pop('__complete')
|
|
create_values.append((0, 0, values))
|
|
record_count += 1
|
|
|
|
return create_values
|
|
|
|
def _generate_internal_permission(self, depth):
|
|
random = populate.Random('internalpermission')
|
|
if depth != 0:
|
|
# we keep it simple and only set custom internal permission to root articles
|
|
return False
|
|
|
|
category = random.choices(['workspace', 'private'], weights=[0.9, 0.1], k=1)[0]
|
|
if category == 'private':
|
|
# private articles should be 'none' and handled at members level
|
|
return 'none'
|
|
|
|
# 80% will be write, 20% read
|
|
return random.choices(['write', 'read'], weights=[0.8, 0.2], k=1)[0]
|
|
|
|
def _prepare_member_ids(self, depth, values, internal_partner_ids, shared_partner_ids):
|
|
random = populate.Random('members')
|
|
private_member_values = []
|
|
|
|
admin_partner_id = self.env.ref('base.user_admin').partner_id.id
|
|
if values.get('internal_permission') == 'none':
|
|
# private article -> keep it simple and assume that people will test with admin user
|
|
# there will be one auto-generated private article for every user anyway (so size is already big)
|
|
private_member_values = [(0, 0, {
|
|
'partner_id': admin_partner_id,
|
|
'permission': 'write'
|
|
})]
|
|
|
|
# we tweak the end range, so that the deeper you go, the less chance you have of having
|
|
# members configuration
|
|
randint = random.randint(1, (depth + 1) * 10)
|
|
if randint > 4 and values.get('internal_permission') == 'write':
|
|
# 60% base chance of not having member configuration
|
|
# unless we don't have a 'write' internal permission, in which case we need members
|
|
return private_member_values
|
|
|
|
internal_partner_ids = random.sample(internal_partner_ids, k=min(random.randint(2, 10), len(internal_partner_ids)))
|
|
members_values = []
|
|
for partner_id in internal_partner_ids:
|
|
if partner_id == admin_partner_id and private_member_values:
|
|
continue # avoid duplicate member
|
|
|
|
# force one write members, otherwise 70% of write, 25% read, 5% none
|
|
permission = 'write' if partner_id == internal_partner_ids[0] else random.choices(
|
|
['write', 'read', 'none'], weights=[70, 25, 5], k=1
|
|
)[0]
|
|
|
|
members_values.append({
|
|
'partner_id': partner_id,
|
|
'permission': permission
|
|
})
|
|
|
|
internal_member_ids = [
|
|
(0, 0, member_values)
|
|
for member_values in members_values
|
|
]
|
|
|
|
if randint > 2 or values.get('internal_permission') == 'read':
|
|
# 20% base chance of having only internal members
|
|
return private_member_values + internal_member_ids
|
|
|
|
# 10% base chance of having only external members
|
|
shared_partner_ids = random.sample(shared_partner_ids, k=min(random.randint(2, 10), len(shared_partner_ids)))
|
|
members_values = []
|
|
for partner_id in shared_partner_ids:
|
|
# 90% of write
|
|
members_values.append({
|
|
'partner_id': partner_id,
|
|
'permission': random.choices(['read', 'none'], weights=[0.9, 0.1], k=1)[0]
|
|
})
|
|
|
|
shared_member_ids = [
|
|
(0, 0, member_values)
|
|
for member_values in members_values
|
|
]
|
|
|
|
if randint == 2 and values.get('internal_permission') == 'write':
|
|
# 10% base chance of having only external members (only for write internal permission)
|
|
return shared_member_ids
|
|
|
|
# 10% chance to have a mix of both
|
|
return private_member_values + internal_member_ids + shared_member_ids
|