diff --git a/addons/account/account.py b/addons/account/account.py index 3c7c14737c1..031a803d64c 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -3414,6 +3414,8 @@ class wizard_multi_charts_accounts(osv.osv_memory): all the provided information to create the accounts, the banks, the journals, the taxes, the tax codes, the accounting properties... accordingly for the chosen company. ''' + if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'): + raise openerp.exceptions.AccessError(_("Only administrators can change the settings")) obj_data = self.pool.get('ir.model.data') ir_values_obj = self.pool.get('ir.values') obj_wizard = self.browse(cr, uid, ids[0]) @@ -3430,7 +3432,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): self.pool[tmp2[0]].write(cr, uid, tmp2[1], { 'currency_id': obj_wizard.currency_id.id }) - except ValueError, e: + except ValueError: pass # If the floats for sale/purchase rates have been filled, create templates from them diff --git a/addons/account/report/account_print_invoice.rml b/addons/account/report/account_print_invoice.rml index 3582221df44..6914adfaf20 100644 --- a/addons/account/report/account_print_invoice.rml +++ b/addons/account/report/account_print_invoice.rml @@ -295,7 +295,7 @@ - Total: + Total: [[ formatLang(o.amount_total, digits=get_digits(dp='Account'), currency_obj=o.currency_id) ]] diff --git a/addons/account/res_config.py b/addons/account/res_config.py index 09df82e38fd..bf28e900378 100644 --- a/addons/account/res_config.py +++ b/addons/account/res_config.py @@ -22,13 +22,12 @@ import time import datetime from dateutil.relativedelta import relativedelta -from operator import itemgetter -from os.path import join as opj +import openerp +from openerp import SUPERUSER_ID from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF from openerp.tools.translate import _ from openerp.osv import fields, osv -from openerp import tools class account_config_settings(osv.osv_memory): _name = 'account.config.settings' @@ -276,11 +275,13 @@ class account_config_settings(osv.osv_memory): def set_default_taxes(self, cr, uid, ids, context=None): """ set default sale and purchase taxes for products """ + if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'): + raise openerp.exceptions.AccessError(_("Only administrators can change the settings")) ir_values = self.pool.get('ir.values') config = self.browse(cr, uid, ids[0], context) - ir_values.set_default(cr, uid, 'product.product', 'taxes_id', + ir_values.set_default(cr, SUPERUSER_ID, 'product.product', 'taxes_id', config.default_sale_tax and [config.default_sale_tax.id] or False, company_id=config.company_id.id) - ir_values.set_default(cr, uid, 'product.product', 'supplier_taxes_id', + ir_values.set_default(cr, SUPERUSER_ID, 'product.product', 'supplier_taxes_id', config.default_purchase_tax and [config.default_purchase_tax.id] or False, company_id=config.company_id.id) def set_chart_of_accounts(self, cr, uid, ids, context=None): diff --git a/addons/base_import/static/src/js/import.js b/addons/base_import/static/src/js/import.js index 0c3acd98265..1238bc9570f 100644 --- a/addons/base_import/static/src/js/import.js +++ b/addons/base_import/static/src/js/import.js @@ -373,7 +373,7 @@ openerp.base_import = function (instance) { return $.when([{ type: 'error', record: false, - message: error.data.fault_code, + message: error.data.arguments[1], }]); }) ; }, diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index f239ad739c1..8ce2aa1c6c7 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -38,6 +38,9 @@ class crm_lead2opportunity_partner(osv.osv_memory): 'section_id': fields.many2one('crm.case.section', 'Sales Team', select=True), } + def onchange_action(self, cr, uid, ids, action, context=None): + return {'value': {'partner_id': False if action != 'exist' else self._find_matching_partner(cr, uid, context=context)}} + def default_get(self, cr, uid, fields, context=None): """ Default get for name, opportunity_ids. diff --git a/addons/crm/wizard/crm_lead_to_opportunity_view.xml b/addons/crm/wizard/crm_lead_to_opportunity_view.xml index 4f6522c27de..09506c3ed81 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_view.xml +++ b/addons/crm/wizard/crm_lead_to_opportunity_view.xml @@ -31,7 +31,7 @@ - + diff --git a/addons/crm_claim/report/crm_claim_report_view.xml b/addons/crm_claim/report/crm_claim_report_view.xml index 2d0787ca527..965c4b5bffc 100644 --- a/addons/crm_claim/report/crm_claim_report_view.xml +++ b/addons/crm_claim/report/crm_claim_report_view.xml @@ -51,7 +51,9 @@ - + + diff --git a/addons/document/document.py b/addons/document/document.py index 91af97246b8..5fc62578b1d 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -69,7 +69,11 @@ class document_file(osv.osv): def check(self, cr, uid, ids, mode, context=None, values=None): """Overwrite check to verify access on directory to validate specifications of doc/access_permissions.rst""" + if not isinstance(ids, list): + ids = [ids] + super(document_file, self).check(cr, uid, ids, mode, context=context, values=values) + if ids: self.pool.get('ir.model.access').check(cr, uid, 'document.directory', mode) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index efa1387435a..6e19b1eb8aa 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -369,7 +369,7 @@ class email_template(osv.osv): attachment_ids=[attach.id for attach in template.attachment_ids], ) - # Add report in attachments + # Add report in attachments: generate once for all template_res_ids if template.report_template: for res_id in template_res_ids: attachments = [] @@ -387,8 +387,7 @@ class email_template(osv.osv): if not report_name.endswith(ext): report_name += ext attachments.append((report_name, result)) - - values['attachments'] = attachments + results[res_id]['attachments'] = attachments return results diff --git a/addons/google_drive/google_drive.py b/addons/google_drive/google_drive.py index df0d6a3c5af..b69c7ddd173 100644 --- a/addons/google_drive/google_drive.py +++ b/addons/google_drive/google_drive.py @@ -60,10 +60,9 @@ class config(osv.Model): def get_access_token(self, cr, uid, scope=None, context=None): ir_config = self.pool['ir.config_parameter'] google_drive_refresh_token = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_refresh_token') - group_config = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_erp_manager')[1] - user = self.pool['res.users'].read(cr, uid, uid, "groups_id") + user_is_admin = self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager') if not google_drive_refresh_token: - if group_config in user['groups_id']: + if user_is_admin: raise self.pool.get('res.config.settings').get_config_warning(cr, _("You haven't configured 'Authorization Code' generated from google, Please generate and configure it in %(menu:base_setup.menu_general_configuration)s."), context=context) else: raise osv.except_osv(_('Error!'), _("Google Drive is not yet configured. Please contact your administrator.")) @@ -81,7 +80,7 @@ class config(osv.Model): req = urllib2.Request('https://accounts.google.com/o/oauth2/token', data, headers) content = urllib2.urlopen(req).read() except urllib2.HTTPError: - if group_config in user['groups_id']: + if user_is_admin: raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during the token generation. Please request again an authorization code in %(menu:base_setup.menu_general_configuration)s."), context=context) else: raise osv.except_osv(_('Error!'), _("Google Drive is not yet configured. Please contact your administrator.")) diff --git a/addons/google_spreadsheet/google_spreadsheet.py b/addons/google_spreadsheet/google_spreadsheet.py index a724e3c425e..81ec536b6f2 100644 --- a/addons/google_spreadsheet/google_spreadsheet.py +++ b/addons/google_spreadsheet/google_spreadsheet.py @@ -18,6 +18,7 @@ # ############################################################################## +import cgi import simplejson import logging from lxml import etree @@ -67,24 +68,24 @@ class config(osv.osv): request = ''' - https://spreadsheets.google.com/feeds/cells/%s/od6/private/full + https://spreadsheets.google.com/feeds/cells/{key}/od6/private/full A1 - https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R1C1 + https://spreadsheets.google.com/feeds/cells/{key}/od6/private/full/R1C1 - + href="https://spreadsheets.google.com/feeds/cells/{key}/od6/private/full/R1C1"/> + A2 - https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R60C15 + https://spreadsheets.google.com/feeds/cells/{key}/od6/private/full/R60C15 - + href="https://spreadsheets.google.com/feeds/cells/{key}/od6/private/full/R60C15"/> + -''' % (spreadsheet_key, spreadsheet_key, spreadsheet_key, formula.replace('"', '"'), spreadsheet_key, spreadsheet_key, config_formula.replace('"', '"')) +''' .format(key=spreadsheet_key, formula=cgi.escape(formula, quote=True), config=cgi.escape(config_formula, quote=True)) try: req = urllib2.Request( diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index c169f1dc3f3..b38c2eae1d9 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -1096,7 +1096,7 @@ class mail_thread(osv.AbstractModel): encoding = part.get_content_charset() # None if attachment # 1) Explicit Attachments -> attachments if filename or part.get('content-disposition', '').strip().startswith('attachment'): - attachments.append((filename or 'attachment', part.get_payload(decode=True))) + attachments.append((decode(filename) or 'attachment', part.get_payload(decode=True))) continue # 2) text/plain ->
                 if part.get_content_type() == 'text/plain' and (not alternative or not body):
@@ -1321,6 +1321,40 @@ class mail_thread(osv.AbstractModel):
                     mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'author_id': partner_info['partner_id']}, context=context)
         return result
 
+    def _message_preprocess_attachments(self, cr, uid, attachments, attachment_ids, attach_model, attach_res_id, context=None):
+        """ Preprocess attachments for mail_thread.message_post() or mail_mail.create().
+
+        :param list attachments: list of attachment tuples in the form ``(name,content)``,
+                                 where content is NOT base64 encoded
+        :param list attachment_ids: a list of attachment ids, not in tomany command form
+        :param str attach_model: the model of the attachments parent record
+        :param integer attach_res_id: the id of the attachments parent record
+        """
+        Attachment = self.pool['ir.attachment']
+        m2m_attachment_ids = []
+        if attachment_ids:
+            filtered_attachment_ids = Attachment.search(cr, SUPERUSER_ID, [
+                ('res_model', '=', 'mail.compose.message'),
+                ('create_uid', '=', uid),
+                ('id', 'in', attachment_ids)], context=context)
+            if filtered_attachment_ids:
+                Attachment.write(cr, SUPERUSER_ID, filtered_attachment_ids, {'res_model': attach_model, 'res_id': attach_res_id}, context=context)
+            m2m_attachment_ids += [(4, id) for id in attachment_ids]
+        # Handle attachments parameter, that is a dictionary of attachments
+        for name, content in attachments:
+            if isinstance(content, unicode):
+                content = content.encode('utf-8')
+            data_attach = {
+                'name': name,
+                'datas': base64.b64encode(str(content)),
+                'datas_fname': name,
+                'description': name,
+                'res_model': attach_model,
+                'res_id': attach_res_id,
+            }
+            m2m_attachment_ids.append((0, 0, data_attach))
+        return m2m_attachment_ids
+
     def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
                      subtype=None, parent_id=False, attachments=None, context=None,
                      content_subtype='html', **kwargs):
