# Part of OpenG2P Registry. See LICENSE file for full copyright and licensing details.
import logging
from odoo import fields, models
from odoo.osv import expression
_logger = logging.getLogger(__name__)
class G2PMembershipGroup(models.Model):
_inherit = "res.partner"
group_membership_ids = fields.One2many(
"g2p.group.membership", "group", "Group Members" # , auto_join=True
)
force_recompute_canary = fields.Datetime(
compute="_compute_force_recompute_group", store=True, readonly=True
)
z_ind_grp_num_individuals = fields.Integer(
"Number of individuals",
compute="_compute_ind_grp_num_individuals",
store=True,
)
def _compute_force_recompute_group(self):
# _logger.info("SQL DEBUG: force_recompute_group: records:%s" % self.ids)
# We use this trick to have a consolidated list of groups to recompute
self.with_delay(
priority=5, channel="root.recompute_indicators"
).recompute_indicators()
for group in self:
group.force_recompute_canary = fields.Datetime.now()
def _compute_ind_grp_num_individuals(self):
self.compute_count_and_set_indicator("z_ind_grp_num_individuals", None, [])
def recompute_indicators_for_all_records(self, recomputed_fields=None):
# Set the batch size to 10000
batch_size = 10000
# Get the total number of records
total_records = self.env["res.partner"].search_count(
[
("is_group", "=", True),
("is_registrant", "=", True),
("disabled", "=", None),
]
)
# Iterate through the records in batches of 10000
for i in range(0, total_records, batch_size):
self.with_delay(
priority=5, channel="root.recompute_indicators"
).recompute_indicators_for_batch(
i, batch_size, recomputed_fields=recomputed_fields
)
def recompute_indicators_for_batch(self, offset, limit, recomputed_fields=None):
# Get the records
partners = self.env["res.partner"].search(
[
("is_group", "=", True),
("is_registrant", "=", True),
("disabled", "=", None),
],
offset=offset,
limit=limit,
order="id",
)
partners.recompute_indicators(recomputed_fields=recomputed_fields)
def recompute_indicators(self, recomputed_fields=None):
if recomputed_fields is None:
recomputed_fields = self._get_calculated_group_fields()
for field in recomputed_fields:
self.env.add_to_compute(field, self)
def _get_calculated_group_fields(self):
model_fields_id = self.env["res.partner"]._fields
fields = []
for field_name, field in model_fields_id.items():
els = field_name.split("_")
if field.compute and len(els) >= 3 and els[2] == "grp" and els[1] == "ind":
fields.append(field)
return fields
def count_individuals(self, relationship_kinds=None, domain=None):
"""
Count the number of individuals in the group that match the kinds and domain.
"""
# _logger.info("SQL DEBUG: count_individuals: records:%s" % self.ids)
membership_kind_domain = None
individual_domain = None
if self.group_membership_ids:
if relationship_kinds:
membership_kind_domain = [("name", "in", relationship_kinds)]
else:
return dict()
if domain is not None:
individual_domain = domain
query_result = self._query_members_aggregate(
membership_kind_domain, individual_domain
)
return query_result
def _query_members_aggregate(
self, membership_kind_domain=None, individual_domain=None
):
# _logger.info("SQL DEBUG: query_members_aggregate: records:%s" % self.ids)
ids = self.ids
partner_model = "res.partner"
domain = [
("is_registrant", "=", True),
("is_group", "=", True),
("disabled", "=", None),
]
query_obj = self.env[partner_model]._where_calc(domain)
membership_alias = query_obj.left_join(
"res_partner", "id", "g2p_group_membership", "group", "id"
)
individual_alias = query_obj.left_join(
membership_alias, "individual", "res_partner", "id", "individual"
)
membership_kind_rel_alias = query_obj.left_join(
membership_alias,
"id",
"g2p_group_membership_g2p_group_membership_kind_rel",
"g2p_group_membership_id",
"id",
)
rel_kind_alias = query_obj.left_join(
membership_kind_rel_alias,
"g2p_group_membership_kind_id",
"g2p_group_membership_kind",
"id",
"id",
)
# Add INNER JOIN with VALUES (ids)
# TODO: In the absence of managing "INNER JOIN" by Odoo Query object,
# We will create the inner join manually
inner_join_vals = "(" + "), (".join(map(str, ids)) + ")"
inner_join_query = "INNER JOIN ( VALUES %s ) vals(v)" % inner_join_vals
inner_join_query += ' ON ("%s"."group" = v and "%s"."ended_date" IS NULL) ' % (
membership_alias,
membership_alias,
)
# Build where clause for the membership_alias
membership_query_obj = expression.expression(
model=self.env["g2p.group.membership"],
domain=[("ended_date", "=", None)], # ("group", "in", ids)],
alias=membership_alias,
).query
(
membership_from_clause,
membership_where_clause,
membership_where_params,
) = membership_query_obj.get_sql()
# _logger.info("SQL DEBUG: Membership Kind Query: From:%s, Where:%s, Params:%s" %
# (membership_from_clause,membership_where_clause,membership_where_params))
query_obj.add_where(membership_where_clause, membership_where_params)
if membership_kind_domain:
membership_kind_query_obj = expression.expression(
model=self.env["g2p.group.membership.kind"],
domain=membership_kind_domain,
alias=rel_kind_alias,
).query
(
membership_kind_from_clause,
membership_kind_where_clause,
membership_kind_where_params,
) = membership_kind_query_obj.get_sql()
# _logger.info("SQL DEBUG: Membership Kind Query: From:%s, Where:%s, Params:%s" %
# (membership_kind_from_clause,membership_kind_where_clause,membership_kind_where_params))
query_obj.add_where(
membership_kind_where_clause, membership_kind_where_params
)
if individual_domain:
individual_query_obj = expression.expression(
model=self.env[partner_model],
domain=individual_domain,
alias=individual_alias,
).query
(
individual_from_clause,
individual_where_clause,
individual_where_params,
) = individual_query_obj.get_sql()
# _logger.info("SQL DEBUG: Individual Query: From:%s, Where:%s, Params:%s" %
# (individual_from_clause,individual_where_clause,individual_where_params))
query_obj.add_where(individual_where_clause, individual_where_params)
select_query, select_params = query_obj.select(
"res_partner.id AS id", "count(*) AS members_cnt"
)
# TODO: In the absence of managing "GROUP BY" by Odoo Query object,
# we will add the GROUP BY clause manually
select_query += " GROUP BY " + partner_model.replace(".", "_") + ".id"
# TODO: In the absence of managing "INNER JOIN" by Odoo Query object,
# Inject the prepared INNER JOIN manually
index = select_query.find("WHERE")
select_query = select_query[:index] + inner_join_query + select_query[index:]
# _logger.info(
# "SQL DEBUG: SQL query: %s, params: %s" % (select_query, select_params)
# )
self._cr.execute(select_query, select_params)
# Generate result as tuple
results = self._cr.fetchall()
# _logger.info("SQL DEBUG: SQL Query Result: %s" % results)
return results
[docs] def compute_count_and_set_indicator(
self, field_name, kinds, domain, presence_only=False
):
"""
This method computes the count matching a domain, then sets the indicator on the field name.
:param field_name: The name of the field.
:type field_name: str
:param kinds: The kinds of roles in the group
:type kinds: list
:param domain: The domain to filter group members.
:type domain: list
:param presence_only: A boolean value to define if we return a boolean instead of the count
:type presence_only: bool
:return: The count of the specified field, then sets the indicator on the field name.
:rtype: int, bool
"""
# _logger.info(
# "SQL DEBUG: compute_count_and_set_indicator: total records:%s" % len(self)
# )
# Get groups only
records = self.filtered(lambda a: a.is_group)
query_result = None
if records:
# Generate the SQL query
query_result = records.count_individuals(
relationship_kinds=kinds, domain=domain
)
# _logger.info(
# "SQL DEBUG: compute_count_and_set_indicator: field:%s, results:%s"
# % (field_name, query_result)
# )
result_map = dict(query_result)
for record in records:
if presence_only:
record[field_name] = result_map.get(record.id, 0) > 0
else:
record[field_name] = result_map.get(record.id, 0)
def _update_compute_fields(
self, records, field_name, kinds, domain, presence_only=False
):
# Get groups only
records = records.filtered(lambda a: a.is_group)
query_result = None
if records:
# Generate the SQL query using Job Queue
query_result = records.count_individuals(
relationship_kinds=kinds, domain=domain
)
# _logger.info(
# "SQL DEBUG: job_queue->_update_compute_fields: field:%s, results:%s"
# % (field_name, query_result)
# )
# if query_result:
# # Update the compute fields and affected records
result_map = dict(query_result)
for record in records:
# _logger.info(
# "SQL DEBUG: XXX job_queue->_update_compute_fields: record.id:%s, results:%s"
# % (record.id, result_map.get(record.id, 0))
# )
if presence_only:
record[field_name] = result_map.get(record.id, 0) > 0
else:
record[field_name] = result_map.get(record.id, 0)