summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2015-03-02 18:25:40 +0100
committerChristophe Simonis <chs@odoo.com>2015-03-02 18:25:40 +0100
commit9cf6786e758e4e330d8f566b26a8a7a4db6ba687 (patch)
tree0c19c2c847192495ad1e665f3c806bb72d45cf2e
parent7c8d295a40ccf189a8d9211731a1e4171815cbc8 (diff)
parentfefb29897272bf0fe6a6e8aca98b1e2c88ae6af5 (diff)
[MERGE] forward port of branch saas-6 up to fefb298
-rw-r--r--.mailmap173
-rw-r--r--MANIFEST.in1
-rw-r--r--addons/account/partner_view.xml5
-rw-r--r--addons/account_payment/account_payment.py4
-rw-r--r--addons/anonymization/anonymization.py2
-rw-r--r--addons/auth_ldap/users_ldap.py4
-rw-r--r--addons/base_import/static/src/xml/import.xml6
-rw-r--r--addons/base_import/tests/test_cases.py32
-rw-r--r--addons/google_calendar/google_calendar.py1
-rw-r--r--addons/hr/static/src/css/hr.css2
-rw-r--r--addons/hr_timesheet_invoice/hr_timesheet_invoice.py3
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet.py8
-rw-r--r--addons/im_livechat/static/src/js/im_livechat.js3
-rw-r--r--addons/im_livechat/static/src/xml/im_livechat.xml6
-rw-r--r--addons/mail/mail_template.py9
-rw-r--r--addons/mail/wizard/mail_compose_message_view.xml2
-rw-r--r--addons/mrp/stock.py2
-rw-r--r--addons/procurement_jit/procurement_jit.py6
-rw-r--r--addons/product/product.py20
-rw-r--r--addons/product/product_demo.xml1
-rw-r--r--addons/project/project_tip_data.xml2
-rw-r--r--addons/project/project_view.xml32
-rw-r--r--addons/project/static/src/css/project.css17
-rw-r--r--addons/project_issue/project_issue.py4
-rw-r--r--addons/project_issue/project_issue_view.xml6
-rw-r--r--addons/project_timesheet/project_timesheet.py11
-rw-r--r--addons/purchase/purchase.py20
-rw-r--r--addons/purchase/stock.py34
-rw-r--r--addons/purchase_requisition/purchase_requisition.py1
-rw-r--r--addons/rating_project/models/project.py3
-rw-r--r--addons/rating_project/views/project_view.xml8
-rw-r--r--addons/rating_project_issue/models/project_issue.py5
-rw-r--r--addons/rating_project_issue/views/project_issue_view.xml2
-rw-r--r--addons/report/models/abstract_report.py4
-rw-r--r--addons/sale/sale.py6
-rw-r--r--addons/sale/sale_data.xml2
-rw-r--r--addons/sale/sale_view.xml2
-rw-r--r--addons/sale/sales_team.py2
-rw-r--r--addons/sale_stock/sale_stock.py18
-rw-r--r--addons/sale_stock/sale_stock_view.xml4
-rw-r--r--addons/stock/product.py130
-rw-r--r--addons/stock/stock.py102
-rw-r--r--addons/stock/stock_view.xml22
-rw-r--r--addons/stock/wizard/stock_return_picking.py7
-rw-r--r--addons/warning/warning.py5
-rw-r--r--addons/warning/warning_view.xml12
-rw-r--r--addons/web/static/src/css/base.css7
-rw-r--r--addons/web/static/src/css/base.sass20
-rw-r--r--addons/web/static/src/js/view_form.js11
-rw-r--r--addons/web/static/src/js/view_list.js22
-rw-r--r--addons/web/static/src/xml/base.xml29
-rw-r--r--addons/web_kanban/static/src/js/kanban.js9
-rw-r--r--addons/web_tests/tests/test_ui_load.js3
-rw-r--r--addons/website/models/res_config.py2
-rw-r--r--addons/website/static/src/js/website.backend.js44
-rw-r--r--addons/website/views/website_templates.xml33
-rw-r--r--addons/website_blog/static/src/js/website.tour.blog.js2
-rw-r--r--addons/website_event/data/event_demo.xml6
-rw-r--r--addons/website_event/static/src/js/website.tour.event.js2
-rw-r--r--addons/website_event_track/controllers/event.py17
-rw-r--r--addons/website_forum/models/forum.py18
-rw-r--r--addons/website_forum/views/website_forum.xml8
-rw-r--r--addons/website_quote/data/quotation_tip_data.xml2
-rw-r--r--addons/website_quote/models/order.py3
-rw-r--r--addons/website_report/views/layouts.xml16
-rw-r--r--addons/website_sale/controllers/main.py3
-rw-r--r--addons/website_sale/static/src/js/website_sale_tour_shop.js3
-rw-r--r--addons/website_version/models/experiment.py29
-rw-r--r--doc/_themes/odoodoc/static/app.js6
-rw-r--r--doc/cla/corporate/d9t.md16
-rw-r--r--doc/cla/corporate/eezee-it.md22
-rw-r--r--doc/cla/corporate/initos.md21
-rw-r--r--doc/cla/corporate/versada.md14
-rw-r--r--doc/cla/individual/Sathors.md11
-rw-r--r--doc/cla/individual/Toilal.md11
-rw-r--r--doc/cla/individual/leorochael.md11
-rw-r--r--doc/cla/individual/vnsofthe.md11
-rw-r--r--doc/reference/cmdline.rst9
-rw-r--r--doc/setup/install.rst6
-rw-r--r--openerp/addons/base/res/res_bank.py4
-rw-r--r--openerp/addons/base/res/res_config.py4
-rw-r--r--openerp/addons/base/res/res_users.py4
-rw-r--r--openerp/addons/test_inherits/__init__.py2
-rw-r--r--openerp/addons/test_inherits/__openerp__.py16
-rw-r--r--openerp/addons/test_inherits/demo_data.xml19
-rw-r--r--openerp/addons/test_inherits/ir.model.access.csv4
-rw-r--r--openerp/addons/test_inherits/models.py41
-rw-r--r--openerp/addons/test_inherits/tests/__init__.py3
-rw-r--r--openerp/addons/test_inherits/tests/test_inherits.py23
-rw-r--r--openerp/cli/scaffold.py8
-rw-r--r--openerp/cli/server.py6
-rw-r--r--openerp/cli/templates/default/__init__.py.template (renamed from openerp/cli/templates/default/__init__.py)0
-rw-r--r--openerp/cli/templates/default/__openerp__.py.template (renamed from openerp/cli/templates/default/__openerp__.py)0
-rw-r--r--openerp/cli/templates/default/controllers.py.template (renamed from openerp/cli/templates/default/controllers.py)0
-rw-r--r--openerp/cli/templates/default/demo.xml.template (renamed from openerp/cli/templates/default/demo.xml)0
-rw-r--r--openerp/cli/templates/default/models.py.template (renamed from openerp/cli/templates/default/models.py)0
-rw-r--r--openerp/cli/templates/default/security/ir.model.access.csv.template (renamed from openerp/cli/templates/default/security/ir.model.access.csv)0
-rw-r--r--openerp/cli/templates/default/templates.xml.template (renamed from openerp/cli/templates/default/templates.xml)0
-rw-r--r--openerp/cli/templates/theme/__init__.py.template (renamed from openerp/cli/templates/theme/__init__.py)0
-rw-r--r--openerp/cli/templates/theme/__openerp__.py.template (renamed from openerp/cli/templates/theme/__openerp__.py)8
-rw-r--r--openerp/cli/templates/theme/demo/pages.xml.template (renamed from openerp/cli/templates/theme/static/style/custom.less)0
-rw-r--r--openerp/cli/templates/theme/static/src/less/custom.less.template (renamed from openerp/cli/templates/theme/views/options.xml)0
-rw-r--r--openerp/cli/templates/theme/views/options.xml.template (renamed from openerp/cli/templates/theme/views/pages.xml)0
-rw-r--r--openerp/cli/templates/theme/views/snippets.xml.template (renamed from openerp/cli/templates/theme/views/snippets.xml)0
-rw-r--r--openerp/http.py4
-rw-r--r--openerp/models.py8
-rw-r--r--openerp/tests/phantomtest.js6
-rw-r--r--openerp/tools/config.py6
-rw-r--r--openerp/tools/misc.py3
-rw-r--r--requirements.txt2
110 files changed, 899 insertions, 450 deletions
diff --git a/.mailmap b/.mailmap
index de72af905da..9e806a9ffa2 100644
--- a/.mailmap
+++ b/.mailmap
@@ -4,6 +4,8 @@ Ajay Javiya <aja@tinyerp.com>
Ajay Javiya <aja@tinyerp.com> <aja@openerp.com>
Akash Balar <aba@tinyerp.com>
Akash Balar <aba@tinyerp.com> <aba@openerp.com>
+Akta Dakwala <eda@tinyerp.com>
+Akta Dakwala <eda@tinyerp.com> EDA(Axelor) <>
Albert Cervera i Areny <albert@nan-tic.com>
Albert Cervera i Areny <albert@nan-tic.com> Albert Cervera i Areny - http://www.NaN-tic.com <>
Albert Cervera i Areny <albert@nan-tic.com> Albert Cervera i Areny <>
@@ -20,6 +22,7 @@ Alexis de Lattre <alexis.delattre@akretion.com>
Alexis de Lattre <alexis.delattre@akretion.com> <alexis@via.ecp.fr>
Alexis de Lattre <alexis.delattre@akretion.com> Alexis de Lattre <>
Alexis de Lattre <alexis.delattre@akretion.com> alexis.delattre@akretion.com <>
+Aliane Abdelouahab <abdelouahab_al@yahoo.fr>
Aline Preillon <apr@odoo.com>
Aline Preillon <apr@odoo.com> <aline@aline-laptop>
Amit Bhavsar <amb@tinyerp.com>
@@ -103,6 +106,9 @@ Atul Patel <atp@tinyerp.com> ATP(OpenERP) <>
Atul Patel <atp@tinyerp.com> ATUL (OpenERP) <>
Atul Patel <atp@tinyerp.com> atp (OpenERP) <>
Augustin Cisterne-Kaas <augustin.cisternekaas@elico-corp.com> Augustin Cisterne-Kaas <ajite@Aerith.local>
+Avdhesh Patel <avp@tinyerp.com>
+Avdhesh Patel <avp@tinyerp.com> AVP(Axelor) <>
+Avdhesh Patel <avp@tinyerp.com> avp <openerp@openerp-laptop>
Baskhuu Jacara <baskhuujacara@gmail.com> Jacara <>
Baskhuu Jacara <baskhuujacara@gmail.com> jacara <>
Benoit Guillot <benoit.guillot@akretion.com>
@@ -113,6 +119,7 @@ Bharat Devnani <bde@tinyerp.com> <bde@tiny2-desktop>
Bharat Devnani <bde@tinyerp.com> Bharat Devnani (Open ERP) <>
Bharat Devnani <bde@tinyerp.com> Bharat Devnani(OpenERP) <>
Bharat Devnani <bde@tinyerp.com> bde@tinyerp.com <>
+Bhatt Chintal <bch@tinyerp.com> bch <>
Bhavik Bagdiya <bba@tinyerp.com>
Bhavik Bagdiya <bba@tinyerp.com> <bba@openerp.com>
Bhumi Patel <bpa@openerp.com>
@@ -217,6 +224,8 @@ Dhara Shah <dsh@tinyerp.com> Dhara (OpenERP) <>
Dhara Shah <dsh@tinyerp.com> Dhara <dhara@dhara-laptop>
Dhara Shah <dsh@tinyerp.com> dsh (Open ERP) <>
Dhara Shah <dsh@tinyerp.com> dsh(Open ERP) <>
+Dharmesh Rathod <dra@tinyerp.com> <dharmesh247@gmail.com>
+Dharmesh Rathod <dra@tinyerp.com> DRA Axelor <>
Dharmraj Jhala <dja@tinyerp.com>
Dharmraj Jhala <dja@tinyerp.com> <dizzy.zala@gmail.com>
Dharmraj Jhala <dja@tinyerp.com> <dja@openerp.com>
@@ -320,6 +329,7 @@ Guewen Baconnier <guewen.baconnier@camptocamp.com> Guewen Baconnier <>
Guillem Barba <guillem@alcarrer.net> Guillem Barba <>
Guillem Barba <guillem@alcarrer.net> guillem <>
Guillermo Bisheimer <gbisheimer@bys-control.com.ar>
+Gulamfarid Ghanchi <ggh@tinyerp.com>
Géry Debongnie <ged@odoo.com>
Géry Debongnie <ged@odoo.com> <gde@openerp.com>
Géry Debongnie <ged@odoo.com> <ged@openerp.com>
@@ -330,6 +340,7 @@ Hardik Ansodariya <han@tinyerp.com> Hardik Ansodariya (OpenERP) <>
Hardik Ansodariya <han@tinyerp.com> Hardik Ansodariya <>
Hardik R. Shamnani <hsa@tinyerp.com>
Hardik R. Shamnani <hsa@tinyerp.com> hsa-tinyerp <>
+Hardik Sanchawat <hsa@tinyerp.com> Hardik Sanchawat (OpenERP) <>
Harshad Modi <hmo@tinyerp.com>
Harshad Modi <hmo@tinyerp.com> HMO (Open ERP) <>
Harshad Modi <hmo@tinyerp.com> HMO(OpenERP) <>
@@ -345,6 +356,7 @@ Herbert Petersen <office@conexus.at> Conexus.at <>
Hiral Patel <hip@tinyerp.com>
Hitesh Trivedi <htr@tinyerp.com> <htr@openerp.com>
Hugo Santos <hugo.santos@factorlibre.com> hugo <>
+Hugo Santos <hugo.santos@factorlibre.com> hugosdsantos <hugosdsantos@gmail.com>
Humberto Arocha <hbto@vauxoo.com> hbto(humbertoarocha) <>
Husen Daudi <hda@tinyerp.com>
Husen Daudi <hda@tinyerp.com> <hda@hda-desktop>
@@ -352,6 +364,7 @@ Husen Daudi <hda@tinyerp.com> <hda@hda-laptop>
Husen Daudi <hda@tinyerp.com> <hda@hda>
Husen Daudi <hda@tinyerp.com> <husen@husen-laptop>
Husen Daudi <hda@tinyerp.com> JVO,HDA <>
+Husen Daudi <hda@tinyerp.com> ashish <ashish@ashish-laptop>
Husen Daudi <hda@tinyerp.com> hda(Open ERP) <>
Husen Daudi <hda@tinyerp.com> hda@tinyerp.com <>
Husen Daudi (Serpent CS) <husen.daudi@serpentcs.com>
@@ -412,6 +425,10 @@ Jean-Baptiste Aubort <jean-baptiste.aubort@camptocamp.com> <jbaubort@wichside>
Jean-Christophe Vassort <jcv@openerp.com>
Jean-Christophe Vassort <jcv@openerp.com>
Jean-Sebastien Suzanne <jssuzanne@anybox.fr> Jean-Sebastien SUZANNE <>
+Jenil Malavia <jma@tinyerp.com>
+Jenil Malavia <jma@tinyerp.com> JMA(OpenEPR) <>
+Jenil Malavia <jma@tinyerp.com> JMA(OpenERP) <>
+Jenil Malavia <jma@tinyerp.com> Jenil(OpenERP) <>
Jeremy Kersten <jke@odoo.com>
Jeremy Kersten <jke@odoo.com> <jke@openerp.com>
Jeremy Kersten <jke@odoo.com> jke-openerp <>
@@ -422,6 +439,7 @@ Jigar Amin <jam@tinyerp.com>
Jigar Amin <jam@tinyerp.com> <jam@openerp.com>
Jigar Amin <jam@tinyerp.com> JAM (OpenERP) <>
Jigar Amin <jam@tinyerp.com> JAM(OpenERP) <>
+Jigar Amin <jam@tinyerp.com> amami <>
Jigar Amin <jam@tinyerp.com> jam <>
Jignesh Rathod <jir@tinyerp.com>
Jignesh Rathod <jir@tinyerp.com> <jir@jir-desktop>
@@ -461,6 +479,9 @@ Kuldeep Joshi <kjo@tinyerp.com>
Kuldeep Joshi <kjo@tinyerp.com> <th_dev2@openerp.co.in>
Kuldeep Joshi <kjo@tinyerp.com> kjo <>
Kunal Chavda <kch@tinyerp.com>
+Kunal Mathur <kum@tinyerp.com>
+Kunal Mathur <kum@tinyerp.com> <kum@tinyerp.co.in>
+Kunal Mathur <kum@tinyerp.com> KUM(OpenERP) <>
Laetitia Gangloff <laetitia.gangloff@acsone.eu>
Launchpad <codehost@crowberry>
Launchpad <codehost@crowberry> Launchpad Translations on behalf of openerp <>
@@ -497,6 +518,8 @@ Mantavya Gajjar <mga@tinyerp.com> <mga@tinyerp.co.in>
Mantavya Gajjar <mga@tinyerp.com> MGA(OpenERP) <>
Mantavya Gajjar <mga@tinyerp.com> Matvaya Gajjar(Openerp) <>
Mantavya Gajjar <mga@tinyerp.com> mga@tinyerp.com <>
+Manuel Abadia <info@tiedra.net> Manu <>
+Manuel Abadia <info@tiedra.net> Manu, Jigar Amin - OpenERP <>
Marcelo Hamra <marcelo.hamra@gmail.com> Marcelo Hamra <>
Marcelo Hamra <marcelo.hamra@gmail.com> marcelo <marcelo@hakuna>
Mario Arias Badila <the.clone.master@gmail.com>
@@ -579,6 +602,7 @@ Nicolas Bessi <nicolas.bessi@camptocamp.com>
Nicolas Bessi <nicolas.bessi@camptocamp.com> Nicolas <>
Nicolas Bessi <nicolas.bessi@camptocamp.com> Nicolas Bessi(C2C) <>
Nicolas Bessi <nicolas.bessi@camptocamp.com> nicolas.bessi@camptocamp.com <>
+Nicolas De Smet <zyphos@zyphos.be> Zyph0s <>
Nicolas Seinlet <nse@odoo.com> <nicolas@seinlet.com>
Nicolas Vanhoren <niv@openerp.com>
Nicolas Vanhoren <niv@openerp.com> <nicolas.vanhoren@openerp.com>
@@ -592,6 +616,9 @@ Niels Huylebroeck <niels@bubbles-it.be> Niels Huylebroeck <>
Niels Huylebroeck <niels@bubbles-it.be> Niels(Red15) <>
Niels Huylebroeck <niels@bubbles-it.be> Niels(Rrd15) <>
Niels Huylebroeck <niels@bubbles-it.be> Red15 <>
+Nikunj Jani <jni@tinyerp.com>
+Nikunj Jani <jni@tinyerp.com> <jni@tinyerp.co.in>
+Nikunj Jani <jni@tinyerp.com> Reporting Team(JNI OpenERP) <>
Nimesh Contractor <nco@tinyerp.com>
Oliver Laurent <olivier.laurent@acsone.eu>
Olivier Dony <odo@openerp.com>
@@ -599,24 +626,33 @@ Olivier Dony <odo@openerp.com> <olinether@gmail.com>
Olivier Dony <odo@openerp.com> <olivier@Olivier-Donys-MacBook-Pro.local>
Olivier Dony <odo@openerp.com> Odowan Kenobi <>
Olivier Dony <odo@openerp.com> Olivier Dony <>
-Olivier Dony <odo@openerp.com> Olvier Dony <>
+Olivier Dony <odo@openerp.com> Olivier Laurent, Olivier Dony <>
Olivier Dony <odo@openerp.com> OpenERP Framework R&D Team <>
Olivier Dony <odo@openerp.com> odo@tinyerp.com <>
Olivier Dossmann <od@tempo-consulting.fr> Olivier DOSSMANN <olivier@tempo-laptop>
Olivier Laurent <olt@odoo.com>
Olivier Laurent <olt@odoo.com> <olt@openerp.com>
Olivier Laurent <olt@odoo.com> <olt@tinyerp.com>
-Olivier Laurent <olt@odoo.com> Olivier Laurent, Olivier Dony <>
Olivier Laurent <olt@odoo.com> olt at tinyerp <>
Olivier Laurent <olt@odoo.com> olt@tinyerp.com <>
+Olivier Macchioni <olivier.macchioni@wingo.ch> <mac@Olivier-Macchionis-MacBook-Pro-2.local>
Omar Castiñeira Saavedra <omar@pexego.es>
Omar Castiñeira Saavedra <omar@pexego.es> Omar (Pexego) <>
Omar Castiñeira Saavedra <omar@pexego.es> Omar <omar@pexego.es>
Omar Castiñeira Saavedra <omar@pexego.es> Omar(Pexego),Anup(OpenERP) <>
Omar Castiñeira Saavedra <omar@pexego.es> omar7r <>
+Ostap Yakovenko <OstapYakovenko@users.noreply.github.com>
Pankaj Joshi <pji@tinyerp.com>
Pankaj Joshi <pji@tinyerp.com> Pankaj Joshi <pji@openerp.com>
Pankita Shah <shp@tinyerp.com>
+Pankti Parikh <pap@tinyerp.com>
+Pankti Parikh <pap@tinyerp.com> <pap@tinyerp.co.in>
+Pankti Parikh <pap@tinyerp.com> PAP (Openerp) <>
+Pankti Parikh <pap@tinyerp.com> PAP(OpenERP) <>
+Pankti Parikh <pap@tinyerp.com> PAP(Openerp) <>
+Pankti Parikh <pap@tinyerp.com> pap (OpenERP) <>
+Pankti Parikh <pap@tinyerp.com> pap(openerp) <>
+Pankti Parikh <pap@tinyerp.com> pap@tinyerp.com <>
Panos Christeas <xrg@openerp.com>
Panos Christeas <xrg@openerp.com> <p_chr...@hol.gr>
Panos Christeas <xrg@openerp.com> <p_christ@hol.gr>
@@ -640,8 +676,10 @@ Pariket Trivedi <ptr@tinyerp.com> <trivedi.pariket@gmail.com>
Parth Gajjar <pga@tinyerp.com>
Parthiv Patel <parthiv@techreceptives.com>
Parthiv Patel <parthiv@techreceptives.com> <parthivgls@gmail.com>
+Patrick Monnerat <pm@datasphere.ch> Patrik <>
Patrick Thieuleux <patrick.thieuleux@groupes.be> Patrick Thieuleux <>
Paul J Stevens <pjstevns@gmail.com> Paul J Stevens <>
+Paul Rad-Dupuy <signup@vivisci.com> vivisci <>
Paulius Sladkevičius <komsas@gmail.com>
Paulius Sladkevičius <komsas@gmail.com> Paulius Sladkevičius <paulius@hacbee.com>
Paulius Sladkevičius <komsas@gmail.com> Paulius Sladkevičius <paulius@hbee.eu>
@@ -654,6 +692,7 @@ Peter Langenberg <pl@agaplan.eu> Peter Langenberg <>
Philu <philubert@gmail.com> philubert@gmail.com <>
Phong Nguyen <phong.nguyen_thanh@yahoo.com> <phongnt@phongnt-laptop>
Phong Nguyen <phong.nguyen_thanh@yahoo.com> Phong Nguyen-Thanh <>
+Phuong Lu <phu@odoo.com> phu <>
Pinakin Nayi <pna@tinyerp.com>
Pinakin Nayi <pna@tinyerp.com> <pna@openerp.com>
Pinakin Nayi <pna@tinyerp.com> Pinakin Nai(OpenERP) <>
@@ -732,6 +771,10 @@ Ravi Gohil <rgo@tinyerp.com>
Ravi Gohil <rgo@tinyerp.com> <rgo@openerp.com>
Ravi Gohil <rgo@tinyerp.com> Ravi Gohil (OpenERP) <>
Ravi Gohil <rgo@tinyerp.com> rgo <>
+Ravindra Mekhiya <rme@tinyerp.com>
+Ravindra Mekhiya <rme@tinyerp.com> <rme@rme-desktop>
+Ravindra Mekhiya <rme@tinyerp.com> RME(OpenERP) <>
+Ravindra Mekhiya <rme@tinyerp.com> Ravindra Mekhiya (OpenERP) <>
Ravish Murari <rmu@tinyerp.com>
Ravish Murari <rmu@tinyerp.com> <rmu@openerp.com>
Ravish Murari <rmu@tinyerp.com> Ravish(OpenERP) <>
@@ -767,6 +810,11 @@ Rikenkumar Bhorania <rch@tinyerp.com> RCH(OpenERP). <>
Rikenkumar Bhorania <rch@tinyerp.com> rch (Open ERP) <>
Rikenkumar Bhorania <rch@tinyerp.com> rch (OpenERP) <>
Rikenkumar Bhorania <rch@tinyerp.com> rch(OpenERP) <>
+Rima Vora <rvo@tinyerp.com>
+Rima Vora <rvo@tinyerp.com> <rvo@tinyerp.co.in>
+Rima Vora <rvo@tinyerp.com> RVO <>
+Rima Vora <rvo@tinyerp.com> RVO(OpenERP) <>
+Rima Vora <rvo@tinyerp.com> Rvo(Open ERP) <>
Robert Sevcik <robajz@robajz.info> Robajz <>
Roberto López López <rlo@openerp.com>
Rohan Nayani <ron@tinyerp.com>
@@ -785,8 +833,23 @@ Rucha Patel <rpa@tinyerp.com> <ruchakpatel@gmail.com>
Rucha Patel <rpa@tinyerp.com> RPA(OpenERP) <>
Rucha Patel <rpa@tinyerp.com> rpa(Open ERP) <>
Rucha Patel <rpa@tinyerp.com> rpa-openerp <>
+Sabrina Romero <sabrina@vauxoo.com>
+Saiyed Azadahmed <saz@tinyerp.com>
+Saiyed Azadahmed <saz@tinyerp.com> <azaz.201186@gmail.com>
+Saiyed Azadahmed <saz@tinyerp.com> SAZ (Open ERP) <>
+Saiyed Azadahmed <saz@tinyerp.com> SAZ (OpenERP) <>
+Saiyed Azadahmed <saz@tinyerp.com> SAZ (Opn ERP) <>
+Saiyed Azadahmed <saz@tinyerp.com> SAZ (OprnERP) <>
+Saiyed Azadahmed <saz@tinyerp.com> SAZ(OpenERP) <>
+Saiyed Azadahmed <saz@tinyerp.com> SAz <>
+Saiyed Azadahmed <saz@tinyerp.com> saz <>
+Saiyed Azadahmed <saz@tinyerp.com> saz(OpenERP) <>
+Saiyed Azadahmed <saz@tinyerp.com> saz(openerp) <>
Salvatore Trimarchi <salvatore@trigluu.com>
Salvi Angjeli <s_angjeli@yahoo.com> Salvi Angjeli <>
+Sananaz Mansuri <sma@tinyerp.com>
+Sananaz Mansuri <sma@tinyerp.com> SMA(OpenERP) <>
+Sananaz Mansuri <sma@tinyerp.com> sma@tinyerp.com <>
Sandy Carter <sandy.carter@savoirfairelinux.com>
Sandy Carter <sandy.carter@savoirfairelinux.com> <bwrsandman@gmail.com>
Sanjay Gohel <sgo@tinyerp.com>
@@ -818,6 +881,7 @@ Sheth Sunny <sus@tinyerp.com>
Sheth Sunny <sus@tinyerp.com> <sus@openerp.com>
Sheth Sunny <sus@tinyerp.com> <sus@sus-desktop>
Sheth Sunny <sus@tinyerp.com> sus <tiny@tiny-desktop>
+Shyamli Singh <ssi@tinyerp.com>
Simon Lejeune <sle@openerp.com>
Simon Lejeune <sle@openerp.com> <openerp-sle@openerp-sle.home>
Simone Orsi <simahawk@gmail.com>
@@ -897,6 +961,11 @@ Ujjvala Collins <uco@tinyerp.com> uco(OpenERP) <>
Vadim Chobanu <enquiries@enapps.co.uk> Vadim - SmartMode LTD <>
Vaibhav Darji <vda@tinyerp.com>
Vaibhav Darji <vda@tinyerp.com> <vdavda@tinyerp.com>
+Vaishali Thakkar <vth@tinyerp.com>
+Vaishali Thakkar <vth@tinyerp.com> VTH(OpenERP) <>
+Valencia Rodrigues <vro@tinyerp.com>
+Valencia Rodrigues <vro@tinyerp.com> VRO(OpenERP) <>
+Vee <vitridex@gmail.com> vitridex <>
Vianney da Costa <vianney.dacosta@akretion.com.br>
Vianney da Costa <vianney.dacosta@akretion.com.br> Vianney - www.akretion.com.br <>
Vianney da Costa <vianney.dacosta@akretion.com.br> Vianney,Anup(OpenERP) <>
@@ -941,6 +1010,9 @@ Xavier Morel <xmo@odoo.com> <xmo@tinyerp.com>
Xavier Morel <xmo@odoo.com> Xavier Morel <>
Xavier Morel <xmo@odoo.com> Xavier(OpenERP) <>
Xavier Morel <xmo@odoo.com> xmo <>
+Yajushi Yagnik <yya@tinyerp.com>
+Yajushi Yagnik <yya@tinyerp.com> YYA(Axelor) <>
+Yajushi Yagnik <yya@tinyerp.com> YYA(OpenERP) <>
Yann Papouin <yann.papouin@gmail.com>
Yann Papouin <yann.papouin@gmail.com> Yann Papouin <>
Yann Papouin <yann.papouin@gmail.com> Yann papouin <>
@@ -950,6 +1022,7 @@ Yannick Vaucher <yannick.vaucher@camptocamp.com> Yannick Vaucher <>
Yannick Vaucher <yannick.vaucher@camptocamp.com> Yannick Vaucher <yvaucher@camptocamp.com>
Yannick Vaucher <yannick.vaucher@camptocamp.com> Yannick Vaucher@Camptocamp <>
Yogesh Parekh <ypa@tinyerp.com>
+Yogesh Parekh <ypa@tinyerp.com> <yogeshparekh1988@gmail.com>
Yogesh Parekh <ypa@tinyerp.com> <ypa@openerp.com>
Yogesh Sakhreliya <ysa@tinyerp.com>
Yogesh Sakhreliya <ysa@tinyerp.com> <ysa-openerp@ysa-openerp>
@@ -968,63 +1041,12 @@ Yury Tello <ytello@cubicerp.com>
Yury Tello <ytello@cubicerp.com> <info@cubicerp.com>
_FIXME ajm <ajm@tinyerp.com>
_FIXME ajm <ajm@tinyerp.com> AJM (OpenERP) <>
-_FIXME bch <bch@tinyerp.com> bch <>
-_FIXME ggh <ggh@tinyerp.com>
-_FIXME jma <jma@tinyerp.com>
-_FIXME jma <jma@tinyerp.com> JMA(OpenEPR) <>
-_FIXME jma <jma@tinyerp.com> JMA(OpenERP) <>
-_FIXME jni <jni@tinyerp.com>
-_FIXME jni <jni@tinyerp.com> <jni@tinyerp.co.in>
-_FIXME jni <jni@tinyerp.com> Reporting Team(JNI OpenERP) <>
-_FIXME kum <kum@tinyerp.com>
-_FIXME kum <kum@tinyerp.com> <kum@tinyerp.co.in>
-_FIXME kum <kum@tinyerp.com> KUM(OpenERP) <>
-_FIXME pap <pap@tinyerp.com>
-_FIXME pap <pap@tinyerp.com> <pap@tinyerp.co.in>
-_FIXME pap <pap@tinyerp.com> PAP (Openerp) <>
-_FIXME pap <pap@tinyerp.com> PAP(OpenERP) <>
-_FIXME pap <pap@tinyerp.com> PAP(Openerp) <>
-_FIXME pap <pap@tinyerp.com> pap (OpenERP) <>
-_FIXME pap <pap@tinyerp.com> pap(openerp) <>
-_FIXME pap <pap@tinyerp.com> pap@tinyerp.com <>
_FIXME rde <rde@tinyerp.com> rde <>
-_FIXME rme <rme@tinyerp.com>
-_FIXME rme <rme@tinyerp.com> <rme@rme-desktop>
-_FIXME rme <rme@tinyerp.com> RME(OpenERP) <>
-_FIXME rvo <rvo@tinyerp.com>
-_FIXME rvo <rvo@tinyerp.com> <rvo@tinyerp.co.in>
-_FIXME rvo <rvo@tinyerp.com> RVO <>
-_FIXME rvo <rvo@tinyerp.com> RVO(OpenERP) <>
-_FIXME rvo <rvo@tinyerp.com> Rvo(Open ERP) <>
-_FIXME saz <saz@tinyerp.com>
-_FIXME saz <saz@tinyerp.com> <azaz.201186@gmail.com>
-_FIXME saz <saz@tinyerp.com> SAZ (Open ERP) <>
-_FIXME saz <saz@tinyerp.com> SAZ (OpenERP) <>
-_FIXME saz <saz@tinyerp.com> SAZ (Opn ERP) <>
-_FIXME saz <saz@tinyerp.com> SAZ (OprnERP) <>
-_FIXME saz <saz@tinyerp.com> SAZ(OpenERP) <>
-_FIXME saz <saz@tinyerp.com> SAz <>
-_FIXME saz <saz@tinyerp.com> saz <>
-_FIXME saz <saz@tinyerp.com> saz(OpenERP) <>
-_FIXME saz <saz@tinyerp.com> saz(openerp) <>
-_FIXME sma <sma@tinyerp.com>
-_FIXME sma <sma@tinyerp.com> SMA(OpenERP) <>
-_FIXME sma <sma@tinyerp.com> sma@tinyerp.com <>
_FIXME sme <sme@tinyerp.com>
_FIXME sme <sme@tinyerp.com> SME(OpenERP) <>
_FIXME sme <sme@tinyerp.com> SME(Tiny/Axelor) <>
_FIXME sme <sme@tinyerp.com> SME(tiny/Axelor) <>
_FIXME sme <sme@tinyerp.com> sme <>
-_FIXME ssi <ssi@tinyerp.com>
-_FIXME vro <vro@tinyerp.com>
-_FIXME vro <vro@tinyerp.com> VRO(OpenERP) <>
-_FIXME vth <vth@tinyerp.com>
-_FIXME vth <vth@tinyerp.com> VTH(OpenERP) <>
-_FIXME yya <eda@tinyerp.com>
-_FIXME yya <eda@tinyerp.com> EDA(Axelor) <>
-_FIXME yya <yya@tinyerp.com>
-_FIXME yya <yya@tinyerp.com> YYA(Axelor) <>
-_FIXME yya <yya@tinyerp.com> YYA(OpenERP) <>
__FIXME bhumi <bhumi@tinyerp.com> <bhumi195@gmail.com>
__FIXME tiny <tiny@tinyerp.com>
__FIXME tiny <tiny@tinyerp.com> <tiny@tiny-desktop>
@@ -1032,25 +1054,22 @@ __FIXME tiny <tiny@tinyerp.com> tiny <>
# TODO INTERNAL
-# 2 AVP(Axelor) <>
-# 1 DRA(Axelor) <>
-# 1 avp <openerp@openerp-laptop>
# 2 Priyanka Jani(OpenERP Trainee) <>
-# 2 Hardik Sanchawat (OpenERP) <>
-# 2 Jenil(OpenERP) <>
# 1 Dhruvit Darji(OpenERP Trainee) <>
-# 1 Ravindra Mekhiya (OpenERP) <>
-# 1 Yogesh Parekh (OpenERP Trainee) <yogeshparekh1988@gmail.com>
# 1 Nikita Parmar (OpenERP Trainee) <nikky.nikita89@gmail.com>
# 1 Sejal Bhut (OpenERP Trainee) <sejalpatel125@gmail.com>
-# 1 Amit (Open ERP) <>
-# 1 phu <>
# 1 vba <>
+
+# TODO INTERNAL Conflict
+
+# 1 Amit (Open ERP) <>
# 1 Ajay Patel <apa@openerp.com>
# 1 Yogesh(Open ERP) <>
# 1 Yogesh(OpenERP) <>
# 1 Chirag <chirag@chirag-desktop>
+# TODO INTERNAL Couples
+
# 1 ACH,Jay <>
# 2 JMA,VRA <>
# 2 Vir,Amp <>
@@ -1088,6 +1107,8 @@ __FIXME tiny <tiny@tinyerp.com> tiny <>
# 1 Olivier Dony, Jay Vora <>
# 1 Christophe Simonis, Olivier Dony <>
+# TODO INTERNAL Team Leaders
+
# 2 Framework Team <>
# 2 OpenERP R&D Framework Team <>
# 2 Quality Team <>
@@ -1095,6 +1116,8 @@ __FIXME tiny <tiny@tinyerp.com> tiny <>
# 1 Mod2 Team(OpenERP) <>
# 1 OnDemand Open Object User <odoo@saas>
+# TODO INTERNAL Leading Space
+
# 1 Amit Dodiya (OpenERP) <>
# 1 Amit Dodiya(OpenERP) <>
# 1 Jean-Christophe VASSORT <>
@@ -1108,7 +1131,6 @@ __FIXME tiny <tiny@tinyerp.com> tiny <>
# 2 jkei <jerome.keirse@gmail.com>
# 2 richierichrawr <richierichrawr@users.noreply.github.com>
# 2 watermelonjuice <watermelonjuice@users.noreply.github.com>
-# 1 vrenaville <vincent.renaville@camptocamp.com>
# 2 youring <youring@gmail.com>
# 2 Дмитро Катюха <Dmytro.Katyukha@jbm.com.ua>
# 1 jbeficent <jordi.ballester@eficent.com>
@@ -1117,9 +1139,7 @@ __FIXME tiny <tiny@tinyerp.com> tiny <>
# 1 jmustonen <jmusto@gmail.com>
# 1 celm1990 <celm1990@hotmail.com>
# 1 cysnake4713 <cysnake4713@gmail.com>
-# 1 dharmesh rathod <dharmesh247@gmail.com>
# 1 driou <a.bourroux@gmail.com>
-# 1 dsabrinarg <sabrina@vauxoo.com>
# 1 eneldoserrata <eneldoserrata@gmail.com>
# 1 fdesmottes <fdesmottes@nuxly.com>
# 1 g.shipilov <g.shipilov@it1>
@@ -1127,38 +1147,25 @@ __FIXME tiny <tiny@tinyerp.com> tiny <>
# 1 senthilnathang <bornspy@gmail.com>
# 1 the-digital-engineer <the-digital-engineer@users.noreply.github.com>
# 1 yingliu4203 <yingliu4203@gmail.com>
-# 1 hugosdsantos <hugosdsantos@gmail.com>
# 1 krnkris <krnkris@users.noreply.github.com>
# 1 buke <wangbuke@gmail.com>
-# 1 OstapYakovenko <OstapYakovenko@users.noreply.github.com>
# 1 DEEP SILVER ACCESSORY <fordeep@hotmail.com>
# 1 neps1192 <neps1192@gmail.com>
-# 1 ALIANE Abdelouahab <abdelouahab_al@yahoo.fr>
# 1 Invitu <contact@invitu.com>
# 1 Jorge <jcamacho@trey.es>
# 1 Maria G <mariagr@europeanstonetile.com>
# 1 Mack <mackz@sina.com>
-# TODO MISSING INFO
-
-# 1 Olivier Macchioni <mac@Olivier-Macchionis-MacBook-Pro-2.local>
-# Email not found
+# TODO MISSING INFO EMAIL
# 2 Denis Seleznyov <>
-# Email not found
# 1 openerp user <openerp@openerp6desktop>
# Name: Milan Tribuson
-# email not found
-# 1 Manu, Jigar Amin - OpenERP <>
-# 1 Ferchu <>
-# 1 Patrik <>
# 1 R.Messier <>
-# 1 Manu <>
-# 1 Zyph0s <>
-# 1 amami <>
-# 1 vitridex <>
-# 1 vivisci <>
-# 1 ashish <ashish@ashish-laptop>
+# Name: Raphael Messier
+
+# 1 Ferchu <>
+# Launchpad: https://launchpad.net/~fgb
diff --git a/MANIFEST.in b/MANIFEST.in
index 2ef37c2bb2e..eea6b83adeb 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -17,6 +17,7 @@ recursive-include openerp *.rng
recursive-include openerp *.rst
recursive-include openerp *.sass
recursive-include openerp *.sql
+recursive-include openerp *.template
recursive-include openerp *.txt
recursive-include openerp *.ttf
recursive-include openerp *.woff
diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml
index ae488f3eb08..9c85d091a71 100644
--- a/addons/account/partner_view.xml
+++ b/addons/account/partner_view.xml
@@ -138,9 +138,10 @@
<field name="model">res.partner</field>
<field name="priority">2</field>
<field name="inherit_id" ref="base.view_partner_form"/>
+ <field name="groups_id" eval="[(4, ref('account.group_account_invoice'))]"/>
<field name="arch" type="xml">
<page name="sales_purchases" position="after">
- <page string="Accounting" col="4" name="accounting" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}" groups="account.group_account_invoice">
+ <page string="Accounting" col="4" name="accounting" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}">
<group>
<group string="Sale" name="acc_sale">
<field name="property_payment_term" widget="selection"/>
@@ -172,7 +173,7 @@
</field>
</group>
</page>
- <page string="Accounting" name="accounting_disabled" attrs="{'invisible': ['|',('is_company','=',True),('parent_id','=',False)]}" groups="account.group_account_invoice">
+ <page string="Accounting" name="accounting_disabled" attrs="{'invisible': ['|',('is_company','=',True),('parent_id','=',False)]}">
<div>
<p>Accounting-related settings are managed on <button name="open_commercial_entity" type="object" string="the parent company" class="oe_link"/></p>
</div>
diff --git a/addons/account_payment/account_payment.py b/addons/account_payment/account_payment.py
index 0b617eeacae..44345abe8e1 100644
--- a/addons/account_payment/account_payment.py
+++ b/addons/account_payment/account_payment.py
@@ -397,8 +397,8 @@ class payment_line(osv.osv):
break
return {'value': data}
- def fields_get(self, cr, uid, fields=None, context=None):
- res = super(payment_line, self).fields_get(cr, uid, fields, context)
+ def fields_get(self, cr, uid, fields=None, context=None, write_access=True, attributes=None):
+ res = super(payment_line, self).fields_get(cr, uid, fields, context, write_access, attributes)
if 'communication2' in res:
res['communication2'].setdefault('states', {})
res['communication2']['states']['structured'] = [('readonly', True)]
diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py
index 1134a849f6f..35c6f98d06e 100644
--- a/addons/anonymization/anonymization.py
+++ b/addons/anonymization/anonymization.py
@@ -539,7 +539,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory):
data = pickle.loads(base64.decodestring(wizard.file_import))
migration_fix_obj = self.pool.get('ir.model.fields.anonymization.migration.fix')
- fix_ids = migration_fix_obj.search(cr, uid, [('target_version', '=', '7.0')])
+ fix_ids = migration_fix_obj.search(cr, uid, [('target_version', '=', '8.0')])
fixes = migration_fix_obj.read(cr, uid, fix_ids, ['model_name', 'field_name', 'query', 'query_type', 'sequence'])
fixes = group(fixes, ('model_name', 'field_name'))
diff --git a/addons/auth_ldap/users_ldap.py b/addons/auth_ldap/users_ldap.py
index 5963ee2a66d..937e672ef89 100644
--- a/addons/auth_ldap/users_ldap.py
+++ b/addons/auth_ldap/users_ldap.py
@@ -105,7 +105,7 @@ class CompanyLDAP(osv.osv):
if results and len(results) == 1:
dn = results[0][0]
conn = self.connect(conf)
- conn.simple_bind_s(dn, password)
+ conn.simple_bind_s(dn, password.encode('utf-8'))
conn.unbind()
entry = results[0]
except ldap.INVALID_CREDENTIALS:
@@ -141,7 +141,7 @@ class CompanyLDAP(osv.osv):
try:
conn = self.connect(conf)
conn.simple_bind_s(conf['ldap_binddn'] or '',
- conf['ldap_password'] or '')
+ conf['ldap_password'].encode('utf-8') or '')
results = conn.search_st(conf['ldap_base'], ldap.SCOPE_SUBTREE,
filter, retrieve_attributes, timeout=60)
conn.unbind()
diff --git a/addons/base_import/static/src/xml/import.xml b/addons/base_import/static/src/xml/import.xml
index 1f8bd6705a8..0f17765e5b2 100644
--- a/addons/base_import/static/src/xml/import.xml
+++ b/addons/base_import/static/src/xml/import.xml
@@ -209,9 +209,9 @@
(e.g. a customer that has multiple tags)?</a></dt>
<dd>
<p>The tags should be separated by a comma without any
- spacing. For example, if you want you customer to be
- lined to both tags 'Manufacturer' and 'Retailer'
- then you will encode it as follow "Manufacturer,
+ spacing. For example, if you want your customer to be
+ linked to both tags 'Manufacturer' and 'Retailer'
+ then you will encode "Manufacturer,
Retailer" in the same column of your CSV file.</p>
<a href="/base_import/static/csv/m2m_customers_tags.csv">
CSV file for Manufacturer, Retailer</a><br/>
diff --git a/addons/base_import/tests/test_cases.py b/addons/base_import/tests/test_cases.py
index bad79bc7cdc..768754df07c 100644
--- a/addons/base_import/tests/test_cases.py
+++ b/addons/base_import/tests/test_cases.py
@@ -210,9 +210,6 @@ class test_preview(TransactionCase):
})
self.assertTrue('error' in result)
- def test_csv_errors(self):
- Import, id = self.make_import()
-
result = Import.parse_preview(self.cr, self.uid, id, {
'quoting': '"',
'separator': 'bob',
@@ -356,3 +353,32 @@ class test_convert_import_data(TransactionCase):
Import._convert_import_data,
record, [False, False, False],
{'quoting': '"', 'separator': ',', 'headers': True,})
+
+class test_failures(TransactionCase):
+ def test_big_attachments(self):
+ """
+ Ensure big fields (e.g. b64-encoded image data) can be imported and
+ we're not hitting limits of the default CSV parser config
+ """
+ import csv, cStringIO
+ from PIL import Image
+
+ im = Image.new('RGB', (1920, 1080))
+ fout = cStringIO.StringIO()
+
+ writer = csv.writer(fout, dialect=None)
+ writer.writerows([
+ ['name', 'db_datas'],
+ ['foo', im.tobytes().encode('base64')]
+ ])
+
+ Import = self.env['base_import.import']
+ imp = Import.create({
+ 'res_model': 'ir.attachment',
+ 'file': fout.getvalue()
+ })
+ [results] = imp.do(
+ ['name', 'db_datas'],
+ {'headers': True, 'separator': ',', 'quoting': '"'})
+ self.assertFalse(
+ results, "results should be empty on successful import")
diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py
index 70c6cceaed4..d4743f22bac 100644
--- a/addons/google_calendar/google_calendar.py
+++ b/addons/google_calendar/google_calendar.py
@@ -502,6 +502,7 @@ class google_calendar(osv.AbstractModel):
rrule = [rule for rule in single_event_dict["recurrence"] if rule.startswith("RRULE:")][0][6:]
result['rrule'] = rrule
+ context = dict(context or {}, no_mail_to_attendees=True)
if type == "write":
res = calendar_event.write(cr, uid, event['id'], result, context=context)
elif type == "copy":
diff --git a/addons/hr/static/src/css/hr.css b/addons/hr/static/src/css/hr.css
index 0a1074b3b3e..ba3dfd7ccdf 100644
--- a/addons/hr/static/src/css/hr.css
+++ b/addons/hr/static/src/css/hr.css
@@ -1,6 +1,6 @@
.oe_employee_details{
min-height: 168px !important;
- max-width: 336px;
+ width: 350px;
}
.oe_employee_image {
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
index 0276e49582d..f34e03b69a7 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
@@ -281,7 +281,8 @@ class account_analytic_line(osv.osv):
details.append("%s" % (line['unit_amount'], ))
if data.get('name', False):
details.append(line['name'])
- note.append(u' - '.join(map(lambda x: unicode(x) or '',details)))
+ if details:
+ note.append(u' - '.join(map(lambda x: unicode(x) or '',details)))
if note:
curr_line['name'] += "\n" + ("\n".join(map(lambda x: unicode(x) or '',note)))
invoice_line_obj.create(cr, uid, curr_line, context=context)
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
index 1bbde4ad70e..454b70e1692 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
@@ -258,6 +258,14 @@ class hr_timesheet_sheet(osv.osv):
raise UserError(_('You cannot delete a timesheet which is already confirmed.'))
elif sheet['total_attendance'] <> 0.00:
raise UserError(_('You cannot delete a timesheet which have attendance entries.'))
+
+ toremove = []
+ analytic_timesheet = self.pool.get('hr.analytic.timesheet')
+ for sheet in self.browse(cr, uid, ids, context=context):
+ for timesheet in sheet.timesheet_ids:
+ toremove.append(timesheet.id)
+ analytic_timesheet.unlink(cr, uid, toremove, context=context)
+
return super(hr_timesheet_sheet, self).unlink(cr, uid, ids, context=context)
def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js
index f75e9b40833..2ecb17d5049 100644
--- a/addons/im_livechat/static/src/js/im_livechat.js
+++ b/addons/im_livechat/static/src/js/im_livechat.js
@@ -60,6 +60,7 @@
this.feedback.on("feedback_sent", this, this.click_close);
}else{
this._super.apply(this, arguments);
+ openerp.set_cookie(im_livechat.COOKIE_NAME, "", -1);
}
},
});
@@ -99,7 +100,7 @@
this.load_template(db, channel, options, rule);
},
_get_template_list: function(){
- return ['/im_chat/static/src/xml/im_chat.xml', '/im_livechat/static/src/xml/im_livechat.xml'];
+ return ['/im_livechat/static/src/xml/im_livechat.xml', '/im_chat/static/src/xml/im_chat.xml'];
},
load_template: function(db, channel, options, rule){
var self = this;
diff --git a/addons/im_livechat/static/src/xml/im_livechat.xml b/addons/im_livechat/static/src/xml/im_livechat.xml
index 0176cb00419..410b3b13c81 100644
--- a/addons/im_livechat/static/src/xml/im_livechat.xml
+++ b/addons/im_livechat/static/src/xml/im_livechat.xml
@@ -12,9 +12,9 @@
Did we correctly answer your question ?
</div>
<div class="oe_livechat_rating_choices">
- <img src='/rating/static/src/img/rating_10.png' alt="Good" data-value="10"/>
- <img src='/rating/static/src/img/rating_5.png' alt="OK" data-value="5"/>
- <img src='/rating/static/src/img/rating_0.png' alt="Bad" data-value="0" />
+ <img t-att-src="_s + '/rating/static/src/img/rating_10.png'" alt="Good" data-value="10"/>
+ <img t-att-src="_s + '/rating/static/src/img/rating_5.png'" alt="OK" data-value="5"/>
+ <img t-att-src="_s + '/rating/static/src/img/rating_0.png'" alt="Bad" data-value="0" />
</div>
<div class="oe_livechat_rating_reason">
<textarea id="reason" placeholder="Explain your note"></textarea>
diff --git a/addons/mail/mail_template.py b/addons/mail/mail_template.py
index 360239bffd2..f73ed0cffcd 100644
--- a/addons/mail/mail_template.py
+++ b/addons/mail/mail_template.py
@@ -177,7 +177,6 @@ class mail_template(osv.osv):
"""
if context is None:
context = {}
- res_ids = filter(None, res_ids) # to avoid browsing [None] below
results = dict.fromkeys(res_ids, u"")
# try to load the template
@@ -189,14 +188,16 @@ class mail_template(osv.osv):
# prepare template variables
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
- records = self.pool[model].browse(cr, uid, res_ids, context=context) or [None]
+ records = self.pool[model].browse(cr, uid, filter(None, res_ids), context=context) # filter to avoid browsing [None]
+ res_to_rec = dict.fromkeys(res_ids, None)
+ for record in records:
+ res_to_rec[record.id] = record
variables = {
'format_tz': lambda dt, tz=False, format=False, context=context: format_tz(self.pool, cr, uid, dt, tz, format, context),
'user': user,
'ctx': context, # context kw would clash with mako internals
}
- for record in records:
- res_id = record.id if record else None
+ for res_id, record in res_to_rec.iteritems():
variables['object'] = record
try:
render_result = template.render(variables)
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
index 58baf8dd029..a6e7cf65ac6 100644
--- a/addons/mail/wizard/mail_compose_message_view.xml
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -15,7 +15,7 @@
<field name="is_log" invisible="1"/>
<field name="parent_id" invisible="1"/>
<field name="mail_server_id" invisible="1"/>
- <field name="active_domain"/>
+ <field name="active_domain" invisible="1"/>
<!-- Various warnings -->
<field name="use_active_domain" invisible="1"/>
diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py
index 70e0f4a9ee3..249d4a9548f 100644
--- a/addons/mrp/stock.py
+++ b/addons/mrp/stock.py
@@ -252,7 +252,7 @@ class stock_warehouse(osv.osv):
route_obj = self.pool.get('stock.location.route')
data_obj = self.pool.get('ir.model.data')
try:
- manufacture_route_id = data_obj.get_object_reference(cr, uid, 'stock', 'route_warehouse0_manufacture')[1]
+ manufacture_route_id = data_obj.get_object_reference(cr, uid, 'mrp', 'route_warehouse0_manufacture')[1]
except:
manufacture_route_id = route_obj.search(cr, uid, [('name', 'like', _('Manufacture'))], context=context)
manufacture_route_id = manufacture_route_id and manufacture_route_id[0] or False
diff --git a/addons/procurement_jit/procurement_jit.py b/addons/procurement_jit/procurement_jit.py
index 9fa3ed45f83..c7b36ebed98 100644
--- a/addons/procurement_jit/procurement_jit.py
+++ b/addons/procurement_jit/procurement_jit.py
@@ -26,7 +26,9 @@ class procurement_order(osv.osv):
_inherit = "procurement.order"
def create(self, cr, uid, vals, context=None):
+ context = context or {}
procurement_id = super(procurement_order, self).create(cr, uid, vals, context=context)
- self.run(cr, uid, [procurement_id], context=context)
- self.check(cr, uid, [procurement_id], context=context)
+ if not context.get('procurement_autorun_defer'):
+ self.run(cr, uid, [procurement_id], context=context)
+ self.check(cr, uid, [procurement_id], context=context)
return procurement_id
diff --git a/addons/product/product.py b/addons/product/product.py
index c2596b93e0c..72a7848fd8c 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -22,8 +22,10 @@
import math
import re
import time
+from collections import OrderedDict
from _common import ceiling
+
from openerp import SUPERUSER_ID
from openerp import tools
from openerp.osv import osv, fields, expression
@@ -796,9 +798,16 @@ class product_template(osv.osv):
pass
return super(product_template, self).name_get(cr, user, ids, context)
-
-
-
+ def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
+ product_product = self.pool['product.product']
+ results = product_product.name_search(
+ cr, user, name, args, operator=operator, context=context, limit=limit)
+ product_ids = [p[0] for p in results]
+ template_ids = [p.product_tmpl_id.id
+ for p in product_product.browse(
+ cr, user, product_ids, context=context)]
+ uniq_ids = OrderedDict.fromkeys(template_ids).keys()
+ return self.name_get(cr, user, uniq_ids, context=context)
class product_product(osv.osv):
_name = "product.product"
@@ -1068,12 +1077,11 @@ class product_product(osv.osv):
# on a database with thousands of matching products, due to the huge merge+unique needed for the
# OR operator (and given the fact that the 'name' lookup results come from the ir.translation table
# Performing a quick memory merge of ids in Python will give much better performance
- ids = set(self.search(cr, user, args + [('default_code', operator, name)], limit=limit, context=context))
+ ids = self.search(cr, user, args + [('default_code', operator, name)], limit=limit, context=context)
if not limit or len(ids) < limit:
# we may underrun the limit because of dupes in the results, that's fine
limit2 = (limit - len(ids)) if limit else False
- ids.update(self.search(cr, user, args + [('name', operator, name), ('id', 'not in', list(ids))], limit=limit2, context=context))
- ids = list(ids)
+ ids += self.search(cr, user, args + [('name', operator, name), ('id', 'not in', ids)], limit=limit2, context=context)
elif not ids and operator in expression.NEGATIVE_TERM_OPERATORS:
ids = self.search(cr, user, args + ['&', ('default_code', operator, name), ('name', operator, name)], limit=limit, context=context)
if not ids and operator in positive_operators:
diff --git a/addons/product/product_demo.xml b/addons/product/product_demo.xml
index c239098bcf7..144685d45cf 100644
--- a/addons/product/product_demo.xml
+++ b/addons/product/product_demo.xml
@@ -186,7 +186,6 @@ FaceTime HD Camera, 1.2 MP Photos</field>
<field name="type">consu</field>
<field name="uom_id" ref="product_uom_unit"/>
<field name="uom_po_id" ref="product_uom_unit"/>
- <field name="description">Custom computer assembled on order based on customer's requirement.</field>
<field name="description_sale">Bose's smallest portable Bluetooth speaker</field>
<field name="default_code">B3423</field>
</record>
diff --git a/addons/project/project_tip_data.xml b/addons/project/project_tip_data.xml
index aefdb33472f..ba920200a20 100644
--- a/addons/project/project_tip_data.xml
+++ b/addons/project/project_tip_data.xml
@@ -23,7 +23,7 @@
</record>
<record model="web.tip" id="project_tip_3">
- <field name="description">This where you can track history and collaborate on tasks. Send a new message to notify followers or log an internal note to keep history but don’t notify followers.</field>
+ <field name="description">This where you can track history and collaborate on tasks. Send a new message to notify followers or log an internal note to store information without notifying the followers.</field>
<field name="action_id" ref="action_view_task"/>
<field name="model">project.task</field>
<field name="mode">form</field>
diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml
index 01aaf42e378..7ed6f0593c9 100644
--- a/addons/project/project_view.xml
+++ b/addons/project/project_view.xml
@@ -258,7 +258,7 @@
<field name="doc_count"/>
<templates>
<t t-name="kanban-box">
- <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_project oe_kanban_global_click">
+ <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_project oe_kanban_global_click oe_kanban_no_auto_height">
<div class="oe_dropdown_toggle oe_dropdown_kanban" groups="base.group_user">
<i class="fa fa-bars fa-lg"/>
<ul class="oe_dropdown_menu">
@@ -268,7 +268,7 @@
</ul>
</div>
<div class="oe_project_kanban_content">
- <div class="row">
+ <div class="oe_kanban_content">
<h4 class="text-center">
<field name="name"/>
</h4>
@@ -279,26 +279,28 @@
</div>
</div>
<div class="row oe_margin_top_8 oe_kanban_project_list">
- <div class="col-md-6">
- <a t-if="record.use_tasks.raw_value" name="%(act_project_project_2_project_task_all)d" type="action">
+ <div t-if="record.use_tasks.raw_value" class="col-md-6">
+ <a name="%(act_project_project_2_project_task_all)d" type="action">
<t t-raw="record.task_count.raw_value" />
<field name="label_tasks"/>
</a>
</div>
- <div class="col-md-6">
- <a t-if="record.doc_count.raw_value" name="attachment_tree_view" type="object">
- <field name="doc_count"/> Documents
- </a>
- </div>
- <div t-if="record.use_tasks.raw_value and record.task_count.raw_value lt 1" class="col-md-12">
+ <div t-if="record.use_tasks.raw_value and record.task_count.raw_value == 0" class="col-md-12">
<img src="/project/static/src/img/top_left_arrow.png" style="margin-top: -8px; width: 22px;" />
<span>No activity yet, click to enter in the project.</span>
</div>
+ <div t-if="record.doc_count.raw_value" class="col-md-6">
+ <a name="attachment_tree_view" type="object">
+ <field name="doc_count"/> Documents
+ </a>
+ </div>
</div>
- <div class="oe_margin_top_8 oe_kanban_project_avatars">
- <t t-foreach="record.members.raw_value.slice(0,12)" t-as="member">
- <img t-att-src="kanban_image('res.users', 'image_small', member)" t-att-data-member_id="member"/>
- </t>
+ <div class="row oe_margin_top_8 oe_kanban_project_avatars">
+ <div class="col-md-12">
+ <t t-foreach="record.members.raw_value.slice(0,12)" t-as="member">
+ <img t-att-src="kanban_image('res.users', 'image_small', member)" t-att-data-member_id="member"/>
+ </t>
+ </div>
</div>
<div class="clearfix"/>
</div>
@@ -729,7 +731,7 @@
<field name="legend_priority" nolabel="1"/>
<p class="text-muted" colspan="2">
- You can also add a description to help your coworkers understanding the meaning and purpose of the stage.
+ You can also add a description to help your coworkers understand the meaning and purpose of the stage.
</p>
<field name="description" placeholder="Add a description..." nolabel="1" colspan="2"/>
</group>
diff --git a/addons/project/static/src/css/project.css b/addons/project/static/src/css/project.css
index 20e6f6b09f8..207b4df0a05 100644
--- a/addons/project/static/src/css/project.css
+++ b/addons/project/static/src/css/project.css
@@ -16,8 +16,8 @@
}
.openerp .oe_kanban_view .oe_kanban_project {
- width: 290px;
- min-height: 150px;
+ width: 250px;
+ min-height: 160px !important;
cursor: default;
}
@@ -28,15 +28,6 @@
.openerp .oe_margin_top_8 {
margin-top: 8px;
}
-.openerp .oe_kanban_project .oe_kanban_project_tags{
- text-align: right;
-}
-.openerp .oe_kanban_project .oe_kanban_project_tags .label{
- margin: 3px 2px;
-}
-
-.openerp label.oe_project_kanban_legend {
- min-width: inherit !important;
- margin-top: 6px;
- margin-right: 8px;
+.openerp .oe_kanban_project .oe_kanban_project_list .col-md-6 a{
+ margin-left: 5px;
}
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index 2a3ec26e891..cea055ec30a 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -493,7 +493,7 @@ class project(osv.Model):
states={'close': [('readonly', True)], 'cancelled': [('readonly', True)]}),
'issue_count': fields.function(_issue_count, type='integer', string="Issues",),
'issue_ids': fields.one2many('project.issue', 'project_id', string="Issues",
- domain=[('date_closed', '!=', False)]),
+ domain=[('stage_id.fold', '=', False)]),
}
def _check_escalation(self, cr, uid, ids, context=None):
@@ -580,7 +580,7 @@ class res_partner(osv.osv):
partner_id: Issue.search_count(cr,uid, [('partner_id', '=', partner_id)])
for partner_id in ids
}
-
+
""" Inherits partner and adds Issue information in the partner form """
_inherit = 'res.partner'
_columns = {
diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml
index 05da0efa5ae..40abd94245d 100644
--- a/addons/project_issue/project_issue_view.xml
+++ b/addons/project_issue/project_issue_view.xml
@@ -350,15 +350,15 @@
<field name="issue_ids" invisible="1"/>
</field>
<xpath expr="//div[contains(@class, 'oe_kanban_project_list')]/div" position="after">
- <div class="col-md-6">
- <a t-if="record.use_issues.raw_value" name="%(act_project_project_2_project_issue_all)d" type="action">
+ <div t-if="record.use_issues.raw_value" class="col-md-6">
+ <a name="%(act_project_project_2_project_issue_all)d" type="action">
<t t-raw="record.issue_ids.raw_value.length"/>
<field name="label_issues"/>
</a>
</div>
</xpath>
<xpath expr="//div[contains(@class, 'oe_kanban_project_list')]/div[contains(@class, 'col-md-12')]" position="replace">
- <div t-if="!(record.use_tasks.raw_value and record.task_count.raw_value gt 0) and !(record.use_issues.raw_value and record.issue_ids.raw_value.length gt 0)" class="col-md-12">
+ <div t-if="(record.use_tasks.raw_value or record.use_issues.raw_value) and !(record.use_tasks.raw_value and record.task_count.raw_value gt 0) and !(record.use_issues.raw_value and record.issue_ids.raw_value.length gt 0)" class="col-md-12">
<img src="/project/static/src/img/top_left_arrow.png" style="margin-top: -8px; width: 22px;" />
<span>No activity yet, click to enter in the project.</span>
</div>
diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py
index dfa2ea0cad7..9ec821dfc9c 100644
--- a/addons/project_timesheet/project_timesheet.py
+++ b/addons/project_timesheet/project_timesheet.py
@@ -115,9 +115,12 @@ class project_work(osv.osv):
vals_line['user_id'] = vals['user_id']
vals_line['product_id'] = result['product_id']
if vals.get('date'):
- timestamp = datetime.datetime.strptime(vals['date'], tools.DEFAULT_SERVER_DATETIME_FORMAT)
- ts = fields.datetime.context_timestamp(cr, uid, timestamp, context)
- vals_line['date'] = ts.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ if len(vals['date']) > 10:
+ timestamp = datetime.datetime.strptime(vals['date'], tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ ts = fields.datetime.context_timestamp(cr, uid, timestamp, context)
+ vals_line['date'] = ts.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ else:
+ vals_line['date'] = vals['date']
# Calculate quantity based on employee's product's uom
vals_line['unit_amount'] = vals['hours']
@@ -260,7 +263,7 @@ class task(osv.osv):
missing_analytic_entries[task_work.id] = {
'name' : task_work.name,
'user_id' : task_work.user_id.id,
- 'date' : task_work.date and task_work.date[:10] or False,
+ 'date' : task_work.date,
'account_id': acc_id,
'hours' : task_work.hours,
'task_id' : task_obj.id
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index ff76b51d826..96918163a2c 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -198,6 +198,12 @@ class purchase_order(osv.osv):
('cancel', 'Cancelled')
]
+ READONLY_STATES = {
+ 'confirmed': [('readonly', True)],
+ 'approved': [('readonly', True)],
+ 'done': [('readonly', True)]
+ }
+
_columns = {
'name': fields.char('Order Reference', required=True, select=True, copy=False,
help="Unique number of the purchase order, "
@@ -219,16 +225,16 @@ class purchase_order(osv.osv):
copy=False),
'date_approve':fields.date('Date Approved', readonly=1, select=True, copy=False,
help="Date on which purchase order has been approved"),
- 'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
+ 'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states=READONLY_STATES,
change_default=True, track_visibility='always'),
'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
- states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
+ states=READONLY_STATES,
help="Put an address if you want to deliver directly from the supplier to the customer. " \
"Otherwise, keep empty to deliver to your own company."
),
- 'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]} ),
- 'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
- 'currency_id': fields.many2one('res.currency','Currency', readonly=True, required=True,states={'draft': [('readonly', False)],'sent': [('readonly', False)]}),
+ 'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states=READONLY_STATES),
+ 'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states=READONLY_STATES, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
+ 'currency_id': fields.many2one('res.currency','Currency', required=True, states=READONLY_STATES),
'state': fields.selection(STATE_SELECTION, 'Status', readonly=True,
help="The status of the purchase order or the quotation request. "
"A request for quotation is a purchase order in a 'Draft' status. "
@@ -1433,8 +1439,8 @@ class procurement_order(osv.osv):
po_line_ids = po_line_obj.search(cr, uid, [('order_id', '=', add_purchase), ('product_id', 'in', [x.product_id.id for x in procurements])], context=context)
po_lines = po_line_obj.browse(cr, uid, po_line_ids, context=context)
po_prod_dict = {}
- for po in po_lines:
- po_prod_dict[po.product_id.id] = po
+ for pol in po_lines:
+ po_prod_dict[pol.product_id.id] = pol
procs_to_create = []
#Check which procurements need a new line and which need to be added to an existing one
for proc in procurements:
diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py
index 102ef73deea..00c69b4e86b 100644
--- a/addons/purchase/stock.py
+++ b/addons/purchase/stock.py
@@ -70,13 +70,33 @@ class stock_move(osv.osv):
self.pool.get('purchase.order').write(cr, uid, [purchase_line.order_id.id], {
'invoice_ids': [(4, invoice_line_vals['invoice_id'])],
})
+ purchase_line_obj = self.pool.get('purchase.order.line')
+ purchase_obj = self.pool.get('purchase.order')
+ invoice_line_obj = self.pool.get('account.invoice.line')
+ purchase_id = move.purchase_line_id.order_id.id
+ purchase_line_ids = purchase_line_obj.search(cr, uid, [('order_id', '=', purchase_id), ('product_id.type', '=', 'service'), ('invoice_lines', '=', False)], context=context)
+ if purchase_line_ids:
+ inv_lines = []
+ for po_line in purchase_line_obj.browse(cr, uid, purchase_line_ids, context=context):
+ acc_id = purchase_obj._choose_account_from_po_line(cr, uid, po_line, context=context)
+ inv_line_data = purchase_obj._prepare_inv_line(cr, uid, acc_id, po_line, context=context)
+ inv_line_id = invoice_line_obj.create(cr, uid, inv_line_data, context=context)
+ inv_lines.append(inv_line_id)
+ po_line.write({'invoice_lines': [(4, inv_line_id)]})
+ invoice_line_obj.write(cr, uid, inv_lines, {'invoice_id': invoice_line_vals['invoice_id']}, context=context)
return invoice_line_id
def _get_master_data(self, cr, uid, move, company, context=None):
if move.purchase_line_id:
purchase_order = move.purchase_line_id.order_id
return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id
- else:
+ elif move.picking_id:
+ # In case of an extra move, it is better to use the data from the original moves
+ for purchase_move in move.picking_id.move_lines:
+ if purchase_move.purchase_line_id:
+ purchase_order = purchase_move.purchase_line_id.order_id
+ return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id
+
partner = move.picking_id and move.picking_id.partner_id or False
code = self.get_code_from_locs(cr, uid, move, context=context)
if partner and partner.property_product_pricelist_purchase and code == 'incoming':
@@ -149,18 +169,6 @@ class stock_picking(osv.osv):
purchase_line_obj = self.pool.get('purchase.order.line')
invoice_line_obj = self.pool.get('account.invoice.line')
invoice_id = super(stock_picking, self)._create_invoice_from_picking(cr, uid, picking, vals, context=context)
- if picking.move_lines and picking.move_lines[0].purchase_line_id:
- purchase_id = picking.move_lines[0].purchase_line_id.order_id.id
- purchase_line_ids = purchase_line_obj.search(cr, uid, [('order_id', '=', purchase_id), ('product_id.type', '=', 'service'), ('invoiced', '=', False)], context=context)
- if purchase_line_ids:
- inv_lines = []
- for po_line in purchase_line_obj.browse(cr, uid, purchase_line_ids, context=context):
- acc_id = purchase_obj._choose_account_from_po_line(cr, uid, po_line, context=context)
- inv_line_data = purchase_obj._prepare_inv_line(cr, uid, acc_id, po_line, context=context)
- inv_line_id = invoice_line_obj.create(cr, uid, inv_line_data, context=context)
- inv_lines.append(inv_line_id)
- po_line.write({'invoice_lines': [(4, inv_line_id)]})
- invoice_line_obj.write(cr, uid, inv_lines, {'invoice_id': invoice_id}, context=context)
return invoice_id
def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None):
diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py
index 277b3945798..5a8630c9a90 100644
--- a/addons/purchase_requisition/purchase_requisition.py
+++ b/addons/purchase_requisition/purchase_requisition.py
@@ -173,6 +173,7 @@ class purchase_requisition(osv.osv):
'order_id': purchase_id,
'product_id': product.id,
'account_analytic_id': requisition_line.account_analytic_id.id,
+ 'taxes_id': [(6, 0, vals.get('taxes_ids', []))],
})
return vals
diff --git a/addons/rating_project/models/project.py b/addons/rating_project/models/project.py
index d2f08cc46cc..68ebd3420dc 100644
--- a/addons/rating_project/models/project.py
+++ b/addons/rating_project/models/project.py
@@ -52,7 +52,8 @@ class Project(models.Model):
compute='_compute_percentage_satisfaction_task', string='% Happy', store=True, default=-1)
percentage_satisfaction_project = fields.Integer(
compute="_compute_percentage_satisfaction_project", string="% Happy", store=True, default=-1)
- is_visible_happy_customer = fields.Boolean(string="Customer Satisfaction", default=False)
+ is_visible_happy_customer = fields.Boolean(string="Customer Satisfaction", default=False,
+ help="Display informations about rating of the project on kanban and form view. This buttons will only be displayed if at least a rating exists.")
@api.multi
diff --git a/addons/rating_project/views/project_view.xml b/addons/rating_project/views/project_view.xml
index 17d32a97030..dfbacdd1f2f 100644
--- a/addons/rating_project/views/project_view.xml
+++ b/addons/rating_project/views/project_view.xml
@@ -10,7 +10,7 @@
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button name="action_view_task_rating"
- attrs="{'invisible': ['|', ('use_issues','=', False), ('is_visible_happy_customer','=', False)]}"
+ attrs="{'invisible': ['|', '|', ('is_visible_happy_customer', '=', False), ('use_tasks','=', False), ('percentage_satisfaction_task', '=', -1)]}"
class="oe_stat_button oe_percent"
type="object"
icon="fa-smile-o">
@@ -46,9 +46,9 @@
<field name="percentage_satisfaction_project" invisible="1"/>
<field name="is_visible_happy_customer" invisible="1"/>
</field>
- <xpath expr="//div[contains(@class, 'oe_kanban_project_list')]" position="after">
- <div class="pull-right oe_margin_top_16">
- <a t-if="record.is_visible_happy_customer.raw_value and record.percentage_satisfaction_project.raw_value &gt; -1" name="action_view_all_rating" type="object" title="Percentage of happy people about this project">
+ <xpath expr="//div[contains(@class, 'oe_kanban_project_list')]" position="inside">
+ <div class="col-md-12">
+ <a t-if="record.is_visible_happy_customer.raw_value and record.percentage_satisfaction_project.raw_value &gt; -1" name="action_view_all_rating" type="object" title="Percentage of happy people about this project" class="pull-right">
<h5 t-attf-class="label #{record.percentage_satisfaction_project.raw_value &gt;= 50 ? 'label-success' : 'label-warning'}">
<i t-attf-class="fa #{record.percentage_satisfaction_project.raw_value &gt;= 50 ? 'fa-smile-o' : 'fa-frown-o'}"/> <t t-raw="record.percentage_satisfaction_project.raw_value"/> %
</h5>
diff --git a/addons/rating_project_issue/models/project_issue.py b/addons/rating_project_issue/models/project_issue.py
index 339027d74d7..778000e9418 100644
--- a/addons/rating_project_issue/models/project_issue.py
+++ b/addons/rating_project_issue/models/project_issue.py
@@ -28,6 +28,7 @@ class Project(models.Model):
def _compute_percentage_satisfaction_project(self):
super(Project, self)._compute_percentage_satisfaction_project()
Rating = self.env['rating.rating']
+ Issue = self.env['project.issue']
for record in self.filtered(lambda record: record.use_tasks or record.use_issues):
if record.use_tasks or record.use_issues:
# built the domain according the project parameters (use tasks and/or issues)
@@ -37,8 +38,10 @@ class Project(models.Model):
res_models.append('project.task')
domain += ['&', ('res_model', '=', 'project.task'), ('res_id', 'in', record.tasks.ids)]
if record.use_issues:
+ # TODO: if performance issue, compute the satisfaction with a custom request joining rating and task/issue.
+ issues = Issue.search([('project_id', '=', record.id)])
res_models.append('project.issue')
- domain += ['&', ('res_model', '=', 'project.issue'), ('res_id', 'in', record.issue_ids.ids)]
+ domain += ['&', ('res_model', '=', 'project.issue'), ('res_id', 'in', issues.ids)]
if len(res_models) == 2:
domain = ['|'] + domain
domain = ['&', ('rating', '>=', 0)] + domain
diff --git a/addons/rating_project_issue/views/project_issue_view.xml b/addons/rating_project_issue/views/project_issue_view.xml
index 2f44dc0c17a..0f16b3bb460 100644
--- a/addons/rating_project_issue/views/project_issue_view.xml
+++ b/addons/rating_project_issue/views/project_issue_view.xml
@@ -10,7 +10,7 @@
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button name="action_view_issue_rating"
- attrs="{'invisible': ['|', ('use_issues','=', False), ('is_visible_happy_customer','=', False)]}"
+ attrs="{'invisible': ['|', '|', ('use_issues','=', False), ('is_visible_happy_customer','=', False), ('percentage_satisfaction_issue', '=', -1)]}"
class="oe_stat_button oe_percent"
type="object"
icon="fa-smile-o">
diff --git a/addons/report/models/abstract_report.py b/addons/report/models/abstract_report.py
index 08d70e83e20..cccaa339b63 100644
--- a/addons/report/models/abstract_report.py
+++ b/addons/report/models/abstract_report.py
@@ -54,7 +54,9 @@ class AbstractReport(osv.AbstractModel):
# Rendering self._template with the wrapped report instance localcontext as
# rendering environment
- docargs = wrapped_report.localcontext
+ docargs = dict(wrapped_report.localcontext)
+ if not docargs.get('lang'):
+ docargs.pop('lang', False)
docargs['docs'] = docargs.get('objects')
# Used in template translation (see translate_doc method from report model)
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 5926fee5ee7..40b3ffe025f 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -691,7 +691,7 @@ class sale_order(osv.osv):
:return: True
"""
- context = dict(context)
+ context = context or {}
context['lang'] = self.pool['res.users'].browse(cr, uid, uid).lang
procurement_obj = self.pool.get('procurement.order')
sale_line_obj = self.pool.get('sale.order.line')
@@ -715,7 +715,9 @@ class sale_order(osv.osv):
if (line.state == 'done') or not line.product_id:
continue
vals = self._prepare_order_line_procurement(cr, uid, order, line, group_id=order.procurement_group_id.id, context=context)
- proc_id = procurement_obj.create(cr, uid, vals, context=context)
+ ctx = context.copy()
+ ctx['procurement_autorun_defer'] = True
+ proc_id = procurement_obj.create(cr, uid, vals, context=ctx)
proc_ids.append(proc_id)
#Confirm procurement order such that rules will be applied on it
#note that the workflow normally ensure proc_ids isn't an empty list
diff --git a/addons/sale/sale_data.xml b/addons/sale/sale_data.xml
index 74f3f060bd7..f5126a25727 100644
--- a/addons/sale/sale_data.xml
+++ b/addons/sale/sale_data.xml
@@ -40,7 +40,7 @@
<record id="mt_order_confirmed" model="mail.message.subtype">
<field name="name">Sales Order Confirmed</field>
<field name="res_model">sale.order</field>
- <field name="default" eval="True"/>
+ <field name="default" eval="False"/>
<field name="description">Quotation confirmed</field>
</record>
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml
index 246356b42c2..1baa2448c8c 100644
--- a/addons/sale/sale_view.xml
+++ b/addons/sale/sale_view.xml
@@ -517,7 +517,7 @@
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline oe_stat_button" name="action_view_sales"
- type="action" icon="fa-strikethrough">
+ type="object" icon="fa-strikethrough">
<field string="Sales" name="sales_count" widget="statinfo" />
</button>
</xpath>
diff --git a/addons/sale/sales_team.py b/addons/sale/sales_team.py
index 7e071b03c62..1dfc09b72cf 100644
--- a/addons/sale/sales_team.py
+++ b/addons/sale/sales_team.py
@@ -20,7 +20,7 @@ class crm_team(osv.Model):
res = {}
for id in ids:
- created_domain = [('team_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)]
+ created_domain = [('type', 'in', ['out_invoice', 'out_refund']), ('team_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)]
values = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context)
for value in values:
value['value'] = float_repr(value.get('value', 0), precision_digits=self.pool['decimal.precision'].precision_get(cr, uid, 'Account'))
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index 75d36eda30f..cfd6a1c0266 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -375,12 +375,23 @@ class stock_move(osv.osv):
self.pool.get('sale.order').write(cr, uid, [sale_line.order_id.id], {
'invoice_ids': [(4, invoice_line_vals['invoice_id'])],
})
+ sale_line_obj = self.pool.get('sale.order.line')
+ invoice_line_obj = self.pool.get('account.invoice.line')
+ sale_line_ids = sale_line_obj.search(cr, uid, [('order_id', '=', move.procurement_id.sale_line_id.order_id.id), ('product_id.type', '=', 'service'), ('invoiced', '=', False)], context=context)
+ if sale_line_ids:
+ created_lines = sale_line_obj.invoice_line_create(cr, uid, sale_line_ids, context=context)
+ invoice_line_obj.write(cr, uid, created_lines, {'invoice_id': invoice_line_vals['invoice_id']}, context=context)
+
return invoice_line_id
def _get_master_data(self, cr, uid, move, company, context=None):
if move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.order_policy == 'picking':
sale_order = move.procurement_id.sale_line_id.order_id
return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id
+ elif move.picking_id.sale_id:
+ # In case of extra move, it is better to use the same data as the original moves
+ sale_order = move.picking_id.sale_id
+ return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id
return super(stock_move, self)._get_master_data(cr, uid, move, company, context=context)
def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None):
@@ -443,13 +454,6 @@ class stock_picking(osv.osv):
sale_line_obj = self.pool.get('sale.order.line')
invoice_line_obj = self.pool.get('account.invoice.line')
invoice_id = super(stock_picking, self)._create_invoice_from_picking(cr, uid, picking, vals, context=context)
- if picking.group_id:
- sale_ids = sale_obj.search(cr, uid, [('procurement_group_id', '=', picking.group_id.id)], context=context)
- if sale_ids:
- sale_line_ids = sale_line_obj.search(cr, uid, [('order_id', 'in', sale_ids), ('product_id.type', '=', 'service'), ('invoiced', '=', False)], context=context)
- if sale_line_ids:
- created_lines = sale_line_obj.invoice_line_create(cr, uid, sale_line_ids, context=context)
- invoice_line_obj.write(cr, uid, created_lines, {'invoice_id': invoice_id}, context=context)
return invoice_id
def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None):
diff --git a/addons/sale_stock/sale_stock_view.xml b/addons/sale_stock/sale_stock_view.xml
index 68bb5635530..bb3ddda3e88 100644
--- a/addons/sale_stock/sale_stock_view.xml
+++ b/addons/sale_stock/sale_stock_view.xml
@@ -151,9 +151,9 @@
<template id="report_sale_order_incoterm" inherit_id="sale.report_saleorder_document">
<xpath expr="//div[@name='payment_term']" position="after">
- <div class="col-xs-3" t-if="o.incoterm" groups="sale.group_display_incoterm">
+ <div class="col-xs-3" t-if="doc.incoterm" groups="sale.group_display_incoterm">
<strong>Incoterms:</strong>
- <p t-field="o.incoterm.code"/>
+ <p t-field="doc.incoterm.code"/>
</div>
</xpath>
</template>
diff --git a/addons/stock/product.py b/addons/stock/product.py
index 17dbdc17cc2..729f9051846 100644
--- a/addons/stock/product.py
+++ b/addons/stock/product.py
@@ -94,11 +94,29 @@ class product_product(osv.osv):
operator = context.get('compute_child', True) and 'child_of' or 'in'
domain = context.get('force_company', False) and ['&', ('company_id', '=', context['force_company'])] or []
- return (
- domain + [('location_id', operator, location_ids)],
- domain + ['&', ('location_dest_id', operator, location_ids), '!', ('location_id', operator, location_ids)],
- domain + ['&', ('location_id', operator, location_ids), '!', ('location_dest_id', operator, location_ids)]
- )
+ locations = location_obj.browse(cr, uid, location_ids, context=context)
+ if operator == "child_of" and locations and locations[0].parent_left != 0:
+ loc_domain = []
+ dest_loc_domain = []
+ for loc in locations:
+ if loc_domain:
+ loc_domain = ['|'] + loc_domain + ['&', ('location_id.parent_left', '>=', loc.parent_left), ('location_id.parent_left', '<', loc.parent_right)]
+ dest_loc_domain = ['|'] + dest_loc_domain + ['&', ('location_dest_id.parent_left', '>=', loc.parent_left), ('location_dest_id.parent_left', '<', loc.parent_right)]
+ else:
+ loc_domain += ['&', ('location_id.parent_left', '>=', loc.parent_left), ('location_id.parent_left', '<', loc.parent_right)]
+ dest_loc_domain += ['&', ('location_dest_id.parent_left', '>=', loc.parent_left), ('location_dest_id.parent_left', '<', loc.parent_right)]
+
+ return (
+ domain + loc_domain,
+ domain + ['&'] + dest_loc_domain + ['!'] + loc_domain,
+ domain + ['&'] + loc_domain + ['!'] + dest_loc_domain
+ )
+ else:
+ return (
+ domain + [('location_id', operator, location_ids)],
+ domain + ['&', ('location_dest_id', operator, location_ids), '!', ('location_id', operator, location_ids)],
+ domain + ['&', ('location_id', operator, location_ids), '!', ('location_dest_id', operator, location_ids)]
+ )
def _get_domain_dates(self, cr, uid, ids, context):
from_date = context.get('from_date', False)
@@ -115,7 +133,8 @@ class product_product(osv.osv):
field_names = field_names or []
domain_products = [('product_id', 'in', ids)]
- domain_quant, domain_move_in, domain_move_out = self._get_domain_locations(cr, uid, ids, context=context)
+ domain_quant, domain_move_in, domain_move_out = [], [], []
+ domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations(cr, uid, ids, context=context)
domain_move_in += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products
domain_move_out += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products
domain_quant += domain_products
@@ -129,9 +148,12 @@ class product_product(osv.osv):
moves_in = []
moves_out = []
else:
+ domain_move_in += domain_move_in_loc
+ domain_move_out += domain_move_out_loc
moves_in = self.pool.get('stock.move').read_group(cr, uid, domain_move_in, ['product_id', 'product_qty'], ['product_id'], context=context)
moves_out = self.pool.get('stock.move').read_group(cr, uid, domain_move_out, ['product_id', 'product_qty'], ['product_id'], context=context)
+ domain_quant += domain_quant_loc
quants = self.pool.get('stock.quant').read_group(cr, uid, domain_quant, ['product_id', 'qty'], ['product_id'], context=context)
quants = dict(map(lambda x: (x['product_id'][0], x['qty']), quants))
@@ -163,17 +185,33 @@ class product_product(osv.osv):
if operator == '=':
operator = '=='
- product_ids = self.search(cr, uid, [], context=context)
ids = []
- if product_ids:
- #TODO: use a query instead of this browse record which is probably making the too much requests, but don't forget
- #the context that can be set with a location, an owner...
- for element in self.browse(cr, uid, product_ids, context=context):
- if eval(str(element[field]) + operator + str(value)):
- ids.append(element.id)
- res.append(('id', 'in', ids))
+ if name == 'qty_available':
+ res.append(('id', 'in', self._search_qty_available(cr, uid, operator, value, context)))
+ else:
+ product_ids = self.search(cr, uid, [], context=context)
+ if product_ids:
+ #TODO: Still optimization possible when searching virtual quantities
+ for element in self.browse(cr, uid, product_ids, context=context):
+ if eval(str(element[field]) + operator + str(value)):
+ ids.append(element.id)
+ res.append(('id', 'in', ids))
return res
+ def _search_qty_available(self, cr, uid, operator, value, context):
+ domain_quant = []
+ if context.get('lot_id'):
+ domain_quant.append(('lot_id', '=', context['lot_id']))
+ if context.get('owner_id'):
+ domain_quant.append(('owner_id', '=', context['owner_id']))
+ if context.get('package_id'):
+ domain_quant.append(('package_id', '=', context['package_id']))
+ domain_quant += self._get_domain_locations(cr, uid, [], context=context)[0]
+ quants = self.pool.get('stock.quant').read_group(cr, uid, domain_quant, ['product_id', 'qty'], ['product_id'], context=context)
+ quants = dict(map(lambda x: (x['product_id'][0], x['qty']), quants))
+ quants = dict((k, v) for k, v in quants.iteritems() if eval(str(v) + operator + str(value)))
+ return(list(quants))
+
def _product_available_text(self, cr, uid, ids, field_names=None, arg=False, context=None):
res = {}
for product in self.browse(cr, uid, ids, context=context):
@@ -294,41 +332,35 @@ class product_template(osv.osv):
_inherit = 'product.template'
def _product_available(self, cr, uid, ids, name, arg, context=None):
- res = dict.fromkeys(ids, 0)
- for product in self.browse(cr, uid, ids, context=context):
- res[product.id] = {
- # "reception_count": sum([p.reception_count for p in product.product_variant_ids]),
- # "delivery_count": sum([p.delivery_count for p in product.product_variant_ids]),
- "qty_available": sum([p.qty_available for p in product.product_variant_ids]),
- "virtual_available": sum([p.virtual_available for p in product.product_variant_ids]),
- "incoming_qty": sum([p.incoming_qty for p in product.product_variant_ids]),
- "outgoing_qty": sum([p.outgoing_qty for p in product.product_variant_ids]),
+ prod_available = {}
+ product_ids = self.browse(cr, uid, ids, context=context)
+ var_ids = []
+ for product in product_ids:
+ var_ids += [p.id for p in product.product_variant_ids]
+ variant_available= self.pool['product.product']._product_available(cr, uid, var_ids, context=context)
+
+ for product in product_ids:
+ qty_available = 0
+ virtual_available = 0
+ incoming_qty = 0
+ outgoing_qty = 0
+ for p in product.product_variant_ids:
+ qty_available += variant_available[p.id]["qty_available"]
+ virtual_available += variant_available[p.id]["virtual_available"]
+ incoming_qty += variant_available[p.id]["incoming_qty"]
+ outgoing_qty += variant_available[p.id]["outgoing_qty"]
+ prod_available[product.id] = {
+ "qty_available": qty_available,
+ "virtual_available": virtual_available,
+ "incoming_qty": incoming_qty,
+ "outgoing_qty": outgoing_qty,
}
- return res
+ return prod_available
def _search_product_quantity(self, cr, uid, obj, name, domain, context):
prod = self.pool.get("product.product")
- res = []
- for field, operator, value in domain:
- #to prevent sql injections
- assert field in ('qty_available', 'virtual_available', 'incoming_qty', 'outgoing_qty'), 'Invalid domain left operand'
- assert operator in ('<', '>', '=', '!=', '<=', '>='), 'Invalid domain operator'
- assert isinstance(value, (float, int)), 'Invalid domain right operand'
-
- if operator == '=':
- operator = '=='
-
- product_ids = prod.search(cr, uid, [], context=context)
- ids = []
- if product_ids:
- #TODO: use a query instead of this browse record which is probably making the too much requests, but don't forget
- #the context that can be set with a location, an owner...
- for element in prod.browse(cr, uid, product_ids, context=context):
- if eval(str(element[field]) + operator + str(value)):
- ids.append(element.id)
- res.append(('product_variant_ids', 'in', ids))
- return res
-
+ product_variant_ids = prod.search(cr, uid, domain, context=context)
+ return [('product_variant_ids', 'in', product_variant_ids)]
def _product_available_text(self, cr, uid, ids, field_names=None, arg=False, context=None):
res = {}
@@ -372,13 +404,13 @@ class product_template(osv.osv):
# fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'),
# 'delivery_count': fields.function(_product_available, multi='qty_available',
# fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'),
- 'qty_available': fields.function(_product_available, multi='qty_available',
+ 'qty_available': fields.function(_product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'),
fnct_search=_search_product_quantity, type='float', string='Quantity On Hand'),
- 'virtual_available': fields.function(_product_available, multi='qty_available',
+ 'virtual_available': fields.function(_product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'),
fnct_search=_search_product_quantity, type='float', string='Quantity Available'),
- 'incoming_qty': fields.function(_product_available, multi='qty_available',
+ 'incoming_qty': fields.function(_product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'),
fnct_search=_search_product_quantity, type='float', string='Incoming'),
- 'outgoing_qty': fields.function(_product_available, multi='qty_available',
+ 'outgoing_qty': fields.function(_product_available, multi='qty_available', digits_compute=dp.get_precision('Product Unit of Measure'),
fnct_search=_search_product_quantity, type='float', string='Outgoing'),
'route_ids': fields.many2many('stock.location.route', 'stock_route_product', 'product_id', 'route_id', 'Routes', domain="[('product_selectable', '=', True)]",
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 48c1eaec9bb..d2a5265ef13 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -296,7 +296,7 @@ class stock_quant(osv.osv):
_columns = {
'name': fields.function(_get_quant_name, type='char', string='Identifier'),
'product_id': fields.many2one('product.product', 'Product', required=True, ondelete="restrict", readonly=True, select=True),
- 'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="restrict", readonly=True, select=True),
+ 'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="restrict", readonly=True, select=True, auto_join=True),
'qty': fields.float('Quantity', required=True, help="Quantity of products in this quant, in the default unit of measure of the product", readonly=True, select=True),
'product_uom_id': fields.related('product_id', 'uom_id', type='many2one', relation="product.uom", string='Unit of Measure', readonly=True),
'package_id': fields.many2one('stock.quant.package', string='Package', help="The package containing this quant", readonly=True, select=True),
@@ -1770,8 +1770,10 @@ class stock_move(osv.osv):
'product_packaging': fields.many2one('product.packaging', 'Prefered Packaging', help="It specifies attributes of packaging like type, quantity of packaging,etc."),
- 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True, states={'done': [('readonly', True)]}, help="Sets a location if you produce at a fixed location. This can be a partner location if you subcontract the manufacturing operations."),
- 'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True, states={'done': [('readonly', True)]}, select=True, help="Location where the system will stock the finished products."),
+ 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True, auto_join=True,
+ states={'done': [('readonly', True)]}, help="Sets a location if you produce at a fixed location. This can be a partner location if you subcontract the manufacturing operations."),
+ 'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True, states={'done': [('readonly', True)]}, select=True,
+ auto_join=True, help="Location where the system will stock the finished products."),
'partner_id': fields.many2one('res.partner', 'Destination Address ', states={'done': [('readonly', True)]}, help="Optional address where goods are to be delivered, specifically used for allotment"),
@@ -2530,6 +2532,7 @@ class stock_move(osv.osv):
'split_from': move.id,
'procurement_id': move.procurement_id.id,
'move_dest_id': move.move_dest_id.id,
+ 'origin_returned_move_id': move.origin_returned_move_id.id,
}
if context.get('source_location_id'):
defaults['location_id'] = context['source_location_id']
@@ -2586,7 +2589,7 @@ class stock_inventory(osv.osv):
:rtype: list of tuple
"""
#default available choices
- res_filter = [('none', _('All products')), ('product', _('One product only'))]
+ res_filter = [('none', _('All products')), ('partial', _('Manual Selection of Products')), ('product', _('One product only'))]
settings_obj = self.pool.get('stock.config.settings')
config_ids = settings_obj.search(cr, uid, [], limit=1, order='id DESC', context=context)
#If we don't have updated config until now, all fields are by default false and so should be not dipslayed
@@ -2629,7 +2632,10 @@ class stock_inventory(osv.osv):
'partner_id': fields.many2one('res.partner', 'Inventoried Owner', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Owner to focus your inventory on a particular Owner."),
'lot_id': fields.many2one('stock.production.lot', 'Inventoried Lot/Serial Number', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Lot/Serial Number to focus your inventory on a particular Lot/Serial Number.", copy=False),
'move_ids_exist': fields.function(_get_move_ids_exist, type='boolean', string=' Stock Move Exists?', help='technical field for attrs in view'),
- 'filter': fields.selection(_get_available_filters, 'Selection Filter', required=True),
+ 'filter': fields.selection(_get_available_filters, 'Inventory of', required=True,
+ help="If you do an entire inventory, you can choose 'All Products' and it will prefill the inventory with the current stock. If you only do some products "\
+ "(e.g. Cycle Counting) you can choose 'Manual Selection of Products' and the system won't propose anything. You can also let the "\
+ "system propose for a single product / lot /... "),
'total_qty': fields.function(_get_total_qty, type="float"),
}
@@ -2741,25 +2747,11 @@ class stock_inventory(osv.osv):
for inventory in self.browse(cr, uid, ids, context=context):
# If there are inventory lines already (e.g. from import), respect those and set their theoretical qty
line_ids = [line.id for line in inventory.line_ids]
- if not line_ids:
+ if not line_ids and inventory.filter != 'partial':
#compute the inventory lines and create them
vals = self._get_inventory_lines(cr, uid, inventory, context=context)
for product_line in vals:
inventory_line_obj.create(cr, uid, product_line, context=context)
- else:
- # On import calculate theoretical quantity
- quant_obj = self.pool.get("stock.quant")
- for line in inventory.line_ids:
- dom = [('company_id', '=', line.company_id.id), ('location_id', 'child_of', line.location_id.id), ('lot_id', '=', line.prod_lot_id.id),
- ('product_id','=', line.product_id.id), ('owner_id', '=', line.partner_id.id)]
- if line.package_id:
- dom += [('package_id', '=', line.package_id.id)]
- quants = quant_obj.search(cr, uid, dom, context=context)
- tot_qty = 0
- for quant in quant_obj.browse(cr, uid, quants, context=context):
- tot_qty += quant.qty
- inventory_line_obj.write(cr, uid, [line.id], {'theoretical_qty': tot_qty}, context=context)
-
return self.write(cr, uid, ids, {'state': 'confirm', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
def _get_inventory_lines(self, cr, uid, inventory, context=None):
@@ -2815,6 +2807,19 @@ class stock_inventory_line(osv.osv):
def _get_prodlot_change(self, cr, uid, ids, context=None):
return self.pool.get('stock.inventory.line').search(cr, uid, [('prod_lot_id', 'in', ids)], context=context)
+ def _get_theoretical_qty(self, cr, uid, ids, name, args, context=None):
+ res = {}
+ quant_obj = self.pool["stock.quant"]
+ uom_obj = self.pool["product.uom"]
+ for line in self.browse(cr, uid, ids, context=context):
+ quant_ids = self._get_quants(cr, uid, line, context=context)
+ quants = quant_obj.browse(cr, uid, quant_ids, context=context)
+ tot_qty = sum([x.qty for x in quants])
+ if line.product_uom_id and line.product_id.uom_id.id != line.product_uom_id.id:
+ tot_qty = uom_obj._compute_qty_obj(cr, uid, line.product_id.uom_id, tot_qty, line.product_uom_id, context=context)
+ res[line.id] = tot_qty
+ return res
+
_columns = {
'inventory_id': fields.many2one('stock.inventory', 'Inventory', ondelete='cascade', select=True),
'location_id': fields.many2one('stock.location', 'Location', required=True, select=True),
@@ -2825,7 +2830,8 @@ class stock_inventory_line(osv.osv):
'company_id': fields.related('inventory_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, select=True, readonly=True),
'prod_lot_id': fields.many2one('stock.production.lot', 'Serial Number', domain="[('product_id','=',product_id)]"),
'state': fields.related('inventory_id', 'state', type='char', string='Status', readonly=True),
- 'theoretical_qty': fields.float('Theoretical Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), readonly=True),
+ 'theoretical_qty': fields.function(_get_theoretical_qty, type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
+ store=True, readonly=True, string="Theoretical Quantity"),
'partner_id': fields.many2one('res.partner', 'Owner'),
'product_name': fields.related('product_id', 'name', type='char', string='Product Name', store={
'product.product': (_get_product_name_change, ['name', 'default_code'], 20),
@@ -2842,7 +2848,7 @@ class stock_inventory_line(osv.osv):
}
_defaults = {
- 'product_qty': 1,
+ 'product_qty': 0,
}
def create(self, cr, uid, values, context=None):
@@ -2853,6 +2859,37 @@ class stock_inventory_line(osv.osv):
values['product_uom_id'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
return super(stock_inventory_line, self).create(cr, uid, values, context=context)
+ def _get_quants(self, cr, uid, line, context=None):
+ quant_obj = self.pool["stock.quant"]
+ dom = [('company_id', '=', line.company_id.id), ('location_id', 'child_of', line.location_id.id), ('lot_id', '=', line.prod_lot_id.id),
+ ('product_id','=', line.product_id.id), ('owner_id', '=', line.partner_id.id), ('package_id', '=', line.package_id.id)]
+ quants = quant_obj.search(cr, uid, dom, context=context)
+ return quants
+
+ def onchange_createline(self, cr, uid, ids, location_id=False, product_id=False, uom_id=False, package_id=False, prod_lot_id=False, partner_id=False, company_id=False, context=None):
+ quant_obj = self.pool["stock.quant"]
+ uom_obj = self.pool["product.uom"]
+ res = {'value': {}}
+ # If no UoM already put the default UoM of the product
+ if product_id and not uom_id:
+ product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+ res['value']['product_uom_id'] = product.uom_id.id
+ res['domain'] = {'product_uom_id': [('category_id','=',product.uom_id.category_id.id)]}
+ uom_id = product.uom_id.id
+ # Calculate theoretical quantity by searching the quants as in quants_get
+ if product_id and location_id:
+ product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+ if not company_id:
+ company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
+ dom = [('company_id', '=', company_id), ('location_id', 'child_of', location_id), ('lot_id', '=', prod_lot_id),
+ ('product_id','=', product_id), ('owner_id', '=', partner_id), ('package_id', '=', package_id)]
+ quants = quant_obj.search(cr, uid, dom, context=context)
+ th_qty = sum([x.qty for x in quant_obj.browse(cr, uid, quants, context=context)])
+ if product_id and uom_id and product.uom_id.id != uom_id:
+ th_qty = uom_obj._compute_qty(cr, uid, product.uom_id.id, th_qty, uom_id)
+ res['value']['theoretical_qty'] = th_qty
+ return res
+
def _resolve_inventory_line(self, cr, uid, inventory_line, context=None):
stock_move_obj = self.pool.get('stock.move')
diff = inventory_line.theoretical_qty - inventory_line.product_qty
@@ -2883,26 +2920,11 @@ class stock_inventory_line(osv.osv):
vals['product_uom_qty'] = diff
return stock_move_obj.create(cr, uid, vals, context=context)
+ # Should be left out in next version
def restrict_change(self, cr, uid, ids, theoretical_qty, context=None):
- if ids and theoretical_qty:
- #if the user try to modify a line prepared by openerp, reject the change and display an error message explaining how he should do
- old_value = self.browse(cr, uid, ids[0], context=context)
- return {
- 'value': {
- 'product_id': old_value.product_id.id,
- 'product_uom_id': old_value.product_uom_id.id,
- 'location_id': old_value.location_id.id,
- 'prod_lot_id': old_value.prod_lot_id.id,
- 'package_id': old_value.package_id.id,
- 'partner_id': old_value.partner_id.id,
- },
- 'warning': {
- 'title': _('Error'),
- 'message': _('You can only change the checked quantity of an existing inventory line. If you want modify a data, please set the checked quantity to 0 and create a new inventory line.')
- }
- }
return {}
+ # Should be left out in next version
def on_change_product_id(self, cr, uid, ids, product, uom, theoretical_qty, context=None):
""" Changes UoM
@param location_id: Location id
@@ -2910,8 +2932,6 @@ class stock_inventory_line(osv.osv):
@param uom: UoM product
@return: Dictionary of changed values
"""
- if ids and theoretical_qty:
- return self.restrict_change(cr, uid, ids, theoretical_qty, context=context)
if not product:
return {'value': {'product_uom_id': False}}
obj_product = self.pool.get('product.product').browse(cr, uid, product, context=context)
diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml
index 6c1de13baa9..43f5463dc8c 100644
--- a/addons/stock/stock_view.xml
+++ b/addons/stock/stock_view.xml
@@ -58,7 +58,7 @@
</field>
</record>
- <record id="view_inventory_filter" model="ir.ui.view">
+ <record id="view_inventory_filter" model="ir.ui.view">
<field name="name">stock.inventory.filter</field>
<field name="model">stock.inventory</field>
<field name="arch" type="xml">
@@ -112,10 +112,10 @@
<group>
<field name="date"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
- <field name="product_id" domain="[('type','=','product')]" attrs="{'invisible': [('filter', 'not in', ('product', 'product_owner'))]}"/>
- <field name="lot_id" attrs="{'invisible': [('filter', '!=', 'lot')]}" groups="stock.group_production_lot" />
- <field name="partner_id" attrs="{'invisible': [('filter', 'not in', ('owner', 'product_owner'))]}" groups="stock.group_tracking_owner"/>
- <field name="package_id" attrs="{'invisible': [('filter', '!=', 'pack')]}" groups="stock.group_tracking_lot"/>
+ <field name="product_id" domain="[('type','=','product')]" attrs="{'invisible': [('filter', 'not in', ('product', 'product_owner'))], 'required': [('filter', 'in', ('product', 'product_owner'))]}"/>
+ <field name="lot_id" attrs="{'invisible': [('filter', '!=', 'lot')], 'required': [('filter', '=', 'lot')]}" groups="stock.group_production_lot" />
+ <field name="partner_id" attrs="{'invisible': [('filter', 'not in', ('owner', 'product_owner'))], 'required': [('filter', 'in', ('owner', 'product_owner'))]}" groups="stock.group_tracking_owner"/>
+ <field name="package_id" attrs="{'invisible': [('filter', '!=', 'pack')], 'required': [('filter', '=', 'pack')]}" groups="stock.group_tracking_lot"/>
</group>
</group>
<notebook attrs="{'invisible':[('state','=','draft')]}">
@@ -123,12 +123,12 @@
<button name="reset_real_qty" states="confirm" string="⇒ Set quantities to 0" type="object" class="oe_link oe_right" groups="stock.group_stock_user"/>
<field name="line_ids" string="Inventory Details" context="{'default_location_id': location_id, 'default_product_id': product_id, 'default_prod_lot_id': lot_id, 'default_package_id': package_id, 'default_partner_id': partner_id}">
<tree string="Inventory Details" editable="bottom" colors="blue: product_qty != theoretical_qty; red: theoretical_qty &lt; 0">
- <field context="{'location':location_id, 'uom':product_uom_id, 'to_date':parent.date}" name="product_id" on_change="on_change_product_id(product_id,product_uom_id,theoretical_qty,context)" domain="[('type','=','product')]"/>
- <field name="product_uom_id" groups="product.group_uom" on_change="restrict_change(theoretical_qty)"/>
- <field domain="[('usage','=','internal')]" name="location_id" groups="stock.group_locations" on_change="restrict_change(theoretical_qty)"/>
- <field name="prod_lot_id" on_change="restrict_change(theoretical_qty)" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot"/>
- <field name="package_id" on_change="restrict_change(theoretical_qty)" groups="stock.group_tracking_lot"/>
- <field name="partner_id" on_change="restrict_change(theoretical_qty)" groups="stock.group_tracking_owner"/>
+ <field name="product_id" domain="[('type','=','product')]" on_change="onchange_createline(location_id, product_id, product_uom_id, package_id, prod_lot_id, partner_id)"/>
+ <field name="product_uom_id" groups="product.group_uom" string="UoM" on_change="onchange_createline(location_id, product_id, product_uom_id, package_id, prod_lot_id, partner_id)"/>
+ <field name="location_id" domain="[('id', 'child_of', parent.location_id)]" groups="stock.group_locations" on_change="onchange_createline(location_id, product_id, product_uom_id, package_id, prod_lot_id, partner_id)"/>
+ <field name="prod_lot_id" on_change="onchange_createline(location_id, product_id, product_uom_id, package_id, prod_lot_id, partner_id)" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot"/>
+ <field name="package_id" domain="['|', ('location_id','=', False), ('location_id', '=', location_id)]" on_change="onchange_createline(location_id, product_id, product_uom_id, package_id, prod_lot_id, partner_id)" groups="stock.group_tracking_lot"/>
+ <field name="partner_id" on_change="onchange_createline(location_id, product_id, product_uom_id, package_id, prod_lot_id, partner_id)" groups="stock.group_tracking_owner"/>
<field name="theoretical_qty" readonly="1"/>
<field name="product_qty" string="Real Quantity"/>
<field name="state" invisible="True"/>
diff --git a/addons/stock/wizard/stock_return_picking.py b/addons/stock/wizard/stock_return_picking.py
index c72a4ccce86..24440192dde 100644
--- a/addons/stock/wizard/stock_return_picking.py
+++ b/addons/stock/wizard/stock_return_picking.py
@@ -133,6 +133,12 @@ class stock_return_picking(osv.osv_memory):
raise UserError(_("You have manually created product lines, please delete them to proceed"))
new_qty = data_get.quantity
if new_qty:
+ # The return of a return should be linked with the original's destination move if it was not cancelled
+ if move.origin_returned_move_id.move_dest_id.id and move.origin_returned_move_id.move_dest_id.state != 'cancel':
+ move_dest_id = move.origin_returned_move_id.move_dest_id.id
+ else:
+ move_dest_id = False
+
returned_lines += 1
move_obj.copy(cr, uid, move.id, {
'product_id': data_get.product_id.id,
@@ -145,6 +151,7 @@ class stock_return_picking(osv.osv_memory):
'origin_returned_move_id': move.id,
'procure_method': 'make_to_stock',
'restrict_lot_id': data_get.lot_id.id,
+ 'move_dest_id': move_dest_id,
})
if not returned_lines:
diff --git a/addons/warning/warning.py b/addons/warning/warning.py
index f9b19839b20..e888144a2a2 100644
--- a/addons/warning/warning.py
+++ b/addons/warning/warning.py
@@ -172,10 +172,7 @@ class stock_picking(osv.osv):
if partner.picking_warn == 'block':
return {'value': {'partner_id': False}, 'warning': warning}
- result = {}
- if result.get('warning',False):
- warning['title'] = title and title +' & '+ result['warning']['title'] or result['warning']['title']
- warning['message'] = message and message + ' ' + result['warning']['message'] or result['warning']['message']
+ result = {'value': {}}
if warning:
result['warning'] = warning
diff --git a/addons/warning/warning_view.xml b/addons/warning/warning_view.xml
index 735758cb9ab..06314a37582 100644
--- a/addons/warning/warning_view.xml
+++ b/addons/warning/warning_view.xml
@@ -59,15 +59,17 @@
</notebook>
</field>
</record>
- <record id="stock_picking_form_view_inherit" model="ir.ui.view">
- <field name="name">stock.picking.form.inherit</field>
+
+ <record id="view_picking_form" model="ir.ui.view">
+ <field name="name">stock.picking.form.warning</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
- <xpath expr="//sheet/group/group/field[@name='partner_id']" position="replace">
- <field name="partner_id" on_change="onchange_partner_id(partner_id)" />
- </xpath>
+ <field name="partner_id" position="attributes">
+ <attribute name="on_change">onchange_partner_id(partner_id)</attribute>
+ </field>
</field>
</record>
+
</data>
</openerp>
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index bf13ccdb012..bc9edd3c5a7 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -35,7 +35,7 @@
border: 0;
}
-.oe_indicator{
+.oe_indicator {
z-index: 99999;
position: absolute;
top: 10px;
@@ -541,7 +541,7 @@
background-image: -webkit-radial-gradient(circle, #eee 0%, #ccc 40%, #bbb 100%);
background-image: -moz-radial-gradient(#eee 0%, #ccc 40%, #bbb 100%);
background-image: -ms-radial-gradient(#eee 0%, #ccc 40%, #bbb 100%);
- background-image: radial-gradient(circle, #eee 0%, #ccc 40%, #bbb 100%);
+ background-image: radial-gradient(circle, #eeeeee 0%, #cccccc 40%, #bbbbbb 100%);
}
.openerp .oe_kanban_status_green {
background: green;
@@ -1092,7 +1092,8 @@
}
.openerp .oe-view-manager-header .oe-view-manager-sidebar .oe_sidebar_delete_item {
padding: 0;
- display: inline-block;
+ position: absolute;
+ right: 10px;
}
.openerp .oe-view-manager-header .oe-view-manager-sidebar .dropdown-menu li a {
width: 100%;
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index 368fc53e5fb..9659cd5e483 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -146,16 +146,16 @@ $sheet-padding: 16px
// Notifications {{{
.oe_indicator
- z-index: 99999
- position: absolute
- top: 10px
- display: inline-block
- color: white
- background: #a24689
- padding: 6px 16px
- border-radius: 4px
- box-shadow: 0 0 1px black
- left: 40%
+ z-index: 99999
+ position: absolute
+ top: 10px
+ display: inline-block
+ color: white
+ background: #a24689
+ padding: 6px 16px
+ border-radius: 4px
+ box-shadow: 0 0 1px black
+ left: 40%
.oe_notification
z-index: 1600
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index b47a5b2c929..89af8bd34fb 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -2427,6 +2427,7 @@ instance.web.form.KanbanSelection = instance.web.form.FieldChar.extend({
}
if (stage_data && stage_data[0][self.options.states_legend[res[0]]]) {
value['state_name'] = stage_data[0][self.options.states_legend[res[0]]];
+ value['tooltip'] = stage_data[0][self.options.states_legend[res[0]]];
}
if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
@@ -2438,7 +2439,6 @@ instance.web.form.KanbanSelection = instance.web.form.FieldChar.extend({
},
render_value: function() {
var self = this;
- this.record_id = this.view.datarecord.id;
var dd_fetched = this.prepare_dropdown_selection();;
return $.when(dd_fetched).then(function (states) {
self.states = states;
@@ -2459,7 +2459,7 @@ instance.web.form.KanbanSelection = instance.web.form.FieldChar.extend({
write_values[self.name] = value;
return this.view.dataset._model.call(
'write', [
- [self.record_id],
+ [this.view.datarecord.id],
write_values,
self.view.dataset.get_context()
]).done(self.reload_record.bind(self));
@@ -2497,10 +2497,11 @@ instance.web.form.Priority = instance.web.form.FieldChar.extend({
},
render_value: function() {
var self = this;
- this.record_id = this.view.datarecord.id;
this.priorities = this.prepare_priority();
this.$el.html(QWeb.render("Priority", {'widget': this}));
- this.$el.find('li').on('click', this.set_priority.bind(this));
+ if (!this.get('readonly')){
+ this.$el.find('li').on('click', this.set_priority.bind(this));
+ }
},
/* setting the value: in view mode, perform an asynchronous call and reload
the form view; in edit mode, use set_value to save the new value that will
@@ -2515,7 +2516,7 @@ instance.web.form.Priority = instance.web.form.FieldChar.extend({
write_values[self.name] = value;
return this.view.dataset._model.call(
'write', [
- [self.record_id],
+ [this.view.datarecord.id],
write_values,
self.view.dataset.get_context()
]).done(self.reload_record.bind(self));
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index d42d8ecb099..dcd6ae77117 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -404,7 +404,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
var total = dataset.size();
var limit = this.limit() || total;
- this.$pager.find('.oe-pager-button').toggle(total > limit);
+ this.$pager.find('.oe-pager-buttons').toggle(total > limit);
this.$pager.find('.oe_pager_value').toggle(total !== 0);
var spager = '-';
if (total) {
@@ -1301,7 +1301,11 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
var $prev = $('<button type="button" data-pager-action="previous">&lt;</button>')
.click(function (e) {
e.stopPropagation();
- self.page -= 1;
+ if (self.page > 0) {
+ self.page -= 1;
+ } else {
+ self.page = self.max_page_index;
+ }
self.$row.closest('tbody').next()
.replaceWith(self.render());
@@ -1309,7 +1313,11 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
var $next = $('<button type="button" data-pager-action="next">&gt;</button>')
.click(function (e) {
e.stopPropagation();
- self.page += 1;
+ if (self.page < self.max_page_index) {
+ self.page += 1;
+ } else {
+ self.page = 0;
+ }
self.$row.closest('tbody').next()
.replaceWith(self.render());
@@ -1505,19 +1513,19 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
self.$row.find('td.oe_list_group_pagination').find('button').remove();
self.$row.find('td.oe_list_group_pagination').find('span').remove();
} else {
- var pages = Math.ceil(dataset.size() / limit);
+ self.max_page_index = Math.ceil(dataset.size() / limit) - 1;
self.$row
.find('.oe_list_pager_state')
.text(_.str.sprintf(_t("%(page)d/%(page_count)d"), {
page: page + 1,
- page_count: pages
+ page_count: self.max_page_index + 1
}))
.end()
.find('button[data-pager-action=previous]')
.toggleClass('disabled', page === 0)
.end()
.find('button[data-pager-action=next]')
- .toggleClass('disabled', page === pages - 1);
+ .toggleClass('disabled', page === self.max_page_index);
}
}
@@ -1608,7 +1616,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
.pluck('name').value(),
function (groups) {
// page count is irrelevant on grouped page, replace by limit
- self.view.$pager.find('.oe_pager_group').hide();
+ self.view.$pager.find('.oe-pager-buttons').hide();
self.view.$pager.find('.oe_list_pager_state').text(self.view._limit ? self.view._limit : '∞');
$el[0].appendChild(
self.render_groups(groups));
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index e97e0da19e2..6585266bfac 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -94,7 +94,7 @@
<tr>
<td><label for="db_name">Select a database name:</label></td>
<td>
- <input type="text" name="db_name" class="required" matches="^[a-zA-Z0-9][a-zA-Z0-9_-]+$" autofocus="true" placeholder="e.g. mycompany"/>
+ <input type="text" name="db_name" class="required" matches="^[a-zA-Z0-9][a-zA-Z0-9_-]+$" autofocus="true" placeholder="e.g. mycompany"/>
</td>
</tr>
<tr>
@@ -484,7 +484,7 @@
<div class="oe-view-manager-buttons">
<t t-foreach="widget.views" t-as="view">
<div t-attf-class="oe-#{view}-buttons"/>
- </t>
+ </t>
</div>
<div class="oe-view-manager-sidebar"></div>
</div>
@@ -527,8 +527,8 @@
<li><a data-action="translate">Technical Translation</a></li>
<li><a data-action="manage_views">Manage Views</a></li>
<li><a data-action="edit" data-model="ir.ui.view" t-att-data-id="view.fields_view.view_id">Edit <t t-esc="_.str.capitalize(view.fields_view.type)"/> View</a></li>
- <li t-if="view_manager.searchview"><a data-action="edit" data-model="ir.ui.view" t-att-data-id="view_manager.searchview.view_id">Edit SearchView</a></li>
- <li t-if="view_manager.action"><a data-action="edit" t-att-data-model="view_manager.action.type" t-att-data-id="view_manager.action.id">Edit Action</a></li>
+ <li t-if="view_manager.searchview"><a data-action="edit" data-model="ir.ui.view" t-att-data-id="view_manager.searchview.view_id">Edit SearchView</a></li>
+ <li t-if="view_manager.action"><a data-action="edit" t-att-data-model="view_manager.action.type" t-att-data-id="view_manager.action.id">Edit Action</a></li>
<li><a data-action="edit_workflow">Edit Workflow</a></li>
<li><a data-action="print_workflow">Print Workflow</a></li>
<li class="divider"/>
@@ -579,7 +579,7 @@
<div t-name="Sidebar" class="oe_sidebar btn-group">
<t t-foreach="widget.sections" t-as="section">
<div class="oe_form_dropdown_section btn-group btn-group-sm">
- <button class="btn btn-default dropdown-toggle"
+ <button class="btn btn-default dropdown-toggle"
t-if="section.name != 'buttons'"
data-toggle="dropdown">
<t t-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/>
@@ -612,8 +612,10 @@
</t>
<a class="oe_file_attachment" t-att-title="item.title or ''" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url">
<t t-raw="item.label"/>
+ <span t-if="section.name == 'files' and !item.callback" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">
+ <i class="fa fa-trash-o"/>
+ </span>
</a>
- <a t-if="section.name == 'files' and !item.callback" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a>
</li>
<li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
<t t-call="HiddenInputFile">
@@ -792,8 +794,8 @@
<div t-name="FormView.buttons">
<t t-if="widget.options.action_buttons !== false">
<div class="oe_form_buttons_view">
- <button t-if="widget.is_action_enabled('edit')"
- type="button"
+ <button t-if="widget.is_action_enabled('edit')"
+ type="button"
class="oe_form_button_edit btn btn-default btn-sm" accesskey="E">
Edit
</button>
@@ -918,7 +920,7 @@
<button class="btn btn-default fa fa-line-chart oe-line-mode" title="Line Chart"></button>
<button class="btn btn-default fa fa-pie-chart oe-pie-mode" title="Pie Chart"></button>
</div>
- <div class="btn-group btn-group-sm">
+ <div class="btn-group btn-group-sm">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
Measure <span class="caret"/>
</button>
@@ -950,7 +952,7 @@
<button class="btn btn-default fa fa-expand oe-pivot-flip" title="Flip axis"></button>
<button class="btn btn-default fa fa-arrows-alt oe-pivot-expand-all" title="Expand all"></button>
</div>
- <div class="btn-group btn-group-sm">
+ <div class="btn-group btn-group-sm">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
Measures <span class="caret"/>
</button>
@@ -1107,9 +1109,10 @@
<ul style="list-style: none; padding-left: 2px; display: inline-block;">
<t t-foreach="widget.priorities" t-as="rec" >
<li t-att-data-value="rec.click_value" style="display: inline-block;">
- <a href="#" t-att-title="rec.name">
+ <a t-if="!widget.get('readonly')" href="#" t-att-title="rec.name">
<i t-att-class="widget.get('value') gte rec.value and 'fa fa-star oe_star_on' or 'fa fa-star oe_star_off'"/>
</a>
+ <span t-if="widget.get('readonly')" t-att-title="rec.name" t-att-class="widget.get('value') gte rec.value and 'oe_e oe_star_on' or 'oe_e oe_star_off'">7</span>
</li>
</t>
</ul>
@@ -1148,7 +1151,7 @@
></textarea><img class="oe_field_translate oe_input_icon"
t-if="widget.field.translate and !widget.get('effective_readonly')"
t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"
- />
+ />
</t>
<t t-if="widget.get('effective_readonly')">
<span class="oe_form_text_content"></span>
@@ -1476,7 +1479,7 @@
<div class="oe_placeholder_files"/>
<div class="oe_add" t-if="!widget.get('effective_readonly')">
<!-- uploader of file -->
- <button class="oe_attach"><i class="fa fa-paperclip"/></button>
+ <button class="oe_attach"><i class="fa fa-paperclip"/></button>
<span class='oe_attach_label'><t t-esc="widget.string"/></span>
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js
index d2bbb9045de..4ad9c893097 100644
--- a/addons/web_kanban/static/src/js/kanban.js
+++ b/addons/web_kanban/static/src/js/kanban.js
@@ -1332,8 +1332,14 @@ instance.web_kanban.Priority = instance.web_kanban.AbstractField.extend({
var self = this;
this.record_id = self.parent.id;
this.priorities = self.prepare_priority();
+ var readonly = this.field && this.field.readonly;
+ if (readonly){
+ this.set('readonly', true);
+ }
this.$el = $(QWeb.render("Priority", {'widget': this}));
- this.$el.find('li').click(self.do_action.bind(self));
+ if (!readonly){
+ this.$el.find('li').click(self.do_action.bind(self));
+ }
},
do_action: function(e) {
var self = this;
@@ -1367,6 +1373,7 @@ instance.web_kanban.KanbanSelection = instance.web_kanban.AbstractField.extend({
var leg_opt = self.options && self.options.states_legend || null;
if (leg_opt && leg_opt[res[0]] && self.parent.group.values && self.parent.group.values[leg_opt[res[0]]]) {
value['state_name'] = self.parent.group.values[leg_opt[res[0]]];
+ value['tooltip'] = self.parent.group.values[leg_opt[res[0]]];
}
if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
diff --git a/addons/web_tests/tests/test_ui_load.js b/addons/web_tests/tests/test_ui_load.js
index 9d76250d239..8daf8e876e0 100644
--- a/addons/web_tests/tests/test_ui_load.js
+++ b/addons/web_tests/tests/test_ui_load.js
@@ -1,4 +1,5 @@
// Load helper
-phantom.injectJs(phantom.args[0]);
+var system = require('system');
+phantom.injectJs(system.args[1]);
pt = new PhantomTest();
pt.run("/", "console.log('ok')", "console");
diff --git a/addons/website/models/res_config.py b/addons/website/models/res_config.py
index bd6f2e9cbe5..940b9a558e1 100644
--- a/addons/website/models/res_config.py
+++ b/addons/website/models/res_config.py
@@ -27,6 +27,8 @@ class website_config_settings(osv.osv_memory):
}
def on_change_website_id(self, cr, uid, ids, website_id, context=None):
+ if not website_id:
+ return {'value': {}}
website_data = self.pool.get('website').read(cr, uid, [website_id], [], context=context)[0]
values = {'website_name': website_data['name']}
for fname, v in website_data.items():
diff --git a/addons/website/static/src/js/website.backend.js b/addons/website/static/src/js/website.backend.js
index 158ad12c029..2345ca0fd3a 100644
--- a/addons/website/static/src/js/website.backend.js
+++ b/addons/website/static/src/js/website.backend.js
@@ -46,15 +46,9 @@ instance.web.form.FieldTextHtml = widget.extend({
// init resize
this.resize = function resize() {
+ if (self.get('effective_readonly')) { return; }
if ($("body").hasClass("o_form_FieldTextHtml_fullscreen")) {
self.$iframe.css('height', $("body").hasClass('o_form_FieldTextHtml_fullscreen') ? (document.body.clientHeight - self.$iframe.offset().top) + 'px' : '');
- } else if (self.get('effective_readonly')) {
- var height = 110;
- try {
- height += self.$body.height();
- height += parseInt(self.$content.parent().css("margin-top"));
- } catch (e) {}
- self.$iframe.css("height", height+"px");
} else {
self.$iframe.css("height", (self.$body.find("#oe_snippets").length ? 500 : 300) + "px");
}
@@ -93,15 +87,17 @@ instance.web.form.FieldTextHtml = widget.extend({
return src;
},
initialize_content: function() {
- this.$iframe = this.$el.find('iframe');
- this.document = null;
- this.$body = $();
- this.$content = $();
- this.dirty = false;
- this.editor = false;
- window.openerp[this.callback+"_set_value"] = function () {};
+ if (!this.get("effective_readonly")) {
+ this.$iframe = this.$el.find('iframe');
+ this.document = null;
+ this.$body = $();
+ this.$content = $();
+ this.dirty = false;
+ this.editor = false;
+ window.openerp[this.callback+"_set_value"] = function () {};
- this.$iframe.attr("src", this.get_url());
+ this.$iframe.attr("src", this.get_url());
+ }
},
on_content_loaded: function () {
var self = this;
@@ -134,12 +130,16 @@ instance.web.form.FieldTextHtml = widget.extend({
}, 500);
},
render_value: function() {
- if (!this.$content) {
- return;
- }
- if(window.openerp[this.callback+"_set_value"]) {
- window.openerp[this.callback+"_set_value"](this.get('value') || '', this.view.get_fields_values());
- this.resize();
+ if (!this.get("effective_readonly")) {
+ if (!this.$content) {
+ return;
+ }
+ if(window.openerp[this.callback+"_set_value"]) {
+ window.openerp[this.callback+"_set_value"](this.get('value') || '', this.view.get_fields_values());
+ this.resize();
+ }
+ } else {
+ this.$el.html(this.get('value'));
}
},
is_false: function() {
@@ -160,4 +160,4 @@ instance.web.form.FieldTextHtml = widget.extend({
}
});
-}; \ No newline at end of file
+};
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 07b7a383c53..5eddecb3227 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -383,26 +383,31 @@
their performance.
</p>
</div>
- <ul class="list-inline js_language_selector mt16" t-if="(request.website_multilang and len(languages) &gt; 1) or editable">
- <li t-foreach="languages" t-as="lg">
- <a t-att-href="url_for(request.httprequest.path + '?' + keep_query(), lang=lg[0])"
- t-att-data-default-lang="editable and 'true' if lg[0] == website.default_lang_code else None">
- <t t-esc="lg[1].split('/').pop()"/></a>
- </li>
- <li groups="base.group_website_publisher">
- <t t-set="url_return" t-value="url_for('', '[lang]') + '?' + keep_query()"/>
- <a t-attf-href="/web#action=base.action_view_base_language_install&amp;website_id=#{website.id}&amp;url_return=#{url_return}">
- <i class="fa fa-plus-circle"/>
- Add a language...
- </a>
- </li>
- </ul>
+ <t t-call="website.language_selector"/>
</div>
</div>
</div>
</xpath>
</template>
+<template id="language_selector">
+ <ul class="list-inline js_language_selector mt16" t-if="(request and request.website_multilang and len(languages) &gt; 1) or (website and editable)">
+ <li t-foreach="languages" t-as="lg">
+ <a t-att-href="url_for(request.httprequest.path + '?' + keep_query(), lang=lg[0])"
+ t-att-data-default-lang="editable and 'true' if website and lg[0] == website.default_lang_code else None">
+ <t t-esc="lg[1].split('/').pop()"/>
+ </a>
+ </li>
+ <li groups="base.group_website_publisher">
+ <t t-set="url_return" t-value="url_for('', '[lang]') + '?' + keep_query()"/>
+ <a t-attf-href="/web#action=base.action_view_base_language_install&amp;website_id=#{website.id if website else ''}&amp;url_return=#{url_return}">
+ <i class="fa fa-plus-circle"/>
+ Add a language...
+ </a>
+ </li>
+ </ul>
+</template>
+
<template id="publish_management">
<div groups="base.group_website_publisher" t-ignore="true" class="pull-right css_editable_mode_hidden" t-att-style="style or ''">
<div t-attf-class="btn-group js_publish_management #{object.website_published and 'css_published' or 'css_unpublished'}" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-controller="publish_controller">
diff --git a/addons/website_blog/static/src/js/website.tour.blog.js b/addons/website_blog/static/src/js/website.tour.blog.js
index bf2c170a8dc..5e9ed46a7b4 100644
--- a/addons/website_blog/static/src/js/website.tour.blog.js
+++ b/addons/website_blog/static/src/js/website.tour.blog.js
@@ -33,7 +33,7 @@
content: _t("Click <em>Continue</em> to create the blog post."),
},
{
- waitFor: 'body:has(button[data-action=save]:visible):has(.js_blog)',
+ waitFor: '#o_scroll .oe_snippet',
title: _t("Blog Post Created"),
content: _t("This is your new blog post. Let's edit it."),
popover: { next: _t("Continue") },
diff --git a/addons/website_event/data/event_demo.xml b/addons/website_event/data/event_demo.xml
index c36230fa1b5..f63e01e9f49 100644
--- a/addons/website_event/data/event_demo.xml
+++ b/addons/website_event/data/event_demo.xml
@@ -61,7 +61,7 @@
<li class="list-group-item">Loops</li>
<li class="list-group-item">Arrays</li>
</ul><div class="panel-footer text-center">
-<p class="text-muted"><i>You will be able to develop simple dynamic compenents in HTML pages.</i></p>
+<p class="text-muted"><i>You will be able to develop simple dynamic components in HTML pages.</i></p>
</div>
</div>
</div>
@@ -227,7 +227,7 @@
<li class="list-group-item">Loops</li>
<li class="list-group-item">Arrays</li>
</ul><div class="panel-footer text-center">
-<p class="text-muted"><i>You will be able to develop simple dynamic compenents in HTML pages.</i></p>
+<p class="text-muted"><i>You will be able to develop simple dynamic components in HTML pages.</i></p>
</div>
</div>
</div>
@@ -405,7 +405,7 @@
<li class="list-group-item">Loops</li>
<li class="list-group-item">Arrays</li>
</ul><div class="panel-footer text-center">
-<p class="text-muted"><i>You will be able to develop simple dynamic compenents in HTML pages.</i></p>
+<p class="text-muted"><i>You will be able to develop simple dynamic components in HTML pages.</i></p>
</div>
</div>
</div>
diff --git a/addons/website_event/static/src/js/website.tour.event.js b/addons/website_event/static/src/js/website.tour.event.js
index 43e8c24bdf2..94f920e8d90 100644
--- a/addons/website_event/static/src/js/website.tour.event.js
+++ b/addons/website_event/static/src/js/website.tour.event.js
@@ -41,7 +41,7 @@
content: _t("Click <em>Continue</em> to create the event."),
},
{
- waitFor: 'body:has(button[data-action=save]:visible):has(.js_event)',
+ waitFor: '#o_scroll .oe_snippet',
title: _t("New Event Created"),
content: _t("This is your new event page. We will edit the event presentation page."),
popover: { next: _t("Continue") },
diff --git a/addons/website_event_track/controllers/event.py b/addons/website_event_track/controllers/event.py
index 9f67b7c5e3b..0a083218c9b 100644
--- a/addons/website_event_track/controllers/event.py
+++ b/addons/website_event_track/controllers/event.py
@@ -18,17 +18,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-import collections
+import collections
import datetime
-import re
-
import pytz
+import re
import openerp
-import openerp.tools
from openerp.addons.web import http
from openerp.addons.web.http import request
+from openerp.tools import html_escape as escape
+
class website_event(http.Controller):
@http.route(['''/event/<model("event.event"):event>/track/<model("event.track", "[('event_id','=',event[0])]"):track>'''], type='http', auth="public", website=True)
@@ -150,7 +150,6 @@ class website_event(http.Controller):
if post.get('tag_'+str(tag.id)):
tags.append(tag.id)
- e = openerp.tools.escape
track_description = '''<section>
<div class="container">
<div class="row">
@@ -166,8 +165,8 @@ class website_event(http.Controller):
</div>
</div>
</div>
-</section>''' % (e(post['track_name']),
- e(post['description']), e(post['biography']))
+</section>''' % (escape(post['track_name']),
+ escape(post['description']), escape(post['biography']))
track_id = tobj.create(cr, openerp.SUPERUSER_ID, {
'name': post['track_name'],
@@ -179,8 +178,8 @@ class website_event(http.Controller):
tobj.message_post(cr, openerp.SUPERUSER_ID, [track_id], body="""Proposed By: %s<br/>
Mail: <a href="mailto:%s">%s</a><br/>
- Phone: %s""" % (e(post['partner_name']), e(post['email_from']),
- e(post['email_from']), e(post['phone'])), context=context)
+ Phone: %s""" % (escape(post['partner_name']), escape(post['email_from']),
+ escape(post['email_from']), escape(post['phone'])), context=context)
track = tobj.browse(cr, uid, track_id, context=context)
values = {'track': track, 'event':event}
diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py
index 9c7e630cfab..68082e81ed8 100644
--- a/addons/website_forum/models/forum.py
+++ b/addons/website_forum/models/forum.py
@@ -111,6 +111,14 @@ class Forum(models.Model):
karma_editor = fields.Integer(string='Editor Features: image and links',
default=30, oldname='karma_editor_link_files')
+ @api.one
+ @api.constrains('allow_question', 'allow_discussion', 'allow_link', 'default_post_type')
+ def _check_default_post_type(self):
+ if (self.default_post_type == 'question' and not self.allow_question) \
+ or (self.default_post_type == 'discussion' and not self.allow_discussion) \
+ or (self.default_post_type == 'link' and not self.allow_link):
+ raise UserError(_('You cannot choose %s as default post since the forum does not allow it.' % self.default_post_type))
+
@api.model
def create(self, values):
return super(Forum, self.with_context(mail_create_nolog=True)).create(values)
@@ -163,7 +171,7 @@ class Post(models.Model):
('question', 'Question'),
('link', 'Article'),
('discussion', 'Discussion')],
- string='Type', default='question')
+ string='Type', default='question', required=True)
website_message_ids = fields.One2many(
'mail.message', 'res_id',
domain=lambda self: ['&', ('model', '=', self._name), ('type', 'in', ['email', 'comment'])],
@@ -302,6 +310,14 @@ class Post(models.Model):
self.can_comment = user.karma >= self.karma_comment
self.can_comment_convert = user.karma >= self.karma_comment_convert
+ @api.one
+ @api.constrains('post_type', 'forum_id')
+ def _check_post_type(self):
+ if (self.post_type == 'question' and not self.forum_id.allow_question) \
+ or (self.post_type == 'discussion' and not self.forum_id.allow_discussion) \
+ or (self.post_type == 'link' and not self.forum_id.allow_link):
+ raise UserError(_('This forum does not allow %s' % self.post_type))
+
def name_get(self, cr, uid, ids, context=None):
result = []
for post in self.browse(cr, uid, ids, context=context):
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index 6d8bf808997..3d4ad999f6a 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -82,7 +82,9 @@
<a t-attf-href="/forum/#{ slug(forum) }/badge">Badges</a>
</li>
<li t-if="not is_public_user" t-att-class="searches.get('my_profile') and 'active' or '' ">
- <a t-attf-href="/forum/#{ slug(forum) }/user/#{uid}">My Profile</a>
+ <a t-attf-href="/forum/#{ slug(forum) }/user/#{uid}">
+ <span t-field="user.name"/> (k: <t t-esc="user.karma"/>)
+ </a>
</li>
</ul>
<form class="navbar-form navbar-right" role="search" t-attf-action="/forum/#{ slug(forum) }" method="get">
@@ -1053,9 +1055,7 @@
</form>
</div>
</div>
- <script type="text/javascript">
- CKEDITOR.replace("description");
- </script>
+
</t>
</template>
diff --git a/addons/website_quote/data/quotation_tip_data.xml b/addons/website_quote/data/quotation_tip_data.xml
index 61c03b25710..20c809e867c 100644
--- a/addons/website_quote/data/quotation_tip_data.xml
+++ b/addons/website_quote/data/quotation_tip_data.xml
@@ -3,7 +3,7 @@
<data>
<record model="web.tip" id="quotation_tip_1">
<field name="title"></field>
- <field name="description"><![CDATA[Click here to see the polished and professional proposal that is automatically created. The customer will be able to sign it online.]]></field>
+ <field name="description"><![CDATA[Click here to see the elegant and professional proposal that is automatically created. The customer will be able to sign it online.]]></field>
<field name="model">sale.order</field>
<field name="mode">form</field>
<field name="trigger_selector">.oe_form_buttons_view:visible, header >.oe_button.oe_form_button:nth-child(3):not(.oe_form_invisible)</field>
diff --git a/addons/website_quote/models/order.py b/addons/website_quote/models/order.py
index 6b15301c7cd..9090d68a68e 100644
--- a/addons/website_quote/models/order.py
+++ b/addons/website_quote/models/order.py
@@ -173,9 +173,8 @@ class sale_order(osv.osv):
if not template_id:
return True
- if context is None:
- context = {}
if partner:
+ context = dict(context or {})
context['lang'] = self.pool['res.partner'].browse(cr, uid, partner, context).lang
lines = [(5,)]
diff --git a/addons/website_report/views/layouts.xml b/addons/website_report/views/layouts.xml
index c1cc3b49365..5994adfde32 100644
--- a/addons/website_report/views/layouts.xml
+++ b/addons/website_report/views/layouts.xml
@@ -20,21 +20,7 @@
</xpath>
<xpath expr="//header" position="replace"/>
<xpath expr="//footer" position="replace">
- <ul class="list-inline js_language_selector mt16" t-if="(request and request.website_multilang and len(languages) &gt; 1) or (website and editable)">
- <li t-foreach="languages" t-as="lg">
- <a t-att-href="url_for(request.httprequest.path + '?' + keep_query(), lang=lg[0])"
- t-att-data-default-lang="editable and 'true' if website and lg[0] == website.default_lang_code else None">
- <t t-esc="lg[1].split('/').pop()"/>
- </a>
- </li>
- <li groups="base.group_website_publisher">
- <t t-set="url_return" t-value="url_for('', '[lang]') + '?' + keep_query()"/>
- <a t-attf-href="/web#action=base.action_view_base_language_install&amp;website_id=#{website.id if website else ''}&amp;url_return=#{url_return}">
- <i class="fa fa-plus-circle"/>
- Add a language...
- </a>
- </li>
- </ul>
+ <t t-call="website.language_selector"/>
</xpath>
</template>
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 476a6d30c47..e32ca940dae 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -153,7 +153,6 @@ class website_sale(http.Controller):
('description_sale', 'ilike', srch), ('product_variant_ids.default_code', 'ilike', srch)]
if category:
domain += [('public_categ_ids', 'child_of', int(category))]
-
attrib_list = request.httprequest.args.getlist('attrib')
attrib_values = [map(int,v.split("-")) for v in attrib_list if v]
attrib_set = set([v[1] for v in attrib_values])
@@ -188,6 +187,8 @@ class website_sale(http.Controller):
if category:
category = pool['product.public.category'].browse(cr, uid, int(category), context=context)
url = "/shop/category/%s" % slug(category)
+ if attrib_list:
+ post['attrib'] = attrib_list
style_obj = pool['product.style']
style_ids = style_obj.search(cr, uid, [], context=context)
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop.js b/addons/website_sale/static/src/js/website_sale_tour_shop.js
index 3a3a55541aa..8f1a02d816d 100644
--- a/addons/website_sale/static/src/js/website_sale_tour_shop.js
+++ b/addons/website_sale/static/src/js/website_sale_tour_shop.js
@@ -41,7 +41,7 @@
content: _t("Click on <em>Continue</em> to create the product."),
},
{
- waitFor: 'body:has(button[data-action=save]:visible):has(.js_sale)',
+ waitFor: '#o_scroll .oe_snippet',
title: _t("New product created"),
content: _t("This page contains all the information related to the new product."),
popover: { next: _t("Continue") },
@@ -72,6 +72,7 @@
},
{
element: '.modal .btn.save',
+ placement: 'right',
waitFor: '.existing-attachment-cell.media_selected',
title: _t("Save"),
content: _t("Click on <em>Save</em> to add the image to the product description"),
diff --git a/addons/website_version/models/experiment.py b/addons/website_version/models/experiment.py
index e5cf5f11e6b..cfed7fd5af1 100644
--- a/addons/website_version/models/experiment.py
+++ b/addons/website_version/models/experiment.py
@@ -62,9 +62,31 @@ class Experiment(models.Model):
if not exp_ver.version_id.website_id.id == exp.website_id.id:
raise ValidationError('This experiment must have versions which are in the same website')
- _group_by_full = {
- 'state': lambda *args, **kwargs: ([('running', 'Running'), ('paused', 'Paused'), ('ended', 'Ended')], dict())
- }
+ def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):
+ """ Override read_group to always display all states. """
+ if groupby and groupby[0] == "state":
+ # Default result structure
+ # states = self._get_state_list(cr, uid, context=context)
+ states = [('running', 'Running'), ('paused', 'Paused'), ('ended', 'Ended')]
+ read_group_all_states = [{
+ '__context': {'group_by': groupby[1:]},
+ '__domain': domain + [('state', '=', state_value)],
+ 'state': state_value,
+ 'state_count': 0,
+ } for state_value, state_name in states]
+ # Get standard results
+ read_group_res = super(Experiment, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
+ # Update standard results with default results
+ result = []
+ for state_value, state_name in states:
+ res = filter(lambda x: x['state'] == state_value, read_group_res)
+ if not res:
+ res = filter(lambda x: x['state'] == state_value, read_group_all_states)
+ res[0]['state'] = [state_value, state_name]
+ result.append(res[0])
+ return result
+ else:
+ return super(Experiment, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
@api.one
def _get_version_number(self):
@@ -127,6 +149,7 @@ class Experiment(models.Model):
self.env['google.management'].delete_an_experiment(exp.google_id, exp.website_id.id)
return super(Experiment, self).unlink()
+ @api.multi
def update_goals(self):
gm_obj = self.env['google.management']
goals_obj = self.env['website_version.goals']
diff --git a/doc/_themes/odoodoc/static/app.js b/doc/_themes/odoodoc/static/app.js
index 64a77c15c40..16f5445d860 100644
--- a/doc/_themes/odoodoc/static/app.js
+++ b/doc/_themes/odoodoc/static/app.js
@@ -106,8 +106,10 @@ $(function () {
// programming language switcher
function findSheet(pattern, fromSheet) {
if (fromSheet) {
- for(var i=0; i<fromSheet.cssRules.length; ++i) {
- var rule = fromSheet.cssRules[i];
+ // cssRules may be `null` in iOS safari (?)
+ var rules = fromSheet.cssRules || [];
+ for(var i=0; i<rules.length; ++i) {
+ var rule = rules[i];
if (rule.type !== CSSRule.IMPORT_RULE) { continue; }
if (pattern.test(rule.href)) {
return rule.styleSheet;
diff --git a/doc/cla/corporate/d9t.md b/doc/cla/corporate/d9t.md
new file mode 100644
index 00000000000..3c20e3fa4a4
--- /dev/null
+++ b/doc/cla/corporate/d9t.md
@@ -0,0 +1,16 @@
+Germany, 2015-02-23
+
+D9T GmbH agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Daniel Kraft, daniel.kraft@d9t.de https://github.com/wamdam
+
+List of contributors:
+
+Daniel Kraft, daniel.kraft@d9t.de https://github.com/wamdam
+Oliver Roch, oliver.roch@d9t.de https://github.com/veroc
diff --git a/doc/cla/corporate/eezee-it.md b/doc/cla/corporate/eezee-it.md
new file mode 100644
index 00000000000..12cedecec59
--- /dev/null
+++ b/doc/cla/corporate/eezee-it.md
@@ -0,0 +1,22 @@
+Belgium, 2015-03-02
+
+EEZEE-IT agrees to the terms of the Odoo Corporate Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Antony Brugger antony.brugger@eezee-it.com https://github.com/antonybrugger
+
+List of contributors:
+
+Julien Laloux julien.laloux@eezee-it.com https://github.com/jlaloux
+Sylvain Van Hoof sylvain.vanhoof@eezee-it.com https://github.com/sylvainvanhoof
+Aristóbulo Meneses aristobulo.meneses@eezee-it.com https://github.com/aristobulo-eezee
+Janik De Goÿ janik.de.goy@eezee-it.com https://github.com/jdegoy
+François Lawarrée francois.lawarre@eezee-it.com https://github.com/francoislawarree
+José Moreno jose.moreno@eezee-it.com https://github.com/jose-eezee
+Javier Carrasco javier.carrasco@eezee-it.com https://github.com/jose-eezee
+David Cajot david.cajot@eezee-it.com https://github.com/dcajot \ No newline at end of file
diff --git a/doc/cla/corporate/initos.md b/doc/cla/corporate/initos.md
new file mode 100644
index 00000000000..0bede7135e2
--- /dev/null
+++ b/doc/cla/corporate/initos.md
@@ -0,0 +1,21 @@
+Germany, 2015-02-26
+
+initOS GmbH & Co. KG agrees to the terms of the Odoo Corporate
+Contributor License Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Markus Schneider markus.schneider@initos.com https://github.com/OSguard
+
+List of contributors:
+
+Markus Schneider markus.schneider@initos.com https://github.com/OSguard
+Thomas Rehn thomas.rehn@initos.com https://github.com/tremlin
+Katja Matthes katja.matthes@initos.com https://github.com/kmatthes
+Frederik Kramer frederik.kramer@initos.com https://github.com/OSevangelist
+Nikolina Todorova nikolina.todorova@initos.com https://github.com/ntodorova
+Peter Hahn peter.hahn@initos.com https://github.com/codingforfun
+Claudia Haida claudia.haida@initos.com
diff --git a/doc/cla/corporate/versada.md b/doc/cla/corporate/versada.md
new file mode 100644
index 00000000000..24610ad24fc
--- /dev/null
+++ b/doc/cla/corporate/versada.md
@@ -0,0 +1,14 @@
+Lithuania, 2015-02-26
+
+Versada agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this declaration.
+
+Signed,
+
+Andrius Preimantas andrius@versada.lt https://github.com/andrius-preimantas
+
+List of contributors:
+
+Andrius Preimantas andrius@versada.lt https://github.com/andrius-preimantas
+
diff --git a/doc/cla/individual/Sathors.md b/doc/cla/individual/Sathors.md
new file mode 100644
index 00000000000..8e005313156
--- /dev/null
+++ b/doc/cla/individual/Sathors.md
@@ -0,0 +1,11 @@
+Ecuador, 2nd of March 2015
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Nicolas Schmitt sathors@free.fr https://github.com/Sathors
diff --git a/doc/cla/individual/Toilal.md b/doc/cla/individual/Toilal.md
new file mode 100644
index 00000000000..706d3a3ff14
--- /dev/null
+++ b/doc/cla/individual/Toilal.md
@@ -0,0 +1,11 @@
+France, 2015-02-20
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Rémi Alvergnat toilal.dev@gmail.com https://github.com/Toilal
diff --git a/doc/cla/individual/leorochael.md b/doc/cla/individual/leorochael.md
new file mode 100644
index 00000000000..8833e872814
--- /dev/null
+++ b/doc/cla/individual/leorochael.md
@@ -0,0 +1,11 @@
+Brazil, 2015-02-26
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Leonardo Rochael Almeida leorochael@gmail.com https://github.com/leorochael
diff --git a/doc/cla/individual/vnsofthe.md b/doc/cla/individual/vnsofthe.md
new file mode 100644
index 00000000000..70c0c189c12
--- /dev/null
+++ b/doc/cla/individual/vnsofthe.md
@@ -0,0 +1,11 @@
+China, 2015-02-27
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+vnsoft vnsoft.he@gmail.com https://github.com/vnsofthe
diff --git a/doc/reference/cmdline.rst b/doc/reference/cmdline.rst
index 280cd079622..434b0d9cd9d 100644
--- a/doc/reference/cmdline.rst
+++ b/doc/reference/cmdline.rst
@@ -278,6 +278,14 @@ customize the amout of logging output
In case of conflict between :option:`--log-level` and
:option:`--log-handler`, the latter is used
+Advanced options
+----------------
+
+.. option:: --auto-reload
+
+ enable auto-reloading of python files and xml files without having to
+ restart the server. Requires pyinotify_.
+
.. _reference/cmdline/scaffold:
Scaffolding
@@ -347,3 +355,4 @@ to that file.
http://www.postgresql.org/docs/9.2/static/libpq-connect.html#AEN38208
.. _Werkzeug's proxy support:
http://werkzeug.pocoo.org/docs/0.9/contrib/fixers/#werkzeug.contrib.fixers.ProxyFix
+.. _pyinotify: https://github.com/seb-m/pyinotify/wiki
diff --git a/doc/setup/install.rst b/doc/setup/install.rst
index 4221eb15225..701dec1a137 100644
--- a/doc/setup/install.rst
+++ b/doc/setup/install.rst
@@ -124,8 +124,10 @@ daemon and automatically start it.
.. danger:: to print PDF reports, you must install wkhtmltopdf_ yourself:
the version of wkhtmltopdf_ available in debian repositories does
not support headers and footers so it can not be installed
- automatically. Use the version available on
- `the wkhtmltopdf download page`_.
+ automatically. The recommended version is 0.12.1 and is available on
+ `the wkhtmltopdf download page`_, in the archive section. As there
+ is no official release for Debian Jessie, you can find ours on
+ http://nightly.odoo.com/extra/.
Configuration
'''''''''''''
diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py
index dbb02673f9e..3c62c8bbd05 100644
--- a/openerp/addons/base/res/res_bank.py
+++ b/openerp/addons/base/res/res_bank.py
@@ -152,8 +152,8 @@ class res_partner_bank(osv.osv):
'name': '/'
}
- def fields_get(self, cr, uid, allfields=None, context=None):
- res = super(res_partner_bank, self).fields_get(cr, uid, allfields=allfields, context=context)
+ def fields_get(self, cr, uid, allfields=None, context=None, write_access=True, attributes=None):
+ res = super(res_partner_bank, self).fields_get(cr, uid, allfields=allfields, context=context, write_access=write_access, attributes=attributes)
bank_type_obj = self.pool.get('res.partner.bank.type')
type_ids = bank_type_obj.search(cr, uid, [])
types = bank_type_obj.browse(cr, uid, type_ids)
diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py
index e90c4204c22..c696566f2d0 100644
--- a/openerp/addons/base/res/res_config.py
+++ b/openerp/addons/base/res/res_config.py
@@ -363,13 +363,13 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
self.already_installed(cr, uid, context=context),
True))
- def fields_get(self, cr, uid, fields=None, context=None, write_access=True):
+ def fields_get(self, cr, uid, fields=None, context=None, write_access=True, attributes=None):
""" If an addon is already installed, set it to readonly as
res.config.installer doesn't handle uninstallations of already
installed addons
"""
fields = super(res_config_installer, self).fields_get(
- cr, uid, fields, context, write_access)
+ cr, uid, fields, context, write_access, attributes)
for name in self.already_installed(cr, uid, context=context):
if name not in fields:
diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py
index 95a6b8ce653..f51ac267487 100644
--- a/openerp/addons/base/res/res_users.py
+++ b/openerp/addons/base/res/res_users.py
@@ -893,8 +893,8 @@ class users_view(osv.osv):
selected = [gid for gid in get_selection_groups(f) if gid in gids]
values[f] = selected and selected[-1] or False
- def fields_get(self, cr, uid, allfields=None, context=None, write_access=True):
- res = super(users_view, self).fields_get(cr, uid, allfields, context, write_access)
+ def fields_get(self, cr, uid, allfields=None, context=None, write_access=True, attributes=None):
+ res = super(users_view, self).fields_get(cr, uid, allfields, context, write_access, attributes)
# add reified groups fields
for app, kind, gs in self.pool['res.groups'].get_groups_by_application(cr, uid, context):
if kind == 'selection':
diff --git a/openerp/addons/test_inherits/__init__.py b/openerp/addons/test_inherits/__init__.py
new file mode 100644
index 00000000000..a0fdc10fe11
--- /dev/null
+++ b/openerp/addons/test_inherits/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from . import models
diff --git a/openerp/addons/test_inherits/__openerp__.py b/openerp/addons/test_inherits/__openerp__.py
new file mode 100644
index 00000000000..21010738f8e
--- /dev/null
+++ b/openerp/addons/test_inherits/__openerp__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': 'test-inherits',
+ 'version': '0.1',
+ 'category': 'Tests',
+ 'description': """A module to verify the inheritance using _inherits.""",
+ 'author': 'Camptocamp',
+ 'website': 'http://www.camptocamp.com',
+ 'depends': ['base'],
+ 'data': [
+ 'ir.model.access.csv',
+ 'demo_data.xml',
+ ],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/openerp/addons/test_inherits/demo_data.xml b/openerp/addons/test_inherits/demo_data.xml
new file mode 100644
index 00000000000..98b19095acd
--- /dev/null
+++ b/openerp/addons/test_inherits/demo_data.xml
@@ -0,0 +1,19 @@
+<openerp>
+ <data>
+ <record id="unit_a" model="test.unit">
+ <field name="name">Unit A</field>
+ <field name="state">a</field>
+ </record>
+
+ <record id="box_a" model="test.box">
+ <field name="unit_id" ref="unit_a"/>
+ <field name="field_in_box">A</field>
+ </record>
+
+ <record id="pallet_a" model="test.pallet">
+ <field name="box_id" ref="box_a"/>
+ <field name="field_in_pallet">A</field>
+ </record>
+
+ </data>
+</openerp>
diff --git a/openerp/addons/test_inherits/ir.model.access.csv b/openerp/addons/test_inherits/ir.model.access.csv
new file mode 100644
index 00000000000..22ecf366e31
--- /dev/null
+++ b/openerp/addons/test_inherits/ir.model.access.csv
@@ -0,0 +1,4 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+access_test_unit,access_test_unit,model_test_unit,,1,1,1,1
+access_test_box,access_test_box,model_test_box,,1,1,1,1
+access_test_pallet,access_test_pallet,model_test_pallet,,1,1,1,1
diff --git a/openerp/addons/test_inherits/models.py b/openerp/addons/test_inherits/models.py
new file mode 100644
index 00000000000..c8f86ec52d7
--- /dev/null
+++ b/openerp/addons/test_inherits/models.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+from openerp import models, fields, api, osv
+
+
+# We just create a new model
+class Unit(models.Model):
+ _name = 'test.unit'
+
+ _columns = {
+ 'name': osv.fields.char('Name', required=True),
+ 'state': osv.fields.selection([('a', 'A'), ('b', 'B')],
+ string='State'),
+ }
+
+ surname = fields.Char(compute='_compute_surname')
+
+ @api.one
+ @api.depends('name')
+ def _compute_surname(self):
+ self.surname = self.name or ''
+
+
+# We want to _inherits from the parent model and we add some fields
+# in the child object
+class Box(models.Model):
+ _name = 'test.box'
+ _inherits = {'test.unit': 'unit_id'}
+
+ unit_id = fields.Many2one('test.unit', 'Unit', required=True,
+ ondelete='cascade')
+ field_in_box = fields.Char('Field1')
+
+
+# We add a third level of _inherits
+class Pallet(models.Model):
+ _name = 'test.pallet'
+ _inherits = {'test.box': 'box_id'}
+
+ box_id = fields.Many2one('test.box', 'Box', required=True,
+ ondelete='cascade')
+ field_in_pallet = fields.Char('Field2')
diff --git a/openerp/addons/test_inherits/tests/__init__.py b/openerp/addons/test_inherits/tests/__init__.py
new file mode 100644
index 00000000000..694d1720d65
--- /dev/null
+++ b/openerp/addons/test_inherits/tests/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import test_inherits
diff --git a/openerp/addons/test_inherits/tests/test_inherits.py b/openerp/addons/test_inherits/tests/test_inherits.py
new file mode 100644
index 00000000000..7ff5dee814b
--- /dev/null
+++ b/openerp/addons/test_inherits/tests/test_inherits.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+from openerp.tests import common
+
+
+class test_inherits(common.TransactionCase):
+
+ def test_create_3_levels_inherits(self):
+ """ Check that we can create an inherits on 3 levels """
+ pallet = self.env['test.pallet'].create({
+ 'name': 'B',
+ 'field_in_box': 'box',
+ 'field_in_pallet': 'pallet',
+ })
+ self.assertTrue(pallet)
+ self.assertEqual(pallet.name, 'B')
+ self.assertEqual(pallet.field_in_box, 'box')
+ self.assertEqual(pallet.field_in_pallet, 'pallet')
+
+ def test_write_3_levels_inherits(self):
+ """ Check that we can create an inherits on 3 levels """
+ pallet = self.env.ref('test_inherits.pallet_a')
+ pallet.write({'name': 'C'})
+ self.assertEqual(pallet.name, 'C')
diff --git a/openerp/cli/scaffold.py b/openerp/cli/scaffold.py
index 8194aa6cbba..3f86965fb9a 100644
--- a/openerp/cli/scaffold.py
+++ b/openerp/cli/scaffold.py
@@ -112,16 +112,18 @@ class template(object):
"""
# overwrite with local
for path, content in self.files():
- _, ext = os.path.splitext(path)
-
local = os.path.relpath(path, self.path)
+ # strip .template extension
+ root, ext = os.path.splitext(local)
+ if ext == '.template':
+ local = root
dest = os.path.join(directory, modname, local)
destdir = os.path.dirname(dest)
if not os.path.exists(destdir):
os.makedirs(destdir)
with open(dest, 'wb') as f:
- if ext not in ('.py', '.xml', '.csv', '.js', '.rst', '.html'):
+ if ext not in ('.py', '.xml', '.csv', '.js', '.rst', '.html', '.template'):
f.write(content)
else:
env.from_string(content)\
diff --git a/openerp/cli/server.py b/openerp/cli/server.py
index d93d3c364ea..80e3e16b922 100644
--- a/openerp/cli/server.py
+++ b/openerp/cli/server.py
@@ -30,6 +30,7 @@ GNU Public Licence.
"""
import atexit
+import csv
import logging
import os
import signal
@@ -141,6 +142,11 @@ def main(args):
config = openerp.tools.config
+ # the default limit for CSV fields in the module is 128KiB, which is not
+ # quite sufficient to import images to store in attachment. 500MiB is a
+ # bit overkill, but better safe than sorry I guess
+ csv.field_size_limit(500 * 1024 * 1024)
+
if config["db_name"]:
try:
openerp.service.db._create_empty_database(config["db_name"])
diff --git a/openerp/cli/templates/default/__init__.py b/openerp/cli/templates/default/__init__.py.template
index 43dbbaa6318..43dbbaa6318 100644
--- a/openerp/cli/templates/default/__init__.py
+++ b/openerp/cli/templates/default/__init__.py.template
diff --git a/openerp/cli/templates/default/__openerp__.py b/openerp/cli/templates/default/__openerp__.py.template
index 2aae5fbdd46..2aae5fbdd46 100644
--- a/openerp/cli/templates/default/__openerp__.py
+++ b/openerp/cli/templates/default/__openerp__.py.template
diff --git a/openerp/cli/templates/default/controllers.py b/openerp/cli/templates/default/controllers.py.template
index 505208171fc..505208171fc 100644
--- a/openerp/cli/templates/default/controllers.py
+++ b/openerp/cli/templates/default/controllers.py.template
diff --git a/openerp/cli/templates/default/demo.xml b/openerp/cli/templates/default/demo.xml.template
index 5ca25672404..5ca25672404 100644
--- a/openerp/cli/templates/default/demo.xml
+++ b/openerp/cli/templates/default/demo.xml.template
diff --git a/openerp/cli/templates/default/models.py b/openerp/cli/templates/default/models.py.template
index 562aa3a851b..562aa3a851b 100644
--- a/openerp/cli/templates/default/models.py
+++ b/openerp/cli/templates/default/models.py.template
diff --git a/openerp/cli/templates/default/security/ir.model.access.csv b/openerp/cli/templates/default/security/ir.model.access.csv.template
index 71a2352fedc..71a2352fedc 100644
--- a/openerp/cli/templates/default/security/ir.model.access.csv
+++ b/openerp/cli/templates/default/security/ir.model.access.csv.template
diff --git a/openerp/cli/templates/default/templates.xml b/openerp/cli/templates/default/templates.xml.template
index b9166501678..b9166501678 100644
--- a/openerp/cli/templates/default/templates.xml
+++ b/openerp/cli/templates/default/templates.xml.template
diff --git a/openerp/cli/templates/theme/__init__.py b/openerp/cli/templates/theme/__init__.py.template
index 40a96afc6ff..40a96afc6ff 100644
--- a/openerp/cli/templates/theme/__init__.py
+++ b/openerp/cli/templates/theme/__init__.py.template
diff --git a/openerp/cli/templates/theme/__openerp__.py b/openerp/cli/templates/theme/__openerp__.py.template
index 329483fa078..9eba44581a9 100644
--- a/openerp/cli/templates/theme/__openerp__.py
+++ b/openerp/cli/templates/theme/__openerp__.py.template
@@ -7,13 +7,17 @@
'version': '1.0',
'depends': ['website'],
- # templates, pages, and snippets
+ # templates
'data': [
- 'views/pages.xml',
'views/options.xml',
'views/snippets.xml',
],
+ # demo pages
+ 'demo': [
+ 'demo/pages.xml',
+ ],
+
# Your information
'author': "Your Company",
'website': "",
diff --git a/openerp/cli/templates/theme/static/style/custom.less b/openerp/cli/templates/theme/demo/pages.xml.template
index e69de29bb2d..e69de29bb2d 100644
--- a/openerp/cli/templates/theme/static/style/custom.less
+++ b/openerp/cli/templates/theme/demo/pages.xml.template
diff --git a/openerp/cli/templates/theme/views/options.xml b/openerp/cli/templates/theme/static/src/less/custom.less.template
index e69de29bb2d..e69de29bb2d 100644
--- a/openerp/cli/templates/theme/views/options.xml
+++ b/openerp/cli/templates/theme/static/src/less/custom.less.template
diff --git a/openerp/cli/templates/theme/views/pages.xml b/openerp/cli/templates/theme/views/options.xml.template
index e69de29bb2d..e69de29bb2d 100644
--- a/openerp/cli/templates/theme/views/pages.xml
+++ b/openerp/cli/templates/theme/views/options.xml.template
diff --git a/openerp/cli/templates/theme/views/snippets.xml b/openerp/cli/templates/theme/views/snippets.xml.template
index e69de29bb2d..e69de29bb2d 100644
--- a/openerp/cli/templates/theme/views/snippets.xml
+++ b/openerp/cli/templates/theme/views/snippets.xml.template
diff --git a/openerp/http.py b/openerp/http.py
index bdd009bfedf..a12f912af7b 100644
--- a/openerp/http.py
+++ b/openerp/http.py
@@ -561,7 +561,7 @@ class JsonRequest(WebRequest):
start_time = time.time()
_, start_vms = 0, 0
if psutil:
- _, start_vms = psutil.Process().get_memory_info()
+ _, start_vms = psutil.Process(os.getpid()).get_memory_info()
if rpc_request and rpc_response_flag:
rpc_request.debug('%s: %s %s, %s',
endpoint, model, method, pprint.pformat(args))
@@ -572,7 +572,7 @@ class JsonRequest(WebRequest):
end_time = time.time()
_, end_vms = 0, 0
if psutil:
- _, end_vms = psutil.Process().get_memory_info()
+ _, end_vms = psutil.Process(os.getpid()).get_memory_info()
logline = '%s: %s %s: time:%.3fs mem: %sk -> %sk (diff: %sk)' % (
endpoint, model, method, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024)
if rpc_response_flag:
diff --git a/openerp/models.py b/openerp/models.py
index 8d2aff9df3a..62ecc41db11 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -49,7 +49,7 @@ import pytz
import re
import time
from collections import defaultdict, MutableMapping
-from inspect import getmembers
+from inspect import getmembers, currentframe
import babel.dates
import dateutil.relativedelta
@@ -66,6 +66,7 @@ from .osv import fields
from .osv.query import Query
from .tools import frozendict, lazy_property, ormcache
from .tools.config import config
+from .tools.func import frame_codeinfo
from .tools.misc import CountingStream, DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
from .tools.safe_eval import safe_eval as eval
from .tools.translate import _
@@ -2907,6 +2908,7 @@ class BaseModel(object):
cls._inherit_fields = struct = {}
for parent_model, parent_field in cls._inherits.iteritems():
parent = cls.pool[parent_model]
+ parent._inherits_reload()
for name, column in parent._columns.iteritems():
struct[name] = (parent_model, parent_field, column, parent_model)
for name, source in parent._inherit_fields.iteritems():
@@ -5479,7 +5481,9 @@ class BaseModel(object):
""" Test whether two recordsets are equivalent (up to reordering). """
if not isinstance(other, BaseModel):
if other:
- _logger.warning("Comparing apples and oranges: %s == %s", self, other)
+ filename, lineno = frame_codeinfo(currentframe(), 1)
+ _logger.warning("Comparing apples and oranges: %r == %r (%s:%s)",
+ self, other, filename, lineno)
return False
return self._name == other._name and set(self._ids) == set(other._ids)
diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js
index 8a0f54c97b8..4f264118485 100644
--- a/openerp/tests/phantomtest.js
+++ b/openerp/tests/phantomtest.js
@@ -1,6 +1,8 @@
// Phantomjs odoo helper
// jshint evil: true, loopfunc: true
+var system = require('system');
+
function waitFor (condition, callback, timeout, timeoutMessageCallback) {
timeout = timeout || 10000;
var start = new Date();
@@ -19,7 +21,7 @@ function waitFor (condition, callback, timeout, timeoutMessageCallback) {
function PhantomTest() {
var self = this;
- this.options = JSON.parse(phantom.args[phantom.args.length-1]);
+ this.options = JSON.parse(system.args[system.args.length-1]);
this.inject = this.options.inject || [];
this.timeout = this.options.timeout ? Math.round(parseFloat(this.options.timeout)*1000 - 5000) : 10000;
this.origin = 'http://localhost';
@@ -143,7 +145,7 @@ function PhantomTest() {
}
// js mode or jsfile mode
-if(phantom.args.length === 1) {
+if(system.args.length === 2) {
pt = new PhantomTest();
pt.run(pt.options.url_path, pt.options.code, pt.options.ready);
}
diff --git a/openerp/tools/config.py b/openerp/tools/config.py
index 9b90c36394a..3f38720d739 100644
--- a/openerp/tools/config.py
+++ b/openerp/tools/config.py
@@ -627,7 +627,8 @@ class configmanager(object):
if not os.path.exists(d):
os.makedirs(d, 0700)
else:
- os.chmod(d, 0700)
+ assert os.access(d, os.W_OK), \
+ "%s: directory is not writable" % d
return d
@property
@@ -636,7 +637,8 @@ class configmanager(object):
if not os.path.exists(d):
os.makedirs(d, 0700)
else:
- os.chmod(d, 0700)
+ assert os.access(d, os.W_OK), \
+ "%s: directory is not writable" % d
return d
def filestore(self, dbname):
diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py
index 210063a10cb..f464d57ac72 100644
--- a/openerp/tools/misc.py
+++ b/openerp/tools/misc.py
@@ -119,7 +119,8 @@ def exec_pg_command(name, *args):
prog = find_pg_tool(name)
env = exec_pg_environ()
with open(os.devnull) as dn:
- rc = subprocess.call((prog,) + args, env=env, stdout=dn, stderr=subprocess.STDOUT)
+ args2 = (prog,) + args
+ rc = subprocess.call(args2, env=env, stdout=dn, stderr=subprocess.STDOUT)
if rc:
raise Exception('Postgres subprocess %s error %s' % (args2, rc))
diff --git a/requirements.txt b/requirements.txt
index d3fe518208e..3f19e42c037 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@ Jinja2==2.7.3
Mako==1.0.1
MarkupSafe==0.23
Pillow==2.7.0
-http://download.gna.org/pychart/PyChart-1.39.tar.gz
+http://download.gna.org/pychart/PyChart-1.39.tar.gz#egg=PyChart
PyYAML==3.11
Werkzeug==0.9.6
argparse==1.2.1