def gitsource(thing): # type: (sources.Sourceable) -> None for source in sources.find_source(thing): try: fmt = format_source(source) except GitSourceError as exc: fmt = "{}: {}".format(color.module(source.module), color.missing(str(exc))) print(fmt)
def grep_(self, *args, **kwargs): # type: (object, object) -> None """grep through the combined source code of the model. See help(odoo_repl.grep) for more information. """ assert self._real is not None # TODO: handle multiple classes in single file properly argv = grep.build_grep_argv(args, kwargs) seen = set() # type: t.Set[t.Text] for src in sources.find_source(self._real): if src.fname not in seen: seen.add(src.fname) argv.append(src.fname) subprocess.Popen(argv).wait()
def edit(thing, index=0, bg=None): # type: (sources.Sourceable, t.Union[int, t.Text], t.Optional[bool]) -> None """Open a model or field definition in an editor.""" # TODO: editor kwarg and/or argparse flag src = sources.find_source(thing) if not src: raise RuntimeError("Can't find source file!") if isinstance(index, int): try: module, fname, lnum = src[index] except IndexError: raise RuntimeError("Can't find match #{}".format(index)) elif isinstance(index, Text): for module, fname, lnum in src: if module == index: break else: raise RuntimeError( "Can't find match for module {!r}".format(index)) else: raise TypeError(index) return _edit(fname, lnum=lnum, bg=bg)
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 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 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)