diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml index 75f4f42ece8..84110e25070 100644 --- a/addons/hr_timesheet/views/project_views.xml +++ b/addons/hr_timesheet/views/project_views.xml @@ -81,7 +81,7 @@ Your project is linked to an inactive analytic account. Change it, or reactivate the current one to timesheet on the project. - + @@ -90,6 +90,50 @@ + + + + + + + + + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + + +
+
+
+
+
+
+
+ + + + + + + + + + +
diff --git a/addons/mail/static/src/js/utils.js b/addons/mail/static/src/js/utils.js index 16507035982..d1271bec2f3 100644 --- a/addons/mail/static/src/js/utils.js +++ b/addons/mail/static/src/js/utils.js @@ -18,7 +18,7 @@ function _parse_and_transform(nodes, transform_function) { // Suggested URL Javascript regex of http://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url // Adapted to make http(s):// not required if (and only if) www. is given. So `should.notmatch` does not match. -var url_regexp = /\b(?:https?:\/\/\d{1,3}(?:\.\d{1,3}){3}|(?:https?:\/\/|(?:www\.))[-a-z0-9@:%._\+~#=]{2,256}\.[a-z]{2,13})\b(?:[-a-z0-9@:%_\+.~#?&'$//=]*)/gi; +var url_regexp = /\b(?:https?:\/\/\d{1,3}(?:\.\d{1,3}){3}|(?:https?:\/\/|(?:www\.))[-a-z0-9@:%._\+~#=]{2,256}\.[a-z]{2,13})\b(?:[-a-z0-9@:%_\+.~#?&'$//=;]*)/gi; function linkify(text, attrs) { attrs = attrs || {}; if (attrs.target === undefined) { diff --git a/addons/product/models/product.py b/addons/product/models/product.py index dbd39ceafce..68adb54aaf4 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -385,7 +385,7 @@ class ProductProduct(models.Model): # Prefetch the fields used by the `name_get`, so `browse` doesn't fetch other fields # Use `load=False` to not call `name_get` for the `product_tmpl_id` - self.sudo().read(['name', 'default_code', 'product_tmpl_id', 'attribute_value_ids'], load=False) + self.sudo().read(['name', 'default_code', 'product_tmpl_id', 'attribute_value_ids', 'attribute_line_ids'], load=False) product_template_ids = self.sudo().mapped('product_tmpl_id').ids @@ -402,7 +402,7 @@ class ProductProduct(models.Model): supplier_info_by_template.setdefault(r.product_tmpl_id, []).append(r) for product in self.sudo(): # display only the attributes with multiple possible values on the template - variable_attributes = product.attribute_value_ids.filtered(lambda v: len(v.attribute_id.value_ids) > 1).mapped('attribute_id') + variable_attributes = product.attribute_line_ids.filtered(lambda l: len(l.value_ids) > 1).mapped('attribute_id') variant = product.attribute_value_ids._variant_name(variable_attributes) name = variant and "%s (%s)" % (product.name, variant) or product.name diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py index 3674012e091..c1034fe7963 100644 --- a/addons/survey/controllers/main.py +++ b/addons/survey/controllers/main.py @@ -251,6 +251,10 @@ class Survey(http.Controller): def print_survey(self, survey, token=None, **post): '''Display an survey in printable view; if is set, it will grab the answers of the user_input_id that has .''' + + if survey.auth_required and request.env.user == request.website.user_id: + return request.render("survey.auth_required", {'survey': survey, 'token': token}) + return request.render('survey.survey_print', {'survey': survey, 'token': token, diff --git a/addons/web/static/src/js/chrome/search_menus.js b/addons/web/static/src/js/chrome/search_menus.js index e2b1c1bdcbd..054533f5c1b 100644 --- a/addons/web/static/src/js/chrome/search_menus.js +++ b/addons/web/static/src/js/chrome/search_menus.js @@ -191,6 +191,10 @@ return Widget.extend({ icon: 'fa-star', field: { get_context: function () { + var filterContext = filter.context; + if (typeof filter.context === 'string') { + filterContext = pyeval.eval('context', filter.context); + } var sortParsed = JSON.parse(filter.sort || "[]"); var orderedBy = []; _.each(sortParsed, function (sort) { @@ -200,7 +204,7 @@ return Widget.extend({ }); }); - return _.defaults({}, filter.context, {orderedBy : orderedBy}); + return _.defaults({}, filterContext, {orderedBy : orderedBy}); }, get_groupby: function () { return [filter.context]; }, get_domain: function () { return filter.domain; } diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js index 54f469bbc19..c4590c1430b 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -208,7 +208,7 @@ var PivotModel = AbstractModel.extend({ return {has_data: false}; } return { - colGroupBys: this.data.main_col.groupbys, + colGroupBys: this.data.main_col.root.groupbys, context: this.data.context, domain: this.data.domain, fields: this.fields, @@ -217,7 +217,7 @@ var PivotModel = AbstractModel.extend({ mainColWidth: this.data.main_col.width, measures: this.data.measures, rows: !isRaw && this._computeRows(), - rowGroupBys: this.data.main_row.groupbys, + rowGroupBys: this.data.main_row.root.groupbys, sortedColumn: this.data.sorted_column, }; }, @@ -283,22 +283,19 @@ var PivotModel = AbstractModel.extend({ return this._loadData(); } - var old_row_root = this.data.main_row.root; - var old_col_root = this.data.main_col.root; + var old_row = this.data.main_row; + var old_col = this.data.main_col; return this._loadData().then(function () { - var new_groupby_length; if (!('groupBy' in params) && !('pivot_row_groupby' in (params.context || {}))) { // we only update the row groupbys according to the old groupbys // if we don't have the key 'groupBy' in params. In that case, // we want to have the full open state for the groupbys. - self._updateTree(old_row_root, self.data.main_row.root); - new_groupby_length = self._getHeaderDepth(self.data.main_row.root) - 1; - self.data.main_row.groupbys = old_row_root.groupbys.slice(0, new_groupby_length); + self._updateTree(old_row.root, self.data.main_row.root); + self._updateMainGroupBys(old_row, self.data.main_row); } - self._updateTree(old_col_root, self.data.main_col.root); - new_groupby_length = self._getHeaderDepth(self.data.main_col.root) - 1; - self.data.main_row.groupbys = old_row_root.groupbys.slice(0, new_groupby_length); + self._updateTree(old_col.root, self.data.main_col.root); + self._updateMainGroupBys(old_col, self.data.main_col); }); }, /** @@ -680,9 +677,6 @@ var PivotModel = AbstractModel.extend({ } } - this.data.main_row.groupbys = rowGroupBys; - this.data.main_col.groupbys = colGroupBys; - main_row_header.other_root = main_col_header; main_col_header.other_root = main_row_header; @@ -751,6 +745,19 @@ var PivotModel = AbstractModel.extend({ this._traverseTree(root.children[i], f, arg1, arg2, arg3); } }, + /** + * Updates the groupBys that the main[row | col] holds + * + * @private + * @param {Object} old: The main abstract header before reload + * @param {Object} main: The main abstract header after reload + * + */ + _updateMainGroupBys: function (old, main) { + var new_groupby_length = this._getHeaderDepth(main.root) - 1; + var new_groupby_list = old.root.groupbys.slice(0, new_groupby_length); + main.root.groupbys = new_groupby_list; + }, /** * @param {Object} old_tree * @param {Object} new_tree diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js index 81abfbe0d6c..414305f6f06 100644 --- a/addons/web/static/src/js/widgets/model_field_selector.js +++ b/addons/web/static/src/js/widgets/model_field_selector.js @@ -257,7 +257,6 @@ var ModelFieldSelector = Widget.extend({ if (this.dirty) { this.dirty = false; - this.pages = this.pages.slice(0, this.chain.length || 1); this.trigger_up("field_chain_changed", {chain: this.chain}); } }, diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js index 7500531cdc8..904c517e5de 100644 --- a/addons/web/static/tests/views/pivot_tests.js +++ b/addons/web/static/tests/views/pivot_tests.js @@ -831,6 +831,80 @@ QUnit.module('Views', { pivot.destroy(); }); + QUnit.test('correctly remove pivot_ keys from the context', function (assert) { + assert.expect(5); + + this.data.partner.fields.amount = {string: "Amount", type: "float"}; + + // Equivalent to loading with default filter + var pivot = createView({ + View: PivotView, + model: "partner", + data: this.data, + arch: '' + + '' + + '' + + '', + viewOptions: { + context: { + pivot_measures: ['foo'], + pivot_column_groupby: ['customer'], + pivot_row_groupby: ['product_id'], + }, + }, + }); + + // Equivalent to unload the filter + var reloadParams = { + context: {}, + }; + pivot.reload(reloadParams); + + assert.deepEqual(pivot.getContext(), { + pivot_column_groupby: ['customer'], + pivot_measures: ['foo'], + pivot_row_groupby: ['product_id'], + }, "context should be correct"); + + // Let's get rid of the rows groupBy + pivot.$('tbody .o_pivot_header_cell_opened').click(); + + assert.deepEqual(pivot.getContext(), { + pivot_column_groupby: ['customer'], + pivot_measures: ['foo'], + pivot_row_groupby: [], + }, "context should be correct"); + + // And now, get rid of the col groupby + pivot.$('thead .o_pivot_header_cell_opened').click(); + + assert.deepEqual(pivot.getContext(), { + pivot_column_groupby: [], + pivot_measures: ['foo'], + pivot_row_groupby: [], + }, "context should be correct"); + + pivot.$('tbody .o_pivot_header_cell_closed').click(); + pivot.$('.o_pivot_field_menu li[data-field=product_id] a').click(); + + assert.deepEqual(pivot.getContext(), { + pivot_column_groupby: [], + pivot_measures: ['foo'], + pivot_row_groupby: ['product_id'], + }, "context should be correct"); + + pivot.$('thead .o_pivot_header_cell_closed').click(); + pivot.$('.o_pivot_field_menu li[data-field=customer] a').click(); + + assert.deepEqual(pivot.getContext(), { + pivot_column_groupby: ['customer'], + pivot_measures: ['foo'], + pivot_row_groupby: ['product_id'], + }, "context should be correct"); + + pivot.destroy(); + }); + QUnit.test('correctly uses pivot_ keys from the context', function (assert) { assert.expect(7); diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 0aa3dd24753..f0aae0ee8fe 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -389,7 +389,17 @@ class Web_Editor(http.Controller): # Create a view to extend the template which adds the original file to link the new modified version instead IrUiView = request.env["ir.ui.view"] - view_to_xpath = IrUiView.get_related_views(bundle_xmlid, bundles=True).filtered(lambda v: v.arch.find(url) >= 0) + + def views_linking_url(view): + """ + Returns whether the view arch has some html link tag linked to the url. + + (note: searching for the URL string is not enough as it could appear in a comment or an xpath expression.) + """ + return bool(etree.XML(view.arch).xpath("link[@href='{}']".format(url))) + + view_to_xpath = IrUiView.get_related_views(bundle_xmlid, bundles=True).filtered(views_linking_url) + IrUiView.create(dict( name = custom_url, mode = "extension", diff --git a/doc/cla/individual/i-vyshnevska.md b/doc/cla/individual/i-vyshnevska.md new file mode 100644 index 00000000000..0df4e78944d --- /dev/null +++ b/doc/cla/individual/i-vyshnevska.md @@ -0,0 +1,9 @@ +Ukraine, 2019-01-09 + +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, + +Vyshnevska Iryna i.vyshnevska@mobilunity.com https://github.com/i-vyshnevska/ \ No newline at end of file diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py index 1bd159188d9..33ecb666d40 100644 --- a/odoo/addons/base/models/ir_module.py +++ b/odoo/addons/base/models/ir_module.py @@ -455,7 +455,8 @@ class Module(models.Model): """ modules_to_remove = self.mapped('name') self.env['ir.model.data']._module_data_uninstall(modules_to_remove) - self.write({'state': 'uninstalled', 'latest_version': False}) + # we deactivate prefetching to not try to read a column that has been deleted + self.with_context(prefetch_fields=False).write({'state': 'uninstalled', 'latest_version': False}) return True @api.multi diff --git a/odoo/addons/base/wizard/base_partner_merge.py b/odoo/addons/base/wizard/base_partner_merge.py index 3134a592e75..2416d322ab7 100644 --- a/odoo/addons/base/wizard/base_partner_merge.py +++ b/odoo/addons/base/wizard/base_partner_merge.py @@ -127,14 +127,14 @@ class MergePartnerAutomatic(models.TransientModel): # unique key treated query = """ UPDATE "%(table)s" as ___tu - SET %(column)s = %%s + SET "%(column)s" = %%s WHERE - %(column)s = %%s AND + "%(column)s" = %%s AND NOT EXISTS ( SELECT 1 FROM "%(table)s" as ___tw WHERE - %(column)s = %%s AND + "%(column)s" = %%s AND ___tu.%(value)s = ___tw.%(value)s )""" % query_dic for partner in src_partners: @@ -142,7 +142,7 @@ class MergePartnerAutomatic(models.TransientModel): else: try: with mute_logger('odoo.sql_db'), self._cr.savepoint(): - query = 'UPDATE "%(table)s" SET %(column)s = %%s WHERE %(column)s IN %%s' % query_dic + query = 'UPDATE "%(table)s" SET "%(column)s" = %%s WHERE "%(column)s" IN %%s' % query_dic self._cr.execute(query, (dst_partner.id, tuple(src_partners.ids),)) # handle the recursivity with parent relation