def access_repr(access): # type: (odoo.models.IrModelAccess) -> t.Text parts = [] parts.append(color.record_header(access)) parts.append(color.display_name(access.display_name)) parts.append( color.record(access.group_id.name) + util.xml_id_tag(access.group_id) if access.group_id else color.green. bold("Everyone")) parts.append(_crud_format(access)) return "\n".join(parts)
def rule_repr(rule): # type: (odoo.models.IrRule) -> t.Text parts = [] parts.append(color.record_header(rule)) parts.append(color.display_name(rule.display_name)) groups = ", ".join( color.record(group.name) + util.xml_id_tag(group) for group in rule.groups) if not groups: parts.append( color.green.bold("Everyone") if rule["global"] else color.red. bold("No-one")) else: parts.append(groups) parts.append(_crud_format(rule)) if rule.domain_force not in { None, False, "[]", "[(1, '=', 1)]", '[(1, "=", 1)]' }: assert rule.domain_force parts.append( color.highlight(_domain_format(rule.env, rule.domain_force))) parts.extend(sources.format_sources(sources.find_source(rule))) return "\n".join(parts)
def record_repr(obj): # type: (BaseModel) -> t.Text """Display all of a record's fields.""" obj = util.unwrap(obj) if not hasattr(obj, "_ids"): return repr(obj) elif not obj: return u"{}[]".format(obj._name) elif len(obj) > 1: return color.basic_render_record(obj) if obj.env.cr.closed: return color.basic_render_record(obj) + " (closed cursor)" field_names = sorted(field for field in obj._fields if field not in odoo_repl.models.FIELD_BLACKLIST and not obj._fields[field].related) max_len = max(len(f) for f in field_names) if field_names else 0 parts = [] parts.append(color.record_header(obj)) name = obj.sudo().display_name default_name = "{},{}".format(obj._name, obj.id) if name and name != default_name: parts.append(color.display_name(name)) if not obj.exists(): parts.append(color.missing("Missing")) return "\n".join(parts) # Odoo precomputes a field for up to 200 records at a time. # This can be a problem if we're only interested in one of them. # So we do our best to disable it. # For Odoo 8, we do everything in a separate env where the ID cache is # empty. We make a separate env by changing the context. This has the added # advantage of informing models that they're running in odoo_repl, in case # they care. In _color_repr we clear the cache in case it got filled. # For Odoo 10-13, we slice the record. Odoo tries to be smart and narrows # the prefetch cache if we slice while keeping it when iterating. # I don't know what Odoo 9 does but I hope it's one of the above. # TODO: When .print_()ing a recordset we do want prefetching. if isinstance(obj.id, odoo.models.NewId): # Cache gets wonky if we start a new environment no_prefetch_obj = obj else: no_prefetch_obj = obj.with_context(odoo_repl=True)[:] for field in field_names: parts.append("{}: ".format(color.field(field)) + (max_len - len(field)) * " " + _color_repr(no_prefetch_obj, field)) history_lines = _get_create_write_history(obj.sudo()) if history_lines: parts.append("") parts.extend(history_lines) src = sources.find_source(obj) if src: parts.append("") parts.extend(sources.format_sources(src)) return "\n".join(parts)
def model_repr(obj): # type: (t.Union[ModelProxy, BaseModel]) -> t.Text """Summarize a model's fields.""" if isinstance(obj, ModelProxy) and obj._real is None: return repr(obj) obj = util.unwrap(obj) field_names = [] delegated = [] for field in sorted(obj._fields): if field in FIELD_BLACKLIST: continue if getattr(obj._fields[field], "related", False): delegated.append(obj._fields[field]) continue field_names.append(field) max_len = max(len(f) for f in field_names) if field_names else 0 parts = [] original_module = obj._module for parent in type(obj).__bases__: if getattr(parent, "_name", None) == obj._name: original_module = getattr(parent, "_module", original_module) parts.append(color.header(obj._name)) if obj._transient: parts[-1] += " (transient)" if getattr(obj, "_abstract", False): parts[-1] += " (abstract)" elif not obj._auto: parts[-1] += " (no automatic table)" if getattr(obj, "_description", False) and obj._description != obj._name: parts.append(color.display_name(obj._description)) if getattr(obj, "_inherits", False): for model_name, field_name in obj._inherits.items(): parts.append("Inherits from {} through {}".format( color.model(model_name), color.field(field_name))) inherits = _find_inheritance(obj) if inherits: # Giving this a very similar message to the one for _inherits feels dirty # But then _inherit is already very similar to _inherits so maybe it's ok parts.append("Inherits from {}".format(", ".join( color.model(inherit) for inherit in sorted(inherits)))) docs = list( sources.find_docs((util.module(cls), cls) for cls in type(obj).__bases__ if getattr(cls, "_name", obj._name) == obj._name)) parts.extend(sources.format_docs(docs)) src = sources.find_source(obj) by_module = collections.defaultdict(list) for field in field_names: f_obj = obj._fields[field] rep = format_single_field(f_obj, max_len=max_len) f_module = sources.find_field_module(f_obj) or original_module by_module[f_module].append(rep) ordered_modules = [original_module] for src_item in reversed(src): if src_item.module not in ordered_modules: ordered_modules.append(src_item.module) for module in by_module: if module not in ordered_modules: ordered_modules.append(module) for module in ordered_modules: if module not in by_module: continue parts.append("") parts.append("{}:".format(color.module(module))) parts.extend(by_module[module]) if delegated: buckets = collections.defaultdict( list) # type: t.DefaultDict[t.Tuple[t.Text, ...], t.List[t.Text]] for f_obj in delegated: assert f_obj.related buckets[tuple(f_obj.related[:-1])].append( color.field(f_obj.name) if f_obj.related[-1] == f_obj.name else "{} (.{})".format(color.field(f_obj.name), f_obj.related[-1])) parts.append("") for related_field, field_names in buckets.items(): # TODO: figure out name of model of real field parts.append("Delegated to {}: {}".format( color.yellow.bold(".".join(related_field)), ", ".join(field_names))) parts.append("") parts.extend(sources.format_sources(src)) return "\n".join(parts)
def addon_repr(addon): # type: (Addon) -> t.Text # TODO: A lot of the most interesting information is at the top so you have # to scroll up # Ideas: # - Put it at the bottom instead # - Don't show the README by default try: addon.manifest except RuntimeError: return repr(addon) defined_models = (addon._env["ir.model"].browse( addon._env["ir.model.data"].search([ ("model", "=", "ir.model"), ("module", "=", addon._module) ]).mapped("res_id")).mapped("model")) state = addon.record.state if (state == "installed" and addon.record.installed_version != addon.manifest.version): state += " (out of date)" if state == "installed": state = color.green.bold(state.capitalize()) elif not state: state = color.yellow.bold("???") elif state in ("uninstallable", "uninstalled"): state = color.red.bold(state.capitalize()) else: state = color.yellow.bold(state.capitalize()) description = addon.manifest.description if isinstance(addon.manifest.author, Text): author = addon.manifest.author else: author = ", ".join(addon.manifest.author) parts = [] parts.append("{} {} by {}".format(color.module(addon._module), addon.manifest.version, author)) parts.append(util.link_for_record(addon.record)) parts.append(addon.path) parts.append(state) parts.append(color.display_name(addon.manifest.name)) parts.append(addon.manifest.summary) def format_depends(pretext, modules): # type: (t.Text, odoo.models.IrModuleModule) -> None if modules: names = map(_color_state, sorted(modules, key=lambda mod: mod.name)) parts.append("{}: {}".format(pretext, ", ".join(names))) # TODO: Indirect dependencies are a bit noisy, when/how do we show them? direct, _indirect = addon._get_depends() format_depends("Depends", direct) # format_depends("Indirectly depends", indirect) r_direct, _r_indirect = addon._get_rdepends() format_depends("Dependents", r_direct) # format_depends("Indirect dependents", r_indirect) if defined_models: parts.append("Defines: {}".format(", ".join( map(color.model, defined_models)))) if description: parts.append("") # rst2ansi might be better here # (https://pypi.org/project/rst2ansi/) parts.append(color.highlight(description, "rst")) return "\n".join(parts)