diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 931ae7f41df..de6394b2ea2 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -645,8 +645,9 @@ class AccountBankStatementLine(models.Model): # Look for a single move line with the same amount field = currency and 'amount_residual_currency' or 'amount_residual' liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit' + liquidity_amt_clause = currency and '%(amount)s' or 'abs(%(amount)s)' sql_query = self._get_common_sql_query(excluded_ids=excluded_ids) + \ - " AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \ + " AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = " + liquidity_amt_clause + ")) \ ORDER BY date_maturity asc, aml.id asc LIMIT 1" self.env.cr.execute(sql_query, params) results = self.env.cr.fetchone() diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 24b0aa71b33..f8ba2384408 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -1128,8 +1128,8 @@ class Meeting(models.Model): data['rrule_type'] = 'weekly' #repeat monthly by nweekday ((weekday, weeknumber), ) if rule._bynweekday: - data['week_list'] = day_list[rule._bynweekday[0][0]].upper() - data['byday'] = str(rule._bynweekday[0][1]) + data['week_list'] = day_list[list(rule._bynweekday)[0][0]].upper() + data['byday'] = str(list(rule._bynweekday)[0][1]) data['month_by'] = 'day' data['rrule_type'] = 'monthly' diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 2756b0ebd42..ecdda31d27d 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -218,9 +218,10 @@ class HrExpense(models.Model): raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name)) emp_account = expense.employee_id.address_home_id.property_account_payable_id.id + aml_name = expense.employee_id.name + ': ' + expense.name.split('\n')[0][:64] move_lines.append({ 'type': 'dest', - 'name': expense.employee_id.name, + 'name': aml_name, 'price': total, 'account_id': emp_account, 'date_maturity': acc_date, @@ -253,9 +254,10 @@ class HrExpense(models.Model): if not account: raise UserError(_('Please configure Default Expense account for Product expense: `property_account_expense_categ_id`.')) + aml_name = expense.employee_id.name + ': ' + expense.name.split('\n')[0][:64] move_line = { 'type': 'src', - 'name': expense.name.split('\n')[0][:64], + 'name': aml_name, 'price_unit': expense.unit_amount, 'quantity': expense.quantity, 'price': expense.total_amount, diff --git a/addons/hr_payroll/models/hr_payroll.py b/addons/hr_payroll/models/hr_payroll.py index c2700586b3a..dc8ac6d0569 100644 --- a/addons/hr_payroll/models/hr_payroll.py +++ b/addons/hr_payroll/models/hr_payroll.py @@ -388,9 +388,10 @@ class HrPayslip(models.Model): return localdict class BrowsableObject(object): - def __init__(self, employee_id, dict): + def __init__(self, employee_id, dict, env): self.employee_id = employee_id self.dict = dict + self.env = env def __getattr__(self, attr): return attr in self.dict and self.dict.__getitem__(attr) or 0.0 @@ -455,11 +456,11 @@ class HrPayslip(models.Model): for input_line in payslip.input_line_ids: inputs_dict[input_line.code] = input_line - categories = BrowsableObject(payslip.employee_id.id, {}) - inputs = InputLine(payslip.employee_id.id, inputs_dict) - worked_days = WorkedDays(payslip.employee_id.id, worked_days_dict) - payslips = Payslips(payslip.employee_id.id, payslip) - rules = BrowsableObject(payslip.employee_id.id, rules_dict) + categories = BrowsableObject(payslip.employee_id.id, {}, self.env) + inputs = InputLine(payslip.employee_id.id, inputs_dict, self.env) + worked_days = WorkedDays(payslip.employee_id.id, worked_days_dict, self.env) + payslips = Payslips(payslip.employee_id.id, payslip, self.env) + rules = BrowsableObject(payslip.employee_id.id, rules_dict, self.env) baselocaldict = {'categories': categories, 'rules': rules, 'payslip': payslips, 'worked_days': worked_days, 'inputs': inputs} #get the ids of the structures on the contracts and their parent id as well diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 80cbb50eee6..e8e3bbccd2f 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -464,7 +464,7 @@ class PaymentTransaction(models.Model): 'partner_zip': partner and partner.zip or False, 'partner_address': _partner_format_address(partner and partner.street or '', partner and partner.street2 or ''), 'partner_city': partner and partner.city or False, - 'partner_country_id': partner and partner.country_id.id or self._default_partner_country_id(), + 'partner_country_id': partner and partner.country_id.id or self._get_default_partner_country_id(), 'partner_phone': partner and partner.phone or False, }} return {} diff --git a/addons/payment_stripe/static/src/js/stripe.js b/addons/payment_stripe/static/src/js/stripe.js index ca2d6eb9ad2..022e279f1de 100644 --- a/addons/payment_stripe/static/src/js/stripe.js +++ b/addons/payment_stripe/static/src/js/stripe.js @@ -42,11 +42,12 @@ odoo.define('payment_stripe.stripe', function(require) { $(this).attr('disabled','disabled'); var $form = $(e.currentTarget).parents('form'); - var acquirer_id = $(e.currentTarget).parents('div.oe_sale_acquirer_button').first().data('id'); + var acquirer_id = $(e.currentTarget).closest('div.oe_sale_acquirer_button,div.oe_quote_acquirer_button').data('id'); if (! acquirer_id) { return false; } + e.preventDefault(); ajax.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', {}).then(function (data) { $form.html(data); handler.open({ @@ -55,7 +56,6 @@ odoo.define('payment_stripe.stripe', function(require) { currency: $("input[name='currency']").val(), amount: $("input[name='amount']").val()*100 }); - e.preventDefault(); }); }); diff --git a/addons/point_of_sale/data/point_of_sale_demo.xml b/addons/point_of_sale/data/point_of_sale_demo.xml index 174ca4edaf0..8e8d7b05681 100644 --- a/addons/point_of_sale/data/point_of_sale_demo.xml +++ b/addons/point_of_sale/data/point_of_sale_demo.xml @@ -141,6 +141,7 @@ + diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py index 5cf2f86dea5..dc158c76226 100644 --- a/addons/purchase/report/purchase_report.py +++ b/addons/purchase/report/purchase_report.py @@ -5,7 +5,7 @@ # Please note that these reports are not multi-currency !!! # -from odoo import api, fields, models +from odoo import api, fields, models, tools class PurchaseReport(models.Model): @@ -50,8 +50,9 @@ class PurchaseReport(models.Model): @api.model_cr def init(self): + tools.drop_view_if_exists(self._cr, 'purchase_report') self._cr.execute(""" - create or replace view purchase_report as ( + create view purchase_report as ( WITH currency_rate as (%s) select min(l.id) as id, diff --git a/addons/rating_project/data/project_data.xml b/addons/rating_project/data/project_data.xml index 5f7d4a09b7a..d60e24ef33c 100644 --- a/addons/rating_project/data/project_data.xml +++ b/addons/rating_project/data/project_data.xml @@ -53,7 +53,7 @@

