diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py index 237f8f06162..8f57d844e0a 100644 --- a/addons/l10n_fr_fec/wizard/account_fr_fec.py +++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py @@ -44,8 +44,8 @@ class AccountFrFec(models.TransientModel): '-' AS PieceRef, %s AS PieceDate, '/' AS EcritureLib, - replace(CASE WHEN COALESCE(sum(aml.balance), 0) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Debit, - replace(CASE WHEN COALESCE(sum(aml.balance), 0) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Credit, + replace(CASE WHEN COALESCE(sum(aml.balance), 0) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit, + replace(CASE WHEN COALESCE(sum(aml.balance), 0) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit, '' AS EcritureLet, '' AS DateLet, %s AS ValidDate, @@ -143,8 +143,8 @@ class AccountFrFec(models.TransientModel): '-' AS PieceRef, %s AS PieceDate, '/' AS EcritureLib, - replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Debit, - replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Credit, + replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit, + replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit, '' AS EcritureLet, '' AS DateLet, %s AS ValidDate, @@ -228,8 +228,8 @@ class AccountFrFec(models.TransientModel): '-' AS PieceRef, %s AS PieceDate, '/' AS EcritureLib, - replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Debit, - replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '999999999999999D99') END, '.', ',') AS Credit, + replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit, + replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit, '' AS EcritureLet, '' AS DateLet, %s AS ValidDate, @@ -290,14 +290,14 @@ class AccountFrFec(models.TransientModel): AS PieceRef, TO_CHAR(am.date, 'YYYYMMDD') AS PieceDate, CASE WHEN aml.name IS NULL THEN '/' ELSE replace(aml.name, '|', '/') END AS EcritureLib, - replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '999999999999999D99') END, '.', ',') AS Debit, - replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '999999999999999D99') END, '.', ',') AS Credit, + replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit, + replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit, CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet, CASE WHEN aml.full_reconcile_id IS NULL THEN '' ELSE TO_CHAR(rec.create_date, 'YYYYMMDD') END AS DateLet, TO_CHAR(am.date, 'YYYYMMDD') AS ValidDate, CASE WHEN aml.amount_currency IS NULL OR aml.amount_currency = 0 THEN '' - ELSE replace(to_char(aml.amount_currency, '999999999999999D99'), '.', ',') + ELSE replace(to_char(aml.amount_currency, '000000000000000D99'), '.', ',') END AS Montantdevise, CASE WHEN aml.currency_id IS NULL THEN '' ELSE rc.name END AS Idevise FROM diff --git a/addons/mass_mailing/static/src/less/themes/theme_default.less b/addons/mass_mailing/static/src/less/themes/theme_default.less index cfd1264e0c7..adfa4bba5ce 100644 --- a/addons/mass_mailing/static/src/less/themes/theme_default.less +++ b/addons/mass_mailing/static/src/less/themes/theme_default.less @@ -50,6 +50,7 @@ // ===== Layout ===== .o_layout { overflow: hidden; + box-sizing: content-box; width: 94%; min-height: 100%; background-color: @o_mm_def_color_alpha; diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 2ffae162cf4..6334bd198c0 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -18,9 +18,11 @@ class StockMoveLots(models.Model): 'stock.production.lot', 'Lot', domain="[('product_id', '=', product_id)]") lot_produced_id = fields.Many2one('stock.production.lot', 'Finished Lot') - lot_produced_qty = fields.Float('Quantity Finished Product', help="Informative, not used in matching") - quantity = fields.Float('To Do', default=1.0) - quantity_done = fields.Float('Done') + lot_produced_qty = fields.Float( + 'Quantity Finished Product', digits=dp.get_precision('Product Unit of Measure'), + help="Informative, not used in matching") + quantity = fields.Float('To Do', default=1.0, digits=dp.get_precision('Product Unit of Measure')) + quantity_done = fields.Float('Done', digits=dp.get_precision('Product Unit of Measure')) product_id = fields.Many2one( 'product.product', 'Product', readonly=True, related="move_id.product_id", store=True) diff --git a/addons/payment_stripe/static/src/js/stripe.js b/addons/payment_stripe/static/src/js/stripe.js index 14e19775747..9db27aea858 100644 --- a/addons/payment_stripe/static/src/js/stripe.js +++ b/addons/payment_stripe/static/src/js/stripe.js @@ -68,6 +68,7 @@ odoo.define('payment_stripe.stripe', function(require) { e.preventDefault(); if ($('.o_website_payment').length !== 0) { var currency = $("input[name='currency']").val(); + var currency_id = $("input[name='currency_id']").val(); var amount = parseFloat($("input[name='amount']").val() || '0.0'); if (!_.contains(int_currencies, currency)) { amount = amount*100; @@ -76,7 +77,7 @@ odoo.define('payment_stripe.stripe', function(require) { ajax.jsonRpc('/website_payment/transaction', 'call', { reference: $("input[name='invoice_num']").val(), amount: amount, - currency_id: currency, + currency_id: currency_id, acquirer_id: acquirer_id }) handler.open({ diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 9bd060d39c0..64adca3ce49 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -17,6 +17,7 @@ import operator import os import re import sys +import tempfile import time import werkzeug import werkzeug.utils @@ -38,6 +39,7 @@ from odoo.http import content_disposition, dispatch_rpc, request, \ serialize_exception as _serialize_exception from odoo.exceptions import AccessError, UserError from odoo.models import check_method_name +from odoo.service import db _logger = logging.getLogger(__name__) @@ -710,12 +712,15 @@ class Database(http.Controller): @http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False) def restore(self, master_pwd, backup_file, name, copy=False): try: - data = base64.b64encode(backup_file.read()) - dispatch_rpc('db', 'restore', [master_pwd, name, data, str2bool(copy)]) + with tempfile.NamedTemporaryFile(delete=False) as data_file: + backup_file.save(data_file) + db.restore_db(name, data_file.name, str2bool(copy)) return http.local_redirect('/web/database/manager') except Exception, e: error = "Database restore error: %s" % (str(e) or repr(e)) return self._render_template(error=error) + finally: + os.unlink(data_file.name) @http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False) def change_password(self, master_pwd, master_pwd_new): diff --git a/addons/web/static/src/js/framework/utils.js b/addons/web/static/src/js/framework/utils.js index dd35364a12f..45dda977dc8 100644 --- a/addons/web/static/src/js/framework/utils.js +++ b/addons/web/static/src/js/framework/utils.js @@ -288,7 +288,7 @@ function human_size (size) { size /= 1024; ++i; } - return size.toFixed(2) + ' ' + units[i]; + return size.toFixed(2) + ' ' + units[i].trim(); } /** diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js index 866d0d68e84..5f28c9510bb 100644 --- a/addons/web_editor/static/src/js/rte.summernote.js +++ b/addons/web_editor/static/src/js/rte.summernote.js @@ -398,7 +398,9 @@ eventHandler.modules.imageDialog.showImageDialog = function ($editable) { if (r.sc.tagName && r.sc.childNodes.length) { r.sc = r.sc.childNodes[r.so]; } - new widgets.MediaDialog(null, {}, $editable, dom.isImg(r.sc) ? r.sc : null).open(); + new widgets.MediaDialog(null, {}, $editable, $(r.sc).parents().addBack().filter(function (i, el) { + return dom.isImg(el); + })[0]).open(); return new $.Deferred().reject(); }; $.summernote.pluginEvents.alt = function (event, editor, layoutInfo, sorted) { diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 843db91a50c..92ab292e19c 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -61,7 +61,7 @@ class Http(models.AbstractModel): if not request.session.uid: env = api.Environment(request.cr, SUPERUSER_ID, request.context) website = env['website'].get_current_website() - if website: + if website and website.user_id: request.uid = website.user_id.id else: request.uid = env.ref('base.public_user').id diff --git a/addons/website/models/website.py b/addons/website/models/website.py index e93d6f563fb..9ce4d0a07e7 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -177,7 +177,7 @@ class Website(models.Model): google_management_client_id = fields.Char('Google Client ID') google_management_client_secret = fields.Char('Google Client Secret') - user_id = fields.Many2one('res.users', string='Public User', default=lambda self: self.env.ref('base.public_user').id) + user_id = fields.Many2one('res.users', string='Public User', required=True, default=lambda self: self.env.ref('base.public_user').id) cdn_activated = fields.Boolean('Activate CDN for assets') cdn_url = fields.Char('CDN Base URL', default='') cdn_filters = fields.Text('CDN Filters', default=lambda s: '\n'.join(DEFAULT_CDN_FILTERS), help="URL matching those filters will be rewritten using the CDN Base URL") diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py index 8e4af85e398..ce9257387a1 100644 --- a/addons/website_blog/controllers/main.py +++ b/addons/website_blog/controllers/main.py @@ -173,7 +173,9 @@ class WebsiteBlog(http.Controller): v = {} v['blog'] = blog v['base_url'] = request.env['ir.config_parameter'].sudo().get_param('web.base.url') - v['posts'] = request.env['blog.post'].search([('blog_id','=', blog.id)], limit=min(int(limit), 50)) + v['posts'] = request.env['blog.post'].search([('blog_id','=', blog.id)], + limit=min(int(limit), 50), + order="post_date DESC") v['html2plaintext'] = html2plaintext r = request.render("website_blog.blog_feed", v, headers=[('Content-Type', 'application/atom+xml')]) return r diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py index d8daea8a6ff..65bd0c472fe 100644 --- a/addons/website_blog/models/website_blog.py +++ b/addons/website_blog/models/website_blog.py @@ -154,7 +154,7 @@ class BlogPost(models.Model): write_date = fields.Datetime('Last Modified on', index=True, readonly=True) write_uid = fields.Many2one('res.users', 'Last Contributor', index=True, readonly=True) author_avatar = fields.Binary(related='author_id.image_small', string="Avatar") - visits = fields.Integer('No of Views') + visits = fields.Integer('No of Views', copy=False) ranking = fields.Float(compute='_compute_ranking', string='Ranking') @api.multi diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml index bcaa3a4c2aa..49a5210624f 100644 --- a/addons/website_blog/views/website_blog_templates.xml +++ b/addons/website_blog/views/website_blog_templates.xml @@ -552,14 +552,14 @@ <link t-att-href="'%s/blog/%s' % (base_url ,blog.id)"/> <id t-esc="'%s/blog/%s' % (base_url, blog.id)"/> - <updated t-esc="(posts[0] if posts else blog).write_date.replace(' ', 'T') + 'Z'"/> + <updated t-esc="(posts[0] if posts else blog).post_date.replace(' ', 'T') + 'Z'"/> <entry t-foreach="posts" t-as="post"> <title t-esc="post.name"/> <link t-att-href="'%s%s' % (base_url, post.website_url)"/> <id t-esc="'%s%s' % (base_url, post.website_url)"/> <author><name t-esc="post.sudo().author_id.name"/></author> <summary t-esc="html2plaintext(post.teaser)"/> - <updated t-esc="post.write_date.replace(' ', 'T') + 'Z'"/> + <updated t-esc="post.post_date.replace(' ', 'T') + 'Z'"/> </entry> </feed> </template> diff --git a/addons/website_event_track/controllers/main.py b/addons/website_event_track/controllers/main.py index 3463ec0b3be..0a592053453 100644 --- a/addons/website_event_track/controllers/main.py +++ b/addons/website_event_track/controllers/main.py @@ -67,11 +67,13 @@ class WebsiteEventTrackController(http.Controller): @http.route(['''/event/<model("event.event", "[('website_track','=',1)]"):event>/agenda'''], type='http', auth="public", website=True) def event_agenda(self, event, tag=None, **post): + local_tz = pytz.timezone(event.date_tz or 'UTC') days_tracks = collections.defaultdict(lambda: []) for track in event.track_ids.sorted(lambda track: (track.date, bool(track.location_id))): if not track.date: continue - days_tracks[track.date[:10]].append(track) + date = fields.Datetime.from_string(track.date).replace(tzinfo=pytz.utc).astimezone(local_tz) + days_tracks[str(date)[:10]].append(track) days = {} tracks_by_days = {} diff --git a/doc/cla/corporate/meta-it.md b/doc/cla/corporate/meta-it.md index 01b33f35c8b..f321433af3f 100644 --- a/doc/cla/corporate/meta-it.md +++ b/doc/cla/corporate/meta-it.md @@ -12,4 +12,4 @@ Robin Lucbenet robin@meta-it.fr https://github.com/n1b0r List of contributors: -Guillaume Masson guillaume.masson@meta-it.fr +Guillaume Masson guillaume.masson@meta-it.fr https://github.com/metaminux diff --git a/doc/cla/individual/rcastro-tyc.md b/doc/cla/individual/rcastro-tyc.md new file mode 100644 index 00000000000..ea6de8af17b --- /dev/null +++ b/doc/cla/individual/rcastro-tyc.md @@ -0,0 +1,11 @@ +Costa Rica, 2018-01-11 + +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, + +Randall Castro randall_castro@me.com https://github.com/rcastro-tyc diff --git a/doc/reference/testing.rst b/doc/reference/testing.rst index ab32a9d3e0a..664540c5e0d 100644 --- a/doc/reference/testing.rst +++ b/doc/reference/testing.rst @@ -75,6 +75,10 @@ in each method:: # other tests... +.. note:: + + Test methods must start with ``test_`` + Running tests ------------- diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index b174b1c45e2..26cab3653b1 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -576,7 +576,13 @@ class IrActionsServer(models.Model): for exp in action.fields_lines: res[exp.col1.name] = exp.eval_value(eval_context=eval_context)[exp.id] - self.env[action.model_id.model].browse(self._context.get('active_id')).write(res) + if self._context.get('onchange_self'): + record_cached = self._context['onchange_self'] + for field, new_value in res.iteritems(): + record_cached[field] = new_value + else: + self.env[action.model_id.model].browse(self._context.get('active_id')).write(res) + @api.model def run_action_object_create(self, action, eval_context=None): @@ -671,6 +677,8 @@ class IrActionsServer(models.Model): elif hasattr(self, 'run_action_%s' % action.state): active_id = self._context.get('active_id') + if not active_id and self._context.get('onchange_self'): + active_id = self._context['onchange_self']._origin.id active_ids = self._context.get('active_ids', [active_id] if active_id else []) for active_id in active_ids: # run context dedicated to a particular active_id diff --git a/odoo/addons/base/ir/ir_cron.py b/odoo/addons/base/ir/ir_cron.py index 96f363428fe..ac5b0fd7ac9 100644 --- a/odoo/addons/base/ir/ir_cron.py +++ b/odoo/addons/base/ir/ir_cron.py @@ -213,7 +213,7 @@ class ir_cron(models.Model): _logger.debug("Job `%s` already executed by another process/thread. skipping it", job['cron_name']) continue # Got the lock on the job row, run its code - _logger.debug('Starting job `%s`.', job['cron_name']) + _logger.info('Starting job `%s`.', job['cron_name']) job_cr = db.cursor() try: registry = odoo.registry(db_name) diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py index 7f8578c2b7b..23cf31dbae3 100644 --- a/odoo/addons/base/ir/ir_ui_view.py +++ b/odoo/addons/base/ir/ir_ui_view.py @@ -227,7 +227,7 @@ actual arch. if fullpath: arch_fs = get_view_arch_from_file(fullpath, view.xml_id) # replace %(xml_id)s, %(xml_id)d, %%(xml_id)s, %%(xml_id)d by the res_id - arch_fs = arch_fs and resolve_external_ids(arch_fs, view.xml_id) + arch_fs = arch_fs and resolve_external_ids(arch_fs, view.xml_id).replace('%%', '%') else: _logger.warning("View %s: Full path [%s] cannot be found.", view.xml_id, view.arch_fs) arch_fs = False diff --git a/odoo/service/db.py b/odoo/service/db.py index 5b25ef72d8f..3c66d84126e 100644 --- a/odoo/service/db.py +++ b/odoo/service/db.py @@ -213,9 +213,13 @@ def dump_db(db_name, stream, backup_format='zip'): return stdout def exp_restore(db_name, data, copy=False): + def chunks(d, n=8192): + for i in range(0, len(d), n): + yield d[i:i+n] data_file = tempfile.NamedTemporaryFile(delete=False) try: - data_file.write(data.decode('base64')) + for chunk in chunks(data): + data_file.write(chunk.decode('base64')) data_file.close() restore_db(db_name, data_file.name, copy=copy) finally: