diff --git a/.copier-answers.yml b/.copier-answers.yml
new file mode 100644
index 00000000..f23d2e3a
--- /dev/null
+++ b/.copier-answers.yml
@@ -0,0 +1,25 @@
+# Do NOT update manually; changes here will be overwritten by Copier
+_commit: v1.14.2
+_src_path: https://github.com/OCA/oca-addons-repo-template.git
+ci: GitHub
+dependency_installation_mode: PIP
+generate_requirements_txt: false
+github_check_license: true
+github_ci_extra_env: {}
+github_enable_codecov: true
+github_enable_makepot: false
+github_enable_stale_action: true
+github_enforce_dev_status_compatibility: false
+include_wkhtmltopdf: false
+odoo_version: 16.0
+org_name: Camptocamp
+org_slug: camptocamp
+rebel_module_groups:
+- attachment_azure,cloud_platform_azure
+repo_description: ''
+repo_name: Odoo Cloud Addons
+repo_slug: odoo-cloud-platform
+repo_website: https://github.com/camptocamp/odoo-cloud-platform
+travis_apt_packages: []
+travis_apt_sources: []
+
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..bfd7ac53
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+# Configuration for known file extensions
+[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{json,yml,yaml,rst,md}]
+indent_size = 2
+
+# Do not configure editor for libs and autogenerated content
+[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}]
+charset = unset
+end_of_line = unset
+indent_size = unset
+indent_style = unset
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/.eslintrc.yml b/.eslintrc.yml
new file mode 100644
index 00000000..9429bc68
--- /dev/null
+++ b/.eslintrc.yml
@@ -0,0 +1,187 @@
+env:
+ browser: true
+ es6: true
+
+# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449
+parserOptions:
+ ecmaVersion: 2019
+
+overrides:
+ - files:
+ - "**/*.esm.js"
+ parserOptions:
+ sourceType: module
+
+# Globals available in Odoo that shouldn't produce errorings
+globals:
+ _: readonly
+ $: readonly
+ fuzzy: readonly
+ jQuery: readonly
+ moment: readonly
+ odoo: readonly
+ openerp: readonly
+ owl: readonly
+
+# Styling is handled by Prettier, so we only need to enable AST rules;
+# see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890
+rules:
+ accessor-pairs: warn
+ array-callback-return: warn
+ callback-return: warn
+ capitalized-comments:
+ - warn
+ - always
+ - ignoreConsecutiveComments: true
+ ignoreInlineComments: true
+ complexity:
+ - warn
+ - 15
+ constructor-super: warn
+ dot-notation: warn
+ eqeqeq: warn
+ global-require: warn
+ handle-callback-err: warn
+ id-blacklist: warn
+ id-match: warn
+ init-declarations: error
+ max-depth: warn
+ max-nested-callbacks: warn
+ max-statements-per-line: warn
+ no-alert: warn
+ no-array-constructor: warn
+ no-caller: warn
+ no-case-declarations: warn
+ no-class-assign: warn
+ no-cond-assign: error
+ no-const-assign: error
+ no-constant-condition: warn
+ no-control-regex: warn
+ no-debugger: error
+ no-delete-var: warn
+ no-div-regex: warn
+ no-dupe-args: error
+ no-dupe-class-members: error
+ no-dupe-keys: error
+ no-duplicate-case: error
+ no-duplicate-imports: error
+ no-else-return: warn
+ no-empty-character-class: warn
+ no-empty-function: error
+ no-empty-pattern: error
+ no-empty: warn
+ no-eq-null: error
+ no-eval: error
+ no-ex-assign: error
+ no-extend-native: warn
+ no-extra-bind: warn
+ no-extra-boolean-cast: warn
+ no-extra-label: warn
+ no-fallthrough: warn
+ no-func-assign: error
+ no-global-assign: error
+ 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-irregular-whitespace: warn
+ no-iterator: warn
+ no-label-var: warn
+ no-labels: warn
+ no-lone-blocks: warn
+ no-lonely-if: error
+ no-mixed-requires: error
+ no-multi-str: warn
+ no-native-reassign: error
+ no-negated-condition: warn
+ no-negated-in-lhs: error
+ no-new-func: warn
+ no-new-object: warn
+ no-new-require: warn
+ no-new-symbol: warn
+ no-new-wrappers: warn
+ no-new: warn
+ no-obj-calls: warn
+ no-octal-escape: warn
+ no-octal: warn
+ no-param-reassign: warn
+ no-path-concat: warn
+ 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: error
+ no-script-url: warn
+ no-self-assign: warn
+ no-self-compare: warn
+ no-sequences: warn
+ no-shadow-restricted-names: warn
+ no-shadow: warn
+ no-sparse-arrays: warn
+ no-sync: warn
+ no-this-before-super: warn
+ no-throw-literal: warn
+ no-undef-init: warn
+ no-undef: error
+ no-unmodified-loop-condition: warn
+ no-unneeded-ternary: error
+ no-unreachable: error
+ no-unsafe-finally: error
+ no-unused-expressions: error
+ no-unused-labels: error
+ no-unused-vars: error
+ no-use-before-define: error
+ 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-with: warn
+ operator-assignment: [error, always]
+ prefer-const: warn
+ radix: warn
+ require-yield: warn
+ sort-imports: warn
+ spaced-comment: [error, always]
+ strict: [error, function]
+ use-isnan: error
+ 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
+ yoda: warn
diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000..e397e8ed
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,12 @@
+[flake8]
+max-line-length = 88
+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
+per-file-ignores=
+ __init__.py:F401
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 00000000..f4f16d33
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,35 @@
+name: pre-commit
+
+on:
+ pull_request:
+ branches:
+ - "16.0*"
+ push:
+ branches:
+ - "16.0"
+ - "16.0-ocabot-*"
+
+jobs:
+ pre-commit:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v2
+ - name: Get python version
+ run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
+ - uses: actions/cache@v1
+ with:
+ path: ~/.cache/pre-commit
+ key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
+ - name: Install pre-commit
+ run: pip install pre-commit
+ - name: Run pre-commit
+ run: pre-commit run --all-files --show-diff-on-failure --color=always
+ - name: Check that all files generated by pre-commit are in git
+ run: |
+ newfiles="$(git ls-files --others --exclude-from=.gitignore)"
+ if [ "$newfiles" != "" ] ; then
+ echo "Please check-in the following files:"
+ echo "$newfiles"
+ exit 1
+ fi
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 00000000..1693a125
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,69 @@
+name: Mark stale issues and pull requests
+
+on:
+ schedule:
+ - cron: "0 12 * * 0"
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Stale PRs and issues policy
+ uses: actions/stale@v4
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ # General settings.
+ ascending: true
+ remove-stale-when-updated: true
+ # Pull Requests settings.
+ # 120+30 day stale policy for PRs
+ # * Except PRs marked as "no stale"
+ days-before-pr-stale: 120
+ days-before-pr-close: 30
+ exempt-pr-labels: "no stale"
+ stale-pr-label: "stale"
+ stale-pr-message: >
+ There hasn't been any activity on this pull request in the past 4 months, so
+ it has been marked as stale and it will be closed automatically if no
+ further activity occurs in the next 30 days.
+
+ If you want this PR to never become stale, please ask a PSC member to apply
+ the "no stale" label.
+ # Issues settings.
+ # 180+30 day stale policy for open issues
+ # * Except Issues marked as "no stale"
+ days-before-issue-stale: 180
+ days-before-issue-close: 30
+ exempt-issue-labels: "no stale,needs more information"
+ stale-issue-label: "stale"
+ stale-issue-message: >
+ There hasn't been any activity on this issue in the past 6 months, so it has
+ been marked as stale and it will be closed automatically if no further
+ activity occurs in the next 30 days.
+
+ If you want this issue to never become stale, please ask a PSC member to
+ apply the "no stale" label.
+
+ # 15+30 day stale policy for issues pending more information
+ # * Issues that are pending more information
+ # * Except Issues marked as "no stale"
+ - name: Needs more information stale issues policy
+ uses: actions/stale@v4
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ ascending: true
+ only-labels: "needs more information"
+ exempt-issue-labels: "no stale"
+ days-before-stale: 15
+ days-before-close: 30
+ days-before-pr-stale: -1
+ days-before-pr-close: -1
+ remove-stale-when-updated: true
+ stale-issue-label: "stale"
+ stale-issue-message: >
+ This issue needs more information and there hasn't been any activity
+ recently, so it has been marked as stale and it will be closed automatically
+ if no further activity occurs in the next 30 days.
+
+ If you think this is a mistake, please ask a PSC member to remove the "needs
+ more information" label.
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..d2e29c9d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,82 @@
+name: tests
+
+on:
+ pull_request:
+ branches:
+ - "16.0*"
+ push:
+ branches:
+ - "16.0"
+ - "16.0-ocabot-*"
+
+jobs:
+ unreleased-deps:
+ runs-on: ubuntu-latest
+ name: Detect unreleased dependencies
+ steps:
+ - uses: actions/checkout@v3
+ - run: |
+ for reqfile in requirements.txt test-requirements.txt ; do
+ if [ -f ${reqfile} ] ; then
+ result=0
+ # reject non-comment lines that contain a / (i.e. URLs, relative paths)
+ grep "^[^#].*/" ${reqfile} || result=$?
+ if [ $result -eq 0 ] ; then
+ echo "Unreleased dependencies found in ${reqfile}."
+ exit 1
+ fi
+ fi
+ done
+ test:
+ runs-on: ubuntu-22.04
+ container: ${{ matrix.container }}
+ name: ${{ matrix.name }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - container: ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest
+ include: "attachment_azure,cloud_platform_azure"
+ makepot: "false"
+ name: test azure with Odoo
+ - container: ghcr.io/oca/oca-ci/py3.10-ocb16.0:latest
+ include: "attachment_azure,cloud_platform_azure"
+ name: test azure with OCA
+ - container: ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest
+ exclude: "attachment_s3,cloud_platform_exoscale,attachment_swift,cloud_platform_ovh,attachment_azure,cloud_platform_azure"
+ makepot: "false"
+ name: test others with Odoo
+ - container: ghcr.io/oca/oca-ci/py3.10-ocb16.0:latest
+ exclude: "attachment_s3,cloud_platform_exoscale,attachment_swift,cloud_platform_ovh,attachment_azure,cloud_platform_azure"
+ name: test others with OCB
+ services:
+ postgres:
+ image: postgres:12.0
+ env:
+ POSTGRES_USER: odoo
+ POSTGRES_PASSWORD: odoo
+ POSTGRES_DB: odoo
+ ports:
+ - 5432:5432
+ env:
+ INCLUDE: "${{ matrix.include }}"
+ EXCLUDE: "${{ matrix.exclude }}"
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ persist-credentials: false
+ - name: Install addons and dependencies
+ run: oca_install_addons
+ - name: Check licenses
+ run: manifestoo -d . check-licenses
+ - name: Check development status
+ run: manifestoo -d . check-dev-status --default-dev-status=Beta
+ continue-on-error: true
+ - name: Initialize test db
+ run: oca_init_test_database
+ - name: Run tests
+ run: oca_run_tests
+ - uses: codecov/codecov-action@v1
+ - name: Update .pot files
+ run: oca_export_and_push_pot https://x-access-token:${{ secrets.GIT_PUSH_TOKEN }}@github.com/${{ github.repository }}
+ if: ${{ matrix.makepot == 'true' && github.event_name == 'push' && github.repository_owner == 'camptocamp' }}
diff --git a/.gitignore b/.gitignore
index 50b17c63..9c283fd4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
+/.venv
+/.pytest_cache
# C extensions
*.so
@@ -13,8 +15,6 @@ build/
develop-eggs/
dist/
eggs/
-.eggs/
-lib/
lib64/
parts/
sdist/
@@ -22,6 +22,7 @@ var/
*.egg-info/
.installed.cfg
*.egg
+*.eggs
# Installer logs
pip-log.txt
@@ -41,6 +42,19 @@ coverage.xml
# Pycharm
.idea
+# Eclipse
+.settings
+
+# Visual Studio cache/options directory
+.vs/
+.vscode
+
+# OSX Files
+.DS_Store
+
+# Django stuff:
+*.log
+
# Mr Developer
.mr.developer.cfg
.project
@@ -50,8 +64,11 @@ coverage.xml
.ropeproject
# Sphinx documentation
-connector/doc/_build/
+docs/_build/
# Backup files
*~
*.swp
+
+# OCA rules
+!static/lib/
diff --git a/.isort.cfg b/.isort.cfg
new file mode 100644
index 00000000..0ec187ef
--- /dev/null
+++ b/.isort.cfg
@@ -0,0 +1,13 @@
+[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
+default_section=THIRDPARTY
+ensure_newline_before_comments = True
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..18a030da
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,140 @@
+exclude: |
+ (?x)
+ # NOT INSTALLABLE ADDONS
+ ^attachment_s3/|
+ ^attachment_swift/|
+ ^base_fileurl_field/|
+ ^cloud_platform_exoscale/|
+ ^cloud_platform_ovh/|
+ ^monitoring_log_requests/|
+ ^monitoring_statsd/|
+ ^test_base_fileurl_field/|
+ # END NOT INSTALLABLE ADDONS
+ # Files and folders generated by bots, to avoid loops
+ ^setup/|/static/description/index\.html$|
+ # We don't want to mess with tool-generated files
+ .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|
+ # Maybe reactivate this when all README files include prettier ignore tags?
+ ^README\.md$|
+ # Library files can have extraneous formatting (even minimized)
+ /static/(src/)?lib/|
+ # Repos using Sphinx to generate docs don't need prettying
+ ^docs/_templates/.*\.html$|
+ # You don't usually want a bot to modify your legal texts
+ (LICENSE.*|COPYING.*)
+default_language_version:
+ python: python3
+ node: "16.17.0"
+repos:
+ - repo: local
+ hooks:
+ # These files are most likely copier diff rejection junks; if found,
+ # review them manually, fix the problem (if needed) and remove them
+ - id: forbidden-files
+ name: forbidden files
+ entry: found forbidden files; remove them
+ language: fail
+ files: "\\.rej$"
+ - id: en-po-files
+ name: en.po files cannot exist
+ entry: found a en.po file
+ language: fail
+ files: '[a-zA-Z0-9_]*/i18n/en\.po$'
+ - repo: https://github.com/oca/maintainer-tools
+ rev: 4cd2b852214dead80822e93e6749b16f2785b2fe
+ hooks:
+ # update the NOT INSTALLABLE ADDONS section above
+ - id: oca-update-pre-commit-excluded-addons
+ - id: oca-fix-manifest-website
+ args: ["https://github.com/camptocamp/odoo-cloud-platform"]
+ - repo: https://github.com/myint/autoflake
+ rev: v1.6.1
+ hooks:
+ - id: autoflake
+ args:
+ - --expand-star-imports
+ - --ignore-init-module-imports
+ - --in-place
+ - --remove-all-unused-imports
+ - --remove-duplicate-keys
+ - --remove-unused-variables
+ - repo: https://github.com/psf/black
+ rev: 22.8.0
+ hooks:
+ - id: black
+ - repo: https://github.com/pre-commit/mirrors-prettier
+ rev: v2.7.1
+ hooks:
+ - id: prettier
+ name: prettier (with plugin-xml)
+ additional_dependencies:
+ - "prettier@2.7.1"
+ - "@prettier/plugin-xml@2.2.0"
+ args:
+ - --plugin=@prettier/plugin-xml
+ files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
+ - repo: https://github.com/pre-commit/mirrors-eslint
+ rev: v8.24.0
+ hooks:
+ - id: eslint
+ verbose: true
+ args:
+ - --color
+ - --fix
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.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: fix-encoding-pragma
+ args: ["--remove"]
+ - id: check-case-conflict
+ - id: check-docstring-first
+ - id: check-executables-have-shebangs
+ - id: check-merge-conflict
+ # exclude files where underlines are not distinguishable from merge conflicts
+ exclude: /README\.rst$|^docs/.*\.rst$
+ - id: check-symlinks
+ - id: check-xml
+ - id: mixed-line-ending
+ args: ["--fix=lf"]
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v2.38.2
+ hooks:
+ - id: pyupgrade
+ args: ["--keep-percent-format"]
+ - repo: https://github.com/PyCQA/isort
+ rev: 5.12.0
+ hooks:
+ - id: isort
+ name: isort except __init__.py
+ args:
+ - --settings=.
+ exclude: /__init__\.py$
+ - repo: https://github.com/acsone/setuptools-odoo
+ rev: 3.1.8
+ hooks:
+ - id: setuptools-odoo-make-default
+ - repo: https://github.com/PyCQA/flake8
+ rev: 3.9.2
+ hooks:
+ - id: flake8
+ name: flake8
+ additional_dependencies: ["flake8-bugbear==21.9.2"]
+ - repo: https://github.com/OCA/pylint-odoo
+ rev: 7.0.2
+ hooks:
+ - id: pylint_odoo
+ name: pylint with optional checks
+ args:
+ - --rcfile=.pylintrc
+ - --exit-zero
+ verbose: true
+ - id: pylint_odoo
+ args:
+ - --rcfile=.pylintrc-mandatory
diff --git a/.prettierrc.yml b/.prettierrc.yml
new file mode 100644
index 00000000..5b6d4b36
--- /dev/null
+++ b/.prettierrc.yml
@@ -0,0 +1,8 @@
+# Defaults for all prettier-supported languages.
+# Prettier will complete this with settings from .editorconfig file.
+bracketSpacing: false
+printWidth: 88
+proseWrap: always
+semi: true
+trailingComma: "es5"
+xmlWhitespaceSensitivity: "strict"
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 00000000..54a5e915
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,123 @@
+
+
+[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=Camptocamp
+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=16.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,
+ development-status-allowed,
+ 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,
+ 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,
+ attribute-string-redundant,
+ character-not-valid-in-resource-link,
+ consider-merging-classes-inherited,
+ context-overridden,
+ create-user-wo-reset-password,
+ dangerous-filter-wo-user,
+ dangerous-qweb-replace-wo-priority,
+ deprecated-data-xml-node,
+ deprecated-openerp-xml-node,
+ duplicate-po-message-definition,
+ except-pass,
+ file-not-used,
+ invalid-commit,
+ manifest-maintainers-list,
+ missing-newline-extrafiles,
+ missing-readme,
+ missing-return,
+ odoo-addons-relative-import,
+ old-api7-method-defined,
+ po-msgstr-variables,
+ po-syntax-error,
+ renamed-field-parameter,
+ resource-not-exist,
+ str-format-used,
+ test-folder-imported,
+ translation-contains-variable,
+ translation-positional-used,
+ unnecessary-utf8-coding-comment,
+ website-manifest-key-not-valid-uri,
+ xml-attribute-translatable,
+ xml-deprecated-qweb-directive,
+ xml-deprecated-tree-attribute,
+ external-request-timeout,
+ # 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-manifest-dependency,
+ 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
diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory
new file mode 100644
index 00000000..fedc3ea6
--- /dev/null
+++ b/.pylintrc-mandatory
@@ -0,0 +1,98 @@
+
+[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=Camptocamp
+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=16.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,
+ development-status-allowed,
+ 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,
+ 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,
+ attribute-string-redundant,
+ character-not-valid-in-resource-link,
+ consider-merging-classes-inherited,
+ context-overridden,
+ create-user-wo-reset-password,
+ dangerous-filter-wo-user,
+ dangerous-qweb-replace-wo-priority,
+ deprecated-data-xml-node,
+ deprecated-openerp-xml-node,
+ duplicate-po-message-definition,
+ except-pass,
+ file-not-used,
+ invalid-commit,
+ manifest-maintainers-list,
+ missing-newline-extrafiles,
+ missing-readme,
+ missing-return,
+ odoo-addons-relative-import,
+ old-api7-method-defined,
+ po-msgstr-variables,
+ po-syntax-error,
+ renamed-field-parameter,
+ resource-not-exist,
+ str-format-used,
+ test-folder-imported,
+ translation-contains-variable,
+ translation-positional-used,
+ unnecessary-utf8-coding-comment,
+ website-manifest-key-not-valid-uri,
+ xml-attribute-translatable,
+ xml-deprecated-qweb-directive,
+ xml-deprecated-tree-attribute,
+ external-request-timeout
+
+[REPORTS]
+msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
+output-format=colorized
+reports=no
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8e1aa7ef..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-language: python
-sudo: false
-cache: pip
-
-branches:
- only:
- - "/^[[:digit:]]{1,2}.[[:digit:]]$/"
-
-python:
- # Force a newer version than 3.7.1 which break build
- # due to https://bugs.python.org/issue34921
- - "3.8"
-
-addons:
- postgresql: "9.5"
- apt:
- packages:
- - expect-dev # provides unbuffer utility
- - python-lxml # because pip installation is slow
- - python-simplejson
- - python-serial
-
-env:
- matrix:
- - LINT_CHECK="1"
- - TESTS="1" ODOO_REPO="odoo/odoo" INCLUDE="cloud_platform_exoscale"
- - TESTS="1" ODOO_REPO="OCA/OCB" INCLUDE="cloud_platform_exoscale"
- - TESTS="1" ODOO_REPO="odoo/odoo" INCLUDE="cloud_platform_ovh"
- - TESTS="1" ODOO_REPO="OCA/OCB" INCLUDE="cloud_platform_ovh"
- - TESTS="1" ODOO_REPO="odoo/odoo" EXCLUDE="cloud_platform,cloud_platform_ovh,cloud_platform_exoscale"
- - TESTS="1" ODOO_REPO="OCA/OCB" EXCLUDE="cloud_platform,cloud_platform_ovh,cloud_platform_exoscale"
- global:
- - VERSION="16.0" LINT_CHECK="0" TESTS="0"
-
-install:
- - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
- - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- - travis_install_nightly
-
-script:
- - travis_run_tests
-
-after_success:
- - travis_after_test_success
diff --git a/LICENSE b/LICENSE
index 3ffc5678..be3f7b28 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
-GNU AFFERO GENERAL PUBLIC LICENSE
+ GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
- Copyright (C) 2007 Free Software Foundation, Inc.
+ Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -633,8 +633,8 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License, or
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
@@ -643,7 +643,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
+ along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
@@ -658,4 +658,4 @@ specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
-.
\ No newline at end of file
+.
diff --git a/README.md b/README.md
index a3e64903..4b81d686 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,11 @@
-[![Build Status](https://travis-ci.com/camptocamp/odoo-cloud-platform.svg?token=Lpp9PcS5on9AGbp76WKB&branch=12.0)](https://travis-ci.com/camptocamp/odoo-cloud-platform)
+
+
+[![Pre-commit Status](https://github.com/camptocamp/odoo-cloud-platform/actions/workflows/pre-commit.yml/badge.svg?branch=16.0)](https://github.com/camptocamp/odoo-cloud-platform/actions/workflows/pre-commit.yml?query=branch%3A16.0)
+[![Build Status](https://github.com/camptocamp/odoo-cloud-platform/actions/workflows/test.yml/badge.svg?branch=16.0)](https://github.com/camptocamp/odoo-cloud-platform/actions/workflows/test.yml?query=branch%3A16.0)
+[![codecov](https://codecov.io/gh/camptocamp/odoo-cloud-platform/branch/16.0/graph/badge.svg)](https://codecov.io/gh/camptocamp/odoo-cloud-platform)
+
+
+
# Odoo Cloud Addons
@@ -167,3 +174,26 @@ The checks can be bypassed with the environment variable
To prevent object storage to be accessed while failing for any kind of reason
set this environment variable `DISABLE_ATTACHMENT_STORAGE` set to `1`.
+
+
+
+
+
+[//]: # (addons)
+
+This part will be replaced when running the oca-gen-addons-table script from OCA/maintainer-tools.
+
+[//]: # (end addons)
+
+
+
+## Licenses
+
+This repository is licensed under [AGPL-3.0](LICENSE).
+
+However, each module can have a totally different license, as long as they adhere to Camptocamp
+policy. Consult each module's `__manifest__.py` file, which contains a `license` key
+that explains its license.
+
+----
+
diff --git a/attachment_azure/models/ir_attachment.py b/attachment_azure/models/ir_attachment.py
index 25388175..fd15a21e 100644
--- a/attachment_azure/models/ir_attachment.py
+++ b/attachment_azure/models/ir_attachment.py
@@ -12,13 +12,13 @@
_logger = logging.getLogger(__name__)
try:
+ from azure.core.exceptions import HttpResponseError, ResourceExistsError
from azure.storage.blob import (
+ AccountSasPermissions,
BlobServiceClient,
- generate_account_sas,
ResourceTypes,
- AccountSasPermissions,
+ generate_account_sas,
)
- from azure.core.exceptions import ResourceExistsError, HttpResponseError
except ImportError:
_logger.debug("Cannot 'import azure-storage-blob'.")
@@ -32,9 +32,7 @@ class IrAttachment(models.Model):
_inherit = "ir.attachment"
def _get_stores(self):
- l = ["azure"]
- l += super(IrAttachment, self)._get_stores()
- return l
+ return ["azure"] + super(IrAttachment, self)._get_stores()
@api.model
def _get_blob_service_client(self):
@@ -88,7 +86,7 @@ def _get_blob_service_client(self):
"Error during the connection to Azure container using the "
"connection string."
)
- raise exceptions.UserError(str(error))
+ raise exceptions.UserError(str(error)) from None
else:
try:
sas_token = generate_account_sas(
@@ -107,15 +105,13 @@ def _get_blob_service_client(self):
"Error during the connection to Azure container using the Shared "
"Access Signature (SAS)"
)
- raise exceptions.UserError(str(error))
+ raise exceptions.UserError(str(error)) from None
return blob_service_client
@api.model
def _get_container_name(self):
- """
- Container naming rules:
- https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
- """
+ # Container naming rules:
+ # https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names # noqa: B950
running_env = os.environ.get("RUNNING_ENV", "dev")
storage_name = os.environ.get("AZURE_STORAGE_NAME", r"{env}-{db}")
storage_name = storage_name.format(env=running_env, db=self.env.cr.dbname)
@@ -143,7 +139,7 @@ def _get_azure_container(self, container_name=None):
container_client.create_container()
except HttpResponseError as error:
_logger.exception("Error during the creation of the Azure container")
- raise exceptions.UserError(str(error))
+ raise exceptions.UserError(str(error)) from None
return container_client
@api.model
@@ -181,13 +177,17 @@ def _store_file_write(self, key, bin_data):
try:
blob_client.upload_blob(file, blob_type="BlockBlob")
except ResourceExistsError:
- pass
+ _logger.exception(
+ "Trying to re create an existing resource %s" % filename
+ )
except HttpResponseError as error:
# log verbose error from azure, return short message for user
- _logger.exception("Error during storage of the file %s" % filename)
+ _logger.exception(
+ "HTTP Error during storage of the file %s" % filename
+ )
raise exceptions.UserError(
_("The file could not be stored: %s") % str(error)
- )
+ ) from None
else:
_super = super(IrAttachment, self)
filename = _super._store_file_write(key, bin_data)
diff --git a/attachment_azure/models/ir_binary.py b/attachment_azure/models/ir_binary.py
index 7c99ffac..f08fc874 100644
--- a/attachment_azure/models/ir_binary.py
+++ b/attachment_azure/models/ir_binary.py
@@ -60,6 +60,6 @@ def _record_to_stream(self, record, field_name):
from odoo.addons import documents
documents.models.ir_binary.IrBinary._record_to_stream = IrBinary._record_to_stream
-except ImportError:
+except ImportError: # pylint: disable=except-pass
# document enterprise module if not installed, we just ignore
pass
diff --git a/attachment_s3/__init__.py b/attachment_s3/__init__.py
index a9e33722..0650744f 100644
--- a/attachment_s3/__init__.py
+++ b/attachment_s3/__init__.py
@@ -1,2 +1 @@
-
from . import models
diff --git a/attachment_s3/__manifest__.py b/attachment_s3/__manifest__.py
index fcdc6cb8..7958e620 100644
--- a/attachment_s3/__manifest__.py
+++ b/attachment_s3/__manifest__.py
@@ -13,7 +13,7 @@
"external_dependencies": {
"python": ["boto3"],
},
- "website": "https://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": False,
}
diff --git a/attachment_s3/models/__init__.py b/attachment_s3/models/__init__.py
index 3cf36e5c..aaf38a16 100644
--- a/attachment_s3/models/__init__.py
+++ b/attachment_s3/models/__init__.py
@@ -1,2 +1 @@
-
from . import ir_attachment
diff --git a/attachment_s3/models/ir_attachment.py b/attachment_s3/models/ir_attachment.py
index fcedb8c0..d18c0e23 100644
--- a/attachment_s3/models/ir_attachment.py
+++ b/attachment_s3/models/ir_attachment.py
@@ -2,12 +2,13 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
+import io
import logging
import os
-import io
from urllib.parse import urlsplit
from odoo import _, api, exceptions, models
+
from ..s3uri import S3Uri
_logger = logging.getLogger(__name__)
@@ -26,9 +27,7 @@ class IrAttachment(models.Model):
_inherit = "ir.attachment"
def _get_stores(self):
- l = ['s3']
- l += super()._get_stores()
- return l
+ return ["s3"] + super()._get_stores()
@api.model
def _get_s3_bucket(self, name=None):
@@ -45,42 +44,43 @@ def _get_s3_bucket(self, name=None):
from the environment variable ``AWS_BUCKETNAME`` will be read.
"""
- host = os.environ.get('AWS_HOST')
+ host = os.environ.get("AWS_HOST")
# Ensure host is prefixed with a scheme (use https as default)
if host and not urlsplit(host).scheme:
- host = 'https://%s' % host
+ host = "https://%s" % host
- region_name = os.environ.get('AWS_REGION')
- access_key = os.environ.get('AWS_ACCESS_KEY_ID')
- secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
- bucket_name = name or os.environ.get('AWS_BUCKETNAME')
+ region_name = os.environ.get("AWS_REGION")
+ access_key = os.environ.get("AWS_ACCESS_KEY_ID")
+ secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
+ bucket_name = name or os.environ.get("AWS_BUCKETNAME")
# replaces {db} by the database name to handle multi-tenancy
bucket_name = bucket_name.format(db=self.env.cr.dbname)
params = {
- 'aws_access_key_id': access_key,
- 'aws_secret_access_key': secret_key,
+ "aws_access_key_id": access_key,
+ "aws_secret_access_key": secret_key,
}
if host:
- params['endpoint_url'] = host
+ params["endpoint_url"] = host
if region_name:
- params['region_name'] = region_name
+ params["region_name"] = region_name
if not (access_key and secret_key and bucket_name):
- msg = _('If you want to read from the %s S3 bucket, the following '
- 'environment variables must be set:\n'
- '* AWS_ACCESS_KEY_ID\n'
- '* AWS_SECRET_ACCESS_KEY\n'
- 'If you want to write in the %s S3 bucket, this variable '
- 'must be set as well:\n'
- '* AWS_BUCKETNAME\n'
- 'Optionally, the S3 host can be changed with:\n'
- '* AWS_HOST\n'
- ) % (bucket_name, bucket_name)
+ msg = _(
+ "If you want to read from the %(bucket_name)s S3 bucket, the following "
+ "environment variables must be set:\n"
+ "* AWS_ACCESS_KEY_ID\n"
+ "* AWS_SECRET_ACCESS_KEY\n"
+ "If you want to write in the %(bucket_name)s S3 bucket, this variable "
+ "must be set as well:\n"
+ "* AWS_BUCKETNAME\n"
+ "Optionally, the S3 host can be changed with:\n"
+ "* AWS_HOST\n"
+ ).format(bucket_name=bucket_name)
raise exceptions.UserError(msg)
# try:
- s3 = boto3.resource('s3', **params)
+ s3 = boto3.resource("s3", **params)
bucket = s3.Bucket(bucket_name)
exists = True
try:
@@ -88,13 +88,13 @@ def _get_s3_bucket(self, name=None):
except ClientError as e:
# If a client error is thrown, then check that it was a 404 error.
# If it was a 404 error, then the bucket does not exist.
- error_code = e.response['Error']['Code']
- if error_code == '404':
+ error_code = e.response["Error"]["Code"]
+ if error_code == "404":
exists = False
except EndpointConnectionError as error:
# log verbose error from s3, return short message for user
- _logger.exception('Error during connection on S3')
- raise exceptions.UserError(str(error))
+ msg = _logger.exception("Error during connection on S3")
+ raise exceptions.UserError(str(error)) from None
if not exists:
if not region_name:
@@ -102,14 +102,13 @@ def _get_s3_bucket(self, name=None):
else:
bucket = s3.create_bucket(
Bucket=bucket_name,
- CreateBucketConfiguration={
- 'LocationConstraint': region_name
- })
+ CreateBucketConfiguration={"LocationConstraint": region_name},
+ )
return bucket
@api.model
def _store_file_read(self, fname):
- if fname.startswith('s3://'):
+ if fname.startswith("s3://"):
s3uri = S3Uri(fname)
try:
bucket = self._get_s3_bucket(name=s3uri.bucket())
@@ -117,45 +116,39 @@ def _store_file_read(self, fname):
_logger.exception(
"error reading attachment '%s' from object storage", fname
)
- return ''
+ return ""
try:
key = s3uri.item()
- bucket.meta.client.head_object(
- Bucket=bucket.name, Key=key
- )
+ bucket.meta.client.head_object(Bucket=bucket.name, Key=key)
with io.BytesIO() as res:
bucket.download_fileobj(key, res)
res.seek(0)
read = res.read()
except ClientError:
- read = ''
- _logger.info(
- "attachment '%s' missing on object storage", fname
- )
+ read = ""
+ _logger.info("attachment '%s' missing on object storage", fname)
return read
else:
return super()._store_file_read(fname)
@api.model
def _store_file_write(self, key, bin_data):
- location = self.env.context.get('storage_location') or self._storage()
- if location == 's3':
+ location = self.env.context.get("storage_location") or self._storage()
+ if location == "s3":
bucket = self._get_s3_bucket()
obj = bucket.Object(key=key)
with io.BytesIO() as file:
file.write(bin_data)
file.seek(0)
- filename = 's3://%s/%s' % (bucket.name, key)
+ filename = "s3://%s/%s" % (bucket.name, key)
try:
obj.upload_fileobj(file)
except ClientError as error:
# log verbose error from s3, return short message for user
- _logger.exception(
- 'Error during storage of the file %s' % filename
- )
+ _logger.exception("Error during storage of the file %s" % filename)
raise exceptions.UserError(
- _('The file could not be stored: %s') % str(error)
- )
+ _("The file could not be stored: %s") % str(error)
+ ) from None
else:
_super = super()
filename = _super._store_file_write(key, bin_data)
@@ -163,28 +156,22 @@ def _store_file_write(self, key, bin_data):
@api.model
def _store_file_delete(self, fname):
- if fname.startswith('s3://'):
+ if fname.startswith("s3://"):
s3uri = S3Uri(fname)
bucket_name = s3uri.bucket()
item_name = s3uri.item()
# delete the file only if it is on the current configured bucket
# otherwise, we might delete files used on a different environment
- if bucket_name == os.environ.get('AWS_BUCKETNAME'):
+ if bucket_name == os.environ.get("AWS_BUCKETNAME"):
bucket = self._get_s3_bucket()
obj = bucket.Object(key=item_name)
try:
- bucket.meta.client.head_object(
- Bucket=bucket.name, Key=item_name
- )
+ bucket.meta.client.head_object(Bucket=bucket.name, Key=item_name)
obj.delete()
- _logger.info(
- 'file %s deleted on the object storage' % (fname,)
- )
+ _logger.info("file %s deleted on the object storage" % (fname,))
except ClientError:
# log verbose error from s3, return short message for
# user
- _logger.exception(
- 'Error during deletion of the file %s' % fname
- )
+ _logger.exception("Error during deletion of the file %s" % fname)
else:
- super()._store_file_delete(fname)
+ return super()._store_file_delete(fname)
diff --git a/attachment_swift/__manifest__.py b/attachment_swift/__manifest__.py
index 7991433b..d0c2ed9c 100644
--- a/attachment_swift/__manifest__.py
+++ b/attachment_swift/__manifest__.py
@@ -17,7 +17,7 @@
"keystoneauth1",
],
},
- "website": "https://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": False,
}
diff --git a/attachment_swift/models/ir_attachment.py b/attachment_swift/models/ir_attachment.py
index 7fd659e1..6a8b088a 100644
--- a/attachment_swift/models/ir_attachment.py
+++ b/attachment_swift/models/ir_attachment.py
@@ -4,17 +4,18 @@
import logging
import os
-from ..swift_uri import SwiftUri
-from odoo import api, exceptions, models, _
+from odoo import _, api, exceptions, models
+
+from ..swift_uri import SwiftUri
_logger = logging.getLogger(__name__)
try:
- import swiftclient
import keystoneauth1
import keystoneauth1.identity
import keystoneauth1.session
+ import swiftclient
from swiftclient.exceptions import ClientException
except ImportError:
swiftclient = None
@@ -48,8 +49,9 @@ def __init__(self):
def _get_key(self, auth_url, username, password, project_name):
return (auth_url, username, password, project_name)
- def get_session(self, auth_url=None, username=None, password=None,
- project_name=None):
+ def get_session(
+ self, auth_url=None, username=None, password=None, project_name=None
+ ):
key = self._get_key(auth_url, username, password, project_name)
session = self._sessions.get(key)
if not session:
@@ -58,8 +60,8 @@ def get_session(self, auth_url=None, username=None, password=None,
password=password,
project_name=project_name,
auth_url=auth_url,
- project_domain_id='default',
- user_domain_id='default',
+ project_domain_id="default",
+ user_domain_id="default",
)
session = keystoneauth1.session.Session(
auth=auth,
@@ -73,36 +75,36 @@ def get_session(self, auth_url=None, username=None, password=None,
class IrAttachment(models.Model):
- _inherit = 'ir.attachment'
+ _inherit = "ir.attachment"
def _get_stores(self):
- l = ['swift']
- l += super()._get_stores()
- return l
+ return ["swift"] + super()._get_stores()
@api.model
def _get_swift_connection(self):
- """ Returns a connection object for the Swift object store """
- host = os.environ.get('SWIFT_AUTH_URL')
- account = os.environ.get('SWIFT_ACCOUNT')
- password = os.environ.get('SWIFT_PASSWORD')
- project_name = os.environ.get('SWIFT_PROJECT_NAME')
- if not project_name and os.environ.get('SWIFT_TENANT_NAME'):
- project_name = os.environ['SWIFT_TENANT_NAME']
+ """Returns a connection object for the Swift object store"""
+ host = os.environ.get("SWIFT_AUTH_URL")
+ account = os.environ.get("SWIFT_ACCOUNT")
+ password = os.environ.get("SWIFT_PASSWORD")
+ project_name = os.environ.get("SWIFT_PROJECT_NAME")
+ if not project_name and os.environ.get("SWIFT_TENANT_NAME"):
+ project_name = os.environ["SWIFT_TENANT_NAME"]
_logger.warning(
"SWIFT_TENANT_NAME is deprecated and "
"must be replaced by SWIFT_PROJECT_NAME"
)
- region = os.environ.get('SWIFT_REGION_NAME')
+ region = os.environ.get("SWIFT_REGION_NAME")
os_options = {}
if region:
- os_options['region_name'] = region
+ os_options["region_name"] = region
if not (host and account and password and project_name):
- raise exceptions.UserError(_(
- "Problem connecting to Swift store, are the env variables "
- "(SWIFT_AUTH_URL, SWIFT_ACCOUNT, SWIFT_PASSWORD, "
- "SWIFT_TENANT_NAME) properly set?"
- ))
+ raise exceptions.UserError(
+ _(
+ "Problem connecting to Swift store, are the env variables "
+ "(SWIFT_AUTH_URL, SWIFT_ACCOUNT, SWIFT_PASSWORD, "
+ "SWIFT_TENANT_NAME) properly set?"
+ )
+ )
try:
session = swift_session_store.get_session(
username=account,
@@ -115,13 +117,13 @@ def _get_swift_connection(self):
os_options=os_options,
)
except ClientException:
- _logger.exception('Error connecting to Swift object store')
- raise exceptions.UserError(_('Error on Swift connection'))
+ _logger.exception("Error connecting to Swift object store")
+ raise exceptions.UserError(_("Error on Swift connection")) from None
return conn
@api.model
def _store_file_read(self, fname):
- if fname.startswith('swift://'):
+ if fname.startswith("swift://"):
swifturi = SwiftUri(fname)
try:
conn = self._get_swift_connection()
@@ -129,31 +131,27 @@ def _store_file_read(self, fname):
_logger.exception(
"error reading attachment '%s' from object storage", fname
)
- return ''
+ return ""
try:
- resp, read = conn.get_object(
- swifturi.container(),
- swifturi.item()
- )
+ resp, read = conn.get_object(swifturi.container(), swifturi.item())
except ClientException:
- read = ''
- _logger.exception(
- 'Error reading object from Swift object store')
+ read = ""
+ _logger.exception("Error reading object from Swift object store")
return read
else:
return super()._store_file_read(fname)
def _store_file_write(self, key, bin_data):
- if self._storage() == 'swift':
- container = os.environ.get('SWIFT_WRITE_CONTAINER')
+ if self._storage() == "swift":
+ container = os.environ.get("SWIFT_WRITE_CONTAINER")
conn = self._get_swift_connection()
conn.put_container(container)
- filename = 'swift://{}/{}'.format(container, key)
+ filename = "swift://{}/{}".format(container, key)
try:
conn.put_object(container, key, bin_data)
except ClientException:
- _logger.exception('Error writing to Swift object store')
- raise exceptions.UserError(_('Error writing to Swift'))
+ _logger.exception("Error writing to Swift object store")
+ raise exceptions.UserError(_("Error writing to Swift")) from None
else:
_super = super()
filename = _super._store_file_write(key, bin_data)
@@ -161,19 +159,18 @@ def _store_file_write(self, key, bin_data):
@api.model
def _store_file_delete(self, fname):
- if fname.startswith('swift://'):
+ if fname.startswith("swift://"):
swifturi = SwiftUri(fname)
container = swifturi.container()
# delete the file only if it is on the current configured bucket
# otherwise, we might delete files used on a different environment
- if container == os.environ.get('SWIFT_WRITE_CONTAINER'):
+ if container == os.environ.get("SWIFT_WRITE_CONTAINER"):
conn = self._get_swift_connection()
try:
conn.delete_object(container, swifturi.item())
except ClientException:
- _logger.exception(
- _('Error deleting an object on the Swift store'))
+ _logger.exception(_("Error deleting an object on the Swift store"))
# we ignore the error, file will stay on the object
# storage but won't disrupt the process
else:
- super()._file_delete_from_store(fname)
+ return super()._file_delete_from_store(fname)
diff --git a/attachment_swift/swift_uri.py b/attachment_swift/swift_uri.py
index fdd7ef48..f12c841f 100644
--- a/attachment_swift/swift_uri.py
+++ b/attachment_swift/swift_uri.py
@@ -6,8 +6,7 @@
class SwiftUri(object):
- _url_re = re.compile("^swift:///*([^/]*)/?(.*)",
- re.IGNORECASE | re.UNICODE)
+ _url_re = re.compile("^swift:///*([^/]*)/?(.*)", re.IGNORECASE | re.UNICODE)
def __init__(self, uri):
match = self._url_re.match(uri)
diff --git a/attachment_swift/tests/__init__.py b/attachment_swift/tests/__init__.py
index 506e6b2d..8a60b377 100644
--- a/attachment_swift/tests/__init__.py
+++ b/attachment_swift/tests/__init__.py
@@ -1,2 +1 @@
-
from . import test_mock_swift_api
diff --git a/attachment_swift/tests/test_mock_swift_api.py b/attachment_swift/tests/test_mock_swift_api.py
index 11a54212..a13a80f5 100644
--- a/attachment_swift/tests/test_mock_swift_api.py
+++ b/attachment_swift/tests/test_mock_swift_api.py
@@ -2,30 +2,28 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import base64
-import mock
import os
-from mock import patch
-
import keystoneauth1
+import mock
+from mock import patch
-from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
from odoo.addons.attachment_swift.models.ir_attachment import SwiftSessionStore
from odoo.addons.attachment_swift.swift_uri import SwiftUri
+from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
class TestAttachmentSwift(TestIrAttachment):
-
def setup(self):
- super().setUp()
- self.env['ir.config_parameter'].set_param('ir_attachment.location',
- 'swift')
+ res = super().setUp()
+ self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift")
+ return res
def test_session_store_get_session(self):
- auth_url = 'auth_url'
- username = 'username'
- password = 'password'
- project_name = 'project_name'
+ auth_url = "auth_url"
+ username = "username"
+ password = "password"
+ project_name = "project_name"
store = SwiftSessionStore()
session = store.get_session(
auth_url=auth_url,
@@ -34,10 +32,12 @@ def test_session_store_get_session(self):
project_name=project_name,
)
self.assertEqual(session.auth.auth_url, auth_url)
- self.assertEqual(session.auth.get_cache_id_elements().get(
- 'password_username'), username)
- self.assertEqual(session.auth.get_cache_id_elements().get(
- 'password_password'), password)
+ self.assertEqual(
+ session.auth.get_cache_id_elements().get("password_username"), username
+ )
+ self.assertEqual(
+ session.auth.get_cache_id_elements().get("password_password"), password
+ )
self.assertEqual(session.auth.project_name, project_name)
# get the same session on a second call
@@ -48,73 +48,73 @@ def test_session_store_get_session(self):
password=password,
project_name=project_name,
),
- session
+ session,
)
- @patch('swiftclient.client')
+ @patch("swiftclient.client")
def test_connection(self, mock_swift_client):
- """ Test the connection to the store"""
- os.environ['SWIFT_AUTH_URL'] = 'auth_url'
- os.environ['SWIFT_ACCOUNT'] = 'account'
- os.environ['SWIFT_PASSWORD'] = 'password'
- os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
- os.environ['SWIFT_REGION_NAME'] = 'NOWHERE'
+ """Test the connection to the store"""
+ os.environ["SWIFT_AUTH_URL"] = "auth_url"
+ os.environ["SWIFT_ACCOUNT"] = "account"
+ os.environ["SWIFT_PASSWORD"] = "password"
+ os.environ["SWIFT_PROJECT_NAME"] = "project_name"
+ os.environ["SWIFT_REGION_NAME"] = "NOWHERE"
attachment = self.Attachment
attachment._get_swift_connection()
mock_swift_client.Connection.assert_called_once_with(
session=mock.ANY,
- os_options={'region_name': os.environ.get('SWIFT_REGION_NAME')},
+ os_options={"region_name": os.environ.get("SWIFT_REGION_NAME")},
)
__, kwargs = mock_swift_client.Connection.call_args
- session = kwargs['session']
+ session = kwargs["session"]
self.assertTrue(isinstance(session, keystoneauth1.session.Session))
- self.assertEqual(session.auth.auth_url, os.environ['SWIFT_AUTH_URL'])
- self.assertEqual(session.auth.get_cache_id_elements().get(
- 'password_username'), os.environ['SWIFT_ACCOUNT'])
- self.assertEqual(session.auth.get_cache_id_elements().get(
- 'password_password'), os.environ['SWIFT_PASSWORD'])
- self.assertEqual(session.auth.project_name,
- os.environ['SWIFT_PROJECT_NAME'])
+ self.assertEqual(session.auth.auth_url, os.environ["SWIFT_AUTH_URL"])
+ self.assertEqual(
+ session.auth.get_cache_id_elements().get("password_username"),
+ os.environ["SWIFT_ACCOUNT"],
+ )
+ self.assertEqual(
+ session.auth.get_cache_id_elements().get("password_password"),
+ os.environ["SWIFT_PASSWORD"],
+ )
+ self.assertEqual(session.auth.project_name, os.environ["SWIFT_PROJECT_NAME"])
def test_store_file_on_swift(self):
"""
- Test writing a file
+ Test writing a file
"""
- (self.env['ir.config_parameter'].
- set_param('ir_attachment.location', 'swift'))
- os.environ['SWIFT_AUTH_URL'] = 'auth_url'
- os.environ['SWIFT_ACCOUNT'] = 'account'
- os.environ['SWIFT_PASSWORD'] = 'password'
- os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
- os.environ['SWIFT_WRITE_CONTAINER'] = 'my_container'
- container = os.environ.get('SWIFT_WRITE_CONTAINER')
+ (self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
+ os.environ["SWIFT_AUTH_URL"] = "auth_url"
+ os.environ["SWIFT_ACCOUNT"] = "account"
+ os.environ["SWIFT_PASSWORD"] = "password"
+ os.environ["SWIFT_PROJECT_NAME"] = "project_name"
+ os.environ["SWIFT_WRITE_CONTAINER"] = "my_container"
+ container = os.environ.get("SWIFT_WRITE_CONTAINER")
attachment = self.Attachment
bin_data = base64.b64decode(self.blob1_b64)
- with patch('swiftclient.client.Connection') as MockConnection:
+ with patch("swiftclient.client.Connection") as MockConnection:
conn = MockConnection.return_value
- attachment.create({'name': 'a5', 'datas': self.blob1_b64})
+ attachment.create({"name": "a5", "datas": self.blob1_b64})
conn.put_object.assert_called_with(
- container,
- attachment._compute_checksum(bin_data),
- bin_data)
+ container, attachment._compute_checksum(bin_data), bin_data
+ )
def test_delete_file_on_swift(self):
"""
- Test deleting a file
+ Test deleting a file
"""
- (self.env['ir.config_parameter'].
- set_param('ir_attachment.location', 'swift'))
- os.environ['SWIFT_AUTH_URL'] = 'auth_url'
- os.environ['SWIFT_ACCOUNT'] = 'account'
- os.environ['SWIFT_PASSWORD'] = 'password'
- os.environ['SWIFT_PROJECT_NAME'] = 'project_name'
- os.environ['SWIFT_WRITE_CONTAINER'] = 'my_container'
+ (self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
+ os.environ["SWIFT_AUTH_URL"] = "auth_url"
+ os.environ["SWIFT_ACCOUNT"] = "account"
+ os.environ["SWIFT_PASSWORD"] = "password"
+ os.environ["SWIFT_PROJECT_NAME"] = "project_name"
+ os.environ["SWIFT_WRITE_CONTAINER"] = "my_container"
attachment = self.Attachment
- container = os.environ.get('SWIFT_WRITE_CONTAINER')
- with patch('swiftclient.client.Connection') as MockConnection:
+ container = os.environ.get("SWIFT_WRITE_CONTAINER")
+ with patch("swiftclient.client.Connection") as MockConnection:
conn = MockConnection.return_value
- a5 = attachment.create({'name': 'a5', 'datas': self.blob1_b64})
+ a5 = attachment.create({"name": "a5", "datas": self.blob1_b64})
uri = SwiftUri(a5.store_fname)
a5.unlink()
conn.delete_object.assert_called_with(container, uri.item())
diff --git a/attachment_swift/tests/test_with_swift_store.py b/attachment_swift/tests/test_with_swift_store.py
index e46ba41f..83823b6b 100644
--- a/attachment_swift/tests/test_with_swift_store.py
+++ b/attachment_swift/tests/test_with_swift_store.py
@@ -1,9 +1,11 @@
# Copyright 2017-2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
+from swiftclient.exceptions import ClientException
+
from odoo.addons.base.tests.test_ir_attachment import TestIrAttachment
+
from ..swift_uri import SwiftUri
-from swiftclient.exceptions import ClientException
class TestAttachmentSwift(TestIrAttachment):
@@ -12,28 +14,26 @@ class TestAttachmentSwift(TestIrAttachment):
"""
def setup(self):
- super().setUp()
- self.env['ir.config_parameter'].set_param('ir_attachment.location',
- 'swift')
+ res = super().setUp()
+ self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift")
+ return res
def test_connection(self):
- """ Test the connection to the Swift object store """
+ """Test the connection to the Swift object store"""
conn = self.Attachment._get_swift_connection()
self.assertNotEqual(conn, False)
def test_store_file_on_swift(self):
- """ Test writing a file and then reading it """
- (self.env['ir.config_parameter'].
- set_param('ir_attachment.location', 'swift'))
- a5 = self.Attachment.create({'name': 'a5', 'datas': self.blob1_b64})
+ """Test writing a file and then reading it"""
+ (self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
+ a5 = self.Attachment.create({"name": "a5", "datas": self.blob1_b64})
a5bis = self.Attachment.browse(a5.id)[0]
self.assertEqual(a5.datas, a5bis.datas)
def test_delete_file_on_swift(self):
- """ Create a file and then test the deletion """
- (self.env['ir.config_parameter'].
- set_param('ir_attachment.location', 'swift'))
- a5 = self.Attachment.create({'name': 'a5', 'datas': self.blob1_b64})
+ """Create a file and then test the deletion"""
+ (self.env["ir.config_parameter"].set_param("ir_attachment.location", "swift"))
+ a5 = self.Attachment.create({"name": "a5", "datas": self.blob1_b64})
uri = SwiftUri(a5.store_fname)
con = self.Attachment._get_swift_connection()
con.get_object(uri.container(), uri.item())
diff --git a/base_attachment_object_storage/__manifest__.py b/base_attachment_object_storage/__manifest__.py
index 39cc63aa..c82d990a 100644
--- a/base_attachment_object_storage/__manifest__.py
+++ b/base_attachment_object_storage/__manifest__.py
@@ -10,7 +10,7 @@
"license": "AGPL-3",
"category": "Knowledge Management",
"depends": ["base"],
- "website": "http://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": ["data/res_config_settings_data.xml"],
"installable": True,
"auto_install": True,
diff --git a/base_attachment_object_storage/data/res_config_settings_data.xml b/base_attachment_object_storage/data/res_config_settings_data.xml
index 76c6961d..4a1b8d4b 100644
--- a/base_attachment_object_storage/data/res_config_settings_data.xml
+++ b/base_attachment_object_storage/data/res_config_settings_data.xml
@@ -1,9 +1,11 @@
-
+
ir_attachment.storage.force.database
- {"image/": 51200, "application/javascript": 0, "text/css": 0}
+ {"image/": 51200, "application/javascript": 0, "text/css": 0}
diff --git a/base_attachment_object_storage/models/ir_attachment.py b/base_attachment_object_storage/models/ir_attachment.py
index a4e3ec9d..e8b17f54 100644
--- a/base_attachment_object_storage/models/ir_attachment.py
+++ b/base_attachment_object_storage/models/ir_attachment.py
@@ -5,16 +5,16 @@
import logging
import os
import time
-from .strtobool import strtobool
+from contextlib import closing, contextmanager
import psycopg2
-import odoo
-from contextlib import closing, contextmanager
-from odoo import api, exceptions, models, _
+import odoo
+from odoo import _, api, exceptions, models
from odoo.osv.expression import AND, OR, normalize_domain
from odoo.tools.safe_eval import const_eval
+from .strtobool import strtobool
_logger = logging.getLogger(__name__)
@@ -242,7 +242,7 @@ def _file_delete(self, fname):
if not count:
self._store_file_delete(fname)
else:
- super()._file_delete(fname)
+ return super()._file_delete(fname)
@api.model
def _is_file_from_a_store(self, fname):
@@ -395,7 +395,8 @@ def _force_storage_to_object_storage(self, new_cr=False):
# is required! It's because of an override of _search in ir.attachment
# which adds ('res_field', '=', False) when the domain does not
# contain 'res_field'.
- # https://github.com/odoo/odoo/blob/9032617120138848c63b3cfa5d1913c5e5ad76db/odoo/addons/base/ir/ir_attachment.py#L344-L347
+ # https://github.com/odoo/odoo/blob/9032617120138848c63b3cfa5d1913c5e5ad76db/odoo/addons/base/ir/ir_attachment.py#L344-L347 # noqa: B950
+
domain = [
"!",
("store_fname", "=like", "{}://%".format(storage)),
diff --git a/base_attachment_object_storage/models/strtobool.py b/base_attachment_object_storage/models/strtobool.py
index 44d1eb2c..12f4b828 100644
--- a/base_attachment_object_storage/models/strtobool.py
+++ b/base_attachment_object_storage/models/strtobool.py
@@ -1,21 +1,21 @@
_MAP = {
- 'y': True,
- 'yes': True,
- 't': True,
- 'true': True,
- 'on': True,
- '1': True,
- 'n': False,
- 'no': False,
- 'f': False,
- 'false': False,
- 'off': False,
- '0': False
+ "y": True,
+ "yes": True,
+ "t": True,
+ "true": True,
+ "on": True,
+ "1": True,
+ "n": False,
+ "no": False,
+ "f": False,
+ "false": False,
+ "off": False,
+ "0": False,
}
def strtobool(value):
try:
return _MAP[str(value).lower()]
- except KeyError:
- raise ValueError('"{}" is not a valid bool value'.format(value))
+ except KeyError as error:
+ raise ValueError('"{}" is not a valid bool value'.format(value)) from error
diff --git a/base_fileurl_field/__init__.py b/base_fileurl_field/__init__.py
index 08405c58..73544389 100644
--- a/base_fileurl_field/__init__.py
+++ b/base_fileurl_field/__init__.py
@@ -1,2 +1 @@
from . import fields
-
diff --git a/base_fileurl_field/__manifest__.py b/base_fileurl_field/__manifest__.py
index 8cd5907e..1570e60d 100644
--- a/base_fileurl_field/__manifest__.py
+++ b/base_fileurl_field/__manifest__.py
@@ -6,6 +6,7 @@
"version": "15.0.1.0.0",
"category": "Technical Settings",
"author": "Camptocamp, Odoo Community Association (OCA)",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"license": "AGPL-3",
"depends": [
"base_attachment_object_storage",
diff --git a/base_fileurl_field/fields.py b/base_fileurl_field/fields.py
index 133082af..720db3ac 100644
--- a/base_fileurl_field/fields.py
+++ b/base_fileurl_field/fields.py
@@ -4,7 +4,6 @@
from odoo import fields
-
fields.Field.__doc__ += """
.. _field-fileurl:
@@ -29,10 +28,10 @@
class FileURL(fields.Binary):
_slots = {
- 'attachment': True, # Override default with True
- 'storage_location': '', # External storage activated on the system (cf base_attachment_storage) # noqa
- 'storage_path': '', # Path to be used as storage key (prefix of filename) # noqa
- 'filename': '', # Field to use to store the filename on ir.attachment
+ "attachment": True, # Override default with True
+ "storage_location": "", # External storage activated on the system (cf base_attachment_storage) # noqa
+ "storage_path": "", # Path to be used as storage key (prefix of filename) # noqa
+ "filename": "", # Field to use to store the filename on ir.attachment
}
# pylint: disable=method-required-super
@@ -47,22 +46,22 @@ def create(self, record_values):
if not value:
continue
vals = {
- 'name': self.name,
- 'res_model': self.model_name,
- 'res_field': self.name,
- 'res_id': record.id,
- 'type': 'binary',
- 'datas': value,
+ "name": self.name,
+ "res_model": self.model_name,
+ "res_field": self.name,
+ "res_id": record.id,
+ "type": "binary",
+ "datas": value,
}
fname = False
if self.filename:
fname = record[self.filename]
- vals['datas_fname'] = fname
+ vals["datas_fname"] = fname
if fname and self.storage_path:
storage_key = self._build_storage_key(fname)
if not fname:
storage_key = False
- env['ir.attachment'].sudo().with_context(
+ env["ir.attachment"].sudo().with_context(
binary_field_real_user=env.user,
storage_location=self.storage_location,
force_storage_key=storage_key,
@@ -80,21 +79,22 @@ def write(self, records, value):
storage_location=self.storage_location,
force_storage_key=storage_key,
),
- value
+ value,
)
return True
def _setup_regular_base(self, model):
- super()._setup_regular_base(model)
+ res = super()._setup_regular_base(model)
if self.storage_path:
- assert self.filename is not None, \
+ assert self.filename is not None, (
"Field %s defines storage_path without filename" % self
+ )
+ return res
def _build_storage_key(self, filename):
- return '/'.join([
- self.storage_path.rstrip('/'),
- unicodedata.normalize('NFKC', filename)
- ])
+ return "/".join(
+ [self.storage_path.rstrip("/"), unicodedata.normalize("NFKC", filename)]
+ )
fields.FileURL = FileURL
diff --git a/cloud_platform/__init__.py b/cloud_platform/__init__.py
index a9e33722..0650744f 100644
--- a/cloud_platform/__init__.py
+++ b/cloud_platform/__init__.py
@@ -1,2 +1 @@
-
from . import models
diff --git a/cloud_platform/__manifest__.py b/cloud_platform/__manifest__.py
index 0c5bb58a..fc63b366 100644
--- a/cloud_platform/__manifest__.py
+++ b/cloud_platform/__manifest__.py
@@ -15,7 +15,7 @@
"logging_json",
"server_environment", # OCA/server-tools
],
- "website": "https://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": True,
}
diff --git a/cloud_platform/models/__init__.py b/cloud_platform/models/__init__.py
index fe15395f..5d08f36c 100644
--- a/cloud_platform/models/__init__.py
+++ b/cloud_platform/models/__init__.py
@@ -1,2 +1 @@
-
from . import cloud_platform
diff --git a/cloud_platform/models/cloud_platform.py b/cloud_platform/models/cloud_platform.py
index 65a4d112..e55cfa51 100644
--- a/cloud_platform/models/cloud_platform.py
+++ b/cloud_platform/models/cloud_platform.py
@@ -4,46 +4,39 @@
import logging
import os
import re
-
from collections import namedtuple
-from .strtobool import strtobool
from odoo import api, models
from odoo.tools.config import config
+from .strtobool import strtobool
_logger = logging.getLogger(__name__)
def is_true(strval):
- return bool(strtobool(strval or '0'))
+ return bool(strtobool(strval or "0"))
-PlatformConfig = namedtuple(
- 'PlatformConfig',
- 'filestore'
-)
+PlatformConfig = namedtuple("PlatformConfig", "filestore")
-FilestoreKind = namedtuple(
- 'FilestoreKind',
- ['name', 'location']
-)
+FilestoreKind = namedtuple("FilestoreKind", ["name", "location"])
class CloudPlatform(models.AbstractModel):
- _name = 'cloud.platform'
- _description = 'cloud.platform'
+ _name = "cloud.platform"
+ _description = "cloud.platform"
@api.model
def _default_config(self):
- return PlatformConfig(self._filestore_kinds()['db'])
+ return PlatformConfig(self._filestore_kinds()["db"])
@api.model
def _filestore_kinds(self):
return {
- 'db': FilestoreKind('db', 'local'),
- 'file': FilestoreKind('file', 'local'),
+ "db": FilestoreKind("db", "local"),
+ "file": FilestoreKind("file", "local"),
}
@api.model
@@ -53,33 +46,31 @@ def _platform_kinds(self):
@api.model
def _config_by_server_env(self, platform_kind, environment):
configs_getter = getattr(
- self,
- '_config_by_server_env_for_%s' % platform_kind,
- None
+ self, "_config_by_server_env_for_%s" % platform_kind, None
)
configs = configs_getter() if configs_getter else {}
return configs.get(environment) or self._default_config()
def _get_running_env(self):
- environment_name = config['running_env']
- if environment_name.startswith('labs'):
+ environment_name = config["running_env"]
+ if environment_name.startswith("labs"):
# We allow to have environments such as 'labs-logistics'
# or 'labs-finance', in order to have the matching ribbon.
- environment_name = 'labs'
+ environment_name = "labs"
return environment_name
@api.model
def _install(self, platform_kind):
assert platform_kind in self._platform_kinds()
- params = self.env['ir.config_parameter'].sudo()
- params.set_param('cloud.platform.kind', platform_kind)
+ params = self.env["ir.config_parameter"].sudo()
+ params.set_param("cloud.platform.kind", platform_kind)
environment_name = self._get_running_env()
configs = self._config_by_server_env(platform_kind, environment_name)
- params.set_param('ir_attachment.location', configs.filestore.name)
+ params.set_param("ir_attachment.location", configs.filestore.name)
self.check()
- if configs.filestore.location == 'remote':
- self.env['ir.attachment'].sudo().force_storage()
- _logger.info('cloud platform configured for {}'.format(platform_kind))
+ if configs.filestore.location == "remote":
+ self.env["ir.attachment"].sudo().force_storage()
+ _logger.info("cloud platform configured for {}".format(platform_kind))
@api.model
def install(self):
@@ -91,39 +82,39 @@ def _check_filestore(self, environment_name):
@api.model
def _check_redis(self, environment_name):
- if environment_name in ('prod', 'integration', 'labs', 'test'):
- assert is_true(os.environ.get('ODOO_SESSION_REDIS')), (
+ if environment_name in ("prod", "integration", "labs", "test"):
+ assert is_true(os.environ.get("ODOO_SESSION_REDIS")), (
"Redis must be activated on prod, integration, labs,"
" test instances. This is done by setting ODOO_SESSION_REDIS=1."
)
- assert (os.environ.get('ODOO_SESSION_REDIS_HOST') or
- os.environ.get('ODOO_SESSION_REDIS_SENTINEL_HOST') or
- os.environ.get('ODOO_SESSION_REDIS_URL')), (
+ assert (
+ os.environ.get("ODOO_SESSION_REDIS_HOST")
+ or os.environ.get("ODOO_SESSION_REDIS_SENTINEL_HOST")
+ or os.environ.get("ODOO_SESSION_REDIS_URL")
+ ), (
"ODOO_SESSION_REDIS_HOST or "
"ODOO_SESSION_REDIS_SENTINEL_HOST or "
"ODOO_SESSION_REDIS_URL "
"environment variable is required to connect on Redis"
)
- assert os.environ.get('ODOO_SESSION_REDIS_PREFIX'), (
+ assert os.environ.get("ODOO_SESSION_REDIS_PREFIX"), (
"ODOO_SESSION_REDIS_PREFIX environment variable is required "
"to store sessions on Redis"
)
- prefix = os.environ['ODOO_SESSION_REDIS_PREFIX']
- assert re.match(r'^[a-z-0-9]+-odoo-[a-z-0-9]+$', prefix), (
+ prefix = os.environ["ODOO_SESSION_REDIS_PREFIX"]
+ assert re.match(r"^[a-z-0-9]+-odoo-[a-z-0-9]+$", prefix), (
"ODOO_SESSION_REDIS_PREFIX must match '-odoo-'"
", we got: '%s'" % (prefix,)
)
@api.model
def check(self):
- if is_true(os.environ.get('ODOO_CLOUD_PLATFORM_UNSAFE')):
- _logger.warning(
- "cloud platform checks disabled, this is not safe"
- )
+ if is_true(os.environ.get("ODOO_CLOUD_PLATFORM_UNSAFE")):
+ _logger.warning("cloud platform checks disabled, this is not safe")
return
- params = self.env['ir.config_parameter'].sudo()
- kind = params.get_param('cloud.platform.kind')
+ params = self.env["ir.config_parameter"].sudo()
+ kind = params.get_param("cloud.platform.kind")
if not kind:
_logger.warning(
"cloud platform not configured, you should "
diff --git a/cloud_platform/models/strtobool.py b/cloud_platform/models/strtobool.py
index 44d1eb2c..12f4b828 100644
--- a/cloud_platform/models/strtobool.py
+++ b/cloud_platform/models/strtobool.py
@@ -1,21 +1,21 @@
_MAP = {
- 'y': True,
- 'yes': True,
- 't': True,
- 'true': True,
- 'on': True,
- '1': True,
- 'n': False,
- 'no': False,
- 'f': False,
- 'false': False,
- 'off': False,
- '0': False
+ "y": True,
+ "yes": True,
+ "t": True,
+ "true": True,
+ "on": True,
+ "1": True,
+ "n": False,
+ "no": False,
+ "f": False,
+ "false": False,
+ "off": False,
+ "0": False,
}
def strtobool(value):
try:
return _MAP[str(value).lower()]
- except KeyError:
- raise ValueError('"{}" is not a valid bool value'.format(value))
+ except KeyError as error:
+ raise ValueError('"{}" is not a valid bool value'.format(value)) from error
diff --git a/cloud_platform/songs.py b/cloud_platform/songs.py
index 043fc7ba..1a0f7888 100644
--- a/cloud_platform/songs.py
+++ b/cloud_platform/songs.py
@@ -1,3 +1,2 @@
-
def install(ctx):
- ctx.env['cloud.platform'].install()
+ ctx.env["cloud.platform"].install()
diff --git a/cloud_platform_azure/README.md b/cloud_platform_azure/README.md
index 1f7bd5d1..449ab29b 100644
--- a/cloud_platform_azure/README.md
+++ b/cloud_platform_azure/README.md
@@ -1,5 +1,4 @@
-Cloud Platform Azure
-====================
+# Cloud Platform Azure
Install addons specific to the Azure setup.
diff --git a/cloud_platform_azure/__manifest__.py b/cloud_platform_azure/__manifest__.py
index 30dab452..b1179587 100644
--- a/cloud_platform_azure/__manifest__.py
+++ b/cloud_platform_azure/__manifest__.py
@@ -18,7 +18,7 @@
"cloud_platform_ovh",
"cloud_platform_exoscale",
],
- "website": "https://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": True,
}
diff --git a/cloud_platform_azure/models/cloud_platform.py b/cloud_platform_azure/models/cloud_platform.py
index e93e706a..b72d2294 100644
--- a/cloud_platform_azure/models/cloud_platform.py
+++ b/cloud_platform_azure/models/cloud_platform.py
@@ -1,13 +1,15 @@
# Copyright 2016-2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
-import re
import os
+import re
-from odoo import models, api
-from odoo.addons.cloud_platform.models.cloud_platform import FilestoreKind
-from odoo.addons.cloud_platform.models.cloud_platform import PlatformConfig
+from odoo import api, models
+from odoo.addons.cloud_platform.models.cloud_platform import (
+ FilestoreKind,
+ PlatformConfig,
+)
AZURE_STORE_KIND = FilestoreKind("azure", "remote")
@@ -42,8 +44,7 @@ def _config_by_server_env_for_azure(self):
@api.model
def _check_filestore(self, environment_name):
params = self.env["ir.config_parameter"].sudo()
- use_azure = (params.get_param("ir_attachment.location") ==
- AZURE_STORE_KIND.name)
+ use_azure = params.get_param("ir_attachment.location") == AZURE_STORE_KIND.name
if environment_name in ("prod", "integration"):
# Labs instances use azure by default, but we don't want
# to enforce it in case we want to test something with a different
diff --git a/cloud_platform_exoscale/README.md b/cloud_platform_exoscale/README.md
index f2931eaf..3c815ed0 100644
--- a/cloud_platform_exoscale/README.md
+++ b/cloud_platform_exoscale/README.md
@@ -1,5 +1,4 @@
-Cloud Platform Exoscale
-=======================
+# Cloud Platform Exoscale
Install addons specific to the Exoscale setup.
diff --git a/cloud_platform_exoscale/__manifest__.py b/cloud_platform_exoscale/__manifest__.py
index 55f298b1..45ead050 100644
--- a/cloud_platform_exoscale/__manifest__.py
+++ b/cloud_platform_exoscale/__manifest__.py
@@ -17,7 +17,7 @@
"excludes": [
"cloud_platform_ovh",
],
- "website": "https://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": False,
}
diff --git a/cloud_platform_exoscale/models/cloud_platform.py b/cloud_platform_exoscale/models/cloud_platform.py
index 3a29942b..66702fdb 100644
--- a/cloud_platform_exoscale/models/cloud_platform.py
+++ b/cloud_platform_exoscale/models/cloud_platform.py
@@ -1,50 +1,51 @@
# Copyright 2016-2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
-import re
import os
+import re
-from odoo import models, api
-from odoo.addons.cloud_platform.models.cloud_platform import FilestoreKind
-from odoo.addons.cloud_platform.models.cloud_platform import PlatformConfig
+from odoo import api, models
+from odoo.addons.cloud_platform.models.cloud_platform import (
+ FilestoreKind,
+ PlatformConfig,
+)
-S3_STORE_KIND = FilestoreKind('s3', 'remote')
+S3_STORE_KIND = FilestoreKind("s3", "remote")
class CloudPlatform(models.AbstractModel):
- _inherit = 'cloud.platform'
+ _inherit = "cloud.platform"
@api.model
def _filestore_kinds(self):
kinds = super(CloudPlatform, self)._filestore_kinds()
- kinds['s3'] = S3_STORE_KIND
+ kinds["s3"] = S3_STORE_KIND
return kinds
@api.model
def _platform_kinds(self):
kinds = super(CloudPlatform, self)._platform_kinds()
- kinds.append('exoscale')
+ kinds.append("exoscale")
return kinds
@api.model
def _config_by_server_env_for_exoscale(self):
fs_kinds = self._filestore_kinds()
configs = {
- 'prod': PlatformConfig(filestore=fs_kinds['s3']),
- 'integration': PlatformConfig(filestore=fs_kinds['s3']),
- 'labs': PlatformConfig(filestore=fs_kinds['s3']),
- 'test': PlatformConfig(filestore=fs_kinds['db']),
- 'dev': PlatformConfig(filestore=fs_kinds['db']),
+ "prod": PlatformConfig(filestore=fs_kinds["s3"]),
+ "integration": PlatformConfig(filestore=fs_kinds["s3"]),
+ "labs": PlatformConfig(filestore=fs_kinds["s3"]),
+ "test": PlatformConfig(filestore=fs_kinds["db"]),
+ "dev": PlatformConfig(filestore=fs_kinds["db"]),
}
return configs
@api.model
def _check_filestore(self, environment_name):
- params = self.env['ir.config_parameter'].sudo()
- use_s3 = (params.get_param('ir_attachment.location') ==
- S3_STORE_KIND.name)
- if environment_name in ('prod', 'integration'):
+ params = self.env["ir.config_parameter"].sudo()
+ use_s3 = params.get_param("ir_attachment.location") == S3_STORE_KIND.name
+ if environment_name in ("prod", "integration"):
# Labs instances use s3 by default, but we don't want
# to enforce it in case we want to test something with a different
# storage. At your own risks!
@@ -55,16 +56,16 @@ def _check_filestore(self, environment_name):
"automatically."
)
if use_s3:
- assert os.environ.get('AWS_ACCESS_KEY_ID'), (
+ assert os.environ.get("AWS_ACCESS_KEY_ID"), (
"AWS_ACCESS_KEY_ID environment variable is required when "
"ir_attachment.location is 's3'."
)
- assert os.environ.get('AWS_SECRET_ACCESS_KEY'), (
+ assert os.environ.get("AWS_SECRET_ACCESS_KEY"), (
"AWS_SECRET_ACCESS_KEY environment variable is required when "
"ir_attachment.location is 's3'."
)
- bucket_name = os.environ.get('AWS_BUCKETNAME', '')
- if environment_name in ('prod', 'integration', 'labs'):
+ bucket_name = os.environ.get("AWS_BUCKETNAME", "")
+ if environment_name in ("prod", "integration", "labs"):
assert bucket_name, (
"AWS_BUCKETNAME environment variable is required when "
"ir_attachment.location is 's3'.\n"
@@ -80,10 +81,10 @@ def _check_filestore(self, environment_name):
#
# Use AWS_BUCKETNAME_UNSTRUCTURED to by-pass check on bucket name
# structure
- if os.environ.get('AWS_BUCKETNAME_UNSTRUCTURED'):
+ if os.environ.get("AWS_BUCKETNAME_UNSTRUCTURED"):
return
- prod_bucket = bool(re.match(r'[a-z-0-9]+-odoo-prod', bucket_name))
- if environment_name == 'prod':
+ prod_bucket = bool(re.match(r"[a-z-0-9]+-odoo-prod", bucket_name))
+ if environment_name == "prod":
assert prod_bucket, (
"AWS_BUCKETNAME should match '-odoo-prod', "
"we got: '%s'" % (bucket_name,)
@@ -96,9 +97,9 @@ def _check_filestore(self, environment_name):
"we got: '%s'" % (bucket_name,)
)
- elif environment_name == 'test':
+ elif environment_name == "test":
# store in DB so we don't have files local to the host
- assert params.get_param('ir_attachment.location') == 'db', (
+ assert params.get_param("ir_attachment.location") == "db", (
"In test instances, files must be stored in the database with "
"'ir_attachment.location' set to 'db'. This is "
"automatically set by the function 'install()'."
@@ -106,4 +107,4 @@ def _check_filestore(self, environment_name):
@api.model
def install(self):
- self._install('exoscale')
+ self._install("exoscale")
diff --git a/cloud_platform_ovh/README.md b/cloud_platform_ovh/README.md
index c350eba0..f82fe48d 100644
--- a/cloud_platform_ovh/README.md
+++ b/cloud_platform_ovh/README.md
@@ -1,7 +1,5 @@
-Cloud Platform OVH
-==================
+# Cloud Platform OVH
Install addons specific to the OVH setup.
* The object storage is Swift
-
diff --git a/cloud_platform_ovh/__manifest__.py b/cloud_platform_ovh/__manifest__.py
index 6467f581..8f22580e 100644
--- a/cloud_platform_ovh/__manifest__.py
+++ b/cloud_platform_ovh/__manifest__.py
@@ -17,7 +17,7 @@
"excludes": [
"cloud_platform_exoscale",
],
- "website": "https://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": False,
}
diff --git a/cloud_platform_ovh/models/cloud_platform.py b/cloud_platform_ovh/models/cloud_platform.py
index c4e02165..bed4db9a 100644
--- a/cloud_platform_ovh/models/cloud_platform.py
+++ b/cloud_platform_ovh/models/cloud_platform.py
@@ -1,51 +1,51 @@
# Copyright 2017-2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
-import re
import os
+import re
from odoo import api, models
-from odoo.addons.cloud_platform.models.cloud_platform import FilestoreKind
-from odoo.addons.cloud_platform.models.cloud_platform import PlatformConfig
-
+from odoo.addons.cloud_platform.models.cloud_platform import (
+ FilestoreKind,
+ PlatformConfig,
+)
-SWIFT_STORE_KIND = FilestoreKind('swift', 'remote')
+SWIFT_STORE_KIND = FilestoreKind("swift", "remote")
class CloudPlatform(models.AbstractModel):
- _inherit = 'cloud.platform'
+ _inherit = "cloud.platform"
@api.model
def _filestore_kinds(self):
kinds = super(CloudPlatform, self)._filestore_kinds()
- kinds['swift'] = SWIFT_STORE_KIND
+ kinds["swift"] = SWIFT_STORE_KIND
return kinds
@api.model
def _platform_kinds(self):
kinds = super()._platform_kinds()
- kinds.append('ovh')
+ kinds.append("ovh")
return kinds
@api.model
def _config_by_server_env_for_ovh(self):
fs_kinds = self._filestore_kinds()
configs = {
- 'prod': PlatformConfig(filestore=fs_kinds['swift']),
- 'integration': PlatformConfig(filestore=fs_kinds['swift']),
- 'labs': PlatformConfig(filestore=fs_kinds['swift']),
- 'test': PlatformConfig(filestore=fs_kinds['db']),
- 'dev': PlatformConfig(filestore=fs_kinds['db']),
+ "prod": PlatformConfig(filestore=fs_kinds["swift"]),
+ "integration": PlatformConfig(filestore=fs_kinds["swift"]),
+ "labs": PlatformConfig(filestore=fs_kinds["swift"]),
+ "test": PlatformConfig(filestore=fs_kinds["db"]),
+ "dev": PlatformConfig(filestore=fs_kinds["db"]),
}
return configs
@api.model
def _check_filestore(self, environment_name):
- params = self.env['ir.config_parameter'].sudo()
- use_swift = (params.get_param('ir_attachment.location') ==
- SWIFT_STORE_KIND.name)
- if environment_name in ('prod', 'integration'):
+ params = self.env["ir.config_parameter"].sudo()
+ use_swift = params.get_param("ir_attachment.location") == SWIFT_STORE_KIND.name
+ if environment_name in ("prod", "integration"):
# Labs instances use swift by default, but we don't want
# to enforce it in case we want to test something with a different
# storage. At your own risks!
@@ -56,20 +56,20 @@ def _check_filestore(self, environment_name):
"automatically."
)
if use_swift:
- assert os.environ.get('SWIFT_AUTH_URL'), (
+ assert os.environ.get("SWIFT_AUTH_URL"), (
"SWIFT_AUTH_URL environment variable is required when "
"ir_attachment.location is 'swift'."
)
- assert os.environ.get('SWIFT_ACCOUNT'), (
+ assert os.environ.get("SWIFT_ACCOUNT"), (
"SWIFT_ACCOUNT environment variable is required when "
"ir_attachment.location is 'swift'."
)
- assert os.environ.get('SWIFT_PASSWORD'), (
+ assert os.environ.get("SWIFT_PASSWORD"), (
"SWIFT_PASSWORD environment variable is required when "
"ir_attachment.location is 'swift'."
)
- container_name = os.environ.get('SWIFT_WRITE_CONTAINER', '')
- if environment_name in ('prod', 'integration', 'labs'):
+ container_name = os.environ.get("SWIFT_WRITE_CONTAINER", "")
+ if environment_name in ("prod", "integration", "labs"):
assert container_name, (
"SWIFT_WRITE_CONTAINER environment variable is required when "
"ir_attachment.location is 'swift'.\n"
@@ -80,16 +80,15 @@ def _check_filestore(self, environment_name):
"If you don't actually need a bucket, change the"
" 'ir_attachment.location' parameter."
)
- prod_container = bool(re.match(r'[a-z0-9-]+-odoo-prod',
- container_name))
+ prod_container = bool(re.match(r"[a-z0-9-]+-odoo-prod", container_name))
# A bucket name is defined under the following format
# -odoo-
#
# Use SWIFT_WRITE_CONTAINER_UNSTRUCTURED to by-pass check on bucket name
# structure
- if os.environ.get('SWIFT_WRITE_CONTAINER_UNSTRUCTURED'):
+ if os.environ.get("SWIFT_WRITE_CONTAINER_UNSTRUCTURED"):
return
- if environment_name == 'prod':
+ if environment_name == "prod":
assert prod_container, (
"SWIFT_WRITE_CONTAINER should match '-odoo-prod', "
"we got: '%s'" % (container_name,)
@@ -101,9 +100,9 @@ def _check_filestore(self, environment_name):
"SWIFT_WRITE_CONTAINER should not match "
"'-odoo-prod', we got: '%s'" % (container_name,)
)
- elif environment_name == 'test':
+ elif environment_name == "test":
# store in DB so we don't have files local to the host
- assert params.get_param('ir_attachment.location') == 'db', (
+ assert params.get_param("ir_attachment.location") == "db", (
"In test instances, files must be stored in the database with "
"'ir_attachment.location' set to 'db'. This is "
"automatically set by the function 'install()'."
@@ -111,4 +110,4 @@ def _check_filestore(self, environment_name):
@api.model
def install(self):
- self._install('ovh')
+ self._install("ovh")
diff --git a/kwkhtmltopdf_assets/__init__.py b/kwkhtmltopdf_assets/__init__.py
index a9e33722..0650744f 100644
--- a/kwkhtmltopdf_assets/__init__.py
+++ b/kwkhtmltopdf_assets/__init__.py
@@ -1,2 +1 @@
-
from . import models
diff --git a/kwkhtmltopdf_assets/__manifest__.py b/kwkhtmltopdf_assets/__manifest__.py
index 02b1ab9d..c0d30249 100644
--- a/kwkhtmltopdf_assets/__manifest__.py
+++ b/kwkhtmltopdf_assets/__manifest__.py
@@ -11,7 +11,7 @@
"depends": [
"base",
],
- "website": "http://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": True,
}
diff --git a/kwkhtmltopdf_assets/models/__init__.py b/kwkhtmltopdf_assets/models/__init__.py
index 0e71e216..33ac73ee 100644
--- a/kwkhtmltopdf_assets/models/__init__.py
+++ b/kwkhtmltopdf_assets/models/__init__.py
@@ -1,2 +1 @@
-
from . import ir_qweb
diff --git a/kwkhtmltopdf_assets/models/ir_qweb.py b/kwkhtmltopdf_assets/models/ir_qweb.py
index 354fe300..9025b96c 100644
--- a/kwkhtmltopdf_assets/models/ir_qweb.py
+++ b/kwkhtmltopdf_assets/models/ir_qweb.py
@@ -1,8 +1,8 @@
# Copyright 2016-2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
-from odoo.tools import config
from odoo import models
+from odoo.tools import config
class IrQweb(models.AbstractModel):
diff --git a/logging_json/__init__.py b/logging_json/__init__.py
index 1f1aa382..9f2ed663 100644
--- a/logging_json/__init__.py
+++ b/logging_json/__init__.py
@@ -1,2 +1 @@
-
from . import json_log
diff --git a/logging_json/__manifest__.py b/logging_json/__manifest__.py
index c94984e8..42475b8b 100644
--- a/logging_json/__manifest__.py
+++ b/logging_json/__manifest__.py
@@ -13,7 +13,7 @@
"external_dependencies": {
"python": ["python-json-logger"],
},
- "website": "http://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": True,
}
diff --git a/logging_json/json_log.py b/logging_json/json_log.py
index 60313b38..cc7efcb4 100644
--- a/logging_json/json_log.py
+++ b/logging_json/json_log.py
@@ -6,10 +6,10 @@
import threading
import uuid
-from .strtobool import strtobool
-
from odoo import http
+from .strtobool import strtobool
+
_logger = logging.getLogger(__name__)
try:
diff --git a/logging_json/strtobool.py b/logging_json/strtobool.py
index 44d1eb2c..12f4b828 100644
--- a/logging_json/strtobool.py
+++ b/logging_json/strtobool.py
@@ -1,21 +1,21 @@
_MAP = {
- 'y': True,
- 'yes': True,
- 't': True,
- 'true': True,
- 'on': True,
- '1': True,
- 'n': False,
- 'no': False,
- 'f': False,
- 'false': False,
- 'off': False,
- '0': False
+ "y": True,
+ "yes": True,
+ "t": True,
+ "true": True,
+ "on": True,
+ "1": True,
+ "n": False,
+ "no": False,
+ "f": False,
+ "false": False,
+ "off": False,
+ "0": False,
}
def strtobool(value):
try:
return _MAP[str(value).lower()]
- except KeyError:
- raise ValueError('"{}" is not a valid bool value'.format(value))
+ except KeyError as error:
+ raise ValueError('"{}" is not a valid bool value'.format(value)) from error
diff --git a/monitoring_log_requests/__init__.py b/monitoring_log_requests/__init__.py
index a9e33722..0650744f 100644
--- a/monitoring_log_requests/__init__.py
+++ b/monitoring_log_requests/__init__.py
@@ -1,2 +1 @@
-
from . import models
diff --git a/monitoring_log_requests/__manifest__.py b/monitoring_log_requests/__manifest__.py
index bbc6cea3..687c37e0 100644
--- a/monitoring_log_requests/__manifest__.py
+++ b/monitoring_log_requests/__manifest__.py
@@ -9,7 +9,7 @@
"license": "AGPL-3",
"category": "category",
"depends": ["base", "web"],
- "website": "http://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": False,
}
diff --git a/monitoring_log_requests/models/__init__.py b/monitoring_log_requests/models/__init__.py
index 0d643ba0..9a5eb718 100644
--- a/monitoring_log_requests/models/__init__.py
+++ b/monitoring_log_requests/models/__init__.py
@@ -1,2 +1 @@
-
from . import ir_http
diff --git a/monitoring_log_requests/models/ir_http.py b/monitoring_log_requests/models/ir_http.py
index 559115da..3df49231 100644
--- a/monitoring_log_requests/models/ir_http.py
+++ b/monitoring_log_requests/models/ir_http.py
@@ -9,7 +9,6 @@
from odoo.http import request as http_request
from odoo.tools.config import config
-
_logger = logging.getLogger("monitoring.http.requests")
diff --git a/monitoring_prometheus/__manifest__.py b/monitoring_prometheus/__manifest__.py
index 6809e1b1..5745fa30 100644
--- a/monitoring_prometheus/__manifest__.py
+++ b/monitoring_prometheus/__manifest__.py
@@ -13,7 +13,7 @@
"web",
"server_environment",
],
- "website": "http://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"external_dependencies": {
"python": ["prometheus_client"],
diff --git a/monitoring_prometheus/controllers/prometheus_metrics.py b/monitoring_prometheus/controllers/prometheus_metrics.py
index 411a2acf..4aa49960 100644
--- a/monitoring_prometheus/controllers/prometheus_metrics.py
+++ b/monitoring_prometheus/controllers/prometheus_metrics.py
@@ -1,11 +1,12 @@
# Copyright 2016-2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
-from odoo.http import Controller, route
from prometheus_client import generate_latest
+from odoo.http import Controller, route
+
class PrometheusController(Controller):
- @route('/metrics', auth='public')
+ @route("/metrics", auth="public")
def metrics(self):
return generate_latest()
diff --git a/monitoring_prometheus/models/ir_http.py b/monitoring_prometheus/models/ir_http.py
index 050ca5bb..b2316c81 100644
--- a/monitoring_prometheus/models/ir_http.py
+++ b/monitoring_prometheus/models/ir_http.py
@@ -1,10 +1,10 @@
# Copyright 2016-2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
+from prometheus_client import Counter, Summary
+
from odoo import models
from odoo.http import request
-from prometheus_client import Summary, Counter
-
REQUEST_TIME = Summary(
"request_latency_sec", "Request response time in sec", ["query_type"]
diff --git a/monitoring_statsd/__init__.py b/monitoring_statsd/__init__.py
index a9e33722..0650744f 100644
--- a/monitoring_statsd/__init__.py
+++ b/monitoring_statsd/__init__.py
@@ -1,2 +1 @@
-
from . import models
diff --git a/monitoring_statsd/__manifest__.py b/monitoring_statsd/__manifest__.py
index 0d811c52..d841d58b 100644
--- a/monitoring_statsd/__manifest__.py
+++ b/monitoring_statsd/__manifest__.py
@@ -13,7 +13,7 @@
"web",
"server_environment",
],
- "website": "http://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"external_dependencies": {
"python": ["statsd"],
diff --git a/monitoring_statsd/models/__init__.py b/monitoring_statsd/models/__init__.py
index 0d643ba0..9a5eb718 100644
--- a/monitoring_statsd/models/__init__.py
+++ b/monitoring_statsd/models/__init__.py
@@ -1,2 +1 @@
-
from . import ir_http
diff --git a/monitoring_statsd/models/ir_http.py b/monitoring_statsd/models/ir_http.py
index db430d9f..7b7d47a0 100644
--- a/monitoring_statsd/models/ir_http.py
+++ b/monitoring_statsd/models/ir_http.py
@@ -4,38 +4,46 @@
from odoo import models
from odoo.http import request
-from ..statsd_client import statsd, customer, environment
+from ..statsd_client import customer, environment, statsd
class IrHttp(models.AbstractModel):
- _inherit = 'ir.http'
+ _inherit = "ir.http"
@classmethod
def _dispatch(cls, endpoint):
if not statsd:
return super()._dispatch(endpoint)
- path_info = request.httprequest.environ.get('PATH_INFO')
- if path_info.startswith('/longpolling/'):
+ path_info = request.httprequest.environ.get("PATH_INFO")
+ if path_info.startswith("/longpolling/"):
return super()._dispatch(endpoint)
- parts = ['http', ]
- if path_info.startswith('/web/dataset/call_button'):
- parts += ['button',
- customer, environment,
- request.params['model'].replace('.', '_'),
- request.params['method'],
- ]
- elif path_info.startswith('/web/dataset/exec_workflow'):
- parts += ['workflow',
- customer, environment,
- request.params['model'].replace('.', '_'),
- request.params['signal'],
- ]
+ parts = [
+ "http",
+ ]
+ if path_info.startswith("/web/dataset/call_button"):
+ parts += [
+ "button",
+ customer,
+ environment,
+ request.params["model"].replace(".", "_"),
+ request.params["method"],
+ ]
+ elif path_info.startswith("/web/dataset/exec_workflow"):
+ parts += [
+ "workflow",
+ customer,
+ environment,
+ request.params["model"].replace(".", "_"),
+ request.params["signal"],
+ ]
else:
- parts += ['request',
- customer, environment,
- ]
+ parts += [
+ "request",
+ customer,
+ environment,
+ ]
- with statsd.timer('.'.join(parts)):
+ with statsd.timer(".".join(parts)):
return super()._dispatch(endpoint)
diff --git a/monitoring_statsd/models/strtobool.py b/monitoring_statsd/models/strtobool.py
index 44d1eb2c..6a6881c8 100644
--- a/monitoring_statsd/models/strtobool.py
+++ b/monitoring_statsd/models/strtobool.py
@@ -1,16 +1,16 @@
_MAP = {
- 'y': True,
- 'yes': True,
- 't': True,
- 'true': True,
- 'on': True,
- '1': True,
- 'n': False,
- 'no': False,
- 'f': False,
- 'false': False,
- 'off': False,
- '0': False
+ "y": True,
+ "yes": True,
+ "t": True,
+ "true": True,
+ "on": True,
+ "1": True,
+ "n": False,
+ "no": False,
+ "f": False,
+ "false": False,
+ "off": False,
+ "0": False,
}
diff --git a/monitoring_status/__manifest__.py b/monitoring_status/__manifest__.py
index 9a00dfb2..37dad5c6 100644
--- a/monitoring_status/__manifest__.py
+++ b/monitoring_status/__manifest__.py
@@ -9,7 +9,7 @@
"license": "AGPL-3",
"category": "category",
"depends": ["base", "web"],
- "website": "http://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": True,
}
diff --git a/monitoring_status/controllers/main.py b/monitoring_status/controllers/main.py
index c2463eb4..21b6893c 100644
--- a/monitoring_status/controllers/main.py
+++ b/monitoring_status/controllers/main.py
@@ -1,18 +1,18 @@
# Copyright 2016-2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
-import logging
import json
+import logging
import werkzeug
from odoo import http
+
from odoo.addons.web.controllers.main import ensure_db
class HealthCheckFilter(logging.Filter):
-
- def __init__(self, path, name=''):
+ def __init__(self, path, name=""):
super().__init__(name)
self.path = path
@@ -20,20 +20,19 @@ def filter(self, record):
return self.path not in record.getMessage()
-logging.getLogger('werkzeug').addFilter(
- HealthCheckFilter('GET /monitoring/status HTTP')
+logging.getLogger("werkzeug").addFilter(
+ HealthCheckFilter("GET /monitoring/status HTTP")
)
class Monitoring(http.Controller):
-
- @http.route('/monitoring/status', type='http', auth='none')
+ @http.route("/monitoring/status", type="http", auth="none")
def status(self):
ensure_db()
# TODO: add 'sub-systems' status and infos:
# queue job, cron, database, ...
- headers = {'Content-Type': 'application/json'}
- info = {'status': 1}
+ headers = {"Content-Type": "application/json"}
+ info = {"status": 1}
session = http.request.session
# We set a custom expiration of 1 second for this request, as we do a
# lot of health checks, we don't want those anonymous sessions to be
diff --git a/session_redis/__init__.py b/session_redis/__init__.py
index 8f2ee9ec..a64bcb4b 100644
--- a/session_redis/__init__.py
+++ b/session_redis/__init__.py
@@ -1,3 +1,2 @@
-
from . import http
from . import session
diff --git a/session_redis/__manifest__.py b/session_redis/__manifest__.py
index 6a7240a0..e4acaf78 100644
--- a/session_redis/__manifest__.py
+++ b/session_redis/__manifest__.py
@@ -13,7 +13,7 @@
"external_dependencies": {
"python": ["redis"],
},
- "website": "http://www.camptocamp.com",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"data": [],
"installable": True,
}
diff --git a/session_redis/http.py b/session_redis/http.py
index ad6e3949..64595296 100644
--- a/session_redis/http.py
+++ b/session_redis/http.py
@@ -4,13 +4,12 @@
import logging
import os
-from .strtobool import strtobool
-
from odoo import http
from odoo.tools import config
from odoo.tools.func import lazy_property
from .session import RedisSessionStore
+from .strtobool import strtobool
_logger = logging.getLogger(__name__)
@@ -52,10 +51,13 @@ def session_store(self):
redis_client = redis.from_url(url)
else:
redis_client = redis.Redis(host=host, port=port, password=password)
- return RedisSessionStore(redis=redis_client, prefix=prefix,
- expiration=expiration,
- anon_expiration=anon_expiration,
- session_class=http.Session)
+ return RedisSessionStore(
+ redis=redis_client,
+ prefix=prefix,
+ expiration=expiration,
+ anon_expiration=anon_expiration,
+ session_class=http.Session,
+ )
def purge_fs_sessions(path):
@@ -64,7 +66,7 @@ def purge_fs_sessions(path):
try:
os.unlink(path)
except OSError:
- pass
+ _logger.warning("OS Error during purge of redis sessions.")
if is_true(os.environ.get("ODOO_SESSION_REDIS")):
@@ -77,8 +79,12 @@ def purge_fs_sessions(path):
sentinel_port,
)
else:
- _logger.debug("HTTP sessions stored in Redis with prefix '%s' on "
- "%s:%s", prefix or '', host, port)
+ _logger.debug(
+ "HTTP sessions stored in Redis with prefix '%s' on " "%s:%s",
+ prefix or "",
+ host,
+ port,
+ )
http.Application.session_store = session_store
# clean the existing sessions on the file system
purge_fs_sessions(config.session_dir)
diff --git a/session_redis/json_encoding.py b/session_redis/json_encoding.py
index f535a8f0..bb1c57a1 100644
--- a/session_redis/json_encoding.py
+++ b/session_redis/json_encoding.py
@@ -2,7 +2,6 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import json
-
from datetime import date, datetime
import dateutil
diff --git a/session_redis/session.py b/session_redis/session.py
index 57ab9e02..9854268c 100644
--- a/session_redis/session.py
+++ b/session_redis/session.py
@@ -18,10 +18,16 @@
class RedisSessionStore(SessionStore):
- """ SessionStore that saves session to redis """
-
- def __init__(self, redis, session_class=None,
- prefix='', expiration=None, anon_expiration=None):
+ """SessionStore that saves session to redis"""
+
+ def __init__(
+ self,
+ redis,
+ session_class=None,
+ prefix="",
+ expiration=None,
+ anon_expiration=None,
+ ):
super().__init__(session_class=session_class)
self.redis = redis
if expiration is None:
@@ -32,14 +38,12 @@ def __init__(self, redis, session_class=None,
self.anon_expiration = DEFAULT_SESSION_TIMEOUT_ANONYMOUS
else:
self.anon_expiration = anon_expiration
- self.prefix = 'session:'
+ self.prefix = "session:"
if prefix:
- self.prefix = '%s:%s:' % (
- self.prefix, prefix
- )
+ self.prefix = "%s:%s:" % (self.prefix, prefix)
def build_key(self, sid):
- return '%s%s' % (self.prefix, sid)
+ return "%s%s" % (self.prefix, sid)
def save(self, session):
key = self.build_key(session.sid)
@@ -52,51 +56,59 @@ def save(self, session):
expiration = session.expiration or self.anon_expiration
if _logger.isEnabledFor(logging.DEBUG):
if session.uid:
- user_msg = "user '%s' (id: %s)" % (
- session.login, session.uid)
+ user_msg = "user '%s' (id: %s)" % (session.login, session.uid)
else:
user_msg = "anonymous user"
- _logger.debug("saving session with key '%s' and "
- "expiration of %s seconds for %s",
- key, expiration, user_msg)
+ _logger.debug(
+ "saving session with key '%s' and " "expiration of %s seconds for %s",
+ key,
+ expiration,
+ user_msg,
+ )
- data = json.dumps(
- dict(session), cls=json_encoding.SessionEncoder
- ).encode('utf-8')
+ data = json.dumps(dict(session), cls=json_encoding.SessionEncoder).encode(
+ "utf-8"
+ )
if self.redis.set(key, data):
return self.redis.expire(key, expiration)
def delete(self, session):
key = self.build_key(session.sid)
- _logger.debug('deleting session with key %s', key)
+ _logger.debug("deleting session with key %s", key)
return self.redis.delete(key)
def get(self, sid):
if not self.is_valid_key(sid):
- _logger.debug("session with invalid sid '%s' has been asked, "
- "returning a new one", sid)
+ _logger.debug(
+ "session with invalid sid '%s' has been asked, " "returning a new one",
+ sid,
+ )
return self.new()
key = self.build_key(sid)
saved = self.redis.get(key)
if not saved:
- _logger.debug("session with non-existent key '%s' has been asked, "
- "returning a new one", key)
+ _logger.debug(
+ "session with non-existent key '%s' has been asked, "
+ "returning a new one",
+ key,
+ )
return self.new()
try:
- data = json.loads(
- saved.decode('utf-8'), cls=json_encoding.SessionDecoder
- )
+ data = json.loads(saved.decode("utf-8"), cls=json_encoding.SessionDecoder)
except ValueError:
- _logger.debug("session for key '%s' has been asked but its json "
- "content could not be read, it has been reset", key)
+ _logger.debug(
+ "session for key '%s' has been asked but its json "
+ "content could not be read, it has been reset",
+ key,
+ )
data = {}
return self.session_class(data, sid, False)
def list(self):
- keys = self.redis.keys('%s*' % self.prefix)
+ keys = self.redis.keys("%s*" % self.prefix)
_logger.debug("a listing redis keys has been called")
- return [key[len(self.prefix):] for key in keys]
+ return [key[len(self.prefix) :] for key in keys]
def rotate(self, session, env):
self.delete(session)
@@ -106,7 +118,7 @@ def rotate(self, session, env):
self.save(session)
def vacuum(self):
- """ Do not garbage collect the sessions
+ """Do not garbage collect the sessions
Redis keys are automatically cleaned at the end of their
expiration.
diff --git a/session_redis/strtobool.py b/session_redis/strtobool.py
index 44d1eb2c..12f4b828 100644
--- a/session_redis/strtobool.py
+++ b/session_redis/strtobool.py
@@ -1,21 +1,21 @@
_MAP = {
- 'y': True,
- 'yes': True,
- 't': True,
- 'true': True,
- 'on': True,
- '1': True,
- 'n': False,
- 'no': False,
- 'f': False,
- 'false': False,
- 'off': False,
- '0': False
+ "y": True,
+ "yes": True,
+ "t": True,
+ "true": True,
+ "on": True,
+ "1": True,
+ "n": False,
+ "no": False,
+ "f": False,
+ "false": False,
+ "off": False,
+ "0": False,
}
def strtobool(value):
try:
return _MAP[str(value).lower()]
- except KeyError:
- raise ValueError('"{}" is not a valid bool value'.format(value))
+ except KeyError as error:
+ raise ValueError('"{}" is not a valid bool value'.format(value)) from error
diff --git a/test_base_fileurl_field/__manifest__.py b/test_base_fileurl_field/__manifest__.py
index 5aeaee07..9d084dfa 100644
--- a/test_base_fileurl_field/__manifest__.py
+++ b/test_base_fileurl_field/__manifest__.py
@@ -6,10 +6,9 @@
"version": "12.0.1.0.0",
"category": "Tests",
"author": "Camptocamp,Odoo Community Association (OCA)",
+ "website": "https://github.com/camptocamp/odoo-cloud-platform",
"license": "AGPL-3",
- "depends": [
- "base_fileurl_field"
- ],
+ "depends": ["base_fileurl_field"],
"data": [
"views/res_partner.xml",
"views/res_users.xml",
diff --git a/test_base_fileurl_field/data/sample.txt b/test_base_fileurl_field/data/sample.txt
index 5251e0f1..8a03e0e5 100644
--- a/test_base_fileurl_field/data/sample.txt
+++ b/test_base_fileurl_field/data/sample.txt
@@ -1 +1 @@
-This is a simple text file.
\ No newline at end of file
+This is a simple text file.
diff --git a/test_base_fileurl_field/models/res_partner.py b/test_base_fileurl_field/models/res_partner.py
index 359843c9..48ca0a44 100644
--- a/test_base_fileurl_field/models/res_partner.py
+++ b/test_base_fileurl_field/models/res_partner.py
@@ -1,44 +1,46 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
-from odoo import models, fields, api, _
+from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ResPartner(models.Model):
- _inherit = 'res.partner'
+ _inherit = "res.partner"
name = fields.Char()
url_file = fields.FileURL(
- storage_location='s3',
- filename='url_file_fname',
- storage_path='partner'
+ storage_location="s3", filename="url_file_fname", storage_path="partner"
)
url_file_fname = fields.Char()
url_image = fields.FileURL(
- storage_location='s3',
- filename='url_image_fname',
- storage_path='partner_image',
+ storage_location="s3",
+ filename="url_image_fname",
+ storage_path="partner_image",
)
url_image_fname = fields.Char()
- @api.constrains('url_file', 'url_file_fname')
+ @api.constrains("url_file", "url_file_fname")
def _check_url_file_fname(self):
- rec = self.search([('url_file_fname', '=', self.url_file_fname)])
+ rec = self.search([("url_file_fname", "=", self.url_file_fname)])
if len(rec) > 1:
- raise ValidationError(_(
- "This file name is already used on an existing record. "
- "Please use another file name or delete the url_file on :\n"
- "Model: %s Id: %s" % (self._name, rec.id)
- ))
+ raise ValidationError(
+ _(
+ "This file name is already used on an existing record. "
+ "Please use another file name or delete the url_file on :\n"
+ "Model: %s Id: %s" % (self._name, rec.id)
+ )
+ )
- @api.constrains('url_image', 'url_image_fname')
+ @api.constrains("url_image", "url_image_fname")
def _check_url_image_fname(self):
- rec = self.search([('url_image_fname', '=', self.url_image_fname)])
+ rec = self.search([("url_image_fname", "=", self.url_image_fname)])
if len(rec) > 1:
- raise ValidationError(_(
- "This file name is already used on an existing record. "
- "Please use another file name or delete the url_image on :\n"
- "Model: %s Id: %s" % (self._name, rec.id)
- ))
+ raise ValidationError(
+ _(
+ "This file name is already used on an existing record. "
+ "Please use another file name or delete the url_image on :\n"
+ "Model: %s Id: %s" % (self._name, rec.id)
+ )
+ )
diff --git a/test_base_fileurl_field/models/res_users.py b/test_base_fileurl_field/models/res_users.py
index da0b80b2..c8bc3240 100644
--- a/test_base_fileurl_field/models/res_users.py
+++ b/test_base_fileurl_field/models/res_users.py
@@ -1,11 +1,11 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
-from odoo import models, fields
+from odoo import fields, models
class ResUsers(models.Model):
- _inherit = 'res.users'
+ _inherit = "res.users"
- partner_url_file = fields.FileURL(related='partner_id.url_file')
- partner_url_file_fname = fields.Char(related='partner_id.url_file_fname')
+ partner_url_file = fields.FileURL(related="partner_id.url_file")
+ partner_url_file_fname = fields.Char(related="partner_id.url_file_fname")
diff --git a/test_base_fileurl_field/tests/ir_attachment.py b/test_base_fileurl_field/tests/ir_attachment.py
index d07017db..88035717 100644
--- a/test_base_fileurl_field/tests/ir_attachment.py
+++ b/test_base_fileurl_field/tests/ir_attachment.py
@@ -3,7 +3,7 @@
import logging
-from odoo import models, api
+from odoo import api, models
_logger = logging.getLogger(__name__)
@@ -14,23 +14,23 @@ class IrAttachment(models.Model):
_inherit = "ir.attachment"
def _get_stores(self):
- l = ['s3']
+ l = ["s3"]
l += super(IrAttachment, self)._get_stores()
return l
@api.model
def _store_file_read(self, fname, bin_size=False):
- if fname.startswith('s3://'):
+ if fname.startswith("s3://"):
return FAKE_S3_BUCKET.get(fname)
else:
return super(IrAttachment, self)._store_file_read(fname, bin_size)
@api.model
def _store_file_write(self, key, bin_data):
- location = self.env.context.get('storage_location') or self._storage()
- if location == 's3':
+ location = self.env.context.get("storage_location") or self._storage()
+ if location == "s3":
FAKE_S3_BUCKET[key] = bin_data
- filename = 's3://fake_bucket/%s' % key
+ filename = "s3://fake_bucket/%s" % key
else:
_super = super(IrAttachment, self)
filename = _super._store_file_write(key, bin_data)
@@ -38,7 +38,7 @@ def _store_file_write(self, key, bin_data):
@api.model
def _store_file_delete(self, fname):
- if fname.startswith('s3://'):
+ if fname.startswith("s3://"):
FAKE_S3_BUCKET.pop(fname)
else:
super(IrAttachment, self)._store_file_delete(fname)
diff --git a/test_base_fileurl_field/tests/test_fileurl_fields.py b/test_base_fileurl_field/tests/test_fileurl_fields.py
index c56bbe15..8be2bec3 100644
--- a/test_base_fileurl_field/tests/test_fileurl_fields.py
+++ b/test_base_fileurl_field/tests/test_fileurl_fields.py
@@ -2,38 +2,41 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import base64
-from odoo.tests import TransactionCase
-from odoo.modules.module import get_module_resource
from odoo.exceptions import ValidationError
+from odoo.modules.module import get_module_resource
+from odoo.tests import TransactionCase
class TestFileUrlFields(TransactionCase):
-
def test_fileurl_fields(self):
- file_path = get_module_resource('test_base_fileurl_field', 'data',
- 'sample.txt')
- image_path = get_module_resource('test_base_fileurl_field', 'data',
- 'pattern.png')
- partner = self.env.ref('base.main_partner')
- with open(file_path, 'rb') as f:
- with open(image_path, 'rb') as i:
- partner.write({
- 'url_file': base64.b64encode(f.read()),
- 'url_file_fname': 'sample.txt',
- 'url_image': base64.b64encode(i.read()),
- 'url_image_fname': 'pattern.png',
- })
+ file_path = get_module_resource("test_base_fileurl_field", "data", "sample.txt")
+ image_path = get_module_resource(
+ "test_base_fileurl_field", "data", "pattern.png"
+ )
+ partner = self.env.ref("base.main_partner")
+ with open(file_path, "rb") as f:
+ with open(image_path, "rb") as i:
+ partner.write(
+ {
+ "url_file": base64.b64encode(f.read()),
+ "url_file_fname": "sample.txt",
+ "url_image": base64.b64encode(i.read()),
+ "url_image_fname": "pattern.png",
+ }
+ )
- with open(file_path, 'rb') as f:
+ with open(file_path, "rb") as f:
self.assertEqual(base64.decodebytes(partner.url_file), f.read())
- with open(image_path, 'rb') as i:
+ with open(image_path, "rb") as i:
self.assertEqual(base64.decodebytes(partner.url_image), i.read())
- partner2 = self.env.ref('base.partner_admin')
- with open(file_path, 'rb') as f:
+ partner2 = self.env.ref("base.partner_admin")
+ with open(file_path, "rb") as f:
with self.assertRaises(ValidationError):
- partner2.write({
- 'url_file': base64.b64encode(f.read()),
- 'url_file_fname': 'sample.txt',
- })
+ partner2.write(
+ {
+ "url_file": base64.b64encode(f.read()),
+ "url_file_fname": "sample.txt",
+ }
+ )
diff --git a/test_base_fileurl_field/views/res_partner.xml b/test_base_fileurl_field/views/res_partner.xml
index 08c33e44..f3367e99 100644
--- a/test_base_fileurl_field/views/res_partner.xml
+++ b/test_base_fileurl_field/views/res_partner.xml
@@ -1,4 +1,4 @@
-
+
res.partner.form.inherit
@@ -9,11 +9,15 @@
-
+
-
-
+
+
diff --git a/test_base_fileurl_field/views/res_users.xml b/test_base_fileurl_field/views/res_users.xml
index ffb56753..cbfdc722 100644
--- a/test_base_fileurl_field/views/res_users.xml
+++ b/test_base_fileurl_field/views/res_users.xml
@@ -1,4 +1,4 @@
-
+
res.users.form.inherit
@@ -9,7 +9,7 @@
-
+