[MERGE] forward port branch 11.0 up to 19558129f0
This commit is contained in:
@@ -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.
|
||||
</div>
|
||||
</group>
|
||||
<field name="timesheet_ids" attrs="{'invisible': [('allow_timesheets', '=', False)]}" context="{'default_project_id': project_id, 'default_name':'', 'default_analytic_account_id': analytic_account_id}">
|
||||
<field name="timesheet_ids" mode="tree,kanban" attrs="{'invisible': [('allow_timesheets', '=', False)]}" context="{'default_project_id': project_id, 'default_name':'', 'default_analytic_account_id': analytic_account_id}">
|
||||
<tree editable="bottom" string="Timesheet Activities" default_order="date">
|
||||
<field name="date"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
@@ -90,6 +90,50 @@
|
||||
<field name="unit_amount" string="Duration" widget="float_time"/>
|
||||
<field name="project_id" invisible="1"/>
|
||||
</tree>
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="date"/>
|
||||
<field name="user_id"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="name"/>
|
||||
<field name="unit_amount"/>
|
||||
<field name="project_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<strong><span><t t-esc="record.employee_id.value"/></span></strong>
|
||||
</div>
|
||||
<div class="col-xs-6 pull-right text-right">
|
||||
<strong><t t-esc="record.date.value"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 text-muted">
|
||||
<span><t t-esc="record.name.value"/></span>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<span class="pull-right text-right">
|
||||
<field name="unit_amount" widget="float_time"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
<form string="Timesheet Activities">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="date"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="employee_id" required="1"/>
|
||||
<field name="name"/>
|
||||
<field name="unit_amount" string="Duration" widget="float_time"/>
|
||||
<field name="project_id" invisible="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
<group attrs="{'invisible': [('allow_timesheets', '=', False)]}">
|
||||
<group class="oe_subtotal_footer oe_right" name="project_hours">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -251,6 +251,10 @@ class Survey(http.Controller):
|
||||
def print_survey(self, survey, token=None, **post):
|
||||
'''Display an survey in printable view; if <token> is set, it will
|
||||
grab the answers of the user_input_id that has <token>.'''
|
||||
|
||||
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,
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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: '<pivot>' +
|
||||
'<field name="date" interval="day" type="col"/>' +
|
||||
'<field name="amount" type="measure"/>' +
|
||||
'</pivot>',
|
||||
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);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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/
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user