[MERGE] Forward-port 9.0 up to 9a3e9291ffa1509f4afa0f35f9d8d71b6e0932d1

This commit is contained in:
Olivier Dony
2015-10-30 10:47:13 +01:00
63 changed files with 18440 additions and 18580 deletions
@@ -27,10 +27,6 @@ var JournalDashboardGraph = kanban_widgets.AbstractField.extend({
margin: {'left': 0, 'right': 0, 'top': 0, 'bottom': 0},
showYAxis: false,
showLegend: false,
tooltips: true,
tooltipContent: function(key, x, y, e, graph) {
return self.create_tooltip(x, y, e);
},
});
self.chart.xAxis
.tickFormat(function(d) {
@@ -55,11 +51,7 @@ var JournalDashboardGraph = kanban_widgets.AbstractField.extend({
.y(function(d) { return d.value })
.showValues(false)
.showYAxis(false)
.margin({'left': 0, 'right': 0, 'top': 0, 'bottom': 40})
.tooltips(true)
.tooltipContent(function(key, x, y, e, graph) {
return self.create_tooltip(x, y, e);
});
.margin({'left': 0, 'right': 0, 'top': 0, 'bottom': 40});
self.chart.xAxis.axisLabel(self.data[0].title);
self.chart.yAxis.tickFormat(d3.format(',.2f'));
@@ -94,23 +86,6 @@ var JournalDashboardGraph = kanban_widgets.AbstractField.extend({
}
},
create_tooltip: function(x, y, e){
var header = _.findWhere(e.series.values, {x: x})
header = header && header.name || x;
var $tooltip = $('<div>').addClass('o_tooltip');
$('<b>')
.addClass('o_tooltip_title')
.html(header)
.appendTo($tooltip)
$('<div>')
.addClass('o_tooltip_content')
.html('Balance ' + y)
.appendTo($tooltip)
return $tooltip[0].outerHTML;
},
destroy: function(){
nv.utils.offWindowResize(this.on_resize);
this._super();
@@ -37,14 +37,6 @@
.o_kanban_graph_section {
.o-position-absolute(auto, 0, 0, 0);
.nvtooltip {
padding: 0 8px;
.o_tooltip_content {
color: @odoo-brand-primary;
font-weight: bold;
}
}
.o_graph_linechart {
> svg {
height: 75px;
@@ -128,7 +128,7 @@
<label for="partner_id" string="Vendor" attrs="{'invisible': [('type','!=','purchase')]}"/>
</div>
<field name="partner_id" string="Partner" nolabel="1"/>
<field name="invoice_id" string="Invoice"/>
<field name="invoice_id" string="Invoice" options="{'no_create': True}"/>
</group>
</group>
<notebook colspan="4">
+9 -11
View File
@@ -47,7 +47,6 @@
<form string="Sales Tags">
<group>
<field name="name"/>
<field name="team_id"/>
<field name="color"/>
</group>
</form>
@@ -60,7 +59,6 @@
<field name="arch" type="xml">
<tree string="Sales Tags">
<field name="name"/>
<field name="team_id"/>
<field name="color"/>
</tree>
</field>
@@ -371,7 +369,7 @@
help="No salesperson"/>
<separator />
<filter string="Important Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<filter string="Archived" domain="[('active', '=', False)]"/>
<filter string="Archived" name="archived" domain="[('active', '=', False)]"/>
<separator />
<filter string="Followup" name="activities"
domain="[('date_action', '&lt;=', datetime.datetime.now().replace(hour=23, minute=59, second=59))]"/>
@@ -380,15 +378,15 @@
name='not_opt_out' domain="[('opt_out', '=', False)]"
help="Leads that did not ask not to be included in mass mailing campaigns"/>
<group expand="0" string="Group By">
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Sales Team" domain="[]" context="{'group_by':'team_id'}"/>
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Salesperson" name="groupby_salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Sales Team" name="groupby_team" domain="[]" context="{'group_by':'team_id'}"/>
<filter string="Stage" name="groupby_stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter name="customer" string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Campaign" domain="[]" context="{'group_by':'campaign_id'}"/>
<filter string="Source" domain="[]" context="{'group_by':'source_id'}"/>
<filter string="Channel" domain="[]" context="{'group_by':'medium_id'}"/>
<filter string="Creation Month" domain="[]" context="{'group_by':'create_date'}"/>
<filter string="Expected Closing" domain="[]" context="{'group_by':'date_deadline'}"/>
<filter string="Campaign" name="groupby_campaign" domain="[]" context="{'group_by':'campaign_id'}"/>
<filter string="Source" name="groupby_source" domain="[]" context="{'group_by':'source_id'}"/>
<filter string="Channel" name="groupby_channel" domain="[]" context="{'group_by':'medium_id'}"/>
<filter string="Creation Month" name="groupby_createmonth" domain="[]" context="{'group_by':'create_date'}"/>
<filter string="Expected Closing" name="groupby_expectclosing" domain="[]" context="{'group_by':'date_deadline'}"/>
</group>
</search>
</field>
+2 -4
View File
@@ -857,11 +857,9 @@ class google_calendar(osv.AbstractModel):
if actSrc == 'GG':
try:
self.delete_an_event(cr, uid, current_event[0], context=context)
except Exception, e:
error = json.loads(e.read())
error_nr = error.get('error', {}).get('code')
except urllib2.HTTPError, e:
# if already deleted from gmail or never created
if error_nr in (404, 410,):
if e.code in (404, 410,):
pass
else:
raise e
+21
View File
@@ -0,0 +1,21 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * im_livechat
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 9.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-10-09 09:20+0000\n"
"PO-Revision-Date: 2015-10-09 09:20+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: im_livechat
#: model:ir.model.fields,field_description:im_livechat.field_im_livechat_report_channel_partner_id
msgid "Opertor"
msgstr "Operator"
@@ -199,7 +199,7 @@ class ImLivechatChannel(models.Model):
if country_code:
country_ids = self.env['res.country'].sudo().search([('code', '=', country_code)])
if country_ids:
country_id = country_ids[0]
country_id = country_ids[0].id
# extract url
url = request.httprequest.headers.get('Referer') or request.httprequest.base_url
# find the match rule for the given country and url
+3 -2
View File
@@ -82,8 +82,9 @@ class MailMail(osv.Model):
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('(<img(?=\s)[^>]*\ssrc=")(/[^/][^"]+)"', _sub_relative2absolute, body)
return match.group(1) + urlparse.urljoin(domain, match.group(2))
body = re.sub('(<img(?=\s)[^>]*\ssrc=")(/[^/][^"]+)', _sub_relative2absolute, body)
body = re.sub(r'(<[^>]+\bstyle="[^"]+\burl\(\'?)(/[^/\'][^\'")]+)', _sub_relative2absolute, body)
# generate tracking URL
if mail.statistics_ids:
+1 -1
View File
@@ -393,7 +393,7 @@ class PaymentTransaction(osv.Model):
custom_method_name = '%s_compute_fees' % acquirer.provider
if hasattr(Acquirer, custom_method_name):
fees = getattr(Acquirer, custom_method_name)(
cr, uid, acquirer.id, values.get('amount', 0.0), values.get('currency_id'), values.get('country_id'), context=None)
cr, uid, acquirer.id, values.get('amount', 0.0), values.get('currency_id'), values.get('partner_country_id'), context=None)
values['fees'] = float_round(fees, 2)
# custom create
@@ -40,8 +40,8 @@
<field name="config_id"/>
<field name="partner_id"/>
<field name="user_id"/>
<field name="partner_id"/>
<field name="user_id"/>
<field name="product_id"/>
<field name="product_categ_id"/>
<group expand="1" string="Group By">
<filter string="Salesperson" name="User" context="{'group_by':'user_id'}"/>
<filter string="Product Category" context="{'group_by':'product_categ_id'}"/>
@@ -1809,6 +1809,11 @@ var PaymentScreenWidget = ScreenWidget.extend({
return;
}
// get rid of payment lines with an amount of 0, because
// since accounting v9 we cannot have bank statement lines
// with an amount of 0
order.clean_empty_paymentlines();
var plines = order.get_paymentlines();
for (var i = 0; i < plines.length; i++) {
if (plines[i].get_type() === 'bank' && plines[i].get_amount() < 0) {
@@ -79,8 +79,7 @@
t-field-options='{"widget": "monetary", "display_currency": "statement.currency_id"}'/>
</td>
<td class="text-right" groups="base.group_multi_currency">
<span t-field="statement.currency"
t-field-options='{"widget": "monetary", "display_currency": "statement.currency"}'/>
<span t-field="statement.currency"/>
</td>
<td class="text-right"><span t-field="statement.state"/></td>
</tr>
+1 -1
View File
@@ -140,8 +140,8 @@ class wizard_user(osv.osv_memory):
wizard_user.user_id.write({'active': True, 'groups_id': [(4, portal.id)]})
# prepare for the signup process
wizard_user.user_id.partner_id.signup_prepare()
self._send_email(cr, uid, wizard_user.id, context)
wizard_user.refresh()
self._send_email(cr, uid, wizard_user.id, context)
else:
# remove the user (if it exists) from the portal group
if wizard_user.user_id and (portal in wizard_user.user_id.groups_id):
+1 -1
View File
@@ -66,7 +66,7 @@
<field name="product_id"/>
<field name="user_id"/>
<group expand="0" string="Extended Filters">
<field name="categ_id"/>
<field name="categ_id" filter_domain="[('categ_id', 'child_of', self)]"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group expand="1" string="Group By">
+1 -1
View File
@@ -62,4 +62,4 @@ class crm_team(osv.Model):
}
def update_invoiced_target(self, cr, uid, id, value, context=None):
return self.write(cr, uid, [id], {'invoiced_target': round(float(value))}, context=context)
return self.write(cr, uid, [id], {'invoiced_target': round(float(value or 0))}, context=context)
+8 -5
View File
@@ -2341,7 +2341,7 @@ class stock_move(osv.osv):
context = context or {}
quant_obj = self.pool.get("stock.quant")
uom_obj = self.pool['product.uom']
to_assign_moves = []
to_assign_moves = set()
main_domain = {}
todo_moves = []
operations = set()
@@ -2349,12 +2349,12 @@ class stock_move(osv.osv):
if move.state not in ('confirmed', 'waiting', 'assigned'):
continue
if move.location_id.usage in ('supplier', 'inventory', 'production'):
to_assign_moves.append(move.id)
to_assign_moves.add(move.id)
#in case the move is returned, we want to try to find quants before forcing the assignment
if not move.origin_returned_move_id:
continue
if move.product_id.type == 'consu':
to_assign_moves.append(move.id)
to_assign_moves.add(move.id)
continue
else:
todo_moves.append(move)
@@ -2419,7 +2419,7 @@ class stock_move(osv.osv):
#force assignation of consumable products and incoming from supplier/inventory/production
# Do not take force_assign as it would create pack operations
if to_assign_moves:
self.write(cr, uid, to_assign_moves, {'state': 'assigned'}, context=context)
self.write(cr, uid, list(to_assign_moves), {'state': 'assigned'}, context=context)
if not no_prepare:
self.check_recompute_pack_op(cr, uid, ids, context=context)
@@ -2783,12 +2783,15 @@ class stock_move(osv.osv):
'product_uom_qty': uom_qty,
'procure_method': 'make_to_stock',
'restrict_lot_id': restrict_lot_id,
'restrict_partner_id': restrict_partner_id,
'split_from': move.id,
'procurement_id': move.procurement_id.id,
'move_dest_id': move.move_dest_id.id,
'origin_returned_move_id': move.origin_returned_move_id.id,
}
if restrict_partner_id:
defaults['restrict_partner_id'] = restrict_partner_id
if context.get('source_location_id'):
defaults['location_id'] = context['source_location_id']
new_move = self.copy(cr, uid, move.id, defaults, context=context)
@@ -44,7 +44,7 @@ class stock_change_product_qty(osv.osv_memory):
try:
self.pool.get('stock.location').check_access_rule(cr, uid, [location_id], 'read', context=context)
except (AccessError):
pass
location_id = False
res['location_id'] = location_id
return res
@@ -182,13 +182,13 @@ class stock_landed_cost(osv.osv):
if not cost.valuation_adjustment_lines or not self._check_sum(cr, uid, cost, context=context):
raise UserError(_('You cannot validate a landed cost which has no valid valuation adjustments lines. Did you click on Compute?'))
move_id = self._create_account_move(cr, uid, cost, context=context)
quant_dict = {}
for line in cost.valuation_adjustment_lines:
if not line.move_id:
continue
per_unit = line.final_cost / line.quantity
diff = per_unit - line.former_cost_per_unit
quants = [quant for quant in line.move_id.quant_ids]
quant_dict = {}
for quant in quants:
if quant.id not in quant_dict:
quant_dict[quant.id] = quant.cost + diff
@@ -189,7 +189,7 @@
<group>
<field name="split_method"/>
<field name="standard_price"/>
<field name="property_account_expense_id"/>
<field name="property_account_expense_id" domain="[('internal_type','=','other'),('deprecated', '=', False)]"/>
</group>
<group>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
+1 -1
View File
@@ -108,7 +108,7 @@ class purchase_order(osv.Model):
class account_invoice(osv.osv):
_inherit = 'account.invoice'
@api.onchange('partner_id')
@api.onchange('partner_id', 'company_id')
def _onchange_partner_id(self):
result = super(account_invoice, self)._onchange_partner_id()
partner = self.partner_id
+1 -5
View File
@@ -34,7 +34,6 @@ import openerp
import openerp.modules.registry
from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
from openerp.modules import get_module_resource
from openerp.service import model as service_model
from openerp.tools import topological_sort
from openerp.tools.translate import _
from openerp.tools import ustr
@@ -964,10 +963,7 @@ class DataSet(http.Controller):
if method.startswith('_'):
raise AccessError(_("Underscore prefixed methods cannot be remotely called"))
@service_model.check
def checked_call(__dbname, *args, **kwargs):
return getattr(request.registry.get(model), method)(request.cr, request.uid, *args, **kwargs)
return checked_call(request.db, *args, **kwargs)
return getattr(request.registry.get(model), method)(request.cr, request.uid, *args, **kwargs)
@http.route('/web/dataset/call', type='json', auth="user")
def call(self, model, method, args, domain_id=None, context_id=None):
+4854 -3787
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -812,7 +812,7 @@ var BufferedDataSet = DataSetStatic.extend({
_.each(ids, function (id) {
self.get_cache(id).to_delete = true;
});
this.set_ids(_.without(this.ids, _.pluck(_.filter(this.cache, function (c) {return c.to_delete;}), 'id')));
this.set_ids(_.difference(this.ids, _.pluck(_.filter(this.cache, function (c) {return c.to_delete;}), 'id')));
this.trigger("dataset_changed", ids, callback, error_callback);
return $.async_when({result: true}).done(callback);
},
@@ -780,7 +780,7 @@ var FieldBarChart = common.AbstractField.extend({
.margin({top: 0, right: 0, bottom: 0, left: 0})
.tooltips(false)
.showValues(false)
.transitionDuration(350)
.transition(350)
.showXAxis(false)
.showYAxis(false);
@@ -158,7 +158,7 @@ return Widget.extend({
var chart = nv.models.multiBarChart();
chart.options({
delay: 250,
transitionDuration: 10,
transition: 10,
showLegend: true,
showXAxis: true,
showYAxis: true,
@@ -212,7 +212,7 @@ return Widget.extend({
var chart = nv.models.pieChart();
chart.options({
delay: 250,
transitionDuration: 100,
transition: 100,
color: d3.scale.category10().range(),
});
@@ -72,6 +72,9 @@ var ProgressBar = Widget.extend({
on_change_input: function(e) {
var $input = $(e.target);
if(e.type === 'change' && !$input.is(':focus')) {
return;
}
if(isNaN($input.val())) {
this.do_warn(_t("Wrong value entered!"), _t("Only Integer Value should be valid."));
} else {
+17 -7
View File
@@ -216,8 +216,13 @@
<t t-raw="0"/>
</div>
<div class="btn-group btn-group-sm oe-pager-buttons" style="display: inline-block">
<a class="fa fa-chevron-left btn btn-default oe-pager-button" accesskey="O" type="button" data-pager-action="previous"></a>
<a class="fa fa-chevron-right btn btn-default oe-pager-button" accesskey="P" type="button" data-pager-action="next"></a>
<!-- accesskeys not wanted in X2Many widgets -->
<t t-if="!widget.x2m">
<t t-set="att_prev" t-value="{'accesskey': 'O'}" />
<t t-set="att_next" t-value="{'accesskey': 'P'}" />
</t>
<a class="fa fa-chevron-left btn btn-default oe-pager-button" t-att="att_prev" type="button" data-pager-action="previous"></a>
<a class="fa fa-chevron-right btn btn-default oe-pager-button" t-att="att_next" type="button" data-pager-action="next"></a>
</div>
</span>
@@ -361,7 +366,7 @@
<t t-name="ListView.buttons">
<t t-if="!widget.no_leaf and widget.options.action_buttons !== false and widget.options.addable and widget.is_action_enabled('create')">
<div class="oe_list_buttons">
<button type="button" class="btn btn-sm btn-primary o_list_button_add">
<button type="button" class="btn btn-sm btn-primary o_list_button_add" accesskey="C">
<t t-esc="widget.options.addable"/>
</button>
</div>
@@ -402,10 +407,10 @@
</tr>
<t t-extend="ListView.buttons">
<t t-jquery="button.o_list_button_add" t-operation="after">
<button type="button" class="btn btn-primary btn-sm o_list_button_save">
<button type="button" class="btn btn-primary btn-sm o_list_button_save" accesskey="S">
Save
</button>
<button type="button" class="btn btn-default btn-sm o_list_button_discard">
<button type="button" class="btn btn-default btn-sm o_list_button_discard" accesskey="D">
Discard
</button>
</t>
@@ -1425,8 +1430,13 @@
<div t-name="Pager">
<span class="o_pager_value"></span> / <span class="o_pager_limit"></span>
<span class="btn-group btn-group-sm">
<button type="button" class="fa fa-chevron-left btn btn-icon o_pager_previous" accesskey="O"/>
<button type="button" class="fa fa-chevron-right btn btn-icon o_pager_next" accesskey="P"/>
<!-- accesskeys not wanted in X2Many widgets -->
<t t-if="!widget.getParent().x2m">
<t t-set="att_prev" t-value="{'accesskey': 'O'}" />
<t t-set="att_next" t-value="{'accesskey': 'P'}" />
</t>
<button type="button" class="fa fa-chevron-left btn btn-icon o_pager_previous" t-att="att_prev"/>
<button type="button" class="fa fa-chevron-right btn btn-icon o_pager_next" t-att="att_next"/>
</span>
</div>
@@ -248,8 +248,7 @@ var BuildingBlock = Widget.extend({
});
$styles.addClass("hidden");
globalSelector = {
closest: function ($from) {
globalSelector.closest = function ($from) {
var $temp;
var $target;
var len = selector.length;
@@ -260,8 +259,8 @@ var BuildingBlock = Widget.extend({
}
}
return $target;
},
all: function ($from) {
};
globalSelector.all = function ($from) {
var $target;
var len = selector.length;
for (var i = 0; i<len; i++) {
@@ -269,8 +268,8 @@ var BuildingBlock = Widget.extend({
else $target = $target.add(selector[i].all($from));
}
return $target;
},
is: function ($from) {
};
globalSelector.is = function ($from) {
var len = selector.length;
for (var i = 0; i<len; i++) {
if (selector[i].is($from)) {
@@ -278,7 +277,6 @@ var BuildingBlock = Widget.extend({
}
}
return false;
},
};
var number = 0;
@@ -318,11 +316,11 @@ var BuildingBlock = Widget.extend({
$("body").toggleClass("editor_has_snippets", !!number);
// select all default text to edit (if snippet default text)
/*self.$snippets.find('.oe_snippet_body, .oe_snippet_body *')
self.$snippets.find('.oe_snippet_body, .oe_snippet_body *')
.contents()
.filter(function() {
return this.nodeType === 3 && this.textContent.match(/\S/);
}).parent().addClass("o_default_snippet_text");*/
}).parent().addClass("o_default_snippet_text");
$(document).on("mouseup", ".o_default_snippet_text", function (event) {
$(event.target).selectContent();
});
@@ -1149,8 +1147,9 @@ var Editor = Class.extend({
});
var data = {
'Class': BuildingBlock,
'globalSelector': globalSelector,
Class: BuildingBlock,
Editor: Editor,
globalSelector: globalSelector,
};
return data;
+12 -17
View File
@@ -814,17 +814,6 @@ range.WrappedRange.prototype.reRange = function (keep_end, isNotBreakable) {
return new range.WrappedRange(sc, so, ec, eo);
};
range.WrappedRange.prototype.deleteContents = function (towrite) {
var prevBP = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite);
$(dom.node(prevBP.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
return new range.WrappedRange(
prevBP.sc,
prevBP.so,
prevBP.ec,
prevBP.eo
);
};
// isOnImg: judge whether range is an image node or not
range.WrappedRange.prototype.isOnImg = function () {
var nb = 0;
@@ -1242,10 +1231,13 @@ $.summernote.pluginEvents.delete = function (event, editor, layoutInfo) {
if (!r.isCollapsed()) {
if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
remove_table_content(r);
return range.create(r.ec, 0).select();
range.create(r.ec, 0).select();
} else {
r = r.deleteContents();
r.select();
}
r = r.deleteContents();
r.select();
event.preventDefault();
return false;
}
var target = r.ec;
@@ -1378,10 +1370,13 @@ $.summernote.pluginEvents.backspace = function (event, editor, layoutInfo) {
if (!r.isCollapsed()) {
if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
remove_table_content(r);
return range.create(r.sc, dom.nodeLength(r.sc)).select();
range.create(r.sc, dom.nodeLength(r.sc)).select();
} else {
r = r.deleteContents();
r.select();
}
r = r.deleteContents();
r.select();
event.preventDefault();
return false;
}
var target = r.sc;
+1 -1
View File
@@ -132,7 +132,7 @@ Tour.register({
},
{
waitFor: '#editable_area > section .row > div:first:has( font:last:containsExact(Bat) )',
element: '.note-air-popover button[data-event="undo"]',
element: '.note-image-popover button[data-event="undo"]',
title: "undo",
},
{
+4 -2
View File
@@ -285,6 +285,9 @@ var ImageDialog = Widget.extend({
o.id = +o.url.match(/\/web\/content\/([0-9]*)/, '')[1];
}
this.parent.$(".pager > li").click(function (e) {
if(!self.$el.is(':visible')) {
return;
}
e.preventDefault();
var $target = $(e.currentTarget);
if ($target.hasClass('disabled')) {
@@ -303,7 +306,6 @@ var ImageDialog = Widget.extend({
var img = _.select(this.images, function (v) { return v.id == attachment.id;});
if (img.length) {
this.images.splice(this.images.indexOf(img[0]),1);
return;
}
} else {
this.images = [];
@@ -421,11 +423,11 @@ var ImageDialog = Widget.extend({
var $button = this.$('button.filepicker');
if (!error) {
$button.addClass('btn-success');
this.set_image(attachment);
} else {
this.$('form').addClass('has-error')
.find('.help-block').text(error);
$button.addClass('btn-danger');
this.set_image(attachment, error);
}
if (!this.options.select_images) {
@@ -24,10 +24,10 @@
// Generate all margins for one size, given
// the name of the margin and intended size
.o-margins-define(@name, @size: @name) {
.mt@{name} {margin-top: ~"@{size}px";}
.mb@{name} {margin-bottom: ~"@{size}px";}
.ml@{name} {margin-left: ~"@{size}px";}
.mr@{name} {margin-right: ~"@{size}px";}
.mt@{name} {margin-top: ~"@{size}px"!important;}
.mb@{name} {margin-bottom: ~"@{size}px"!important;}
.ml@{name} {margin-left: ~"@{size}px"!important;}
.mr@{name} {margin-right: ~"@{size}px"!important;}
}
// Generate all margins
@@ -3,7 +3,7 @@
<t t-name="KanbanView.buttons">
<t t-if="display">
<div>
<button type="button" class="btn btn-primary btn-sm o-kanban-button-new">
<button type="button" class="btn btn-primary btn-sm o-kanban-button-new" accesskey="C">
<t t-esc="widget.options.create_text || _t('Create')"/>
</button>
</div>
+33 -3
View File
@@ -12,8 +12,8 @@ import urllib2
import werkzeug.wrappers
import openerp
from openerp.addons.web.controllers.main import WebClient
from openerp.addons.web_editor.controllers.main import Web_Editor
from openerp.addons.base.ir.ir_qweb import AssetsBundle
from openerp.addons.web.controllers.main import WebClient, Binary
from openerp.addons.web import http
from openerp.http import request
@@ -303,7 +303,7 @@ class Website(openerp.addons.web.controllers.main.Home):
return [enable, disable]
@http.route(['/website/theme_customize'], type='json', auth="public", website=True)
def theme_customize(self, enable, disable):
def theme_customize(self, enable, disable, get_bundle=False):
""" enable or Disable lists of ``xml_id`` of the inherit templates
"""
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
@@ -317,6 +317,10 @@ class Website(openerp.addons.web.controllers.main.Home):
set_active(disable, False)
set_active(enable, True)
if get_bundle:
bundle = AssetsBundle('website.assets_frontend', cr=http.request.cr, uid=http.request.uid, context={}, registry=http.request.registry)
return bundle.to_html()
return True
@http.route(['/website/theme_customize_reload'], type='http', auth="public", website=True)
@@ -369,3 +373,29 @@ class Website(openerp.addons.web.controllers.main.Home):
if res:
return res
return request.redirect('/')
#------------------------------------------------------
# Retrocompatibility routes
#------------------------------------------------------
class WebsiteBinary(openerp.http.Controller):
@http.route([
'/website/image',
'/website/image/<xmlid>',
'/website/image/<xmlid>/<int:width>x<int:height>',
'/website/image/<xmlid>/<field>',
'/website/image/<xmlid>/<field>/<int:width>x<int:height>',
'/website/image/<model>/<id>/<field>',
'/website/image/<model>/<id>/<field>/<int:width>x<int:height>'
], type='http', auth="public", website=False, multilang=False)
def content_image(self, id=None, max_width=0, max_height=0, **kw):
if max_width:
kw['width'] = max_width
if max_height:
kw['height'] = max_height
if id:
id, _, unique = id.partition('_')
kw['id'] = int(id)
if unique:
kw['unique'] = unique
return Binary().content_image(**kw)
+2
View File
@@ -125,6 +125,8 @@ DEFAULT_CDN_FILTERS = [
"^/web/(css|js)/",
"^/web/image",
"^/web/content",
# retrocompatibility
"^/website/image/",
]
def unslug(s):
+1 -9
View File
@@ -589,7 +589,7 @@ ul.oe_menu_editor .disclose {
}
#theme_customize_modal {
overflow: visible;
overflow: auto;
z-index: 1020;
background: transparent;
display: block;
@@ -655,11 +655,3 @@ ul.oe_menu_editor .disclose {
position: absolute;
z-index: 1;
}
/* ---- force the browse to re-compute the stylesheets ---- */
body.theme_customize_css_loading {
magin-top: 1px;
}
body.theme_customize_css_loading #wrapwrap {
magin-top: -1px;
}
+1 -9
View File
@@ -508,7 +508,7 @@ $infobar_height: 20px
#theme_error
background: #ffc
#theme_customize_modal
overflow: visible
overflow: auto
z-index: 1020
background: transparent
display: block
@@ -565,11 +565,3 @@ $infobar_height: 20px
opacity: 0.3
position: absolute
z-index: 1
/* ---- force the browse to re-compute the stylesheets ---- */
body.theme_customize_css_loading
magin-top: 1px
#wrapwrap
magin-top: -1px
// }}}
@@ -187,7 +187,7 @@
}
#theme_customize_modal {
overflow: visible;
overflow: auto;
z-index: 1020;
background: transparent;
display: block;
@@ -253,11 +253,3 @@
position: absolute;
z-index: 1;
}
/* ---- force the browse to re-compute the stylesheets ---- */
body.theme_customize_css_loading {
magin-top: 1px;
}
body.theme_customize_css_loading #wrapwrap {
magin-top: -1px;
}
@@ -167,7 +167,7 @@ $infobar_height: 20px
#theme_error
background: #ffc
#theme_customize_modal
overflow: visible
overflow: auto
z-index: 1020
background: transparent
display: block
@@ -224,11 +224,3 @@ $infobar_height: 20px
opacity: 0.3
position: absolute
z-index: 1
/* ---- force the browse to re-compute the stylesheets ---- */
body.theme_customize_css_loading
magin-top: 1px
#wrapwrap
magin-top: -1px
// }}}
@@ -232,7 +232,7 @@ options.registry.gallery = options.Class.extend({
editor.on('saved', this, function (attachments) {
for (var i = 0 ; i < attachments.length; i++) {
var img = $('<img class="img img-responsive mb8 mt8"/>')
.attr("src", attachments[i].url)
.attr("src", attachments[i].src)
.attr('data-index', index+i)
.data('index', index+i)
.appendTo($container);
+34 -47
View File
@@ -3,6 +3,7 @@ odoo.define('website.theme', function (require) {
var ajax = require('web.ajax');
var core = require('web.core');
var session = require('web.session');
var Widget = require('web.Widget');
var base = require('web_editor.base');
var website = require('website.website');
@@ -121,57 +122,41 @@ var Theme = Widget.extend({
});
return xml_ids;
},
compute_stylesheets: function () {
var self = this;
self.has_error = false;
$('link[href*=".assets_"]').attr('data-loading', true);
function theme_customize_css_onload() {
if ($('link[data-loading]').size()) {
$('body').toggleClass('theme_customize_css_loading');
setTimeout(theme_customize_css_onload, 50);
} else {
$('body').removeClass('theme_customize_css_loading');
self.$el.removeClass("loading");
if (window.getComputedStyle($('button[data-toggle="collapse"]:first')[0]).getPropertyValue('position') === 'static' ||
window.getComputedStyle($('#theme_customize_modal')[0]).getPropertyValue('display') === 'none') {
if (self.has_error) {
window.location.hash = "theme=true";
window.location.reload();
} else {
self.has_error = true;
$('link[href*=".assets_"][data-error]').removeAttr('data-error').attr('data-loading', true);
self.update_stylesheets();
setTimeout(theme_customize_css_onload, 50);
}
}
}
}
theme_customize_css_onload();
},
update_stylesheets: function () {
$('link[href*=".assets_"]').each(function update () {
var $style = $(this);
var href = $style.attr("href").replace(/[^\/]+$/, new Date().getTime());
var $asset = $('<link rel="stylesheet" href="'+href+'"/>');
$asset.attr("onload", "$(this).prev().attr('disable', true).remove(); $(this).removeAttr('onload').removeAttr('onerror');");
$asset.attr("onerror", "$(this).prev().removeAttr('data-loading').attr('data-error','loading'); $(this).attr('disable', true).remove();");
$style.after($asset);
});
},
update_style: function (enable, disable, reload) {
var self = this;
if (this.$el.hasClass("loading")) return;
this.$el.addClass("loading");
if (this.$el.hasClass("loading")) {
return;
}
this.$el.addClass('loading');
if (!reload && $('link[href*=".assets_"]').size()) {
this.compute_stylesheets();
if (!reload && !session.debug) {
var self = this;
return ajax.jsonRpc('/website/theme_customize', 'call', {
'enable': enable,
'disable': disable
}).then(function () {
self.update_stylesheets();
enable: enable,
disable: disable,
get_bundle: true,
}).then(function (bundleHTML) {
var $links = $('link[href*=".assets_frontend"]');
var $newLinks = $(bundleHTML).filter('link');
var linksLoaded = $.Deferred();
var nbLoaded = 0;
$newLinks.on('load', function (e) {
if (++nbLoaded >= $newLinks.length) {
linksLoaded.resolve();
}
});
$newLinks.on('error', function (e) {
linksLoaded.reject();
window.location.hash = "theme=true";
window.location.reload();
});
$links.last().after($newLinks);
return linksLoaded.then(function () {
$links.remove();
self.$el.removeClass('loading');
});
});
} else {
var href = '/website/theme_customize_reload'+
'?href='+encodeURIComponent(window.location.href)+
@@ -325,4 +310,6 @@ website.TopBar.include({
}
});
return Theme;
});
+1 -1
View File
@@ -827,7 +827,7 @@ class Tags(models.Model):
posts_count = fields.Integer('Number of Posts', compute='_get_posts_count', store=True)
_sql_constraints = [
('name_uniq', 'unique (name)', "Tag name already exists !"),
('name_uniq', 'unique (name, forum_id)', "Tag name already exists !"),
]
@api.multi
@@ -60,7 +60,7 @@ if(!$('.o_website_links_chart').length) {
.x(function(d) { return getDate(d); })
.y(function(d) { return getNbClicks(d); })
.tooltips(true)
.transitionDuration(0)
.transition(0)
.showYAxis(true)
.showXAxis(true);
+11 -9
View File
@@ -37,16 +37,16 @@ class WebsiteMembership(http.Controller):
partner_obj = request.registry['res.partner']
post_name = post.get('search') or post.get('name', '')
current_country = None
today = time.strftime(DEFAULT_SERVER_DATE_FORMAT)
# base domain for groupby / searches
base_line_domain = [("partner.website_published", "=", True), ('state', 'in', ['free', 'paid'])]
base_line_domain = [
("partner.website_published", "=", True), ('state', '=', 'paid'),
('date_to', '>=', today), ('date_from', '<=', today)
]
if membership_id and membership_id != 'free':
membership_id = int(membership_id)
today = time.strftime(DEFAULT_SERVER_DATE_FORMAT)
base_line_domain += [
('membership_id', '=', membership_id), ('date_to', '>=', today),
('date_from', '<=', today), ('state', '=', 'paid')
]
base_line_domain.append(('membership_id', '=', membership_id))
membership = product_obj.browse(cr, uid, membership_id, context=context)
else:
membership = None
@@ -58,12 +58,14 @@ class WebsiteMembership(http.Controller):
if membership_id != 'free':
membership_line_ids = membership_line_obj.search(cr, SUPERUSER_ID, base_line_domain, context=context)
country_domain = [('member_lines', 'in', membership_line_ids)]
if not membership_id:
country_domain = ['|', country_domain[0], ('membership_state', '=', 'free')]
else:
membership_line_ids = []
country_domain = [('membership_state', '=', 'free')]
if post_name:
country_domain += ['|', ('name', 'ilike', post_name),
('website_description', 'ilike', post_name)]
if post_name:
country_domain += ['|', ('name', 'ilike', post_name),
('website_description', 'ilike', post_name)]
countries = partner_obj.read_group(
cr, SUPERUSER_ID, country_domain + [("website_published", "=", True)], ["id", "country_id"],
groupby="country_id", orderby="country_id", context=request.context)
@@ -36,8 +36,8 @@
</div>
</div>
<h1>Payment Methods</h1>
<h4>Add a new payment method:</h4>
<div class="row">
<h4 t-if="acquirers">Add a new payment method:</h4>
<div t-if="acquirers" class="row">
<div class="col-md-6">
<div t-if="error_message" class="alert alert-danger">
<t t-foreach="error_message" t-as="err"><t t-esc="err"/><br /></t>
+5 -1
View File
@@ -77,7 +77,7 @@ class sale_quote(http.Controller):
context=render_ctx)
return request.website.render('website_quote.so_quotation', values)
@http.route(['/quote/accept/<int:order_id>'], type='http', auth="public", website=True, methods=['POST'])
@http.route(['/quote/accept'], type='json', auth="public", website=True)
def accept(self, order_id, token=None, signer=None, sign=None, **post):
order_obj = request.registry.get('sale.order')
order = order_obj.browse(request.cr, SUPERUSER_ID, order_id)
@@ -85,6 +85,8 @@ class sale_quote(http.Controller):
return request.website.render('website.404')
if order.require_payment:
return request.website.render('website.404')
if order.state != 'sent':
return False
attachments=sign and [('signature.png', sign.decode('base64'))] or []
order_obj.action_confirm(request.cr, SUPERUSER_ID, [order_id], context=request.context)
message = _('Order signed by %s') % (signer,)
@@ -97,6 +99,8 @@ class sale_quote(http.Controller):
order = order_obj.browse(request.cr, SUPERUSER_ID, order_id)
if token != order.access_token:
return request.website.render('website.404')
if order.state != 'sent':
return werkzeug.utils.redirect("/quote/%s/%s?message=4" % (order_id, token))
request.registry.get('sale.order').action_cancel(request.cr, SUPERUSER_ID, [order_id])
message = post.get('decline_message')
if message:
@@ -62,15 +62,25 @@ if(!$('.o_website_quote').length) {
submitForm: function(ev){
// extract data
var self = this;
var $confirm_btn = self.$el.find('button[type="submit"]');
// Support 2 routes:
// - <form id="accept" method="POST" t-attf-action="/quote/accept/#{quotation.id}/?token=#{quotation.access_token}" ...>
// - <form id="accept" method="POST" t-att-data-order-id="quotation.id" t-att-data-token="quotation.access_token" ...>
// The first route is deprecated but might still be used if the template is not updated
var href = self.$el.find('form').attr("action");
var action = href.match(/quote\/([a-z]+)/);
var order_id = href.match(/quote\/[a-z]+\/([0-9]+)/);
var token = href.match(/token=(.*)/);
if (token){
token = token[1];
if (href) {
var action = href.match(/quote\/([a-z]+)/)[1];
var order_id = parseInt(href.match(/quote\/[a-z]+\/([0-9]+)/)[1]);
var token = href.match(/token=(.*)/) && href.match(/token=(.*)/)[1];
}
else {
var action = 'accept';
var order_id = self.$el.find('form').data("order-id");
var token = self.$el.find('form').data("token");
}
if (action[1]=='accept') {
if (action == 'accept') {
ev.preventDefault();
// process : display errors, or submit
var signer_name = self.$("#name").val();
@@ -84,14 +94,16 @@ if(!$('.o_website_quote').length) {
})
return false;
}
ajax.jsonRpc("/quote/"+action[1], 'call', {
'order_id': parseInt(order_id[1]),
$confirm_btn.prepend('<i class="fa fa-spinner fa-spin"></i> ');
$confirm_btn.attr('disabled', true);
ajax.jsonRpc("/quote/"+action, 'call', {
'order_id': order_id,
'token': token,
'signer': signer_name,
'sign': signature?JSON.stringify(signature[1]):false,
}).then(function (data) {
self.$el.modal('hide');
window.location.href = '/quote/'+order_id[1]+'/'+token+'?message=3';
window.location.href = '/quote/'+order_id.toString()+'/'+token+'?message=3';
});
return false;
}
@@ -225,11 +225,19 @@
Your transaction is waiting confirmation.
<span t-if='tx_post_msg' t-raw="tx_post_msg"/>
</div>
<div class="alert alert-warning alert-dismissable" t-if="message==4 and quotation.state != 'sent'">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
This order has already been
<t t-if="quotation.state=='cancel'">cancelled</t>
<t t-if="quotation.state not in ('cancel','draft')">validated</t>
! You can <a href="#discussion">contact us</a> for
any question.
</div>
<!-- modal relative to the actions Accept/Reject/Cancel -->
<div class="modal fade" id="modalaccept" role="dialog" aria-hidden="true">
<div class="modal-dialog" t-if="not quotation.require_payment and not need_payment">
<form id="accept" method="POST" t-attf-action="/quote/accept/#{quotation.id}/?token=#{quotation.access_token}" class="js_accept_json modal-content js_website_submit_form">
<form id="accept" method="POST" t-att-data-order-id="quotation.id" t-att-data-token="quotation.access_token" class="js_accept_json modal-content js_website_submit_form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&amp;times;</button>
+25 -12
View File
@@ -123,10 +123,16 @@ class sale_order(osv.Model):
class website(orm.Model):
_inherit = 'website'
def _get_pricelist_id(self, cr, uid, ids, name, args, context=None):
res = {}
pricelist = self.get_current_pricelist(cr, uid, context=context)
for data in self.browse(cr, uid, ids, context=context):
res[data.id] = pricelist.id
return res
_columns = {
'pricelist_id': fields.related(
'user_id', 'partner_id', 'property_product_pricelist',
type='many2one', relation='product.pricelist', string='Default Pricelist'),
'pricelist_id': fields.function(_get_pricelist_id,\
type='many2one', relation="product.pricelist", string='Default Pricelist'),
'currency_id': fields.related(
'pricelist_id', 'currency_id',
type='many2one', relation='res.currency', string='Default Currency'),
@@ -180,7 +186,7 @@ class website(orm.Model):
"""
isocountry = request.session.geoip and request.session.geoip.get('country_code') or False
pl_ids = self._get_pl(cr, uid, isocountry, show_visible,
request.website.pricelist_id.id,
request.website.user_id.partner_id.property_product_pricelist.id,
request.session.get('website_sale_current_pl'),
request.website.website_pricelist_ids)
return self.pool['product.pricelist'].browse(cr, uid, pl_ids, context=context)
@@ -229,6 +235,11 @@ class website(orm.Model):
sale_order_id = request.session.get('sale_order_id') or (partner.last_website_so_id.id if partner.last_website_so_id and partner.last_website_so_id.state == 'draft' else False)
sale_order = None
# Test validity of the sale_order_id
if sale_order_id and sale_order_obj.exists(cr, SUPERUSER_ID, sale_order_id, context=context):
sale_order = sale_order_obj.browse(cr, SUPERUSER_ID, sale_order_id, context=context)
else:
sale_order_id = None
pricelist_id = request.session.get('website_sale_current_pl')
if force_pricelist and self.pool['product.pricelist'].search_count(cr, uid, [('id', '=', force_pricelist)], context=context):
@@ -243,26 +254,24 @@ class website(orm.Model):
affiliate_id = request.session.get('affiliate_id')
salesperson_id = affiliate_id if user_obj.exists(cr, SUPERUSER_ID, affiliate_id, context=context) else request.website.salesperson_id.id
for w in self.browse(cr, uid, ids):
addr = partner.address_get(['delivery', 'invoice'])
values = {
'partner_id': partner.id,
'pricelist_id': pricelist_id,
'payment_term_id': partner.property_payment_term_id.id if partner.property_payment_term_id else False,
'team_id': w.salesteam_id.id,
'partner_invoice_id': addr['invoice'],
'partner_shipping_id': addr['delivery'],
'user_id': salesperson_id or w.salesperson_id.id,
}
sale_order_id = sale_order_obj.create(cr, SUPERUSER_ID, values, context=context)
sale_order_obj.onchange_partner_id(cr, SUPERUSER_ID, [sale_order_id], context=context)
values = {'user_id': salesperson_id or w.salesperson_id.id}
sale_order_obj.write(cr, SUPERUSER_ID, [sale_order_id], values, context=context)
request.session['sale_order_id'] = sale_order_id
sale_order = sale_order_obj.browse(cr, SUPERUSER_ID, sale_order_id, context=context)
if request.website.partner_id.id != partner.id:
self.pool['res.partner'].write(cr, SUPERUSER_ID, partner.id, {'last_website_so_id': sale_order_id})
if sale_order_id:
sale_order = sale_order_obj.browse(cr, SUPERUSER_ID, sale_order_id, context=context)
if not sale_order.exists():
request.session['sale_order_id'] = None
return None
# check for change of pricelist with a coupon
pricelist_id = pricelist_id or partner.property_product_pricelist.id
@@ -321,6 +330,10 @@ class website(orm.Model):
if (code and code != sale_order.pricelist_id.code) or sale_order.partner_id.id != partner.id or force_pricelist:
sale_order = sale_order_obj.browse(cr, SUPERUSER_ID, sale_order.id, context=context)
else:
request.session['sale_order_id'] = None
return None
return sale_order
def sale_get_transaction(self, cr, uid, ids, context=None):
@@ -11,6 +11,7 @@ class TestWebsitePriceList(TransactionCase):
def setUp(self):
super(TestWebsitePriceList, self).setUp()
self.website = self.registry('website').browse(self.cr, self.uid, 1)
self.website.pricelist_id = self.registry('ir.model.data').xmlid_to_res_id(self.cr, self.uid, 'product.list0')
self.patcher = patch('openerp.addons.website_sale.models.sale_order.website.get_pricelist_available', wraps=self._get_pricelist_available)
self.mock_get_pricelist_available = self.patcher.start()
+17 -17
View File
@@ -93,16 +93,16 @@
<del class="text-danger mr8" style="white-space: nowrap;" t-field="product.lst_price" t-field-options='{
"widget": "monetary",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</t>
<span t-field="product.price" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'>
</span>
<span itemprop="price" style="display:none;" t-esc="product.price"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="user_id.partner_id.property_product_pricelist.currency_id.name"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="website.currency_id.name"/>
</b>
</div>
</section>
@@ -423,16 +423,16 @@
t-field-options='{
"widget": "monetary",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
<b class="oe_price" style="white-space: nowrap;"
t-field="product.price"
t-field-options='{
"widget": "monetary",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
<span itemprop="price" style="display:none;" t-esc="product.price"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="user_id.partner_id.property_product_pricelist.currency_id.name"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="website.currency_id.name"/>
</h4>
<h4 class="css_non_editable_mode_hidden">
<span t-field="product.lst_price"
@@ -456,7 +456,7 @@
<t t-esc="variant_id.price_extra > 0 and '+' or ''"/><span t-field="variant_id.price_extra" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</span>
</label>
@@ -481,7 +481,7 @@
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</span>
</option>
@@ -501,7 +501,7 @@
<t t-esc="value_id.price_extra > 0 and '+' or ''"/><span t-field="value_id.price_extra" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</span>
</label>
@@ -776,12 +776,12 @@
t-field="line.product_id.lst_price" t-field-options='{
"widget": "monetary",
"from_currency": "line.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
<span t-field="line.price_reduce" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</td>
</tr>
@@ -898,12 +898,12 @@
t-field="product.lst_price" t-field-options='{
"widget": "monetary",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</t>
<span t-field="product.price" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</td>
<td class="text-center">
@@ -1153,7 +1153,7 @@
<span t-field="line.price_unit" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</td>
</tr>
@@ -1394,7 +1394,7 @@
<span t-field="website_sale_order.amount_untaxed" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</span>
</div>
@@ -1404,7 +1404,7 @@
<span t-field="website_sale_order.amount_tax" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</span>
</div>
@@ -1414,7 +1414,7 @@
<span t-field="website_sale_order.amount_total" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
"display_currency": "website.currency_id"
}'/>
</span>
</div>
@@ -59,7 +59,7 @@
t-field="product.lst_price"
t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
<span class="oe_price" style="white-space: nowrap;"
@@ -67,7 +67,7 @@
t-field="product.price"
t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
@@ -107,7 +107,7 @@
t-field="product.lst_price"
t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"from_currency": "product.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/><br/>
<span class="oe_price" style="white-space: nowrap;"
+11
View File
@@ -0,0 +1,11 @@
Saudi Arabia, 2015-10-13
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,
Mohammed Barsi barsintod@gmail.com
+1 -1
View File
@@ -1973,7 +1973,7 @@ Read-Write Field
Read-only fields, which only display content and don't allow the
user to modify it can be useful, but most fields in Odoo also allow editing.
This makes the field classes more complicated, mostly because fields are
supposed to handle both and editable and non-editable mode, those modes are
supposed to handle both editable and non-editable mode, those modes are
often completely different (for design and usability purpose) and the fields
must be able to switch between modes at any moment.
+1 -1
View File
@@ -552,7 +552,7 @@ class view(osv.osv):
separator = child.get('separator', ',')
if separator == ' ':
separator = None # squash spaces
to_add = map(str.strip, child.get('add', '').split(separator))
to_add = filter(bool, map(str.strip, child.get('add', '').split(separator)))
to_remove = map(str.strip, child.get('remove', '').split(separator))
values = map(str.strip, node.get(attribute, '').split(separator))
value = (separator or ' ').join(filter(lambda s: s not in to_remove, values) + to_add)
+1 -3
View File
@@ -20,9 +20,6 @@
<field name="rml_header1" placeholder="e.g. Global Business Solutions"/>
</h3>
</div>
<group col="4">
<field name="parent_id" groups="base.group_multi_company"/>
</group>
<notebook colspan="4">
<page string="General Information">
<group>
@@ -45,6 +42,7 @@
<field name="email"/>
<field name="vat"/>
<field name="company_registry"/>
<field name="parent_id" groups="base.group_multi_company"/>
</group>
</group>
</page>
@@ -36,6 +36,7 @@
<field name="symbol">R$</field>
<field name="rounding">0.01</field>
<field name="active" eval="False"/>
<field name="position">before</field>
</record>
<record id="CNY" model="res.currency">
@@ -229,6 +230,7 @@
<field name="symbol">$</field>
<field name="rounding">0.01</field>
<field name="active" eval="False"/>
<field name="position">before</field>
</record>
<record id="MYR" model="res.currency">
@@ -275,3 +275,27 @@ class TestGlobalDefaults(FiltersCase):
dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'),
dict(name='b', user_id=False, is_default=True, domain='[]', context=context_value, sort='[]'),
])
from openerp.tests.common import TransactionCase
class TestReadGroup(TransactionCase):
"""Test function read_group with groupby on a many2one field to a model
(in test, "user_id" to "res.users") which is ordered by an inherited not stored field (in
test, "name" inherited from "res.partners").
"""
def setUp(self):
super(TestReadGroup, self).setUp()
self.ir_filters_model = self.env['ir.filters']
self.res_partner_model = self.env['res.partner']
self.res_users_model = self.env['res.users']
def test_read_group_1(self):
self.assertEqual(self.res_users_model._order, "name, login", "Model res.users must be ordered by name, login")
self.assertFalse(self.res_users_model._fields['name'].store, "Field name is not stored in res.users")
filter_a = self.ir_filters_model.create(dict(name="Filter_A", model_id="ir.filters"))
filter_b = self.ir_filters_model.create(dict(name="Filter_B", model_id="ir.filters"))
filter_b.write(dict(user_id=False))
res = self.ir_filters_model.read_group([], ['name', 'user_id'], ['user_id'])
self.assertTrue(any(val['user_id'] == False for val in res), "At least one group must contain val['user_id'] == False.")
+2 -2
View File
@@ -748,7 +748,7 @@ class test_views(ViewCase):
<attribute name="thing">bob tata lolo</attribute>
<attribute name="thing" add="bibi and co" remove="tata" separator=" " />
<attribute name="otherthing">bob, tata,lolo</attribute>
<attribute name="otherthing" add="bibi" remove="tata, bob"/>
<attribute name="otherthing" remove="tata, bob"/>
</footer>
"""
})
@@ -768,7 +768,7 @@ class test_views(ViewCase):
E.p("Replacement data"),
E.footer(
E.button(name="action_next", type="object", string="New button"),
thing="bob lolo bibi and co", otherthing="lolo,bibi"
thing="bob lolo bibi and co", otherthing="lolo"
),
string="Replacement title", version="7.0"))
+4 -4
View File
@@ -2152,7 +2152,7 @@ class BaseModel(object):
return parent_alias
@api.model
def _inherits_join_calc(self, alias, field, query):
def _inherits_join_calc(self, alias, field, query, implicit=True, outer=False):
"""
Adds missing table select and join clause(s) to ``query`` for reaching
the field coming from an '_inherits' parent table (no duplicates).
@@ -2172,7 +2172,7 @@ class BaseModel(object):
# JOIN parent_model._table AS parent_alias ON alias.parent_field = parent_alias.id
parent_alias, _ = query.add_join(
(alias, parent_model._table, parent_field, 'id', parent_field),
implicit=True,
implicit=implicit, outer=outer,
)
model, alias = parent_model, parent_alias
# handle the case where the field is translated
@@ -2225,7 +2225,7 @@ class BaseModel(object):
# if val is a many2one, just write the ID
if type(val) == tuple:
val = val[0]
if val is not False:
if f._type == 'boolean' or val is not False:
cr.execute(update_query, (ss[1](val), key))
@api.model
@@ -4682,7 +4682,7 @@ class BaseModel(object):
parent_obj = self.pool[self._inherit_fields[order_field][3]]
order_column = parent_obj._columns[order_field]
if order_column._classic_read:
inner_clauses = [self._inherits_join_calc(alias, order_field, query)]
inner_clauses = [self._inherits_join_calc(alias, order_field, query, implicit=False, outer=True)]
add_dir = True
elif order_column._type == 'many2one':
key = (parent_obj._name, order_column._obj, order_field)
+6 -1
View File
@@ -54,7 +54,12 @@ def check(f):
if args and isinstance(args[-1], dict):
ctx = args[-1]
elif isinstance(kwargs, dict):
ctx = kwargs.get('context', {})
if 'context' in kwargs:
ctx = kwargs['context']
elif 'kwargs' in kwargs:
# http entry points such as call_kw()
ctx = kwargs['kwargs'].get('context')
uid = 1
if args and isinstance(args[0], (long, int)):