[MERGE] forward port of branch 8.0 up to c9fce1a
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_account_analytic_default,account.analytic.default,model_account_analytic_default,account.group_account_user,1,0,0,0
|
||||
access_account_analytic_default_analytic,account.analytic.default analytic,model_account_analytic_default,analytic.group_analytic_accounting,1,0,0,0
|
||||
access_account_analytic_default_invoice,account.analytic.default invoice,model_account_analytic_default,account.group_account_invoice,1,1,1,1
|
||||
access_account_analytic_default_salesman,account.analytic.default.salesman,model_account_analytic_default,base.group_sale_salesman,1,1,1,1
|
||||
|
||||
|
@@ -36,7 +36,7 @@ class report_print_check(report_sxw.rml_parse):
|
||||
})
|
||||
|
||||
def fill_stars(self, amount):
|
||||
if len(amount) < 100:
|
||||
if amount and len(amount) < 100:
|
||||
stars = 100 - len(amount)
|
||||
return ' '.join([amount,'*'*stars])
|
||||
|
||||
|
||||
@@ -1290,7 +1290,7 @@ class account_voucher(osv.osv):
|
||||
'move_id': move_id,
|
||||
'partner_id': line.voucher_id.partner_id.id,
|
||||
'currency_id': line.move_line_id.currency_id.id,
|
||||
'amount_currency': -1 * foreign_currency_diff,
|
||||
'amount_currency': (-1 if line.type == 'cr' else 1) * foreign_currency_diff,
|
||||
'quantity': 1,
|
||||
'credit': 0.0,
|
||||
'debit': 0.0,
|
||||
|
||||
@@ -102,6 +102,7 @@ class view(osv.osv):
|
||||
self.import_module(cr, uid, mod_name, path, force=force, context=context)
|
||||
success.append(mod_name)
|
||||
except Exception, e:
|
||||
_logger.exception('Error while importing module')
|
||||
errors[mod_name] = exception_to_unicode(e)
|
||||
r = ["Successfully imported module '%s'" % mod for mod in success]
|
||||
for mod, error in errors.items():
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_hr_resource_manager,hr.employee.resource.manager,resource.model_resource_resource,base.group_hr_manager,1,1,1,1
|
||||
access_hr_resource_calendar_user,hr.employee.resource.calendar.user,resource.model_resource_calendar,base.group_hr_user,1,1,1,1
|
||||
access_hr_resource_calendar_attendance_user,hr.employee.resource.calendar.attendance.user,resource.model_resource_calendar_attendance,base.group_hr_user,1,1,1,1
|
||||
access_hr_contract_type_manager,hr.contract.type.manager,model_hr_contract_type,base.group_hr_manager,1,1,1,1
|
||||
access_hr_contract_manager,hr.contract.manager,model_hr_contract,base.group_hr_manager,1,1,1,1
|
||||
|
||||
|
@@ -48,18 +48,24 @@ class hr_timesheet_sheet(osv.osv):
|
||||
""" Compute the attendances, analytic lines timesheets and differences between them
|
||||
for all the days of a timesheet and the current day
|
||||
"""
|
||||
res = dict.fromkeys(ids, {
|
||||
'total_attendance': 0.0,
|
||||
'total_timesheet': 0.0,
|
||||
'total_difference': 0.0,
|
||||
})
|
||||
|
||||
cr.execute("""
|
||||
SELECT sheet_id as id,
|
||||
sum(total_attendance) as total_attendance,
|
||||
sum(total_timesheet) as total_timesheet,
|
||||
sum(total_difference) as total_difference
|
||||
FROM hr_timesheet_sheet_sheet_day
|
||||
WHERE sheet_id IN %s
|
||||
GROUP BY sheet_id
|
||||
""", (tuple(ids),))
|
||||
|
||||
res.update(dict((x.pop('id'), x) for x in cr.dictfetchall()))
|
||||
|
||||
res = {}
|
||||
for sheet in self.browse(cr, uid, ids, context=context or {}):
|
||||
res.setdefault(sheet.id, {
|
||||
'total_attendance': 0.0,
|
||||
'total_timesheet': 0.0,
|
||||
'total_difference': 0.0,
|
||||
})
|
||||
for period in sheet.period_ids:
|
||||
res[sheet.id]['total_attendance'] += period.total_attendance
|
||||
res[sheet.id]['total_timesheet'] += period.total_timesheet
|
||||
res[sheet.id]['total_difference'] += period.total_attendance - period.total_timesheet
|
||||
return res
|
||||
|
||||
def check_employee_attendance_state(self, cr, uid, sheet_id, context=None):
|
||||
|
||||
@@ -42,6 +42,8 @@ class report_custom(report_rml):
|
||||
rml_obj.localcontext.update({'lang':context.get('lang',False)})
|
||||
company_currency = user_pool.browse(cr, uid, uid).company_id.currency_id
|
||||
company_currency_symbol = company_currency.symbol or company_currency.name
|
||||
product_uom_digits = rml_obj.get_digits(dp='Product Unit of Measure')
|
||||
purchase_price_digits = rml_obj.get_digits(dp='Product Price')
|
||||
def process_bom(bom, currency_id, factor=1):
|
||||
xml = '<row>'
|
||||
sum = 0
|
||||
@@ -63,7 +65,7 @@ class report_custom(report_rml):
|
||||
'uom': prod.uom_po_id.id,
|
||||
'date': time.strftime('%Y-%m-%d'),
|
||||
})[pricelist.id]
|
||||
main_sp_price = """<b>"""+rml_obj.formatLang(price)+' '+ (company_currency_symbol)+"""</b>\r\n"""
|
||||
main_sp_price = """<b>"""+rml_obj.formatLang(price, digits=purchase_price_digits)+' '+ (company_currency_symbol)+"""</b>\r\n"""
|
||||
sum += prod_qtty*price
|
||||
std_price = product_uom_pool._compute_price(cr, uid, prod.uom_id.id, prod.standard_price, to_uom_id=product_uom.id)
|
||||
main_strd_price = str(std_price) + '\r\n'
|
||||
@@ -78,11 +80,11 @@ class report_custom(report_rml):
|
||||
'uom': prod.uom_po_id.id,
|
||||
'date': time.strftime('%Y-%m-%d'),
|
||||
})[pricelist.id]
|
||||
sellers_price += """<i>"""+rml_obj.formatLang(price) +' '+ (company_currency_symbol) +"""</i>\r\n"""
|
||||
sellers_price += """<i>"""+rml_obj.formatLang(price, digits=purchase_price_digits) +' '+ (company_currency_symbol) +"""</i>\r\n"""
|
||||
xml += """<col para='yes'> """+ prod_name +""" </col>
|
||||
<col para='yes'> """+ main_sp_name + sellers + """ </col>
|
||||
<col f='yes'>"""+ rml_obj.formatLang(prod_qtty) +' '+ product_uom_name +"""</col>
|
||||
<col f='yes'>"""+ rml_obj.formatLang(float(main_strd_price)) +' '+ (company_currency_symbol) +"""</col>
|
||||
<col f='yes'>"""+ rml_obj.formatLang(prod_qtty, digits=product_uom_digits) +' '+ product_uom_name +"""</col>
|
||||
<col f='yes'>"""+ rml_obj.formatLang(float(main_strd_price), digits=purchase_price_digits) +' '+ (company_currency_symbol) +"""</col>
|
||||
<col f='yes'>""" + main_sp_price + sellers_price + """</col>'"""
|
||||
|
||||
xml += '</row>'
|
||||
@@ -140,8 +142,6 @@ class report_custom(report_rml):
|
||||
</row>
|
||||
""" % (_('Components'), _('Components suppliers'), _('Quantity'),_('Cost Price per Unit of Measure'), _('Supplier Price per Unit of Measure'))
|
||||
|
||||
purchase_price_digits = rml_obj.get_digits(dp='Product Price')
|
||||
|
||||
for product in product_pool.browse(cr, uid, ids, context=context):
|
||||
product_uom_name = to_xml(product.uom_id.name)
|
||||
bom_id = bom_pool._bom_find(cr, uid, product_id=product.id, context=context)
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
% endif
|
||||
</p>
|
||||
|
||||
<% set signup_url = object.get_signup_url() %>
|
||||
% set signup_url = object.get_signup_url()
|
||||
% if signup_url and object.amount_total > 0 :
|
||||
<p>
|
||||
You can access this document and pay online via our Customer Portal:
|
||||
@@ -128,7 +128,7 @@
|
||||
% endif
|
||||
</p>
|
||||
|
||||
<% set signup_url = object.get_signup_url() %>
|
||||
% set signup_url = object.get_signup_url()
|
||||
% if signup_url:
|
||||
<p>
|
||||
You can access the invoice document and pay online via our Customer Portal:
|
||||
|
||||
@@ -424,6 +424,14 @@ class product_attribute_line(osv.osv):
|
||||
'value_ids': fields.many2many('product.attribute.value', id1='line_id', id2='val_id', string='Product Attribute Value(s)'),
|
||||
}
|
||||
|
||||
def _check_valid_attribute(self, cr, uid, ids, context=None):
|
||||
obj_pal = self.browse(cr, uid, ids[0], context=context)
|
||||
return obj_pal.value_ids <= obj_pal.attribute_id.value_ids
|
||||
|
||||
_constraints = [
|
||||
(_check_valid_attribute, 'Error ! You cannot use this attribute with the following value.', ['attribute_id'])
|
||||
]
|
||||
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Products
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
@@ -127,7 +128,7 @@ class project_task(osv.osv):
|
||||
proc_obj = self.pool.get("procurement.order")
|
||||
for task in self.browse(cr, uid, ids, context=context):
|
||||
if task.procurement_id:
|
||||
proc_obj.check(cr, uid, [task.procurement_id.id], context=context)
|
||||
proc_obj.check(cr, SUPERUSER_ID, [task.procurement_id.id], context=context)
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
""" When closing tasks, validate subflows. """
|
||||
|
||||
@@ -488,7 +488,7 @@ class stock_picking(osv.osv):
|
||||
def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None):
|
||||
inv_vals = super(stock_picking, self)._get_invoice_vals(cr, uid, key, inv_type, journal_id, move, context=context)
|
||||
sale = move.picking_id.sale_id
|
||||
if sale:
|
||||
if sale and inv_type in ('out_invoice', 'out_refund'):
|
||||
inv_vals.update({
|
||||
'fiscal_position': sale.fiscal_position.id,
|
||||
'payment_term': sale.payment_term.id,
|
||||
|
||||
@@ -1400,7 +1400,8 @@ class stock_picking(osv.osv):
|
||||
This can be used to provide a button that rereserves taking into account the existing pack operations
|
||||
"""
|
||||
for pick in self.browse(cr, uid, ids, context=context):
|
||||
self.rereserve_quants(cr, uid, pick, move_ids = [x.id for x in pick.move_lines], context=context)
|
||||
self.rereserve_quants(cr, uid, pick, move_ids = [x.id for x in pick.move_lines
|
||||
if x.state not in ('done', 'cancel')], context=context)
|
||||
|
||||
def rereserve_quants(self, cr, uid, picking, move_ids=[], context=None):
|
||||
""" Unreserve quants then try to reassign quants."""
|
||||
|
||||
@@ -1047,8 +1047,11 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
|
||||
return this._super(id, signal);
|
||||
},
|
||||
alter_ids: function(n_ids) {
|
||||
var dirty = !_.isEqual(this.ids, n_ids);
|
||||
this._super(n_ids);
|
||||
this.trigger("dataset_changed", n_ids);
|
||||
if(dirty) {
|
||||
this.trigger("dataset_changed", n_ids);
|
||||
}
|
||||
},
|
||||
});
|
||||
instance.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
|
||||
|
||||
@@ -312,7 +312,16 @@ instance.web.round_precision = function(value, precision){
|
||||
var epsilon_magnitude = Math.log(Math.abs(normalized_value))/Math.log(2);
|
||||
var epsilon = Math.pow(2, epsilon_magnitude - 53);
|
||||
normalized_value += normalized_value >= 0 ? epsilon : -epsilon;
|
||||
var rounded_value = Math.round(normalized_value);
|
||||
|
||||
/**
|
||||
* Javascript performs strictly the round half up method, which is asymmetric. However, in
|
||||
* Python, the method is symmetric. For example:
|
||||
* - In JS, Math.round(-0.5) is equal to -0.
|
||||
* - In Python, round(-0.5) is equal to -1.
|
||||
* We want to keep the Python behavior for consistency.
|
||||
*/
|
||||
var sign = normalized_value < 0 ? -1.0 : 1.0;
|
||||
var rounded_value = sign * Math.round(Math.abs(normalized_value));
|
||||
return rounded_value * precision;
|
||||
};
|
||||
|
||||
|
||||
@@ -4413,10 +4413,6 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
|
||||
}));
|
||||
this.on('edit:after', this, this.proxy('_after_edit'));
|
||||
this.on('save:before cancel:before', this, this.proxy('_before_unedit'));
|
||||
|
||||
this.records
|
||||
.bind('add', this.proxy("changed_records"))
|
||||
.bind('remove', this.proxy("changed_records"));
|
||||
this.on('save:after', this, this.proxy("changed_records"));
|
||||
},
|
||||
start: function () {
|
||||
|
||||
@@ -532,6 +532,9 @@ openerp.web_calendar = function(instance) {
|
||||
throw new Error("Incomplete data received from dataset for record " + evt.id);
|
||||
}
|
||||
}
|
||||
else if (_.contains(["date", "datetime"], self.fields[fieldname].type)) {
|
||||
temp_ret[fieldname] = instance.web.format_value(value, self.fields[fieldname]);
|
||||
}
|
||||
else {
|
||||
temp_ret[fieldname] = value;
|
||||
}
|
||||
|
||||
@@ -274,7 +274,8 @@ class ir_http(orm.AbstractModel):
|
||||
logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
|
||||
if 'qweb_exception' in values:
|
||||
view = request.registry.get("ir.ui.view")
|
||||
views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
|
||||
views = view._views_get(request.cr, request.uid, exception.qweb['template'],
|
||||
context=request.context)
|
||||
to_reset = [v for v in views if v.model_data_id.noupdate is True and not v.page]
|
||||
values['views'] = to_reset
|
||||
elif code == 403:
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
}'/>
|
||||
</t>
|
||||
<t t-if="not message.author_id"><t t-esc="message.email_from"/></t>
|
||||
- <i class="fa fa-calendar"/> <t t-esc="message.date"/>
|
||||
- <i class="fa fa-calendar"/> <span t-field="message.date"/>
|
||||
</small>
|
||||
<div t-raw="message.body"/>
|
||||
|
||||
@@ -262,7 +262,7 @@
|
||||
}'/>
|
||||
</t>
|
||||
<t t-if="not thread.author_id"><t t-esc="thread.email_from"/></t>
|
||||
- <i class="fa fa-calendar"/> <t t-esc="thread.date"/>
|
||||
- <i class="fa fa-calendar"/> <span t-field="thread.date"/>
|
||||
- <i class="fa fa-paperclip"/> <t t-esc="len(thread.attachment_ids)"/>
|
||||
</small>
|
||||
<p t-if="thread.child_ids" class="mt8">
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
% endif
|
||||
</p>
|
||||
|
||||
<% set signup_url = object.get_signup_url() %>
|
||||
% set signup_url = object.get_signup_url()
|
||||
% if signup_url and object.amount_total > 0 :
|
||||
<p>
|
||||
You can access this document and pay online via our Customer Portal:
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
$(document).ready(function () {
|
||||
|
||||
function update_product_image(event_source, product_id) {
|
||||
var $img = $(event_source).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
|
||||
$img.attr("src", "/website/image/product.product/" + product_id + "/image");
|
||||
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
|
||||
.data('oe-model', 'product.product').data('oe-id', product_id);
|
||||
}
|
||||
|
||||
$('.oe_website_sale').each(function () {
|
||||
var oe_website_sale = this;
|
||||
|
||||
@@ -157,9 +165,7 @@ $('.oe_website_sale').each(function () {
|
||||
var $parent = $(this).closest('.js_product');
|
||||
$parent.find(".oe_default_price:first .oe_currency_value").html( price_to_str(+$(this).data('lst_price')) );
|
||||
$parent.find(".oe_price:first .oe_currency_value").html(price_to_str(+$(this).data('price')) );
|
||||
|
||||
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
|
||||
$img.attr("src", "/website/image/product.product/" + $(this).val() + "/image");
|
||||
update_product_image(this, +$(this).val());
|
||||
});
|
||||
|
||||
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) {
|
||||
@@ -195,10 +201,7 @@ $('.oe_website_sale').each(function () {
|
||||
}
|
||||
|
||||
if (product_id) {
|
||||
var $img = $(this).closest('tr.js_product, .oe_website_sale').find('span[data-oe-model^="product."][data-oe-type="image"] img:first, img.product_detail_img');
|
||||
$img.attr("src", "/website/image/product.product/" + product_id + "/image");
|
||||
$img.parent().attr('data-oe-model', 'product.product').attr('data-oe-id', product_id)
|
||||
.data('oe-model', 'product.product').data('oe-id', product_id);
|
||||
update_product_image(this, product_id);
|
||||
}
|
||||
|
||||
$parent.find("input.js_variant_change:radio, select.js_variant_change").each(function () {
|
||||
|
||||
@@ -141,5 +141,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="products_add_to_cart" inherit_id="website_sale.products_add_to_cart">
|
||||
<xpath expr="//div[@class='product_price']/t[1]" position="attributes">
|
||||
<attribute name="t-if">len(product.product_variant_ids) == 1 and not len(product.optional_product_ids) >= 1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@class='product_price']/t[2]" position="attributes">
|
||||
<attribute name="t-if">len(product.product_variant_ids) > 1 or len(product.optional_product_ids) >= 1</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -12,3 +12,4 @@ List of contributors:
|
||||
|
||||
Damián Soriano ds@adhoc.com.ar https://github.com/damiansoriano
|
||||
Juan José Scarafía jjs@adhoc.com.ar https://github.com/jjscarafia
|
||||
Nicolás Mac Rouillon nmr@adhoc.com.ar https://github.com/nicomacr
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
United States, 2015-09-18
|
||||
|
||||
Sodexis, Inc. agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Stephan Keller skeller@sodexis.com https://github.com/stephankeller
|
||||
|
||||
List of contributors:
|
||||
|
||||
Stephan Keller skeller@sodexis.com https://github.com/stephankeller
|
||||
Suganthi Karunanithi ksuganthi@sodexis.com https://github.com/suganthikarunanithi
|
||||
Xavier Dass xavier@sodexis.com https://github.com/xavier-dass
|
||||
Atchuthan atchuthan@sodexis.com https://github.com/atchuthan
|
||||
Dhinesh dhinesh@sodexis.com https://github.com/dvdhinesh
|
||||
@@ -26,6 +26,7 @@ import os
|
||||
import time
|
||||
import datetime
|
||||
import dateutil
|
||||
import pytz
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
@@ -97,6 +98,9 @@ class actions(osv.osv):
|
||||
'time': time,
|
||||
'datetime': datetime,
|
||||
'dateutil': dateutil,
|
||||
# NOTE: only `timezone` function. Do not provide the whole `pytz` module as users
|
||||
# will have access to `pytz.os` and `pytz.sys` to do nasty things...
|
||||
'timezone': pytz.timezone,
|
||||
}
|
||||
|
||||
class ir_actions_report_xml(osv.osv):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import unittest2
|
||||
|
||||
import openerp
|
||||
import openerp.osv.expression as expression
|
||||
from openerp.osv.expression import get_unaccent_wrapper
|
||||
from openerp.osv.orm import BaseModel
|
||||
import openerp.tests.common as common
|
||||
@@ -502,6 +503,25 @@ class test_expression(common.TransactionCase):
|
||||
norm_domain = ['&', '&', '&'] + domain
|
||||
assert norm_domain == expression.normalize_domain(domain), "Non-normalized domains should be properly normalized"
|
||||
|
||||
def test_40_negating_long_expression(self):
|
||||
source = ['!','&',('user_id','=',4),('partner_id','in',[1,2])]
|
||||
expect = ['|',('user_id','!=',4),('partner_id','not in',[1,2])]
|
||||
self.assertEqual(expression.distribute_not(source), expect,
|
||||
"distribute_not on expression applied wrongly")
|
||||
|
||||
pos_leaves = [[('a', 'in', [])], [('d', '!=', 3)]]
|
||||
neg_leaves = [[('a', 'not in', [])], [('d', '=', 3)]]
|
||||
|
||||
source = expression.OR([expression.AND(pos_leaves)] * 1000)
|
||||
expect = source
|
||||
self.assertEqual(expression.distribute_not(source), expect,
|
||||
"distribute_not on long expression without negation operator should not alter it")
|
||||
|
||||
source = ['!'] + source
|
||||
expect = expression.AND([expression.OR(neg_leaves)] * 1000)
|
||||
self.assertEqual(expression.distribute_not(source), expect,
|
||||
"distribute_not on long expression applied wrongly")
|
||||
|
||||
def test_translate_search(self):
|
||||
Country = self.registry('res.country')
|
||||
be = self.ref('base.be')
|
||||
|
||||
+51
-44
@@ -167,6 +167,26 @@ TERM_OPERATORS = ('=', '!=', '<=', '<', '>', '>=', '=?', '=like', '=ilike',
|
||||
# legal in the processed term.
|
||||
NEGATIVE_TERM_OPERATORS = ('!=', 'not like', 'not ilike', 'not in')
|
||||
|
||||
# Negation of domain expressions
|
||||
DOMAIN_OPERATORS_NEGATION = {
|
||||
AND_OPERATOR: OR_OPERATOR,
|
||||
OR_OPERATOR: AND_OPERATOR,
|
||||
}
|
||||
TERM_OPERATORS_NEGATION = {
|
||||
'<': '>=',
|
||||
'>': '<=',
|
||||
'<=': '>',
|
||||
'>=': '<',
|
||||
'=': '!=',
|
||||
'!=': '=',
|
||||
'in': 'not in',
|
||||
'like': 'not like',
|
||||
'ilike': 'not ilike',
|
||||
'not in': 'in',
|
||||
'not like': 'like',
|
||||
'not ilike': 'ilike',
|
||||
}
|
||||
|
||||
TRUE_LEAF = (1, '=', 1)
|
||||
FALSE_LEAF = (0, '=', 1)
|
||||
|
||||
@@ -263,51 +283,36 @@ def distribute_not(domain):
|
||||
['|',('user_id','!=',4),('partner_id','not in',[1,2])]
|
||||
|
||||
"""
|
||||
def negate(leaf):
|
||||
"""Negates and returns a single domain leaf term,
|
||||
using the opposite operator if possible"""
|
||||
left, operator, right = leaf
|
||||
mapping = {
|
||||
'<': '>=',
|
||||
'>': '<=',
|
||||
'<=': '>',
|
||||
'>=': '<',
|
||||
'=': '!=',
|
||||
'!=': '=',
|
||||
}
|
||||
if operator in ('in', 'like', 'ilike'):
|
||||
operator = 'not ' + operator
|
||||
return [(left, operator, right)]
|
||||
if operator in ('not in', 'not like', 'not ilike'):
|
||||
operator = operator[4:]
|
||||
return [(left, operator, right)]
|
||||
if operator in mapping:
|
||||
operator = mapping[operator]
|
||||
return [(left, operator, right)]
|
||||
return [NOT_OPERATOR, (left, operator, right)]
|
||||
|
||||
def distribute_negate(domain):
|
||||
"""Negate the domain ``subtree`` rooted at domain[0],
|
||||
leaving the rest of the domain intact, and return
|
||||
(negated_subtree, untouched_domain_rest)
|
||||
"""
|
||||
if is_leaf(domain[0]):
|
||||
return negate(domain[0]), domain[1:]
|
||||
if domain[0] == AND_OPERATOR:
|
||||
done1, todo1 = distribute_negate(domain[1:])
|
||||
done2, todo2 = distribute_negate(todo1)
|
||||
return [OR_OPERATOR] + done1 + done2, todo2
|
||||
if domain[0] == OR_OPERATOR:
|
||||
done1, todo1 = distribute_negate(domain[1:])
|
||||
done2, todo2 = distribute_negate(todo1)
|
||||
return [AND_OPERATOR] + done1 + done2, todo2
|
||||
if not domain:
|
||||
return []
|
||||
if domain[0] != NOT_OPERATOR:
|
||||
return [domain[0]] + distribute_not(domain[1:])
|
||||
if domain[0] == NOT_OPERATOR:
|
||||
done, todo = distribute_negate(domain[1:])
|
||||
return done + distribute_not(todo)
|
||||
# This is an iterative version of a recursive function that split domain
|
||||
# into subdomains, processes them and combine the results. The "stack" below
|
||||
# represents the recursive calls to be done.
|
||||
result = []
|
||||
stack = [False]
|
||||
|
||||
for token in domain:
|
||||
negate = stack.pop()
|
||||
# negate tells whether the subdomain starting with token must be negated
|
||||
if is_leaf(token):
|
||||
if negate:
|
||||
left, operator, right = token
|
||||
if operator in TERM_OPERATORS_NEGATION:
|
||||
result.append((left, TERM_OPERATORS_NEGATION[operator], right))
|
||||
else:
|
||||
result.append(NOT_OPERATOR)
|
||||
result.append(token)
|
||||
else:
|
||||
result.append(token)
|
||||
elif token == NOT_OPERATOR:
|
||||
stack.append(not negate)
|
||||
elif token in DOMAIN_OPERATORS_NEGATION:
|
||||
result.append(DOMAIN_OPERATORS_NEGATION[token] if negate else token)
|
||||
stack.append(negate)
|
||||
stack.append(negate)
|
||||
else:
|
||||
result.append(token)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# --------------------------------------------------
|
||||
@@ -724,6 +729,8 @@ class expression(object):
|
||||
""" Return a domain implementing the child_of operator for [(left,child_of,ids)],
|
||||
either as a range using the parent_left/right tree lookup fields
|
||||
(when available), or as an expanded [(left,in,child_ids)] """
|
||||
if not ids:
|
||||
return FALSE_DOMAIN
|
||||
if left_model._parent_store and (not left_model.pool._init):
|
||||
# TODO: Improve where joins are implemented for many with '.', replace by:
|
||||
# doms += ['&',(prefix+'.parent_left','<',o.parent_right),(prefix+'.parent_left','>=',o.parent_left)]
|
||||
|
||||
Reference in New Issue
Block a user