We appreciate your feedback. It helps us to improve continuously.

- +

diff --git a/addons/rating_project/models/project.py b/addons/rating_project/models/project.py index 345576768ae..5c4eb6fc119 100644 --- a/addons/rating_project/models/project.py +++ b/addons/rating_project/models/project.py @@ -4,6 +4,7 @@ from datetime import timedelta from odoo import api, fields, models +from odoo.tools.safe_eval import safe_eval class ProjectTaskType(models.Model): @@ -104,7 +105,9 @@ class Project(models.Model): def action_view_task_rating(self): """ return the action to see all the rating about the tasks of the project """ action = self.env['ir.actions.act_window'].for_xml_id('rating', 'action_view_rating') - return dict(action, domain=[('res_id', 'in', self.tasks.ids), ('res_model', '=', 'project.task')]) + action_domain = safe_eval(action['domain']) if action['domain'] else [] + domain = action_domain + [('res_id', 'in', self.tasks.ids), ('res_model', '=', 'project.task')] + return dict(action, domain=domain) @api.multi def action_view_all_rating(self): diff --git a/addons/rating_project_issue/models/project_issue.py b/addons/rating_project_issue/models/project_issue.py index 5b2b126cad2..047edd6441f 100644 --- a/addons/rating_project_issue/models/project_issue.py +++ b/addons/rating_project_issue/models/project_issue.py @@ -4,6 +4,7 @@ from datetime import timedelta from odoo import api, fields, models +from odoo.tools.safe_eval import safe_eval class ProjectIssue(models.Model): @@ -28,7 +29,6 @@ class ProjectIssue(models.Model): return super(ProjectIssue, self).rating_apply(rate, token=token, feedback=feedback, subtype="rating_project_issue.mt_issue_rating") - class Stage(models.Model): _inherit = ['project.task.type'] @@ -45,7 +45,7 @@ class Project(models.Model): def _send_rating_mail(self): super(Project, self)._send_rating_mail() for project in self: - project.issue_ids._send_issue_rating_mail() + self.env['project.issue'].search([('project_id', '=', project.id)])._send_issue_rating_mail() @api.depends('percentage_satisfaction_task', 'percentage_satisfaction_issue') def _compute_percentage_satisfaction_project(self): @@ -58,7 +58,7 @@ class Project(models.Model): activity_great = activity_task['great'] activity_sum = sum(activity_task.values()) if project.use_issues: - activity_issue = project.issue_ids.rating_get_grades(domain) + activity_issue = self.env['project.issue'].search([('project_id', '=', project.id)]).rating_get_grades(domain) activity_great += activity_issue['great'] activity_sum += sum(activity_issue.values()) project.percentage_satisfaction_project = activity_great * 100 / activity_sum if activity_sum else -1 @@ -77,7 +77,9 @@ class Project(models.Model): """ return the action to see all the rating about the issues of the project """ action = self.env['ir.actions.act_window'].for_xml_id('rating', 'action_view_rating') issues = self.env['project.issue'].search([('project_id', 'in', self.ids)]) - return dict(action, domain=[('res_id', 'in', issues.ids), ('res_model', '=', 'project.issue')]) + action_domain = safe_eval(action['domain']) if action['domain'] else [] + domain = action_domain + [('res_id', 'in', issues.ids), ('res_model', '=', 'project.issue')] + return dict(action, domain=domain) @api.multi def action_view_all_rating(self): diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 7ae10fe6bfc..b3cbaad92a3 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -108,6 +108,15 @@ class PickingType(models.Model): res.append((picking_type.id, name)) return res + @api.model + def name_search(self, name, args=None, operator='ilike', limit=100): + args = args or [] + domain = [] + if name: + domain = ['|', ('name', operator, name), ('warehouse_id.name', operator, name)] + picks = self.search(domain + args, limit=limit) + return picks.name_get() + @api.onchange('code') def onchange_picking_code(self): if self.code == 'incoming': diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py index a81b0260253..6f0c4e7a351 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -40,7 +40,7 @@ class ProductTemplate(models.Model): @api.one @api.depends('property_valuation', 'categ_id.property_valuation') def _compute_valuation_type(self): - self.valuation = self.property_valuation if self.property_valuation else self.categ_id.property_valuation + self.valuation = self.property_valuation or self.categ_id.property_valuation @api.one def _set_valuation_type(self): @@ -49,7 +49,7 @@ class ProductTemplate(models.Model): @api.one @api.depends('property_cost_method', 'categ_id.property_cost_method') def _compute_cost_method(self): - self.cost_method = self.property_cost_method if self.property_cost_method else self.categ_id.property_cost_method + self.cost_method = self.property_cost_method or self.categ_id.property_cost_method @api.one def _set_cost_method(self): @@ -57,9 +57,8 @@ class ProductTemplate(models.Model): @api.onchange('type') def onchange_type_valuation(self): - if self.type != 'product': - self.valuation = 'manual_periodic' - return {} + # TO REMOVE IN MASTER + pass @api.multi def _get_product_accounts(self): @@ -90,9 +89,8 @@ class ProductProduct(models.Model): @api.onchange('type') def onchange_type_valuation(self): - if self.type != 'product': - self.valuation = 'manual_periodic' - return {} + # TO REMOVE IN MASTER + pass @api.multi def do_change_standard_price(self, new_price, account_id): diff --git a/addons/stock_account/views/product_views.xml b/addons/stock_account/views/product_views.xml index 94f9bcac870..4ba2938c0ea 100644 --- a/addons/stock_account/views/product_views.xml +++ b/addons/stock_account/views/product_views.xml @@ -33,8 +33,8 @@ - - + + diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js index cdf2ff18c63..e92437d1551 100644 --- a/addons/web/static/src/js/views/list_view.js +++ b/addons/web/static/src/js/views/list_view.js @@ -1098,7 +1098,7 @@ ListView.List = Class.extend({ } } return column.format(record.toForm().data, { - model: this.model, + model: this.dataset.model, id: record.get('id') }); }, diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js index 101c3463e6e..28bfe83a345 100644 --- a/addons/web_editor/static/src/js/summernote.js +++ b/addons/web_editor/static/src/js/summernote.js @@ -2302,13 +2302,16 @@ eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) { } }; -options.onPaste = function (e) { - e.preventDefault(); // FIXME this does not work with our custom out-dated summernote. - document.execCommand("undo"); // FIXME ... so use this hack until we decide to do something about it. - - var pastedText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text'); - var formattedText = pastedText.replace(/([^.!?:;])\r?\n/g, "$1").trim(); // Remove linebreaks which are not at the end of a sentence - document.execCommand("insertText", false, formattedText); +// override summernote clipboard functionality +eventHandler.modules.clipboard.attach = function(layoutInfo) { + var $editable = layoutInfo.editable(); + $editable.on('paste', function(e) { + e.preventDefault(); + $editable.data('NoteHistory').recordUndo($editable); + var pastedText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text'); + var formattedText = pastedText.replace(/([^.!?:;])\r?\n/g, "$1").trim(); // Remove linebreaks which are not at the end of a sentence + document.execCommand("insertText", false, formattedText); + }); }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index b1a0fff4e10..3edf0313e3f 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -166,14 +166,13 @@ class Http(models.AbstractModel): langs = [lg[0] for lg in request.website.get_languages()] path = request.httprequest.path.split('/') if first_pass: + is_a_bot = cls.is_a_bot() nearest_lang = not func and cls.get_nearest_lang(path[1]) url_lang = nearest_lang and path[1] preferred_lang = ((cook_lang if cook_lang in langs else False) - or cls.get_nearest_lang(request.lang) + or (not is_a_bot and cls.get_nearest_lang(request.lang)) or request.website.default_lang_code) - is_a_bot = cls.is_a_bot() - request.lang = context['lang'] = nearest_lang or preferred_lang # if lang in url but not the displayed or default language --> change or remove # or no lang in url, and lang to dispay not the default language --> add lang diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py index e21696d63b2..a466dcf3701 100644 --- a/addons/website_blog/controllers/main.py +++ b/addons/website_blog/controllers/main.py @@ -4,6 +4,9 @@ import json import werkzeug import itertools +import pytz +import babel.dates +from collections import OrderedDict from odoo import http, fields, _ from odoo.addons.website.controllers.main import QueryURL @@ -29,13 +32,20 @@ class WebsiteBlog(http.Controller): (r, label) = group['post_date'] start, end = r.split('/') group['post_date'] = label - group['date_begin'] = fields.Date.to_string(self._to_date(start)) - group['date_end'] = fields.Date.to_string(self._to_date(end)) - group['month'] = self._to_date(start).strftime("%B") - group['year'] = self._to_date(start).strftime("%Y") - return {year: [m for m in months] for year, months in itertools.groupby(groups, lambda g: g['year'])} + group['date_begin'] = start + group['date_end'] = end + + locale = request.context.get('lang', 'en_US') + start = pytz.UTC.localize(fields.Datetime.from_string(start)) + tzinfo = pytz.timezone(request.context.get('tz', 'utc') or 'utc') + + group['month'] = babel.dates.format_datetime(start, format='MMMM', tzinfo=tzinfo, locale=locale) + group['year'] = babel.dates.format_datetime(start, format='YYYY', tzinfo=tzinfo, locale=locale) + + return OrderedDict((year, [m for m in months]) for year, months in itertools.groupby(groups, lambda g: g['year'])) def _to_date(self, dt): + # TODO remove me in master/saas-14 return fields.Date.from_string(dt) @http.route([ diff --git a/addons/website_event/views/website_event_templates.xml b/addons/website_event/views/website_event_templates.xml index b205a3a58b0..918c59e3d1e 100644 --- a/addons/website_event/views/website_event_templates.xml +++ b/addons/website_event/views/website_event_templates.xml @@ -241,7 +241,7 @@

