mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 05:12:02 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
20
odoo-bringout-oca-ocb-base/odoo/cli/__init__.py
Normal file
20
odoo-bringout-oca-ocb-base/odoo/cli/__init__.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
import odoo
|
||||
|
||||
from .command import Command, main
|
||||
|
||||
from . import cloc
|
||||
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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
48
odoo-bringout-oca-ocb-base/odoo/cli/cloc.py
Normal file
48
odoo-bringout-oca-ocb-base/odoo/cli/cloc.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# 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 """
|
||||
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)
|
||||
if not opt.database and not opt.path:
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
c = cloc.Cloc()
|
||||
if opt.database:
|
||||
config.parse_config(['-d', opt.database] + unknown)
|
||||
c.count_database(opt.database)
|
||||
if opt.path:
|
||||
for i in opt.path:
|
||||
c.count_path(i)
|
||||
c.report(opt.verbose)
|
||||
68
odoo-bringout-oca-ocb-base/odoo/cli/command.py
Normal file
68
odoo-bringout-oca-ocb-base/odoo/cli/command.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import odoo
|
||||
from odoo.modules import get_modules, get_module_path, initialize_sys_path
|
||||
|
||||
commands = {}
|
||||
class Command:
|
||||
name = None
|
||||
def __init_subclass__(cls):
|
||||
cls.name = cls.name or cls.__name__.lower()
|
||||
commands[cls.name] = cls
|
||||
|
||||
|
||||
ODOO_HELP = """\
|
||||
Odoo CLI, use '{odoo_bin} --help' for regular server options.
|
||||
|
||||
Available commands:
|
||||
{command_list}
|
||||
|
||||
Use '{odoo_bin} <command> --help' for individual command help."""
|
||||
|
||||
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("-"):
|
||||
# parse only the addons-path, do not setup the logger...
|
||||
odoo.tools.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]
|
||||
args = args[1:]
|
||||
|
||||
if command in commands:
|
||||
o = commands[command]()
|
||||
o.run(args)
|
||||
else:
|
||||
sys.exit('Unknown command %r' % (command,))
|
||||
174
odoo-bringout-oca-ocb-base/odoo/cli/db.py
Normal file
174
odoo-bringout-oca-ocb-base/odoo/cli/db.py
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import argparse
|
||||
import io
|
||||
import urllib.parse
|
||||
import sys
|
||||
import zipfile
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
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'
|
||||
|
||||
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.add_argument('-c', '--config')
|
||||
parser.add_argument('-D', '--data-dir')
|
||||
parser.add_argument('--addons-path')
|
||||
parser.add_argument('-r', '--db_user')
|
||||
parser.add_argument('-w', '--db_password')
|
||||
parser.add_argument('--pg_path')
|
||||
parser.add_argument('--db_host')
|
||||
parser.add_argument('--db_port')
|
||||
parser.add_argument('--db_sslmode')
|
||||
parser.set_defaults(func=lambda _: exit(parser.format_help()))
|
||||
|
||||
subs = parser.add_subparsers()
|
||||
load = subs.add_parser(
|
||||
"load", help="Load a dump file.",
|
||||
description="Loads a dump file into odoo, dump file can be a URL. "
|
||||
"If `database` is provided, uses that as the database name. "
|
||||
"Otherwise uses the dump file name without extension.")
|
||||
load.set_defaults(func=self.load)
|
||||
load.add_argument(
|
||||
'-f', '--force', action='store_const', default=False, const=True,
|
||||
help="delete `database` database before loading if it exists"
|
||||
)
|
||||
load.add_argument(
|
||||
'-n', '--neutralize', action='store_const', default=False, const=True,
|
||||
help="neutralize the database after restore"
|
||||
)
|
||||
load.add_argument(
|
||||
'database', nargs='?',
|
||||
help="database to create, defaults to dump file's name "
|
||||
"(without extension)"
|
||||
)
|
||||
load.add_argument('dump_file', help="zip or pg_dump file to load")
|
||||
|
||||
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.")
|
||||
dump.set_defaults(func=self.dump)
|
||||
dump.add_argument('database', help="database to dump")
|
||||
dump.add_argument(
|
||||
'dump_path', nargs='?', default='-',
|
||||
help="if provided, database is dumped to specified path, otherwise "
|
||||
"or if `-`, dumped to stdout",
|
||||
)
|
||||
|
||||
duplicate = subs.add_parser("duplicate", help="Duplicate a database including filestore.")
|
||||
duplicate.set_defaults(func=self.duplicate)
|
||||
duplicate.add_argument(
|
||||
'-f', '--force', action='store_const', default=False, const=True,
|
||||
help="delete `target` database before copying if it exists"
|
||||
)
|
||||
duplicate.add_argument(
|
||||
'-n', '--neutralize', action='store_const', default=False, const=True,
|
||||
help="neutralize the target database after duplicate"
|
||||
)
|
||||
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 = subs.add_parser("rename", help="Rename a database including filestore.")
|
||||
rename.set_defaults(func=self.rename)
|
||||
rename.add_argument(
|
||||
'-f', '--force', action='store_const', default=False, const=True,
|
||||
help="delete `target` database before renaming if it exists"
|
||||
)
|
||||
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 = subs.add_parser("drop", help="Delete a database including filestore")
|
||||
drop.set_defaults(func=self.drop)
|
||||
drop.add_argument("database", help="database to delete")
|
||||
|
||||
args = parser.parse_args(cmdargs)
|
||||
|
||||
config.parse_config([
|
||||
val
|
||||
for k, v in vars(args).items()
|
||||
if v is not None
|
||||
if k in ['config', 'data_dir', 'addons_path'] or k.startswith(('db_', 'pg_'))
|
||||
for val in [
|
||||
'--data-dir' if k == 'data_dir'\
|
||||
else '--addons-path' if k == 'addons_path'\
|
||||
else f'--{k}',
|
||||
v,
|
||||
]
|
||||
])
|
||||
# force db management active to bypass check when only a
|
||||
# `check_db_management_enabled` version is available.
|
||||
config['list_db'] = True
|
||||
report_configuration()
|
||||
|
||||
args.func(args)
|
||||
|
||||
def load(self, args):
|
||||
db_name = args.database or Path(args.dump_file).stem
|
||||
self._check_target(db_name, delete_if_exists=args.force)
|
||||
|
||||
url = urllib.parse.urlparse(args.dump_file)
|
||||
if url.scheme:
|
||||
eprint(f"Fetching {args.dump_file}...", end='')
|
||||
r = requests.get(args.dump_file, timeout=10)
|
||||
if not r.ok:
|
||||
exit(f" unable to fetch {args.dump_file}: {r.reason}")
|
||||
|
||||
eprint(" done")
|
||||
dump_file = io.BytesIO(r.content)
|
||||
else:
|
||||
eprint(f"Restoring {args.dump_file}...")
|
||||
dump_file = args.dump_file
|
||||
|
||||
if not zipfile.is_zipfile(dump_file):
|
||||
exit("Not a zipped dump file, use `pg_restore` to restore raw dumps,"
|
||||
" and `psql` to execute sql dumps or scripts.")
|
||||
|
||||
restore_db(db=db_name, dump_file=dump_file, copy=True, neutralize_database=args.neutralize)
|
||||
|
||||
def dump(self, args):
|
||||
if args.dump_path == '-':
|
||||
dump_db(args.database, sys.stdout.buffer)
|
||||
else:
|
||||
with open(args.dump_path, 'wb') as f:
|
||||
dump_db(args.database, f)
|
||||
|
||||
def duplicate(self, args):
|
||||
self._check_target(args.target, delete_if_exists=args.force)
|
||||
exp_duplicate_database(args.source, args.target, neutralize_database=args.neutralize)
|
||||
|
||||
def rename(self, args):
|
||||
self._check_target(args.target, delete_if_exists=args.force)
|
||||
exp_rename(args.source, args.target)
|
||||
|
||||
def drop(self, args):
|
||||
if not exp_drop(args.database):
|
||||
exit(f"Database {args.database} does not exist.")
|
||||
|
||||
def _check_target(self, target, *, delete_if_exists):
|
||||
if exp_db_exist(target):
|
||||
if delete_if_exists:
|
||||
exp_drop(target)
|
||||
else:
|
||||
exit(f"Target database {target} exists, aborting.\n\n"
|
||||
f"\tuse `--force` to delete the existing database anyway.")
|
||||
89
odoo-bringout-oca-ocb-base/odoo/cli/deploy.py
Normal file
89
odoo-bringout-oca-ocb-base/odoo/cli/deploy.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# 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__()
|
||||
self.session = requests.session()
|
||||
|
||||
def deploy_module(self, module_path, url, login, password, db='', force=False):
|
||||
url = url.rstrip('/')
|
||||
module_file = self.zip_module(module_path)
|
||||
try:
|
||||
return self.login_upload_module(module_file, url, login, password, db, force=force)
|
||||
finally:
|
||||
os.remove(module_file)
|
||||
|
||||
def login_upload_module(self, module_file, url, login, password, db, force=False):
|
||||
print("Uploading module file...")
|
||||
self.session.get(f'{url}/web/login?db={db}', allow_redirects=False) # this set the db in the session
|
||||
endpoint = url + '/base_import_module/login_upload'
|
||||
post_data = {
|
||||
'login': login,
|
||||
'password': password,
|
||||
'db': db,
|
||||
'force': '1' if force else '',
|
||||
}
|
||||
with open(module_file, 'rb') as f:
|
||||
res = self.session.post(endpoint, files={'mod_file': f}, data=post_data)
|
||||
|
||||
if res.status_code == 404:
|
||||
raise Exception(
|
||||
"The server '%s' does not have the 'base_import_module' installed or is not up-to-date." % url)
|
||||
res.raise_for_status()
|
||||
return res.text
|
||||
|
||||
def zip_module(self, path):
|
||||
path = os.path.abspath(path)
|
||||
if not os.path.isdir(path):
|
||||
raise Exception("Could not find module directory '%s'" % path)
|
||||
container, module_name = os.path.split(path)
|
||||
temp = tempfile.mktemp(suffix='.zip')
|
||||
try:
|
||||
print("Zipping module directory...")
|
||||
with zipfile.ZipFile(temp, 'w') as zfile:
|
||||
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())
|
||||
return temp
|
||||
except Exception:
|
||||
os.remove(temp)
|
||||
raise
|
||||
|
||||
def run(self, cmdargs):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f'{Path(sys.argv[0]).name} {self.name}',
|
||||
description=self.__doc__
|
||||
)
|
||||
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.')
|
||||
parser.add_argument('--login', dest='login', default="admin", help='Login (default=admin)')
|
||||
parser.add_argument('--password', dest='password', default="admin", help='Password (default=admin)')
|
||||
parser.add_argument('--verify-ssl', action='store_true', help='Verify SSL certificate')
|
||||
parser.add_argument('--force', action='store_true', help='Force init even if module is already installed. (will update `noupdate="1"` records)')
|
||||
if not cmdargs:
|
||||
sys.exit(parser.print_help())
|
||||
|
||||
args = parser.parse_args(args=cmdargs)
|
||||
|
||||
if not args.verify_ssl:
|
||||
self.session.verify = False
|
||||
|
||||
try:
|
||||
if not args.url.startswith(('http://', 'https://')):
|
||||
args.url = 'https://%s' % args.url
|
||||
result = self.deploy_module(args.path, args.url, args.login, args.password, args.db, force=args.force)
|
||||
print(result)
|
||||
except Exception as e:
|
||||
sys.exit("ERROR: %s" % e)
|
||||
36
odoo-bringout-oca-ocb-base/odoo/cli/genproxytoken.py
Normal file
36
odoo-bringout-oca-ocb-base/odoo/cli/genproxytoken.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# 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')
|
||||
51
odoo-bringout-oca-ocb-base/odoo/cli/neutralize.py
Normal file
51
odoo-bringout-oca-ocb-base/odoo/cli/neutralize.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
import optparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import odoo
|
||||
|
||||
from . import Command
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Neutralize(Command):
|
||||
"""Neutralize a production database for testing: no emails sent, etc."""
|
||||
|
||||
def run(self, args):
|
||||
parser = odoo.tools.config.parser
|
||||
parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
||||
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)
|
||||
|
||||
dbname = odoo.tools.config['db_name']
|
||||
if not dbname:
|
||||
_logger.error('Neutralize command needs a database name. Use "-d" argument')
|
||||
sys.exit(1)
|
||||
|
||||
if not opt.to_stdout:
|
||||
_logger.info("Starting %s database neutralization", dbname)
|
||||
|
||||
try:
|
||||
with odoo.sql_db.db_connect(dbname).cursor() as cursor:
|
||||
if opt.to_stdout:
|
||||
installed_modules = odoo.modules.neutralize.get_installed_modules(cursor)
|
||||
queries = odoo.modules.neutralize.get_neutralization_queries(installed_modules)
|
||||
# pylint: disable=bad-builtin
|
||||
print('BEGIN;')
|
||||
for query in queries:
|
||||
# pylint: disable=bad-builtin
|
||||
print(query.rstrip(";") + ";")
|
||||
# pylint: disable=bad-builtin
|
||||
print("COMMIT;")
|
||||
else:
|
||||
odoo.modules.neutralize.neutralize_database(cursor)
|
||||
|
||||
except Exception:
|
||||
_logger.critical("An error occurred during the neutralization. THE DATABASE IS NOT NEUTRALIZED!")
|
||||
sys.exit(1)
|
||||
250
odoo-bringout-oca-ocb-base/odoo/cli/obfuscate.py
Normal file
250
odoo-bringout-oca-ocb-base/odoo/cli/obfuscate.py
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import odoo
|
||||
import sys
|
||||
import optparse
|
||||
import logging
|
||||
|
||||
from collections import defaultdict
|
||||
from psycopg2 import sql
|
||||
|
||||
from . import Command
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Obfuscate(Command):
|
||||
"""Obfuscate data in a given odoo database"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.cr = None
|
||||
|
||||
def _ensure_cr(func):
|
||||
def check_cr(self, *args, **kwargs):
|
||||
if not self.cr:
|
||||
raise Exception("No database connection")
|
||||
return func(self, *args, **kwargs)
|
||||
return check_cr
|
||||
|
||||
@_ensure_cr
|
||||
def begin(self):
|
||||
self.cr.execute("begin work")
|
||||
self.cr.execute("CREATE EXTENSION IF NOT EXISTS pgcrypto")
|
||||
|
||||
@_ensure_cr
|
||||
def commit(self):
|
||||
self.cr.commit()
|
||||
|
||||
@_ensure_cr
|
||||
def rollback(self):
|
||||
self.cr.rollback()
|
||||
|
||||
@_ensure_cr
|
||||
def set_pwd(self, pwd):
|
||||
"""Set password to cypher/uncypher datas"""
|
||||
self.cr.execute("INSERT INTO ir_config_parameter (key, value) VALUES ('odoo_cyph_pwd', 'odoo_cyph_'||encode(pgp_sym_encrypt(%s, %s), 'base64')) ON CONFLICT(key) DO NOTHING", [pwd, pwd])
|
||||
|
||||
@_ensure_cr
|
||||
def check_pwd(self, pwd):
|
||||
"""If password is set, check if it's valid"""
|
||||
uncypher_pwd = self.uncypher_string(sql.Identifier('value'))
|
||||
|
||||
try:
|
||||
qry = sql.SQL("SELECT {uncypher_pwd} FROM ir_config_parameter WHERE key='odoo_cyph_pwd'").format(uncypher_pwd=uncypher_pwd)
|
||||
self.cr.execute(qry, {'pwd': pwd})
|
||||
if self.cr.rowcount == 0 or (self.cr.rowcount == 1 and self.cr.fetchone()[0] == pwd):
|
||||
return True
|
||||
except Exception as e: # noqa: BLE001
|
||||
_logger.error("Error checking password: %s", e)
|
||||
return False
|
||||
|
||||
@_ensure_cr
|
||||
def clear_pwd(self):
|
||||
"""Unset password to cypher/uncypher datas"""
|
||||
self.cr.execute("DELETE FROM ir_config_parameter WHERE key='odoo_cyph_pwd' ")
|
||||
|
||||
def cypher_string(self, sql_field):
|
||||
# don't double cypher fields
|
||||
return sql.SQL("""CASE WHEN starts_with({field_name}, 'odoo_cyph_') THEN {field_name} ELSE 'odoo_cyph_'||encode(pgp_sym_encrypt({field_name}, %(pwd)s), 'base64') END""").format(field_name=sql_field)
|
||||
|
||||
def uncypher_string(self, sql_field):
|
||||
return sql.SQL("""CASE WHEN starts_with({field_name}, 'odoo_cyph_') THEN pgp_sym_decrypt(decode(substring({field_name}, 11)::text, 'base64'), %(pwd)s) ELSE {field_name} END""").format(field_name=sql_field)
|
||||
|
||||
def check_field(self, table, field):
|
||||
qry = "SELECT udt_name FROM information_schema.columns WHERE table_name=%s AND column_name=%s"
|
||||
self.cr.execute(qry, [table, field])
|
||||
if self.cr.rowcount == 1:
|
||||
res = self.cr.fetchone()
|
||||
if res[0] in ['text', 'varchar']:
|
||||
# Doesn t work for selection fields ...
|
||||
return 'string'
|
||||
if res[0] == 'jsonb':
|
||||
return 'json'
|
||||
return False
|
||||
|
||||
def get_all_fields(self):
|
||||
qry = "SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='public' AND udt_name IN ['text', 'varchar', 'jsonb'] AND NOT table_name LIKE 'ir_%' ORDER BY 1,2"
|
||||
self.cr.execute(qry)
|
||||
return self.cr.fetchall()
|
||||
|
||||
def convert_table(self, table, fields, pwd, with_commit=False, unobfuscate=False):
|
||||
cypherings = []
|
||||
cyph_fct = self.uncypher_string if unobfuscate else self.cypher_string
|
||||
|
||||
for field in fields:
|
||||
field_type = self.check_field(table, field)
|
||||
if field_type == 'string':
|
||||
cypher_query = cyph_fct(sql.Identifier(field))
|
||||
cypherings.append(sql.SQL('{field}={cypher}').format(field=sql.Identifier(field), cypher=cypher_query))
|
||||
elif field_type == 'json':
|
||||
# List every key
|
||||
# Loop on keys
|
||||
# Nest the jsonb_set calls to update all values at once
|
||||
# Do not create the key in json if doesn't esist
|
||||
new_field_value = sql.Identifier(field)
|
||||
self.cr.execute(sql.SQL('select distinct jsonb_object_keys({field}) as key from {table}').format(field=sql.Identifier(field), table=sql.Identifier(table)))
|
||||
keys = [k[0] for k in self.cr.fetchall()]
|
||||
for key in keys:
|
||||
cypher_query = cyph_fct(sql.SQL("{field}->>{key}").format(field=sql.Identifier(field), key=sql.Literal(key)))
|
||||
new_field_value = sql.SQL("""jsonb_set({new_field_value}, array[{key}], to_jsonb({cypher_query})::jsonb, FALSE) """).format(new_field_value=new_field_value, key=sql.Literal(key), cypher_query=cypher_query)
|
||||
cypherings.append(sql.SQL('{field}={cypher}').format(field=sql.Identifier(field), cypher=new_field_value))
|
||||
|
||||
if cypherings:
|
||||
query = sql.SQL("UPDATE {table} SET {fields}").format(table=sql.Identifier(table), fields=sql.SQL(',').join(cypherings))
|
||||
self.cr.execute(query, {"pwd": pwd})
|
||||
if with_commit:
|
||||
self.commit()
|
||||
self.begin()
|
||||
|
||||
def confirm_not_secure(self):
|
||||
_logger.info("The obfuscate method is not considered as safe to transfer anonymous datas to a third party.")
|
||||
conf_y = input(f"This will alter data in the database {self.dbname} and can lead to a data loss. Would you like to proceed [y/N]? ")
|
||||
if conf_y.upper() != 'Y':
|
||||
self.rollback()
|
||||
sys.exit(0)
|
||||
conf_db = input(f"Please type your database name ({self.dbname}) in UPPERCASE to confirm you understand this operation is not considered secure : ")
|
||||
if self.dbname.upper() != conf_db:
|
||||
self.rollback()
|
||||
sys.exit(0)
|
||||
return True
|
||||
|
||||
def run(self, cmdargs):
|
||||
parser = odoo.tools.config.parser
|
||||
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")
|
||||
group.add_option('--exclude', dest="exclude", default=False, help="List of table.columns to exclude from obfuscate/unobfuscate: table1.column1,table2.column1,table2.column2")
|
||||
group.add_option('--file', dest="file", default=False, help="File containing the list of table.columns to obfuscate/unobfuscate")
|
||||
group.add_option('--unobfuscate', action='store_true', default=False)
|
||||
group.add_option('--allfields', action='store_true', default=False, help="Used in unobfuscate mode, try to unobfuscate all fields. Cannot be used in obfuscate mode. Slower than specifying fields.")
|
||||
group.add_option('--vacuum', action='store_true', default=False, help="Vacuum database after unobfuscating")
|
||||
group.add_option('--pertablecommit', action='store_true', default=False, help="Commit after each table instead of a big transaction")
|
||||
group.add_option(
|
||||
'-y', '--yes', dest="yes", action='store_true', default=False,
|
||||
help="Don't ask for manual confirmation. Use it carefully as the obfuscate method is not considered as safe to transfer anonymous datas to a third party.")
|
||||
|
||||
parser.add_option_group(group)
|
||||
if not cmdargs:
|
||||
sys.exit(parser.print_help())
|
||||
|
||||
try:
|
||||
opt = odoo.tools.config.parse_config(cmdargs)
|
||||
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.registry = odoo.registry(self.dbname)
|
||||
with self.registry.cursor() as cr:
|
||||
self.cr = cr
|
||||
self.begin()
|
||||
if self.check_pwd(opt.pwd):
|
||||
fields = [
|
||||
('mail_tracking_value', 'old_value_char'),
|
||||
('mail_tracking_value', 'old_value_text'),
|
||||
('mail_tracking_value', 'new_value_char'),
|
||||
('mail_tracking_value', 'new_value_text'),
|
||||
('res_partner', 'name'),
|
||||
('res_partner', 'display_name'),
|
||||
('res_partner', 'email'),
|
||||
('res_partner', 'phone'),
|
||||
('res_partner', 'mobile'),
|
||||
('res_partner', 'street'),
|
||||
('res_partner', 'street2'),
|
||||
('res_partner', 'city'),
|
||||
('res_partner', 'zip'),
|
||||
('res_partner', 'vat'),
|
||||
('res_partner', 'website'),
|
||||
('res_country', 'name'),
|
||||
('mail_message', 'subject'),
|
||||
('mail_message', 'email_from'),
|
||||
('mail_message', 'reply_to'),
|
||||
('mail_message', 'body'),
|
||||
('crm_lead', 'name'),
|
||||
('crm_lead', 'contact_name'),
|
||||
('crm_lead', 'partner_name'),
|
||||
('crm_lead', 'email_from'),
|
||||
('crm_lead', 'phone'),
|
||||
('crm_lead', 'mobile'),
|
||||
('crm_lead', 'website'),
|
||||
('crm_lead', 'description'),
|
||||
]
|
||||
|
||||
if opt.fields:
|
||||
if not opt.allfields:
|
||||
fields += [tuple(f.split('.')) for f in opt.fields.split(',')]
|
||||
else:
|
||||
_logger.error("--allfields option is set, ignoring --fields option")
|
||||
if opt.file:
|
||||
with open(opt.file, encoding='utf-8') as f:
|
||||
fields += [tuple(l.strip().split('.')) for l in f]
|
||||
if opt.exclude:
|
||||
if not opt.allfields:
|
||||
fields = [f for f in fields if f not in [tuple(f.split('.')) for f in opt.exclude.split(',')]]
|
||||
else:
|
||||
_logger.error("--allfields option is set, ignoring --exclude option")
|
||||
|
||||
if opt.allfields:
|
||||
fields = self.get_all_fields()
|
||||
else:
|
||||
invalid_fields = [f for f in fields if not self.check_field(f[0], f[1])]
|
||||
if invalid_fields:
|
||||
_logger.error("Invalid fields: %s", ', '.join([f"{f[0]}.{f[1]}" for f in invalid_fields]))
|
||||
fields = [f for f in fields if f not in invalid_fields]
|
||||
|
||||
if not opt.unobfuscate and not opt.yes:
|
||||
self.confirm_not_secure()
|
||||
|
||||
_logger.info("Processing fields: %s", ', '.join([f"{f[0]}.{f[1]}" for f in fields]))
|
||||
tables = defaultdict(set)
|
||||
|
||||
for t, f in fields:
|
||||
if t[0:3] != 'ir_' and '.' not in t:
|
||||
tables[t].add(f)
|
||||
|
||||
if opt.unobfuscate:
|
||||
_logger.info("Unobfuscating datas")
|
||||
for table in tables:
|
||||
_logger.info("Unobfuscating table %s", table)
|
||||
self.convert_table(table, tables[table], opt.pwd, opt.pertablecommit, True)
|
||||
|
||||
if opt.vacuum:
|
||||
_logger.info("Vacuuming obfuscated tables")
|
||||
for table in tables:
|
||||
_logger.debug("Vacuuming table %s", table)
|
||||
self.cr.execute(sql.SQL("""VACUUM FULL {table}""").format(table=sql.Identifier(table)))
|
||||
self.clear_pwd()
|
||||
else:
|
||||
_logger.info("Obfuscating datas")
|
||||
self.set_pwd(opt.pwd)
|
||||
for table in tables:
|
||||
_logger.info("Obfuscating table %s", table)
|
||||
self.convert_table(table, tables[table], opt.pwd, opt.pertablecommit)
|
||||
|
||||
self.commit()
|
||||
else:
|
||||
self.rollback()
|
||||
|
||||
except Exception as e: # noqa: BLE001
|
||||
sys.exit("ERROR: %s" % e)
|
||||
129
odoo-bringout-oca-ocb-base/odoo/cli/populate.py
Normal file
129
odoo-bringout-oca-ocb-base/odoo/cli/populate.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import fnmatch
|
||||
import logging
|
||||
import optparse
|
||||
import sys
|
||||
import time
|
||||
from contextlib import nullcontext
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
import odoo
|
||||
|
||||
from . import Command
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Populate(Command):
|
||||
""" Inject fake data inside a database for testing """
|
||||
|
||||
def run(self, cmdargs):
|
||||
parser = odoo.tools.config.parser
|
||||
parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
||||
group = optparse.OptionGroup(parser, "Populate Configuration")
|
||||
group.add_option("--size", dest="population_size",
|
||||
help="Populate database with auto-generated data. Value should be the population size: small, medium or large",
|
||||
default='small')
|
||||
group.add_option("--models",
|
||||
dest='populate_models',
|
||||
help="Comma separated list of model or pattern (fnmatch)")
|
||||
group.add_option("--profile",
|
||||
dest='profiling_enabled', action="store_true",
|
||||
help="Specify if you want to profile records population.",
|
||||
default=False)
|
||||
group.add_option("--rollback",
|
||||
dest='populate_rollback', action="store_true",
|
||||
help="Specify if you want to rollback database population.",
|
||||
default=False)
|
||||
parser.add_option_group(group)
|
||||
opt = odoo.tools.config.parse_config(cmdargs)
|
||||
populate_models = opt.populate_models and set(opt.populate_models.split(','))
|
||||
dbname = odoo.tools.config['db_name']
|
||||
registry = odoo.registry(dbname)
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
self.populate(
|
||||
env, opt.population_size, populate_models,
|
||||
profiling_enabled=opt.profiling_enabled,
|
||||
commit=not opt.populate_rollback)
|
||||
|
||||
@classmethod
|
||||
def populate(cls, env, size, model_patterns=False, profiling_enabled=False, commit=True):
|
||||
registry = env.registry
|
||||
populated_models = None
|
||||
try:
|
||||
registry.populated_models = {} # todo master, initialize with already populated models
|
||||
ordered_models = cls._get_ordered_models(env, model_patterns)
|
||||
|
||||
_logger.log(25, 'Populating database')
|
||||
for model in ordered_models:
|
||||
if profiling_enabled:
|
||||
profiling_context = odoo.tools.profiler.Profiler(
|
||||
description=f'{model} {size}',
|
||||
db=env.cr.dbname
|
||||
)
|
||||
else:
|
||||
profiling_context = nullcontext()
|
||||
|
||||
if commit:
|
||||
commit_context = nullcontext()
|
||||
else:
|
||||
commit_context = patch('odoo.sql_db.Cursor.commit')
|
||||
|
||||
_logger.info('Populating database for model %s', model._name)
|
||||
t0 = time.time()
|
||||
|
||||
with profiling_context, commit_context:
|
||||
registry.populated_models[model._name] = model._populate(size).ids
|
||||
|
||||
if not registry.populated_models[model._name]:
|
||||
# Do not create ir.profile records
|
||||
# for models without any population factories
|
||||
profiling_context.db = False
|
||||
|
||||
# force the flush to make sure population time still
|
||||
# considers flushing all values to database
|
||||
env.flush_all()
|
||||
|
||||
if commit:
|
||||
env.cr.commit()
|
||||
|
||||
model_time = time.time() - t0
|
||||
if model_time > 1:
|
||||
_logger.info('Populated database for model %s (total: %fs) (average: %fms per record)',
|
||||
model._name, model_time, model_time / len(registry.populated_models[model._name]) * 1000)
|
||||
except:
|
||||
_logger.exception('Something went wrong populating database')
|
||||
finally:
|
||||
if not commit:
|
||||
env.cr.rollback()
|
||||
populated_models = registry.populated_models
|
||||
del registry.populated_models
|
||||
|
||||
return populated_models
|
||||
|
||||
@classmethod
|
||||
def _get_ordered_models(cls, env, model_patterns=False):
|
||||
_logger.info('Computing model order')
|
||||
processed = set()
|
||||
ordered_models = []
|
||||
visited = set()
|
||||
def add_model(model):
|
||||
if model not in processed:
|
||||
if model in visited:
|
||||
raise ValueError('Cyclic dependency detected for %s' % model)
|
||||
visited.add(model)
|
||||
for dep in model._populate_dependencies:
|
||||
add_model(env[dep])
|
||||
ordered_models.append(model)
|
||||
processed.add(model)
|
||||
for model in env.values():
|
||||
if model_patterns and not any(fnmatch.fnmatch(model._name, match) for match in model_patterns):
|
||||
continue
|
||||
if model._transient or model._abstract:
|
||||
continue
|
||||
ir_model = env['ir.model'].search([('model', '=', model._name)])
|
||||
if not model_patterns and all(module.startswith('test_') for module in ir_model.modules.split(',')):
|
||||
continue
|
||||
add_model(model)
|
||||
|
||||
return ordered_models
|
||||
136
odoo-bringout-oca-ocb-base/odoo/cli/scaffold.py
Normal file
136
odoo-bringout-oca-ocb-base/odoo/cli/scaffold.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# 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 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.add_argument(
|
||||
'-t', '--template', type=template, default=template('default'),
|
||||
help="Use a custom module template, can be a template name or the"
|
||||
" path to a module template (default: %(default)s)")
|
||||
parser.add_argument('name', help="Name of the module to create")
|
||||
parser.add_argument(
|
||||
'dest', default='.', nargs='?',
|
||||
help="Directory to create the module in (default: %(default)s)")
|
||||
|
||||
if not cmdargs:
|
||||
sys.exit(parser.print_help())
|
||||
args = parser.parse_args(args=cmdargs)
|
||||
|
||||
args.template.render_to(
|
||||
snake(args.name),
|
||||
directory(args.dest, create=True),
|
||||
{'name': args.name})
|
||||
|
||||
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__)),
|
||||
'templates',
|
||||
*args)
|
||||
|
||||
def snake(s):
|
||||
""" snake cases ``s``
|
||||
|
||||
:param str s:
|
||||
:return: str
|
||||
"""
|
||||
# insert a space before each uppercase character preceded by a
|
||||
# non-uppercase letter
|
||||
s = re.sub(r'(?<=[^A-Z])\B([A-Z])', r' \1', s)
|
||||
# lowercase everything, split on whitespace and join
|
||||
return '_'.join(s.lower().split())
|
||||
def pascal(s):
|
||||
return ''.join(
|
||||
ss.capitalize()
|
||||
for ss in re.sub(r'[_\s]+', ' ', s).split()
|
||||
)
|
||||
|
||||
def directory(p, create=False):
|
||||
expanded = os.path.abspath(
|
||||
os.path.expanduser(
|
||||
os.path.expandvars(p)))
|
||||
if create and not os.path.exists(expanded):
|
||||
os.makedirs(expanded)
|
||||
if not os.path.isdir(expanded):
|
||||
die("%s is not a directory" % p)
|
||||
return expanded
|
||||
|
||||
env = jinja2.Environment()
|
||||
env.filters['snake'] = snake
|
||||
env.filters['pascal'] = pascal
|
||||
class template(object):
|
||||
def __init__(self, identifier):
|
||||
# TODO: archives (zipfile, tarfile)
|
||||
self.id = identifier
|
||||
# is identifier a builtin?
|
||||
self.path = builtins(identifier)
|
||||
if os.path.isdir(self.path):
|
||||
return
|
||||
# is identifier a directory?
|
||||
self.path = identifier
|
||||
if os.path.isdir(self.path):
|
||||
return
|
||||
die("{} is not a valid module template".format(identifier))
|
||||
|
||||
def __str__(self):
|
||||
return self.id
|
||||
|
||||
def files(self):
|
||||
""" Lists the (local) path and content of all files in the template
|
||||
"""
|
||||
for root, _, files in os.walk(self.path):
|
||||
for f in files:
|
||||
path = os.path.join(root, f)
|
||||
yield path, open(path, 'rb').read()
|
||||
|
||||
def render_to(self, modname, directory, params=None):
|
||||
""" Render this module template to ``dest`` with the provided
|
||||
rendering parameters
|
||||
"""
|
||||
# overwrite with local
|
||||
for path, content in self.files():
|
||||
local = os.path.relpath(path, self.path)
|
||||
# strip .template extension
|
||||
root, ext = os.path.splitext(local)
|
||||
if ext == '.template':
|
||||
local = root
|
||||
dest = os.path.join(directory, modname, local)
|
||||
destdir = os.path.dirname(dest)
|
||||
if not os.path.exists(destdir):
|
||||
os.makedirs(destdir)
|
||||
|
||||
with open(dest, 'wb') as f:
|
||||
if ext not in ('.py', '.xml', '.csv', '.js', '.rst', '.html', '.template'):
|
||||
f.write(content)
|
||||
else:
|
||||
env.from_string(content.decode('utf-8'))\
|
||||
.stream(params or {})\
|
||||
.dump(f, encoding='utf-8')
|
||||
|
||||
def die(message, code=1):
|
||||
print(message, file=sys.stderr)
|
||||
sys.exit(code)
|
||||
|
||||
def warn(message):
|
||||
# ASK: shall we use logger ?
|
||||
print("WARNING:", message)
|
||||
187
odoo-bringout-oca-ocb-base/odoo/cli/server.py
Normal file
187
odoo-bringout-oca-ocb-base/odoo/cli/server.py
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
"""
|
||||
OpenERP - Server
|
||||
OpenERP is an ERP+CRM program for small and medium businesses.
|
||||
|
||||
The whole source code is distributed under the terms of the
|
||||
GNU Public Licence.
|
||||
|
||||
(c) 2003-TODAY, Fabien Pinckaers - OpenERP SA
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import csv # pylint: disable=deprecated-module
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from psycopg2 import ProgrammingError, errorcodes
|
||||
|
||||
import odoo
|
||||
|
||||
from . import Command
|
||||
|
||||
__author__ = odoo.release.author
|
||||
__version__ = odoo.release.version
|
||||
|
||||
# Also use the `odoo` logger for the main script.
|
||||
_logger = logging.getLogger('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")
|
||||
|
||||
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)
|
||||
|
||||
def report_configuration():
|
||||
""" Log the server version and some configuration values.
|
||||
|
||||
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)
|
||||
_logger.info('addons paths: %s', odoo.addons.__path__)
|
||||
if config.get('upgrade_path'):
|
||||
_logger.info('upgrade path: %s', config['upgrade_path'])
|
||||
if config.get('pre_upgrade_scripts'):
|
||||
_logger.info('extra upgrade scripts: %s', config['pre_upgrade_scripts'])
|
||||
host = config['db_host'] or os.environ.get('PGHOST', 'default')
|
||||
port = config['db_port'] or os.environ.get('PGPORT', 'default')
|
||||
user = config['db_user'] or os.environ.get('PGUSER', 'default')
|
||||
_logger.info('database: %s@%s:%s', user, host, port)
|
||||
if sys.version_info[:2] > odoo.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))
|
||||
)
|
||||
|
||||
def rm_pid_file(main_pid):
|
||||
config = odoo.tools.config
|
||||
if config['pidfile'] and main_pid == os.getpid():
|
||||
try:
|
||||
os.unlink(config['pidfile'])
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def setup_pid_file():
|
||||
""" Create a file with the process id written in it.
|
||||
|
||||
This function assumes the configuration has been initialized.
|
||||
"""
|
||||
config = odoo.tools.config
|
||||
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.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)
|
||||
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 ProgrammingError as err:
|
||||
if err.pgcode == errorcodes.INSUFFICIENT_PRIVILEGE:
|
||||
# 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)
|
||||
else:
|
||||
raise 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)
|
||||
|
||||
# This needs to be done now to ensure the use of the multiprocessing
|
||||
# signaling mechanism for registries loaded with -d
|
||||
if config['workers']:
|
||||
odoo.multi_process = True
|
||||
|
||||
stop = config["stop_after_init"]
|
||||
|
||||
setup_pid_file()
|
||||
rc = odoo.service.server.start(preload=preload, 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}'
|
||||
main(args)
|
||||
124
odoo-bringout-oca-ocb-base/odoo/cli/shell.py
Normal file
124
odoo-bringout-oca-ocb-base/odoo/cli/shell.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import code
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import odoo
|
||||
from odoo.tools import config
|
||||
from . import Command
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
"""
|
||||
Shell exit behaviors
|
||||
====================
|
||||
|
||||
Legend:
|
||||
stop = The REPL main loop stop.
|
||||
raise = Exception raised.
|
||||
loop = Stay in REPL.
|
||||
|
||||
Shell | ^D | exit() | quit() | sys.exit() | raise SystemExit()
|
||||
----------------------------------------------------------------------
|
||||
python | stop | raise | raise | raise | raise
|
||||
ipython | stop | stop | stop | loop | loop
|
||||
ptpython | stop | raise | raise | raise | raise
|
||||
bpython | stop | stop | stop | stop | stop
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def raise_keyboard_interrupt(*a):
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
|
||||
class Console(code.InteractiveConsole):
|
||||
def __init__(self, locals=None, filename="<console>"):
|
||||
code.InteractiveConsole.__init__(self, locals, 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.parse_and_bind("tab: complete")
|
||||
|
||||
|
||||
class Shell(Command):
|
||||
"""Start odoo in an interactive shell"""
|
||||
supported_shells = ['ipython', 'ptpython', 'bpython', 'python']
|
||||
|
||||
def init(self, args):
|
||||
config.parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
|
||||
config.parse_config(args)
|
||||
odoo.cli.server.report_configuration()
|
||||
odoo.service.server.start(preload=[], stop=True)
|
||||
signal.signal(signal.SIGINT, raise_keyboard_interrupt)
|
||||
|
||||
def console(self, local_vars):
|
||||
if not os.isatty(sys.stdin.fileno()):
|
||||
local_vars['__name__'] = '__main__'
|
||||
exec(sys.stdin.read(), local_vars)
|
||||
else:
|
||||
if 'env' not in local_vars:
|
||||
print('No environment set, use `%s shell -d dbname` to get one.' % sys.argv[0])
|
||||
for i in sorted(local_vars):
|
||||
print('%s: %s' % (i, local_vars[i]))
|
||||
|
||||
preferred_interface = config.options.get('shell_interface')
|
||||
if preferred_interface:
|
||||
shells_to_try = [preferred_interface, 'python']
|
||||
else:
|
||||
shells_to_try = self.supported_shells
|
||||
|
||||
for shell in shells_to_try:
|
||||
try:
|
||||
return getattr(self, shell)(local_vars)
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception:
|
||||
_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 ptpython(self, local_vars):
|
||||
from ptpython.repl import embed
|
||||
embed({}, local_vars)
|
||||
|
||||
def bpython(self, local_vars):
|
||||
from bpython import embed
|
||||
embed(local_vars)
|
||||
|
||||
def python(self, local_vars):
|
||||
Console(locals=local_vars).interact()
|
||||
|
||||
def shell(self, dbname):
|
||||
local_vars = {
|
||||
'openerp': odoo,
|
||||
'odoo': odoo,
|
||||
}
|
||||
if dbname:
|
||||
registry = odoo.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)
|
||||
local_vars['env'] = env
|
||||
local_vars['self'] = env.user
|
||||
self.console(local_vars)
|
||||
cr.rollback()
|
||||
else:
|
||||
self.console(local_vars)
|
||||
|
||||
def run(self, args):
|
||||
self.init(args)
|
||||
self.shell(config['db_name'])
|
||||
return 0
|
||||
84
odoo-bringout-oca-ocb-base/odoo/cli/start.py
Normal file
84
odoo-bringout-oca-ocb-base/odoo/cli/start.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# 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.service.db import _create_empty_database, DatabaseExists
|
||||
|
||||
|
||||
class Start(Command):
|
||||
""" Quickly start the odoo server with default options """
|
||||
|
||||
def get_module_list(self, path):
|
||||
mods = itertools.chain.from_iterable(
|
||||
glob.glob(os.path.join(path, '*/%s' % mname))
|
||||
for mname in MANIFEST_NAMES
|
||||
)
|
||||
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=".",
|
||||
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")
|
||||
|
||||
|
||||
args, unknown = 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:
|
||||
# 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
|
||||
project_path = os.path.abspath(os.path.join(project_path, os.pardir))
|
||||
|
||||
# check if one of the subfolders has at least one module
|
||||
mods = self.get_module_list(project_path)
|
||||
if mods and '--addons-path' not in cmdargs:
|
||||
cmdargs.append('--addons-path=%s' % project_path)
|
||||
|
||||
if not args.db_name:
|
||||
args.db_name = db_name or project_path.split(os.path.sep)[-1]
|
||||
cmdargs.extend(('-d', args.db_name))
|
||||
|
||||
# TODO: forbid some database names ? eg template1, ...
|
||||
try:
|
||||
_create_empty_database(args.db_name)
|
||||
odoo.tools.config['init']['base'] = True
|
||||
except DatabaseExists as e:
|
||||
pass
|
||||
except Exception as e:
|
||||
die("Could not create database `%s`. (%s)" % (args.db_name, e))
|
||||
|
||||
if '--db-filter' not in cmdargs:
|
||||
cmdargs.append('--db-filter=^%s$' % args.db_name)
|
||||
|
||||
# Remove --path /-p options from the command arguments
|
||||
def to_remove(i, l):
|
||||
return l[i] == '-p' or l[i].startswith('--path') or \
|
||||
(i > 0 and l[i-1] in ['-p', '--path'])
|
||||
cmdargs = [v for i, v in enumerate(cmdargs)
|
||||
if not to_remove(i, cmdargs)]
|
||||
|
||||
main(cmdargs)
|
||||
|
||||
def die(message, code=1):
|
||||
print(message, file=sys.stderr)
|
||||
sys.exit(code)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "{{ name }}",
|
||||
|
||||
'summary': """
|
||||
Short (1 phrase/line) summary of the module's purpose, used as
|
||||
subtitle on modules listing or apps.openerp.com""",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'author': "My Company",
|
||||
'website': "https://www.yourcompany.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Uncategorized',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
'views/views.xml',
|
||||
'views/templates.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{%- 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
|
||||
|
||||
|
||||
# class {{ mod|pascal }}(http.Controller):
|
||||
# @http.route('{{ root }}', auth='public')
|
||||
# def index(self, **kw):
|
||||
# return "Hello, world"
|
||||
|
||||
# @http.route('{{ root }}/objects', auth='public')
|
||||
# def list(self, **kw):
|
||||
# return http.request.render('{{ mod }}.listing', {
|
||||
# 'root': '{{ root }}',
|
||||
# 'objects': http.request.env['{{ model }}'].search([]),
|
||||
# })
|
||||
|
||||
# @http.route('{{ root }}/objects/<model("{{ model }}"):obj>', auth='public')
|
||||
# def object(self, obj, **kw):
|
||||
# return http.request.render('{{ mod }}.object', {
|
||||
# 'object': obj
|
||||
# })
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{%- set mod= name|snake -%}
|
||||
{%- set model = "%s.%s"|format(mod, mod) -%}
|
||||
<odoo>
|
||||
<data>
|
||||
<!--{% for item in range(5) %}
|
||||
<record id="object{{ item }}" model="{{ model }}">
|
||||
<field name="name">Object {{ item }}</field>
|
||||
<field name="value">{{ item * 10 }}</field>
|
||||
</record>
|
||||
{% endfor %}-->
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# from odoo import models, fields, api
|
||||
|
||||
|
||||
# class {{ name|snake }}(models.Model):
|
||||
# _name = '{{ name|snake }}.{{ name|snake }}'
|
||||
# _description = '{{ name|snake }}.{{ name|snake }}'
|
||||
|
||||
# name = fields.Char()
|
||||
# value = fields.Integer()
|
||||
# value2 = fields.Float(compute="_value_pc", store=True)
|
||||
# description = fields.Text()
|
||||
#
|
||||
# @api.depends('value')
|
||||
# def _value_pc(self):
|
||||
# for record in self:
|
||||
# record.value2 = float(record.value) / 100
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{%- set snake_name = name|snake -%}
|
||||
{%- set id = "access_%s_%s"|format(snake_name, snake_name) -%}
|
||||
{%- set name = "%s.%s"|format(snake_name, snake_name) -%}
|
||||
{%- set model_id = "model_%s_%s"|format(snake_name, snake_name) -%}
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
{{ id }},{{ name }},{{ model_id }},base.group_user,1,1,1,1
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<template id="listing">
|
||||
<ul>
|
||||
<li t-foreach="objects" t-as="object">
|
||||
<a t-attf-href="#{ root }/objects/#{ object.id }">
|
||||
<t t-esc="object.display_name"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template id="object">
|
||||
<h1><t t-esc="object.display_name"/></h1>
|
||||
<dl>
|
||||
<t t-foreach="object._fields" t-as="field">
|
||||
<dt><t t-esc="field"/></dt>
|
||||
<dd><t t-esc="object[field]"/></dd>
|
||||
</t>
|
||||
</dl>
|
||||
</template>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
{%- set mod = name|snake -%}
|
||||
{%- set model = "%s.%s"|format(mod, mod) -%}
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- explicit list view definition -->
|
||||
<!--
|
||||
<record model="ir.ui.view" id="{{mod}}.list">
|
||||
<field name="name">{{name}} list</field>
|
||||
<field name="model">{{model}}</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="value"/>
|
||||
<field name="value2"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- actions opening views on models -->
|
||||
<!--
|
||||
<record model="ir.actions.act_window" id="{{mod}}.action_window">
|
||||
<field name="name">{{name}} window</field>
|
||||
<field name="res_model">{{model}}</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- server action to the one above -->
|
||||
<!--
|
||||
<record model="ir.actions.server" id="{{mod}}.action_server">
|
||||
<field name="name">{{name}} server</field>
|
||||
<field name="model_id" ref="model_{{mod}}_{{mod}}"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
action = {
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": model._name,
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- Top menu item -->
|
||||
<!--
|
||||
<menuitem name="{{name}}" id="{{mod}}.menu_root"/>
|
||||
-->
|
||||
<!-- menu categories -->
|
||||
<!--
|
||||
<menuitem name="Menu 1" id="{{mod}}.menu_1" parent="{{mod}}.menu_root"/>
|
||||
<menuitem name="Menu 2" id="{{mod}}.menu_2" parent="{{mod}}.menu_root"/>
|
||||
-->
|
||||
<!-- actions -->
|
||||
<!--
|
||||
<menuitem name="List" id="{{mod}}.menu_1_list" parent="{{mod}}.menu_1"
|
||||
action="{{mod}}.action_window"/>
|
||||
<menuitem name="Server to list" id="{{mod}}" parent="{{mod}}.menu_2"
|
||||
action="{{mod}}.action_server"/>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
# Theme information
|
||||
'name': "{{name}}",
|
||||
'description': """
|
||||
""",
|
||||
'category': 'Theme',
|
||||
'version': '0.1',
|
||||
'depends': ['website'],
|
||||
|
||||
# templates
|
||||
'data': [
|
||||
'views/options.xml',
|
||||
'views/snippets.xml',
|
||||
],
|
||||
|
||||
# demo pages
|
||||
'demo': [
|
||||
'demo/pages.xml',
|
||||
],
|
||||
|
||||
# Your information
|
||||
'author': "My Company",
|
||||
'website': "https://www.yourcompany.com",
|
||||
}
|
||||
133
odoo-bringout-oca-ocb-base/odoo/cli/tsconfig.py
Normal file
133
odoo-bringout-oca-ocb-base/odoo/cli/tsconfig.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# 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",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue