diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index 4e476be5b2a..543a710ff71 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -60,8 +60,8 @@
Description |
Quantity |
Unit Price |
- Disc.(%) |
- Taxes |
+ Disc.(%) |
+ Taxes |
Price |
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index de757a1cf91..b0f8cf55b88 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -4,6 +4,8 @@
from collections import defaultdict
from datetime import datetime
from dateutil.relativedelta import relativedelta
+import datetime as DT
+import dateutil
import time
import logging
@@ -121,7 +123,10 @@ class base_action_rule(osv.osv):
""" Prepare the context used when evaluating python code
:returns: dict -- evaluation context given to (safe_)eval """
return {
+ 'datetime': DT,
+ 'dateutil': dateutil,
'time': time,
+ 'uid': uid,
'user': self.pool['res.users'].browse(cr, uid, uid, context=context),
}
diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py
index 6575ad609ef..fe5083f096b 100644
--- a/addons/base_import_module/models/ir_module.py
+++ b/addons/base_import_module/models/ir_module.py
@@ -7,7 +7,7 @@ from os.path import join as opj
import openerp
from openerp import fields
from openerp.osv import osv
-from openerp.tools import convert_file
+from openerp.tools import convert_file, exception_to_unicode
from openerp.tools.translate import _
from openerp.exceptions import UserError
@@ -105,7 +105,7 @@ class ir_module_module(osv.osv):
self.import_module(cr, uid, mod_name, path, force=force, context=context)
success.append(mod_name)
except Exception, e:
- errors[mod_name] = str(e)
+ errors[mod_name] = exception_to_unicode(e)
r = ["Successfully imported module '%s'" % mod for mod in success]
for mod, error in errors.items():
r.append("Error while importing module '%s': %r" % (mod, error))
diff --git a/addons/crm/tests/__init__.py b/addons/crm/tests/__init__.py
new file mode 100644
index 00000000000..69d299aea81
--- /dev/null
+++ b/addons/crm/tests/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+import test_new_lead_notification
diff --git a/addons/crm/tests/test_new_lead_notification.py b/addons/crm/tests/test_new_lead_notification.py
new file mode 100644
index 00000000000..8eefc00df19
--- /dev/null
+++ b/addons/crm/tests/test_new_lead_notification.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+import unittest
+from openerp.tests.common import TransactionCase
+
+class NewLeadNotificationTest(TransactionCase):
+ @unittest.skip("Broken in 9.0. auto_subscribe does not handle channels")
+ def test_new_lead_notification(self):
+ # Create a new user
+ groups = [
+ self.env.ref('base.group_user').id,
+ self.env.ref('base.group_sale_salesman').id,
+ ]
+ user = self.env["res.users"].create({
+ "name": __file__,
+ "login": __file__,
+ 'groups_id': [(6, 0, groups)],
+ })
+
+ chan = self.env['mail.channel'].create({
+ 'name': 'Follow leads',
+ 'group_ids': [(6, 0, groups)],
+ })
+
+ # Subscribe him to sales department
+ team = self.env.ref("sales_team.team_sales_department")
+ subtype = self.env.ref("crm.mt_salesteam_lead")
+ team.message_subscribe(channel_ids=[chan.id], subtype_ids=[subtype.id])
+
+ # Imitate what happens in the controller when somebody creates a new
+ # lead from the website form
+ lead = (self.env["crm.lead"]
+ .with_context(mail_create_nosubscribe=True)
+ .sudo()
+ .create({"contact_name": "Somebody",
+ "description": "Some question",
+ "email_from": "somemail@example.com",
+ "name": "Some subject",
+ "partner_name": "Some company",
+ "team_id": team.id,
+ "phone": "+0000000000"}))
+
+ # The user should have a new unread message
+ self.assertTrue(lead.sudo(user).message_unread)
diff --git a/addons/link_tracker/models/link_tracker.py b/addons/link_tracker/models/link_tracker.py
index 591804304b9..ad71b56286e 100644
--- a/addons/link_tracker/models/link_tracker.py
+++ b/addons/link_tracker/models/link_tracker.py
@@ -12,7 +12,7 @@ from urlparse import urlparse
from openerp import models, fields, api, _
-URL_REGEX = r'(\bhref=[\'"]([^\'"]+)[\'"])'
+URL_REGEX = r'(\bhref=[\'"](?!mailto:)([^\'"]+)[\'"])'
def VALIDATE_URL(url):
if urlparse(url).scheme not in ('http', 'https', 'ftp', 'ftps'):
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index 32c66ce23eb..83ed0312c03 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -5,6 +5,8 @@ import logging
from email.utils import formataddr
from urlparse import urljoin
+import psycopg2
+
from openerp import _, api, fields, models
from openerp import tools
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
@@ -285,6 +287,12 @@ class MailMail(models.Model):
'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option',
mail.id, mail.message_id)
raise
+ except psycopg2.Error:
+ # If an error with the database occurs, chances are that the cursor is unusable.
+ # This will lead to an `psycopg2.InternalError` being raised when trying to write
+ # `state`, shadowing the original exception and forbid a retry on concurrent
+ # update. Let's bubble it.
+ raise
except Exception as e:
failure_reason = tools.ustr(e)
_logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason)
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 50cc5c8f4c0..20a4b77f96f 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -201,6 +201,13 @@ class MailThread(models.AbstractModel):
doc_name = self.env['ir.model'].search([('model', '=', self._name)]).read(['name'])[0]['name']
thread.message_post(body=_('%s created') % doc_name)
+ # auto_subscribe: take values and defaults into account
+ create_values = dict(values)
+ for key, val in self._context.iteritems():
+ if key.startswith('default_') and key[8:] not in create_values:
+ create_values[key[8:]] = val
+ thread.message_auto_subscribe(create_values.keys(), values=create_values)
+
# track values
if not self._context.get('mail_notrack'):
if 'lang' not in self._context:
@@ -212,13 +219,6 @@ class MailThread(models.AbstractModel):
initial_values = {thread.id: dict.fromkeys(tracked_fields, False)}
track_thread.message_track(tracked_fields, initial_values)
- # auto_subscribe: take values and defaults into account
- create_values = dict(values)
- for key, val in self._context.iteritems():
- if key.startswith('default_') and key[8:] not in create_values:
- create_values[key[8:]] = val
- thread.message_auto_subscribe(create_values.keys(), values=create_values)
-
return thread
@api.multi
diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py
index 5872c786641..7ad891944ac 100644
--- a/addons/mass_mailing/models/mail_mail.py
+++ b/addons/mass_mailing/models/mail_mail.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import re
import urlparse
import re
import werkzeug.urls
@@ -79,6 +80,10 @@ class MailMail(osv.Model):
domain = self.pool.get("ir.config_parameter").get_param(cr, uid, "web.base.url", context=context)
base = "" % domain
body = tools.append_content_to_html(base, body, plaintext=False, container_tag='div')
+ # resolve relative image url to absolute for outlook.com
+ def _sub_relative2absolute(match):
+ return match.group(1) + urlparse.urljoin(domain, match.group(2)) + '"'
+ body = re.sub('(
]*\ssrc=")(/[^/][^"]+)"', _sub_relative2absolute, body)
# generate tracking URL
if mail.statistics_ids:
diff --git a/addons/product/product.py b/addons/product/product.py
index 05e07af5344..4f9ace5259b 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -46,18 +46,21 @@ class product_uom(osv.osv):
def name_create(self, cr, uid, name, context=None):
""" The UoM category and factor are required, so we'll have to add temporary values
for imported UoMs """
+ if not context:
+ context = {}
uom_categ = self.pool.get('product.uom.categ')
+ values = {self._rec_name: name, 'factor': 1}
# look for the category based on the english name, i.e. no context on purpose!
# TODO: should find a way to have it translated but not created until actually used
- categ_misc = 'Unsorted/Imported Units'
- categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)])
- if categ_id:
- categ_id = categ_id[0]
- else:
- categ_id, _ = uom_categ.name_create(cr, uid, categ_misc)
- uom_id = self.create(cr, uid, {self._rec_name: name,
- 'category_id': categ_id,
- 'factor': 1})
+ if not context.get('default_category_id'):
+ categ_misc = 'Unsorted/Imported Units'
+ categ_id = uom_categ.search(cr, uid, [('name', '=', categ_misc)])
+ if categ_id:
+ values['category_id'] = categ_id[0]
+ else:
+ values['category_id'] = uom_categ.name_create(
+ cr, uid, categ_misc, context=context)[0]
+ uom_id = self.create(cr, uid, values, context=context)
return self.name_get(cr, uid, [uom_id], context=context)[0]
def create(self, cr, uid, data, context=None):
diff --git a/addons/project/security/project_security.xml b/addons/project/security/project_security.xml
index 8357df26ad8..03a2ed38adb 100644
--- a/addons/project/security/project_security.xml
+++ b/addons/project/security/project_security.xml
@@ -127,5 +127,12 @@
+
+ Task Analysis multi-company
+
+
+ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+
+
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index afc7c88f195..57b4424fa46 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -71,7 +71,7 @@ class PurchaseOrder(models.Model):
@api.model
def _default_picking_type(self):
type_obj = self.env['stock.picking.type']
- company_id = self.env.user.company_id.id
+ company_id = self.env.context.get('company_id') or self.env.user.company_id.id
types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)])
if not types:
types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id', '=', False)])
diff --git a/addons/report_webkit/webkit_report.py b/addons/report_webkit/webkit_report.py
index e5bec873b6d..f8411c64cc6 100644
--- a/addons/report_webkit/webkit_report.py
+++ b/addons/report_webkit/webkit_report.py
@@ -18,6 +18,7 @@ from report_helper import WebKitHelper
import openerp
from openerp.modules.module import get_module_resource
from openerp.report.report_sxw import *
+from openerp import SUPERUSER_ID
from openerp import tools
from openerp.tools.translate import _
from urllib import urlencode, quote as quote
@@ -102,7 +103,7 @@ class WebKitParser(report_sxw):
def get_lib(self, cursor, uid):
"""Return the lib wkhtml path"""
proxy = self.pool['ir.config_parameter']
- webkit_path = proxy.get_param(cursor, uid, 'webkit_path')
+ webkit_path = proxy.get_param(cursor, SUPERUSER_ID, 'webkit_path')
if not webkit_path:
try:
diff --git a/addons/web/static/src/js/views/graph_view.js b/addons/web/static/src/js/views/graph_view.js
index 030a7dcc62e..8a604640fc9 100644
--- a/addons/web/static/src/js/views/graph_view.js
+++ b/addons/web/static/src/js/views/graph_view.js
@@ -29,7 +29,7 @@ var GraphView = View.extend({
this.widget = undefined;
},
start: function () {
- var load_fields = this.model.call('fields_get', [])
+ var load_fields = this.model.call('fields_get', [], {context: this.dataset.get_context()})
.then(this.prepare_fields.bind(this));
return $.when(this._super(), load_fields);
diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js
index e6fff652026..72f1ac4bf1d 100644
--- a/addons/web/static/src/js/views/pivot_view.js
+++ b/addons/web/static/src/js/views/pivot_view.js
@@ -73,7 +73,7 @@ var PivotView = View.extend({
start: function () {
this.$table_container = this.$('.o-pivot-table');
- var load_fields = this.model.call('fields_get', [])
+ var load_fields = this.model.call('fields_get', [], {context: this.dataset.get_context()})
.then(this.prepare_fields.bind(this));
return $.when(this._super(), load_fields).then(this.render_field_selection.bind(this));
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 9f13da9b099..0340cddc5c4 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -163,7 +163,7 @@ class ir_http(orm.AbstractModel):
if path[1] == request.website.default_lang_code:
request.context['edit_translations'] = False
if not request.context.get('tz'):
- request.context['tz'] = request.session['geoip'].get('time_zone')
+ request.context['tz'] = request.session.get('geoip', {}).get('time_zone')
# bind modified context
request.website = request.website.with_context(request.context)
diff --git a/addons/website/static/src/css/common.css b/addons/website/static/src/css/common.css
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css
index 2b7cc4d4481..963ecd40046 100644
--- a/addons/website/static/src/css/website.css
+++ b/addons/website/static/src/css/website.css
@@ -120,7 +120,7 @@ hr {
font-size: 120%;
}
-.jumbotron.para_large p{
+.jumbotron.para_large p {
font-size: 150%;
}
@@ -184,7 +184,6 @@ body > nav.navbar {
}
}
/* ----- BOOTSTRAP FIX ----- */
-
@-moz-document url-prefix() {
.table .img-responsive {
width: 100%;
@@ -753,6 +752,10 @@ div.carousel .item.text_only .container > .carousel-image {
margin-right: 0 !important;
}
+/* ---- footer style ---- {{{ */
+#footer div.row {
+ word-wrap: break-word;
+}
.container nav#oe_main_menu_navbar {
position: inherit;
}
diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass
index 11049bba0e9..028bd25f41e 100644
--- a/addons/website/static/src/css/website.sass
+++ b/addons/website/static/src/css/website.sass
@@ -90,9 +90,8 @@ hr
.para_large
font-size: 120%
-.jumbotron.para_large p{
- font-size: 150%;
-}
+.jumbotron.para_large p
+ font-size: 150%
.readable
font-size: 120%
@@ -616,6 +615,10 @@ div.carousel
.navbar-nav.navbar-right:last-child
margin-right: 0 !important
+/* ---- footer style ---- {{{ */
+#footer div.row
+ word-wrap: break-word
+
.container
nav#oe_main_menu_navbar
position: inherit
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 969b6f8a8ca..176b7881c0f 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -382,13 +382,13 @@