diff --git a/addons/account_analytic_default/security/ir.model.access.csv b/addons/account_analytic_default/security/ir.model.access.csv index cdfebbbb156..dc2ecfdb076 100644 --- a/addons/account_analytic_default/security/ir.model.access.csv +++ b/addons/account_analytic_default/security/ir.model.access.csv @@ -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 diff --git a/addons/account_check_writing/report/check_print.py b/addons/account_check_writing/report/check_print.py index e67d334ae34..f5c304a90b7 100644 --- a/addons/account_check_writing/report/check_print.py +++ b/addons/account_check_writing/report/check_print.py @@ -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]) diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index 32baf5b302a..344bf902d11 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -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, diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index cbaaa77bd38..05ccdc384d1 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -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(): diff --git a/addons/hr_contract/security/ir.model.access.csv b/addons/hr_contract/security/ir.model.access.csv index 5e35e147230..820935111f0 100644 --- a/addons/hr_contract/security/ir.model.access.csv +++ b/addons/hr_contract/security/ir.model.access.csv @@ -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 diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index 6be3ba3b885..90265a055fd 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -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): diff --git a/addons/mrp/report/price.py b/addons/mrp/report/price.py index 87387280c95..1b0f7f589f2 100644 --- a/addons/mrp/report/price.py +++ b/addons/mrp/report/price.py @@ -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 = '' 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 = """"""+rml_obj.formatLang(price)+' '+ (company_currency_symbol)+"""\r\n""" + main_sp_price = """"""+rml_obj.formatLang(price, digits=purchase_price_digits)+' '+ (company_currency_symbol)+"""\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 += """"""+rml_obj.formatLang(price) +' '+ (company_currency_symbol) +"""\r\n""" + sellers_price += """"""+rml_obj.formatLang(price, digits=purchase_price_digits) +' '+ (company_currency_symbol) +"""\r\n""" xml += """ """+ prod_name +""" """+ main_sp_name + sellers + """ - """+ rml_obj.formatLang(prod_qtty) +' '+ product_uom_name +""" - """+ rml_obj.formatLang(float(main_strd_price)) +' '+ (company_currency_symbol) +""" + """+ rml_obj.formatLang(prod_qtty, digits=product_uom_digits) +' '+ product_uom_name +""" + """+ rml_obj.formatLang(float(main_strd_price), digits=purchase_price_digits) +' '+ (company_currency_symbol) +""" """ + main_sp_price + sellers_price + """'""" xml += '' @@ -140,8 +142,6 @@ class report_custom(report_rml): """ % (_('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) diff --git a/addons/portal_sale/portal_sale_data.xml b/addons/portal_sale/portal_sale_data.xml index 77cb4f3b2dc..fb611c845a1 100644 --- a/addons/portal_sale/portal_sale_data.xml +++ b/addons/portal_sale/portal_sale_data.xml @@ -41,7 +41,7 @@ % endif

- <% set signup_url = object.get_signup_url() %> + % set signup_url = object.get_signup_url() % if signup_url and object.amount_total > 0 :

You can access this document and pay online via our Customer Portal: @@ -128,7 +128,7 @@ % endif

- <% set signup_url = object.get_signup_url() %> + % set signup_url = object.get_signup_url() % if signup_url:

You can access the invoice document and pay online via our Customer Portal: diff --git a/addons/product/product.py b/addons/product/product.py index 874eb872a79..e68df7014a3 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -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 diff --git a/addons/sale_service/models/sale_service.py b/addons/sale_service/models/sale_service.py index 74540d43547..02d86af6a81 100644 --- a/addons/sale_service/models/sale_service.py +++ b/addons/sale_service/models/sale_service.py @@ -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. """ diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index 872ebce9a9d..8f2c32a2cb5 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -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, diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 4aafa8abfe6..701494ac3ae 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -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.""" diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index c00cedc620b..b15780d252a 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -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_.*$/; diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index b716f0f0ef3..3461134d725 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -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; }; diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 929db311e30..04f6511a1de 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -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 () { diff --git a/addons/web_calendar/static/src/js/web_calendar.js b/addons/web_calendar/static/src/js/web_calendar.js index 4da7e37a2fe..dd91ca775ac 100644 --- a/addons/web_calendar/static/src/js/web_calendar.js +++ b/addons/web_calendar/static/src/js/web_calendar.js @@ -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; } diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 11db1070ea2..9af3f55df3c 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -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: diff --git a/addons/website_mail_group/views/website_mail_group.xml b/addons/website_mail_group/views/website_mail_group.xml index 6617e3d7895..28ab10b4ea9 100644 --- a/addons/website_mail_group/views/website_mail_group.xml +++ b/addons/website_mail_group/views/website_mail_group.xml @@ -190,7 +190,7 @@ }'/> - - + -

@@ -262,7 +262,7 @@ }'/> - - + - -

diff --git a/addons/website_quote/data/website_quotation_data.xml b/addons/website_quote/data/website_quotation_data.xml index 56a8810e76b..9466aebee95 100644 --- a/addons/website_quote/data/website_quotation_data.xml +++ b/addons/website_quote/data/website_quotation_data.xml @@ -201,7 +201,7 @@ % endif

- <% set signup_url = object.get_signup_url() %> + % set signup_url = object.get_signup_url() % if signup_url and object.amount_total > 0 :

You can access this document and pay online via our Customer Portal: diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js index 03b913fff1c..67c17e86fec 100644 --- a/addons/website_sale/static/src/js/website_sale.js +++ b/addons/website_sale/static/src/js/website_sale.js @@ -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 () { diff --git a/addons/website_sale_options/views/templates.xml b/addons/website_sale_options/views/templates.xml index 411ce05942b..f3e8b7f7a1a 100644 --- a/addons/website_sale_options/views/templates.xml +++ b/addons/website_sale_options/views/templates.xml @@ -141,5 +141,14 @@

+ + diff --git a/doc/cla/corporate/adhoc.md b/doc/cla/corporate/adhoc.md index b43d8ef0e93..627065bafab 100644 --- a/doc/cla/corporate/adhoc.md +++ b/doc/cla/corporate/adhoc.md @@ -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 diff --git a/doc/cla/corporate/sodexis.md b/doc/cla/corporate/sodexis.md new file mode 100644 index 00000000000..2a642474497 --- /dev/null +++ b/doc/cla/corporate/sodexis.md @@ -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 diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index b1f1abb5060..c1fbd1a0b22 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -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): diff --git a/openerp/addons/base/tests/test_expression.py b/openerp/addons/base/tests/test_expression.py index 43bd7e33394..7750a8d82fb 100644 --- a/openerp/addons/base/tests/test_expression.py +++ b/openerp/addons/base/tests/test_expression.py @@ -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') diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 4ad8b9d3051..ea2815a0226 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -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)]