diff --git a/.mailmap b/.mailmap index de72af905da..9e806a9ffa2 100644 --- a/.mailmap +++ b/.mailmap @@ -4,6 +4,8 @@ Ajay Javiya Ajay Javiya Akash Balar Akash Balar +Akta Dakwala +Akta Dakwala EDA(Axelor) <> Albert Cervera i Areny Albert Cervera i Areny Albert Cervera i Areny - http://www.NaN-tic.com <> Albert Cervera i Areny Albert Cervera i Areny <> @@ -20,6 +22,7 @@ Alexis de Lattre Alexis de Lattre Alexis de Lattre Alexis de Lattre <> Alexis de Lattre alexis.delattre@akretion.com <> +Aliane Abdelouahab Aline Preillon Aline Preillon Amit Bhavsar @@ -103,6 +106,9 @@ Atul Patel ATP(OpenERP) <> Atul Patel ATUL (OpenERP) <> Atul Patel atp (OpenERP) <> Augustin Cisterne-Kaas Augustin Cisterne-Kaas +Avdhesh Patel +Avdhesh Patel AVP(Axelor) <> +Avdhesh Patel avp Baskhuu Jacara Jacara <> Baskhuu Jacara jacara <> Benoit Guillot @@ -113,6 +119,7 @@ Bharat Devnani Bharat Devnani Bharat Devnani (Open ERP) <> Bharat Devnani Bharat Devnani(OpenERP) <> Bharat Devnani bde@tinyerp.com <> +Bhatt Chintal bch <> Bhavik Bagdiya Bhavik Bagdiya Bhumi Patel @@ -217,6 +224,8 @@ Dhara Shah Dhara (OpenERP) <> Dhara Shah Dhara Dhara Shah dsh (Open ERP) <> Dhara Shah dsh(Open ERP) <> +Dharmesh Rathod +Dharmesh Rathod DRA Axelor <> Dharmraj Jhala Dharmraj Jhala Dharmraj Jhala @@ -320,6 +329,7 @@ Guewen Baconnier Guewen Baconnier <> Guillem Barba Guillem Barba <> Guillem Barba guillem <> Guillermo Bisheimer +Gulamfarid Ghanchi Géry Debongnie Géry Debongnie Géry Debongnie @@ -330,6 +340,7 @@ Hardik Ansodariya Hardik Ansodariya (OpenERP) <> Hardik Ansodariya Hardik Ansodariya <> Hardik R. Shamnani Hardik R. Shamnani hsa-tinyerp <> +Hardik Sanchawat Hardik Sanchawat (OpenERP) <> Harshad Modi Harshad Modi HMO (Open ERP) <> Harshad Modi HMO(OpenERP) <> @@ -345,6 +356,7 @@ Herbert Petersen Conexus.at <> Hiral Patel Hitesh Trivedi Hugo Santos hugo <> +Hugo Santos hugosdsantos Humberto Arocha hbto(humbertoarocha) <> Husen Daudi Husen Daudi @@ -352,6 +364,7 @@ Husen Daudi Husen Daudi Husen Daudi Husen Daudi JVO,HDA <> +Husen Daudi ashish Husen Daudi hda(Open ERP) <> Husen Daudi hda@tinyerp.com <> Husen Daudi (Serpent CS) @@ -412,6 +425,10 @@ Jean-Baptiste Aubort Jean-Christophe Vassort Jean-Christophe Vassort Jean-Sebastien Suzanne Jean-Sebastien SUZANNE <> +Jenil Malavia +Jenil Malavia JMA(OpenEPR) <> +Jenil Malavia JMA(OpenERP) <> +Jenil Malavia Jenil(OpenERP) <> Jeremy Kersten Jeremy Kersten Jeremy Kersten jke-openerp <> @@ -422,6 +439,7 @@ Jigar Amin Jigar Amin Jigar Amin JAM (OpenERP) <> Jigar Amin JAM(OpenERP) <> +Jigar Amin amami <> Jigar Amin jam <> Jignesh Rathod Jignesh Rathod @@ -461,6 +479,9 @@ Kuldeep Joshi Kuldeep Joshi Kuldeep Joshi kjo <> Kunal Chavda +Kunal Mathur +Kunal Mathur +Kunal Mathur KUM(OpenERP) <> Laetitia Gangloff Launchpad Launchpad Launchpad Translations on behalf of openerp <> @@ -497,6 +518,8 @@ Mantavya Gajjar Mantavya Gajjar MGA(OpenERP) <> Mantavya Gajjar Matvaya Gajjar(Openerp) <> Mantavya Gajjar mga@tinyerp.com <> +Manuel Abadia Manu <> +Manuel Abadia Manu, Jigar Amin - OpenERP <> Marcelo Hamra Marcelo Hamra <> Marcelo Hamra marcelo Mario Arias Badila @@ -579,6 +602,7 @@ Nicolas Bessi Nicolas Bessi Nicolas <> Nicolas Bessi Nicolas Bessi(C2C) <> Nicolas Bessi nicolas.bessi@camptocamp.com <> +Nicolas De Smet Zyph0s <> Nicolas Seinlet Nicolas Vanhoren Nicolas Vanhoren @@ -592,6 +616,9 @@ Niels Huylebroeck Niels Huylebroeck <> Niels Huylebroeck Niels(Red15) <> Niels Huylebroeck Niels(Rrd15) <> Niels Huylebroeck Red15 <> +Nikunj Jani +Nikunj Jani +Nikunj Jani Reporting Team(JNI OpenERP) <> Nimesh Contractor Oliver Laurent Olivier Dony @@ -599,24 +626,33 @@ Olivier Dony Olivier Dony Olivier Dony Odowan Kenobi <> Olivier Dony Olivier Dony <> -Olivier Dony Olvier Dony <> +Olivier Dony Olivier Laurent, Olivier Dony <> Olivier Dony OpenERP Framework R&D Team <> Olivier Dony odo@tinyerp.com <> Olivier Dossmann Olivier DOSSMANN Olivier Laurent Olivier Laurent Olivier Laurent -Olivier Laurent Olivier Laurent, Olivier Dony <> Olivier Laurent olt at tinyerp <> Olivier Laurent olt@tinyerp.com <> +Olivier Macchioni Omar Castiñeira Saavedra Omar Castiñeira Saavedra Omar (Pexego) <> Omar Castiñeira Saavedra Omar Omar Castiñeira Saavedra Omar(Pexego),Anup(OpenERP) <> Omar Castiñeira Saavedra omar7r <> +Ostap Yakovenko Pankaj Joshi Pankaj Joshi Pankaj Joshi Pankita Shah +Pankti Parikh +Pankti Parikh +Pankti Parikh PAP (Openerp) <> +Pankti Parikh PAP(OpenERP) <> +Pankti Parikh PAP(Openerp) <> +Pankti Parikh pap (OpenERP) <> +Pankti Parikh pap(openerp) <> +Pankti Parikh pap@tinyerp.com <> Panos Christeas Panos Christeas Panos Christeas @@ -640,8 +676,10 @@ Pariket Trivedi Parth Gajjar Parthiv Patel Parthiv Patel +Patrick Monnerat Patrik <> Patrick Thieuleux Patrick Thieuleux <> Paul J Stevens Paul J Stevens <> +Paul Rad-Dupuy vivisci <> Paulius Sladkevičius Paulius Sladkevičius Paulius Sladkevičius Paulius Sladkevičius Paulius Sladkevičius @@ -654,6 +692,7 @@ Peter Langenberg Peter Langenberg <> Philu philubert@gmail.com <> Phong Nguyen Phong Nguyen Phong Nguyen-Thanh <> +Phuong Lu phu <> Pinakin Nayi Pinakin Nayi Pinakin Nayi Pinakin Nai(OpenERP) <> @@ -732,6 +771,10 @@ Ravi Gohil Ravi Gohil Ravi Gohil Ravi Gohil (OpenERP) <> Ravi Gohil rgo <> +Ravindra Mekhiya +Ravindra Mekhiya +Ravindra Mekhiya RME(OpenERP) <> +Ravindra Mekhiya Ravindra Mekhiya (OpenERP) <> Ravish Murari Ravish Murari Ravish Murari Ravish(OpenERP) <> @@ -767,6 +810,11 @@ Rikenkumar Bhorania RCH(OpenERP). <> Rikenkumar Bhorania rch (Open ERP) <> Rikenkumar Bhorania rch (OpenERP) <> Rikenkumar Bhorania rch(OpenERP) <> +Rima Vora +Rima Vora +Rima Vora RVO <> +Rima Vora RVO(OpenERP) <> +Rima Vora Rvo(Open ERP) <> Robert Sevcik Robajz <> Roberto López López Rohan Nayani @@ -785,8 +833,23 @@ Rucha Patel Rucha Patel RPA(OpenERP) <> Rucha Patel rpa(Open ERP) <> Rucha Patel rpa-openerp <> +Sabrina Romero +Saiyed Azadahmed +Saiyed Azadahmed +Saiyed Azadahmed SAZ (Open ERP) <> +Saiyed Azadahmed SAZ (OpenERP) <> +Saiyed Azadahmed SAZ (Opn ERP) <> +Saiyed Azadahmed SAZ (OprnERP) <> +Saiyed Azadahmed SAZ(OpenERP) <> +Saiyed Azadahmed SAz <> +Saiyed Azadahmed saz <> +Saiyed Azadahmed saz(OpenERP) <> +Saiyed Azadahmed saz(openerp) <> Salvatore Trimarchi Salvi Angjeli Salvi Angjeli <> +Sananaz Mansuri +Sananaz Mansuri SMA(OpenERP) <> +Sananaz Mansuri sma@tinyerp.com <> Sandy Carter Sandy Carter Sanjay Gohel @@ -818,6 +881,7 @@ Sheth Sunny Sheth Sunny Sheth Sunny Sheth Sunny sus +Shyamli Singh Simon Lejeune Simon Lejeune Simone Orsi @@ -897,6 +961,11 @@ Ujjvala Collins uco(OpenERP) <> Vadim Chobanu Vadim - SmartMode LTD <> Vaibhav Darji Vaibhav Darji +Vaishali Thakkar +Vaishali Thakkar VTH(OpenERP) <> +Valencia Rodrigues +Valencia Rodrigues VRO(OpenERP) <> +Vee vitridex <> Vianney da Costa Vianney da Costa Vianney - www.akretion.com.br <> Vianney da Costa Vianney,Anup(OpenERP) <> @@ -941,6 +1010,9 @@ Xavier Morel Xavier Morel Xavier Morel <> Xavier Morel Xavier(OpenERP) <> Xavier Morel xmo <> +Yajushi Yagnik +Yajushi Yagnik YYA(Axelor) <> +Yajushi Yagnik YYA(OpenERP) <> Yann Papouin Yann Papouin Yann Papouin <> Yann Papouin Yann papouin <> @@ -950,6 +1022,7 @@ Yannick Vaucher Yannick Vaucher <> Yannick Vaucher Yannick Vaucher Yannick Vaucher Yannick Vaucher@Camptocamp <> Yogesh Parekh +Yogesh Parekh Yogesh Parekh Yogesh Sakhreliya Yogesh Sakhreliya @@ -968,63 +1041,12 @@ Yury Tello Yury Tello _FIXME ajm _FIXME ajm AJM (OpenERP) <> -_FIXME bch bch <> -_FIXME ggh -_FIXME jma -_FIXME jma JMA(OpenEPR) <> -_FIXME jma JMA(OpenERP) <> -_FIXME jni -_FIXME jni -_FIXME jni Reporting Team(JNI OpenERP) <> -_FIXME kum -_FIXME kum -_FIXME kum KUM(OpenERP) <> -_FIXME pap -_FIXME pap -_FIXME pap PAP (Openerp) <> -_FIXME pap PAP(OpenERP) <> -_FIXME pap PAP(Openerp) <> -_FIXME pap pap (OpenERP) <> -_FIXME pap pap(openerp) <> -_FIXME pap pap@tinyerp.com <> _FIXME rde rde <> -_FIXME rme -_FIXME rme -_FIXME rme RME(OpenERP) <> -_FIXME rvo -_FIXME rvo -_FIXME rvo RVO <> -_FIXME rvo RVO(OpenERP) <> -_FIXME rvo Rvo(Open ERP) <> -_FIXME saz -_FIXME saz -_FIXME saz SAZ (Open ERP) <> -_FIXME saz SAZ (OpenERP) <> -_FIXME saz SAZ (Opn ERP) <> -_FIXME saz SAZ (OprnERP) <> -_FIXME saz SAZ(OpenERP) <> -_FIXME saz SAz <> -_FIXME saz saz <> -_FIXME saz saz(OpenERP) <> -_FIXME saz saz(openerp) <> -_FIXME sma -_FIXME sma SMA(OpenERP) <> -_FIXME sma sma@tinyerp.com <> _FIXME sme _FIXME sme SME(OpenERP) <> _FIXME sme SME(Tiny/Axelor) <> _FIXME sme SME(tiny/Axelor) <> _FIXME sme sme <> -_FIXME ssi -_FIXME vro -_FIXME vro VRO(OpenERP) <> -_FIXME vth -_FIXME vth VTH(OpenERP) <> -_FIXME yya -_FIXME yya EDA(Axelor) <> -_FIXME yya -_FIXME yya YYA(Axelor) <> -_FIXME yya YYA(OpenERP) <> __FIXME bhumi __FIXME tiny __FIXME tiny @@ -1032,25 +1054,22 @@ __FIXME tiny tiny <> # TODO INTERNAL -# 2 AVP(Axelor) <> -# 1 DRA(Axelor) <> -# 1 avp # 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) # 1 Nikita Parmar (OpenERP Trainee) # 1 Sejal Bhut (OpenERP Trainee) -# 1 Amit (Open ERP) <> -# 1 phu <> # 1 vba <> + +# TODO INTERNAL Conflict + +# 1 Amit (Open ERP) <> # 1 Ajay Patel # 1 Yogesh(Open ERP) <> # 1 Yogesh(OpenERP) <> # 1 Chirag +# TODO INTERNAL Couples + # 1 ACH,Jay <> # 2 JMA,VRA <> # 2 Vir,Amp <> @@ -1088,6 +1107,8 @@ __FIXME tiny 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 <> # 1 Mod2 Team(OpenERP) <> # 1 OnDemand Open Object User +# TODO INTERNAL Leading Space + # 1 Amit Dodiya (OpenERP) <> # 1 Amit Dodiya(OpenERP) <> # 1 Jean-Christophe VASSORT <> @@ -1108,7 +1131,6 @@ __FIXME tiny tiny <> # 2 jkei # 2 richierichrawr # 2 watermelonjuice -# 1 vrenaville # 2 youring # 2 Дмитро Катюха # 1 jbeficent @@ -1117,9 +1139,7 @@ __FIXME tiny tiny <> # 1 jmustonen # 1 celm1990 # 1 cysnake4713 -# 1 dharmesh rathod # 1 driou -# 1 dsabrinarg # 1 eneldoserrata # 1 fdesmottes # 1 g.shipilov @@ -1127,38 +1147,25 @@ __FIXME tiny tiny <> # 1 senthilnathang # 1 the-digital-engineer # 1 yingliu4203 -# 1 hugosdsantos # 1 krnkris # 1 buke -# 1 OstapYakovenko # 1 DEEP SILVER ACCESSORY # 1 neps1192 -# 1 ALIANE Abdelouahab # 1 Invitu # 1 Jorge # 1 Maria G # 1 Mack -# TODO MISSING INFO - -# 1 Olivier Macchioni -# Email not found +# TODO MISSING INFO EMAIL # 2 Denis Seleznyov <> -# Email not found # 1 openerp user # 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 +# 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 @@ res.partner 2 + - + @@ -172,7 +173,7 @@ - +

