Skip to content

Commit

Permalink
[IMP] M2M Dynamic Fields.
Browse files Browse the repository at this point in the history
  • Loading branch information
Murtaza-OSI committed Feb 14, 2024
1 parent 7c7f05b commit d4d0491
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 20 deletions.
15 changes: 8 additions & 7 deletions product_configurator/models/product_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1481,13 +1481,14 @@ def flatten_val_ids(self, value_ids):
(e.g: [1, 2, 3, [4, 5, 6]])
:returns: flattened list of ids ([1, 2, 3, 4, 5, 6])"""
flat_val_ids = set()
for val in value_ids:
if not val:
continue
if isinstance(val, list):
flat_val_ids |= set(val)
elif isinstance(val, int):
flat_val_ids.add(val)
if value_ids and value_ids[0]:
for val in value_ids[0]:
if not val:
continue
if isinstance(val, list):
flat_val_ids.add(val[1])
elif isinstance(val, int):
flat_val_ids.add(val)
return list(flat_val_ids)

def formatPrices(self, prices=None, dp="Product Price"):
Expand Down
235 changes: 222 additions & 13 deletions product_configurator/wizard/product_configurator.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from typing import Dict, List
from odoo.tools.misc import OrderedSet

from lxml import etree

from odoo import _, api, fields, models, tools
Expand Down Expand Up @@ -227,9 +230,11 @@ def get_form_vals(
continue
available_val_ids = domains[k][0][2]
if isinstance(v, list):
# pass
if any(type(el) != int for el in v):
v = v[0][2]
value_ids = list(set(v) & set(available_val_ids))

v = v[0][1]
value_ids = available_val_ids.append(v)
dynamic_fields.update({k: value_ids})
vals[k] = [[6, 0, value_ids]]
elif v not in available_val_ids:
Expand All @@ -239,6 +244,7 @@ def get_form_vals(
vals[k] = v

final_cfg_val_ids = list(dynamic_fields.values())
print ("\n final_cfg_val_ids",final_cfg_val_ids)

vals.update(self.get_onchange_vals(final_cfg_val_ids, config_session_id))
# To solve the Multi selection problem removing extra []
Expand Down Expand Up @@ -307,11 +313,18 @@ def apply_onchange_values(self, values, field_name, field_onchange):
# Get the unstored values from the client view
for k, v in dynamic_fields.items():
attr_id = int(k.split(field_prefix)[1])
# if isinstance(v, list):
# dynamic_fields[k] = v[0][2]
valve_ids = product_tmpl_id.config_line_ids.filtered(
lambda line: int(v) in line.domain_id.domain_line_ids.value_ids.ids
).mapped("value_ids")
valve_ids = self.env['product.attribute.value']
if isinstance(v, list):
for att in v:
valve_ids |= product_tmpl_id.config_line_ids.filtered(
lambda line: int(att[1])
in line.domain_id.domain_line_ids.value_ids.ids
).mapped("value_ids")
else:
valve_ids = product_tmpl_id.config_line_ids.filtered(
lambda line: int(v) in line.domain_id.domain_line_ids.value_ids.ids
).mapped("value_ids")

self.domain_attr_2_ids = [(6, 0, valve_ids.ids)]

line_attributes = cfg_step.attribute_line_ids.mapped("attribute_id")
Expand All @@ -322,15 +335,15 @@ def apply_onchange_values(self, values, field_name, field_onchange):
if not v:
continue
if isinstance(v, list):
view_val_ids |= set(v[0][2])
for a in v:
view_val_ids.add(a[1])
elif isinstance(v, int):
view_val_ids.add(v)

# Clear all DB values belonging to attributes changed in the wizard
cfg_vals = cfg_vals.filtered(
lambda v: v.attribute_id.id not in view_attribute_ids
)

# Combine database values with wizard values_available
cfg_val_ids = cfg_vals.ids + list(view_val_ids)

Expand All @@ -354,7 +367,6 @@ def apply_onchange_values(self, values, field_name, field_onchange):
product_tmpl_id=product_tmpl_id,
config_session_id=config_session_id,
)

return {"value": vals, "domain": domains}

def onchange(self, values, field_name, field_onchange):
Expand Down Expand Up @@ -732,9 +744,7 @@ def add_dynamic_fields(self, res, dynamic_fields, wiz):
attr_lines = wiz.product_tmpl_id.attribute_line_ids.sorted()

# Loop over the dynamic fields and add them to the view one by one
for attr_line in attr_lines.filtered(
lambda attr_line: not attr_line.multi
): # TODO: NC: Added a filter for multi
for attr_line in attr_lines: # TODO: NC: Added a filter for multi
(
attrs,
field_name,
Expand All @@ -759,6 +769,8 @@ def add_dynamic_fields(self, res, dynamic_fields, wiz):
"active_id": wiz.product_tmpl_id.id,
"wizard_id": wiz.id,
"field_name": field_name,
"is_m2m": attr_line.multi,
"value_ids": attr_line.value_ids.ids,
}
),
options=str(
Expand Down Expand Up @@ -1095,6 +1107,203 @@ def action_config_done(self):
}
return action

def web_read(self, specification: Dict[str, Dict]) -> List[Dict]:
fields_to_read = list(specification) or ["id"]

if fields_to_read == ["id"]:
# if we request to read only the ids, we have them already so we can build the return dictionaries immediately
# this also avoid a call to read on the co-model that might have different access rules
values_list = [{"id": id_} for id_ in self._ids]
else:
values_list: List[Dict] = self.read(fields_to_read, load=None)

if not values_list:
return values_list

def cleanup(vals: Dict) -> Dict:
"""Fixup vals['id'] of a new record."""
if not vals["id"]:
vals["id"] = vals["id"].origin or False
return vals

for field_name, field_spec in specification.items():
field = self._fields.get(field_name)
if field is None:
if (
field_spec.get("context")
and "is_m2m" in field_spec.get("context")
and field_spec.get("context").get("is_m2m")
):
if not field_spec:
continue

if field_spec.get("context").get("value_ids"):
co_records = self.env["product.attribute.value"].browse(
field_spec.get("context").get("value_ids")
)

if "order" in field_spec and field_spec["order"]:
co_records = co_records.search(
[("id", "in", co_records.ids)], order=field_spec["order"]
)
order_key = {
co_record.id: index
for index, co_record in enumerate(co_records)
}
for values in values_list:
# filter out inaccessible corecords in case of "cache pollution"
values[field_name] = [
id_ for id_ in values[field_name] if id_ in order_key
]
values[field_name] = sorted(
values[field_name], key=order_key.__getitem__
)

if "context" in field_spec:
co_records = co_records.with_context(**field_spec["context"])

if "fields" in field_spec:
if field_spec.get("limit") is not None:
limit = field_spec["limit"]
ids_to_read = OrderedSet(
id_
for values in values_list
for id_ in values[field_name][:limit]
)
co_records = co_records.browse(ids_to_read)

x2many_data = {
vals["id"]: vals
for vals in co_records.web_read(field_spec["fields"])
}
for values in values_list:
values[field_name] = [
x2many_data.get(id_) or {"id": id_}
for id_ in x2many_data
]
continue

if field.type == "many2one":
if "fields" not in field_spec:
for values in values_list:
if isinstance(values[field_name], NewId):
values[field_name] = values[field_name].origin
continue

co_records = self[field_name]
if "context" in field_spec:
co_records = co_records.with_context(**field_spec["context"])

extra_fields = dict(field_spec["fields"])
extra_fields.pop("display_name", None)

many2one_data = {
vals["id"]: cleanup(vals)
for vals in co_records.web_read(extra_fields)
}

if "display_name" in field_spec["fields"]:
for rec in co_records.sudo():
many2one_data[rec.id]["display_name"] = rec.display_name

for values in values_list:
if values[field_name] is False:
continue
vals = many2one_data[values[field_name]]
values[field_name] = vals["id"] and vals

elif field.type in ("one2many", "many2many"):
if not field_spec:
continue

co_records = self[field_name]

if "order" in field_spec and field_spec["order"]:
co_records = co_records.search(
[("id", "in", co_records.ids)], order=field_spec["order"]
)
order_key = {
co_record.id: index
for index, co_record in enumerate(co_records)
}
for values in values_list:
# filter out inaccessible corecords in case of "cache pollution"
values[field_name] = [
id_ for id_ in values[field_name] if id_ in order_key
]
values[field_name] = sorted(
values[field_name], key=order_key.__getitem__
)

if "context" in field_spec:
co_records = co_records.with_context(**field_spec["context"])

if "fields" in field_spec:
if field_spec.get("limit") is not None:
limit = field_spec["limit"]
ids_to_read = OrderedSet(
id_
for values in values_list
for id_ in values[field_name][:limit]
)
co_records = co_records.browse(ids_to_read)

x2many_data = {
vals["id"]: vals
for vals in co_records.web_read(field_spec["fields"])
}

for values in values_list:
values[field_name] = [
x2many_data.get(id_) or {"id": id_}
for id_ in values[field_name]
]

elif field.type in ("reference", "many2one_reference"):
if not field_spec:
continue

values_by_id = {vals["id"]: vals for vals in values_list}
for record in self:
if not record[field_name]:
continue

if field.type == "reference":
co_record = record[field_name]
else: # field.type == 'many2one_reference'
co_record = self.env[record[field.model_field]].browse(
record[field_name]
)

if "context" in field_spec:
co_record = co_record.with_context(**field_spec["context"])

if "fields" in field_spec:
reference_read = co_record.web_read(field_spec["fields"])
if any(fname != "id" for fname in field_spec["fields"]):
co_record_exists = bool(reference_read)
else:
co_record_exists = co_record.exists()
else:
co_record_exists = co_record.exists()

record_values = values_by_id[record.id]

if not co_record_exists:
record_values[field_name] = False
if field.type == "many2one_reference":
record_values[field.model_field] = False
continue

if "fields" in field_spec:
record_values[field_name] = reference_read[0]
if field.type == "reference":
record_values[field_name]["id"] = {
"id": co_record.id,
"model": co_record._name,
}
return values_list


# class ProductConfiguratorCustomValue(models.TransientModel):
# _name = "product.configurator.custom.value"
Expand Down

0 comments on commit d4d0491

Please sign in to comment.