mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 08:52:08 +02:00
18.0 vanilla
This commit is contained in:
parent
d72e748793
commit
0a7ae8db93
337 changed files with 399651 additions and 232598 deletions
|
|
@ -1,68 +0,0 @@
|
|||
from typing import Optional
|
||||
|
||||
import astroid
|
||||
from pylint import interfaces, checkers
|
||||
|
||||
try:
|
||||
from pylint.checkers.utils import only_required_for_messages
|
||||
except ImportError:
|
||||
from pylint.checkers.utils import check_messages as only_required_for_messages
|
||||
|
||||
|
||||
class OdooBaseChecker(checkers.BaseChecker):
|
||||
__implements__ = interfaces.IAstroidChecker
|
||||
name = 'odoo'
|
||||
|
||||
msgs = {
|
||||
'E8504': (
|
||||
'The Markup constructor called with a non-constant argument',
|
||||
'non-const-markup',
|
||||
'',
|
||||
)
|
||||
}
|
||||
|
||||
@only_required_for_messages('non-const-markup')
|
||||
def visit_call(self, node):
|
||||
if (isinstance(node.func, astroid.Name) and
|
||||
node.func.name == "Markup" and
|
||||
not self._is_constant(node.args[0])):
|
||||
self.add_message('non-const-markup', node=node, col_offset=len(node.as_string().split('\\n')))
|
||||
elif (isinstance(node.func, astroid.Attribute) and
|
||||
node.func.attrname == "Markup" and
|
||||
not self._is_constant(node.args[0])):
|
||||
self.add_message('non-const-markup', node=node, col_offset=len(node.as_string().split('\\n')))
|
||||
|
||||
def _is_constant(self, node: Optional[astroid.node_classes.NodeNG]) -> bool:
|
||||
if isinstance(node, astroid.Const) or node is None:
|
||||
return True
|
||||
elif isinstance(node, astroid.JoinedStr):
|
||||
return all(map(self._is_constant, node.values))
|
||||
elif isinstance(node, astroid.FormattedValue):
|
||||
return self._is_constant(node.value)
|
||||
elif isinstance(node, astroid.Name):
|
||||
_, assignments = node.lookup(node.name)
|
||||
return all(map(self._is_constant, assignments))
|
||||
elif isinstance(node, astroid.AssignName):
|
||||
return self._is_constant(node.parent)
|
||||
elif isinstance(node, astroid.Assign):
|
||||
return self._is_constant(node.value)
|
||||
elif (isinstance(node, astroid.Call) and
|
||||
isinstance(node.func, astroid.Attribute) and
|
||||
node.func.attrname in ["format", "join"]):
|
||||
return (self._is_constant(node.func.expr) and
|
||||
all(map(self._is_constant, node.args)) and
|
||||
all(map(self._is_constant, node.keywords)))
|
||||
elif isinstance(node, astroid.Keyword):
|
||||
return self._is_constant(node.value)
|
||||
elif isinstance(node, (astroid.List, astroid.Set, astroid.Tuple)):
|
||||
return all(map(self._is_constant, node.elts))
|
||||
elif isinstance(node, astroid.Dict):
|
||||
return all(map(self._is_constant, node.values))
|
||||
elif isinstance(node, astroid.BinOp):
|
||||
return self._is_constant(node.left) and self._is_constant(node.right)
|
||||
elif isinstance(node, astroid.IfExp):
|
||||
return self._is_constant(node.body) and self._is_constant(node.orelse)
|
||||
return False
|
||||
|
||||
def register(linter):
|
||||
linter.register_checker(OdooBaseChecker(linter))
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,13 +3,13 @@
|
|||
The module :mod:`odoo.tests.form` provides an implementation of a client form
|
||||
view for server-side unit tests.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import collections
|
||||
import itertools
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from lxml import etree
|
||||
|
||||
|
|
@ -96,15 +96,13 @@ class Form:
|
|||
the view in "creation" mode from default values, while a
|
||||
singleton will put it in "edit" mode and only load the
|
||||
view's data.
|
||||
:type record: odoo.models.Model
|
||||
:param view: the id, xmlid or actual view object to use for onchanges and
|
||||
view constraints. If none is provided, simply loads the
|
||||
default view for the model.
|
||||
:type view: int | str | odoo.model.Model
|
||||
|
||||
.. versionadded:: 12.0
|
||||
"""
|
||||
def __init__(self, record, view=None):
|
||||
def __init__(self, record: BaseModel, view: None | int | str | BaseModel = None) -> None:
|
||||
assert isinstance(record, BaseModel)
|
||||
assert len(record) <= 1
|
||||
|
||||
|
|
@ -123,7 +121,7 @@ class Form:
|
|||
|
||||
views = record.get_views([(view_id, 'form')])
|
||||
object.__setattr__(self, '_models_info', views['models'])
|
||||
# self._models_info = {model_name: {field_name: field_info}}
|
||||
# self._models_info = {model_name: {fields: {field_name: field_info}}}
|
||||
tree = etree.fromstring(views['views']['form']['arch'])
|
||||
view = self._process_view(tree, record)
|
||||
object.__setattr__(self, '_view', view)
|
||||
|
|
@ -143,6 +141,31 @@ class Form:
|
|||
else:
|
||||
self._init_from_defaults()
|
||||
|
||||
@classmethod
|
||||
def from_action(cls, env: odoo.api.Environment, action: dict) -> Form:
|
||||
assert action['type'] == 'ir.actions.act_window', \
|
||||
f"only window actions are valid, got {action['type']}"
|
||||
# ensure the first-requested view is a form view
|
||||
if views := action.get('views'):
|
||||
assert views[0][1] == 'form', \
|
||||
f"the actions dict should have a form as first view, got {views[0][1]}"
|
||||
view_id = views[0][0]
|
||||
else:
|
||||
view_mode = action.get('view_mode', '')
|
||||
if not view_mode.startswith('form'):
|
||||
raise ValueError(f"The actions dict should have a form first view mode, got {view_mode}")
|
||||
view_id = action.get('view_id')
|
||||
if view_id and ',' in view_mode:
|
||||
raise ValueError(f"A `view_id` is only valid if the action has a single `view_mode`, got {view_mode}")
|
||||
context = action.get('context', {})
|
||||
if isinstance(context, str):
|
||||
context = ast.literal_eval(context)
|
||||
record = env[action['res_model']]\
|
||||
.with_context(context)\
|
||||
.browse(action.get('res_id'))
|
||||
|
||||
return cls(record, view_id)
|
||||
|
||||
def _process_view(self, tree, model, level=2):
|
||||
""" Post-processes to augment the view_get with:
|
||||
* an id field (may not be present if not in the view but needed)
|
||||
|
|
@ -160,7 +183,7 @@ class Form:
|
|||
field_name = node.get('name')
|
||||
|
||||
# add field_info into fields
|
||||
field_info = self._models_info.get(model._name, {}).get(field_name) or {'type': None}
|
||||
field_info = self._models_info.get(model._name, {}).get("fields", {}).get(field_name) or {'type': None}
|
||||
fields[field_name] = field_info
|
||||
fields_spec[field_name] = field_spec = {}
|
||||
|
||||
|
|
@ -251,7 +274,7 @@ class Form:
|
|||
views = {
|
||||
view.tag: view for view in node.xpath('./*[descendant::field]')
|
||||
}
|
||||
for view_type in ['tree', 'form']:
|
||||
for view_type in ['list', 'form']:
|
||||
if view_type in views:
|
||||
continue
|
||||
if field_info['invisible'] == 'True':
|
||||
|
|
@ -263,14 +286,17 @@ class Form:
|
|||
subnode = etree.fromstring(subviews['views'][view_type]['arch'])
|
||||
views[view_type] = subnode
|
||||
node.append(subnode)
|
||||
for model_name, fields in subviews['models'].items():
|
||||
self._models_info.setdefault(model_name, {}).update(fields)
|
||||
for model_name, value in subviews['models'].items():
|
||||
model_info = self._models_info.setdefault(model_name, {})
|
||||
if "fields" not in model_info:
|
||||
model_info["fields"] = {}
|
||||
model_info["fields"].update(value["fields"])
|
||||
|
||||
# pick the first editable subview
|
||||
view_type = next(
|
||||
vtype for vtype in node.get('mode', 'tree').split(',') if vtype != 'form'
|
||||
vtype for vtype in node.get('mode', 'list').split(',') if vtype != 'form'
|
||||
)
|
||||
if not (view_type == 'tree' and views['tree'].get('editable')):
|
||||
if not (view_type == 'list' and views['list'].get('editable')):
|
||||
view_type = 'form'
|
||||
|
||||
# don't recursively process o2ms in o2ms
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import importlib
|
||||
import importlib.util
|
||||
import inspect
|
||||
import itertools
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import case
|
||||
|
||||
from .. import tools
|
||||
from .tag_selector import TagsSelector
|
||||
|
|
@ -13,9 +13,41 @@ from .suite import OdooSuite
|
|||
from .result import OdooTestResult
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_module_test_cases(module):
|
||||
"""Return a suite of all test cases contained in the given module"""
|
||||
for obj in module.__dict__.values():
|
||||
if not isinstance(obj, type):
|
||||
continue
|
||||
if not issubclass(obj, case.TestCase):
|
||||
continue
|
||||
if obj.__module__ != module.__name__:
|
||||
continue
|
||||
|
||||
test_case_class = obj
|
||||
test_cases = test_case_class.__dict__.items()
|
||||
if getattr(test_case_class, 'allow_inherited_tests_method', False):
|
||||
# keep iherited method for specific classes.
|
||||
# This is likely to be removed once a better solution is found
|
||||
test_cases = inspect.getmembers(test_case_class, callable)
|
||||
else:
|
||||
# sort test case to keep the initial behaviour.
|
||||
# This is likely to be removed in the future
|
||||
test_cases = sorted(test_cases, key=lambda pair: pair[0])
|
||||
|
||||
for method_name, method in test_cases:
|
||||
if not callable(method):
|
||||
continue
|
||||
if not method_name.startswith('test'):
|
||||
continue
|
||||
yield test_case_class(method_name)
|
||||
|
||||
|
||||
def get_test_modules(module):
|
||||
""" Return a list of module for the addons potentially containing tests to
|
||||
feed unittest.TestLoader.loadTestsFromModule() """
|
||||
feed get_module_test_cases() """
|
||||
results = _get_tests_modules(importlib.util.find_spec(f'odoo.addons.{module}'))
|
||||
results += list(_get_upgrade_test_modules(module))
|
||||
|
||||
|
|
@ -70,13 +102,13 @@ def make_suite(module_names, position='at_install'):
|
|||
t
|
||||
for module_name in module_names
|
||||
for m in get_test_modules(module_name)
|
||||
for t in unwrap_suite(unittest.TestLoader().loadTestsFromModule(m))
|
||||
for t in get_module_test_cases(m)
|
||||
if position_tag.check(t) and config_tags.check(t)
|
||||
)
|
||||
return OdooSuite(sorted(tests, key=lambda t: t.test_sequence))
|
||||
|
||||
|
||||
def run_suite(suite, module_name=None, global_report=None):
|
||||
def run_suite(suite, global_report=None):
|
||||
# avoid dependency hell
|
||||
from ..modules import module
|
||||
module.current_test = True
|
||||
|
|
@ -88,29 +120,3 @@ def run_suite(suite, module_name=None, global_report=None):
|
|||
threading.current_thread().testing = False
|
||||
module.current_test = False
|
||||
return results
|
||||
|
||||
|
||||
def unwrap_suite(test):
|
||||
"""
|
||||
Attempts to unpack testsuites (holding suites or cases) in order to
|
||||
generate a single stream of terminals (either test cases or customized
|
||||
test suites). These can then be checked for run/skip attributes
|
||||
individually.
|
||||
|
||||
An alternative would be to use a variant of @unittest.skipIf with a state
|
||||
flag of some sort e.g. @unittest.skipIf(common.runstate != 'at_install'),
|
||||
but then things become weird with post_install as tests should *not* run
|
||||
by default there
|
||||
"""
|
||||
if isinstance(test, unittest.TestCase):
|
||||
yield test
|
||||
return
|
||||
|
||||
subtests = list(test)
|
||||
## custom test suite (no test cases)
|
||||
#if not len(subtests):
|
||||
# yield test
|
||||
# return
|
||||
|
||||
for item in itertools.chain.from_iterable(unwrap_suite(t) for t in subtests):
|
||||
yield item
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ sys.path.append(os.path.abspath(os.path.join(__file__,'../../../')))
|
|||
|
||||
import odoo
|
||||
from odoo.tools import config, topological_sort, unique
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.netsvc import init_logger
|
||||
from odoo.tests import standalone_tests
|
||||
import odoo.tests.loader
|
||||
|
|
@ -23,11 +24,12 @@ BLACKLIST = {
|
|||
IGNORE = ('hw_', 'theme_', 'l10n_', 'test_')
|
||||
|
||||
INSTALL_BLACKLIST = {
|
||||
'payment_alipay', 'payment_ogone', 'payment_payulatam', 'payment_payumoney',
|
||||
'payment_alipay', 'payment_payulatam', 'payment_payumoney',
|
||||
} # deprecated modules (cannot be installed manually through button_install anymore)
|
||||
|
||||
|
||||
def install(db_name, module_id, module_name):
|
||||
with odoo.registry(db_name).cursor() as cr:
|
||||
with Registry(db_name).cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
module = env['ir.module.module'].browse(module_id)
|
||||
module.button_immediate_install()
|
||||
|
|
@ -35,7 +37,7 @@ def install(db_name, module_id, module_name):
|
|||
|
||||
|
||||
def uninstall(db_name, module_id, module_name):
|
||||
with odoo.registry(db_name).cursor() as cr:
|
||||
with Registry(db_name).cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
module = env['ir.module.module'].browse(module_id)
|
||||
module.button_immediate_uninstall()
|
||||
|
|
@ -124,7 +126,7 @@ class StandaloneAction(argparse.Action):
|
|||
|
||||
def test_cycle(args):
|
||||
""" Test full install/uninstall/reinstall cycle for all modules """
|
||||
with odoo.registry(args.database).cursor() as cr:
|
||||
with Registry(args.database).cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
|
||||
def valid(module):
|
||||
|
|
@ -159,7 +161,7 @@ def test_cycle(args):
|
|||
def test_uninstall(args):
|
||||
""" Tries to uninstall/reinstall one ore more modules"""
|
||||
for module_name in args.uninstall.split(','):
|
||||
with odoo.registry(args.database).cursor() as cr:
|
||||
with Registry(args.database).cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
module = env['ir.module.module'].search([('name', '=', module_name)])
|
||||
module_id, module_state = module.id, module.state
|
||||
|
|
@ -178,7 +180,7 @@ def test_standalone(args):
|
|||
""" Tries to launch standalone scripts tagged with @post_testing """
|
||||
odoo.service.db._check_faketime_mode(args.database) # noqa: SLF001
|
||||
# load the registry once for script discovery
|
||||
registry = odoo.registry(args.database)
|
||||
registry = Registry(args.database)
|
||||
for module_name in registry._init_modules:
|
||||
# import tests for loaded modules
|
||||
odoo.tests.loader.get_test_modules(module_name)
|
||||
|
|
@ -192,7 +194,7 @@ def test_standalone(args):
|
|||
|
||||
start_time = time.time()
|
||||
for index, func in enumerate(funcs, start=1):
|
||||
with odoo.registry(args.database).cursor() as cr:
|
||||
with Registry(args.database).cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
_logger.info("Executing standalone script: %s (%d / %d)",
|
||||
func.__name__, index, len(funcs))
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
from os import environ, path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
#This test is meant to be standalone, correct usage : python test_security.py file1 file2 file3 ...
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
HERE = path.dirname(__file__)
|
||||
|
||||
if 'PYTHONPATH' not in environ:
|
||||
environ['PYTHONPATH'] = HERE
|
||||
else:
|
||||
environ['PYTHONPATH'] += ':' + HERE
|
||||
|
||||
command = ['pylint', '--rcfile=/dev/null', '--disable=all', '--output-format', 'json', '--enable=non-const-markup', '--reports=n', '--load-plugins=_odoo_checker_markup', *sys.argv[1:]]
|
||||
|
||||
proc = subprocess.run(command, env=environ, check=True)
|
||||
Loading…
Add table
Add a link
Reference in a new issue