Accounting-related settings are managed on

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)?

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.

CSV file for Manufacturer, Retailer
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 ?
- Good - OK - Bad + Good + OK + Bad
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 @@ - + 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 consu - Custom computer assembled on order based on customer's requirement. Bose's smallest portable Bluetooth speaker B3423 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 @@ - 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. + 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. project.task form 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 @@ -
+
    @@ -268,7 +268,7 @@
-
+

@@ -279,26 +279,28 @@
-
- + - -
+
No activity yet, click to enter in the project.
+
-
- - - +
+
+ + + +
@@ -729,7 +731,7 @@

- 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.

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 @@ -
- + -
+
No activity yet, click to enter in the project.
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 @@ 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 @@ 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 @@ - + stock.inventory.filter stock.inventory @@ -112,10 +112,10 @@ - - - - + + + + @@ -123,12 +123,12 @@ ') .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 = $('') .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 @@ - + @@ -484,7 +484,7 @@
- +
@@ -527,8 +527,8 @@
  • Manage Views
  • Edit View
  • -
  • Edit SearchView
  • -
  • Edit Action
  • +
  • Edit SearchView
  • +
  • Edit Action
  • Edit Workflow
  • Print Workflow
  • @@ -579,7 +579,7 @@
    -
  • @@ -792,8 +794,8 @@
    - @@ -918,7 +920,7 @@
    -
    +
    @@ -950,7 +952,7 @@
    -
    +
    @@ -1107,9 +1109,10 @@ @@ -1148,7 +1151,7 @@ > + /> @@ -1476,7 +1479,7 @@
    - + 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.

    - +
    + + 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 @@ - + sale.order form .oe_form_buttons_view:visible, header >.oe_button.oe_form_button:nth-child(3):not(.oe_form_invisible) 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 @@ - + 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 Continue 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 Save 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 + + + Unit A + a + + + + + A + + + + + A + + + + 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 similarity index 100% rename from openerp/cli/templates/default/__init__.py rename to openerp/cli/templates/default/__init__.py.template diff --git a/openerp/cli/templates/default/__openerp__.py b/openerp/cli/templates/default/__openerp__.py.template similarity index 100% rename from openerp/cli/templates/default/__openerp__.py rename to openerp/cli/templates/default/__openerp__.py.template diff --git a/openerp/cli/templates/default/controllers.py b/openerp/cli/templates/default/controllers.py.template similarity index 100% rename from openerp/cli/templates/default/controllers.py rename to openerp/cli/templates/default/controllers.py.template diff --git a/openerp/cli/templates/default/demo.xml b/openerp/cli/templates/default/demo.xml.template similarity index 100% rename from openerp/cli/templates/default/demo.xml rename to openerp/cli/templates/default/demo.xml.template diff --git a/openerp/cli/templates/default/models.py b/openerp/cli/templates/default/models.py.template similarity index 100% rename from openerp/cli/templates/default/models.py rename to 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 similarity index 100% rename from openerp/cli/templates/default/security/ir.model.access.csv rename to 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 similarity index 100% rename from openerp/cli/templates/default/templates.xml rename to openerp/cli/templates/default/templates.xml.template diff --git a/openerp/cli/templates/theme/__init__.py b/openerp/cli/templates/theme/__init__.py.template similarity index 100% rename from openerp/cli/templates/theme/__init__.py rename to openerp/cli/templates/theme/__init__.py.template diff --git a/openerp/cli/templates/theme/__openerp__.py b/openerp/cli/templates/theme/__openerp__.py.template similarity index 79% rename from openerp/cli/templates/theme/__openerp__.py rename to 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 similarity index 100% rename from openerp/cli/templates/theme/static/style/custom.less rename to 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 similarity index 100% rename from openerp/cli/templates/theme/views/options.xml rename to 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 similarity index 100% rename from openerp/cli/templates/theme/views/pages.xml rename to 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 similarity index 100% rename from openerp/cli/templates/theme/views/snippets.xml rename to 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
  • Technical Translation