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