Compare commits
1 Commits
16.0
...
survey_rec
| Author | SHA1 | Date | |
|---|---|---|---|
| 853ed418bb |
@@ -1,20 +0,0 @@
|
||||
# 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
|
||||
188
.eslintrc.yml
188
.eslintrc.yml
@@ -1,188 +0,0 @@
|
||||
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
|
||||
luxon: 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
|
||||
@@ -1,42 +0,0 @@
|
||||
name: pre-commit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "16.0*"
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Get python version
|
||||
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
|
||||
# - uses: actions/cache@v4
|
||||
# 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
|
||||
env:
|
||||
# Consider valid a PR that changes README fragments but doesn't
|
||||
# change the README.rst file itself. It's not really a problem
|
||||
# because the bot will update it anyway after merge. This way, we
|
||||
# lower the barrier for functional contributors that want to fix the
|
||||
# readme fragments, while still letting developers get README
|
||||
# auto-generated (which also helps functionals when using runboat).
|
||||
# DOCS https://pre-commit.com/#temporarily-disabling-hooks
|
||||
SKIP: oca-gen-addon-readme
|
||||
- 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
|
||||
@@ -1,150 +0,0 @@
|
||||
exclude: |
|
||||
(?x)
|
||||
# NOT INSTALLABLE ADDONS
|
||||
# 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/|^eslint.config.cjs|^prettier.config.cjs|
|
||||
# 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$|
|
||||
# Don't bother non-technical authors with formatting issues in docs
|
||||
readme/.*\.(rst|md)$|
|
||||
# Ignore build and dist directories in addons
|
||||
/build/|/dist/|
|
||||
# Ignore test files in addons
|
||||
/tests/samples/.*|
|
||||
# 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: f9b919b9868143135a9c9cb03021089cabba8223
|
||||
hooks:
|
||||
# update the NOT INSTALLABLE ADDONS section above
|
||||
- id: oca-update-pre-commit-excluded-addons
|
||||
- id: oca-fix-manifest-website
|
||||
entry:
|
||||
bash -c 'oca-fix-manifest-website "https://git.elabore.coop/elabore/$(basename
|
||||
$(git rev-parse --show-toplevel))"'
|
||||
- id: oca-gen-addon-readme
|
||||
entry:
|
||||
bash -c 'oca-gen-addon-readme
|
||||
--addons-dir=.
|
||||
--branch=$(git symbolic-ref
|
||||
refs/remotes/origin/HEAD | sed "s@^refs/remotes/origin/@@")
|
||||
--repo-name=$(basename $(git rev-parse --show-toplevel))
|
||||
--org-name="Elabore"
|
||||
--if-source-changed --keep-source-digest'
|
||||
|
||||
- repo: https://github.com/OCA/odoo-pre-commit-hooks
|
||||
rev: v0.1.4
|
||||
hooks:
|
||||
- id: oca-checks-odoo-module
|
||||
- id: oca-checks-po
|
||||
args:
|
||||
- --disable=po-pretty-format
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: prettier
|
||||
name: prettier (with plugin-xml)
|
||||
entry: prettier
|
||||
args:
|
||||
- --write
|
||||
- --list-different
|
||||
- --ignore-unknown
|
||||
types: [text]
|
||||
files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
|
||||
language: node
|
||||
additional_dependencies:
|
||||
- "prettier@2.7.1"
|
||||
- "@prettier/plugin-xml@2.2.0"
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: eslint
|
||||
name: eslint
|
||||
entry: eslint
|
||||
args:
|
||||
- --color
|
||||
- --fix
|
||||
verbose: true
|
||||
types: [javascript]
|
||||
language: node
|
||||
additional_dependencies:
|
||||
- "eslint@8.24.0"
|
||||
- "eslint-plugin-jsdoc@"
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.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/PyCQA/docformatter
|
||||
rev: v1.7.7
|
||||
hooks:
|
||||
- id: docformatter
|
||||
args: [
|
||||
"--in-place", # modify the files
|
||||
"--recursive", # run on all the files
|
||||
"--wrap-summaries",
|
||||
"88", # max length of 1st line
|
||||
"--wrap-descriptions",
|
||||
"88", # max length of other lines
|
||||
"--pre-summary-newline", # new line before a long summary
|
||||
"--make-summary-multi-line", # force summary on multilines
|
||||
]
|
||||
additional_dependencies: ["tomli"] # if Python <3.11
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/OCA/pylint-odoo
|
||||
rev: v9.1.3
|
||||
hooks:
|
||||
- id: pylint_odoo
|
||||
name: pylint with optional checks
|
||||
args:
|
||||
- --rcfile=.pylintrc
|
||||
- --exit-zero
|
||||
verbose: true
|
||||
- id: pylint_odoo
|
||||
args:
|
||||
- --rcfile=.pylintrc-mandatory
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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"
|
||||
123
.pylintrc
123
.pylintrc
@@ -1,123 +0,0 @@
|
||||
|
||||
|
||||
[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=Elabore
|
||||
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
|
||||
@@ -1,98 +0,0 @@
|
||||
|
||||
[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=Elabore
|
||||
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
|
||||
31
.ruff.toml
31
.ruff.toml
@@ -1,31 +0,0 @@
|
||||
|
||||
target-version = "py310"
|
||||
fix = true
|
||||
|
||||
[lint]
|
||||
extend-select = [
|
||||
"B",
|
||||
"C90",
|
||||
"E501", # line too long (default 88)
|
||||
"I", # isort
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
extend-safe-fixes = ["UP008"]
|
||||
exclude = ["setup/*"]
|
||||
|
||||
[format]
|
||||
exclude = ["setup/*"]
|
||||
|
||||
[lint.per-file-ignores]
|
||||
"__init__.py" = ["F401", "I001"] # ignore unused and unsorted imports in __init__.py
|
||||
"__manifest__.py" = ["B018"] # useless expression
|
||||
|
||||
[lint.isort]
|
||||
section-order = ["future", "standard-library", "third-party", "odoo", "odoo-addons", "first-party", "local-folder"]
|
||||
|
||||
[lint.isort.sections]
|
||||
"odoo" = ["odoo"]
|
||||
"odoo-addons" = ["odoo.addons"]
|
||||
|
||||
[lint.mccabe]
|
||||
max-complexity = 16
|
||||
@@ -1,54 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * survey_base
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-02-17 14:44+0000\n"
|
||||
"PO-Revision-Date: 2026-02-17 14:44+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: survey_base
|
||||
#: model:ir.model.fields,field_description:survey_base.field_survey_user_input_line__value_file
|
||||
msgid "File"
|
||||
msgstr "Fichier"
|
||||
|
||||
#. module: survey_base
|
||||
#: model:ir.model.fields,field_description:survey_base.field_survey_user_input_line__value_file_fname
|
||||
msgid "File Name"
|
||||
msgstr "Nom du fichier"
|
||||
|
||||
#. module: survey_base
|
||||
#: model:ir.model.fields,field_description:survey_base.field_survey_question_answer__record_reference
|
||||
#: model:ir.model.fields,field_description:survey_base.field_survey_user_input_line__record_reference
|
||||
msgid "Record"
|
||||
msgstr "Enregistrement"
|
||||
|
||||
#. module: survey_base
|
||||
#: model:ir.model.fields,field_description:survey_base.field_survey_question_answer__record_reference_model
|
||||
#: model:ir.model.fields,field_description:survey_base.field_survey_user_input_line__record_reference_model
|
||||
msgid "Record Model"
|
||||
msgstr "Modèle de l'enregistrement"
|
||||
|
||||
#. module: survey_base
|
||||
#: model:ir.model.fields,field_description:survey_base.field_survey_question_answer__smart_search
|
||||
#: model:ir.model.fields,field_description:survey_base.field_survey_user_input_line__smart_search
|
||||
msgid "Smart Search"
|
||||
msgstr "Recherche intelligente"
|
||||
|
||||
#. module: survey_base
|
||||
#: model:ir.model,name:survey_base.model_survey_question_answer
|
||||
msgid "Survey Label"
|
||||
msgstr "Étiquette du sondage"
|
||||
|
||||
#. module: survey_base
|
||||
#: model:ir.model,name:survey_base.model_survey_user_input_line
|
||||
msgid "Survey User Input Line"
|
||||
msgstr "Ligne d'entrée pour l'utilisateur du sondage"
|
||||
@@ -21,7 +21,7 @@ class SurveyUserInputLine(models.Model):
|
||||
|
||||
#record reference fields
|
||||
record_reference = fields.Many2oneReference(model_field="record_reference_model", string="Record")
|
||||
record_reference_model = fields.Char('Record Model')
|
||||
record_reference_model = fields.Char('Record model')
|
||||
|
||||
"""set record_reference when saving survey_user_input line
|
||||
"""
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -1,33 +0,0 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
{
|
||||
"name": "Survey extra fields",
|
||||
"summary": "Add extra question types to surveys",
|
||||
"description": """
|
||||
Add extra question types to surveys:
|
||||
----------------------------------------------------
|
||||
* File upload question type
|
||||
- Allows survey participants to upload a file as an answer
|
||||
- Configurable maximum file size (in MB) per question (default: 10 MB, 0 = no limit)
|
||||
- Configurable allowed file extensions per question (e.g. .pdf,.docx — empty = all types allowed)
|
||||
- Client-side validation (size and extension) before form submission
|
||||
- Server-side validation on save to enforce constraints
|
||||
""",
|
||||
"version": "16.0.1.0.0",
|
||||
"license": "AGPL-3",
|
||||
"author": "Elabore",
|
||||
"website": "https://www.elabore.coop",
|
||||
"category": "",
|
||||
"depends": ["base", "survey_base"],
|
||||
"data": [
|
||||
"views/survey_templates.xml",
|
||||
"views/survey_user_views.xml",
|
||||
"views/survey_question_views.xml",
|
||||
],
|
||||
"assets": {
|
||||
"survey.survey_assets": [
|
||||
"/survey_extra_fields/static/src/js/survey_form.js",
|
||||
],
|
||||
},
|
||||
"installable": True,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
from . import main
|
||||
@@ -1,61 +0,0 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request, content_disposition
|
||||
from odoo.addons.survey.controllers.main import Survey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
|
||||
class SurveyExtraFieldsController(Survey):
|
||||
|
||||
@http.route(
|
||||
"/survey/file/<string:survey_token>/<string:answer_token>/<int:line_id>",
|
||||
type="http",
|
||||
auth="public",
|
||||
)
|
||||
def survey_file_download(
|
||||
self,
|
||||
survey_token: str,
|
||||
answer_token: str,
|
||||
line_id: int,
|
||||
**kwargs: Any
|
||||
) -> Response:
|
||||
survey = request.env["survey.survey"].sudo().search(
|
||||
[("access_token", "=", survey_token)], limit=1
|
||||
)
|
||||
if not survey:
|
||||
return request.not_found()
|
||||
|
||||
answer = request.env["survey.user_input"].sudo().search(
|
||||
[
|
||||
("survey_id", "=", survey.id),
|
||||
("access_token", "=", answer_token),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
if not answer:
|
||||
return request.not_found()
|
||||
|
||||
line = request.env["survey.user_input.line"].sudo().browse(line_id)
|
||||
if not line.exists() or line.user_input_id != answer:
|
||||
return request.not_found()
|
||||
|
||||
if not line.value_file:
|
||||
return request.not_found()
|
||||
|
||||
file_content = base64.b64decode(line.value_file)
|
||||
filename = line.value_file_fname or "file"
|
||||
return request.make_response(
|
||||
file_content,
|
||||
headers=[
|
||||
("Content-Type", "application/octet-stream"),
|
||||
("Content-Disposition", content_disposition(filename)),
|
||||
],
|
||||
)
|
||||
@@ -1,124 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * survey_extra_fields
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-02-18 16:38+0000\n"
|
||||
"PO-Revision-Date: 2026-02-18 16:38+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model_terms:ir.ui.view,arch_db:survey_extra_fields.survey_question_form_inh
|
||||
msgid ".pdf,.docx,.xlsx"
|
||||
msgstr ""
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model.fields,field_description:survey_extra_fields.field_survey_question__allowed_extensions
|
||||
msgid "Allowed Extensions"
|
||||
msgstr "Extensions autorisées"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model.fields,field_description:survey_extra_fields.field_survey_user_input_line__answer_type
|
||||
msgid "Answer Type"
|
||||
msgstr "Type de réponse"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model.fields,help:survey_extra_fields.field_survey_question__allowed_extensions
|
||||
msgid ""
|
||||
"Comma-separated list of allowed extensions (e.g. .pdf,.docx). Leave empty to"
|
||||
" allow all types."
|
||||
msgstr ""
|
||||
"Liste d'extensions autorisées séparées par une virgule (e.g. .pdf,.docx). Laisser vide pour"
|
||||
" autoriser tous les types de fichier."
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model.fields.selection,name:survey_extra_fields.selection__survey_question__question_type__file
|
||||
#: model:ir.model.fields.selection,name:survey_extra_fields.selection__survey_user_input_line__answer_type__file
|
||||
msgid "File"
|
||||
msgstr "Fichier"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model.fields,field_description:survey_extra_fields.field_survey_question__max_file_size
|
||||
#: model_terms:ir.ui.view,arch_db:survey_extra_fields.survey_question_form_inh
|
||||
msgid "Max File Size (MB)"
|
||||
msgstr "Taille maximale du fichier"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model.fields,help:survey_extra_fields.field_survey_question__max_file_size
|
||||
msgid "Maximum file size in MB. Leave 0 for no limit."
|
||||
msgstr "Taille maximale du fichier en MB. Laisser à 0 pour ne pas restreindre la taille. La valeur par défaut est 10 MB."
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model.fields,field_description:survey_extra_fields.field_survey_question__question_type
|
||||
msgid "Question Type"
|
||||
msgstr "Type de question"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model_terms:ir.ui.view,arch_db:survey_extra_fields.survey_page_print_inh_type_file
|
||||
msgid "Skipped"
|
||||
msgstr "Ignoré"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model.fields,field_description:survey_extra_fields.field_survey_question__smart_search
|
||||
#: model:ir.model.fields,field_description:survey_extra_fields.field_survey_user_input__smart_search
|
||||
#: model:ir.model.fields,field_description:survey_extra_fields.field_survey_user_input_line__smart_search
|
||||
msgid "Smart Search"
|
||||
msgstr "Recherche intelligente"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model,name:survey_extra_fields.model_survey_question
|
||||
msgid "Survey Question"
|
||||
msgstr "Question du sondage"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model,name:survey_extra_fields.model_survey_user_input
|
||||
msgid "Survey User Input"
|
||||
msgstr "Entrée utilisateur du sondage"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#: model:ir.model,name:survey_extra_fields.model_survey_user_input_line
|
||||
msgid "Survey User Input Line"
|
||||
msgstr "Ligne d'entrée pour l'utilisateur du sondage"
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#. odoo-python
|
||||
#: code:addons/survey_extra_fields/models/survey_user_input.py:0
|
||||
#, python-format
|
||||
msgid "The file '%(name)s' exceeds the maximum allowed size of %(size)s MB."
|
||||
msgstr "Le fichier '%(name)s' dépasse la taille maximale autorisée de %(size)s MB."
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#. odoo-python
|
||||
#: code:addons/survey_extra_fields/models/survey_user_input.py:0
|
||||
#, python-format
|
||||
msgid "The file '%(name)s' is not allowed. Accepted formats: %(exts)s."
|
||||
msgstr "Le fichier '%(name)s' n'est pas autorisé. Les formats de fichier autorisés sont : %(exts)s."
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#. odoo-javascript
|
||||
#: code:addons/survey_extra_fields/static/src/js/survey_form.js:0
|
||||
#, python-format
|
||||
msgid "The file exceeds the maximum allowed size of %s MB."
|
||||
msgstr "Le fichier dépasse la taille maximale autorisée de %s MB."
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#. odoo-javascript
|
||||
#: code:addons/survey_extra_fields/static/src/js/survey_form.js:0
|
||||
#, python-format
|
||||
msgid "This file type is not allowed. Accepted formats: %s."
|
||||
msgstr "Le fichier n'est pas autorisé. Les formats de fichier autorisés sont : %s."
|
||||
|
||||
#. module: survey_extra_fields
|
||||
#. odoo-javascript
|
||||
#: code:addons/survey_extra_fields/static/src/js/survey_form.js:0
|
||||
#, python-format
|
||||
msgid "This question requires an answer."
|
||||
msgstr "Cette question requiert une réponse."
|
||||
@@ -1,3 +0,0 @@
|
||||
from . import survey_question
|
||||
from . import survey_user_input
|
||||
from . import survey_user_input_line
|
||||
@@ -1,20 +0,0 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class SurveyQuestion(models.Model):
|
||||
_inherit = "survey.question"
|
||||
|
||||
question_type = fields.Selection(
|
||||
selection_add=[("file", "File")]
|
||||
)
|
||||
max_file_size = fields.Integer(
|
||||
string="Max File Size (MB)",
|
||||
default=10,
|
||||
help="Maximum file size in MB. Leave 0 for no limit.",
|
||||
)
|
||||
allowed_extensions = fields.Char(
|
||||
string="Allowed Extensions",
|
||||
help="Comma-separated list of allowed extensions (e.g. .pdf,.docx). Leave empty to allow all types.",
|
||||
)
|
||||
@@ -1,73 +0,0 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from odoo import _, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from odoo.addons.survey_extra_fields.models.survey_question import SurveyQuestion
|
||||
|
||||
|
||||
class SurveyUserInput(models.Model):
|
||||
_inherit = "survey.user_input"
|
||||
|
||||
def save_lines(self, question: SurveyQuestion, answer: str | None, comment: str | None = None) -> None:
|
||||
if question.question_type == "file":
|
||||
old_answers = self.env["survey.user_input.line"].search([
|
||||
("user_input_id", "=", self.id),
|
||||
("question_id", "=", question.id),
|
||||
])
|
||||
vals = {
|
||||
"user_input_id": self.id,
|
||||
"question_id": question.id,
|
||||
"skipped": False,
|
||||
"answer_type": "file",
|
||||
}
|
||||
if answer:
|
||||
file_data = json.loads(answer)
|
||||
file_b64 = file_data.get("data", "")
|
||||
file_name = file_data.get("name", "")
|
||||
self._check_file_constraints(question, file_b64, file_name)
|
||||
vals["value_file"] = file_b64
|
||||
vals["value_file_fname"] = file_name
|
||||
else:
|
||||
vals.update(answer_type=None, skipped=True)
|
||||
if old_answers:
|
||||
old_answers.write(vals)
|
||||
else:
|
||||
self.env["survey.user_input.line"].create(vals)
|
||||
else:
|
||||
return super().save_lines(question, answer, comment=comment)
|
||||
|
||||
def _check_file_constraints(
|
||||
self,
|
||||
question: SurveyQuestion,
|
||||
file_b64: str,
|
||||
file_name: str
|
||||
) -> None:
|
||||
if question.max_file_size:
|
||||
file_size = len(base64.b64decode(file_b64))
|
||||
max_bytes = question.max_file_size * 1024 * 1024
|
||||
if file_size > max_bytes:
|
||||
raise ValidationError(
|
||||
_("The file '%(name)s' exceeds the maximum allowed size of %(size)s MB.",
|
||||
name=file_name, size=question.max_file_size)
|
||||
)
|
||||
if question.allowed_extensions:
|
||||
allowed = [
|
||||
allowed_extension.strip().lower()
|
||||
for allowed_extension in question.allowed_extensions.split(",")
|
||||
if allowed_extension.strip()
|
||||
]
|
||||
file_extension = os.path.splitext(file_name)[1].lower()
|
||||
if file_extension not in allowed:
|
||||
raise ValidationError(
|
||||
_("The file '%(name)s' is not allowed. Accepted formats: %(exts)s.",
|
||||
name=file_name, exts=question.allowed_extensions)
|
||||
)
|
||||
@@ -1,17 +0,0 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class SurveyUserInputLine(models.Model):
|
||||
_inherit = "survey.user_input.line"
|
||||
|
||||
answer_type = fields.Selection(
|
||||
selection_add=[("file", "File")]
|
||||
)
|
||||
|
||||
def _compute_display_name(self):
|
||||
super()._compute_display_name()
|
||||
for line in self:
|
||||
if line.answer_type == "file" and line.value_file_fname:
|
||||
line.display_name = line.value_file_fname
|
||||
@@ -1,146 +0,0 @@
|
||||
odoo.define("survey_extra_fields.survey_form", function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require("web.core");
|
||||
var _t = core._t;
|
||||
var survey_form = require("survey.form");
|
||||
|
||||
survey_form.include({
|
||||
_readFileAsDataURL: function (file) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
reader.onerror = function () {
|
||||
reject(reader.error);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
},
|
||||
|
||||
_submitForm: function (options) {
|
||||
var self = this;
|
||||
var $fileInputs = this.$('input[data-question-type="file"]');
|
||||
var hasFiles = false;
|
||||
$fileInputs.each(function () {
|
||||
if (this.files && this.files.length > 0) {
|
||||
hasFiles = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasFiles || this.options.isStartScreen) {
|
||||
return this._super(options);
|
||||
}
|
||||
|
||||
// Async flow: read files then submit
|
||||
var params = {};
|
||||
if (options.previousPageId) {
|
||||
params.previous_page_id = options.previousPageId;
|
||||
}
|
||||
|
||||
var $form = this.$("form");
|
||||
var formData = new FormData($form[0]);
|
||||
|
||||
if (!options.skipValidation) {
|
||||
if (!this._validateForm($form, formData)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._prepareSubmitValues(formData, params);
|
||||
|
||||
// Read all selected files as base64
|
||||
var filePromises = [];
|
||||
$fileInputs.each(function () {
|
||||
if (this.files && this.files.length > 0) {
|
||||
var file = this.files[0];
|
||||
var name = this.name;
|
||||
filePromises.push(
|
||||
self._readFileAsDataURL(file).then(function (dataURL) {
|
||||
params[name] = JSON.stringify({
|
||||
data: dataURL.split(",")[1],
|
||||
name: file.name,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.preventEnterSubmit = true;
|
||||
|
||||
if (this.options.sessionInProgress) {
|
||||
this.fadeInOutDelay = 400;
|
||||
this.readonly = true;
|
||||
}
|
||||
|
||||
Promise.all(filePromises).then(function () {
|
||||
var submitPromise = self._rpc({
|
||||
route: _.str.sprintf(
|
||||
"%s/%s/%s",
|
||||
"/survey/submit",
|
||||
self.options.surveyToken,
|
||||
self.options.answerToken
|
||||
),
|
||||
params: params,
|
||||
});
|
||||
self._nextScreen(submitPromise, options);
|
||||
});
|
||||
},
|
||||
|
||||
_validateForm: function ($form, formData) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
var errors = {};
|
||||
var inactiveQuestionIds = this.options.sessionInProgress
|
||||
? []
|
||||
: this._getInactiveConditionalQuestionIds();
|
||||
|
||||
$form.find('input[data-question-type="file"]').each(function () {
|
||||
var $questionWrapper = $(this).closest(".js_question-wrapper");
|
||||
var questionId = $questionWrapper.attr("id");
|
||||
if (inactiveQuestionIds.includes(parseInt(questionId))) {
|
||||
return;
|
||||
}
|
||||
var questionRequired = $questionWrapper.data("required");
|
||||
var constrErrorMsg =
|
||||
$questionWrapper.data("constrErrorMsg") ||
|
||||
_t("This question requires an answer.");
|
||||
if (questionRequired && !(this.files && this.files.length > 0)) {
|
||||
errors[questionId] = constrErrorMsg;
|
||||
return;
|
||||
}
|
||||
if (this.files && this.files.length > 0) {
|
||||
var file = this.files[0];
|
||||
var maxSizeMB = parseInt($(this).data("maxFileSize"));
|
||||
if (maxSizeMB && file.size > maxSizeMB * 1024 * 1024) {
|
||||
errors[questionId] = _.str.sprintf(
|
||||
_t("The file exceeds the maximum allowed size of %s MB."),
|
||||
maxSizeMB
|
||||
);
|
||||
return;
|
||||
}
|
||||
var allowedExtensions = $(this).data("allowedExtensions");
|
||||
if (allowedExtensions) {
|
||||
var allowed = allowedExtensions.split(",").map(function (e) {
|
||||
return e.trim().toLowerCase();
|
||||
});
|
||||
var ext = "." + file.name.split(".").pop().toLowerCase();
|
||||
if (!allowed.includes(ext)) {
|
||||
errors[questionId] = _.str.sprintf(
|
||||
_t("This file type is not allowed. Accepted formats: %s."),
|
||||
allowedExtensions
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (_.keys(errors).length > 0) {
|
||||
this._showErrors(errors);
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
from . import test_survey_file
|
||||
@@ -1,278 +0,0 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import base64
|
||||
import json
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from werkzeug.exceptions import NotFound
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
from odoo.addons.survey.tests import common
|
||||
|
||||
|
||||
class TestSurveyFileCommon(common.TestSurveyCommon):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.file_content = b"Hello, this is a test file."
|
||||
self.file_b64 = base64.b64encode(self.file_content).decode()
|
||||
self.file_name = "test_document.pdf"
|
||||
|
||||
self.question_file = self._add_question(
|
||||
self.page_0,
|
||||
"Upload your document",
|
||||
"file",
|
||||
constr_mandatory=False,
|
||||
survey_id=self.survey.id,
|
||||
)
|
||||
self.question_file_required = self._add_question(
|
||||
self.page_0,
|
||||
"Upload your required document",
|
||||
"file",
|
||||
constr_mandatory=True,
|
||||
survey_id=self.survey.id,
|
||||
)
|
||||
|
||||
def _create_answer_with_file(self):
|
||||
answer = self._add_answer(self.survey, False, email="test@example.com")
|
||||
line = self.env["survey.user_input.line"].create({
|
||||
"user_input_id": answer.id,
|
||||
"question_id": self.question_file.id,
|
||||
"answer_type": "file",
|
||||
"skipped": False,
|
||||
"value_file": self.file_b64,
|
||||
"value_file_fname": self.file_name,
|
||||
})
|
||||
return answer, line
|
||||
|
||||
|
||||
class TestSurveyFileSaveLines(TestSurveyFileCommon):
|
||||
"""Test the save_lines method for file question type."""
|
||||
|
||||
def test_save_file_answer(self):
|
||||
"""Submitting a file stores base64 data and filename."""
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json = json.dumps({"data": self.file_b64, "name": self.file_name})
|
||||
|
||||
answer.save_lines(self.question_file, file_json)
|
||||
|
||||
line = answer.user_input_line_ids.filtered(
|
||||
lambda l: l.question_id == self.question_file
|
||||
)
|
||||
self.assertEqual(len(line), 1)
|
||||
self.assertEqual(line.answer_type, "file")
|
||||
self.assertFalse(line.skipped)
|
||||
self.assertEqual(line.value_file, self.file_b64.encode())
|
||||
self.assertEqual(line.value_file_fname, self.file_name)
|
||||
|
||||
def test_save_file_skipped(self):
|
||||
"""Submitting empty answer marks the line as skipped."""
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
|
||||
answer.save_lines(self.question_file, "")
|
||||
|
||||
line = answer.user_input_line_ids.filtered(
|
||||
lambda l: l.question_id == self.question_file
|
||||
)
|
||||
self.assertEqual(len(line), 1)
|
||||
self.assertTrue(line.skipped)
|
||||
self.assertFalse(line.answer_type)
|
||||
|
||||
def test_save_file_update_existing(self):
|
||||
"""Submitting a new file updates the existing answer line."""
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json_1 = json.dumps({"data": self.file_b64, "name": "first.pdf"})
|
||||
answer.save_lines(self.question_file, file_json_1)
|
||||
|
||||
new_b64 = base64.b64encode(b"Updated content").decode()
|
||||
file_json_2 = json.dumps({"data": new_b64, "name": "second.pdf"})
|
||||
answer.save_lines(self.question_file, file_json_2)
|
||||
|
||||
lines = answer.user_input_line_ids.filtered(
|
||||
lambda l: l.question_id == self.question_file
|
||||
)
|
||||
self.assertEqual(len(lines), 1, "Should update, not create a second line")
|
||||
self.assertEqual(lines.value_file, new_b64.encode())
|
||||
self.assertEqual(lines.value_file_fname, "second.pdf")
|
||||
|
||||
def test_save_file_then_skip(self):
|
||||
"""Uploading a file then submitting empty marks line as skipped."""
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json = json.dumps({"data": self.file_b64, "name": self.file_name})
|
||||
answer.save_lines(self.question_file, file_json)
|
||||
|
||||
answer.save_lines(self.question_file, "")
|
||||
|
||||
line = answer.user_input_line_ids.filtered(
|
||||
lambda l: l.question_id == self.question_file
|
||||
)
|
||||
self.assertEqual(len(line), 1)
|
||||
self.assertTrue(line.skipped)
|
||||
|
||||
|
||||
class TestSurveyFileConstraints(TestSurveyFileCommon):
|
||||
"""Test _check_file_constraints validation logic."""
|
||||
|
||||
def test_no_constraints(self):
|
||||
"""No max_file_size and no allowed_extensions: any file passes."""
|
||||
self.question_file.write({"max_file_size": 0, "allowed_extensions": False})
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json = json.dumps({"data": self.file_b64, "name": "anything.exe"})
|
||||
answer.save_lines(self.question_file, file_json)
|
||||
line = answer.user_input_line_ids.filtered(
|
||||
lambda l: l.question_id == self.question_file
|
||||
)
|
||||
self.assertFalse(line.skipped)
|
||||
|
||||
def test_file_within_size_limit(self):
|
||||
"""File smaller than max_file_size passes."""
|
||||
self.question_file.write({"max_file_size": 10})
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json = json.dumps({"data": self.file_b64, "name": self.file_name})
|
||||
answer.save_lines(self.question_file, file_json)
|
||||
line = answer.user_input_line_ids.filtered(
|
||||
lambda l: l.question_id == self.question_file
|
||||
)
|
||||
self.assertFalse(line.skipped)
|
||||
|
||||
def test_file_exceeds_size_limit(self):
|
||||
"""File larger than max_file_size raises ValidationError."""
|
||||
self.question_file.write({"max_file_size": 1})
|
||||
large_content = b"x" * (1 * 1024 * 1024 + 1)
|
||||
large_b64 = base64.b64encode(large_content).decode()
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json = json.dumps({"data": large_b64, "name": self.file_name})
|
||||
with self.assertRaises(Exception):
|
||||
answer.save_lines(self.question_file, file_json)
|
||||
|
||||
def test_allowed_extension_passes(self):
|
||||
"""File with an allowed extension passes."""
|
||||
self.question_file.write({"allowed_extensions": ".pdf,.docx"})
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json = json.dumps({"data": self.file_b64, "name": "report.docx"})
|
||||
answer.save_lines(self.question_file, file_json)
|
||||
line = answer.user_input_line_ids.filtered(
|
||||
lambda l: l.question_id == self.question_file
|
||||
)
|
||||
self.assertFalse(line.skipped)
|
||||
|
||||
def test_disallowed_extension_raises(self):
|
||||
"""File with a disallowed extension raises ValidationError."""
|
||||
self.question_file.write({"allowed_extensions": ".pdf,.docx"})
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json = json.dumps({"data": self.file_b64, "name": "script.exe"})
|
||||
with self.assertRaises(Exception):
|
||||
answer.save_lines(self.question_file, file_json)
|
||||
|
||||
def test_both_constraints_valid(self):
|
||||
"""File respecting both size and extension constraints passes."""
|
||||
self.question_file.write({"max_file_size": 10, "allowed_extensions": ".pdf"})
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
file_json = json.dumps({"data": self.file_b64, "name": self.file_name})
|
||||
answer.save_lines(self.question_file, file_json)
|
||||
line = answer.user_input_line_ids.filtered(
|
||||
lambda l: l.question_id == self.question_file
|
||||
)
|
||||
self.assertFalse(line.skipped)
|
||||
|
||||
|
||||
class TestSurveyFileDisplayName(TestSurveyFileCommon):
|
||||
"""Test the display_name computation for file answer lines."""
|
||||
|
||||
def test_display_name_filename(self):
|
||||
answer = self._add_answer(self.survey, self.survey_manager.partner_id)
|
||||
line = self.env["survey.user_input.line"].create({
|
||||
"user_input_id": answer.id,
|
||||
"question_id": self.question_file.id,
|
||||
"answer_type": "file",
|
||||
"skipped": False,
|
||||
"value_file": self.file_b64,
|
||||
"value_file_fname": "my_filename.pdf",
|
||||
})
|
||||
self.assertEqual(line.display_name, "my_filename.pdf")
|
||||
|
||||
|
||||
class TestSurveyFileDownloadController(TestSurveyFileCommon):
|
||||
"""Test the file download controller logic with mocked request."""
|
||||
|
||||
def _call_download(self, survey_token, answer_token, line_id):
|
||||
"""Call the controller method with a mocked request context."""
|
||||
from odoo.addons.survey_extra_fields.controllers.main import (
|
||||
SurveyExtraFieldsController,
|
||||
)
|
||||
|
||||
mock_request = MagicMock()
|
||||
mock_request.env = self.env
|
||||
mock_request.not_found.return_value = NotFound()
|
||||
mock_request.make_response.side_effect = (
|
||||
lambda content, headers=None: Response(content, headers=headers)
|
||||
)
|
||||
|
||||
controller = SurveyExtraFieldsController()
|
||||
with patch(
|
||||
"odoo.addons.survey_extra_fields.controllers.main.request",
|
||||
mock_request,
|
||||
):
|
||||
try:
|
||||
return controller.survey_file_download(
|
||||
survey_token, answer_token, line_id
|
||||
)
|
||||
except NotFound:
|
||||
return NotFound()
|
||||
|
||||
def test_download_valid(self):
|
||||
"""Valid tokens return the file content."""
|
||||
answer, line = self._create_answer_with_file()
|
||||
|
||||
response = self._call_download(
|
||||
self.survey.access_token, answer.access_token, line.id
|
||||
)
|
||||
|
||||
self.assertIsInstance(response, Response)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data, self.file_content)
|
||||
|
||||
def test_download_invalid_survey_token(self):
|
||||
"""Invalid survey token returns not_found."""
|
||||
answer, line = self._create_answer_with_file()
|
||||
|
||||
response = self._call_download(
|
||||
"invalid-token", answer.access_token, line.id
|
||||
)
|
||||
|
||||
self.assertIsInstance(response, NotFound)
|
||||
|
||||
def test_download_invalid_answer_token(self):
|
||||
"""Invalid answer token returns not_found."""
|
||||
answer, line = self._create_answer_with_file()
|
||||
|
||||
response = self._call_download(
|
||||
self.survey.access_token, "invalid-token", line.id
|
||||
)
|
||||
|
||||
self.assertIsInstance(response, NotFound)
|
||||
|
||||
def test_download_line_not_belonging_to_answer(self):
|
||||
"""Accessing a line from another answer returns not_found."""
|
||||
answer, line = self._create_answer_with_file()
|
||||
other_answer = self._add_answer(self.survey, False, email="other@example.com")
|
||||
|
||||
response = self._call_download(
|
||||
self.survey.access_token, other_answer.access_token, line.id
|
||||
)
|
||||
|
||||
self.assertIsInstance(response, NotFound)
|
||||
|
||||
def test_download_no_file(self):
|
||||
"""Accessing a line without file data returns not_found."""
|
||||
answer = self._add_answer(self.survey, False, email="test@example.com")
|
||||
line = self.env["survey.user_input.line"].create({
|
||||
"user_input_id": answer.id,
|
||||
"question_id": self.question_file.id,
|
||||
"skipped": True,
|
||||
})
|
||||
|
||||
response = self._call_download(
|
||||
self.survey.access_token, answer.access_token, line.id
|
||||
)
|
||||
|
||||
self.assertIsInstance(response, NotFound)
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="survey_question_form_inh" model="ir.ui.view">
|
||||
<field name="name">survey.question.form.inherit.extra_fields</field>
|
||||
<field name="model">survey.question</field>
|
||||
<field name="inherit_id" ref="survey.survey_question_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='options']//field[@name='matrix_subtype']" position="before">
|
||||
<field name="max_file_size"
|
||||
attrs="{'invisible': [('question_type', '!=', 'file')]}"
|
||||
string="Max File Size (MB)"/>
|
||||
<field name="allowed_extensions"
|
||||
attrs="{'invisible': [('question_type', '!=', 'file')]}"
|
||||
placeholder=".pdf,.docx,.xlsx"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,68 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Extend question_container (form view during survey filling) -->
|
||||
<template
|
||||
id="question_container_inh_type_file"
|
||||
inherit_id="survey.question_container"
|
||||
>
|
||||
<xpath expr="//t[@t-call='survey.question_matrix']/.." position="after">
|
||||
<t t-if="question.question_type == 'file'">
|
||||
<t t-call="survey_extra_fields.question_file"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="question_file" name="Question: File">
|
||||
<div class="o_survey_comment_container p-0">
|
||||
<t t-if="survey_form_readonly and answer_lines and answer_lines[0].value_file_fname">
|
||||
<p><t t-out="answer_lines[0].value_file_fname"/></p>
|
||||
</t>
|
||||
<t t-if="not survey_form_readonly">
|
||||
<input
|
||||
type="file"
|
||||
class="o_survey_question_file"
|
||||
t-att-name="question.id"
|
||||
t-att-data-question-type="question.question_type"
|
||||
t-att-accept="question.allowed_extensions or None"
|
||||
t-att-data-max-file-size="question.max_file_size or None"
|
||||
t-att-data-allowed-extensions="question.allowed_extensions or None"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Extend print/review page to show file answers -->
|
||||
<template
|
||||
id="survey_page_print_inh_type_file"
|
||||
inherit_id="survey.survey_page_print"
|
||||
>
|
||||
<xpath expr="//div[hasclass('o_survey_question_error')]" position="before">
|
||||
<t t-if="question.question_type == 'file'">
|
||||
<t t-if="answer_lines">
|
||||
<t t-set="answer_line" t-value="answer_lines[0]"/>
|
||||
<t t-if="answer_line.skipped">
|
||||
<div class="row g-0">
|
||||
<div class="col-12 col-md-6 col-lg-4 rounded ps-4 o_survey_question_skipped">
|
||||
<input type="text"
|
||||
class="form-control fst-italic o_survey_question_file bg-transparent rounded-0 p-0"
|
||||
value="Skipped"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="answer_line.value_file_fname">
|
||||
<div class="row g-0">
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<a t-attf-href="/survey/file/#{survey.access_token}/#{answer.access_token}/#{answer_line.id}"
|
||||
target="_blank">
|
||||
<i class="fa fa-download me-1"/><t t-out="answer_line.value_file_fname"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="survey_user_input_line_view_form_inh" model="ir.ui.view">
|
||||
<field name="name">survey.user_input.line.view.form.inherit.extra_fields</field>
|
||||
<field name="model">survey.user_input.line</field>
|
||||
<field name="inherit_id" ref="survey.survey_user_input_line_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='suggested_answer_id']" position="after">
|
||||
<field name="value_file" filename="value_file_fname" colspan="2"
|
||||
attrs="{'invisible': [('answer_type', '!=', 'file')]}"/>
|
||||
<field name="value_file_fname" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -11,7 +11,7 @@ Allow to create record of any model when sending the form :
|
||||
* Associate question with fields
|
||||
* For x2m fields : Associate values to questions
|
||||
""",
|
||||
"version": "16.0.1.0.2",
|
||||
"version": "16.0.1.0.1",
|
||||
"license": "AGPL-3",
|
||||
"author": "Elabore",
|
||||
"website": "https://www.elabore.coop",
|
||||
|
||||
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-11-13 16:41+0000\n"
|
||||
"PO-Revision-Date: 2025-11-13 16:41+0000\n"
|
||||
"POT-Creation-Date: 2025-04-15 10:34+0000\n"
|
||||
"PO-Revision-Date: 2025-04-15 10:34+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -31,11 +31,6 @@ msgstr ""
|
||||
msgid "Active survey input"
|
||||
msgstr "Participation active"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__allowed_field_ids
|
||||
msgid "Allowed Fields"
|
||||
msgstr "Champs acceptés"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__allowed_question_ids
|
||||
msgid "Allowed Question"
|
||||
@@ -60,16 +55,6 @@ msgstr "Associer une valeur à la réponse"
|
||||
msgid "Attempts Count"
|
||||
msgstr ""
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation__field_to_retrieve_existing_records
|
||||
msgid ""
|
||||
"Choose the field you want to use to retrieve the existing record. WARNING: "
|
||||
"We update only the first record found."
|
||||
msgstr ""
|
||||
"Choisissez le champs à partir duquel nous allons chercher l'enregistrement "
|
||||
"existant. Attention : nous mettons à jour seulement le premier "
|
||||
"enregistrement trouvé."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__create_uid
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__create_uid
|
||||
@@ -117,11 +102,6 @@ msgstr ""
|
||||
msgid "Field"
|
||||
msgstr "Champ"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__field_to_retrieve_existing_records
|
||||
msgid "Field To Retrieve Existing Records"
|
||||
msgstr "Champs pour retrouver l'enregistrement existant"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__field_type
|
||||
msgid "Field Type"
|
||||
@@ -191,20 +171,9 @@ msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,help:survey_record_generation.field_survey_record_creation__ignore_if_mandatory_field_is_missing
|
||||
msgid ""
|
||||
"If a mandatory field is missing when trying to create the record, an error "
|
||||
"is raised when the survey is submitted. If this option is activated, the "
|
||||
"error is ignored."
|
||||
msgstr ""
|
||||
"Si un champs requis est manquant lors de la création de l'enregistrement, "
|
||||
"une erreur est levée lors de la soumission du formulaire. "
|
||||
"En activant cette option, l'erreur sera ignorée."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__ignore_if_mandatory_field_is_missing
|
||||
msgid "Ignore creation if a mandatory field is missing"
|
||||
msgstr "Ignorer la création si un champs requis est manquant"
|
||||
#: model:ir.model,name:survey_record_generation.model_survey_question
|
||||
msgid "Inherit Survey Question for extra fields"
|
||||
msgstr "Question du sondage"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record____last_update
|
||||
@@ -288,16 +257,6 @@ msgstr ""
|
||||
"Lors de la création d'un enregistrement, si un autre enregistrement existe "
|
||||
"avec la même valeur, l'enregistrement ne sera pas créé."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model_terms:ir.ui.view,arch_db:survey_record_generation.survey_survey_view_form
|
||||
msgid ""
|
||||
"Only the first matched record will be updated.\n"
|
||||
" Also to be noticed, the unicity check feature has priority over updating the existing record."
|
||||
msgstr ""
|
||||
"Attention, seul le premier enregistrement trouvé sera mis à jour. Aussi, si "
|
||||
"vous avez des champs avec une contrainte d'unicité, cette contrainte aura la"
|
||||
" priorité sur la mise à jour des enregistrements."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#. odoo-python
|
||||
#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0
|
||||
@@ -370,11 +329,6 @@ msgstr "Sondage"
|
||||
msgid "Survey Label"
|
||||
msgstr "Étiquette du sondage"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model,name:survey_record_generation.model_survey_question
|
||||
msgid "Survey Question"
|
||||
msgstr "Question du sondage"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__survey_record_creation_id
|
||||
msgid "Survey Record Creation"
|
||||
@@ -387,8 +341,8 @@ msgstr "Sondage Création d'enregistrement Valeur des champs"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model,name:survey_record_generation.model_survey_user_input
|
||||
msgid "Survey User Input"
|
||||
msgstr "Saisie utilisateur du sondage"
|
||||
msgid "Survey User Input for custom matrix"
|
||||
msgstr "Entrée utilisateur du sondage"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_generated_record__survey_record_creation_id
|
||||
@@ -400,36 +354,20 @@ msgstr "Génération d'enregistrement depuis la participation"
|
||||
#: code:addons/survey_record_generation/models/survey_user_input.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The field %(field)s is mandatory for model %(model)s. In Record Creation "
|
||||
"tab, drag %(record)s on top of the model %(model)s."
|
||||
msgstr ""
|
||||
"Le champs %(field)s est obligatoire pour le modèle %(model)s. Dans l'onglet "
|
||||
"Création d'un enregistrement, placez la ligne %(record)s au dessus de la "
|
||||
"ligne du modèle %(model)s."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#. odoo-python
|
||||
#: code:addons/survey_record_generation/models/survey_user_input.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The field %s is mandatory. In Record Creation tab, drag %s at the top of the"
|
||||
" table"
|
||||
"The field %s is mandatory. In Record Creation tab, drag "
|
||||
"%s at the top of the table"
|
||||
msgstr ""
|
||||
"Le champ %s est obligatoire. Dans l'onglet Création d'un enregistrement, glissez "
|
||||
"%s sur la première ligne du tableau"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__unicity_check
|
||||
msgid "Unicity constraint"
|
||||
msgstr "Contrainte d'unicité"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation__update_existing_records
|
||||
msgid "Update existing records"
|
||||
msgstr "Mettre à jour les enregistrements existants"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_question_answer__value_char
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__displayed_value
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_boolean
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_char
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_date
|
||||
#: model:ir.model.fields,field_description:survey_record_generation.field_survey_record_creation_field_values__fixed_value_datetime
|
||||
@@ -462,50 +400,6 @@ msgstr "Message d'erreur"
|
||||
msgid "You should append at least one record in %s"
|
||||
msgstr "Vous devez au moins ajouter un enregistrement dans %s"
|
||||
|
||||
#. module: survey_record_generation
|
||||
#. odoo-python
|
||||
#: code:addons/survey_record_generation/models/survey_user_input.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"[Survey record generation] The answer values type '%(type)s' is not "
|
||||
"supported (for question %(question)s). Use 'record' or 'value' instead."
|
||||
msgstr ""
|
||||
"[Survey record generation] La valeur '%(type)s' pour le champs "
|
||||
"'answer_values_type' n'est pas supportée (pour la question %(question)s). "
|
||||
"Veuillez utiliser 'Enregistrement' ou 'Valeur' à la place."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#. odoo-python
|
||||
#: code:addons/survey_record_generation/models/survey_user_input.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"[Survey record generation] The boolean value %s(value)s is not supported "
|
||||
"(for question %(question)s)."
|
||||
msgstr ""
|
||||
"[Survey record generation] La valeur booléenne %s(value)s n'est pas "
|
||||
"supportée (pour la question %(question)s)."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#. odoo-python
|
||||
#: code:addons/survey_record_generation/models/survey_user_input.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"[Survey record generation] The question type %(type)s is not recognized (for"
|
||||
" question %(question)s)."
|
||||
msgstr ""
|
||||
"[Survey record generation] Le type de question %(type)s n'est pas reconnu "
|
||||
"(pour la question %(question)s)."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#. odoo-python
|
||||
#: code:addons/survey_record_generation/models/survey_user_input.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"[Survey record generation] The question type %(type)s is not supported yet."
|
||||
msgstr ""
|
||||
"[Survey record generation] Le type de question %(type)s n'est pas encore "
|
||||
"supporté."
|
||||
|
||||
#. module: survey_record_generation
|
||||
#. odoo-python
|
||||
#: code:addons/survey_record_generation/models/survey_record_creation_field_values.py:0
|
||||
@@ -537,4 +431,4 @@ msgstr ""
|
||||
#. module: survey_record_generation
|
||||
#: model:ir.model,name:survey_record_generation.model_survey_record_creation_field_values_x2m
|
||||
msgid "survey.record.creation.field.values.x2m"
|
||||
msgstr ""
|
||||
msgstr ""
|
||||
@@ -1,14 +1,12 @@
|
||||
|
||||
import logging
|
||||
import ast
|
||||
from typing import Literal
|
||||
|
||||
from odoo import api, fields, models, _, Command
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
AnswerValuesType: Literal["no", "value", "record"]
|
||||
|
||||
class SurveyQuestion(models.Model):
|
||||
_inherit = 'survey.question'
|
||||
@@ -16,8 +14,8 @@ class SurveyQuestion(models.Model):
|
||||
model_id = fields.Many2one('ir.model', string="Model")
|
||||
model_name = fields.Char(related="model_id.model")
|
||||
fill_domain = fields.Char("Domain", default="[]")
|
||||
answer_values_type = fields.Selection([('no', 'No values'),('value','Value'),('record','Record')], string="Associate value to answer", default="no", required=True)
|
||||
|
||||
answer_values_type = fields.Selection([('no', 'No values'),('value','Value'),('record','Record')], string="Associate value to answer", default="no", required=True)
|
||||
|
||||
@api.onchange('model_id')
|
||||
def onchange_model_id(self):
|
||||
self.fill_domain = "[]"
|
||||
|
||||
@@ -9,15 +9,15 @@ _logger = logging.getLogger(__name__)
|
||||
class SurveyQuestionAnswer(models.Model):
|
||||
_inherit = 'survey.question.answer'
|
||||
|
||||
record_id = fields.Reference(string="Referenced record", selection='_selection_target_model')
|
||||
model_id = fields.Many2one('ir.model', related="question_id.model_id")
|
||||
record_id = fields.Reference(string="Referenced record", selection='_selection_target_model')
|
||||
model_id = fields.Many2one('ir.model', related="question_id.model_id")
|
||||
answer_values_type = fields.Selection(related="question_id.answer_values_type")
|
||||
value_char = fields.Char('Value')
|
||||
|
||||
@api.model
|
||||
def _selection_target_model(self):
|
||||
return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])]
|
||||
|
||||
|
||||
@api.onchange('record_id')
|
||||
def onchange_record_id(self):
|
||||
if self.record_id:
|
||||
@@ -31,7 +31,7 @@ class SurveyQuestionAnswer(models.Model):
|
||||
or "record_id" not in fields
|
||||
):
|
||||
return result
|
||||
|
||||
|
||||
model = self.env['ir.model'].browse(result.get("model_id")).model
|
||||
res = self.env[model].search([], limit=1)
|
||||
if res:
|
||||
@@ -39,4 +39,4 @@ class SurveyQuestionAnswer(models.Model):
|
||||
model,
|
||||
res.id,
|
||||
)
|
||||
return result
|
||||
return result
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
@@ -12,44 +13,16 @@ class SurveyRecordCreation(models.Model):
|
||||
|
||||
name = fields.Char('Name')
|
||||
survey_id = fields.Many2one('survey.survey', string="Survey")
|
||||
model_id = fields.Many2one('ir.model', "Model", help="Model of generated record")
|
||||
model_id = fields.Many2one('ir.model', "Model", help="Model of generated record")
|
||||
field_values_ids = fields.One2many('survey.record.creation.field.values', 'survey_record_creation_id', string="Field values")
|
||||
warning_message = fields.Html('Warning message', compute="_compute_warning_message")
|
||||
sequence = fields.Integer("sequence")
|
||||
|
||||
update_existing_records = fields.Boolean(
|
||||
string="Update existing records",
|
||||
)
|
||||
field_to_retrieve_existing_records = fields.Many2one(
|
||||
"ir.model.fields",
|
||||
domain="[('id', 'in', allowed_field_ids)]",
|
||||
ondelete="cascade",
|
||||
help="Choose the field you want to use to retrieve the existing record. "
|
||||
"WARNING: We update only the first record found.",
|
||||
)
|
||||
allowed_field_ids = fields.Many2many(
|
||||
"ir.model.fields",
|
||||
compute="_compute_allowed_field_ids",
|
||||
store=True,
|
||||
string="Allowed Fields",
|
||||
)
|
||||
ignore_if_mandatory_field_is_missing = fields.Boolean(
|
||||
string="Ignore creation if a mandatory field is missing",
|
||||
help="If a mandatory field is missing when trying to create the record, "
|
||||
"an error is raised when the survey is submitted. "
|
||||
"If this option is activated, the error is ignored."
|
||||
)
|
||||
|
||||
@api.depends("field_values_ids.field_id")
|
||||
def _compute_allowed_field_ids(self):
|
||||
for record in self:
|
||||
record.allowed_field_ids = record.field_values_ids.mapped("field_id")
|
||||
|
||||
@api.onchange("model_id")
|
||||
@api.onchange('model_id')
|
||||
def clear_field_values_ids(self):
|
||||
self.field_values_ids = None
|
||||
|
||||
@api.depends("model_id","field_values_ids")
|
||||
@api.depends('model_id','field_values_ids')
|
||||
def _compute_warning_message(self):
|
||||
for record_creation in self:
|
||||
# check if all mandatory fields set
|
||||
@@ -57,10 +30,14 @@ class SurveyRecordCreation(models.Model):
|
||||
required_field_ids = self.model_id.field_id.filtered(lambda f:f.required and "property_" not in f.name)
|
||||
set_field_ids = self.field_values_ids.field_id
|
||||
missing_fields = required_field_ids - set_field_ids
|
||||
|
||||
|
||||
if missing_fields:
|
||||
record_creation.warning_message = _("Some required fields are not set : %s",', '.join([f"<b>{f.field_description}</b> (<i>{f.name}</i>)" for f in missing_fields]))
|
||||
else:
|
||||
record_creation.warning_message = None
|
||||
else:
|
||||
record_creation.warning_message = None
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from odoo.tools.misc import format_date
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
type_mapping = { #field types on the left, question types on the right. TODO : what about booleans ?
|
||||
type_mapping = {
|
||||
"char": ["char_box", "numerical_box", "date", "datetime", "simple_choice", "multiple_choice"],
|
||||
"text": ["char_box", "date", "simple_choice"],
|
||||
"html": ["text_box", "numerical_box", "datetime", "simple_choice"],
|
||||
@@ -17,7 +17,7 @@ type_mapping = { #field types on the left, question types on the right. TODO :
|
||||
"date": ["date"],
|
||||
"datetime": ["datetime"],
|
||||
"many2one": ["simple_choice"],
|
||||
"many2many": ["multiple_choice"],
|
||||
"many2many": ["multiple_choice"],
|
||||
"selection": ["char_box", "simple_choice"]
|
||||
}
|
||||
|
||||
@@ -32,18 +32,18 @@ class SurveyRecordCreationFieldValues(models.Model):
|
||||
model_id = fields.Many2one('ir.model', related="survey_record_creation_id.model_id")
|
||||
|
||||
field_id = fields.Many2one(
|
||||
'ir.model.fields',
|
||||
domain="[('model_id','=',model_id),('ttype','in',['char','selection','text','html','integer','float','date','datetime','many2one','many2many', 'boolean'])]",
|
||||
'ir.model.fields',
|
||||
domain="[('model_id','=',model_id),('readonly','=',False),('ttype','in',['char','selection','text','html','integer','float','date','datetime','many2one','many2many', 'boolean'])]",
|
||||
ondelete="cascade")
|
||||
field_relation = fields.Char(related='field_id.relation')
|
||||
field_type = fields.Selection(related="field_id.ttype")
|
||||
field_help = fields.Html('Help', compute="_compute_field_help")
|
||||
|
||||
value_origin = fields.Selection(
|
||||
[('fixed','Fixed'),('question','Question'),('other_record','From other created record')],
|
||||
string="Value origin",
|
||||
required=True,
|
||||
default='fixed',
|
||||
[('fixed','Fixed'),('question','Question'),('other_record','From other created record')],
|
||||
string="Value origin",
|
||||
required=True,
|
||||
default='fixed',
|
||||
help="""* Fixed: you can set the value in value field
|
||||
* Question: Response of the question will set the value. If you do not see your question, maybe the type of question do not match the type of field
|
||||
* From other created record: You can set other record creation to link several created records. Can only be used with many2one fields.""")
|
||||
@@ -57,7 +57,7 @@ class SurveyRecordCreationFieldValues(models.Model):
|
||||
fixed_value_integer = fields.Integer("Value")
|
||||
fixed_value_float = fields.Float("Value")
|
||||
fixed_value_date = fields.Date("Value")
|
||||
fixed_value_datetime = fields.Datetime("Value")
|
||||
fixed_value_datetime = fields.Datetime("Value")
|
||||
fixed_value_boolean = fields.Boolean("Value")
|
||||
|
||||
displayed_value = fields.Char("Value", compute="_compute_displayed_value")
|
||||
@@ -84,7 +84,7 @@ class SurveyRecordCreationFieldValues(models.Model):
|
||||
record_creation_field_values.allowed_question_ids = None
|
||||
return
|
||||
question_domain = [('survey_id','=',record_creation_field_values.survey_id.id)]
|
||||
|
||||
|
||||
if record_creation_field_values.field_id.ttype in ['many2one','many2many']:
|
||||
question_domain.extend(['|','&',('answer_values_type','=','record'),('model_id','=',record_creation_field_values.field_id.relation),('answer_values_type','=','value')])
|
||||
if record_creation_field_values.field_id.ttype in type_mapping:
|
||||
@@ -96,7 +96,7 @@ class SurveyRecordCreationFieldValues(models.Model):
|
||||
@api.model
|
||||
def _selection_target_model(self):
|
||||
return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])]
|
||||
|
||||
|
||||
def clean_values(self):
|
||||
# clean values
|
||||
self.fixed_value_many2many = None
|
||||
@@ -119,7 +119,7 @@ class SurveyRecordCreationFieldValues(models.Model):
|
||||
# Set reference field model and select first record
|
||||
if self.field_id and self.field_id.ttype == 'many2one' and self.field_id.relation:
|
||||
rec = self.env[self.field_id.relation].search([], limit=1)
|
||||
if rec:
|
||||
if rec:
|
||||
self.fixed_value_many2one = f"{self.field_id.relation},{rec.id}"
|
||||
else:
|
||||
model_name = self.env['ir.model'].search([('model','=',self.field_id.relation)]).name
|
||||
@@ -136,12 +136,12 @@ class SurveyRecordCreationFieldValues(models.Model):
|
||||
if self.value_origin == 'fixed':
|
||||
if self.field_type == 'many2one':
|
||||
if self.fixed_value_many2one:
|
||||
return self.fixed_value_many2one.id
|
||||
return self.fixed_value_many2one.id
|
||||
elif self.field_type == 'many2many':
|
||||
return [m2m.value_reference.id for m2m in self.fixed_value_many2many if m2m.value_reference]
|
||||
else:
|
||||
return self["fixed_value_"+self.field_type]
|
||||
|
||||
|
||||
|
||||
@api.onchange("fixed_value_char","fixed_value_selection","fixed_value_text","fixed_value_html","fixed_value_integer","fixed_value_float","fixed_value_date","fixed_value_datetime",'fixed_value_many2one', "fixed_value_many2many","other_created_record_id","question_id")
|
||||
def _compute_displayed_value(self):
|
||||
@@ -172,7 +172,7 @@ class SurveyRecordCreationFieldValues(models.Model):
|
||||
record.displayed_value = ""
|
||||
|
||||
class SurveyRecordCreationFieldValuesX2m(models.Model):
|
||||
"""O2m an M2m default values
|
||||
"""O2m an M2m default values
|
||||
"""
|
||||
_name = 'survey.record.creation.field.values.x2m'
|
||||
|
||||
@@ -182,16 +182,16 @@ class SurveyRecordCreationFieldValuesX2m(models.Model):
|
||||
@api.model
|
||||
def _selection_target_model(self):
|
||||
return [(model.model, model.name) for model in self.env['ir.model'].sudo().search([])]
|
||||
|
||||
|
||||
|
||||
@api.onchange('survey_record_creation_field_values_id')
|
||||
def _onchange_model_name(self):
|
||||
# Set reference field model and select first record
|
||||
field = self.survey_record_creation_field_values_id.field_id
|
||||
field = self.survey_record_creation_field_values_id.field_id
|
||||
if field and "2many" in field.ttype and field.relation:
|
||||
rec = self.env[field.relation].search([], limit=1)
|
||||
if rec:
|
||||
if rec:
|
||||
self.value_reference = f"{field.relation},{rec.id}"
|
||||
else:
|
||||
model_name = self.env['ir.model'].search([('model','=',field.relation)]).name
|
||||
raise ValueError(_('You should append at least one record in %s',(model_name,)))
|
||||
raise ValueError(_('You should append at least one record in %s',(model_name,)))
|
||||
@@ -1,25 +1,13 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo import models, fields, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Command
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .survey_question import AnswerValuesType
|
||||
from .survey_record_creation import SurveyRecordCreation
|
||||
from .survey_record_creation_field_values import SurveyRecordCreationFieldValues
|
||||
|
||||
|
||||
class SurveyUserInput(models.Model):
|
||||
_inherit = "survey.user_input"
|
||||
|
||||
generated_record_ids = fields.One2many(
|
||||
"survey.generated.record", "user_input_id", "Generated records"
|
||||
)
|
||||
generated_records_count = fields.Integer(
|
||||
"Attempts Count", compute="_compute_generated_records_count"
|
||||
)
|
||||
generated_record_ids = fields.One2many('survey.generated.record', 'user_input_id', 'Generated records')
|
||||
generated_records_count = fields.Integer("Attempts Count", compute='_compute_generated_records_count')
|
||||
|
||||
def _compute_generated_records_count(self):
|
||||
for user_input in self:
|
||||
@@ -28,312 +16,109 @@ class SurveyUserInput(models.Model):
|
||||
def action_redirect_to_generated_records(self):
|
||||
self.ensure_one()
|
||||
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(
|
||||
"survey_record_generation.survey_generated_record_action"
|
||||
)
|
||||
action = self.env['ir.actions.act_window']._for_xml_id('survey_record_generation.survey_generated_record_action')
|
||||
""" context = dict(self.env.context or {})
|
||||
|
||||
context['create'] = False
|
||||
context['search_default_survey_id'] = self.survey_id.id
|
||||
context['search_default_group_by_survey'] = False
|
||||
if self.partner_id:
|
||||
context['search_default_partner_id'] = self.partner_id.id
|
||||
elif self.email:
|
||||
context['search_default_email'] = self.email
|
||||
|
||||
action['context'] = context """
|
||||
|
||||
return action
|
||||
|
||||
def _mark_done(self):
|
||||
def _mark_done(self, ignore_when_res_partner_mandatory_fields_are_missing = False):
|
||||
# generate records
|
||||
for user_input in self:
|
||||
created_records = {}
|
||||
other_record_fields_to_update: list[SurveyRecordCreationFieldValues] = []
|
||||
fields_to_update = []
|
||||
|
||||
record_creation: SurveyRecordCreation
|
||||
for (
|
||||
record_creation
|
||||
) in user_input.survey_id.survey_record_creation_ids.sorted("sequence"):
|
||||
model: str = record_creation.model_id.model
|
||||
vals: dict = {}
|
||||
|
||||
field_value: SurveyRecordCreationFieldValues
|
||||
for record_creation in user_input.survey_id.survey_record_creation_ids.sorted('sequence'):
|
||||
model = record_creation.model_id.model
|
||||
vals = {}
|
||||
ModelClass = self.env[model]
|
||||
|
||||
for field_value in record_creation.field_values_ids:
|
||||
value, other_record_fields_to_update = (
|
||||
self.get_value_based_on_value_origin(
|
||||
field_value=field_value,
|
||||
user_input=user_input,
|
||||
created_records=created_records,
|
||||
model=model,
|
||||
other_record_fields_to_update=other_record_fields_to_update,
|
||||
)
|
||||
)
|
||||
field_name: str = field_value.field_id.name
|
||||
if field_value.value_origin == 'fixed':
|
||||
vals[field_value.field_id.name] = field_value.get_fixed_value_for_record_creation()
|
||||
elif field_value.value_origin == 'question':
|
||||
# find user_input_lines of the question
|
||||
user_input_lines = [user_input_line for user_input_line in user_input.user_input_line_ids if user_input_line.question_id == field_value.question_id]
|
||||
|
||||
vals[field_name] = value
|
||||
if not user_input_lines:
|
||||
continue
|
||||
|
||||
existing_record = self.find_existing_record(record_creation, vals)
|
||||
|
||||
duplicate = self.find_duplicate_if_there_are_fields_with_unicity_check(
|
||||
model, record_creation, vals
|
||||
)
|
||||
if field_value.question_id.question_type in ['simple_choice', 'multiple_choice','matrix']:
|
||||
if field_value.question_id.answer_values_type == 'record':
|
||||
record_ids = []
|
||||
for user_input_line in user_input_lines:
|
||||
if user_input_line.suggested_answer_id and user_input_line.suggested_answer_id.record_id:
|
||||
record_ids.append(user_input_line.suggested_answer_id.record_id.id)
|
||||
if field_value.question_id.question_type == 'simple_choice':
|
||||
vals[field_value.field_id.name] = record_ids[0]
|
||||
else:
|
||||
vals[field_value.field_id.name] = record_ids
|
||||
if field_value.question_id.answer_values_type == 'value':
|
||||
if field_value.field_id.ttype == "boolean":
|
||||
boolean_value = user_input_lines[0].suggested_answer_id.value_char in [True, 1, "1", "True", "true", "Oui", "oui"]
|
||||
vals[field_value.field_id.name] = boolean_value
|
||||
else:
|
||||
vals[field_value.field_id.name] = user_input_lines[0].suggested_answer_id.value_char
|
||||
elif user_input_lines[0].answer_type: # if value not filled by user, answer_type not set
|
||||
vals[field_value.field_id.name] = user_input_lines[0][f"value_{user_input_lines[0].answer_type}"]
|
||||
else:
|
||||
vals[field_value.field_id.name] = None
|
||||
elif field_value.value_origin == 'other_record':
|
||||
fields_to_update.append(field_value)
|
||||
# check if the field to update is mandatory
|
||||
if ModelClass._fields[field_value.field_id.name].required:
|
||||
# check if the other record is already created, if yes add it to vals
|
||||
if len(created_records) > 0 and created_records[field_value.other_created_record_id.id]:
|
||||
linked_record = created_records[field_value.other_created_record_id.id]
|
||||
vals[field_value.field_id.name] = linked_record.id
|
||||
else:
|
||||
raise UserError(
|
||||
_("The field %s is mandatory. In Record Creation tab, drag %s at the top of the table")
|
||||
% (field_value.field_id.display_name, field_value.other_created_record_id.name)
|
||||
)
|
||||
# check duplicates
|
||||
uniq_fields = [field_value.field_id.name for field_value in record_creation.field_values_ids.filtered(lambda r:r.unicity_check)]
|
||||
duplicate = None
|
||||
if uniq_fields:
|
||||
uniq_domain = []
|
||||
for uniq_field in uniq_fields:
|
||||
uniq_domain.append((uniq_field,'=',vals[uniq_field]))
|
||||
duplicate = self.env[model].search(uniq_domain, limit=1)
|
||||
|
||||
if duplicate:
|
||||
record = duplicate
|
||||
elif existing_record:
|
||||
vals_with_keys_not_in_record = {
|
||||
k: v
|
||||
for k, v in vals.items()
|
||||
if not getattr(existing_record, k, False)
|
||||
}
|
||||
existing_record.write(vals_with_keys_not_in_record)
|
||||
record = existing_record
|
||||
else:
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
record = self.env[model].create(vals)
|
||||
if model == "res.partner" and not self.partner_id:
|
||||
self.partner_id = record.id
|
||||
except Exception:
|
||||
# This a broad exception because it could be IntegrityError,
|
||||
# EmptyNamesError in case partner_firstname is installed etc...
|
||||
if record_creation.ignore_if_mandatory_field_is_missing:
|
||||
if model == "res.partner" and ignore_when_res_partner_mandatory_fields_are_missing:
|
||||
# this part has been developed for Calim specific needs : being able to create several Contacts with the same survey
|
||||
# TODO : find a way to make it generic for all models ?
|
||||
if not vals.get("lastname") and not vals.get("firstname"):
|
||||
continue
|
||||
raise
|
||||
# Create record
|
||||
record = self.env[model].create(vals)
|
||||
# Link generated records to user input
|
||||
self.env["survey.generated.record"].create(
|
||||
{
|
||||
"survey_record_creation_name": record_creation.name,
|
||||
"survey_record_creation_id": record_creation.id,
|
||||
"user_input_id": user_input.id,
|
||||
"created_record_id": f"{model},{record.id}",
|
||||
}
|
||||
)
|
||||
self.env['survey.generated.record'].create({
|
||||
'survey_record_creation_name':record_creation.name,
|
||||
'survey_record_creation_id':record_creation.id,
|
||||
'user_input_id':user_input.id,
|
||||
"created_record_id":"%s,%s" % (model,record.id)
|
||||
})
|
||||
|
||||
created_records[record_creation.id] = record
|
||||
|
||||
# update linked record
|
||||
for field_to_update in other_record_fields_to_update:
|
||||
record_to_update = created_records.get(
|
||||
field_to_update.survey_record_creation_id.id
|
||||
)
|
||||
# update linked records
|
||||
for field_to_update in fields_to_update:
|
||||
record_to_update = created_records.get(field_to_update.survey_record_creation_id.id)
|
||||
if record_to_update:
|
||||
linked_record = created_records[
|
||||
field_to_update.other_created_record_id.id
|
||||
]
|
||||
value = self.get_value_for_relational_field(
|
||||
field_to_update, linked_record
|
||||
)
|
||||
record_to_update.write({field_to_update.field_id.name: value})
|
||||
linked_record = created_records[field_to_update.other_created_record_id.id]
|
||||
record_to_update.write({field_to_update.field_id.name:linked_record.id})
|
||||
|
||||
return super()._mark_done()
|
||||
|
||||
def find_existing_record(
|
||||
self, record_creation: "SurveyRecordCreation", vals: dict
|
||||
) -> Any:
|
||||
if record_creation.update_existing_records:
|
||||
model = record_creation.model_id.model
|
||||
search_field = record_creation.field_to_retrieve_existing_records
|
||||
user_answer_value = vals.get(search_field.name)
|
||||
if user_answer_value:
|
||||
return self.env[model].search(
|
||||
[(search_field.name, "=", user_answer_value)], limit=1
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def get_value_based_on_value_origin(
|
||||
self,
|
||||
field_value: "SurveyRecordCreationFieldValues",
|
||||
user_input: "SurveyUserInput",
|
||||
created_records: dict[Any, Any],
|
||||
model: str,
|
||||
other_record_fields_to_update: list["SurveyRecordCreationFieldValues"],
|
||||
) -> tuple[Any, list["SurveyRecordCreationFieldValues"]]:
|
||||
value: Any = None
|
||||
|
||||
if field_value.value_origin == "fixed":
|
||||
value = field_value.get_fixed_value_for_record_creation()
|
||||
elif field_value.value_origin == "question":
|
||||
value = self.get_value_from_user_answer(field_value, user_input)
|
||||
elif field_value.value_origin == "other_record":
|
||||
# if the other_record value is a required field, get it or raise
|
||||
value = self.get_required_value_from_other_record(
|
||||
model, created_records, field_value
|
||||
)
|
||||
# otherwise, we update the record later (out of this for loop)
|
||||
if not value:
|
||||
other_record_fields_to_update.append(field_value)
|
||||
return value, other_record_fields_to_update
|
||||
|
||||
@staticmethod
|
||||
def get_value_for_relational_field(
|
||||
field_to_update: "SurveyRecordCreationFieldValues", linked_record
|
||||
) -> Any:
|
||||
field_type = field_to_update.field_id.ttype
|
||||
if field_type == "many2one":
|
||||
return linked_record.id
|
||||
else:
|
||||
# many2many or one2many
|
||||
return [Command.set(linked_record.ids)]
|
||||
|
||||
def find_duplicate_if_there_are_fields_with_unicity_check(
|
||||
self, model: str, record_creation: "SurveyRecordCreation", vals: dict[Any, Any]
|
||||
) -> Any:
|
||||
# check duplicates
|
||||
unique_fields = [
|
||||
field_value.field_id.name
|
||||
for field_value in record_creation.field_values_ids.filtered(
|
||||
lambda r: r.unicity_check
|
||||
)
|
||||
]
|
||||
duplicate = None
|
||||
if unique_fields:
|
||||
uniq_domain = []
|
||||
for uniq_field in unique_fields:
|
||||
uniq_domain.append((uniq_field, "=", vals[uniq_field]))
|
||||
duplicate = self.env[model].search(uniq_domain, limit=1)
|
||||
return duplicate
|
||||
|
||||
def get_required_value_from_other_record(
|
||||
self,
|
||||
model: str,
|
||||
created_records: dict[Any, Any],
|
||||
field_value: "SurveyRecordCreationFieldValues",
|
||||
) -> Any:
|
||||
model_class = self.env[model]
|
||||
if model_class._fields[field_value.field_id.name].required:
|
||||
# check if the other record is already created,
|
||||
# if yes add it to vals, else raise
|
||||
if (
|
||||
len(created_records) > 0
|
||||
and created_records[field_value.other_created_record_id.id]
|
||||
):
|
||||
linked_record = created_records[field_value.other_created_record_id.id]
|
||||
return self.get_value_for_relational_field(field_value, linked_record)
|
||||
else:
|
||||
raise UserError(
|
||||
_(
|
||||
"The field %(field)s is mandatory for model %(model)s. "
|
||||
"In Record Creation tab, drag %(record)s "
|
||||
"on top of the model %(model)s."
|
||||
)
|
||||
% {
|
||||
"field": field_value.field_id.display_name,
|
||||
"model": model,
|
||||
"record": field_value.other_created_record_id.name,
|
||||
}
|
||||
)
|
||||
|
||||
def get_value_from_user_answer(
|
||||
self,
|
||||
field_value: "SurveyRecordCreationFieldValues",
|
||||
user_input: "SurveyUserInput",
|
||||
) -> Any:
|
||||
# find user_input_lines (which are user's answers) for the question
|
||||
user_input_lines = [
|
||||
user_input_line
|
||||
for user_input_line in user_input.user_input_line_ids
|
||||
if user_input_line.question_id == field_value.question_id
|
||||
]
|
||||
|
||||
if not user_input_lines:
|
||||
# If the question has not been displayed to the user,
|
||||
# there are no user_input_lines
|
||||
return None
|
||||
if user_input_lines[0].skipped:
|
||||
# The question has been ignored by the user
|
||||
return None
|
||||
|
||||
question_type = field_value.question_id.question_type
|
||||
|
||||
if question_type in [
|
||||
"char_box",
|
||||
"text_box",
|
||||
"numerical_box",
|
||||
"date",
|
||||
"datetime",
|
||||
]:
|
||||
return user_input_lines[0][f"value_{user_input_lines[0].answer_type}"]
|
||||
elif question_type in ["simple_choice", "multiple_choice", "matrix"]:
|
||||
answer_values_type = field_value.question_id.answer_values_type
|
||||
return self.get_value_based_on_answer_values_type(
|
||||
answer_values_type, field_value, question_type, user_input_lines
|
||||
)
|
||||
else:
|
||||
raise UserError(
|
||||
_(
|
||||
"[Survey record generation] The question type %(type)s is not "
|
||||
"recognized (for question %(question)s)."
|
||||
)
|
||||
% {"type": question_type, "question": field_value.question_id.title}
|
||||
)
|
||||
|
||||
def get_value_based_on_answer_values_type(
|
||||
self,
|
||||
answer_values_type: "AnswerValuesType",
|
||||
field_value: "SurveyRecordCreationFieldValues",
|
||||
question_type: Literal["simple_choice", "multiple_choice", "matrix"],
|
||||
user_input_lines: list[Any],
|
||||
) -> Any:
|
||||
if answer_values_type == "record":
|
||||
answered_record_ids = []
|
||||
for user_input_line in user_input_lines:
|
||||
if (
|
||||
user_input_line.suggested_answer_id
|
||||
and user_input_line.suggested_answer_id.record_id
|
||||
):
|
||||
answered_record_ids.append(
|
||||
user_input_line.suggested_answer_id.record_id.id
|
||||
)
|
||||
if not answered_record_ids:
|
||||
return None
|
||||
if question_type == "simple_choice":
|
||||
return answered_record_ids[0]
|
||||
elif question_type == "multiple_choice":
|
||||
return answered_record_ids
|
||||
else:
|
||||
raise UserError(
|
||||
_(
|
||||
"[Survey record generation] The question type"
|
||||
" %(type)s is not supported yet."
|
||||
)
|
||||
% {"type": question_type}
|
||||
)
|
||||
elif answer_values_type == "value":
|
||||
answer_value_char = user_input_lines[0].suggested_answer_id.value_char
|
||||
if field_value.field_id.ttype != "boolean":
|
||||
return answer_value_char
|
||||
else:
|
||||
return self.get_boolean_value(
|
||||
answer_value_char=answer_value_char,
|
||||
question_title=field_value.question_id.title,
|
||||
)
|
||||
else:
|
||||
raise UserError(
|
||||
_(
|
||||
"[Survey record generation] The answer values type '%(type)s' "
|
||||
"is not supported (for question %(question)s). Use 'record' or "
|
||||
"'value' instead."
|
||||
)
|
||||
% {
|
||||
"type": answer_values_type,
|
||||
"question": field_value.question_id.title,
|
||||
}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_boolean_value(answer_value_char: str, question_title: str) -> bool:
|
||||
# Below code is a trick to be able to use "simple_choice" question
|
||||
# with values 'yes' and 'no' and transform it to boolean.
|
||||
if boolean_value := answer_value_char in [
|
||||
"1",
|
||||
"True",
|
||||
"true",
|
||||
"Oui",
|
||||
"oui",
|
||||
"Yes",
|
||||
"yes",
|
||||
]:
|
||||
return boolean_value
|
||||
else:
|
||||
raise UserError(
|
||||
_(
|
||||
"[Survey record generation] The boolean value %s(value)s "
|
||||
"is not supported (for question %(question)s)."
|
||||
)
|
||||
% {
|
||||
"value": answer_value_char,
|
||||
"question": question_title,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
* `Elabore <https://www.elabore.coop>`_
|
||||
|
||||
* Clément Thomas
|
||||
* Quentin Mondot
|
||||
* Clément Thomas
|
||||
@@ -1 +0,0 @@
|
||||
from . import test_survey_record_creation
|
||||
@@ -1,893 +0,0 @@
|
||||
from datetime import date
|
||||
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from odoo.addons.survey.tests.common import SurveyCase
|
||||
|
||||
|
||||
class TestSurveyRecordCreation(SurveyCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# We create a survey
|
||||
self.survey = self.env["survey.survey"].create(
|
||||
{
|
||||
"title": "Test Survey",
|
||||
}
|
||||
)
|
||||
# With a single question "name"
|
||||
self.question_name = self._add_question(
|
||||
page=None,
|
||||
name="Name",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
|
||||
self.res_partner_model = self.env["ir.model"]._get("res.partner")
|
||||
# The administrator has set up a record creation on res.partner model
|
||||
self.survey_record_creation = self.env["survey.record.creation"].create(
|
||||
{
|
||||
"name": "Contact",
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
}
|
||||
)
|
||||
self.name_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "name")]
|
||||
)
|
||||
# And linked the res_partner field "name" to the answer of question_name
|
||||
self.name_survey_record_creation_field_values = self.env[
|
||||
"survey.record.creation.field.values"
|
||||
].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": self.name_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_name.id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_record_is_created(self):
|
||||
# Easy test to become familiar with the subject (check comments of setUp)
|
||||
|
||||
# We simulate that jean@test.fr has answered the survey
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
# He answered "Jean" to the question_name
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
# And validate the survey
|
||||
self.answer._mark_done()
|
||||
|
||||
# Thus, the res.partner with the name "Jean" has been created
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(partner.name == "Jean")
|
||||
|
||||
def test_all_types_of_question(self):
|
||||
# Still todo : "datetime" and "text_box"
|
||||
# Also todo : "simple_choice" with answer_values_type "no"
|
||||
# Also todo : "multiple_choice" with answer_values_type "no" and "value"
|
||||
# Note : matrix question type is never proposed in the allowed_question_ids
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
|
||||
### "char_box" type of question, tested with field CHAR "name" ###
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
|
||||
### "numerical_box" type of question,
|
||||
# tested with FLOAT field "partner_latitude" ###
|
||||
self.question_partner_latitude = self._add_question(
|
||||
page=None,
|
||||
name="Partner latitude",
|
||||
qtype="numerical_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
|
||||
partner_latitude_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "partner_latitude")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": partner_latitude_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_partner_latitude.id,
|
||||
}
|
||||
)
|
||||
|
||||
self._add_answer_line(
|
||||
question=self.question_partner_latitude,
|
||||
answer=self.answer,
|
||||
answer_value=44.73333,
|
||||
)
|
||||
|
||||
### "date" type of question, tested with DATE field "date" ###
|
||||
self.question_date = self._add_question(
|
||||
page=None, name="Date", qtype="date", survey_id=self.survey.id, sequence=1
|
||||
)
|
||||
|
||||
date_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "date")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": date_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_date.id,
|
||||
}
|
||||
)
|
||||
|
||||
self._add_answer_line(
|
||||
question=self.question_date, answer=self.answer, answer_value=date.today()
|
||||
)
|
||||
|
||||
### "simple_choice" type of question, tested with SELECTION field "type" ###
|
||||
### Here we also test answer_values_type "value"
|
||||
self.question_type = self._add_question(
|
||||
page=None,
|
||||
name="Type",
|
||||
qtype="simple_choice",
|
||||
labels=[
|
||||
{"value": "contact"},
|
||||
{"value": "other"},
|
||||
],
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
answer_values_type="value",
|
||||
)
|
||||
|
||||
self.question_type.suggested_answer_ids[0].value_char = (
|
||||
self.question_type.suggested_answer_ids[0].value
|
||||
)
|
||||
self.question_type.suggested_answer_ids[1].value_char = (
|
||||
self.question_type.suggested_answer_ids[1].value
|
||||
)
|
||||
|
||||
type_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "type")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": type_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_type.id,
|
||||
}
|
||||
)
|
||||
|
||||
self._add_answer_line(
|
||||
question=self.question_type,
|
||||
answer=self.answer,
|
||||
answer_value=self.question_type.suggested_answer_ids[0].id,
|
||||
)
|
||||
|
||||
### "simple_choice" type of question, tested with MANY2ONE field "title" ###
|
||||
### Here we also test answer_values_type "record"
|
||||
|
||||
mister_title = self.env["res.partner.title"].create({"name": "Mister"})
|
||||
madam_title = self.env["res.partner.title"].create({"name": "Madam"})
|
||||
self.question_title = self._add_question(
|
||||
page=None,
|
||||
name="Title",
|
||||
qtype="simple_choice",
|
||||
labels=[
|
||||
{"value": mister_title.display_name},
|
||||
{"value": madam_title.display_name},
|
||||
],
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
answer_values_type="record",
|
||||
)
|
||||
|
||||
self.question_title.suggested_answer_ids[0].record_id = (
|
||||
f"res.partner.title,{mister_title.id}"
|
||||
)
|
||||
self.question_title.suggested_answer_ids[1].record_id = (
|
||||
f"res.partner.title,{madam_title.id}"
|
||||
)
|
||||
|
||||
title_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "title")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": title_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_title.id,
|
||||
}
|
||||
)
|
||||
|
||||
self._add_answer_line(
|
||||
question=self.question_title,
|
||||
answer=self.answer,
|
||||
answer_value=self.question_title.suggested_answer_ids[0].id,
|
||||
)
|
||||
|
||||
### "multiple_choice" type of question,
|
||||
# tested with MANY2MANY field "category" ###
|
||||
adult_category = self.env["res.partner.category"].create({"name": "Adult"})
|
||||
teenager_category = self.env["res.partner.category"].create(
|
||||
{"name": "Teenager"}
|
||||
)
|
||||
child_category = self.env["res.partner.category"].create({"name": "Child"})
|
||||
self.question_category = self._add_question(
|
||||
page=None,
|
||||
name="Category",
|
||||
qtype="multiple_choice",
|
||||
labels=[
|
||||
{"value": adult_category.display_name},
|
||||
{"value": teenager_category.display_name},
|
||||
{"value": child_category.display_name},
|
||||
],
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
answer_values_type="record",
|
||||
)
|
||||
|
||||
self.question_category.suggested_answer_ids[0].record_id = (
|
||||
f"res.partner.category,{adult_category.id}"
|
||||
)
|
||||
self.question_category.suggested_answer_ids[1].record_id = (
|
||||
f"res.partner.category,{teenager_category.id}"
|
||||
)
|
||||
self.question_category.suggested_answer_ids[2].record_id = (
|
||||
f"res.partner.category,{child_category.id}"
|
||||
)
|
||||
|
||||
category_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "category_id")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": category_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_category.id,
|
||||
}
|
||||
)
|
||||
|
||||
self._add_answer_line(
|
||||
question=self.question_category,
|
||||
answer=self.answer,
|
||||
answer_value=self.question_category.suggested_answer_ids[0].id,
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_category,
|
||||
answer=self.answer,
|
||||
answer_value=self.question_category.suggested_answer_ids[1].id,
|
||||
)
|
||||
|
||||
self.answer._mark_done()
|
||||
partner = self.env["res.partner"].search(
|
||||
[
|
||||
("name", "=", "Jean"),
|
||||
("partner_latitude", "=", 44.73333),
|
||||
("date", "=", date.today()),
|
||||
("type", "=", "contact"),
|
||||
("title", "=", mister_title.id),
|
||||
("category_id", "=", adult_category.id),
|
||||
("category_id", "=", teenager_category.id),
|
||||
("category_id", "!=", child_category.id),
|
||||
]
|
||||
)
|
||||
self.assertTrue(partner.name == "Jean")
|
||||
|
||||
def test_records_are_created(self):
|
||||
# we test that several records can be created at the end of the same survey
|
||||
# concurrently, we test the value_origin "other_record" and "fixed"
|
||||
res_partner_bank_model = self.env["ir.model"]._get("res.partner.bank")
|
||||
self.bank_survey_record_creation = self.env["survey.record.creation"].create(
|
||||
{
|
||||
"name": "Bank",
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": res_partner_bank_model.id,
|
||||
}
|
||||
)
|
||||
# Below we test "value_origin": "other_record" with a required field
|
||||
partner_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner.bank"), ("name", "=", "partner_id")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.bank_survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": res_partner_bank_model.id,
|
||||
"field_id": partner_field.id,
|
||||
"value_origin": "other_record",
|
||||
"other_created_record_id": self.survey_record_creation.id,
|
||||
}
|
||||
)
|
||||
# Below we test "value_origin": "fixed"
|
||||
acc_number_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner.bank"), ("name", "=", "acc_number")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.bank_survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": res_partner_bank_model.id,
|
||||
"field_id": acc_number_field.id,
|
||||
"value_origin": "fixed",
|
||||
"fixed_value_char": "FR76 1444 5004 0004 0000 0000 000",
|
||||
}
|
||||
)
|
||||
# Below we test "value_origin": "other_record" with a NOT required field
|
||||
bank_ids_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "bank_ids")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": bank_ids_field.id,
|
||||
"value_origin": "other_record",
|
||||
"other_created_record_id": self.bank_survey_record_creation.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(partner.name == "Jean")
|
||||
bank_account = self.env["res.partner.bank"].search(
|
||||
[("partner_id", "=", partner.id)]
|
||||
)
|
||||
self.assertTrue(bank_account.acc_number == "FR76 1444 5004 0004 0000 0000 000")
|
||||
|
||||
def test_records_of_same_model_are_created(self):
|
||||
# When we have 2 survey.record.creation on res.partner,
|
||||
# we check that 2 contacts are created
|
||||
self.second_question_name = self._add_question(
|
||||
page=None,
|
||||
name="Name of second person",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
|
||||
self.second_contact_creation = self.env["survey.record.creation"].create(
|
||||
{
|
||||
"name": "Contact 2",
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
}
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.second_contact_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": self.name_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.second_question_name.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.first_answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name,
|
||||
answer=self.first_answer,
|
||||
answer_value="Jean",
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.second_question_name,
|
||||
answer=self.first_answer,
|
||||
answer_value="Jeanne",
|
||||
)
|
||||
self.first_answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(partner.name == "Jean")
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jeanne")])
|
||||
self.assertTrue(partner.name == "Jeanne")
|
||||
|
||||
def test_survey_submitted_twice_by_same_user(self):
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(partner.name == "Jean")
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partners = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(len(partners) == 2)
|
||||
|
||||
def test_unicity_check(self):
|
||||
# In this test, we check the behavior of unicity_check
|
||||
self.name_survey_record_creation_field_values.unicity_check = True
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(partner.name == "Jean")
|
||||
|
||||
self.second_answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.second_answer, answer_value="Jean"
|
||||
)
|
||||
self.second_answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(partner.name == "Jean")
|
||||
self.assertTrue(len(partner) == 1)
|
||||
|
||||
def test_some_questions_are_not_answered(self):
|
||||
self.question_email = self._add_question(
|
||||
page=None,
|
||||
name="Email",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
mister_title = self.env["res.partner.title"].create({"name": "Mister"})
|
||||
madam_title = self.env["res.partner.title"].create({"name": "Madam"})
|
||||
self.question_title = self._add_question(
|
||||
page=None,
|
||||
name="Title",
|
||||
qtype="simple_choice",
|
||||
labels=[
|
||||
{"value": mister_title.display_name},
|
||||
{"value": madam_title.display_name},
|
||||
],
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
answer_values_type="record",
|
||||
)
|
||||
self.question_title.suggested_answer_ids[0].record_id = (
|
||||
f"res.partner.title,{mister_title.id}"
|
||||
)
|
||||
self.question_title.suggested_answer_ids[1].record_id = (
|
||||
f"res.partner.title,{madam_title.id}"
|
||||
)
|
||||
|
||||
self.question_street = self._add_question(
|
||||
page=None,
|
||||
name="Street",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
|
||||
email_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "email")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": email_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_email.id,
|
||||
}
|
||||
)
|
||||
title_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "title")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": title_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_title.id,
|
||||
}
|
||||
)
|
||||
street_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "street")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": street_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_street.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_email,
|
||||
answer=self.answer,
|
||||
answer_value=False,
|
||||
skipped=True,
|
||||
answer_type=False,
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_title,
|
||||
answer=self.answer,
|
||||
answer_value=False,
|
||||
skipped=True,
|
||||
answer_type=False,
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(partner.name == "Jean")
|
||||
self.assertFalse(partner.email)
|
||||
self.assertFalse(partner.title)
|
||||
self.assertFalse(partner.street)
|
||||
|
||||
def test_boolean_field(self):
|
||||
self.question_employee = self._add_question(
|
||||
page=None,
|
||||
name="Employee",
|
||||
qtype="simple_choice",
|
||||
labels=[
|
||||
{"value": "yes"},
|
||||
{"value": "no"},
|
||||
],
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
answer_values_type="value",
|
||||
)
|
||||
|
||||
self.question_employee.suggested_answer_ids[0].value_char = (
|
||||
self.question_employee.suggested_answer_ids[0].value
|
||||
)
|
||||
self.question_employee.suggested_answer_ids[1].value_char = (
|
||||
self.question_employee.suggested_answer_ids[1].value
|
||||
)
|
||||
|
||||
employee_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "employee")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": employee_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_employee.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_employee,
|
||||
answer=self.answer,
|
||||
answer_value=self.question_employee.suggested_answer_ids[0].id,
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(partner.employee)
|
||||
|
||||
def test_update_existing_record(self):
|
||||
# A contact with name 'Jean' already exists.
|
||||
# We'll update the email of this partner (and not create a new one)
|
||||
self.env["res.partner"].create({"name": "Jean"})
|
||||
|
||||
self.question_email = self._add_question(
|
||||
page=None,
|
||||
name="Email",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
|
||||
self.survey_record_creation.write(
|
||||
{
|
||||
"update_existing_records": True,
|
||||
"field_to_retrieve_existing_records": self.name_field.id,
|
||||
}
|
||||
)
|
||||
email_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "email")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": email_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_email.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_email,
|
||||
answer=self.answer,
|
||||
answer_value="jean@test.fr",
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(len(partner) == 1)
|
||||
self.assertTrue(partner.email == "jean@test.fr")
|
||||
|
||||
def test_update_only_empty_fields_when_updating_records(self):
|
||||
# A contact with name 'Jean' and email 'jean@test.fr' already exists.
|
||||
# We'll update the field 'function' of this partner and won't update the email
|
||||
# because it's already filled up
|
||||
self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Jean",
|
||||
"email": "jean@test.fr",
|
||||
# when the survey is submitted, email should not be updated
|
||||
}
|
||||
)
|
||||
self.question_email = self._add_question(
|
||||
page=None,
|
||||
name="Email",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
self.question_function = self._add_question(
|
||||
page=None,
|
||||
name="Function",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
|
||||
self.survey_record_creation.write(
|
||||
{
|
||||
"update_existing_records": True,
|
||||
"field_to_retrieve_existing_records": self.name_field.id,
|
||||
}
|
||||
)
|
||||
email_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "email")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": email_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_email.id,
|
||||
}
|
||||
)
|
||||
function_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "function")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": function_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_function.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_email,
|
||||
answer=self.answer,
|
||||
answer_value="ThisEmailShouldNotBeUpdated@test.fr",
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_function,
|
||||
answer=self.answer,
|
||||
answer_value="happiness office manager",
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(len(partner) == 1)
|
||||
self.assertTrue(partner.email == "jean@test.fr")
|
||||
self.assertTrue(partner.function == "happiness office manager")
|
||||
|
||||
def test_unicity_check_has_priority_over_update(self):
|
||||
# In this test, we verify that if a field is set up with unicity_check
|
||||
# it has priority over updating the existing record
|
||||
self.name_survey_record_creation_field_values.unicity_check = True
|
||||
|
||||
self.env["res.partner"].create({"name": "Jean"})
|
||||
|
||||
self.question_email = self._add_question(
|
||||
page=None,
|
||||
name="Email",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
|
||||
self.survey_record_creation.write(
|
||||
{
|
||||
"update_existing_records": True,
|
||||
"field_to_retrieve_existing_records": self.name_field.id,
|
||||
}
|
||||
)
|
||||
email_field = self.env["ir.model.fields"].search(
|
||||
[("model", "=", "res.partner"), ("name", "=", "email")]
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.survey_record_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": email_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.question_email.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_email,
|
||||
answer=self.answer,
|
||||
answer_value="jean@test.fr",
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertTrue(len(partner) == 1)
|
||||
self.assertTrue(getattr(partner, "Email", None) is None)
|
||||
|
||||
def test_required_fields_are_not_filled_up(self):
|
||||
# In this test, we check the behavior when a required field (name) is missing
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
# TODO : propose a better user experience than IntegrityError when
|
||||
# a mandatory field is missing
|
||||
self.answer._mark_done()
|
||||
|
||||
def test_ignore_if_mandatory_field_is_missing(self):
|
||||
# In this test, we check the behavior of ignore_if_mandatory_field_is_missing
|
||||
|
||||
self.survey_record_creation.write(
|
||||
{"ignore_if_mandatory_field_is_missing": True}
|
||||
)
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
|
||||
self.answer._mark_done()
|
||||
|
||||
# No partner has been created, and no IntegrityError has been raised
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertEqual(len(partner), 0)
|
||||
|
||||
def test_fill_up_partner_id_in_survey_input(self):
|
||||
# In this test, we check that the field partner_id is filled up
|
||||
# when we create a res.partner
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name, answer=self.answer, answer_value="Jean"
|
||||
)
|
||||
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertEqual(self.answer.partner_id, partner)
|
||||
|
||||
def test_partner_id_in_survey_input_is_filled_up_by_first_contact_record_creation(self):
|
||||
# In this test, we verify that when creating several contacts with the same survey,
|
||||
# the 1st created contact is used to fill up survey_input.partner_id
|
||||
self.second_question_name = self._add_question(
|
||||
page=None,
|
||||
name="Name of second person",
|
||||
qtype="char_box",
|
||||
survey_id=self.survey.id,
|
||||
sequence=1,
|
||||
)
|
||||
|
||||
self.second_contact_creation = self.env["survey.record.creation"].create(
|
||||
{
|
||||
"name": "Contact 2",
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
}
|
||||
)
|
||||
self.env["survey.record.creation.field.values"].create(
|
||||
{
|
||||
"survey_record_creation_id": self.second_contact_creation.id,
|
||||
"survey_id": self.survey.id,
|
||||
"model_id": self.res_partner_model.id,
|
||||
"field_id": self.name_field.id,
|
||||
"value_origin": "question",
|
||||
"question_id": self.second_question_name.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.answer = self._add_answer(
|
||||
survey=self.survey, partner=False, email="jean@test.fr"
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.question_name,
|
||||
answer=self.answer,
|
||||
answer_value="Jean",
|
||||
)
|
||||
self._add_answer_line(
|
||||
question=self.second_question_name,
|
||||
answer=self.answer,
|
||||
answer_value="Jeanne",
|
||||
)
|
||||
self.answer._mark_done()
|
||||
|
||||
partner = self.env["res.partner"].search([("name", "=", "Jean")])
|
||||
self.assertEqual(self.answer.partner_id, partner)
|
||||
@@ -18,27 +18,16 @@
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="model_id" />
|
||||
<field name="ignore_if_mandatory_field_is_missing" />
|
||||
<field name="update_existing_records" />
|
||||
<field name="allowed_field_ids" attrs="{'invisible': True}"/>
|
||||
<field name="field_to_retrieve_existing_records" attrs="{'invisible': [('update_existing_records', '=', False)]}"/>
|
||||
<div colspan="2" style="width:100%;">
|
||||
<div class="alert alert-warning"
|
||||
attrs="{'invisible': [('update_existing_records', '=', False)]}">
|
||||
Only the first matched record will be updated.
|
||||
Also to be noticed, the unicity check feature has priority over updating the existing record.
|
||||
</div>
|
||||
</div>
|
||||
<field name="field_values_ids">
|
||||
<tree>
|
||||
<field name="field_id" />
|
||||
<field name="displayed_value" />
|
||||
<field name="unicity_check" />
|
||||
</tree>
|
||||
</tree>
|
||||
<form>
|
||||
<group>
|
||||
<field name="model_id" invisible="1" />
|
||||
<field name="field_id" />
|
||||
<field name="model_id" invisible="1" />
|
||||
<field name="field_id" />
|
||||
<field name="unicity_check" />
|
||||
<field name="field_relation" invisible="1" />
|
||||
<field name="field_type" invisible="1" />
|
||||
@@ -51,37 +40,37 @@
|
||||
</group>
|
||||
<div attrs="{'invisible':['|',('value_origin','!=','fixed'),('field_id','=',False)]}">
|
||||
<group>
|
||||
<field name="displayed_value" invisible="1" />
|
||||
<field
|
||||
name="fixed_value_char"
|
||||
<field name="displayed_value" invisible="1" />
|
||||
<field
|
||||
name="fixed_value_char"
|
||||
attrs="{'invisible':[('field_type','!=','char')]}"
|
||||
/>
|
||||
<field
|
||||
name="fixed_value_selection"
|
||||
<field
|
||||
name="fixed_value_selection"
|
||||
attrs="{'invisible':[('field_type','!=','selection')]}"
|
||||
/>
|
||||
<field
|
||||
name="fixed_value_text"
|
||||
<field
|
||||
name="fixed_value_text"
|
||||
attrs="{'invisible':[('field_type','!=','text')]}"
|
||||
/>
|
||||
<field
|
||||
name="fixed_value_html"
|
||||
<field
|
||||
name="fixed_value_html"
|
||||
attrs="{'invisible':[('field_type','!=', 'html')]}"
|
||||
/>
|
||||
<field
|
||||
name="fixed_value_integer"
|
||||
<field
|
||||
name="fixed_value_integer"
|
||||
attrs="{'invisible':[('field_type','!=', 'integer')]}"
|
||||
/>
|
||||
<field
|
||||
name="fixed_value_float"
|
||||
<field
|
||||
name="fixed_value_float"
|
||||
attrs="{'invisible':[('field_type','!=', 'float')]}"
|
||||
/>
|
||||
<field
|
||||
name="fixed_value_date"
|
||||
<field
|
||||
name="fixed_value_date"
|
||||
attrs="{'invisible':[('field_type','!=', 'date')]}"
|
||||
/>
|
||||
<field
|
||||
name="fixed_value_datetime"
|
||||
<field
|
||||
name="fixed_value_datetime"
|
||||
attrs="{'invisible':[('field_type','!=', 'datetime')]}"
|
||||
/>
|
||||
<field
|
||||
@@ -98,7 +87,7 @@
|
||||
attrs="{'invisible':[('field_type','not in',['one2many','many2many'])]}">
|
||||
<tree editable="bottom">
|
||||
<field name="survey_record_creation_field_values_id" invisible="1" />
|
||||
<field name="value_reference"
|
||||
<field name="value_reference"
|
||||
options="{'hide_model': True, 'no_create': True, 'no_edit': True, 'no_open': True}"
|
||||
/>
|
||||
</tree>
|
||||
@@ -115,7 +104,7 @@
|
||||
<field name="other_created_record_id" />
|
||||
</group>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</field>
|
||||
<div colspan="2">
|
||||
<field name="warning_message" />
|
||||
|
||||
Reference in New Issue
Block a user