mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 07:52:07 +02:00
19.0 vanilla
This commit is contained in:
parent
0a7ae8db93
commit
991d2234ca
416 changed files with 646602 additions and 300844 deletions
|
|
@ -1,23 +1,4 @@
|
|||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
import odoo
|
||||
|
||||
from .command import Command, main
|
||||
|
||||
from . import cloc
|
||||
from . import upgrade_code
|
||||
from . import deploy
|
||||
from . import scaffold
|
||||
from . import server
|
||||
from . import shell
|
||||
from . import start
|
||||
from . import populate
|
||||
from . import tsconfig
|
||||
from . import neutralize
|
||||
from . import obfuscate
|
||||
from . import genproxytoken
|
||||
from . import db
|
||||
# Import just the command, the rest will get imported as needed
|
||||
from .command import Command, main # noqa: F401
|
||||
|
||||
COMMAND = None
|
||||
|
|
|
|||
|
|
@ -1,47 +1,43 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from odoo.tools import cloc, config
|
||||
from . import Command
|
||||
|
||||
|
||||
class Cloc(Command):
|
||||
""" Count lines of code per modules """
|
||||
|
||||
description = """
|
||||
Odoo cloc is a tool to count the number of relevant lines written
|
||||
in Python, Javascript or XML. This can be used as rough metric for
|
||||
pricing maintenance of customizations.
|
||||
|
||||
It has two modes of operation, either by providing a path:
|
||||
|
||||
odoo-bin cloc -p module_path
|
||||
|
||||
Or by providing the name of a database:
|
||||
|
||||
odoo-bin --addons-path=dirs cloc -d database
|
||||
|
||||
In the latter mode, only the custom code is accounted for.
|
||||
"""
|
||||
|
||||
def run(self, args):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f'{Path(sys.argv[0]).name} {self.name}',
|
||||
description="""\
|
||||
Odoo cloc is a tool to count the number of relevant lines written in
|
||||
Python, Javascript or XML. This can be used as rough metric for pricing
|
||||
maintenance of customizations.
|
||||
|
||||
It has two modes of operation, either by providing a path:
|
||||
|
||||
odoo-bin cloc -p module_path
|
||||
|
||||
Or by providing the name of a database:
|
||||
|
||||
odoo-bin cloc --addons-path=dirs -d database
|
||||
|
||||
In the latter mode, only the custom code is accounted for.
|
||||
""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
parser.add_argument('--database', '-d', dest="database", help="Database name")
|
||||
parser.add_argument('--path', '-p', action='append', help="File or directory path")
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
opt, unknown = parser.parse_known_args(args)
|
||||
self.parser.add_argument('--database', '-d', dest="database", help="Database name")
|
||||
self.parser.add_argument('--path', '-p', action='append', help="File or directory path")
|
||||
self.parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
opt, unknown = self.parser.parse_known_args(args)
|
||||
if not opt.database and not opt.path:
|
||||
parser.print_help()
|
||||
self.parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
c = cloc.Cloc()
|
||||
if opt.database:
|
||||
if ',' in opt.database:
|
||||
sys.exit("-d/--database has multiple databases, please provide a single one")
|
||||
config.parse_config(['-d', opt.database] + unknown)
|
||||
c.count_database(opt.database)
|
||||
c.count_database(config['db_name'][0])
|
||||
if opt.path:
|
||||
for i in opt.path:
|
||||
c.count_path(i)
|
||||
|
|
|
|||
|
|
@ -1,69 +1,139 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
import os
|
||||
import argparse
|
||||
import contextlib
|
||||
import re
|
||||
import sys
|
||||
from inspect import cleandoc
|
||||
from pathlib import Path
|
||||
|
||||
import odoo
|
||||
from odoo.modules import get_modules, get_module_path, initialize_sys_path
|
||||
import odoo.init # import first for core setup
|
||||
import odoo.cli
|
||||
from odoo.modules import initialize_sys_path, load_script
|
||||
from odoo.tools import config
|
||||
|
||||
|
||||
COMMAND_NAME_RE = re.compile(r'^[a-z][a-z0-9_]*$', re.I)
|
||||
PROG_NAME = Path(sys.argv[0]).name
|
||||
commands = {}
|
||||
"""All loaded commands"""
|
||||
|
||||
|
||||
class Command:
|
||||
name = None
|
||||
description = None
|
||||
epilog = None
|
||||
_parser = None
|
||||
|
||||
def __init_subclass__(cls):
|
||||
cls.name = cls.name or cls.__name__.lower()
|
||||
commands[cls.name] = cls
|
||||
module = cls.__module__.rpartition('.')[2]
|
||||
if not cls.is_valid_name(cls.name):
|
||||
raise ValueError(
|
||||
f"Command name {cls.name!r} "
|
||||
f"must match {COMMAND_NAME_RE.pattern!r}")
|
||||
if cls.name != module:
|
||||
raise ValueError(
|
||||
f"Command name {cls.name!r} "
|
||||
f"must match Module name {module!r}")
|
||||
commands[cls.name] = cls
|
||||
|
||||
@property
|
||||
def prog(self):
|
||||
return f"{PROG_NAME} [--addons-path=PATH,...] {self.name}"
|
||||
|
||||
@property
|
||||
def parser(self):
|
||||
if not self._parser:
|
||||
self._parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
prog=self.prog,
|
||||
description=cleandoc(self.description or self.__doc__ or ""),
|
||||
epilog=cleandoc(self.epilog or ""),
|
||||
)
|
||||
return self._parser
|
||||
|
||||
@classmethod
|
||||
def is_valid_name(cls, name):
|
||||
return re.match(COMMAND_NAME_RE, name)
|
||||
|
||||
|
||||
ODOO_HELP = """\
|
||||
Odoo CLI, use '{odoo_bin} --help' for regular server options.
|
||||
def load_internal_commands():
|
||||
""" Load ``commands`` from ``odoo.cli`` """
|
||||
for path in odoo.cli.__path__:
|
||||
for module in Path(path).iterdir():
|
||||
if module.suffix != '.py':
|
||||
continue
|
||||
__import__(f'odoo.cli.{module.stem}')
|
||||
|
||||
Available commands:
|
||||
{command_list}
|
||||
|
||||
Use '{odoo_bin} <command> --help' for individual command help."""
|
||||
def load_addons_commands(command=None):
|
||||
"""
|
||||
Search the addons path for modules with a ``cli/{command}.py`` file.
|
||||
In case no command is provided, discover and load all the commands.
|
||||
"""
|
||||
if command is None:
|
||||
command = '*'
|
||||
elif not Command.is_valid_name(command):
|
||||
return
|
||||
|
||||
mapping = {}
|
||||
initialize_sys_path()
|
||||
for path in odoo.addons.__path__:
|
||||
for fullpath in Path(path).glob(f'*/cli/{command}.py'):
|
||||
if (found_command := fullpath.stem) and Command.is_valid_name(found_command):
|
||||
# loading as odoo.cli and not odoo.addons.{module}.cli
|
||||
# so it doesn't load odoo.addons.{module}.__init__
|
||||
mapping[f'odoo.cli.{found_command}'] = fullpath
|
||||
|
||||
for fq_name, fullpath in mapping.items():
|
||||
with contextlib.suppress(ImportError):
|
||||
load_script(fullpath, fq_name)
|
||||
|
||||
|
||||
def find_command(name: str) -> Command | None:
|
||||
""" Get command by name. """
|
||||
|
||||
# built-in commands
|
||||
if command := commands.get(name):
|
||||
return command
|
||||
|
||||
# import from odoo.cli
|
||||
with contextlib.suppress(ImportError):
|
||||
__import__(f'odoo.cli.{name}')
|
||||
return commands[name]
|
||||
|
||||
# import from odoo.addons.*.cli
|
||||
load_addons_commands(command=name)
|
||||
return commands.get(name)
|
||||
|
||||
class Help(Command):
|
||||
""" Display the list of available commands """
|
||||
def run(self, args):
|
||||
padding = max([len(cmd) for cmd in commands]) + 2
|
||||
command_list = "\n ".join([
|
||||
" {}{}".format(name.ljust(padding), (command.__doc__ or "").strip())
|
||||
for name, command in sorted(commands.items())
|
||||
])
|
||||
print(ODOO_HELP.format( # pylint: disable=bad-builtin
|
||||
odoo_bin=Path(sys.argv[0]).name,
|
||||
command_list=command_list
|
||||
))
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
|
||||
# The only shared option is '--addons-path=' needed to discover additional
|
||||
# commands from modules
|
||||
if len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith("-"):
|
||||
if len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith('-'):
|
||||
# parse only the addons-path, do not setup the logger...
|
||||
odoo.tools.config._parse_config([args[0]])
|
||||
config._parse_config([args[0]])
|
||||
args = args[1:]
|
||||
|
||||
# Default legacy command
|
||||
command = "server"
|
||||
|
||||
# TODO: find a way to properly discover addons subcommands without importing the world
|
||||
# Subcommand discovery
|
||||
if len(args) and not args[0].startswith("-"):
|
||||
logging.disable(logging.CRITICAL)
|
||||
initialize_sys_path()
|
||||
for module in get_modules():
|
||||
if (Path(get_module_path(module)) / 'cli').is_dir():
|
||||
__import__('odoo.addons.' + module)
|
||||
logging.disable(logging.NOTSET)
|
||||
command = args[0]
|
||||
if len(args) and not args[0].startswith('-'):
|
||||
# Command specified, search for it
|
||||
command_name = args[0]
|
||||
args = args[1:]
|
||||
|
||||
if command in commands:
|
||||
o = commands[command]()
|
||||
odoo.cli.COMMAND = command
|
||||
o.run(args)
|
||||
elif '-h' in args or '--help' in args:
|
||||
# No command specified, but help is requested
|
||||
command_name = 'help'
|
||||
args = [x for x in args if x not in ('-h', '--help')]
|
||||
else:
|
||||
sys.exit('Unknown command %r' % (command,))
|
||||
# No command specified, default command used
|
||||
command_name = 'server'
|
||||
|
||||
if command := find_command(command_name):
|
||||
odoo.cli.COMMAND = command_name
|
||||
command().run(args)
|
||||
else:
|
||||
message = (
|
||||
f"Unknown command {command_name!r}.\n"
|
||||
f"Use '{PROG_NAME} --help' to see the list of available commands."
|
||||
)
|
||||
sys.exit(message)
|
||||
|
|
|
|||
|
|
@ -1,35 +1,41 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import argparse
|
||||
import io
|
||||
import urllib.parse
|
||||
import sys
|
||||
import textwrap
|
||||
import urllib.parse
|
||||
import zipfile
|
||||
from argparse import RawTextHelpFormatter
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from ..service.db import (
|
||||
dump_db,
|
||||
exp_create_database,
|
||||
exp_db_exist,
|
||||
exp_drop,
|
||||
exp_duplicate_database,
|
||||
exp_rename,
|
||||
restore_db,
|
||||
)
|
||||
from ..tools import config
|
||||
from . import Command
|
||||
from .server import report_configuration
|
||||
from ..service.db import dump_db, exp_drop, exp_db_exist, exp_duplicate_database, exp_rename, restore_db
|
||||
from ..tools import config
|
||||
|
||||
eprint = partial(print, file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
class Db(Command):
|
||||
""" Create, drop, dump, load databases """
|
||||
name = 'db'
|
||||
description = """
|
||||
Command-line version of the database manager.
|
||||
|
||||
Commands are all filestore-aware.
|
||||
"""
|
||||
|
||||
def run(self, cmdargs):
|
||||
"""Command-line version of the database manager.
|
||||
|
||||
Doesn't provide a `create` command as that's not useful. Commands are
|
||||
all filestore-aware.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f'{Path(sys.argv[0]).name} {self.name}',
|
||||
description=self.__doc__.strip()
|
||||
)
|
||||
parser = self.parser
|
||||
parser.add_argument('-c', '--config')
|
||||
parser.add_argument('-D', '--data-dir')
|
||||
parser.add_argument('--addons-path')
|
||||
|
|
@ -42,6 +48,55 @@ class Db(Command):
|
|||
parser.set_defaults(func=lambda _: exit(parser.format_help()))
|
||||
|
||||
subs = parser.add_subparsers()
|
||||
|
||||
# INIT ----------------------------------
|
||||
|
||||
init = subs.add_parser(
|
||||
"init",
|
||||
help="Create and initialize a database",
|
||||
description="Create an empty database and install the minimum required modules",
|
||||
formatter_class=RawTextHelpFormatter,
|
||||
)
|
||||
init.set_defaults(func=self.init)
|
||||
init.add_argument(
|
||||
'database',
|
||||
help="database to create",
|
||||
)
|
||||
init.add_argument(
|
||||
'--with-demo', action='store_true',
|
||||
help="install demo data in the new database",
|
||||
)
|
||||
init.add_argument(
|
||||
'--force', action='store_true',
|
||||
help="delete database if exists",
|
||||
)
|
||||
init.add_argument(
|
||||
'--language', default='en_US',
|
||||
help="default language for the instance, default 'en_US'",
|
||||
)
|
||||
init.add_argument(
|
||||
'--username', default='admin',
|
||||
help="admin username, default 'admin'",
|
||||
)
|
||||
init.add_argument(
|
||||
'--password', default='admin',
|
||||
help="admin password, default 'admin'",
|
||||
)
|
||||
init.add_argument(
|
||||
'--country',
|
||||
help="country to be set on the main company",
|
||||
)
|
||||
init.epilog = textwrap.dedent("""\
|
||||
|
||||
Database initialization will install the minimum required modules.
|
||||
To install more modules, use the `module install` command.
|
||||
For more info:
|
||||
|
||||
$ odoo-bin module install --help
|
||||
""")
|
||||
|
||||
# LOAD ----------------------------------
|
||||
|
||||
load = subs.add_parser(
|
||||
"load", help="Load a dump file.",
|
||||
description="Loads a dump file into odoo, dump file can be a URL. "
|
||||
|
|
@ -63,11 +118,13 @@ class Db(Command):
|
|||
)
|
||||
load.add_argument('dump_file', help="zip or pg_dump file to load")
|
||||
|
||||
# DUMP ----------------------------------
|
||||
|
||||
dump = subs.add_parser(
|
||||
"dump", help="Create a dump with filestore.",
|
||||
description="Creates a dump file. The dump is always in zip format "
|
||||
"(with filestore), to get a no-filestore format use "
|
||||
"pg_dump directly.")
|
||||
"(with filestore), to get pg_dump format, use "
|
||||
"dump_format argument.")
|
||||
dump.set_defaults(func=self.dump)
|
||||
dump.add_argument('database', help="database to dump")
|
||||
dump.add_argument(
|
||||
|
|
@ -75,6 +132,18 @@ class Db(Command):
|
|||
help="if provided, database is dumped to specified path, otherwise "
|
||||
"or if `-`, dumped to stdout",
|
||||
)
|
||||
dump.add_argument(
|
||||
'--format', dest='dump_format', choices=('zip', 'dump'), default='zip',
|
||||
help="if provided, database is dumped used the specified format, "
|
||||
"otherwise defaults to `zip`.\n"
|
||||
"Supported formats are `zip`, `dump` (pg_dump format) ",
|
||||
)
|
||||
dump.add_argument(
|
||||
'--no-filestore', action='store_const', dest='filestore', default=True, const=False,
|
||||
help="if passed, zip database is dumped without filestore (default: false)"
|
||||
)
|
||||
|
||||
# DUPLICATE -----------------------------
|
||||
|
||||
duplicate = subs.add_parser("duplicate", help="Duplicate a database including filestore.")
|
||||
duplicate.set_defaults(func=self.duplicate)
|
||||
|
|
@ -89,6 +158,8 @@ class Db(Command):
|
|||
duplicate.add_argument("source")
|
||||
duplicate.add_argument("target", help="database to copy `source` to, must not exist unless `-f` is specified in which case it will be dropped first")
|
||||
|
||||
# RENAME --------------------------------
|
||||
|
||||
rename = subs.add_parser("rename", help="Rename a database including filestore.")
|
||||
rename.set_defaults(func=self.rename)
|
||||
rename.add_argument(
|
||||
|
|
@ -98,6 +169,8 @@ class Db(Command):
|
|||
rename.add_argument('source')
|
||||
rename.add_argument("target", help="database to rename `source` to, must not exist unless `-f` is specified, in which case it will be dropped first")
|
||||
|
||||
# DROP ----------------------------------
|
||||
|
||||
drop = subs.add_parser("drop", help="Delete a database including filestore")
|
||||
drop.set_defaults(func=self.drop)
|
||||
drop.add_argument("database", help="database to delete")
|
||||
|
|
@ -123,6 +196,18 @@ class Db(Command):
|
|||
|
||||
args.func(args)
|
||||
|
||||
def init(self, args):
|
||||
self._check_target(args.database, delete_if_exists=args.force)
|
||||
exp_create_database(
|
||||
db_name=args.database,
|
||||
demo=args.with_demo,
|
||||
lang=args.language,
|
||||
login=args.username,
|
||||
user_password=args.password,
|
||||
country_code=args.country,
|
||||
phone=None,
|
||||
)
|
||||
|
||||
def load(self, args):
|
||||
db_name = args.database or Path(args.dump_file).stem
|
||||
self._check_target(db_name, delete_if_exists=args.force)
|
||||
|
|
@ -151,7 +236,7 @@ class Db(Command):
|
|||
dump_db(args.database, sys.stdout.buffer)
|
||||
else:
|
||||
with open(args.dump_path, 'wb') as f:
|
||||
dump_db(args.database, f)
|
||||
dump_db(args.database, f, args.dump_format, args.filestore)
|
||||
|
||||
def duplicate(self, args):
|
||||
self._check_target(args.target, delete_if_exists=args.force)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import argparse
|
||||
import os
|
||||
import requests
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from . import Command
|
||||
|
||||
|
||||
class Deploy(Command):
|
||||
"""Deploy a module on an Odoo instance"""
|
||||
|
||||
def __init__(self):
|
||||
super(Deploy, self).__init__()
|
||||
super().__init__()
|
||||
self.session = requests.session()
|
||||
|
||||
def deploy_module(self, module_path, url, login, password, db='', force=False):
|
||||
|
|
@ -51,7 +50,7 @@ class Deploy(Command):
|
|||
try:
|
||||
print("Zipping module directory...")
|
||||
with zipfile.ZipFile(temp, 'w') as zfile:
|
||||
for root, dirs, files in os.walk(path):
|
||||
for root, _dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
zfile.write(file_path, file_path.split(container).pop())
|
||||
|
|
@ -61,10 +60,7 @@ class Deploy(Command):
|
|||
raise
|
||||
|
||||
def run(self, cmdargs):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f'{Path(sys.argv[0]).name} {self.name}',
|
||||
description=self.__doc__
|
||||
)
|
||||
parser = self.parser
|
||||
parser.add_argument('path', help="Path of the module to deploy")
|
||||
parser.add_argument('url', nargs='?', help='Url of the server (default=http://localhost:8069)', default="http://localhost:8069")
|
||||
parser.add_argument('--db', dest='db', help='Database to use if server does not use db-filter.')
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import argparse
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from passlib.hash import pbkdf2_sha512
|
||||
|
||||
from . import Command
|
||||
from odoo.tools import config
|
||||
|
||||
|
||||
class GenProxyToken(Command):
|
||||
""" Generate and (re)set proxy access token in config file """
|
||||
|
||||
def generate_token(self, length=16):
|
||||
token = secrets.token_hex(int(length / 2))
|
||||
split_size = int(length / 4)
|
||||
return '-'.join(textwrap.wrap(token, split_size))
|
||||
|
||||
def run(self, cmdargs):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f'{Path(sys.argv[0]).name} {self.name}',
|
||||
description=self.__doc__.strip()
|
||||
)
|
||||
parser.add_argument('-c', '--config', type=str, help="Specify an alternate config file")
|
||||
parser.add_argument('--token-length', type=int, help="Token Length", default=16)
|
||||
args, _ = parser.parse_known_args()
|
||||
if args.config:
|
||||
config.rcfile = args.config
|
||||
token = self.generate_token(length=args.token_length)
|
||||
config['proxy_access_token'] = pbkdf2_sha512.hash(token)
|
||||
config.save()
|
||||
sys.stdout.write(f'{token}\n')
|
||||
40
odoo-bringout-oca-ocb-base/odoo/cli/help.py
Normal file
40
odoo-bringout-oca-ocb-base/odoo/cli/help.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import textwrap
|
||||
|
||||
import odoo.addons
|
||||
import odoo.modules
|
||||
import odoo.release
|
||||
|
||||
from .command import PROG_NAME, Command, commands, load_addons_commands, load_internal_commands
|
||||
|
||||
|
||||
class Help(Command):
|
||||
""" Display the list of available commands """
|
||||
|
||||
template = textwrap.dedent("""\
|
||||
usage: {prog_name} [--addons-path=PATH,...] <command> [...]
|
||||
|
||||
Odoo {version}
|
||||
Available commands:
|
||||
|
||||
{command_list}
|
||||
|
||||
Use '{prog_name} server --help' for regular server options.
|
||||
Use '{prog_name} <command> --help' for other individual commands options.
|
||||
""")
|
||||
|
||||
def run(self, args):
|
||||
load_internal_commands()
|
||||
load_addons_commands()
|
||||
|
||||
padding = max(len(cmd_name) for cmd_name in commands) + 2
|
||||
name_desc = [
|
||||
(cmd_name, (cmd.__doc__ or "").strip())
|
||||
for cmd_name, cmd in sorted(commands.items())
|
||||
]
|
||||
command_list = "\n".join(f" {name:<{padding}}{desc}" for name, desc in name_desc)
|
||||
|
||||
print(Help.template.format( # noqa: T201
|
||||
prog_name=PROG_NAME,
|
||||
version=odoo.release.version,
|
||||
command_list=command_list,
|
||||
))
|
||||
240
odoo-bringout-oca-ocb-base/odoo/cli/i18n.py
Normal file
240
odoo-bringout-oca-ocb-base/odoo/cli/i18n.py
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
from odoo import SUPERUSER_ID
|
||||
from odoo.api import Environment
|
||||
from odoo.cli.command import Command
|
||||
from odoo.modules import get_module_path
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.tools import OrderedSet, config
|
||||
from odoo.tools.translate import TranslationImporter, load_language, trans_export
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
EXPORT_EXTENSIONS = ['.po', '.pot', '.tgz', '.csv']
|
||||
IMPORT_EXTENSIONS = ['.po', '.csv']
|
||||
|
||||
|
||||
class SubcommandHelpFormatter(argparse.RawTextHelpFormatter):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs, max_help_position=80)
|
||||
|
||||
|
||||
class I18n(Command):
|
||||
""" Import, export, setup languages and internationalization files """
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
subparsers = self.parser.add_subparsers(
|
||||
dest='subcommand', required=True,
|
||||
help='Subcommands help')
|
||||
|
||||
self.import_parser = subparsers.add_parser(
|
||||
'import',
|
||||
help="Import i18n files",
|
||||
description="Imports provided translation files",
|
||||
formatter_class=SubcommandHelpFormatter,
|
||||
)
|
||||
self.export_parser = subparsers.add_parser(
|
||||
'export',
|
||||
help="Export i18n files",
|
||||
description="Exports language files into the i18n folder of each module",
|
||||
formatter_class=SubcommandHelpFormatter,
|
||||
)
|
||||
self.loadlang_parser = subparsers.add_parser(
|
||||
'loadlang',
|
||||
help="Load languages",
|
||||
description="Loads languages",
|
||||
formatter_class=SubcommandHelpFormatter,
|
||||
)
|
||||
|
||||
for parser in (self.import_parser, self.export_parser, self.loadlang_parser):
|
||||
parser.add_argument(
|
||||
'-c', '--config', dest='config',
|
||||
help="use a specific configuration file")
|
||||
parser.add_argument(
|
||||
'-d', '--database', dest='db_name', default=None,
|
||||
help="database name, connection details will be taken from the config file")
|
||||
parser.epilog = textwrap.dedent("""\
|
||||
Language codes must follow the XPG (POSIX) locale format.
|
||||
see: https://www.gnu.org/software/libc/manual/html_node/Locale-Names.html
|
||||
|
||||
To list available codes, you can search them querying the database:
|
||||
$ psql -d <dbname> -c "SELECT iso_code FROM res_lang ORDER BY iso_code"
|
||||
|
||||
Examples:
|
||||
odoo-bin i18n loadlang -l en # English (U.S.)
|
||||
odoo-bin i18n loadlang -l es es_AR # Spanish (Spain, Argentina)
|
||||
odoo-bin i18n loadlang -l sr@latin # Serbian (Latin)
|
||||
""")
|
||||
|
||||
self.import_parser.add_argument(
|
||||
'files', nargs='+', metavar='FILE', type=Path,
|
||||
help=f"files to be imported. Allowed extensions: {', '.join(IMPORT_EXTENSIONS)}\n")
|
||||
self.import_parser.add_argument(
|
||||
'-w', '--overwrite', action='store_true',
|
||||
help="overwrite existing terms")
|
||||
self.import_parser.add_argument(
|
||||
'-l', '--language', dest='language', metavar='LANG', required=True,
|
||||
help="language code")
|
||||
|
||||
self.export_parser.add_argument(
|
||||
'-l', '--languages', dest='languages', nargs='+', default=['pot'], metavar='LANG',
|
||||
help="list of language codes, 'pot' for template (default)")
|
||||
self.export_parser.add_argument(
|
||||
'modules', nargs='+', metavar='MODULE',
|
||||
help="modules to be exported")
|
||||
self.export_parser.add_argument(
|
||||
'-o', '--output', metavar="FILE", dest='output',
|
||||
help=(
|
||||
"output only one file with translations from all provided modules\n"
|
||||
f"allowed extensions: {', '.join(EXPORT_EXTENSIONS)},"
|
||||
" '-' writes a '.po' file to stdout\n"
|
||||
"only one language is allowed when this option is active"
|
||||
),
|
||||
)
|
||||
|
||||
self.loadlang_parser.add_argument(
|
||||
'-l', '--languages', dest='languages', nargs='+', metavar='LANG',
|
||||
help="List of language codes to install")
|
||||
|
||||
def run(self, cmdargs):
|
||||
parsed_args = self.parser.parse_args(args=cmdargs)
|
||||
|
||||
config_args = []
|
||||
if parsed_args.config:
|
||||
config_args += ['-c', parsed_args.config]
|
||||
if parsed_args.db_name:
|
||||
config_args += ['-d', parsed_args.db_name]
|
||||
|
||||
config.parse_config(config_args, setup_logging=True)
|
||||
|
||||
db_names = config['db_name']
|
||||
if not db_names or len(db_names) > 1:
|
||||
self.parser.error("Please provide a single database in the config file")
|
||||
parsed_args.db_name = db_names[0]
|
||||
|
||||
match parsed_args.subcommand:
|
||||
case 'import':
|
||||
self._import(parsed_args)
|
||||
case 'export':
|
||||
self._export(parsed_args)
|
||||
case 'loadlang':
|
||||
self._loadlang(parsed_args)
|
||||
|
||||
def _get_languages(self, env, language_codes, active_test=True):
|
||||
# We want to log invalid parameters
|
||||
Lang = env['res.lang'].with_context(active_test=False)
|
||||
languages = Lang.search([('iso_code', 'in', language_codes)])
|
||||
if not_found_language_codes := set(language_codes) - set(languages.mapped("iso_code")):
|
||||
_logger.warning("Ignoring not found languages: %s", ', '.join(not_found_language_codes))
|
||||
if active_test:
|
||||
if not_installed_languages := languages.filtered(lambda x: not x.active):
|
||||
languages -= not_installed_languages
|
||||
iso_code_str = ", ".join(not_installed_languages.mapped("iso_code"))
|
||||
_logger.warning("Ignoring not installed languages: %s", iso_code_str)
|
||||
return languages
|
||||
|
||||
def _import(self, parsed_args):
|
||||
paths = OrderedSet(parsed_args.files)
|
||||
if invalid_paths := [path for path in paths if (
|
||||
not path.exists()
|
||||
or path.suffix not in IMPORT_EXTENSIONS
|
||||
)]:
|
||||
_logger.warning("Ignoring invalid paths: %s",
|
||||
', '.join(str(path) for path in invalid_paths))
|
||||
paths -= set(invalid_paths)
|
||||
if not paths:
|
||||
self.import_parser.error("No valid path was provided")
|
||||
|
||||
with Registry(parsed_args.db_name).cursor() as cr:
|
||||
env = Environment(cr, SUPERUSER_ID, {})
|
||||
translation_importer = TranslationImporter(cr)
|
||||
language = self._get_languages(env, [parsed_args.language])
|
||||
if not language:
|
||||
self.import_parser.error("No valid language has been provided")
|
||||
for path in paths:
|
||||
with path.open("rb") as infile:
|
||||
translation_importer.load(infile, path.suffix.removeprefix('.'), language.code)
|
||||
translation_importer.save(overwrite=parsed_args.overwrite)
|
||||
|
||||
def _export(self, parsed_args):
|
||||
export_pot = 'pot' in parsed_args.languages
|
||||
|
||||
if parsed_args.output:
|
||||
if len(parsed_args.languages) != 1:
|
||||
self.export_parser.error(
|
||||
"When --output is specified, one single --language must be supplied")
|
||||
if parsed_args.output != '-':
|
||||
parsed_args.output = Path(parsed_args.output)
|
||||
if parsed_args.output.suffix not in EXPORT_EXTENSIONS:
|
||||
self.export_parser.error(
|
||||
f"Extensions allowed for --output are {', '.join(EXPORT_EXTENSIONS)}")
|
||||
if export_pot and parsed_args.output.suffix == '.csv':
|
||||
self.export_parser.error(
|
||||
"Cannot export template in .csv format, please specify a language.")
|
||||
|
||||
if export_pot:
|
||||
parsed_args.languages.remove('pot')
|
||||
|
||||
with Registry(parsed_args.db_name).cursor(readonly=True) as cr:
|
||||
env = Environment(cr, SUPERUSER_ID, {})
|
||||
|
||||
# We want to log invalid parameters
|
||||
modules = env['ir.module.module'].search_fetch(
|
||||
[('name', 'in', parsed_args.modules)], ['name', 'state'])
|
||||
if not_found_module_names := set(parsed_args.modules) - set(modules.mapped("name")):
|
||||
_logger.warning("Ignoring not found modules: %s",
|
||||
", ".join(not_found_module_names))
|
||||
if not_installed_modules := modules.filtered(lambda x: x.state != 'installed'):
|
||||
_logger.warning("Ignoring not installed modules: %s",
|
||||
", ".join(not_installed_modules.mapped("name")))
|
||||
modules -= not_installed_modules
|
||||
if len(modules) < 1:
|
||||
self.export_parser.error("No valid module has been provided")
|
||||
module_names = modules.mapped("name")
|
||||
|
||||
languages = self._get_languages(env, parsed_args.languages)
|
||||
languages_count = len(languages) + export_pot
|
||||
if languages_count == 0:
|
||||
self.export_parser.error("No valid language has been provided")
|
||||
|
||||
if parsed_args.output:
|
||||
self._export_file(env, module_names, languages.code, parsed_args.output)
|
||||
else:
|
||||
# Po(t) files in the modules' i18n folders
|
||||
for module_name in module_names:
|
||||
i18n_path = Path(get_module_path(module_name), 'i18n')
|
||||
if export_pot:
|
||||
path = i18n_path / f'{module_name}.pot'
|
||||
self._export_file(env, [module_name], None, path)
|
||||
for language in languages:
|
||||
path = i18n_path / f'{language.iso_code}.po'
|
||||
self._export_file(env, [module_name], language.code, path)
|
||||
|
||||
def _export_file(self, env, module_names, lang_code, path):
|
||||
source = module_names[0] if len(module_names) == 1 else 'modules'
|
||||
destination = 'stdout' if path == '-' else path
|
||||
_logger.info("Exporting %s (%s) to %s", source, lang_code or 'pot', destination)
|
||||
|
||||
if destination == 'stdout':
|
||||
if not trans_export(lang_code, module_names, sys.stdout.buffer, 'po', env):
|
||||
_logger.warning("No translatable terms were found in %s.", module_names)
|
||||
return
|
||||
|
||||
path.parent.mkdir(exist_ok=True)
|
||||
export_format = path.suffix.removeprefix('.')
|
||||
if export_format == 'pot':
|
||||
export_format = 'po'
|
||||
with path.open('wb') as outfile:
|
||||
if not trans_export(lang_code, module_names, outfile, export_format, env):
|
||||
_logger.warning("No translatable terms were found in %s.", module_names)
|
||||
|
||||
def _loadlang(self, parsed_args):
|
||||
with Registry(parsed_args.db_name).cursor() as cr:
|
||||
env = Environment(cr, SUPERUSER_ID, {})
|
||||
for language in self._get_languages(env, parsed_args.languages, active_test=False):
|
||||
load_language(env.cr, language.code)
|
||||
175
odoo-bringout-oca-ocb-base/odoo/cli/module.py
Normal file
175
odoo-bringout-oca-ocb-base/odoo/cli/module.py
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import argparse
|
||||
import logging
|
||||
import textwrap
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from odoo import SUPERUSER_ID
|
||||
from odoo.api import Environment
|
||||
from odoo.cli.command import Command
|
||||
from odoo.modules.loading import force_demo
|
||||
from odoo.modules.module import get_module_path, initialize_sys_path
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.tools import OrderedSet, config, parse_version
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Module(Command):
|
||||
""" Manage modules, install demo data """
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
subparsers = self.parser.add_subparsers(
|
||||
dest='subcommand', required=True,
|
||||
help='Subcommands help')
|
||||
|
||||
install_parser = subparsers.add_parser(
|
||||
'install',
|
||||
help="Install modules",
|
||||
description="Install selected modules",
|
||||
)
|
||||
install_parser.set_defaults(func=self._install)
|
||||
upgrade_parser = subparsers.add_parser(
|
||||
'upgrade',
|
||||
help="Upgrade modules",
|
||||
description="Upgrade selected modules",
|
||||
)
|
||||
upgrade_parser.set_defaults(func=self._upgrade)
|
||||
uninstall_parser = subparsers.add_parser(
|
||||
'uninstall',
|
||||
help="Uninstall modules",
|
||||
description="Uninstall selected modules",
|
||||
)
|
||||
uninstall_parser.set_defaults(func=self._uninstall)
|
||||
force_demo_parser = subparsers.add_parser(
|
||||
'force-demo',
|
||||
help="Install demo data (force)",
|
||||
description="Install demonstration data (force)",
|
||||
)
|
||||
force_demo_parser.set_defaults(func=self._force_demo)
|
||||
|
||||
for parser in (
|
||||
install_parser,
|
||||
uninstall_parser,
|
||||
upgrade_parser,
|
||||
force_demo_parser,
|
||||
):
|
||||
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
||||
parser.add_argument(
|
||||
'-c', '--config', dest='config',
|
||||
help="use a specific configuration file")
|
||||
parser.add_argument(
|
||||
'-d', '--database', dest='db_name', default=None,
|
||||
help="database name, connection details will be taken from the config file")
|
||||
|
||||
install_parser.add_argument(
|
||||
'modules', nargs='+', metavar='MODULE',
|
||||
help="names of the modules to be installed. For data modules (.zip), use the path instead")
|
||||
install_parser.epilog = textwrap.dedent("""\
|
||||
Before installing modules, an Odoo database needs to be created and initialized
|
||||
on your PostgreSQL instance, using the `db init` command:
|
||||
|
||||
$ odoo-bin db init <db_name>
|
||||
|
||||
To get help on its parameters, see:
|
||||
|
||||
$ odoo-bin db init --help
|
||||
""")
|
||||
uninstall_parser.add_argument(
|
||||
'modules', nargs='+', metavar='MODULE',
|
||||
help="names of the modules to be uninstalled")
|
||||
upgrade_parser.add_argument(
|
||||
'modules', nargs='+', metavar='MODULE',
|
||||
help="name of the modules to be upgraded, use 'base' if you want to upgrade everything")
|
||||
upgrade_parser.add_argument(
|
||||
'--outdated', action='store_true',
|
||||
help="only update modules that have a newer version on disk",
|
||||
)
|
||||
|
||||
def run(self, cmdargs):
|
||||
parsed_args = self.parser.parse_args(args=cmdargs)
|
||||
config_args = []
|
||||
if parsed_args.config:
|
||||
config_args += ['-c', parsed_args.config]
|
||||
if parsed_args.db_name:
|
||||
config_args += ['-d', parsed_args.db_name]
|
||||
config.parse_config(config_args, setup_logging=True)
|
||||
|
||||
db_names = config['db_name']
|
||||
if not db_names or len(db_names) > 1:
|
||||
self.parser.error("Please provide a single database in the config file")
|
||||
parsed_args.db_name = db_names[0]
|
||||
|
||||
parsed_args.func(parsed_args)
|
||||
|
||||
def _get_zip_path(self, path):
|
||||
fullpath = Path(path).resolve()
|
||||
if fullpath.is_file() and fullpath.suffix.lower() == '.zip':
|
||||
return fullpath
|
||||
return None
|
||||
|
||||
def _get_module_names(self, module_names):
|
||||
""" Get valid module names from disk before starting the Db environment """
|
||||
initialize_sys_path()
|
||||
return {
|
||||
module
|
||||
for module in set(module_names)
|
||||
if get_module_path(module)
|
||||
or self._get_zip_path(module)
|
||||
}
|
||||
|
||||
def _get_modules(self, env, module_names):
|
||||
Module = env['ir.module.module']
|
||||
Module.update_list()
|
||||
return Module.search([('name', 'in', module_names)])
|
||||
|
||||
@contextmanager
|
||||
def _create_env_context(self, db_name):
|
||||
with Registry.new(db_name).cursor() as cr:
|
||||
yield Environment(cr, SUPERUSER_ID, {})
|
||||
|
||||
def _install(self, parsed_args):
|
||||
with self._create_env_context(parsed_args.db_name) as env:
|
||||
|
||||
valid_module_names = self._get_module_names(parsed_args.modules)
|
||||
installable_modules = self._get_modules(env, valid_module_names)
|
||||
if installable_modules:
|
||||
installable_modules.button_immediate_install()
|
||||
|
||||
non_installable_modules = OrderedSet(
|
||||
module
|
||||
for module in parsed_args.modules
|
||||
if module not in set(installable_modules.mapped("name"))
|
||||
)
|
||||
importable_zipfiles = [
|
||||
fullpath
|
||||
for module in non_installable_modules
|
||||
if (fullpath := self._get_zip_path(module))
|
||||
]
|
||||
if importable_zipfiles:
|
||||
if 'imported' not in env['ir.module.module']._fields:
|
||||
_logger.warning("Cannot import data modules unless the `base_import_module` module is installed")
|
||||
else:
|
||||
for importable_zipfile in importable_zipfiles:
|
||||
env['ir.module.module']._import_zipfile(importable_zipfile)
|
||||
|
||||
def _upgrade(self, parsed_args):
|
||||
with self._create_env_context(parsed_args.db_name) as env:
|
||||
valid_module_names = self._get_module_names(parsed_args.modules)
|
||||
upgradable_modules = self._get_modules(env, valid_module_names)
|
||||
if parsed_args.outdated:
|
||||
upgradable_modules = upgradable_modules.filtered(
|
||||
lambda x: parse_version(x.installed_version) > parse_version(x.latest_version),
|
||||
)
|
||||
if upgradable_modules:
|
||||
upgradable_modules.button_immediate_upgrade()
|
||||
|
||||
def _uninstall(self, parsed_args):
|
||||
with self._create_env_context(parsed_args.db_name) as env:
|
||||
if modules := self._get_modules(env, parsed_args.modules):
|
||||
modules.button_immediate_uninstall()
|
||||
|
||||
def _force_demo(self, parsed_args):
|
||||
with self._create_env_context(parsed_args.db_name) as env:
|
||||
force_demo(env)
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
import optparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import odoo
|
||||
import odoo.modules.neutralize
|
||||
import odoo.sql_db
|
||||
import odoo.tools.config
|
||||
|
||||
from . import Command
|
||||
|
||||
|
|
@ -16,17 +16,20 @@ class Neutralize(Command):
|
|||
|
||||
def run(self, args):
|
||||
parser = odoo.tools.config.parser
|
||||
parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
||||
parser.prog = self.prog
|
||||
group = optparse.OptionGroup(parser, "Neutralize", "Neutralize the database specified by the `-d` argument.")
|
||||
group.add_option("--stdout", action="store_true", dest="to_stdout",
|
||||
help="Output the neutralization SQL instead of applying it")
|
||||
parser.add_option_group(group)
|
||||
opt = odoo.tools.config.parse_config(args, setup_logging=True)
|
||||
|
||||
dbname = odoo.tools.config['db_name']
|
||||
if not dbname:
|
||||
dbnames = odoo.tools.config['db_name']
|
||||
if not dbnames:
|
||||
_logger.error('Neutralize command needs a database name. Use "-d" argument')
|
||||
sys.exit(1)
|
||||
if len(dbnames) > 1:
|
||||
sys.exit("-d/--database/db_name has multiple database, please provide a single one")
|
||||
dbname = dbnames[0]
|
||||
|
||||
if not opt.to_stdout:
|
||||
_logger.info("Starting %s database neutralization", dbname)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import odoo
|
||||
import sys
|
||||
import optparse
|
||||
import logging
|
||||
|
|
@ -8,7 +6,7 @@ from collections import defaultdict
|
|||
|
||||
from . import Command
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.tools import SQL
|
||||
from odoo.tools import SQL, config
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -133,7 +131,8 @@ class Obfuscate(Command):
|
|||
return True
|
||||
|
||||
def run(self, cmdargs):
|
||||
parser = odoo.tools.config.parser
|
||||
parser = config.parser
|
||||
parser.prog = self.prog
|
||||
group = optparse.OptionGroup(parser, "Obfuscate Configuration")
|
||||
group.add_option('--pwd', dest="pwd", default=False, help="Cypher password")
|
||||
group.add_option('--fields', dest="fields", default=False, help="List of table.columns to obfuscate/unobfuscate: table1.column1,table2.column1,table2.column2")
|
||||
|
|
@ -152,14 +151,14 @@ class Obfuscate(Command):
|
|||
sys.exit(parser.print_help())
|
||||
|
||||
try:
|
||||
opt = odoo.tools.config.parse_config(cmdargs, setup_logging=True)
|
||||
opt = config.parse_config(cmdargs, setup_logging=True)
|
||||
if not opt.pwd:
|
||||
_logger.error("--pwd is required")
|
||||
sys.exit("ERROR: --pwd is required")
|
||||
if opt.allfields and not opt.unobfuscate:
|
||||
_logger.error("--allfields can only be used in unobfuscate mode")
|
||||
sys.exit("ERROR: --allfields can only be used in unobfuscate mode")
|
||||
self.dbname = odoo.tools.config['db_name']
|
||||
self.dbname = config['db_name']
|
||||
self.registry = Registry(self.dbname)
|
||||
with self.registry.cursor() as cr:
|
||||
self.cr = cr
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
import optparse
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from odoo import api
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.tools import config
|
||||
from odoo.tools.populate import populate_models
|
||||
|
||||
from . import Command
|
||||
import odoo
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.tools.populate import populate_models
|
||||
from odoo.api import Environment
|
||||
|
||||
DEFAULT_FACTOR = '10000'
|
||||
DEFAULT_SEPARATOR = '_'
|
||||
|
|
@ -22,8 +21,8 @@ class Populate(Command):
|
|||
"""Populate database via duplication of existing data for testing/demo purposes"""
|
||||
|
||||
def run(self, cmdargs):
|
||||
parser = odoo.tools.config.parser
|
||||
parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
||||
parser = config.parser
|
||||
parser.prog = self.prog
|
||||
group = optparse.OptionGroup(parser, "Populate Configuration")
|
||||
group.add_option("--factors", dest="factors",
|
||||
help="Comma separated list of factors for each model, or just a single factor."
|
||||
|
|
@ -39,7 +38,7 @@ class Populate(Command):
|
|||
help="Single character separator for char/text fields.",
|
||||
default=DEFAULT_SEPARATOR)
|
||||
parser.add_option_group(group)
|
||||
opt = odoo.tools.config.parse_config(cmdargs, setup_logging=True)
|
||||
opt = config.parse_config(cmdargs, setup_logging=True)
|
||||
|
||||
# deduplicate models if necessary, and keep the last corresponding
|
||||
# factor for each model
|
||||
|
|
@ -53,14 +52,16 @@ class Populate(Command):
|
|||
except TypeError:
|
||||
raise ValueError("Separator must be a single Unicode character.")
|
||||
|
||||
dbname = odoo.tools.config['db_name']
|
||||
registry = Registry(dbname)
|
||||
dbnames = config['db_name']
|
||||
if len(dbnames) > 1:
|
||||
sys.exit("-d/--database/db_name has multiple database, please provide a single one")
|
||||
registry = Registry(dbnames[0])
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {'active_test': False})
|
||||
env = api.Environment(cr, api.SUPERUSER_ID, {'active_test': False})
|
||||
self.populate(env, model_factors, separator_code)
|
||||
|
||||
@classmethod
|
||||
def populate(cls, env: Environment, modelname_factors: dict[str, int], separator_code: int):
|
||||
def populate(cls, env: api.Environment, modelname_factors: dict[str, int], separator_code: int):
|
||||
model_factors = {
|
||||
model: factor
|
||||
for model_name, factor in modelname_factors.items()
|
||||
|
|
|
|||
|
|
@ -1,24 +1,25 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import jinja2
|
||||
|
||||
from . import Command
|
||||
|
||||
|
||||
class Scaffold(Command):
|
||||
""" Generates an Odoo module skeleton. """
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.epilog = "Built-in templates available are: %s" % ', '.join(
|
||||
d for d in os.listdir(builtins())
|
||||
if d != 'base'
|
||||
)
|
||||
|
||||
def run(self, cmdargs):
|
||||
# TODO: bash completion file
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f'{Path(sys.argv[0]).name} {self.name}',
|
||||
description=self.__doc__,
|
||||
epilog=self.epilog(),
|
||||
)
|
||||
parser = self.parser
|
||||
parser.add_argument(
|
||||
'-t', '--template', type=template, default=template('default'),
|
||||
help="Use a custom module template, can be a template name or the"
|
||||
|
|
@ -47,11 +48,6 @@ class Scaffold(Command):
|
|||
params=params,
|
||||
)
|
||||
|
||||
def epilog(self):
|
||||
return "Built-in templates available are: %s" % ', '.join(
|
||||
d for d in os.listdir(builtins())
|
||||
if d != 'base'
|
||||
)
|
||||
|
||||
builtins = lambda *args: os.path.join(
|
||||
os.path.abspath(os.path.dirname(__file__)),
|
||||
|
|
@ -82,7 +78,7 @@ def directory(p, create=False):
|
|||
if create and not os.path.exists(expanded):
|
||||
os.makedirs(expanded)
|
||||
if not os.path.isdir(expanded):
|
||||
die("%s is not a directory" % p)
|
||||
sys.exit("%s is not a directory" % p)
|
||||
return expanded
|
||||
|
||||
env = jinja2.Environment()
|
||||
|
|
@ -100,7 +96,7 @@ class template(object):
|
|||
self.path = identifier
|
||||
if os.path.isdir(self.path):
|
||||
return
|
||||
die("{} is not a valid module template".format(identifier))
|
||||
sys.exit(f"{identifier} is not a valid module template")
|
||||
|
||||
def __str__(self):
|
||||
return self.id
|
||||
|
|
@ -141,10 +137,6 @@ class template(object):
|
|||
.dump(f, encoding='utf-8')
|
||||
f.write(b'\n')
|
||||
|
||||
def die(message, code=1):
|
||||
print(message, file=sys.stderr)
|
||||
sys.exit(code)
|
||||
|
||||
def warn(message):
|
||||
# ASK: shall we use logger ?
|
||||
print("WARNING:", message)
|
||||
|
|
|
|||
|
|
@ -11,40 +11,34 @@ GNU Public Licence.
|
|||
"""
|
||||
|
||||
import atexit
|
||||
import csv # pylint: disable=deprecated-module
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from psycopg2.errors import InsufficientPrivilege
|
||||
|
||||
import odoo
|
||||
from odoo.release import author as __author__ # noqa: F401
|
||||
from odoo.release import version as __version__ # noqa: F401
|
||||
from odoo.service import server
|
||||
from odoo.tools import config
|
||||
|
||||
from . import Command
|
||||
|
||||
__author__ = odoo.release.author
|
||||
__version__ = odoo.release.version
|
||||
|
||||
# Also use the `odoo` logger for the main script.
|
||||
_logger = logging.getLogger('odoo')
|
||||
|
||||
re._MAXCACHE = 4096 # default is 512, a little too small for odoo
|
||||
|
||||
def check_root_user():
|
||||
"""Warn if the process's user is 'root' (on POSIX system)."""
|
||||
if os.name == 'posix':
|
||||
import getpass
|
||||
if getpass.getuser() == 'root':
|
||||
sys.stderr.write("Running as user 'root' is a security risk.\n")
|
||||
if os.name == 'posix' and os.getuid() == 0:
|
||||
sys.stderr.write("Running as user 'root' is a security risk.\n")
|
||||
|
||||
|
||||
def check_postgres_user():
|
||||
""" Exit if the configured database user is 'postgres'.
|
||||
|
||||
This function assumes the configuration has been initialized.
|
||||
"""
|
||||
config = odoo.tools.config
|
||||
if (config['db_user'] or os.environ.get('PGUSER')) == 'postgres':
|
||||
sys.stderr.write("Using the database user 'postgres' is a security risk, aborting.")
|
||||
sys.exit(1)
|
||||
|
|
@ -54,10 +48,11 @@ def report_configuration():
|
|||
|
||||
This function assumes the configuration has been initialized.
|
||||
"""
|
||||
config = odoo.tools.config
|
||||
_logger.info("Odoo version %s", __version__)
|
||||
if os.path.isfile(config.rcfile):
|
||||
_logger.info("Using configuration file at " + config.rcfile)
|
||||
import odoo.addons # noqa: PLC0415
|
||||
import odoo.release # noqa: PLC0415
|
||||
_logger.info("Odoo version %s", odoo.release.version)
|
||||
if os.path.isfile(config['config']):
|
||||
_logger.info("Using configuration file at %s", config['config'])
|
||||
_logger.info('addons paths: %s', odoo.addons.__path__)
|
||||
if config.get('upgrade_path'):
|
||||
_logger.info('upgrade path: %s', config['upgrade_path'])
|
||||
|
|
@ -69,16 +64,15 @@ def report_configuration():
|
|||
_logger.info('database: %s@%s:%s', user, host, port)
|
||||
replica_host = config['db_replica_host']
|
||||
replica_port = config['db_replica_port']
|
||||
if replica_host is not False or replica_port:
|
||||
if replica_host or replica_port or 'replica' in config['dev_mode']:
|
||||
_logger.info('replica database: %s@%s:%s', user, replica_host or 'default', replica_port or 'default')
|
||||
if sys.version_info[:2] > odoo.MAX_PY_VERSION:
|
||||
if sys.version_info[:2] > odoo.release.MAX_PY_VERSION:
|
||||
_logger.warning("Python %s is not officially supported, please use Python %s instead",
|
||||
'.'.join(map(str, sys.version_info[:2])),
|
||||
'.'.join(map(str, odoo.MAX_PY_VERSION))
|
||||
'.'.join(map(str, odoo.release.MAX_PY_VERSION))
|
||||
)
|
||||
|
||||
def rm_pid_file(main_pid):
|
||||
config = odoo.tools.config
|
||||
if config['pidfile'] and main_pid == os.getpid():
|
||||
try:
|
||||
os.unlink(config['pidfile'])
|
||||
|
|
@ -90,93 +84,44 @@ def setup_pid_file():
|
|||
|
||||
This function assumes the configuration has been initialized.
|
||||
"""
|
||||
config = odoo.tools.config
|
||||
import odoo # for evented # noqa: PLC0415
|
||||
if not odoo.evented and config['pidfile']:
|
||||
pid = os.getpid()
|
||||
with open(config['pidfile'], 'w') as fd:
|
||||
fd.write(str(pid))
|
||||
atexit.register(rm_pid_file, pid)
|
||||
|
||||
def export_translation():
|
||||
config = odoo.tools.config
|
||||
dbname = config['db_name']
|
||||
|
||||
if config["language"]:
|
||||
msg = "language %s" % (config["language"],)
|
||||
else:
|
||||
msg = "new language"
|
||||
_logger.info('writing translation file for %s to %s', msg,
|
||||
config["translate_out"])
|
||||
|
||||
fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
|
||||
# .pot is the same fileformat as .po
|
||||
if fileformat == "pot":
|
||||
fileformat = "po"
|
||||
|
||||
with open(config["translate_out"], "wb") as buf:
|
||||
registry = odoo.modules.registry.Registry.new(dbname)
|
||||
with registry.cursor() as cr:
|
||||
odoo.tools.translate.trans_export(config["language"],
|
||||
config["translate_modules"] or ["all"], buf, fileformat, cr)
|
||||
|
||||
_logger.info('translation file written successfully')
|
||||
|
||||
def import_translation():
|
||||
config = odoo.tools.config
|
||||
overwrite = config["overwrite_existing_translations"]
|
||||
dbname = config['db_name']
|
||||
|
||||
registry = odoo.modules.registry.Registry.new(dbname)
|
||||
with registry.cursor() as cr:
|
||||
translation_importer = odoo.tools.translate.TranslationImporter(cr)
|
||||
translation_importer.load_file(config["translate_in"], config["language"])
|
||||
translation_importer.save(overwrite=overwrite)
|
||||
|
||||
def main(args):
|
||||
check_root_user()
|
||||
odoo.tools.config.parse_config(args, setup_logging=True)
|
||||
config.parse_config(args, setup_logging=True)
|
||||
check_postgres_user()
|
||||
report_configuration()
|
||||
|
||||
config = odoo.tools.config
|
||||
|
||||
# the default limit for CSV fields in the module is 128KiB, which is not
|
||||
# quite sufficient to import images to store in attachment. 500MiB is a
|
||||
# bit overkill, but better safe than sorry I guess
|
||||
csv.field_size_limit(500 * 1024 * 1024)
|
||||
|
||||
preload = []
|
||||
if config['db_name']:
|
||||
preload = config['db_name'].split(',')
|
||||
for db_name in preload:
|
||||
try:
|
||||
odoo.service.db._create_empty_database(db_name)
|
||||
config['init']['base'] = True
|
||||
except InsufficientPrivilege as err:
|
||||
# We use an INFO loglevel on purpose in order to avoid
|
||||
# reporting unnecessary warnings on build environment
|
||||
# using restricted database access.
|
||||
_logger.info("Could not determine if database %s exists, "
|
||||
"skipping auto-creation: %s", db_name, err)
|
||||
except odoo.service.db.DatabaseExists:
|
||||
pass
|
||||
|
||||
if config["translate_out"]:
|
||||
export_translation()
|
||||
sys.exit(0)
|
||||
|
||||
if config["translate_in"]:
|
||||
import_translation()
|
||||
sys.exit(0)
|
||||
for db_name in config['db_name']:
|
||||
from odoo.service import db # noqa: PLC0415
|
||||
try:
|
||||
db._create_empty_database(db_name)
|
||||
config['init']['base'] = True
|
||||
except InsufficientPrivilege as err:
|
||||
# We use an INFO loglevel on purpose in order to avoid
|
||||
# reporting unnecessary warnings on build environment
|
||||
# using restricted database access.
|
||||
_logger.info("Could not determine if database %s exists, "
|
||||
"skipping auto-creation: %s", db_name, err)
|
||||
except db.DatabaseExists:
|
||||
pass
|
||||
|
||||
stop = config["stop_after_init"]
|
||||
|
||||
setup_pid_file()
|
||||
rc = odoo.service.server.start(preload=preload, stop=stop)
|
||||
rc = server.start(preload=config['db_name'], stop=stop)
|
||||
sys.exit(rc)
|
||||
|
||||
|
||||
class Server(Command):
|
||||
"""Start the odoo server (default command)"""
|
||||
|
||||
def run(self, args):
|
||||
odoo.tools.config.parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
||||
config.parser.prog = self.prog
|
||||
main(args)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import code
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
import odoo
|
||||
import odoo # to expose in the shell
|
||||
from odoo import api
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.service import server
|
||||
from odoo.tools import config
|
||||
from . import Command
|
||||
from . import Command, server as cli_server
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -39,15 +40,15 @@ def raise_keyboard_interrupt(*a):
|
|||
|
||||
|
||||
class Console(code.InteractiveConsole):
|
||||
def __init__(self, locals=None, filename="<console>"):
|
||||
code.InteractiveConsole.__init__(self, locals, filename)
|
||||
def __init__(self, local_vars=None, filename="<console>"):
|
||||
code.InteractiveConsole.__init__(self, locals=local_vars, filename=filename)
|
||||
try:
|
||||
import readline
|
||||
import rlcompleter
|
||||
except ImportError:
|
||||
print('readline or rlcompleter not available, autocomplete disabled.')
|
||||
else:
|
||||
readline.set_completer(rlcompleter.Completer(locals).complete)
|
||||
readline.set_completer(rlcompleter.Completer(local_vars).complete)
|
||||
readline.parse_and_bind("tab: complete")
|
||||
|
||||
|
||||
|
|
@ -56,10 +57,23 @@ class Shell(Command):
|
|||
supported_shells = ['ipython', 'ptpython', 'bpython', 'python']
|
||||
|
||||
def init(self, args):
|
||||
config.parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
||||
config.parser.prog = self.prog
|
||||
|
||||
group = optparse.OptionGroup(config.parser, "Shell options")
|
||||
group.add_option(
|
||||
'--shell-file', dest='shell_file', type='string', my_default='',
|
||||
help="Specify a python script to be run after the start of the shell. "
|
||||
"Overrides the env variable PYTHONSTARTUP."
|
||||
)
|
||||
group.add_option(
|
||||
'--shell-interface', dest='shell_interface', type='string',
|
||||
help="Specify a preferred REPL to use in shell mode. "
|
||||
"Supported REPLs are: [ipython|ptpython|bpython|python]"
|
||||
)
|
||||
config.parser.add_option_group(group)
|
||||
config.parse_config(args, setup_logging=True)
|
||||
odoo.cli.server.report_configuration()
|
||||
odoo.service.server.start(preload=[], stop=True)
|
||||
cli_server.report_configuration()
|
||||
server.start(preload=[], stop=True)
|
||||
signal.signal(signal.SIGINT, raise_keyboard_interrupt)
|
||||
|
||||
def console(self, local_vars):
|
||||
|
|
@ -72,6 +86,8 @@ class Shell(Command):
|
|||
for i in sorted(local_vars):
|
||||
print('%s: %s' % (i, local_vars[i]))
|
||||
|
||||
pythonstartup = config.options.get('shell_file') or os.environ.get('PYTHONSTARTUP')
|
||||
|
||||
preferred_interface = config.options.get('shell_interface')
|
||||
if preferred_interface:
|
||||
shells_to_try = [preferred_interface, 'python']
|
||||
|
|
@ -80,27 +96,36 @@ class Shell(Command):
|
|||
|
||||
for shell in shells_to_try:
|
||||
try:
|
||||
return getattr(self, shell)(local_vars)
|
||||
shell_func = getattr(self, shell)
|
||||
return shell_func(local_vars, pythonstartup)
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception:
|
||||
_logger.warning("Could not start '%s' shell." % shell)
|
||||
_logger.warning("Could not start '%s' shell.", shell)
|
||||
_logger.debug("Shell error:", exc_info=True)
|
||||
|
||||
def ipython(self, local_vars):
|
||||
from IPython import start_ipython
|
||||
start_ipython(argv=[], user_ns=local_vars)
|
||||
def ipython(self, local_vars, pythonstartup=None):
|
||||
from IPython import start_ipython # noqa: PLC0415
|
||||
argv = (
|
||||
['--TerminalIPythonApp.display_banner=False']
|
||||
+ ([f'--TerminalIPythonApp.exec_files={pythonstartup}'] if pythonstartup else [])
|
||||
)
|
||||
start_ipython(argv=argv, user_ns=local_vars)
|
||||
|
||||
def ptpython(self, local_vars):
|
||||
from ptpython.repl import embed
|
||||
embed({}, local_vars)
|
||||
def ptpython(self, local_vars, pythonstartup=None):
|
||||
from ptpython.repl import embed # noqa: PLC0415
|
||||
embed({}, local_vars, startup_paths=[pythonstartup] if pythonstartup else False)
|
||||
|
||||
def bpython(self, local_vars):
|
||||
from bpython import embed
|
||||
embed(local_vars)
|
||||
def bpython(self, local_vars, pythonstartup=None):
|
||||
from bpython import embed # noqa: PLC0415
|
||||
embed(local_vars, args=['-q', '-i', pythonstartup] if pythonstartup else None)
|
||||
|
||||
def python(self, local_vars):
|
||||
Console(locals=local_vars).interact()
|
||||
def python(self, local_vars, pythonstartup=None):
|
||||
console = Console(local_vars)
|
||||
if pythonstartup:
|
||||
with open(pythonstartup, encoding='utf-8') as f:
|
||||
console.runsource(f.read(), filename=pythonstartup, symbol='exec')
|
||||
console.interact(banner='')
|
||||
|
||||
def shell(self, dbname):
|
||||
local_vars = {
|
||||
|
|
@ -111,9 +136,9 @@ class Shell(Command):
|
|||
threading.current_thread().dbname = dbname
|
||||
registry = Registry(dbname)
|
||||
with registry.cursor() as cr:
|
||||
uid = odoo.SUPERUSER_ID
|
||||
ctx = odoo.api.Environment(cr, uid, {})['res.users'].context_get()
|
||||
env = odoo.api.Environment(cr, uid, ctx)
|
||||
uid = api.SUPERUSER_ID
|
||||
ctx = api.Environment(cr, uid, {})['res.users'].context_get()
|
||||
env = api.Environment(cr, uid, ctx)
|
||||
local_vars['env'] = env
|
||||
local_vars['self'] = env.user
|
||||
# context_get() has started the transaction already. Rollback to
|
||||
|
|
@ -127,5 +152,12 @@ class Shell(Command):
|
|||
|
||||
def run(self, args):
|
||||
self.init(args)
|
||||
self.shell(config['db_name'])
|
||||
|
||||
dbnames = config['db_name']
|
||||
if len(dbnames) > 1:
|
||||
sys.exit("-d/--database/db_name has multiple database, please provide a single one")
|
||||
if not dbnames:
|
||||
self.shell(None)
|
||||
else:
|
||||
self.shell(dbnames[0])
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import argparse
|
||||
import glob
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import odoo
|
||||
from . import Command
|
||||
from .server import main
|
||||
from odoo.modules.module import get_module_root, MANIFEST_NAMES
|
||||
from odoo.modules.module import Manifest, MANIFEST_NAMES
|
||||
from odoo.service.db import _create_empty_database, DatabaseExists
|
||||
from odoo.tools import config
|
||||
|
||||
|
||||
class Start(Command):
|
||||
|
|
@ -24,26 +21,20 @@ class Start(Command):
|
|||
return [mod.split(os.path.sep)[-2] for mod in mods]
|
||||
|
||||
def run(self, cmdargs):
|
||||
odoo.tools.config.parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f'{Path(sys.argv[0]).name} {self.name}',
|
||||
description=self.__doc__.strip(),
|
||||
)
|
||||
parser.add_argument('--path', default=".",
|
||||
config.parser.prog = self.prog
|
||||
self.parser.add_argument('--path', default=".",
|
||||
help="Directory where your project's modules are stored (will autodetect from current dir)")
|
||||
parser.add_argument("-d", "--database", dest="db_name", default=None,
|
||||
help="Specify the database name (default to project's directory name")
|
||||
self.parser.add_argument("-d", "--database", dest="db_name", default=None,
|
||||
help="Specify the database name (default to project's directory name")
|
||||
|
||||
|
||||
args, unknown = parser.parse_known_args(args=cmdargs)
|
||||
args, _unknown = self.parser.parse_known_args(args=cmdargs)
|
||||
|
||||
# When in a virtualenv, by default use it's path rather than the cwd
|
||||
if args.path == '.' and os.environ.get('VIRTUAL_ENV'):
|
||||
args.path = os.environ.get('VIRTUAL_ENV')
|
||||
project_path = os.path.abspath(os.path.expanduser(os.path.expandvars(args.path)))
|
||||
module_root = get_module_root(project_path)
|
||||
db_name = None
|
||||
if module_root:
|
||||
if is_path_in_module(project_path):
|
||||
# started in a module so we choose this module name for database
|
||||
db_name = project_path.split(os.path.sep)[-1]
|
||||
# go to the parent's directory of the module root
|
||||
|
|
@ -61,11 +52,11 @@ class Start(Command):
|
|||
# TODO: forbid some database names ? eg template1, ...
|
||||
try:
|
||||
_create_empty_database(args.db_name)
|
||||
odoo.tools.config['init']['base'] = True
|
||||
config['init']['base'] = True
|
||||
except DatabaseExists as e:
|
||||
pass
|
||||
except Exception as e:
|
||||
die("Could not create database `%s`. (%s)" % (args.db_name, e))
|
||||
sys.exit("Could not create database `%s`. (%s)" % (args.db_name, e))
|
||||
|
||||
if '--db-filter' not in cmdargs:
|
||||
cmdargs.append('--db-filter=^%s$' % args.db_name)
|
||||
|
|
@ -79,6 +70,12 @@ class Start(Command):
|
|||
|
||||
main(cmdargs)
|
||||
|
||||
def die(message, code=1):
|
||||
print(message, file=sys.stderr)
|
||||
sys.exit(code)
|
||||
|
||||
def is_path_in_module(path):
|
||||
old_path = None
|
||||
while path != old_path:
|
||||
if Manifest._from_path(path):
|
||||
return True
|
||||
old_path = path
|
||||
path, _ = os.path.split(path)
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -1,4 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "{{ name }}",
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
{%- set mod = name|snake -%}
|
||||
{%- set model = "%s.%s"|format(mod, mod) -%}
|
||||
{%- set root = "/%s/%s"|format(mod, mod) -%}
|
||||
# -*- coding: utf-8 -*-
|
||||
# from odoo import http
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# from odoo import models, fields, api
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
result_rate = payslip.rule_parameter('l10n_{{code}}_social_employee_rate')
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payroll_report" eval="True"/>
|
||||
<field name="struct_id" ref="l10n_{{code}}_hr_payroll.l10n_{{code}}_hr_payroll_structure_{{code}}_employee_salary"/>
|
||||
</record>
|
||||
|
||||
|
|
@ -30,7 +29,6 @@ result = categories.BASIC
|
|||
result_rate = payslip.rule_parameter('l10n_{{code}}_social_employer_rate')
|
||||
result = categories.BASIC
|
||||
</field>
|
||||
<field name="appears_on_payroll_report" eval="True"/>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
<field name="struct_id" ref="l10n_{{code}}_hr_payroll.l10n_{{code}}_hr_payroll_structure_{{code}}_employee_salary"/>
|
||||
</record>
|
||||
|
|
@ -45,7 +43,6 @@ result = categories.BASIC
|
|||
<field name="amount_python_compute">
|
||||
result = categories['SOCIAL.EMPLOYEE']
|
||||
</field>
|
||||
<field name="appears_on_payroll_report" eval="True"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
<field name="struct_id" ref="l10n_{{code}}_hr_payroll.l10n_{{code}}_hr_payroll_structure_{{code}}_employee_salary"/>
|
||||
</record>
|
||||
|
|
@ -60,7 +57,6 @@ result = categories['SOCIAL.EMPLOYEE']
|
|||
<field name="amount_python_compute">
|
||||
result = categories['SOCIAL.EMPLOYER']
|
||||
</field>
|
||||
<field name="appears_on_payroll_report" eval="True"/>
|
||||
<field name="appears_on_payslip" eval="False"/>
|
||||
<field name="struct_id" ref="l10n_{{code}}_hr_payroll.l10n_{{code}}_hr_payroll_structure_{{code}}_employee_salary"/>
|
||||
</record>
|
||||
|
|
@ -75,7 +71,6 @@ result = categories['SOCIAL.EMPLOYER']
|
|||
<field name="amount_python_compute">
|
||||
result = categories.GROSS
|
||||
</field>
|
||||
<field name="appears_on_payroll_report" eval="True"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
<field name="struct_id" ref="l10n_{{code}}_hr_payroll.l10n_{{code}}_hr_payroll_structure_{{code}}_employee_salary"/>
|
||||
</record>
|
||||
|
|
@ -97,7 +92,6 @@ for low, high, prev, rate in brackets:
|
|||
if low <= taxable < high:
|
||||
result = -(prev + (taxable - low) * rate / 100)
|
||||
</field>
|
||||
<field name="appears_on_payroll_report" eval="True"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
<field name="struct_id" ref="l10n_{{code}}_hr_payroll.l10n_{{code}}_hr_payroll_structure_{{code}}_employee_salary"/>
|
||||
</record>
|
||||
|
|
@ -123,7 +117,6 @@ elif children == 3:
|
|||
elif children >= 4:
|
||||
result = amounts[0] + amounts[1] + amounts[2] + (children - 3) * amounts[3]
|
||||
</field>
|
||||
<field name="appears_on_payroll_report" eval="True"/>
|
||||
<field name="appears_on_payslip" eval="True"/>
|
||||
<field name="struct_id" ref="l10n_{{code}}_hr_payroll.l10n_{{code}}_hr_payroll_structure_{{code}}_employee_salary"/>
|
||||
</record>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<field name="job_id" ref="job_developer_{{name}}"/>
|
||||
<field name="country_id" ref="base.{{code}}"/>
|
||||
<field name="company_id" ref="l10n_{{code}}_hr_payroll.res_company_{{code}}"/>
|
||||
<field name="gender">male</field>
|
||||
<field name="sex">male</field>
|
||||
</record>
|
||||
|
||||
<record id="l10n_{{code}}_res_partner_antonina" model="res.partner">
|
||||
|
|
@ -53,10 +53,10 @@
|
|||
<field name="partner_id" ref="l10n_{{code}}_hr_payroll.res_partner_antonina"/>
|
||||
<field name="login">antoninakaczmarczyk@example.com</field>
|
||||
<field name="password">antoninakaczmarczyk</field>
|
||||
<field name="signature" type="html"><span>--<br/>+A. Kaczmarczyk</span></field>
|
||||
<field name="signature">A. Kaczmarczyk</field>
|
||||
<field name="company_ids" eval="[(4, ref('l10n_{{code}}_hr_payroll.res_company_{{code}}'))]"/>
|
||||
<field name="company_id" ref="l10n_{{code}}_hr_payroll.res_company_{{code}}"/>
|
||||
<field name="groups_id" eval="[(6,0,[ref('base.group_user')])]"/>
|
||||
<field name="group_ids" eval="[(6,0,[ref('base.group_user')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="l10n_{{code}}_res_partner_antonina_work_address" model="res.partner">
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
|
||||
<record id="l10n_{{code}}_hr_employee_antonina" model="hr.employee">
|
||||
<field name="name">Antonina Kaczmarczyk (fpo)</field>
|
||||
<field name="gender">female</field>
|
||||
<field name="sex">female</field>
|
||||
<field name="marital">single</field>
|
||||
<field name="job_title">Software Developer</field>
|
||||
<field name="address_id" ref="l10n_{{code}}_hr_payroll.res_partner_antonina_work_address"/>
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
<field name="country_id" ref="base.{{code}}"/>
|
||||
<field name="resource_calendar_id" ref="resource.resource_calendar_std"/>
|
||||
<field name="identification_id">8752027365496</field>
|
||||
<field name="bank_account_id" ref="l10n_{{code}}_hr_payroll.res_partner_bank_account_norberta"/>
|
||||
<field name="bank_account_ids" eval="[(4, ref('l10n_{{code}}_hr_payroll.res_partner_bank_account_norberta'))]"/>
|
||||
<field name="company_id" ref="l10n_{{code}}_hr_payroll.res_company_{{code}}"/>
|
||||
<field name="user_id" ref="l10n_{{code}}_hr_payroll.user_antonina"/>
|
||||
</record>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr_payslip
|
||||
from . import hr_contract
|
||||
from . import hr_version
|
||||
from . import hr_payslip_worked_days
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from odoo import fields, models
|
||||
|
||||
|
||||
class HrContract(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
class HrVersion(models.Model):
|
||||
_inherit = ['hr.version']
|
||||
|
||||
l10n_{{code}}_field = fields.Char()
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from . import Command
|
||||
from odoo.modules.module import MANIFEST_NAMES
|
||||
|
||||
|
||||
class TSConfig(Command):
|
||||
""" Generates tsconfig files for javascript code """
|
||||
|
||||
def get_module_list(self, path):
|
||||
return [
|
||||
mod.split(os.path.sep)[-2]
|
||||
for mname in MANIFEST_NAMES
|
||||
for mod in glob.glob(os.path.join(path, f'*/{mname}'))
|
||||
]
|
||||
|
||||
def clean_path(self, path):
|
||||
return re.sub(r"/{2,}", "/", path)
|
||||
|
||||
def prefix_suffix_path(self, path, prefix, suffix):
|
||||
return self.clean_path(f"{prefix}/{path}/{suffix}")
|
||||
|
||||
def remove_(self, modules, module):
|
||||
for name, path in modules:
|
||||
if module == name:
|
||||
modules.remove((name, path))
|
||||
|
||||
def run(self, cmdargs):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f'{Path(sys.argv[0]).name} {self.name}',
|
||||
description=self.__doc__.strip()
|
||||
)
|
||||
parser.add_argument('--addons-path', type=str, nargs=1, dest="paths")
|
||||
args = parser.parse_args(args=cmdargs)
|
||||
|
||||
paths = list(map(self.clean_path, args.paths[0].split(',')))
|
||||
modules = {}
|
||||
owl_path = ""
|
||||
for path in paths:
|
||||
for module in self.get_module_list(path):
|
||||
modules[module] = self.prefix_suffix_path(module, path, "/static/src/*")
|
||||
if module == "web":
|
||||
owl_path = self.prefix_suffix_path(module, path, "/static/lib/owl/owl.js")
|
||||
|
||||
content = self.generate_file_content(modules, paths)
|
||||
content["compilerOptions"]["paths"]["@odoo/owl"] = [owl_path]
|
||||
# pylint: disable=bad-builtin
|
||||
print(json.dumps(content, indent=2))
|
||||
|
||||
def generate_imports(self, modules):
|
||||
return {
|
||||
f'@{module}/*': [path]
|
||||
for module, path in modules.items()
|
||||
}
|
||||
|
||||
def generate_file_content(self, modules, paths):
|
||||
return {
|
||||
'compilerOptions': {
|
||||
"baseUrl": ".",
|
||||
"target": "es2019",
|
||||
"checkJs": True,
|
||||
"allowJs": True,
|
||||
"noEmit": True,
|
||||
"typeRoots": list(map(lambda p: p + "/web/tooling/types", paths)),
|
||||
"paths": self.generate_imports(modules)
|
||||
}, "exclude": self.generate_excludes()
|
||||
}
|
||||
|
||||
def generate_excludes(self):
|
||||
return [
|
||||
"/**/*.po",
|
||||
"/**/*.py",
|
||||
"/**/*.pyc",
|
||||
"/**/*.xml",
|
||||
"/**/*.png",
|
||||
"/**/*.md",
|
||||
"/**/*.dat",
|
||||
"/**/*.scss",
|
||||
"/**/*.jpg",
|
||||
"/**/*.svg",
|
||||
"/**/*.pot",
|
||||
"/**/*.csv",
|
||||
"/**/*.mo",
|
||||
"/**/*.txt",
|
||||
"/**/*.less",
|
||||
"/**/*.bcmap",
|
||||
"/**/*.properties",
|
||||
"/**/*.html",
|
||||
"/**/*.ttf",
|
||||
"/**/*.rst",
|
||||
"/**/*.css",
|
||||
"/**/*.pack",
|
||||
"/**/*.idx",
|
||||
"/**/*.h",
|
||||
"/**/*.map",
|
||||
"/**/*.gif",
|
||||
"/**/*.sample",
|
||||
"/**/*.doctree",
|
||||
"/**/*.so",
|
||||
"/**/*.pdf",
|
||||
"/**/*.xslt",
|
||||
"/**/*.conf",
|
||||
"/**/*.woff",
|
||||
"/**/*.xsd",
|
||||
"/**/*.eot",
|
||||
"/**/*.jst",
|
||||
"/**/*.flow",
|
||||
"/**/*.sh",
|
||||
"/**/*.yml",
|
||||
"/**/*.pfb",
|
||||
"/**/*.jpeg",
|
||||
"/**/*.crt",
|
||||
"/**/*.template",
|
||||
"/**/*.pxd",
|
||||
"/**/*.dylib",
|
||||
"/**/*.pem",
|
||||
"/**/*.rng",
|
||||
"/**/*.xsl",
|
||||
"/**/*.xls",
|
||||
"/**/*.cfg",
|
||||
"/**/*.pyi",
|
||||
"/**/*.pth",
|
||||
"/**/*.markdown",
|
||||
"/**/*.key",
|
||||
"/**/*.ico",
|
||||
]
|
||||
|
|
@ -30,6 +30,7 @@ bullets.
|
|||
"""
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from importlib.machinery import SourceFileLoader
|
||||
|
|
@ -38,6 +39,9 @@ from types import ModuleType
|
|||
from typing import Iterator
|
||||
|
||||
ROOT = Path(__file__).parent.parent
|
||||
UPGRADE = ROOT / 'upgrade_code'
|
||||
AVAILABLE_EXT = ('.py', '.js', '.css', '.scss', '.xml', '.csv', '.po', '.pot')
|
||||
|
||||
|
||||
try:
|
||||
import odoo.addons
|
||||
|
|
@ -54,15 +58,18 @@ except ImportError:
|
|||
import release
|
||||
from parse_version import parse_version
|
||||
class Command:
|
||||
pass
|
||||
config = {'addons_path': ''}
|
||||
""" Simplified version of the one in command.py, for standalone execution """
|
||||
@property
|
||||
def parser(self):
|
||||
return argparse.ArgumentParser(
|
||||
prog=Path(sys.argv[0]).name,
|
||||
description=__doc__.replace('/odoo/upgrade_code', str(UPGRADE)),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
config = None
|
||||
initialize_sys_path = None
|
||||
|
||||
|
||||
UPGRADE = ROOT / 'upgrade_code'
|
||||
AVAILABLE_EXT = ('.py', '.js', '.css', '.scss', '.xml', '.csv')
|
||||
|
||||
|
||||
class FileAccessor:
|
||||
addon: Path
|
||||
path: Path
|
||||
|
|
@ -113,11 +120,11 @@ class FileManager:
|
|||
return self._files.get(str(path))
|
||||
|
||||
if sys.stdout.isatty():
|
||||
def print_progress(self, current, total=None):
|
||||
def print_progress(self, current: int, total: int | None =None, file_name : str | Path = ""):
|
||||
total = total or len(self) or 1
|
||||
print(f'{current / total:>4.0%}', end='\r', file=sys.stderr) # noqa: T201
|
||||
print(f'\033[K{current / total:>4.0%} \033[37m{file_name}\033[0m', end='\r', file=sys.stderr) # noqa: T201
|
||||
else:
|
||||
def print_progress(self, current, total=None):
|
||||
def print_progress(self, current: int, total: int | None =None, file_name : str | Path = ""):
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -168,18 +175,8 @@ def migrate(
|
|||
class UpgradeCode(Command):
|
||||
""" Rewrite the entire source code using the scripts found at /odoo/upgrade_code """
|
||||
name = 'upgrade_code'
|
||||
prog_name = Path(sys.argv[0]).name
|
||||
|
||||
def __init__(self):
|
||||
self.parser = argparse.ArgumentParser(
|
||||
prog=(
|
||||
f"{self.prog_name} [--addons-path=PATH,...] {self.name}"
|
||||
if initialize_sys_path else
|
||||
self.prog_name
|
||||
),
|
||||
description=__doc__.replace('/odoo/upgrade_code', str(UPGRADE)),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
group = self.parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument(
|
||||
'--script',
|
||||
|
|
@ -208,7 +205,13 @@ class UpgradeCode(Command):
|
|||
help="list the files that would be re-written, but rewrite none")
|
||||
self.parser.add_argument(
|
||||
'--addons-path',
|
||||
default=config['addons_path'],
|
||||
type=(
|
||||
functools.partial(config.parse, 'addons_path')
|
||||
if config else
|
||||
# the paths must be resolved already
|
||||
functools.partial(str.split, sep=',')
|
||||
),
|
||||
default=config['addons_path'] if config else [],
|
||||
metavar='PATH,...',
|
||||
help="specify additional addons paths (separated by commas)",
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue