[FIX] upgrade_analysis: misleading representation of read/write computed fields
Odoo 14 introduced the widescale usage of computed fields with readonly=False. In that case, the compute method functions as a default that can also be used to compute a value some time *after* the initial creation of the record. In the OpenUpgrade analysis files, these fields would be misrepresented as computed fields rather than fields with a default function. This change fixes that.pull/2416/head
parent
8510463ffe
commit
a01c6d478e
|
@ -134,8 +134,6 @@ def report_generic(new, old, attrs, reprs):
|
||||||
if attr == "required":
|
if attr == "required":
|
||||||
if old[attr] != new["required"] and new["required"]:
|
if old[attr] != new["required"] and new["required"]:
|
||||||
text = "now required"
|
text = "now required"
|
||||||
if new["req_default"]:
|
|
||||||
text += ", req_default: %s" % new["req_default"]
|
|
||||||
fieldprint(old, new, "", text, reprs)
|
fieldprint(old, new, "", text, reprs)
|
||||||
elif attr == "stored":
|
elif attr == "stored":
|
||||||
if old[attr] != new[attr]:
|
if old[attr] != new[attr]:
|
||||||
|
@ -284,15 +282,19 @@ def compare_sets(old_records, new_records):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
printkeys = [
|
# Info that is displayed for deleted fields
|
||||||
|
printkeys_old = [
|
||||||
"relation",
|
"relation",
|
||||||
"required",
|
"required",
|
||||||
"selection_keys",
|
"selection_keys",
|
||||||
"req_default",
|
|
||||||
"_inherits",
|
"_inherits",
|
||||||
"mode",
|
"mode",
|
||||||
"attachment",
|
"attachment",
|
||||||
]
|
]
|
||||||
|
# Info that is displayed for new fields
|
||||||
|
printkeys_new = printkeys_old + [
|
||||||
|
"hasdefault",
|
||||||
|
]
|
||||||
for column in old_records:
|
for column in old_records:
|
||||||
if column["field"] == "_order":
|
if column["field"] == "_order":
|
||||||
continue
|
continue
|
||||||
|
@ -304,7 +306,7 @@ def compare_sets(old_records, new_records):
|
||||||
extra_message = ", ".join(
|
extra_message = ", ".join(
|
||||||
[
|
[
|
||||||
k + ": " + str(column[k]) if k != str(column[k]) else k
|
k + ": " + str(column[k]) if k != str(column[k]) else k
|
||||||
for k in printkeys
|
for k in printkeys_old
|
||||||
if column[k]
|
if column[k]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -312,11 +314,6 @@ def compare_sets(old_records, new_records):
|
||||||
extra_message = " " + extra_message
|
extra_message = " " + extra_message
|
||||||
fieldprint(column, "", "", "DEL" + extra_message, reprs)
|
fieldprint(column, "", "", "DEL" + extra_message, reprs)
|
||||||
|
|
||||||
printkeys.extend(
|
|
||||||
[
|
|
||||||
"hasdefault",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
for column in new_records:
|
for column in new_records:
|
||||||
if column["field"] == "_order":
|
if column["field"] == "_order":
|
||||||
continue
|
continue
|
||||||
|
@ -325,13 +322,13 @@ def compare_sets(old_records, new_records):
|
||||||
continue
|
continue
|
||||||
if column["mode"] == "create":
|
if column["mode"] == "create":
|
||||||
column["mode"] = ""
|
column["mode"] = ""
|
||||||
printkeys_plus = printkeys.copy()
|
printkeys = printkeys_new.copy()
|
||||||
if column["isfunction"] or column["isrelated"]:
|
if column["isfunction"] or column["isrelated"]:
|
||||||
printkeys_plus.extend(["isfunction", "isrelated", "stored"])
|
printkeys.extend(["isfunction", "isrelated", "stored"])
|
||||||
extra_message = ", ".join(
|
extra_message = ", ".join(
|
||||||
[
|
[
|
||||||
k + ": " + str(column[k]) if k != str(column[k]) else k
|
k + ": " + str(column[k]) if k != str(column[k]) else k
|
||||||
for k in printkeys_plus
|
for k in printkeys
|
||||||
if column[k]
|
if column[k]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -104,7 +104,6 @@ class UpgradeRecord(models.Model):
|
||||||
"required",
|
"required",
|
||||||
"stored",
|
"stored",
|
||||||
"selection_keys",
|
"selection_keys",
|
||||||
"req_default",
|
|
||||||
"hasdefault",
|
"hasdefault",
|
||||||
"table",
|
"table",
|
||||||
"_inherits",
|
"_inherits",
|
||||||
|
|
|
@ -74,33 +74,56 @@ def compare_registries(cr, module, registry, local_registry):
|
||||||
old_field[key] = value
|
old_field[key] = value
|
||||||
|
|
||||||
|
|
||||||
def isfunction(model, k):
|
def hasdefault(field):
|
||||||
|
"""Return a representation of the field's default method.
|
||||||
|
|
||||||
|
The default method is only meaningful if the field is a regular read/write
|
||||||
|
field with a `default` method or a `compute` method.
|
||||||
|
|
||||||
|
Note that Odoo fields accept a literal value as a `default` attribute
|
||||||
|
this value is wrapped in a lambda expression in odoo/fields.py:
|
||||||
|
https://github.com/odoo/odoo/blob/7eeba9d/odoo/fields.py#L484-L487
|
||||||
|
"""
|
||||||
if (
|
if (
|
||||||
model._fields[k].compute
|
not field.readonly # It's not a proper computed field
|
||||||
and not model._fields[k].related
|
and not field.inverse # It's not a field that delegates their data
|
||||||
and not model._fields[k].company_dependent
|
and not isrelated(field) # It's not an (unstored) related field.
|
||||||
|
):
|
||||||
|
if field.default:
|
||||||
|
return "default"
|
||||||
|
if field.compute:
|
||||||
|
return "compute"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def isfunction(field):
|
||||||
|
if (
|
||||||
|
field.compute
|
||||||
|
and (field.readonly or field.inverse)
|
||||||
|
and not field.related
|
||||||
|
and not field.company_dependent
|
||||||
):
|
):
|
||||||
return "function"
|
return "function"
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def isproperty(model, k):
|
def isproperty(field):
|
||||||
if model._fields[k].company_dependent:
|
if field.company_dependent:
|
||||||
return "property"
|
return "property"
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def isrelated(model, k):
|
def isrelated(field):
|
||||||
if model._fields[k].related:
|
if field.related:
|
||||||
return "related"
|
return "related"
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _get_relation(v):
|
def _get_relation(field):
|
||||||
if v.type in ("many2many", "many2one", "one2many"):
|
if field.type in ("many2many", "many2one", "one2many"):
|
||||||
return v.comodel_name
|
return field.comodel_name
|
||||||
elif v.type == "many2one_reference":
|
elif field.type == "many2one_reference":
|
||||||
return v.model_field
|
return field.model_field
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@ -125,41 +148,31 @@ def log_model(model, local_registry):
|
||||||
if model._inherits:
|
if model._inherits:
|
||||||
model_registry["_inherits"] = {"_inherits": str(model._inherits)}
|
model_registry["_inherits"] = {"_inherits": str(model._inherits)}
|
||||||
model_registry["_order"] = {"_order": model._order}
|
model_registry["_order"] = {"_order": model._order}
|
||||||
for k, v in model._fields.items():
|
for fieldname, field in model._fields.items():
|
||||||
properties = {
|
properties = {
|
||||||
"type": typemap.get(v.type, v.type),
|
"type": typemap.get(field.type, field.type),
|
||||||
"isfunction": isfunction(model, k),
|
"isfunction": isfunction(field),
|
||||||
"isproperty": isproperty(model, k),
|
"isproperty": isproperty(field),
|
||||||
"isrelated": isrelated(model, k),
|
"isrelated": isrelated(field),
|
||||||
"relation": _get_relation(v),
|
"relation": _get_relation(field),
|
||||||
"table": v.relation if v.type == "many2many" else "",
|
"table": field.relation if field.type == "many2many" else "",
|
||||||
"required": v.required and "required" or "",
|
"required": field.required and "required" or "",
|
||||||
"stored": v.store and "stored" or "",
|
"stored": field.store and "stored" or "",
|
||||||
"selection_keys": "",
|
"selection_keys": "",
|
||||||
"req_default": "",
|
"hasdefault": hasdefault(field),
|
||||||
"hasdefault": model._fields[k].default and "hasdefault" or "",
|
|
||||||
}
|
}
|
||||||
if v.type == "selection":
|
if field.type == "selection":
|
||||||
if isinstance(v.selection, (tuple, list)):
|
if isinstance(field.selection, (tuple, list)):
|
||||||
properties["selection_keys"] = str(sorted(x[0] for x in v.selection))
|
properties["selection_keys"] = str(
|
||||||
|
sorted(x[0] for x in field.selection)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
properties["selection_keys"] = "function"
|
properties["selection_keys"] = "function"
|
||||||
elif v.type == "binary":
|
elif field.type == "binary":
|
||||||
properties["attachment"] = str(getattr(v, "attachment", False))
|
properties["attachment"] = str(getattr(field, "attachment", False))
|
||||||
default = model._fields[k].default
|
|
||||||
if v.required and default:
|
|
||||||
if (
|
|
||||||
callable(default)
|
|
||||||
or isinstance(default, str)
|
|
||||||
and getattr(model._fields[k], default, False)
|
|
||||||
and callable(getattr(model._fields[k], default))
|
|
||||||
):
|
|
||||||
properties["req_default"] = "function"
|
|
||||||
else:
|
|
||||||
properties["req_default"] = str(default)
|
|
||||||
for key, value in properties.items():
|
for key, value in properties.items():
|
||||||
if value:
|
if value:
|
||||||
model_registry.setdefault(k, {})[key] = value
|
model_registry.setdefault(fieldname, {})[key] = value
|
||||||
|
|
||||||
|
|
||||||
def log_xml_id(cr, module, xml_id):
|
def log_xml_id(cr, module, xml_id):
|
||||||
|
|
|
@ -42,9 +42,15 @@ class UpgradeInstallWizard(models.TransientModel):
|
||||||
modules = self.env["ir.module.module"].search(domain)
|
modules = self.env["ir.module.module"].search(domain)
|
||||||
|
|
||||||
for start_pattern in BLACKLIST_MODULES_STARTS_WITH:
|
for start_pattern in BLACKLIST_MODULES_STARTS_WITH:
|
||||||
modules = modules.filtered(lambda x: not x.name.startswith(start_pattern))
|
modules = modules.filtered(
|
||||||
|
lambda x, start_pattern=start_pattern: not x.name.startswith(
|
||||||
|
start_pattern
|
||||||
|
)
|
||||||
|
)
|
||||||
for end_pattern in BLACKLIST_MODULES_ENDS_WITH:
|
for end_pattern in BLACKLIST_MODULES_ENDS_WITH:
|
||||||
modules = modules.filtered(lambda x: not x.name.endswith(end_pattern))
|
modules = modules.filtered(
|
||||||
|
lambda x, end_pattern=end_pattern: not x.name.endswith(end_pattern)
|
||||||
|
)
|
||||||
return [("id", "in", modules.ids)]
|
return [("id", "in", modules.ids)]
|
||||||
|
|
||||||
@api.depends("module_ids")
|
@api.depends("module_ids")
|
||||||
|
|
Loading…
Reference in New Issue