@@ -342,7 +342,7 @@
@@ -521,7 +521,7 @@ to
diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py index 0fcb9485d76..01d36059fad 100644 --- a/addons/website_quote/controllers/main.py +++ b/addons/website_quote/controllers/main.py @@ -30,6 +30,7 @@ class sale_quote(http.Controller): if not Order: return request.render('website.404') + request.session['sale_order_id'] = Order.id days = 0 if Order.validity_date: diff --git a/addons/website_quote/views/website_quote_templates.xml b/addons/website_quote/views/website_quote_templates.xml index 4865662ff70..d9cff518c86 100644 --- a/addons/website_quote/views/website_quote_templates.xml +++ b/addons/website_quote/views/website_quote_templates.xml @@ -701,7 +701,12 @@

- + + + + + + diff --git a/doc/cla/individual/Nowheresly.md b/doc/cla/individual/Nowheresly.md new file mode 100644 index 00000000000..51ea23e893f --- /dev/null +++ b/doc/cla/individual/Nowheresly.md @@ -0,0 +1,11 @@ +Luxembourg, 2016-10-26 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Sylvère Richard sylvere.richard@gmail.com https://github.com/Nowheresly diff --git a/odoo/fields.py b/odoo/fields.py index 36086c0dfe2..e5cd453ed1d 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -301,7 +301,7 @@ class Field(object): 'args': EMPTY_DICT, # the parameters given to __init__() '_attrs': EMPTY_DICT, # the field's non-slot attributes '_module': None, # the field's module name - 'setup_full_done': False, # whether the field has been fully setup + '_setup_done': None, # the field's setup state: None, 'base' or 'full' 'automatic': False, # whether the field is automatically created ("magic" field) 'inherited': False, # whether the field is inherited (_inherits) @@ -345,7 +345,7 @@ class Field(object): kwargs['string'] = string args = {key: val for key, val in kwargs.iteritems() if val is not Default} self.args = args or EMPTY_DICT - self.setup_full_done = False + self._setup_done = None def new(self, **kwargs): """ Return a field of the same type as ``self``, with its own parameters. """ @@ -397,14 +397,15 @@ class Field(object): def setup_base(self, model, name): """ Base setup: things that do not depend on other models/fields. """ - if self.setup_full_done and not self.related: + if self._setup_done and not self.related: # optimization for regular fields: keep the base setup - self.setup_full_done = False + self._setup_done = 'base' else: # do the base setup from scratch self._setup_attrs(model, name) if not self.related: self._setup_regular_base(model) + self._setup_done = 'base' # # Setup field parameter attributes @@ -483,12 +484,12 @@ class Field(object): def setup_full(self, model): """ Full setup: everything else, except recomputation triggers. """ - if not self.setup_full_done: + if self._setup_done != 'full': if not self.related: self._setup_regular_full(model) else: self._setup_related_full(model) - self.setup_full_done = True + self._setup_done = 'full' # # Setup of non-related fields diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 0d0d352f670..883904cd8c1 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -86,6 +86,8 @@ class BaseCase(unittest.TestCase): initialized by subclasses. """ + longMessage = True # more verbose error message by default: https://www.odoo.com/r/Vmh + def cursor(self): return self.registry.cursor() diff --git a/odoo/tools/config.py b/odoo/tools/config.py index b05fd1a018b..2473fe53304 100644 --- a/odoo/tools/config.py +++ b/odoo/tools/config.py @@ -111,7 +111,7 @@ class configmanager(object): group.add_option("--addons-path", dest="addons_path", help="specify additional addons paths (separated by commas).", action="callback", callback=self._check_addons_path, nargs=1, type="string") - group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules. Default is 'web,web_kanban'") + group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules.", my_default='web,web_kanban') group.add_option("-D", "--data-dir", dest="data_dir", my_default=_get_default_datadir(), help="Directory where to store Odoo data") @@ -372,6 +372,9 @@ class configmanager(object): # the same for the pidfile if self.options['pidfile'] in ('None', 'False'): self.options['pidfile'] = False + # and the server_wide_modules + if self.options['server_wide_modules'] in ('', 'None', 'False'): + self.options['server_wide_modules'] = 'web,web_kanban' # if defined dont take the configfile value even if the defined value is None keys = ['xmlrpc_interface', 'xmlrpc_port', 'longpolling_port', @@ -460,20 +463,13 @@ class configmanager(object): if len(self.options['language']) > 5: raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE') - # server_wide_modules defaults to web,web_kanban if empty or unset - server_wide_modules = self.options['server_wide_modules'] = ( - self.options['server_wide_modules'] - if self.options['server_wide_modules'] - else 'web,web_kanban' - ) - if opt.save: self.save() odoo.conf.addons_paths = self.options['addons_path'].split(',') odoo.conf.server_wide_modules = [ - m.strip() for m in server_wide_modules.split(',') + m.strip() for m in self.options['server_wide_modules'].split(',') if m.strip() ] def _is_addons_path(self, path):