@@ -1399,28 +1433,7 @@ class mail_thread(osv.AbstractModel):
 
         # 3. Attachments
         #   - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
-        attachment_ids = kwargs.pop('attachment_ids', []) or []  # because we could receive None (some old code sends None)
-        if attachment_ids:
-            filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
-                ('res_model', '=', 'mail.compose.message'),
-                ('create_uid', '=', uid),
-                ('id', 'in', attachment_ids)], context=context)
-            if filtered_attachment_ids:
-                ir_attachment.write(cr, SUPERUSER_ID, filtered_attachment_ids, {'res_model': model, 'res_id': thread_id}, context=context)
-        attachment_ids = [(4, id) for id in attachment_ids]
-        # Handle attachments parameter, that is a dictionary of attachments
-        for name, content in attachments:
-            if isinstance(content, unicode):
-                content = content.encode('utf-8')
-            data_attach = {
-                'name': name,
-                'datas': base64.b64encode(str(content)),
-                'datas_fname': name,
-                'description': name,
-                'res_model': model,
-                'res_id': thread_id,
-            }
-            attachment_ids.append((0, 0, data_attach))
+        attachment_ids = self._message_preprocess_attachments(cr, uid, attachments, kwargs.pop('attachment_ids', []), model, thread_id, context)
 
         # 4: mail.message.subtype
         subtype_id = False
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index 53afbed7997..ec30fe8615f 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -634,7 +634,7 @@
     margin-bottom: 4px;
 }
 .openerp .oe_followers .oe_invite{
-    float: right;
+    padding-left: 5px;
 }
 .openerp .oe_followers .oe_partner {
     height: 32px;
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index f129e5bf0f9..833811c07a1 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -19,7 +19,9 @@
 #
 ##############################################################################
 
+import base64
 import re
+
 from openerp import tools
 from openerp import SUPERUSER_ID
 from openerp.osv import osv
@@ -260,6 +262,12 @@ class mail_compose_message(osv.TransientModel):
 
             for res_id, mail_values in all_mail_values.iteritems():
                 if mass_mail_mode and not wizard.post:
+                    m2m_attachment_ids = self.pool['mail.thread']._message_preprocess_attachments(
+                        cr, uid, mail_values.pop('attachments', []),
+                        mail_values.pop('attachment_ids', []),
+                        'mail.message', 0,
+                        context=context)
+                    mail_values['attachment_ids'] = m2m_attachment_ids
                     self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
                 else:
                     subtype = 'mail.mt_comment'
@@ -298,7 +306,12 @@ class mail_compose_message(osv.TransientModel):
             if mass_mail_mode and wizard.model:
                 email_dict = rendered_values[res_id]
                 mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
-                mail_values['attachments'] = email_dict.pop('attachments', [])
+                # process attachments: should not be encoded before being processed by message_post / mail_mail create
+                attachments = []
+                if email_dict.get('attachments'):
+                    for name, enc_cont in email_dict.pop('attachments'):
+                        attachments.append((name, base64.b64decode(enc_cont)))
+                mail_values['attachments'] = attachments
                 attachment_ids = []
                 for attach_id in mail_values.pop('attachment_ids'):
                     new_attach_id = self.pool.get('ir.attachment').copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
diff --git a/addons/note/note.py b/addons/note/note.py
index 95fe8f97cdf..00c67c37191 100644
--- a/addons/note/note.py
+++ b/addons/note/note.py
@@ -147,6 +147,7 @@ class note_note(osv.osv):
                     if result and result[0]['stage_id'][0] == current_stage_ids[0]:
                         dom_in = result[0]['__domain'].pop()
                         result[0]['__domain'] = domain + ['|', dom_in, dom_not_in]
+                        result[0]['stage_id_count'] += nb_notes_ws
                     else:
                         # add the first stage column
                         result = [{
diff --git a/addons/pad/static/src/css/etherpad.css b/addons/pad/static/src/css/etherpad.css
index a0002ebe97c..facf15b28de 100644
--- a/addons/pad/static/src/css/etherpad.css
+++ b/addons/pad/static/src/css/etherpad.css
@@ -92,6 +92,7 @@
     font-family: arial, sans-serif;
     font-size: 15px;
     line-height: 19px; 
+    word-wrap: break-word;
 }
 
 .openerp .oe_form_nomargin .etherpad_readonly{ 
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 2a82d0f23f4..75f6856709b 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -860,6 +860,14 @@ class purchase_order_line(osv.osv):
             res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
         return res
 
+    def _get_uom_id(self, cr, uid, context=None):
+        try:
+            proxy = self.pool.get('ir.model.data')
+            result = proxy.get_object_reference(cr, uid, 'product', 'product_uom_unit')
+            return result[1]
+        except Exception, ex:
+            return False
+
     _columns = {
         'name': fields.text('Description', required=True),
         'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
@@ -886,6 +894,7 @@ class purchase_order_line(osv.osv):
 
     }
     _defaults = {
+        'product_uom' : _get_uom_id,
         'product_qty': lambda *a: 1.0,
         'state': lambda *args: 'draft',
         'invoiced': lambda *a: 0,
@@ -1289,8 +1298,8 @@ class account_invoice(osv.Model):
         else:
             user_id = uid
         po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context)
-        if po_ids:
-            purchase_order_obj.message_post(cr, user_id, po_ids, body=_("Invoice paid"), context=context)
+        for po_id in po_ids:
+            purchase_order_obj.message_post(cr, user_id, po_id, body=_("Invoice paid"), context=context)
         return res
 
 class account_invoice_line(osv.Model):
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index 4a301d3fa7b..d58d561a122 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -199,7 +199,7 @@
                         
                         
                             
-                            
+                            
                             
                             
                         
diff --git a/addons/sale/wizard/sale_line_invoice.py b/addons/sale/wizard/sale_line_invoice.py
index e7c854b84aa..cd199760e9e 100644
--- a/addons/sale/wizard/sale_line_invoice.py
+++ b/addons/sale/wizard/sale_line_invoice.py
@@ -61,7 +61,7 @@ class sale_order_line_make_invoice(osv.osv_memory):
             else:
                 pay_term = False
             inv = {
-                'name': order.name,
+                'name': order.client_order_ref or '',
                 'origin': order.name,
                 'type': 'out_invoice',
                 'reference': "P%dSO%d" % (order.partner_id.id, order.id),
diff --git a/addons/sale_stock/res_config.py b/addons/sale_stock/res_config.py
index f52dcf89659..5023d3b8eea 100644
--- a/addons/sale_stock/res_config.py
+++ b/addons/sale_stock/res_config.py
@@ -19,6 +19,8 @@
 #
 ##############################################################################
 
+import openerp
+from openerp import SUPERUSER_ID
 from openerp.osv import fields, osv
 from openerp.tools.translate import _
 
@@ -72,12 +74,13 @@ class sale_configuration(osv.osv_memory):
         }
 
     def set_sale_defaults(self, cr, uid, ids, context=None):
+        if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'):
+            raise openerp.exceptions.AccessError(_("Only administrators can change the settings"))
         ir_values = self.pool.get('ir.values')
-        ir_model_data = self.pool.get('ir.model.data')
         wizard = self.browse(cr, uid, ids)[0]
 
         default_picking_policy = 'one' if wizard.default_picking_policy else 'direct'
-        ir_values.set_default(cr, uid, 'sale.order', 'picking_policy', default_picking_policy)
+        ir_values.set_default(cr, SUPERUSER_ID, 'sale.order', 'picking_policy', default_picking_policy)
         res = super(sale_configuration, self).set_sale_defaults(cr, uid, ids, context)
         return res
     
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index 3f7ac28c900..341aaef8ca8 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -604,7 +604,7 @@ class sale_order_line(osv.osv):
         #check if product is available, and if not: raise an error
         uom2 = False
         if uom:
-            uom2 = product_uom_obj.browse(cr, uid, uom)
+            uom2 = product_uom_obj.browse(cr, uid, uom, context=context)
             if product_obj.uom_id.category_id.id != uom2.category_id.id:
                 uom = False
         if not uom2:
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index c6637163d87..69d2c6e4791 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -1361,9 +1361,9 @@ class stock_picking(osv.osv):
                 self.action_move(cr, uid, [new_picking], context=context)
                 self.signal_button_done(cr, uid, [new_picking])
                 workflow.trg_write(uid, 'stock.picking', pick.id, cr)
-                delivered_pack_id = new_picking
+                delivered_pack_id = pick.id
                 back_order_name = self.browse(cr, uid, delivered_pack_id, context=context).name
-                self.message_post(cr, uid, ids, body=_("Back order %s has been created.") % (back_order_name), context=context)
+                self.message_post(cr, uid, new_picking, body=_("Back order %s has been created.") % (back_order_name), context=context)
             else:
                 self.action_move(cr, uid, [pick.id], context=context)
                 self.signal_button_done(cr, uid, [pick.id])