summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-11-29 20:06:16 +0100
committerChristophe Simonis <chs@odoo.com>2018-11-29 20:06:16 +0100
commitce4cc24621bf6430d7d3a2d469b375b59120c200 (patch)
tree3837b5fe41ff88b24136ab53c871bc01f965cab1 /addons/web
parent4d7aa97da38e497fef31a06543bfa5d2c8785708 (diff)
parent82a1e1dcc33d7c123e50d3e1aacb5e127f57c521 (diff)
[MERGE] forward port branch 12.0 up to 82a1e1dcc33
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/controllers/main.py2
-rw-r--r--addons/web/i18n/da.po23
-rw-r--r--addons/web/i18n/en_GB.po7
-rw-r--r--addons/web/i18n/es_AR.po7
-rw-r--r--addons/web/i18n/es_EC.po7
-rw-r--r--addons/web/i18n/it.po83
-rw-r--r--addons/web/i18n/mk.po7
-rw-r--r--addons/web/i18n/zh_CN.po10
-rw-r--r--addons/web/static/lib/tempusdominus/tempusdominus.js6
-rw-r--r--addons/web/static/src/js/core/dom.js91
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js12
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js1
-rw-r--r--addons/web/static/src/js/views/abstract_view.js1
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js4
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js13
-rw-r--r--addons/web/static/src/js/widgets/dropdown_menu.js10
-rw-r--r--addons/web/static/src/scss/ui.scss5
-rw-r--r--addons/web/static/tests/fields/basic_fields_tests.js74
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js77
-rw-r--r--addons/web/static/tests/helpers/test_utils_mock.js4
-rw-r--r--addons/web/static/tests/views/form_benchmarks.js17
-rw-r--r--addons/web/static/tests/views/search_view_tests.js61
-rw-r--r--addons/web/static/tests/views/view_dialogs_tests.js60
-rw-r--r--addons/web/views/webclient_templates.xml10
24 files changed, 492 insertions, 100 deletions
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index d0059a25a95..64cf2ca9ac5 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -525,7 +525,7 @@ class Home(http.Controller):
request.env['res.users']._invalidate_session_cache()
request.session.session_token = security.compute_session_token(request.session, request.env)
- return http.redirect_with_hash(self._login_redirect(uid))
+ return http.local_redirect(self._login_redirect(uid), keep_hash=True)
class WebClient(http.Controller):
diff --git a/addons/web/i18n/da.po b/addons/web/i18n/da.po
index 5c4b7f55073..bb16198224c 100644
--- a/addons/web/i18n/da.po
+++ b/addons/web/i18n/da.po
@@ -3,27 +3,28 @@
# * web
#
# Translators:
+# Kenneth Hansen <kenneth@vkdata.dk>, 2018
+# Jesper Carstensen <jc@danodoo.dk>, 2018
+# peso <peer.sommerlund@gmail.com>, 2018
+# Visti Kristensen <visti@vkdata.dk>, 2018
+# Jonathan Stein <cgs@image.dk>, 2018
# Martin Trigaux, 2018
# Hans Henrik Gabelgaard <hhg@gabelgaard.org>, 2018
-# Kenneth Hansen <kenneth@vkdata.dk>, 2018
+# jonas jensen <j.jensen@tcomp.dk>, 2018
# Morten Schou <ms@msteknik.dk>, 2018
-# Jesper Carstensen <jc@danodoo.dk>, 2018
# Jarnhold Nattestad <nattestads@gmail.com>, 2018
# Pernille Kristensen <pernillekristensen1994@gmail.com>, 2018
-# Sanne Kristensen <sanne@vkdata.dk>, 2018
# Ejner Sønniksen <ejner@vkdata.dk>, 2018
# lhmflexerp <lhm@flexerp.dk>, 2018
-# peso <peer.sommerlund@gmail.com>, 2018
-# jonas jensen <j.jensen@tcomp.dk>, 2018
-# Visti Kristensen <visti@vkdata.dk>, 2018
-# Jonathan Stein <cgs@image.dk>, 2018
+# Sanne Kristensen <sanne@vkdata.dk>, 2018
+#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-08 06:49+0000\n"
-"PO-Revision-Date: 2018-10-08 06:49+0000\n"
-"Last-Translator: Jonathan Stein <cgs@image.dk>, 2018\n"
+"PO-Revision-Date: 2018-08-24 09:33+0000\n"
+"Last-Translator: Sanne Kristensen <sanne@vkdata.dk>, 2018\n"
"Language-Team: Danish (https://www.transifex.com/odoo/teams/41243/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -1247,7 +1248,7 @@ msgstr "Download xls"
#: code:addons/web/static/src/xml/base.xml:1556
#, python-format
msgid "Dropdown menu"
-msgstr ""
+msgstr "Dropdown menu"
#. module: web
#. openerp-web
@@ -1641,7 +1642,7 @@ msgstr "Sortér efter: %s"
#. module: web
#: model:ir.model,name:web.model_ir_http
msgid "HTTP Routing"
-msgstr ""
+msgstr "HTTP Routing"
#. module: web
#. openerp-web
diff --git a/addons/web/i18n/en_GB.po b/addons/web/i18n/en_GB.po
index d10e0618f04..c9419478555 100644
--- a/addons/web/i18n/en_GB.po
+++ b/addons/web/i18n/en_GB.po
@@ -1520,13 +1520,6 @@ msgstr ""
#. module: web
#. openerp-web
-#: code:addons/web/static/src/js/views/form_relational_widgets.js:329
-#, fuzzy, python-format
-msgid "No value found for the field %s for value %s"
-msgstr "No value found for the field for value %s"
-
-#. module: web
-#. openerp-web
#: code:addons/web/static/src/js/framework/utils.js:144
#, python-format
msgid "Node [%s] is not a JSONified XML node"
diff --git a/addons/web/i18n/es_AR.po b/addons/web/i18n/es_AR.po
index 4e28ef7be45..fbab2c0e3da 100644
--- a/addons/web/i18n/es_AR.po
+++ b/addons/web/i18n/es_AR.po
@@ -1533,13 +1533,6 @@ msgstr ""
#. module: web
#. openerp-web
-#: code:addons/web/static/src/js/views/form_relational_widgets.js:329
-#, fuzzy, python-format
-msgid "No value found for the field %s for value %s"
-msgstr "No se ha encontrado ningún valor para el valor del campo "
-
-#. module: web
-#. openerp-web
#: code:addons/web/static/src/js/framework/utils.js:144
#, python-format
msgid "Node [%s] is not a JSONified XML node"
diff --git a/addons/web/i18n/es_EC.po b/addons/web/i18n/es_EC.po
index d38d22b3403..c96276dc87a 100644
--- a/addons/web/i18n/es_EC.po
+++ b/addons/web/i18n/es_EC.po
@@ -1531,13 +1531,6 @@ msgstr "Sin registro seleccionado"
#. module: web
#. openerp-web
-#: code:addons/web/static/src/js/views/form_relational_widgets.js:329
-#, fuzzy, python-format
-msgid "No value found for the field %s for value %s"
-msgstr "No se encuentran valores para el campo"
-
-#. module: web
-#. openerp-web
#: code:addons/web/static/src/js/framework/utils.js:144
#, python-format
msgid "Node [%s] is not a JSONified XML node"
diff --git a/addons/web/i18n/it.po b/addons/web/i18n/it.po
index ef7ea2d7fe5..c246d4285d4 100644
--- a/addons/web/i18n/it.po
+++ b/addons/web/i18n/it.po
@@ -3,11 +3,9 @@
# * web
#
# Translators:
-# Cécile Collart <cco@odoo.com>, 2018
# Cesare Cugnasco <cesare.cugnasco@gmail.com>, 2018
# Luca Tralli, 2018
# Pietro Della Notte <pdellanotte@gmail.com>, 2018
-# efraimbiffi <eab@odoo.com>, 2018
# Francesco Garganese <francesco.garganese@aeromnia.aero>, 2018
# Luigi Di Naro <gigidn@gmail.com>, 2018
# Giacomo Grasso <giacomo.grasso.82@gmail.com>, 2018
@@ -180,7 +178,7 @@ msgstr "'%s' non è un intero corretto"
#: code:addons/web/static/src/js/fields/field_utils.js:548
#, python-format
msgid "'%s' is not a correct monetary field"
-msgstr ""
+msgstr "\"%s\" non è un campo monetario corretto"
#. module: web
#. openerp-web
@@ -230,7 +228,7 @@ msgstr "(senza etichetta)"
#: code:addons/web/static/src/js/fields/field_utils.js:283
#, python-format
msgid "1 record"
-msgstr ""
+msgstr "1 record"
#. module: web
#. openerp-web
@@ -344,7 +342,7 @@ msgstr ""
#: code:addons/web/static/src/xml/base.xml:1126
#, python-format
msgid "Add Custom Filter"
-msgstr "Aggiungi filtro"
+msgstr "Aggiungi filtro personalizzato"
#. module: web
#. openerp-web
@@ -358,14 +356,14 @@ msgstr "Aggiungi gruppo personalizzato"
#: code:addons/web/static/src/xml/kanban.xml:49
#, python-format
msgid "Add a Column"
-msgstr ""
+msgstr "Aggiungi una colonna"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:1130
#, python-format
msgid "Add a condition"
-msgstr "Aggiungi un filtro"
+msgstr "Aggiungi una condizione"
#. module: web
#. openerp-web
@@ -660,6 +658,8 @@ msgid ""
"Binary fields can not be exported to Excel unless their content is "
"base64-encoded. That does not seem to be the case for %s."
msgstr ""
+"Impossibile esportare %s. I campi binari devono avere il contenuto "
+"codificato in base64 per essere esportati in Excel."
#. module: web
#. openerp-web
@@ -1167,7 +1167,7 @@ msgstr "Descrizione"
#: code:addons/web/static/src/xml/report.xml:18
#, python-format
msgid "Discard"
-msgstr "Annulla"
+msgstr "Abbandona"
#. module: web
#. openerp-web
@@ -1349,7 +1349,7 @@ msgstr "Errore, password non modificata."
#: code:addons/web/static/src/xml/kanban.xml:62
#, python-format
msgid "Esc to discard"
-msgstr ""
+msgstr "Esc per abbandonare"
#. module: web
#. openerp-web
@@ -1391,14 +1391,14 @@ msgstr ""
#: code:addons/web/static/src/js/views/list/list_controller.js:149
#, python-format
msgid "Export"
-msgstr "Esportazione"
+msgstr "Esporta"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/widgets/data_export.js:209
#, python-format
msgid "Export Data"
-msgstr "Esporta dati"
+msgstr "Esportazione dati"
#. module: web
#. openerp-web
@@ -1671,28 +1671,28 @@ msgstr "Premere INVIO per"
#: code:addons/web/static/src/xml/base.xml:431
#, python-format
msgid "Hit ENTER to CREATE"
-msgstr "Premere INVIO per CREA"
+msgstr "Premere INVIO per CREARE"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:423
#, python-format
msgid "Hit ENTER to SAVE"
-msgstr "Premere INVIO per SALVA"
+msgstr "Premere INVIO per SALVARE"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:424
#, python-format
msgid "Hit ESCAPE to DISCARD"
-msgstr ""
+msgstr "Premere ESCAPE per ABBANDONARE"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/colorpicker.xml:41
#, python-format
msgid "Hue"
-msgstr ""
+msgstr "Tonalità"
#. module: web
#. openerp-web
@@ -1772,7 +1772,7 @@ msgstr ""
#: code:addons/web/static/src/js/tools/debug_manager.js:209
#, python-format
msgid "JS Mobile Tests"
-msgstr ""
+msgstr "Test JS per cellulare"
#. module: web
#. openerp-web
@@ -1900,7 +1900,7 @@ msgstr "Blu chiaro"
#: code:addons/web/static/src/xml/colorpicker.xml:49
#, python-format
msgid "Lightness %"
-msgstr ""
+msgstr "Luminosità %"
#. module: web
#. openerp-web
@@ -2134,14 +2134,14 @@ msgstr "Altro"
#: code:addons/web/static/src/xml/base.xml:1415
#, python-format
msgid "Move Down"
-msgstr "Muovi Giù"
+msgstr "Sposta in basso"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:1414
#, python-format
msgid "Move Up"
-msgstr "Muovi Su"
+msgstr "Sposta in alto"
#. module: web
#. openerp-web
@@ -2403,7 +2403,7 @@ msgstr "Solo tu"
#: code:addons/web/static/src/xml/colorpicker.xml:55
#, python-format
msgid "Opacity %"
-msgstr ""
+msgstr "Opacità %"
#. module: web
#. openerp-web
@@ -2550,14 +2550,14 @@ msgstr "pivot"
#: code:addons/web/static/src/js/views/list/list_renderer.js:352
#, python-format
msgid "Please click on the \"save\" button first."
-msgstr ""
+msgstr "Prima fare clic sul pulsante \"Salva\"."
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/widgets/data_export.js:177
#, python-format
msgid "Please enter save field list name"
-msgstr "Inserire il nome per l'elenco salvato"
+msgstr "Inserire il nome dell'elenco campi da salvare"
#. module: web
#. openerp-web
@@ -2571,7 +2571,7 @@ msgstr "Selezionare i campi da esportare..."
#: code:addons/web/static/src/js/widgets/data_export.js:464
#, python-format
msgid "Please select fields to save export list..."
-msgstr "Selezionare l'elenco dei campi da esportare..."
+msgstr "Selezionare i campi da salvare nell'elenco di esportazione..."
#. module: web
#. openerp-web
@@ -2793,21 +2793,21 @@ msgstr "Ripristina"
#: code:addons/web/static/src/xml/base.xml:240
#, python-format
msgid "Run Click Everywhere Test"
-msgstr "Esegui prova \"Fare clic ovunque\""
+msgstr "Esegui test \"Fare clic ovunque\""
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:239
#, python-format
msgid "Run JS Mobile Tests"
-msgstr "Esegui prove JS per dispositivo mobile"
+msgstr "Esegui test JS per dispositivo mobile"
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:238
#, python-format
msgid "Run JS Tests"
-msgstr "Esegui prove JS"
+msgstr "Esegui test JS"
#. module: web
#. openerp-web
@@ -2877,14 +2877,14 @@ msgstr "Salva un record"
#: code:addons/web/static/src/xml/base.xml:1443
#, python-format
msgid "Save as:"
-msgstr "Salva come:"
+msgstr "Salvare come: "
#. module: web
#. openerp-web
#: code:addons/web/static/src/xml/base.xml:1358
#, python-format
msgid "Save current search"
-msgstr "Salvare la ricerca corrente"
+msgstr "Salva la ricerca corrente"
#. module: web
#. openerp-web
@@ -2898,7 +2898,7 @@ msgstr "Salva predefinito"
#: code:addons/web/static/src/xml/base.xml:1419
#, python-format
msgid "Save fields list"
-msgstr "Salva l'elenco dei campi"
+msgstr "Salva elenco campi"
#. module: web
#. openerp-web
@@ -2942,7 +2942,7 @@ msgstr "Cerca ancora..."
#: code:addons/web/static/src/xml/base.xml:1231
#, python-format
msgid "Search..."
-msgstr "Cerca..."
+msgstr "Ricerca..."
#. module: web
#. openerp-web
@@ -3113,7 +3113,7 @@ msgstr "Accendi"
#: code:addons/web/static/src/js/widgets/domain_selector.js:583
#, python-format
msgid "Syntax error"
-msgstr ""
+msgstr "Errore di sintassi"
#. module: web
#. openerp-web
@@ -3206,7 +3206,8 @@ msgid ""
"The record has been modified, your changes will be discarded. Do you want to"
" proceed?"
msgstr ""
-"Il documento è stato modificato, le modifiche verranno eliminate. Procedere?"
+"Il documento è stato modificato, verranno abbandonate le modifiche. "
+"Procedere?"
#. module: web
#. openerp-web
@@ -3233,6 +3234,8 @@ msgid ""
"There are too many rows (%s rows, limit: 65535) to export as Excel 97-2003 "
"(.xls) format. Consider splitting the export."
msgstr ""
+"Impossibile esportare nel formato Excel 97-2003 (.xls) (%s righe, limite: "
+"65535). Valutare di suddividere l'esportazione."
#. module: web
#. openerp-web
@@ -3482,7 +3485,7 @@ msgstr "Senza titolo"
#: code:addons/web/static/src/xml/base.xml:1393
#, python-format
msgid "Update data (import-compatible export)"
-msgstr ""
+msgstr "Aggiornare i dati (compatibile con l'importazione)"
#. module: web
#. openerp-web
@@ -3496,7 +3499,7 @@ msgstr "Aggiorna adesso"
#: code:addons/web/static/src/xml/base.xml:1577
#, python-format
msgid "Upgrade to enterprise"
-msgstr ""
+msgstr "Avanza a Enterprise"
#. module: web
#. openerp-web
@@ -3542,7 +3545,7 @@ msgstr "Utilizzare come predefinita"
#: code:addons/web/static/src/xml/base.xml:1387
#, python-format
msgid "Use data in a spreadsheet (export all data)"
-msgstr ""
+msgstr "Usare i dati in un foglio di calcolo (esportare tutti i dati)"
#. module: web
#. openerp-web
@@ -3584,14 +3587,14 @@ msgstr "Visualizza metadati"
#: code:addons/web/static/src/xml/base.xml:606
#, python-format
msgid "Warning"
-msgstr "Avviso"
+msgstr "Attenzione"
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/widgets/attach_document.js:87
#, python-format
msgid "Warning : You have to save first before attaching a file."
-msgstr ""
+msgstr "Attenzione: prima di allegare un file è necessario salvare."
#. module: web
#: model_terms:ir.ui.view,arch_db:web.benchmark_suite
@@ -3601,12 +3604,12 @@ msgstr ""
#. module: web
#: model_terms:ir.ui.view,arch_db:web.qunit_mobile_suite
msgid "Web Mobile Tests"
-msgstr ""
+msgstr "Test web per dispositivo mobile"
#. module: web
#: model_terms:ir.ui.view,arch_db:web.qunit_suite
msgid "Web Tests"
-msgstr ""
+msgstr "Test web"
#. module: web
#: model_terms:ir.ui.view,arch_db:web.external_layout_boxed
@@ -3634,7 +3637,7 @@ msgstr "Ben fatto!"
#: code:addons/web/static/src/xml/base.xml:1383
#, python-format
msgid "What do you want to do?"
-msgstr "Cosa vuoi fare?"
+msgstr "Cosa si intende fare?"
#. module: web
#. openerp-web
diff --git a/addons/web/i18n/mk.po b/addons/web/i18n/mk.po
index 6fe04cbc033..6f55d6b2662 100644
--- a/addons/web/i18n/mk.po
+++ b/addons/web/i18n/mk.po
@@ -1532,13 +1532,6 @@ msgstr ""
#. module: web
#. openerp-web
-#: code:addons/web/static/src/js/views/form_relational_widgets.js:329
-#, fuzzy, python-format
-msgid "No value found for the field %s for value %s"
-msgstr "Вредноста не е пронајдена во полето за вредност"
-
-#. module: web
-#. openerp-web
#: code:addons/web/static/src/js/framework/utils.js:144
#, python-format
msgid "Node [%s] is not a JSONified XML node"
diff --git a/addons/web/i18n/zh_CN.po b/addons/web/i18n/zh_CN.po
index 484e8fd15a1..c1aa4a4d92c 100644
--- a/addons/web/i18n/zh_CN.po
+++ b/addons/web/i18n/zh_CN.po
@@ -1241,7 +1241,7 @@ msgstr "域名"
#: code:addons/web/static/src/js/core/misc.js:16
#, python-format
msgid "Don't leave yet,<br />it's still loading..."
-msgstr "请不要离开,<br />它还在加载中..."
+msgstr "请不要离开,<br />它仍在加载..."
#. module: web
#. openerp-web
@@ -3068,14 +3068,14 @@ msgstr "特殊:"
#: code:addons/web/static/src/js/core/misc.js:14
#, python-format
msgid "Still loading..."
-msgstr "大菜烹饪中..."
+msgstr "仍在加载..."
#. module: web
#. openerp-web
#: code:addons/web/static/src/js/core/misc.js:15
#, python-format
msgid "Still loading...<br />Please be patient."
-msgstr "大菜烹饪中...<br />稍安勿躁。"
+msgstr "仍在加载...<br />请耐心等待。"
#. module: web
#. openerp-web
@@ -3117,7 +3117,7 @@ msgstr "语法错误"
#: code:addons/web/static/src/js/core/misc.js:18
#, python-format
msgid "Take a minute to get a coffee,<br />because it's loading..."
-msgstr "稍安勿躁,<br />大菜烹饪中..."
+msgstr "花一分钟去喝杯咖啡,<br />因为它在加载..."
#. module: web
#. openerp-web
@@ -3725,7 +3725,7 @@ msgstr "你不能让任何密码为空。"
#, python-format
msgid ""
"You may not believe it,<br />but the application is actually loading..."
-msgstr "稍安勿躁,<br />大菜烹饪中...."
+msgstr "也许你不相信,<br />但是这个应用真的还在加载...."
#. module: web
#. openerp-web
diff --git a/addons/web/static/lib/tempusdominus/tempusdominus.js b/addons/web/static/lib/tempusdominus/tempusdominus.js
index 63daabbd6f7..a5dac48ac3a 100644
--- a/addons/web/static/lib/tempusdominus/tempusdominus.js
+++ b/addons/web/static/lib/tempusdominus/tempusdominus.js
@@ -563,10 +563,10 @@ var DateTimePicker = function ($, moment) {
DateTimePicker.prototype._notifyEvent = function _notifyEvent(e) {
// /!\ ODOO FIX: these next conditions have been modified by odoo
// FIXME should write a test about the tricky case this handles
- if (!e.date && !e.oldDate) {
- return;
- }
if (e.type === DateTimePicker.Event.CHANGE) {
+ if (!e.date && !e.oldDate) {
+ return;
+ }
// check _isUTC flag to ensure that we are not comparing apples and oranges
var bothUTC = e.date && e.oldDate && e.date._isUTC === e.oldDate._isUTC;
if (bothUTC && e.date.isSame(e.oldDate)) {
diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js
index 2a961596862..848e406c4b2 100644
--- a/addons/web/static/src/js/core/dom.js
+++ b/addons/web/static/src/js/core/dom.js
@@ -20,6 +20,7 @@ odoo.define('web.dom', function (require) {
* something happens in the DOM.
*/
+var concurrency = require('web.concurrency');
var config = require('web.config');
var core = require('web.core');
var _t = core._t;
@@ -40,7 +41,9 @@ function _notify(content, callbacks) {
core.bus.trigger('DOM_updated', content);
}
-return {
+var dom = {
+ DEBOUNCE: 400,
+
/**
* Appends content in a jQuery object and optionnally triggers an event
*
@@ -209,6 +212,91 @@ return {
return position;
},
/**
+ * Protects a function which is to be used as a handler by preventing its
+ * execution for the duration of a previous call to it (including async
+ * parts of that call).
+ *
+ * Limitation: as the handler is ignored during async actions,
+ * the 'preventDefault' or 'stopPropagation' calls it may want to do
+ * will be ignored too. Using the 'preventDefault' and 'stopPropagation'
+ * arguments solves that problem.
+ *
+ * @param {function} fct
+ * The function which is to be used as a handler. If a promise
+ * is returned, it is used to determine when the handler's action is
+ * finished. Otherwise, the return is used as jQuery uses it.
+ * @param {function|boolean} preventDefault
+ * @param {function|boolean} stopPropagation
+ */
+ makeAsyncHandler: function (fct, preventDefault, stopPropagation) {
+ // Create a deferred indicating if a previous call to this handler is
+ // still pending.
+ var def = $.when();
+
+ return function (ev) {
+ if (preventDefault === true || preventDefault && preventDefault()) {
+ ev.preventDefault();
+ }
+ if (stopPropagation === true || stopPropagation && stopPropagation()) {
+ ev.stopPropagation();
+ }
+
+ if (def.state() === 'pending') {
+ // If a previous call to this handler is still pending, ignore
+ // the new call.
+ return;
+ }
+ var result = fct.apply(this, arguments);
+ def = $.when(result);
+ return result;
+ };
+ },
+ /**
+ * Creates a debounced version of a function to be used as a button click
+ * handler. Also improves the handler to disable the button for the time of
+ * the debounce and/or the time of the async actions it performs.
+ *
+ * Limitation: if two handlers are put on the same button, the button will
+ * become enabled again once any handler's action finishes (multiple click
+ * handlers should however not be binded to the same button).
+ *
+ * @param {function} fct
+ * The function which is to be used as a button click handler. If a
+ * promise is returned, it is used to determine when the button can be
+ * re-enabled. Otherwise, the return is used as jQuery uses it.
+ */
+ makeButtonHandler: function (fct) {
+ // Fallback: if the final handler is not binded to a button, at least
+ // make it an async handler (also handles the case where some events
+ // might ignore the disabled state of the button).
+ fct = dom.makeAsyncHandler(fct);
+
+ return function (ev) {
+ var result = fct.apply(this, arguments);
+
+ var $button = $(ev.target).closest('.btn');
+ if (!$button.length) {
+ return result;
+ }
+
+ // Disable the button for the duration of the handler's action
+ // or at least for the duration of the click debounce. This makes
+ // a 'real' debounce creation useless. Also, during the debouncing
+ // part, the button is disabled without any visual effect.
+ $button.addClass('o_debounce_disabled');
+ $.when(dom.DEBOUNCE && concurrency.delay(dom.DEBOUNCE)).then(function () {
+ $button.addClass('disabled').prop('disabled', true);
+ $button.removeClass('o_debounce_disabled');
+
+ return $.when(result).always(function () {
+ $button.removeClass('disabled').prop('disabled', false);
+ });
+ });
+
+ return result;
+ };
+ },
+ /**
* Prepends content in a jQuery object and optionnally triggers an event
*
* @param {jQuery} [$target] the node where content will be prepended
@@ -460,4 +548,5 @@ return {
}
},
};
+return dom;
});
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 53fbf99a8cd..aac91081c3c 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -2409,6 +2409,18 @@ var FieldProgressBar = AbstractField.extend({
this.$('.o_progressbar_value').focus().select();
}
},
+ /**
+ * The progress bar has more than one field/value to deal with
+ * i.e. max_value
+ *
+ * @override
+ * @private
+ */
+ _reset: function () {
+ this._super.apply(this, arguments);
+ var new_max_value = this.recordData[this.nodeOptions.max_value];
+ this.max_value = new_max_value !== undefined ? new_max_value : this.max_value;
+ },
isSet: function () {
return true;
},
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index d9e15dab302..60b0275c8c8 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -564,6 +564,7 @@ var FieldMany2One = AbstractField.extend({
initial_ids: ids ? _.map(ids, function (x) { return x[0]; }) : undefined,
initial_view: view,
disable_multiple_selection: true,
+ no_create: !self.can_create,
on_selected: function (records) {
self.reinitialize(records[0]);
self.activate();
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index 8f9f167c69f..024216b5619 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -181,6 +181,7 @@ var AbstractView = Class.extend({
modelName: params.modelName,
res_id: params.currentId,
res_ids: params.ids,
+ orderedBy: params.context ? params.context.orderedBy : [],
};
if (params.modelName) {
this.loadParams.modelName = params.modelName;
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index 345b76856b6..53aa2e56c5a 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -3674,7 +3674,7 @@ var BasicModel = AbstractModel.extend({
model: params.modelName,
offset: params.offset || (type === 'record' ? _.indexOf(res_ids, res_id) : 0),
openGroupByDefault: params.openGroupByDefault,
- orderedBy: params.orderedBy || (params.context && params.context.orderedBy) || [],
+ orderedBy: params.orderedBy || [],
orderedResIDs: params.orderedResIDs,
parentID: params.parentID,
rawContext: params.rawContext,
@@ -4608,7 +4608,7 @@ var BasicModel = AbstractModel.extend({
if (orderData1 > orderData2) {
return order.asc ? 1 : -1;
}
- return compareRecords(record1ID, record2ID, level + 1);
+ return compareRecords(resId1, resId2, level + 1);
};
utils.stableSort(list.res_ids, compareRecords);
}
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 7ba3010a3b5..b677abe377a 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -15,6 +15,7 @@ var DateWidget = Widget.extend({
'change.datetimepicker': 'changeDatetime',
'change .o_datepicker_input': 'changeDatetime',
'input input': '_onInput',
+ 'show.datetimepicker': '_onDateTimePickerShow',
},
/**
* @override
@@ -226,6 +227,18 @@ var DateWidget = Widget.extend({
//--------------------------------------------------------------------------
/**
+ * Reacts to the datetimepicker being shown
+ * Could set/verify our widget value
+ * And subsequently update the datetimepicker
+ *
+ * @private
+ */
+ _onDateTimePickerShow: function () {
+ if (this.$input.val().length !== 0 && this.isValid()) {
+ this.$input.select();
+ }
+ },
+ /**
* Prevents 'input' events triggered by the library to bubble up, as they
* might have unwanted effects (like triggering 'field_changed' events in
* the context of field widgets)
diff --git a/addons/web/static/src/js/widgets/dropdown_menu.js b/addons/web/static/src/js/widgets/dropdown_menu.js
index fdab9b82ebf..e92c714d838 100644
--- a/addons/web/static/src/js/widgets/dropdown_menu.js
+++ b/addons/web/static/src/js/widgets/dropdown_menu.js
@@ -15,6 +15,7 @@ var DropdownMenu = Widget.extend({
'click .o_item_option': '_onOptionClick',
'click span.o_trash_button': '_onTrashClick',
'hidden.bs.dropdown': '_onBootstrapClose',
+ 'click .dropdown-item-text': '_onDropDownItemTextClick',
},
/**
@@ -200,6 +201,15 @@ var DropdownMenu = Widget.extend({
this._renderMenuItems();
},
/**
+ * Reacts to click on bootstrap's dropdown-item-text
+ * Protects against Bootstrap dropdown close from inside click
+ *
+ * @private
+ */
+ _onDropDownItemTextClick: function (ev) {
+ ev.stopPropagation();
+ },
+ /**
* @private
* @param {MouseEvent} event
*/
diff --git a/addons/web/static/src/scss/ui.scss b/addons/web/static/src/scss/ui.scss
index 46995aa9ba1..44d639805be 100644
--- a/addons/web/static/src/scss/ui.scss
+++ b/addons/web/static/src/scss/ui.scss
@@ -78,3 +78,8 @@
outline: 20px solid fade-out($o-brand-primary, 1.0);
}
}
+
+// Declare a similar class as 'disabled' but with no visual effect
+.o_debounce_disabled {
+ pointer-events: none;
+}
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index 807b2ebadca..f176b4e0ab9 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -2933,6 +2933,38 @@ QUnit.module('basic_fields', {
form.destroy();
});
+ QUnit.test('field date should select its content onclick when there is one', function (assert) {
+ assert.expect(2);
+ var done = assert.async();
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="display_name" /> ' + // Do not focus on the date field right away
+ '<field name="date" />' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ form.$el.on({
+ 'show.datetimepicker': function () {
+ assert.ok($('.bootstrap-datetimepicker-widget').is(':visible'),
+ 'bootstrap-datetimepicker is visible');
+ assert.strictEqual(window.getSelection().toString(), "02/03/2017",
+ 'The whole input of the date field should have been selected');
+ done();
+ }
+ });
+ form.$('.o_input[name="date"]').mouseenter().trigger('focus');
+
+ form.destroy();
+ });
+
QUnit.module('FieldDatetime');
QUnit.test('datetime field in form view', function (assert) {
@@ -5379,6 +5411,48 @@ QUnit.module('basic_fields', {
form.destroy();
});
+
+ QUnit.module('FieldProgressBar');
+
+ QUnit.test('Field ProgressBar: max_value should update', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records = this.data.partner.records.slice(0,1);
+ this.data.partner.records[0].qux = 2;
+
+ this.data.partner.onchanges = {
+ display_name: function (obj) {
+ obj.int_field = 999;
+ obj.qux = 5;
+ }
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="display_name" />' +
+ '<field name="qux" invisible="1" />' +
+ '<field name="int_field" widget="progressbar" options="{\'current_value\': \'int_field\', \'max_value\': \'qux\'}" />' +
+ '</form>',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '10 / 2',
+ 'The initial value of the progress bar should be correct');
+
+ // trigger the onchange
+ form.$('.o_input[name=display_name]').val('new name').trigger('input');
+
+ assert.strictEqual(form.$('.o_progressbar_value').text(), '999 / 5',
+ 'The value of the progress bar should be correct after the update');
+
+ form.destroy();
+ });
});
});
});
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 49af80784c9..8d23dafe4f4 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -150,6 +150,83 @@ QUnit.module('relational_fields', {
}
}, function () {
+ QUnit.test('x2many default_order multiple fields', function (assert) {
+ assert.expect(7);
+
+ this.data.partner.records = [
+ {int_field: 10, id: 1, display_name: "record1"},
+ {int_field: 12, id: 2, display_name: "record2"},
+ {int_field: 11, id: 3, display_name: "record3"},
+ {int_field: 12, id: 4, display_name: "record4"},
+ {int_field: 10, id: 5, display_name: "record5"},
+ {int_field: 10, id: 6, display_name: "record6"},
+ {int_field: 11, id: 7, display_name: "record7"},
+ ];
+
+ this.data.partner.records[0].p = [1, 7, 4, 5, 2, 6, 3];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="p" >' +
+ '<tree default_order="int_field,id">' +
+ '<field name="id"/>' +
+ '<field name="int_field"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ var $recordList = form.$('.o_field_x2many_list .o_data_row');
+ var expectedOrderId = ['1', '5', '6', '3', '7', '2', '4'];
+
+ _.each($recordList, function(record, index) {
+ var $record = $(record);
+ assert.strictEqual($record.find('.o_data_cell').eq(0).text(), expectedOrderId[index],
+ 'The record should be the right place. Index: ' + index);
+ });
+
+ form.destroy();
+ });
+
+
+ form.destroy();
+ });
+
+ QUnit.test('one2many from a model that has been sorted', function (assert) {
+ assert.expect(1);
+
+ /* On a standard list view, sort your records by a field
+ * Click on a record which contains a x2m with multiple records in it
+ * The x2m shouldn't take the orderedBy of the parent record (the one on the form)
+ */
+
+ this.data.partner.records[0].turtles = [3, 2];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'<form string="Partners">' +
+ '<field name="turtles">' +
+ '<tree>' +
+ '<field name="turtle_foo"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ context: {
+ orderedBy: [{
+ name: 'foo',
+ asc: false,
+ }]
+ },
+ });
+
+ assert.strictEqual(form.$('.o_field_one2many[name=turtles] tbody').text().trim(), "kawablip",
+ 'The o2m should not have been sorted.');
QUnit.test('onchange on unloaded record clearing posterious change', function (assert) {
// when we got onchange result for fields of record that were not
// already available because they were in a inline view not already
diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js
index 769c179472a..3eaaae0a46f 100644
--- a/addons/web/static/tests/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/helpers/test_utils_mock.js
@@ -13,6 +13,7 @@ odoo.define('web.test_utils_mock', function (require) {
var basic_fields = require('web.basic_fields');
var config = require('web.config');
var core = require('web.core');
+var dom = require('web.dom');
var MockServer = require('web.MockServer');
var session = require('web.session');
@@ -154,6 +155,8 @@ function addMockEnvironment(widget, params) {
// make sure the debounce value for input fields is set to 0
var initialDebounceValue = DebouncedField.prototype.DEBOUNCE;
DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0;
+ var initialDOMDebounceValue = dom.DEBOUNCE;
+ dom.DEBOUNCE = 0;
var initialSession, initialConfig, initialParameters, initialDebounce, initialThrottle;
initialSession = _.extend({}, session);
session.getTZOffset = function () {
@@ -196,6 +199,7 @@ function addMockEnvironment(widget, params) {
core.bus.trigger('clear_cache');
DebouncedField.prototype.DEBOUNCE = initialDebounceValue;
+ dom.DEBOUNCE = initialDOMDebounceValue;
if (params.debounce === false) {
_.debounce = initialDebounce;
}
diff --git a/addons/web/static/tests/views/form_benchmarks.js b/addons/web/static/tests/views/form_benchmarks.js
index c6127f847ec..efd12741d66 100644
--- a/addons/web/static/tests/views/form_benchmarks.js
+++ b/addons/web/static/tests/views/form_benchmarks.js
@@ -1,4 +1,4 @@
-odoo.define('web.list_benchmarks', function (require) {
+odoo.define('web.form_benchmarks', function (require) {
"use strict";
var FormView = require('web.FormView');
@@ -38,7 +38,6 @@ QUnit.module('Form View', {
this.run = function (assert, done, cb) {
var data = this.data;
var arch = this.arch;
- var viewOptions = this.viewOptions;
new Benchmark.Suite({})
.add('form', function () {
var list = createView({
@@ -76,13 +75,13 @@ QUnit.module('Form View', {
this.data.foo.records[0].many2many.push(i);
}
this.arch =
- '<form string="Partners">'
- '<field name="many2many">'
- '<tree editable="top" limit="250">'
- '<field name="char"/>'
- '<field name="many2many" widget="many2many_tags" attrs="{\'readonly\': [(\'char\', \'==\', \'toto\')]}"/>'
- '</tree>'
- '</field>'
+ '<form string="Partners">' +
+ '<field name="many2many">' +
+ '<tree editable="top" limit="250">' +
+ '<field name="char"/>' +
+ '<field name="many2many" widget="many2many_tags" attrs="{\'readonly\': [(\'char\', \'==\', \'toto\')]}"/>' +
+ '</tree>' +
+ '</field>' +
'</form>';
this.run(assert, done, function (form) {
testUtils.form.clickEdit(form);
diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js
index e64ae72a9f4..0dbddbe1fae 100644
--- a/addons/web/static/tests/views/search_view_tests.js
+++ b/addons/web/static/tests/views/search_view_tests.js
@@ -975,5 +975,66 @@ QUnit.module('Search View', {
form.destroy();
});
+ QUnit.test('Customizing filter does not close the filter dropdown', function (assert) {
+ assert.expect(4);
+ var self = this;
+
+ _.each(this.data.partner.records.slice(), function (rec) {
+ var copy = _.defaults({}, rec, {id: rec.id + 10 });
+ self.data.partner.records.push(copy);
+ });
+
+ this.data.partner.fields.date_field.searchable = true;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="bar"/>' +
+ '</form>',
+ viewOptions: {
+ mode: 'edit',
+ },
+ archs: {
+ 'partner,false,list': '<tree><field name="display_name"/></tree>',
+ 'partner,false,search': '<search><field name="date_field"/></search>',
+ },
+ res_id: 1,
+ });
+
+ form.$('.o_input').click();
+ $('.ui-autocomplete .ui-menu-item:contains(Search More)').mouseenter().click();
+
+ var $modal = $('.modal-dialog.modal-lg');
+ assert.strictEqual($modal.length, 1, 'Modal Opened');
+
+ $modal.find('.o_search_options button:contains(Filters)').click();
+
+ var $filterDropDown = $modal.find('.o_dropdown_menu.show');
+
+ $filterDropDown.find('button:contains(Add Custom Filter)').click();
+
+ assert.ok($filterDropDown.find('button:contains(Add Custom Filter)').hasClass('o_open_menu'),
+ 'The right dropdown is open');
+
+ var $filterInputs = $filterDropDown.find('.o_input');
+
+ assert.strictEqual($filterInputs.length, 3,
+ 'The custom filter builder has 3 elements');
+
+ // We really are interested in the click event
+ // We do it twice on each input to make sure
+ // the parent dropdown doesn't react to any of it
+ _.each($filterInputs, function (input) {
+ var $input = $(input);
+ $input.click();
+ $input.click();
+ });
+
+ assert.ok($filterDropDown.is(':visible'));
+
+ form.destroy();
+ });
+
});
});
diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js
index 8898e520618..023a977ad56 100644
--- a/addons/web/static/tests/views/view_dialogs_tests.js
+++ b/addons/web/static/tests/views/view_dialogs_tests.js
@@ -478,6 +478,66 @@ QUnit.module('Views', {
testUtils.mock.unpatch(ListController);
parent.destroy();
});
+
+ QUnit.test('propagate can_create onto the search popup o2m', function (assert) {
+ assert.expect(3);
+
+ this.data.instrument.records = [
+ {id: 1, name: 'Tromblon1'},
+ {id: 2, name: 'Tromblon2'},
+ {id: 3, name: 'Tromblon3'},
+ {id: 4, name: 'Tromblon4'},
+ {id: 5, name: 'Tromblon5'},
+ {id: 6, name: 'Tromblon6'},
+ {id: 7, name: 'Tromblon7'},
+ {id: 8, name: 'Tromblon8'},
+ ];
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="name"/>' +
+ '<field name="instrument" can_create="false"/>' +
+ '</form>',
+ res_id: 1,
+ archs: {
+ 'instrument,false,list': '<tree>'+
+ '<field name="name"/>'+
+ '</tree>',
+ 'instrument,false,search': '<search>'+
+ '<field name="name"/>'+
+ '</search>',
+ },
+ viewOptions: {
+ mode: 'edit',
+ },
+
+ mockRPC: function(route, args) {
+ if (args.method === 'get_formview_id') {
+ return $.when(false);
+ }
+ return this._super(route, args);
+ },
+ });
+
+ form.$('.o_field_widget[name="instrument"] .o_input').click();
+
+ assert.notOk($('.ui-autocomplete a:contains(Create and Edit)').length,
+ 'Create and edit not present in dropdown');
+
+ $('.ui-autocomplete a:contains(Search More)').trigger('mouseenter').trigger('click');
+
+ var $modal = $('.modal-dialog.modal-lg');
+
+ assert.strictEqual($modal.length, 1, 'Modal present');
+
+ assert.strictEqual($modal.find('.modal-footer button').text(), "Cancel",
+ 'Only the cancel button is present in modal');
+
+ form.destroy();
+ });
});
});
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 1ce0a83f63b..10d887fe8e1 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -687,6 +687,16 @@
display: inherit;
}
</style>
+
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_create.js"></script>
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_dom.js"></script>
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_fields.js"></script>
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_form.js"></script>
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_graph.js"></script>
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_kanban.js"></script>
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_mock.js"></script>
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_modal.js"></script>
+ <script type="text/javascript" src="/web/static/tests/helpers/test_utils_pivot.js"></script>
<script type="text/javascript" src="/web/static/tests/helpers/test_utils.js"></script>
<script type="text/javascript" src="/web/static/tests/helpers/mock_server.js"></script>