commit
007e51f931
|
|
@ -0,0 +1,291 @@
|
||||||
|
{
|
||||||
|
"globals": {
|
||||||
|
"$": false,
|
||||||
|
"_": false,
|
||||||
|
"fuzzy": false,
|
||||||
|
"jQuery": false,
|
||||||
|
"moment": false,
|
||||||
|
"odoo": false,
|
||||||
|
"openerp": false,
|
||||||
|
"self": false
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-alert": "warn",
|
||||||
|
"no-array-constructor": "warn",
|
||||||
|
"no-bitwise": "off",
|
||||||
|
"no-caller": "warn",
|
||||||
|
"no-case-declarations": "warn",
|
||||||
|
"no-catch-shadow": "warn",
|
||||||
|
"no-class-assign": "warn",
|
||||||
|
"no-cond-assign": "warn",
|
||||||
|
"no-confusing-arrow": "warn",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-const-assign": "warn",
|
||||||
|
"no-constant-condition": "warn",
|
||||||
|
"no-continue": "off",
|
||||||
|
"no-control-regex": "warn",
|
||||||
|
"no-debugger": "warn",
|
||||||
|
"no-delete-var": "warn",
|
||||||
|
"no-div-regex": "warn",
|
||||||
|
"no-dupe-args": "warn",
|
||||||
|
"no-dupe-class-members": "warn",
|
||||||
|
"no-dupe-keys": "warn",
|
||||||
|
"no-duplicate-case": "warn",
|
||||||
|
"no-duplicate-imports": "warn",
|
||||||
|
"no-else-return": "warn",
|
||||||
|
"no-empty": "warn",
|
||||||
|
"no-empty-character-class": "warn",
|
||||||
|
"no-empty-function": "warn",
|
||||||
|
"no-empty-pattern": "warn",
|
||||||
|
"no-eq-null": "warn",
|
||||||
|
"no-eval": "warn",
|
||||||
|
"no-ex-assign": "warn",
|
||||||
|
"no-extend-native": "warn",
|
||||||
|
"no-extra-bind": "warn",
|
||||||
|
"no-extra-boolean-cast": "warn",
|
||||||
|
"no-extra-label": "warn",
|
||||||
|
"no-extra-parens": "warn",
|
||||||
|
"no-extra-semi": "warn",
|
||||||
|
"no-fallthrough": "warn",
|
||||||
|
"no-floating-decimal": "warn",
|
||||||
|
"no-func-assign": "warn",
|
||||||
|
"no-implicit-coercion": ["warn", {
|
||||||
|
"allow": ["~"]
|
||||||
|
}],
|
||||||
|
"no-implicit-globals": "warn",
|
||||||
|
"no-implied-eval": "warn",
|
||||||
|
"no-inline-comments": "warn",
|
||||||
|
"no-inner-declarations": "warn",
|
||||||
|
"no-invalid-regexp": "warn",
|
||||||
|
"no-invalid-this": "off",
|
||||||
|
"no-irregular-whitespace": "warn",
|
||||||
|
"no-iterator": "warn",
|
||||||
|
"no-label-var": "warn",
|
||||||
|
"no-labels": "warn",
|
||||||
|
"no-lone-blocks": "warn",
|
||||||
|
"no-lonely-if": "warn",
|
||||||
|
"no-loop-func": "off",
|
||||||
|
"no-magic-numbers": "off",
|
||||||
|
"no-mixed-operators": "warn",
|
||||||
|
"no-mixed-requires": "warn",
|
||||||
|
"no-mixed-spaces-and-tabs": "warn",
|
||||||
|
"no-multi-spaces": "warn",
|
||||||
|
"no-multi-str": "warn",
|
||||||
|
"no-multiple-empty-lines": "warn",
|
||||||
|
"no-native-reassign": "warn",
|
||||||
|
"no-negated-condition": "warn",
|
||||||
|
"no-negated-in-lhs": "warn",
|
||||||
|
"no-nested-ternary": "off",
|
||||||
|
"no-new": "warn",
|
||||||
|
"no-new-func": "warn",
|
||||||
|
"no-new-object": "warn",
|
||||||
|
"no-new-require": "warn",
|
||||||
|
"no-new-symbol": "warn",
|
||||||
|
"no-new-wrappers": "warn",
|
||||||
|
"no-obj-calls": "warn",
|
||||||
|
"no-octal": "warn",
|
||||||
|
"no-octal-escape": "warn",
|
||||||
|
"no-param-reassign": "warn",
|
||||||
|
"no-path-concat": "warn",
|
||||||
|
"no-plusplus": "off",
|
||||||
|
"no-process-env": "warn",
|
||||||
|
"no-process-exit": "warn",
|
||||||
|
"no-proto": "warn",
|
||||||
|
"no-prototype-builtins": "warn",
|
||||||
|
"no-redeclare": "warn",
|
||||||
|
"no-regex-spaces": "warn",
|
||||||
|
"no-restricted-globals": "warn",
|
||||||
|
"no-restricted-imports": "warn",
|
||||||
|
"no-restricted-modules": "warn",
|
||||||
|
"no-restricted-syntax": "warn",
|
||||||
|
"no-return-assign": "warn",
|
||||||
|
"no-script-url": "warn",
|
||||||
|
"no-self-assign": "warn",
|
||||||
|
"no-self-compare": "warn",
|
||||||
|
"no-sequences": "warn",
|
||||||
|
"no-shadow": "warn",
|
||||||
|
"no-shadow-restricted-names": "warn",
|
||||||
|
"no-whitespace-before-property": "warn",
|
||||||
|
"no-spaced-func": "warn",
|
||||||
|
"no-sparse-arrays": "warn",
|
||||||
|
"no-sync": "warn",
|
||||||
|
"no-tabs": "warn",
|
||||||
|
"no-ternary": "off",
|
||||||
|
"no-trailing-spaces": "warn",
|
||||||
|
"no-this-before-super": "warn",
|
||||||
|
"no-throw-literal": "warn",
|
||||||
|
"no-undef": "warn",
|
||||||
|
"no-undef-init": "warn",
|
||||||
|
"no-undefined": "off",
|
||||||
|
"no-unexpected-multiline": "warn",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-unmodified-loop-condition": "warn",
|
||||||
|
"no-unneeded-ternary": "warn",
|
||||||
|
"no-unreachable": "warn",
|
||||||
|
"no-unsafe-finally": "warn",
|
||||||
|
"no-unused-expressions": "warn",
|
||||||
|
"no-unused-labels": "warn",
|
||||||
|
"no-unused-vars": "warn",
|
||||||
|
"no-use-before-define": "warn",
|
||||||
|
"no-useless-call": "warn",
|
||||||
|
"no-useless-computed-key": "warn",
|
||||||
|
"no-useless-concat": "warn",
|
||||||
|
"no-useless-constructor": "warn",
|
||||||
|
"no-useless-escape": "warn",
|
||||||
|
"no-useless-rename": "warn",
|
||||||
|
"no-void": "warn",
|
||||||
|
"no-var": "off",
|
||||||
|
"no-warning-comments": "off",
|
||||||
|
"no-with": "warn",
|
||||||
|
"array-bracket-spacing": "off",
|
||||||
|
"array-callback-return": "warn",
|
||||||
|
"arrow-body-style": "warn",
|
||||||
|
"arrow-parens": "warn",
|
||||||
|
"arrow-spacing": "off",
|
||||||
|
"accessor-pairs": "warn",
|
||||||
|
"block-scoped-var": "off",
|
||||||
|
"block-spacing": ["warn", "always"],
|
||||||
|
"brace-style": "warn",
|
||||||
|
"callback-return": "warn",
|
||||||
|
"camelcase": "off",
|
||||||
|
"capitalized-comments": ["warn", "always", {
|
||||||
|
"ignoreConsecutiveComments": true,
|
||||||
|
"ignoreInlineComments": true
|
||||||
|
}],
|
||||||
|
"comma-dangle": ["warn", "always-multiline"],
|
||||||
|
"comma-spacing": ["warn", {
|
||||||
|
"before": false,
|
||||||
|
"after": true
|
||||||
|
}],
|
||||||
|
"comma-style": "warn",
|
||||||
|
"complexity": [
|
||||||
|
"warn",
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"computed-property-spacing": "off",
|
||||||
|
"consistent-return": "off",
|
||||||
|
"consistent-this": "off",
|
||||||
|
"constructor-super": "warn",
|
||||||
|
"curly": "warn",
|
||||||
|
"default-case": "off",
|
||||||
|
"dot-location": ["warn", "property"],
|
||||||
|
"dot-notation": "warn",
|
||||||
|
"eol-last": "warn",
|
||||||
|
"eqeqeq": "warn",
|
||||||
|
"func-names": "off",
|
||||||
|
"func-style": "off",
|
||||||
|
"generator-star-spacing": "off",
|
||||||
|
"global-require": "warn",
|
||||||
|
"guard-for-in": "off",
|
||||||
|
"handle-callback-err": "warn",
|
||||||
|
"id-blacklist": "warn",
|
||||||
|
"id-length": "off",
|
||||||
|
"id-match": "warn",
|
||||||
|
"indent": "warn",
|
||||||
|
"init-declarations": "warn",
|
||||||
|
"jsx-quotes": "warn",
|
||||||
|
"key-spacing": "off",
|
||||||
|
"keyword-spacing": "warn",
|
||||||
|
"linebreak-style": [
|
||||||
|
"warn",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"lines-around-comment": "warn",
|
||||||
|
"max-depth": "warn",
|
||||||
|
"max-len": ["warn", {
|
||||||
|
"code": 88,
|
||||||
|
"ignorePattern": "odoo\\.define\\(",
|
||||||
|
"tabWidth": 4
|
||||||
|
}],
|
||||||
|
"max-lines": "off",
|
||||||
|
"max-nested-callbacks": "warn",
|
||||||
|
"max-params": "off",
|
||||||
|
"max-statements": "off",
|
||||||
|
"max-statements-per-line": "warn",
|
||||||
|
"multiline-ternary": "off",
|
||||||
|
"new-cap": "off",
|
||||||
|
"new-parens": "warn",
|
||||||
|
"newline-after-var": "off",
|
||||||
|
"newline-before-return": "off",
|
||||||
|
"newline-per-chained-call": "off",
|
||||||
|
"object-curly-newline": ["warn", { "consistent": true }],
|
||||||
|
"object-curly-spacing": ["warn", "never"],
|
||||||
|
"object-property-newline": ["warn", {
|
||||||
|
"allowAllPropertiesOnSameLine": true
|
||||||
|
}],
|
||||||
|
"object-shorthand": "off",
|
||||||
|
"one-var": "off",
|
||||||
|
"one-var-declaration-per-line": "off",
|
||||||
|
"operator-assignment": "warn",
|
||||||
|
"operator-linebreak": "warn",
|
||||||
|
"padded-blocks": "off",
|
||||||
|
"prefer-arrow-callback": "off",
|
||||||
|
"prefer-const": "warn",
|
||||||
|
"prefer-reflect": "off",
|
||||||
|
"prefer-rest-params": "off",
|
||||||
|
"prefer-spread": "off",
|
||||||
|
"prefer-template": "off",
|
||||||
|
"quote-props": "off",
|
||||||
|
"quotes": "off",
|
||||||
|
"radix": "warn",
|
||||||
|
"require-yield": "warn",
|
||||||
|
"rest-spread-spacing": "off",
|
||||||
|
"semi": [
|
||||||
|
"warn",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"semi-spacing": "warn",
|
||||||
|
"sort-imports": "warn",
|
||||||
|
"sort-vars": "off",
|
||||||
|
"space-before-blocks": "warn",
|
||||||
|
"space-before-function-paren": "warn",
|
||||||
|
"space-in-parens": "off",
|
||||||
|
"space-infix-ops": "off",
|
||||||
|
"space-unary-ops": "off",
|
||||||
|
"spaced-comment": ["warn", "always"],
|
||||||
|
"strict": ["warn", "function"],
|
||||||
|
"template-curly-spacing": "off",
|
||||||
|
"unicode-bom": "warn",
|
||||||
|
"use-isnan": "warn",
|
||||||
|
"valid-jsdoc": ["warn", {
|
||||||
|
"prefer": {
|
||||||
|
"arg": "param",
|
||||||
|
"argument": "param",
|
||||||
|
"augments": "extends",
|
||||||
|
"constructor": "class",
|
||||||
|
"exception": "throws",
|
||||||
|
"func": "function",
|
||||||
|
"method": "function",
|
||||||
|
"prop": "property",
|
||||||
|
"return": "returns",
|
||||||
|
"virtual": "abstract",
|
||||||
|
"yield": "yields"
|
||||||
|
},
|
||||||
|
"preferType": {
|
||||||
|
"array": "Array",
|
||||||
|
"bool": "Boolean",
|
||||||
|
"boolean": "Boolean",
|
||||||
|
"number": "Number",
|
||||||
|
"object": "Object",
|
||||||
|
"str": "String",
|
||||||
|
"string": "String"
|
||||||
|
},
|
||||||
|
"requireParamDescription": false,
|
||||||
|
"requireReturn": false,
|
||||||
|
"requireReturnDescription": false,
|
||||||
|
"requireReturnType": false
|
||||||
|
}],
|
||||||
|
"valid-typeof": "warn",
|
||||||
|
"vars-on-top": "off",
|
||||||
|
"wrap-iife": "warn",
|
||||||
|
"wrap-regex": "warn",
|
||||||
|
"yield-star-spacing": "off",
|
||||||
|
"yoda": "warn"
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2017
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
[flake8]
|
||||||
|
max-line-length = 80
|
||||||
|
max-complexity = 16
|
||||||
|
# B = bugbear
|
||||||
|
# B9 = bugbear opinionated (incl line length)
|
||||||
|
select = C,E,F,W,B,B9
|
||||||
|
# E203: whitespace before ':' (black behaviour)
|
||||||
|
# E501: flake8 line length (covered by bugbear B950)
|
||||||
|
# W503: line break before binary operator (black behaviour)
|
||||||
|
ignore = E203,E501,W503
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
[settings]
|
||||||
|
; see https://github.com/psf/black
|
||||||
|
multi_line_output=3
|
||||||
|
include_trailing_comma=True
|
||||||
|
force_grid_wrap=0
|
||||||
|
combine_as_imports=True
|
||||||
|
use_parentheses=True
|
||||||
|
line_length=88
|
||||||
|
known_odoo=odoo
|
||||||
|
known_odoo_addons=odoo.addons
|
||||||
|
sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER
|
||||||
|
known_third_party=lxml,setuptools
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
exclude: "^setup/|/static/lib/|/static/src/lib/"
|
||||||
|
default_language_version:
|
||||||
|
python: python3
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 19.3b0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v2.3.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
# exclude autogenerated files
|
||||||
|
exclude: /README\.rst$|\.pot?$
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
# exclude autogenerated files
|
||||||
|
exclude: /README\.rst$|\.pot?$
|
||||||
|
- id: debug-statements
|
||||||
|
- id: flake8
|
||||||
|
name: flake8 except __init__.py
|
||||||
|
exclude: /__init__\.py$
|
||||||
|
additional_dependencies: ["flake8-bugbear==19.8.0"]
|
||||||
|
- id: flake8
|
||||||
|
name: flake8 only __init__.py
|
||||||
|
args: ["--extend-ignore=F401"] # ignore unused imports in __init__.py
|
||||||
|
files: /__init__\.py$
|
||||||
|
additional_dependencies: ["flake8-bugbear==19.8.0"]
|
||||||
|
- id: fix-encoding-pragma
|
||||||
|
args: ["--remove"]
|
||||||
|
- id: check-case-conflict
|
||||||
|
- id: check-docstring-first
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: check-symlinks
|
||||||
|
- id: check-xml
|
||||||
|
- id: mixed-line-ending
|
||||||
|
args: ["--fix=lf"]
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-pylint
|
||||||
|
rev: v2.3.1
|
||||||
|
hooks:
|
||||||
|
- id: pylint
|
||||||
|
name: pylint with optional checks
|
||||||
|
args: ["--rcfile=.pylintrc", "--exit-zero"]
|
||||||
|
verbose: true
|
||||||
|
additional_dependencies: ["pylint-odoo==3.0.3"]
|
||||||
|
- id: pylint
|
||||||
|
name: pylint with mandatory checks
|
||||||
|
args: ["--rcfile=.pylintrc-mandatory"]
|
||||||
|
additional_dependencies: ["pylint-odoo==3.0.3"]
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v1.24.0
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
- repo: https://github.com/asottile/seed-isort-config
|
||||||
|
rev: v1.9.3
|
||||||
|
hooks:
|
||||||
|
- id: seed-isort-config
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-isort
|
||||||
|
rev: v4.3.21
|
||||||
|
hooks:
|
||||||
|
- id: isort
|
||||||
|
name: isort except __init__.py
|
||||||
|
exclude: /__init__\.py$
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||||
|
rev: v6.5.1
|
||||||
|
hooks:
|
||||||
|
- id: eslint
|
||||||
|
verbose: true
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
[MASTER]
|
||||||
|
load-plugins=pylint_odoo
|
||||||
|
score=n
|
||||||
|
|
||||||
|
[ODOOLINT]
|
||||||
|
readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
|
||||||
|
manifest_required_authors=Odoo Community Association (OCA)
|
||||||
|
manifest_required_keys=license
|
||||||
|
manifest_deprecated_keys=description,active
|
||||||
|
license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
|
||||||
|
valid_odoo_versions=13.0
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=all
|
||||||
|
|
||||||
|
# This .pylintrc contains optional AND mandatory checks and is meant to be
|
||||||
|
# loaded in an IDE to have it check everything, in the hope this will make
|
||||||
|
# optional checks more visible to contributors who otherwise never look at a
|
||||||
|
# green travis to see optional checks that failed.
|
||||||
|
# .pylintrc-mandatory containing only mandatory checks is used the pre-commit
|
||||||
|
# config as a blocking check.
|
||||||
|
|
||||||
|
enable=anomalous-backslash-in-string,
|
||||||
|
api-one-deprecated,
|
||||||
|
api-one-multi-together,
|
||||||
|
assignment-from-none,
|
||||||
|
attribute-deprecated,
|
||||||
|
class-camelcase,
|
||||||
|
dangerous-default-value,
|
||||||
|
dangerous-view-replace-wo-priority,
|
||||||
|
duplicate-id-csv,
|
||||||
|
duplicate-key,
|
||||||
|
duplicate-xml-fields,
|
||||||
|
duplicate-xml-record-id,
|
||||||
|
eval-referenced,
|
||||||
|
eval-used,
|
||||||
|
incoherent-interpreter-exec-perm,
|
||||||
|
license-allowed,
|
||||||
|
manifest-author-string,
|
||||||
|
manifest-deprecated-key,
|
||||||
|
manifest-required-author,
|
||||||
|
manifest-required-key,
|
||||||
|
manifest-version-format,
|
||||||
|
method-compute,
|
||||||
|
method-inverse,
|
||||||
|
method-required-super,
|
||||||
|
method-search,
|
||||||
|
missing-import-error,
|
||||||
|
missing-manifest-dependency,
|
||||||
|
openerp-exception-warning,
|
||||||
|
pointless-statement,
|
||||||
|
pointless-string-statement,
|
||||||
|
print-used,
|
||||||
|
redundant-keyword-arg,
|
||||||
|
redundant-modulename-xml,
|
||||||
|
reimported,
|
||||||
|
relative-import,
|
||||||
|
return-in-init,
|
||||||
|
rst-syntax-error,
|
||||||
|
sql-injection,
|
||||||
|
too-few-format-args,
|
||||||
|
translation-field,
|
||||||
|
translation-required,
|
||||||
|
unreachable,
|
||||||
|
use-vim-comment,
|
||||||
|
wrong-tabs-instead-of-spaces,
|
||||||
|
xml-syntax-error,
|
||||||
|
# messages that do not cause the lint step to fail
|
||||||
|
consider-merging-classes-inherited,
|
||||||
|
create-user-wo-reset-password,
|
||||||
|
dangerous-filter-wo-user,
|
||||||
|
deprecated-module,
|
||||||
|
file-not-used,
|
||||||
|
invalid-commit,
|
||||||
|
missing-newline-extrafiles,
|
||||||
|
missing-readme,
|
||||||
|
no-utf8-coding-comment,
|
||||||
|
odoo-addons-relative-import,
|
||||||
|
old-api7-method-defined,
|
||||||
|
redefined-builtin,
|
||||||
|
too-complex,
|
||||||
|
unnecessary-utf8-coding-comment
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
|
||||||
|
output-format=colorized
|
||||||
|
reports=no
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
[MASTER]
|
||||||
|
load-plugins=pylint_odoo
|
||||||
|
score=n
|
||||||
|
|
||||||
|
[ODOOLINT]
|
||||||
|
readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
|
||||||
|
manifest_required_authors=Odoo Community Association (OCA)
|
||||||
|
manifest_required_keys=license
|
||||||
|
manifest_deprecated_keys=description,active
|
||||||
|
license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
|
||||||
|
valid_odoo_versions=13.0
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=all
|
||||||
|
|
||||||
|
enable=anomalous-backslash-in-string,
|
||||||
|
api-one-deprecated,
|
||||||
|
api-one-multi-together,
|
||||||
|
assignment-from-none,
|
||||||
|
attribute-deprecated,
|
||||||
|
class-camelcase,
|
||||||
|
dangerous-default-value,
|
||||||
|
dangerous-view-replace-wo-priority,
|
||||||
|
duplicate-id-csv,
|
||||||
|
duplicate-key,
|
||||||
|
duplicate-xml-fields,
|
||||||
|
duplicate-xml-record-id,
|
||||||
|
eval-referenced,
|
||||||
|
eval-used,
|
||||||
|
incoherent-interpreter-exec-perm,
|
||||||
|
license-allowed,
|
||||||
|
manifest-author-string,
|
||||||
|
manifest-deprecated-key,
|
||||||
|
manifest-required-author,
|
||||||
|
manifest-required-key,
|
||||||
|
manifest-version-format,
|
||||||
|
method-compute,
|
||||||
|
method-inverse,
|
||||||
|
method-required-super,
|
||||||
|
method-search,
|
||||||
|
missing-import-error,
|
||||||
|
missing-manifest-dependency,
|
||||||
|
openerp-exception-warning,
|
||||||
|
pointless-statement,
|
||||||
|
pointless-string-statement,
|
||||||
|
print-used,
|
||||||
|
redundant-keyword-arg,
|
||||||
|
redundant-modulename-xml,
|
||||||
|
reimported,
|
||||||
|
relative-import,
|
||||||
|
return-in-init,
|
||||||
|
rst-syntax-error,
|
||||||
|
sql-injection,
|
||||||
|
too-few-format-args,
|
||||||
|
translation-field,
|
||||||
|
translation-required,
|
||||||
|
unreachable,
|
||||||
|
use-vim-comment,
|
||||||
|
wrong-tabs-instead-of-spaces,
|
||||||
|
xml-syntax-error
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
|
||||||
|
output-format=colorized
|
||||||
|
reports=no
|
||||||
37
.travis.yml
37
.travis.yml
|
|
@ -1,36 +1,47 @@
|
||||||
language: python
|
language: python
|
||||||
sudo: false
|
cache:
|
||||||
cache: pip
|
directories:
|
||||||
|
- $HOME/.cache/pip
|
||||||
|
- $HOME/.cache/pre-commit
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- "3.5"
|
- "3.6"
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
postgresql: "9.6"
|
postgresql: "9.6"
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
- expect-dev # provides unbuffer utility
|
- expect-dev # provides unbuffer utility
|
||||||
- python-lxml # because pip installation is slow
|
|
||||||
|
stages:
|
||||||
|
- linting
|
||||||
|
- test
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage: linting
|
||||||
|
name: "pre-commit"
|
||||||
|
before_install:
|
||||||
|
install: pip install pre-commit
|
||||||
|
script: pre-commit run --all --show-diff-on-failure
|
||||||
|
after_success:
|
||||||
|
- stage: test
|
||||||
|
env:
|
||||||
|
- TESTS="1" ODOO_REPO="odoo/odoo" MAKEPOT="1"
|
||||||
|
- stage: test
|
||||||
|
env:
|
||||||
|
- TESTS="1" ODOO_REPO="OCA/OCB"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- VERSION="13.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0"
|
- VERSION="13.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0"
|
||||||
|
|
||||||
matrix:
|
|
||||||
- LINT_CHECK="1"
|
|
||||||
- TESTS="1" ODOO_REPO="OCA/OCB"
|
|
||||||
- TESTS="1" ODOO_REPO="odoo/odoo" MAKEPOT="1"
|
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install -q unidecode
|
|
||||||
- pip install unicodecsv
|
|
||||||
- git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
|
- git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
|
||||||
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
|
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
|
||||||
- travis_install_nightly
|
- travis_install_nightly
|
||||||
# Requirements to test server_environment modules
|
# Requirements to test server_environment modules
|
||||||
- printf '[options]\n\nrunning_env = dev\n' > ${HOME}/.openerp_serverrc
|
- printf '[options]\n\nrunning_env = dev\n' > ${HOME}/.openerp_serverrc
|
||||||
- ln -s ${TRAVIS_BUILD_DIR}/server_environment_files_sample ${TRAVIS_BUILD_DIR}/server_environment_files
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- travis_run_tests
|
- travis_run_tests
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,12 @@
|
||||||
{
|
{
|
||||||
"name": "server configuration environment files",
|
"name": "server configuration environment files",
|
||||||
"version": "13.0.2.0.0",
|
"version": "13.0.2.0.0",
|
||||||
"depends": [
|
"depends": ["base", "base_sparse_field"],
|
||||||
"base",
|
|
||||||
"base_sparse_field",
|
|
||||||
],
|
|
||||||
"author": "Camptocamp,Odoo Community Association (OCA)",
|
"author": "Camptocamp,Odoo Community Association (OCA)",
|
||||||
"summary": "move some configurations out of the database",
|
"summary": "move some configurations out of the database",
|
||||||
"website": "http://github.com/OCA/server-env",
|
"website": "http://github.com/OCA/server-env",
|
||||||
"license": "GPL-3 or any later version",
|
"license": "GPL-3 or any later version",
|
||||||
"category": "Tools",
|
"category": "Tools",
|
||||||
"data": [
|
"data": ["security/res_groups.xml", "serv_config.xml"],
|
||||||
'security/res_groups.xml',
|
"installable": True,
|
||||||
'serv_config.xml',
|
|
||||||
],
|
|
||||||
'installable': True,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
# License GPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License GPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from functools import partialmethod
|
from functools import partialmethod
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
from ..server_env import serv_config
|
from ..server_env import serv_config
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
@ -96,19 +96,20 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
``keychain.backend``.
|
``keychain.backend``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
_name = 'server.env.mixin'
|
|
||||||
_description = 'Mixin to add server environment in existing models'
|
_name = "server.env.mixin"
|
||||||
|
_description = "Mixin to add server environment in existing models"
|
||||||
|
|
||||||
server_env_defaults = fields.Serialized()
|
server_env_defaults = fields.Serialized()
|
||||||
|
|
||||||
_server_env_getter_mapping = {
|
_server_env_getter_mapping = {
|
||||||
'integer': 'getint',
|
"integer": "getint",
|
||||||
'float': 'getfloat',
|
"float": "getfloat",
|
||||||
'monetary': 'getfloat',
|
"monetary": "getfloat",
|
||||||
'boolean': 'getboolean',
|
"boolean": "getboolean",
|
||||||
'char': 'get',
|
"char": "get",
|
||||||
'selection': 'get',
|
"selection": "get",
|
||||||
'text': 'get',
|
"text": "get",
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -180,16 +181,13 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
# _server_env_has_key_defined so we are sure that the value is
|
# _server_env_has_key_defined so we are sure that the value is
|
||||||
# either in the global or the record config
|
# either in the global or the record config
|
||||||
getter = getattr(serv_config, config_getter)
|
getter = getattr(serv_config, config_getter)
|
||||||
if (section_name in serv_config
|
if section_name in serv_config and field_name in serv_config[section_name]:
|
||||||
and field_name in serv_config[section_name]):
|
|
||||||
value = getter(section_name, field_name)
|
value = getter(section_name, field_name)
|
||||||
else:
|
else:
|
||||||
value = getter(global_section_name, field_name)
|
value = getter(global_section_name, field_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
_logger.exception(
|
_logger.exception(
|
||||||
"error trying to read field %s in section %s",
|
"error trying to read field %s in section %s", field_name, section_name
|
||||||
field_name,
|
|
||||||
section_name,
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return value
|
return value
|
||||||
|
|
@ -203,34 +201,31 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
and field_name in serv_config[global_section_name]
|
and field_name in serv_config[global_section_name]
|
||||||
)
|
)
|
||||||
has_config = (
|
has_config = (
|
||||||
section_name in serv_config
|
section_name in serv_config and field_name in serv_config[section_name]
|
||||||
and field_name in serv_config[section_name]
|
|
||||||
)
|
)
|
||||||
return has_global_config or has_config
|
return has_global_config or has_config
|
||||||
|
|
||||||
def _compute_server_env_from_config(self, field_name, options):
|
def _compute_server_env_from_config(self, field_name, options):
|
||||||
getter_name = options.get('getter') if options else None
|
getter_name = options.get("getter") if options else None
|
||||||
if not getter_name:
|
if not getter_name:
|
||||||
field_type = self._fields[field_name].type
|
field_type = self._fields[field_name].type
|
||||||
getter_name = self._server_env_getter_mapping.get(field_type)
|
getter_name = self._server_env_getter_mapping.get(field_type)
|
||||||
if not getter_name:
|
if not getter_name:
|
||||||
# if you get this message and the field is working as expected,
|
# if you get this message and the field is working as expected,
|
||||||
# you may want to add the type in _server_env_getter_mapping
|
# you may want to add the type in _server_env_getter_mapping
|
||||||
_logger.warning('server.env.mixin is used on a field of type %s, '
|
_logger.warning(
|
||||||
'which may not be supported properly')
|
"server.env.mixin is used on a field of type %s, "
|
||||||
getter_name = 'get'
|
"which may not be supported properly"
|
||||||
value = self._server_env_read_from_config(
|
)
|
||||||
field_name, getter_name
|
getter_name = "get"
|
||||||
)
|
value = self._server_env_read_from_config(field_name, getter_name)
|
||||||
self[field_name] = value
|
self[field_name] = value
|
||||||
|
|
||||||
def _compute_server_env_from_default(self, field_name, options):
|
def _compute_server_env_from_default(self, field_name, options):
|
||||||
if options and options.get('compute_default'):
|
if options and options.get("compute_default"):
|
||||||
getattr(self, options['compute_default'])()
|
getattr(self, options["compute_default"])()
|
||||||
else:
|
else:
|
||||||
default_field = self._server_env_default_fieldname(
|
default_field = self._server_env_default_fieldname(field_name)
|
||||||
field_name
|
|
||||||
)
|
|
||||||
if default_field:
|
if default_field:
|
||||||
self[field_name] = self[default_field]
|
self[field_name] = self[default_field]
|
||||||
else:
|
else:
|
||||||
|
|
@ -248,9 +243,7 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
record._compute_server_env_from_config(field_name, options)
|
record._compute_server_env_from_config(field_name, options)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
record._compute_server_env_from_default(
|
record._compute_server_env_from_default(field_name, options)
|
||||||
field_name, options
|
|
||||||
)
|
|
||||||
|
|
||||||
def _inverse_server_env(self, field_name):
|
def _inverse_server_env(self, field_name):
|
||||||
options = self._server_env_fields[field_name]
|
options = self._server_env_fields[field_name]
|
||||||
|
|
@ -262,8 +255,8 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
# we update the default value in database
|
# we update the default value in database
|
||||||
|
|
||||||
if record[is_editable_field]:
|
if record[is_editable_field]:
|
||||||
if options and options.get('inverse_default'):
|
if options and options.get("inverse_default"):
|
||||||
getattr(record, options['inverse_default'])()
|
getattr(record, options["inverse_default"])()
|
||||||
elif default_field:
|
elif default_field:
|
||||||
record[default_field] = record[field_name]
|
record[default_field] = record[field_name]
|
||||||
|
|
||||||
|
|
@ -278,12 +271,8 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
# in ``_inverse_server_env`` it would reset the value of the field
|
# in ``_inverse_server_env`` it would reset the value of the field
|
||||||
for record in self:
|
for record in self:
|
||||||
for field_name in self._server_env_fields:
|
for field_name in self._server_env_fields:
|
||||||
is_editable_field = self._server_env_is_editable_fieldname(
|
is_editable_field = self._server_env_is_editable_fieldname(field_name)
|
||||||
field_name
|
is_editable = not record._server_env_has_key_defined(field_name)
|
||||||
)
|
|
||||||
is_editable = not record._server_env_has_key_defined(
|
|
||||||
field_name
|
|
||||||
)
|
|
||||||
record[is_editable_field] = is_editable
|
record[is_editable_field] = is_editable
|
||||||
|
|
||||||
def _server_env_view_set_readonly(self, view_arch):
|
def _server_env_view_set_readonly(self, view_arch):
|
||||||
|
|
@ -293,37 +282,32 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
for elem in view_arch.findall(field_xpath % field):
|
for elem in view_arch.findall(field_xpath % field):
|
||||||
# set env-computed fields to readonly if the configuration
|
# set env-computed fields to readonly if the configuration
|
||||||
# files have a key set for this field
|
# files have a key set for this field
|
||||||
elem.set('attrs',
|
elem.set("attrs", str({"readonly": [(is_editable_field, "=", False)]}))
|
||||||
str({'readonly': [(is_editable_field, '=', False)]}))
|
|
||||||
if not view_arch.findall(field_xpath % is_editable_field):
|
if not view_arch.findall(field_xpath % is_editable_field):
|
||||||
# add the _is_editable fields in the view for the 'attrs'
|
# add the _is_editable fields in the view for the 'attrs'
|
||||||
# domain
|
# domain
|
||||||
view_arch.append(
|
view_arch.append(
|
||||||
etree.Element(
|
etree.Element("field", name=is_editable_field, invisible="1")
|
||||||
'field',
|
|
||||||
name=is_editable_field,
|
|
||||||
invisible="1"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return view_arch
|
return view_arch
|
||||||
|
|
||||||
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False,
|
def _fields_view_get(
|
||||||
submenu=False):
|
self, view_id=None, view_type="form", toolbar=False, submenu=False
|
||||||
|
):
|
||||||
view_data = super()._fields_view_get(
|
view_data = super()._fields_view_get(
|
||||||
view_id=view_id, view_type=view_type,
|
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
|
||||||
toolbar=toolbar, submenu=submenu
|
|
||||||
)
|
)
|
||||||
view_arch = etree.fromstring(view_data['arch'].encode('utf-8'))
|
view_arch = etree.fromstring(view_data["arch"].encode("utf-8"))
|
||||||
view_arch = self._server_env_view_set_readonly(view_arch)
|
view_arch = self._server_env_view_set_readonly(view_arch)
|
||||||
view_data['arch'] = etree.tostring(view_arch, encoding='unicode')
|
view_data["arch"] = etree.tostring(view_arch, encoding="unicode")
|
||||||
return view_data
|
return view_data
|
||||||
|
|
||||||
def _server_env_default_fieldname(self, base_field_name):
|
def _server_env_default_fieldname(self, base_field_name):
|
||||||
"""Return the name of the field with default value"""
|
"""Return the name of the field with default value"""
|
||||||
options = self._server_env_fields[base_field_name]
|
options = self._server_env_fields[base_field_name]
|
||||||
if options and options.get('no_default_field'):
|
if options and options.get("no_default_field"):
|
||||||
return ''
|
return ""
|
||||||
return '%s_env_default' % (base_field_name,)
|
return "{}_env_default".format(base_field_name)
|
||||||
|
|
||||||
def _server_env_is_editable_fieldname(self, base_field_name):
|
def _server_env_is_editable_fieldname(self, base_field_name):
|
||||||
"""Return the name of the field for "is editable"
|
"""Return the name of the field for "is editable"
|
||||||
|
|
@ -331,16 +315,14 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
This is the field used to tell if the env-computed field can
|
This is the field used to tell if the env-computed field can
|
||||||
be edited.
|
be edited.
|
||||||
"""
|
"""
|
||||||
return '%s_env_is_editable' % (base_field_name,)
|
return "{}_env_is_editable".format(base_field_name)
|
||||||
|
|
||||||
def _server_env_transform_field_to_read_from_env(self, field):
|
def _server_env_transform_field_to_read_from_env(self, field):
|
||||||
"""Transform the original field in a computed field"""
|
"""Transform the original field in a computed field"""
|
||||||
field.compute = '_compute_server_env'
|
field.compute = "_compute_server_env"
|
||||||
|
|
||||||
inverse_method_name = '_inverse_server_env_%s' % field.name
|
inverse_method_name = "_inverse_server_env_%s" % field.name
|
||||||
inverse_method = partialmethod(
|
inverse_method = partialmethod(type(self)._inverse_server_env, field.name)
|
||||||
type(self)._inverse_server_env, field.name
|
|
||||||
)
|
|
||||||
setattr(type(self), inverse_method_name, inverse_method)
|
setattr(type(self), inverse_method_name, inverse_method)
|
||||||
field.inverse = inverse_method_name
|
field.inverse = inverse_method_name
|
||||||
field.store = False
|
field.store = False
|
||||||
|
|
@ -360,7 +342,7 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
# (inherits), we want to override it with a new one
|
# (inherits), we want to override it with a new one
|
||||||
if fieldname not in self._fields or self._fields[fieldname].inherited:
|
if fieldname not in self._fields or self._fields[fieldname].inherited:
|
||||||
field = fields.Boolean(
|
field = fields.Boolean(
|
||||||
compute='_compute_server_env_is_editable',
|
compute="_compute_server_env_is_editable",
|
||||||
automatic=True,
|
automatic=True,
|
||||||
# this is required to be able to edit fields
|
# this is required to be able to edit fields
|
||||||
# on new records
|
# on new records
|
||||||
|
|
@ -385,14 +367,11 @@ class ServerEnvMixin(models.AbstractModel):
|
||||||
if fieldname not in self._fields or self._fields[fieldname].inherited:
|
if fieldname not in self._fields or self._fields[fieldname].inherited:
|
||||||
base_field_cls = base_field.__class__
|
base_field_cls = base_field.__class__
|
||||||
field_args = base_field.args.copy()
|
field_args = base_field.args.copy()
|
||||||
field_args.pop('_sequence', None)
|
field_args.pop("_sequence", None)
|
||||||
field_args.update({
|
field_args.update({"sparse": "server_env_defaults", "automatic": True})
|
||||||
'sparse': 'server_env_defaults',
|
|
||||||
'automatic': True,
|
|
||||||
})
|
|
||||||
|
|
||||||
if hasattr(base_field, 'selection'):
|
if hasattr(base_field, "selection"):
|
||||||
field_args['selection'] = base_field.selection
|
field_args["selection"] = base_field.selection
|
||||||
field = base_field_cls(**field_args)
|
field = base_field_cls(**field_args)
|
||||||
self._add_field(fieldname, field)
|
self._add_field(fieldname, field)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,14 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
import configparser
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import configparser
|
|
||||||
from lxml import etree
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from odoo import api, models, fields
|
from lxml import etree
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
from odoo.tools.config import config as system_base_config
|
from odoo.tools.config import config as system_base_config
|
||||||
|
|
||||||
from .system_info import get_server_environment
|
from .system_info import get_server_environment
|
||||||
|
|
@ -33,19 +34,29 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from odoo.addons import server_environment_files
|
from odoo.addons import server_environment_files
|
||||||
|
|
||||||
_dir = os.path.dirname(server_environment_files.__file__)
|
_dir = os.path.dirname(server_environment_files.__file__)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
_logger.info('not using server_environment_files for configuration,'
|
_logger.info(
|
||||||
' no directory found')
|
"not using server_environment_files for configuration," " no directory found"
|
||||||
|
)
|
||||||
_dir = None
|
_dir = None
|
||||||
|
|
||||||
ENV_VAR_NAMES = ('SERVER_ENV_CONFIG', 'SERVER_ENV_CONFIG_SECRET')
|
ENV_VAR_NAMES = ("SERVER_ENV_CONFIG", "SERVER_ENV_CONFIG_SECRET")
|
||||||
|
|
||||||
# Same dict as RawConfigParser._boolean_states
|
# Same dict as RawConfigParser._boolean_states
|
||||||
_boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
|
_boolean_states = {
|
||||||
'0': False, 'no': False, 'false': False, 'off': False}
|
"1": True,
|
||||||
|
"yes": True,
|
||||||
|
"true": True,
|
||||||
|
"on": True,
|
||||||
|
"0": False,
|
||||||
|
"no": False,
|
||||||
|
"false": False,
|
||||||
|
"off": False,
|
||||||
|
}
|
||||||
|
|
||||||
if not system_base_config.get('running_env', False):
|
if not system_base_config.get("running_env", False):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"The parameter 'running_env' has not be set neither in base config "
|
"The parameter 'running_env' has not be set neither in base config "
|
||||||
"file option -c or in openerprc.\n"
|
"file option -c or in openerprc.\n"
|
||||||
|
|
@ -56,7 +67,7 @@ if not system_base_config.get('running_env', False):
|
||||||
|
|
||||||
ck_path = None
|
ck_path = None
|
||||||
if _dir:
|
if _dir:
|
||||||
ck_path = os.path.join(_dir, system_base_config['running_env'])
|
ck_path = os.path.join(_dir, system_base_config["running_env"])
|
||||||
|
|
||||||
if not os.path.exists(ck_path):
|
if not os.path.exists(ck_path):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
|
@ -77,25 +88,29 @@ def setboolean(obj, attr, _bool=None):
|
||||||
# Borrowed from MarkupSafe
|
# Borrowed from MarkupSafe
|
||||||
def _escape(s):
|
def _escape(s):
|
||||||
"""Convert the characters &<>'" in string s to HTML-safe sequences."""
|
"""Convert the characters &<>'" in string s to HTML-safe sequences."""
|
||||||
return (str(s).replace('&', '&')
|
return (
|
||||||
.replace('>', '>')
|
str(s)
|
||||||
.replace('<', '<')
|
.replace("&", "&")
|
||||||
.replace("'", ''')
|
.replace(">", ">")
|
||||||
.replace('"', '"'))
|
.replace("<", "<")
|
||||||
|
.replace("'", "'")
|
||||||
|
.replace('"', """)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _listconf(env_path):
|
def _listconf(env_path):
|
||||||
"""List configuration files in a folder."""
|
"""List configuration files in a folder."""
|
||||||
files = [os.path.join(env_path, name)
|
files = [
|
||||||
for name in sorted(os.listdir(env_path))
|
os.path.join(env_path, name)
|
||||||
if name.endswith('.conf')]
|
for name in sorted(os.listdir(env_path))
|
||||||
|
if name.endswith(".conf")
|
||||||
|
]
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
|
||||||
def _load_config_from_server_env_files(config_p):
|
def _load_config_from_server_env_files(config_p):
|
||||||
default = os.path.join(_dir, 'default')
|
default = os.path.join(_dir, "default")
|
||||||
running_env = os.path.join(_dir,
|
running_env = os.path.join(_dir, system_base_config["running_env"])
|
||||||
system_base_config['running_env'])
|
|
||||||
if os.path.isdir(default):
|
if os.path.isdir(default):
|
||||||
conf_files = _listconf(default) + _listconf(running_env)
|
conf_files = _listconf(default) + _listconf(running_env)
|
||||||
else:
|
else:
|
||||||
|
|
@ -104,12 +119,12 @@ def _load_config_from_server_env_files(config_p):
|
||||||
try:
|
try:
|
||||||
config_p.read(conf_files)
|
config_p.read(conf_files)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception('Cannot read config files "%s": %s' % (conf_files, e))
|
raise Exception('Cannot read config files "{}": {}'.format(conf_files, e))
|
||||||
|
|
||||||
|
|
||||||
def _load_config_from_rcfile(config_p):
|
def _load_config_from_rcfile(config_p):
|
||||||
config_p.read(system_base_config.rcfile)
|
config_p.read(system_base_config.rcfile)
|
||||||
config_p.remove_section('options')
|
config_p.remove_section("options")
|
||||||
|
|
||||||
|
|
||||||
def _load_config_from_env(config_p):
|
def _load_config_from_env(config_p):
|
||||||
|
|
@ -120,8 +135,7 @@ def _load_config_from_env(config_p):
|
||||||
config_p.read_string(env_config)
|
config_p.read_string(env_config)
|
||||||
except configparser.Error as err:
|
except configparser.Error as err:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'%s content could not be parsed: %s'
|
"{} content could not be parsed: {}".format(varname, err)
|
||||||
% (varname, err,)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -147,13 +161,15 @@ class _Defaults(dict):
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
def func(*a):
|
def func(*a):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
return dict.__setitem__(self, key, func)
|
return dict.__setitem__(self, key, func)
|
||||||
|
|
||||||
|
|
||||||
class ServerConfiguration(models.TransientModel):
|
class ServerConfiguration(models.TransientModel):
|
||||||
"""Display server configuration."""
|
"""Display server configuration."""
|
||||||
_name = 'server.config'
|
|
||||||
_description = 'Display server configuration'
|
_name = "server.config"
|
||||||
|
_description = "Display server configuration"
|
||||||
_conf_defaults = _Defaults()
|
_conf_defaults = _Defaults()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -164,20 +180,20 @@ class ServerConfiguration(models.TransientModel):
|
||||||
"""
|
"""
|
||||||
ModelClass = super(ServerConfiguration, cls)._build_model(pool, cr)
|
ModelClass = super(ServerConfiguration, cls)._build_model(pool, cr)
|
||||||
ModelClass._add_columns()
|
ModelClass._add_columns()
|
||||||
ModelClass.running_env = system_base_config['running_env']
|
ModelClass.running_env = system_base_config["running_env"]
|
||||||
# Only show passwords in development
|
# Only show passwords in development
|
||||||
ModelClass.show_passwords = ModelClass.running_env in ('dev',)
|
ModelClass.show_passwords = ModelClass.running_env in ("dev",)
|
||||||
ModelClass._arch = None
|
ModelClass._arch = None
|
||||||
ModelClass._build_osv()
|
ModelClass._build_osv()
|
||||||
return ModelClass
|
return ModelClass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _format_key(cls, section, key):
|
def _format_key(cls, section, key):
|
||||||
return '%s_I_%s' % (section, key)
|
return "{}_I_{}".format(section, key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _format_key_display_name(cls, key_name):
|
def _format_key_display_name(cls, key_name):
|
||||||
return key_name.replace('_I_', ' | ')
|
return key_name.replace("_I_", " | ")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _add_columns(cls):
|
def _add_columns(cls):
|
||||||
|
|
@ -185,16 +201,17 @@ class ServerConfiguration(models.TransientModel):
|
||||||
cols = chain(
|
cols = chain(
|
||||||
list(cls._get_base_cols().items()),
|
list(cls._get_base_cols().items()),
|
||||||
list(cls._get_env_cols().items()),
|
list(cls._get_env_cols().items()),
|
||||||
list(cls._get_system_cols().items())
|
list(cls._get_system_cols().items()),
|
||||||
)
|
)
|
||||||
for col, value in cols:
|
for col, value in cols:
|
||||||
col_name = col.replace('.', '_')
|
col_name = col.replace(".", "_")
|
||||||
setattr(ServerConfiguration,
|
setattr(
|
||||||
col_name,
|
ServerConfiguration,
|
||||||
fields.Char(
|
col_name,
|
||||||
string=cls._format_key_display_name(col_name),
|
fields.Char(
|
||||||
readonly=True)
|
string=cls._format_key_display_name(col_name), readonly=True
|
||||||
)
|
),
|
||||||
|
)
|
||||||
cls._conf_defaults[col_name] = value
|
cls._conf_defaults[col_name] = value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -202,7 +219,7 @@ class ServerConfiguration(models.TransientModel):
|
||||||
""" Compute base fields"""
|
""" Compute base fields"""
|
||||||
res = {}
|
res = {}
|
||||||
for col, item in list(system_base_config.options.items()):
|
for col, item in list(system_base_config.options.items()):
|
||||||
key = cls._format_key('odoo', col)
|
key = cls._format_key("odoo", col)
|
||||||
res[key] = item
|
res[key] = item
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
@ -222,7 +239,7 @@ class ServerConfiguration(models.TransientModel):
|
||||||
""" Compute system fields"""
|
""" Compute system fields"""
|
||||||
res = {}
|
res = {}
|
||||||
for col, item in get_server_environment():
|
for col, item in get_server_environment():
|
||||||
key = cls._format_key('system', col)
|
key = cls._format_key("system", col)
|
||||||
res[key] = item
|
res[key] = item
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
@ -232,17 +249,19 @@ class ServerConfiguration(models.TransientModel):
|
||||||
names = []
|
names = []
|
||||||
|
|
||||||
for key in sorted(items):
|
for key in sorted(items):
|
||||||
names.append(key.replace('.', '_'))
|
names.append(key.replace(".", "_"))
|
||||||
return ('<group col="2" colspan="4">' +
|
return (
|
||||||
''.join(['<field name="%s" readonly="1"/>' %
|
'<group col="2" colspan="4">'
|
||||||
_escape(name) for name in names]) +
|
+ "".join(
|
||||||
'</group>')
|
['<field name="%s" readonly="1"/>' % _escape(name) for name in names]
|
||||||
|
)
|
||||||
|
+ "</group>"
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build_osv(cls):
|
def _build_osv(cls):
|
||||||
"""Build the view for the current configuration."""
|
"""Build the view for the current configuration."""
|
||||||
arch = ('<form string="Configuration Form">'
|
arch = '<form string="Configuration Form">' '<notebook colspan="4">'
|
||||||
'<notebook colspan="4">')
|
|
||||||
|
|
||||||
# Odoo server configuration
|
# Odoo server configuration
|
||||||
rcfile = system_base_config.rcfile
|
rcfile = system_base_config.rcfile
|
||||||
|
|
@ -265,23 +284,21 @@ class ServerConfiguration(models.TransientModel):
|
||||||
arch += cls._group(cls._get_system_cols())
|
arch += cls._group(cls._get_system_cols())
|
||||||
arch += '<separator colspan="4"/></page>'
|
arch += '<separator colspan="4"/></page>'
|
||||||
|
|
||||||
arch += '</notebook></form>'
|
arch += "</notebook></form>"
|
||||||
cls._arch = etree.fromstring(arch)
|
cls._arch = etree.fromstring(arch)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
|
def fields_view_get(
|
||||||
submenu=False):
|
self, view_id=None, view_type="form", toolbar=False, submenu=False
|
||||||
|
):
|
||||||
"""Overwrite the default method to render the custom view."""
|
"""Overwrite the default method to render the custom view."""
|
||||||
res = super(ServerConfiguration, self).fields_view_get(view_id,
|
res = super().fields_view_get(view_id, view_type, toolbar)
|
||||||
view_type,
|
View = self.env["ir.ui.view"]
|
||||||
toolbar)
|
if view_type == "form":
|
||||||
View = self.env['ir.ui.view']
|
|
||||||
if view_type == 'form':
|
|
||||||
arch_node = self._arch
|
arch_node = self._arch
|
||||||
xarch, xfields = View.postprocess_and_fields(
|
xarch, xfields = View.postprocess_and_fields(self._name, arch_node, view_id)
|
||||||
self._name, arch_node, view_id)
|
res["arch"] = xarch
|
||||||
res['arch'] = xarch
|
res["fields"] = xfields
|
||||||
res['fields'] = xfields
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
|
@ -291,18 +308,19 @@ class ServerConfiguration(models.TransientModel):
|
||||||
should be secret.
|
should be secret.
|
||||||
:return: list of secret keywords
|
:return: list of secret keywords
|
||||||
"""
|
"""
|
||||||
secret_keys = ['passw', 'key', 'secret', 'token']
|
secret_keys = ["passw", "key", "secret", "token"]
|
||||||
return any(secret_key in key for secret_key in secret_keys)
|
return any(secret_key in key for secret_key in secret_keys)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def default_get(self, fields_list):
|
def default_get(self, fields_list):
|
||||||
res = {}
|
res = super().default_get(fields_list)
|
||||||
if not self.env.user.has_group(
|
if not self.env.user.has_group(
|
||||||
'server_environment.has_server_configuration_access'):
|
"server_environment.has_server_configuration_access"
|
||||||
|
):
|
||||||
return res
|
return res
|
||||||
for key in self._conf_defaults:
|
for key in self._conf_defaults:
|
||||||
if not self.show_passwords and self._is_secret(key=key):
|
if not self.show_passwords and self._is_secret(key=key):
|
||||||
res[key] = '**********'
|
res[key] = "**********"
|
||||||
else:
|
else:
|
||||||
res[key] = self._conf_defaults[key]()
|
res[key] = self._conf_defaults[key]()
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
|
|
@ -28,38 +28,39 @@ from odoo.tools.config import config
|
||||||
|
|
||||||
|
|
||||||
def _get_output(cmd):
|
def _get_output(cmd):
|
||||||
bindir = config['root_path']
|
bindir = config["root_path"]
|
||||||
p = subprocess.Popen(cmd, shell=True, cwd=bindir, stdout=subprocess.PIPE,
|
p = subprocess.Popen(
|
||||||
stderr=subprocess.STDOUT)
|
cmd, shell=True, cwd=bindir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
return p.communicate()[0].rstrip()
|
return p.communicate()[0].rstrip()
|
||||||
|
|
||||||
|
|
||||||
def get_server_environment():
|
def get_server_environment():
|
||||||
# inspired by server/bin/service/web_services.py
|
# inspired by server/bin/service/web_services.py
|
||||||
try:
|
try:
|
||||||
rev_id = 'git:%s' % _get_output('git rev-parse HEAD')
|
rev_id = "git:%s" % _get_output("git rev-parse HEAD")
|
||||||
except Exception:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
rev_id = 'bzr: %s' % _get_output('bzr revision-info')
|
rev_id = "bzr: %s" % _get_output("bzr revision-info")
|
||||||
except Exception:
|
except Exception:
|
||||||
rev_id = 'Can not retrieve revison from git or bzr'
|
rev_id = "Can not retrieve revison from git or bzr"
|
||||||
|
|
||||||
os_lang = '.'.join([x for x in locale.getdefaultlocale() if x])
|
os_lang = ".".join([x for x in locale.getdefaultlocale() if x])
|
||||||
if not os_lang:
|
if not os_lang:
|
||||||
os_lang = 'NOT SET'
|
os_lang = "NOT SET"
|
||||||
if os.name == 'posix' and platform.system() == 'Linux':
|
if os.name == "posix" and platform.system() == "Linux":
|
||||||
lsbinfo = _get_output('lsb_release -a')
|
lsbinfo = _get_output("lsb_release -a")
|
||||||
else:
|
else:
|
||||||
lsbinfo = 'not lsb compliant'
|
lsbinfo = "not lsb compliant"
|
||||||
return (
|
return (
|
||||||
('platform', platform.platform()),
|
("platform", platform.platform()),
|
||||||
('os.name', os.name),
|
("os.name", os.name),
|
||||||
('lsb_release', lsbinfo),
|
("lsb_release", lsbinfo),
|
||||||
('release', platform.release()),
|
("release", platform.release()),
|
||||||
('version', platform.version()),
|
("version", platform.version()),
|
||||||
('architecture', platform.architecture()[0]),
|
("architecture", platform.architecture()[0]),
|
||||||
('locale', os_lang),
|
("locale", os_lang),
|
||||||
('python', platform.python_version()),
|
("python", platform.python_version()),
|
||||||
('odoo', release.version),
|
("odoo", release.version),
|
||||||
('revision', rev_id),
|
("revision", rev_id),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,29 +6,27 @@ from contextlib import contextmanager
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from odoo.tests import common
|
from odoo.tests import common
|
||||||
from odoo.addons.server_environment import server_env
|
|
||||||
from odoo.tools.config import config
|
from odoo.tools.config import config
|
||||||
|
|
||||||
import odoo.addons.server_environment.models.server_env_mixin as \
|
import odoo.addons.server_environment.models.server_env_mixin as server_env_mixin
|
||||||
server_env_mixin
|
from odoo.addons.server_environment import server_env
|
||||||
|
|
||||||
|
|
||||||
class ServerEnvironmentCase(common.SavepointCase):
|
class ServerEnvironmentCase(common.SavepointCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self._original_running_env = config.get('running_env')
|
self._original_running_env = config.get("running_env")
|
||||||
config['running_env'] = 'testing'
|
config["running_env"] = "testing"
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
config['running_env'] = self._original_running_env
|
config["running_env"] = self._original_running_env
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def set_config_dir(self, path):
|
def set_config_dir(self, path):
|
||||||
original_dir = server_env._dir
|
original_dir = server_env._dir
|
||||||
if path and not os.path.isabs(path):
|
if path and not os.path.isabs(path):
|
||||||
path = os.path.join(os.path.dirname(__file__,), path)
|
path = os.path.join(os.path.dirname(__file__), path)
|
||||||
server_env._dir = path
|
server_env._dir = path
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
|
|
@ -39,18 +37,17 @@ class ServerEnvironmentCase(common.SavepointCase):
|
||||||
def set_env_variables(self, public=None, secret=None):
|
def set_env_variables(self, public=None, secret=None):
|
||||||
newkeys = {}
|
newkeys = {}
|
||||||
if public:
|
if public:
|
||||||
newkeys['SERVER_ENV_CONFIG'] = public
|
newkeys["SERVER_ENV_CONFIG"] = public
|
||||||
if secret:
|
if secret:
|
||||||
newkeys['SERVER_ENV_CONFIG_SECRET'] = secret
|
newkeys["SERVER_ENV_CONFIG_SECRET"] = secret
|
||||||
with patch.dict('os.environ', newkeys):
|
with patch.dict("os.environ", newkeys):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def load_config(self, public=None, secret=None):
|
def load_config(self, public=None, secret=None):
|
||||||
original_serv_config = server_env_mixin.serv_config
|
original_serv_config = server_env_mixin.serv_config
|
||||||
try:
|
try:
|
||||||
with self.set_config_dir(None), \
|
with self.set_config_dir(None), self.set_env_variables(public, secret):
|
||||||
self.set_env_variables(public, secret):
|
|
||||||
parser = server_env._load_config()
|
parser = server_env._load_config()
|
||||||
server_env_mixin.serv_config = parser
|
server_env_mixin.serv_config = parser
|
||||||
yield
|
yield
|
||||||
|
|
|
||||||
|
|
@ -3,43 +3,25 @@
|
||||||
|
|
||||||
|
|
||||||
from odoo.addons.server_environment import server_env
|
from odoo.addons.server_environment import server_env
|
||||||
|
|
||||||
from .common import ServerEnvironmentCase
|
from .common import ServerEnvironmentCase
|
||||||
|
|
||||||
|
|
||||||
class TestEnvironmentVariables(ServerEnvironmentCase):
|
class TestEnvironmentVariables(ServerEnvironmentCase):
|
||||||
|
|
||||||
def test_env_variables(self):
|
def test_env_variables(self):
|
||||||
public = (
|
public = "[section]\n" "foo=bar\n" "bar=baz\n"
|
||||||
"[section]\n"
|
secret = "[section]\n" "bar=foo\n" "alice=bob\n"
|
||||||
"foo=bar\n"
|
with self.set_config_dir(None), self.set_env_variables(public, secret):
|
||||||
"bar=baz\n"
|
|
||||||
)
|
|
||||||
secret = (
|
|
||||||
"[section]\n"
|
|
||||||
"bar=foo\n"
|
|
||||||
"alice=bob\n"
|
|
||||||
)
|
|
||||||
with self.set_config_dir(None), \
|
|
||||||
self.set_env_variables(public, secret):
|
|
||||||
parser = server_env._load_config()
|
parser = server_env._load_config()
|
||||||
self.assertEqual(
|
self.assertEqual(list(parser.keys()), ["DEFAULT", "section"])
|
||||||
list(parser.keys()),
|
|
||||||
['DEFAULT', 'section']
|
|
||||||
)
|
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
dict(parser['section'].items()),
|
dict(parser["section"].items()),
|
||||||
{'alice': 'bob',
|
{"alice": "bob", "bar": "foo", "foo": "bar"},
|
||||||
'bar': 'foo',
|
|
||||||
'foo': 'bar'}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_env_variables_override(self):
|
def test_env_variables_override(self):
|
||||||
public = (
|
public = "[external_service.ftp]\n" "user=foo\n"
|
||||||
"[external_service.ftp]\n"
|
with self.set_config_dir("testfiles"), self.set_env_variables(public):
|
||||||
"user=foo\n"
|
|
||||||
)
|
|
||||||
with self.set_config_dir('testfiles'), \
|
|
||||||
self.set_env_variables(public):
|
|
||||||
parser = server_env._load_config()
|
parser = server_env._load_config()
|
||||||
val = parser.get('external_service.ftp', 'user')
|
val = parser.get("external_service.ftp", "user")
|
||||||
self.assertEqual(val, 'foo')
|
self.assertEqual(val, "foo")
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,32 @@
|
||||||
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
# Copyright 2018 Camptocamp (https://www.camptocamp.com).
|
||||||
# License GPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License GPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
from odoo.addons.server_environment import server_env
|
from .. import server_env
|
||||||
from . import common
|
from . import common
|
||||||
|
|
||||||
|
|
||||||
class TestEnv(common.ServerEnvironmentCase):
|
class TestEnv(common.ServerEnvironmentCase):
|
||||||
|
|
||||||
def test_view(self):
|
def test_view(self):
|
||||||
model = self.env['server.config']
|
model = self.env["server.config"]
|
||||||
view = model.fields_view_get()
|
view = model.fields_view_get()
|
||||||
self.assertTrue(view)
|
self.assertTrue(view)
|
||||||
|
|
||||||
def test_default(self):
|
def test_default(self):
|
||||||
model = self.env['server.config']
|
model = self.env["server.config"]
|
||||||
rec = model.create({})
|
rec = model.create({})
|
||||||
defaults = rec.default_get([])
|
defaults = rec.default_get([])
|
||||||
self.assertTrue(defaults)
|
self.assertTrue(defaults)
|
||||||
self.assertIsInstance(defaults, dict)
|
self.assertIsInstance(defaults, dict)
|
||||||
pass_checked = False
|
pass_checked = False
|
||||||
for default in defaults:
|
for default in defaults:
|
||||||
if 'passw' in default:
|
if "passw" in default:
|
||||||
self.assertNotEqual(defaults[default],
|
self.assertNotEqual(defaults[default], "**********")
|
||||||
'**********')
|
|
||||||
pass_checked = True
|
pass_checked = True
|
||||||
self.assertTrue(pass_checked)
|
self.assertTrue(pass_checked)
|
||||||
|
|
||||||
def test_value_retrival(self):
|
def test_value_retrival(self):
|
||||||
with self.set_config_dir('testfiles'):
|
with self.set_config_dir("testfiles"):
|
||||||
parser = server_env._load_config()
|
parser = server_env._load_config()
|
||||||
val = parser.get('external_service.ftp', 'user')
|
val = parser.get("external_service.ftp", "user")
|
||||||
self.assertEqual(val, 'testing')
|
self.assertEqual(val, "testing")
|
||||||
val = parser.get('external_service.ftp', 'host')
|
val = parser.get("external_service.ftp", "host")
|
||||||
self.assertEqual(val, 'sftp.example.com')
|
self.assertEqual(val, "sftp.example.com")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue