[MERGE] forward port of branch saas-5 up to 7eab880

This commit is contained in:
Christophe Simonis
2014-09-15 14:00:02 +02:00
21 changed files with 108 additions and 45 deletions
+1 -1
View File
@@ -145,7 +145,7 @@ class email_template(osv.osv):
# - img src -> check URL # - img src -> check URL
# - a href -> check URL # - a href -> check URL
for node in root.iter(): for node in root.iter():
if node.tag == 'a': if node.tag == 'a' and node.get('href'):
node.set('href', _process_link(node.get('href'))) node.set('href', _process_link(node.get('href')))
elif node.tag == 'img' and not node.get('src', 'data').startswith('data'): elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
node.set('src', _process_link(node.get('src'))) node.set('src', _process_link(node.get('src')))
+9 -13
View File
@@ -277,7 +277,6 @@ class hr_expense_expense(osv.osv):
if not mres: if not mres:
continue continue
res.append(mres) res.append(mres)
tax_code_found= False
#Calculate tax according to default tax on product #Calculate tax according to default tax on product
taxes = [] taxes = []
@@ -295,32 +294,28 @@ class hr_expense_expense(osv.osv):
a = product.categ_id.property_account_expense_categ.id a = product.categ_id.property_account_expense_categ.id
a = fpos_obj.map_account(cr, uid, fpos, a) a = fpos_obj.map_account(cr, uid, fpos, a)
taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False
tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
if not taxes: if not taxes:
continue continue
tax_l = []
#Calculating tax on the line and creating move? #Calculating tax on the line and creating move?
for tax in tax_obj.compute_all(cr, uid, taxes, for tax in tax_obj.compute_all(cr, uid, taxes,
line.unit_amount , line.unit_amount ,
line.unit_quantity, line.product_id, line.unit_quantity, line.product_id,
exp.user_id.partner_id)['taxes']: exp.user_id.partner_id)['taxes']:
tax_code_id = tax['base_code_id'] tax_code_id = tax['base_code_id']
tax_amount = line.total_amount * tax['base_sign'] if not tax_code_id:
if tax_code_found:
if not tax_code_id:
continue
res.append(self.move_line_get_item(cr, uid, line, context))
res[-1]['price'] = 0.0
res[-1]['account_analytic_id'] = False
elif not tax_code_id:
continue continue
tax_code_found = True
res[-1]['tax_code_id'] = tax_code_id res[-1]['tax_code_id'] = tax_code_id
res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm})
## ##
is_price_include = tax_obj.read(cr,uid,tax['id'],['price_include'],context)['price_include'] is_price_include = tax_obj.read(cr,uid,tax['id'],['price_include'],context)['price_include']
if is_price_include: if is_price_include:
## We need to deduce the price for the tax ## We need to deduce the price for the tax
res[-1]['price'] = res[-1]['price'] - (tax['amount'] * tax['base_sign'] or 0.0) res[-1]['price'] = res[-1]['price'] - (tax['amount'] * tax['base_sign'] or 0.0)
# tax amount countains base amount without the tax
tax_amount = (line.total_amount - tax['amount']) * tax['base_sign']
else:
tax_amount = line.total_amount * tax['base_sign']
res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm})
assoc_tax = { assoc_tax = {
'type':'tax', 'type':'tax',
'name':tax['name'], 'name':tax['name'],
@@ -331,7 +326,8 @@ class hr_expense_expense(osv.osv):
'tax_code_id': tax['tax_code_id'], 'tax_code_id': tax['tax_code_id'],
'tax_amount': tax['amount'] * tax['base_sign'], 'tax_amount': tax['amount'] * tax['base_sign'],
} }
res.append(assoc_tax) tax_l.append(assoc_tax)
res += tax_l
return res return res
def move_line_get_item(self, cr, uid, line, context=None): def move_line_get_item(self, cr, uid, line, context=None):
+1 -1
View File
@@ -32,7 +32,7 @@ class res_partner(osv.osv):
def _auto_init(self, cr, context=None): def _auto_init(self, cr, context=None):
result = super(res_partner, self)._auto_init(cr, context=context) result = super(res_partner, self)._auto_init(cr, context=context)
# Remove constrains for vat, nrc on "commercial entities" because is not mandatory by legislation # Remove constrains for vat, nrc on "commercial entities" because is not mandatory by legislation
# Even that VAT numbers are unique, the NRC field is not unique, and there are certain entities that # Even that VAT numbers are unique, the NRC field is not unique, and there are certain entities that
# doesn't have a NRC number plus the formatting was changed few times, so we cannot have a base rule for # doesn't have a NRC number plus the formatting was changed few times, so we cannot have a base rule for
# checking if available and emmited by the Ministry of Finance, only online on their website. # checking if available and emmited by the Ministry of Finance, only online on their website.
+1 -1
View File
@@ -111,7 +111,7 @@ class mail_thread(osv.AbstractModel):
model = context.get('empty_list_help_model') model = context.get('empty_list_help_model')
res_id = context.get('empty_list_help_id') res_id = context.get('empty_list_help_id')
ir_config_parameter = self.pool.get("ir.config_parameter") ir_config_parameter = self.pool.get("ir.config_parameter")
catchall_domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context) catchall_domain = ir_config_parameter.get_param(cr, SUPERUSER_ID, "mail.catchall.domain", context=context)
document_name = context.get('empty_list_help_document_name', _('document')) document_name = context.get('empty_list_help_document_name', _('document'))
alias = None alias = None
+1 -1
View File
@@ -343,7 +343,7 @@ class mrp_repair(osv.osv):
'origin': repair.name, 'origin': repair.name,
'type': 'out_invoice', 'type': 'out_invoice',
'account_id': account_id, 'account_id': account_id,
'partner_id': repair.partner_id.id, 'partner_id': repair.partner_invoice_id.id or repair.partner_id.id,
'currency_id': repair.pricelist_id.currency_id.id, 'currency_id': repair.pricelist_id.currency_id.id,
'comment': repair.quotation_notes, 'comment': repair.quotation_notes,
'fiscal_position': repair.partner_id.property_account_position.id 'fiscal_position': repair.partner_id.property_account_position.id
+1
View File
@@ -296,6 +296,7 @@
<field name="view_mode">kanban,tree,form</field> <field name="view_mode">kanban,tree,form</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_id" ref="product_template_kanban_view"/> <field name="view_id" ref="product_template_kanban_view"/>
<field name="context">{"search_default_filter_to_sell":1}</field>
</record> </record>
<menuitem action="product_template_action" <menuitem action="product_template_action"
+2
View File
@@ -934,6 +934,7 @@
<field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, False)"/> <field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, False)"/>
<field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/> <field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/>
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/> <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
<field name="product_uos_qty" groups="product.group_uos"/>
<field name="product_uos" groups="product.group_uos"/> <field name="product_uos" groups="product.group_uos"/>
<button name="%(stock.move_scrap)d" <button name="%(stock.move_scrap)d"
string="Scrap Products" type="action" string="Scrap Products" type="action"
@@ -960,6 +961,7 @@
<field name="product_id"/> <field name="product_id"/>
<field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/> <field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/>
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/> <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
<field name="product_uos_qty" groups="product.group_uos"/>
<field name="product_uos" groups="product.group_uos"/> <field name="product_uos" groups="product.group_uos"/>
<field name="location_id" groups="stock.group_locations" invisible="1"/> <field name="location_id" groups="stock.group_locations" invisible="1"/>
<field name="picking_id" invisible="1" /> <field name="picking_id" invisible="1" />
+3 -1
View File
@@ -123,7 +123,7 @@ def ensure_db(redirect='/web/database/selector'):
abort_and_redirect(url_redirect) abort_and_redirect(url_redirect)
# if db not provided, use the session one # if db not provided, use the session one
if not db: if not db and http.db_filter([request.session.db]):
db = request.session.db db = request.session.db
# if no database provided and no database in session, use monodb # if no database provided and no database in session, use monodb
@@ -509,6 +509,8 @@ class Home(http.Controller):
@http.route('/login', type='http', auth="none") @http.route('/login', type='http', auth="none")
def login(self, db, login, key, redirect="/web", **kw): def login(self, db, login, key, redirect="/web", **kw):
if not http.db_filter([db]):
return werkzeug.utils.redirect('/', 303)
return login_and_redirect(db, login, key, redirect_url=redirect) return login_and_redirect(db, login, key, redirect_url=redirect)
@http.route([ @http.route([
+7 -5
View File
@@ -1900,17 +1900,17 @@
.openerp .oe_application .oe_form_sheet .oe_notebook_page { .openerp .oe_application .oe_form_sheet .oe_notebook_page {
padding: 0 16px; padding: 0 16px;
} }
.openerp .oe_form > :not(.oe_form_nosheet) header { .openerp .oe_form > :not(.oe_form_nosheet) header, .openerp .oe_form > .oe_form_nosheet header {
padding-left: 2px; padding-left: 2px;
} }
.openerp .oe_form > :not(.oe_form_nosheet) header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu) { .openerp .oe_form > :not(.oe_form_nosheet) header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu), .openerp .oe_form > .oe_form_nosheet header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu) {
display: inline-block; display: inline-block;
float: right; float: right;
} }
.openerp .oe_form > :not(.oe_form_nosheet) header .oe_button { .openerp .oe_form > :not(.oe_form_nosheet) header .oe_button, .openerp .oe_form > .oe_form_nosheet header .oe_button {
margin: 3px 2px 1px; margin: 3px 2px 1px;
} }
.openerp .oe_form > :not(.oe_form_nosheet) header .oe_button:first-child { .openerp .oe_form > :not(.oe_form_nosheet) header .oe_button:first-child, .openerp .oe_form > .oe_form_nosheet header .oe_button:first-child {
margin-left: 6px; margin-left: 6px;
} }
.openerp .oe_form header { .openerp .oe_form header {
@@ -2631,7 +2631,9 @@
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float.oe_readonly, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer.oe_readonly { .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float.oe_readonly, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer.oe_readonly {
padding: 6px 0px 0px; padding: 6px 0px 0px;
text-align: right; text-align: right;
max-width: 100px; }
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float span, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer span {
padding: 0px 6px;
} }
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input { .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input {
width: 100% !important; width: 100% !important;
+3 -2
View File
@@ -1571,7 +1571,7 @@ $sheet-padding: 16px
padding: 0 16px padding: 0 16px
// }}} // }}}
// FormView.header {{{ // FormView.header {{{
.oe_form > :not(.oe_form_nosheet) header .oe_form > :not(.oe_form_nosheet) header, .oe_form > .oe_form_nosheet header
padding-left: 2px padding-left: 2px
ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu) ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu)
display: inline-block display: inline-block
@@ -2142,7 +2142,8 @@ $sheet-padding: 16px
&.oe_readonly &.oe_readonly
padding: 6px 0px 0px padding: 6px 0px 0px
text-align: right text-align: right
max-width: 100px span
padding: 0px 6px
input input
width: 100% !important width: 100% !important
text-align: right text-align: right
+2 -2
View File
@@ -1049,7 +1049,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
this.update_promise = this.update_promise.then(fct, fct); this.update_promise = this.update_promise.then(fct, fct);
}, },
on_menu_help: function() { on_menu_help: function() {
window.open('http://help.openerp.com', '_blank'); window.open('http://help.odoo.com', '_blank');
}, },
on_menu_logout: function() { on_menu_logout: function() {
this.trigger('user_logout'); this.trigger('user_logout');
@@ -1081,7 +1081,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
instance.web.redirect('https://accounts.odoo.com/oauth2/auth?'+$.param(params)); instance.web.redirect('https://accounts.odoo.com/oauth2/auth?'+$.param(params));
}).fail(function(result, ev){ }).fail(function(result, ev){
ev.preventDefault(); ev.preventDefault();
instance.web.redirect('https://accounts.openerp.com/web'); instance.web.redirect('https://accounts.odoo.com/web');
}); });
} }
}, },
+2
View File
@@ -969,6 +969,8 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
sign = -1; sign = -1;
field = field.slice(1); field = field.slice(1);
} }
if(!a[field] && a[field] !== 0){ return sign}
if(!b[field] && b[field] !== 0){ return (sign == -1) ? 1 : -1}
//m2o should be searched based on value[1] not based whole value(i.e. [id, value]) //m2o should be searched based on value[1] not based whole value(i.e. [id, value])
if(_.isArray(a[field]) && a[field].length == 2 && _.isString(a[field][1])){ if(_.isArray(a[field]) && a[field].length == 2 && _.isString(a[field][1])){
return sign * compare(a[field][1], b[field][1]); return sign * compare(a[field][1], b[field][1]);
@@ -106,7 +106,7 @@ $('.oe_website_sale').each(function () {
var product_id = false; var product_id = false;
for (var k in variant_ids) { for (var k in variant_ids) {
if (_.isEqual(variant_ids[k][1], values)) { if (_.isEmpty(_.difference(variant_ids[k][1], values))) {
$price.html(price_to_str(variant_ids[k][2])); $price.html(price_to_str(variant_ids[k][2]));
$default_price.html(price_to_str(variant_ids[k][3])); $default_price.html(price_to_str(variant_ids[k][3]));
if (variant_ids[k][3]-variant_ids[k][2]>0.2) { if (variant_ids[k][3]-variant_ids[k][2]>0.2) {
+2
View File
@@ -240,6 +240,8 @@ class ir_attachment(osv.osv):
for model, mids in res_ids.items(): for model, mids in res_ids.items():
# ignore attachments that are not attached to a resource anymore when checking access rights # ignore attachments that are not attached to a resource anymore when checking access rights
# (resource was deleted but attachment was not) # (resource was deleted but attachment was not)
if not self.pool.get(model):
continue
mids = self.pool[model].exists(cr, uid, mids) mids = self.pool[model].exists(cr, uid, mids)
ima.check(cr, uid, model, mode) ima.check(cr, uid, model, mode)
self.pool[model].check_access_rule(cr, uid, mids, mode, context=context) self.pool[model].check_access_rule(cr, uid, mids, mode, context=context)
+9 -7
View File
@@ -24,7 +24,7 @@ from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.charset import Charset from email.charset import Charset
from email.header import Header from email.header import Header
from email.utils import formatdate, make_msgid, COMMASPACE from email.utils import formatdate, make_msgid, COMMASPACE, parseaddr
from email import Encoders from email import Encoders
import logging import logging
import re import re
@@ -120,6 +120,7 @@ def encode_header_param(param_text):
return param_text_ascii if param_text_ascii\ return param_text_ascii if param_text_ascii\
else Charset('utf8').header_encode(param_text_utf8) else Charset('utf8').header_encode(param_text_utf8)
# TODO master, remove me, no longer used internaly
name_with_email_pattern = re.compile(r'("[^<@>]+")\s*<([^ ,<@]+@[^> ,]+)>') name_with_email_pattern = re.compile(r'("[^<@>]+")\s*<([^ ,<@]+@[^> ,]+)>')
address_pattern = re.compile(r'([^ ,<@]+@[^> ,]+)') address_pattern = re.compile(r'([^ ,<@]+@[^> ,]+)')
@@ -143,15 +144,16 @@ def encode_rfc2822_address_header(header_text):
header_text_ascii = try_coerce_ascii(header_text_utf8) header_text_ascii = try_coerce_ascii(header_text_utf8)
if header_text_ascii: if header_text_ascii:
return header_text_ascii return header_text_ascii
name, email = parseaddr(header_text_utf8)
if not name:
return email
# non-ASCII characters are present, attempt to # non-ASCII characters are present, attempt to
# replace all "Name" patterns with the RFC2047- # replace all "Name" patterns with the RFC2047-
# encoded version # encoded version
def replace(match_obj): name_encoded = str(Header(name, 'utf-8'))
name, email = match_obj.group(1), match_obj.group(2) header_text_utf8 = "%s <%s>" % (name_encoded, email)
name_encoded = str(Header(name, 'utf-8'))
return "%s <%s>" % (name_encoded, email)
header_text_utf8 = name_with_email_pattern.sub(replace,
header_text_utf8)
# try again after encoding # try again after encoding
header_text_ascii = try_coerce_ascii(header_text_utf8) header_text_ascii = try_coerce_ascii(header_text_utf8)
if header_text_ascii: if header_text_ascii:
+5 -2
View File
@@ -869,15 +869,18 @@ class Contact(orm.AbstractModel):
_inherit = 'ir.qweb.field.many2one' _inherit = 'ir.qweb.field.many2one'
def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None): def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
if context is None:
context = {}
if options is None: if options is None:
options = {} options = {}
opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"] opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"]
if not getattr(record, field_name): if not getattr(record, field_name):
return None return None
id = getattr(record, field_name).id id = getattr(record, field_name).id
field_browse = self.pool[column._obj].browse(cr, openerp.SUPERUSER_ID, id, context={"show_address": True}) context.update(show_address=True)
field_browse = self.pool[column._obj].browse(cr, openerp.SUPERUSER_ID, id, context=context)
value = field_browse.name_get()[0][1] value = field_browse.name_get()[0][1]
val = { val = {
+15
View File
@@ -30,6 +30,21 @@ class test_model(orm.Model):
'text': fields.text(), 'text': fields.text(),
} }
# `base` module does not contains any model that implement the `_group_by_full` functionality
# test this feature here...
def _gbf_m2o(self, cr, uid, ids, domain, read_group_order, access_rights_uid, context):
Sub = self.pool['test_converter.test_model.sub']
all_ids = Sub._search(cr, uid, [], access_rights_uid=access_rights_uid, context=context)
result = Sub.name_get(cr, access_rights_uid or uid, all_ids, context=context)
folds = {i: i not in ids for i, _ in result}
return result, folds
_group_by_full = {
'many2one': _gbf_m2o,
}
class test_model_sub(orm.Model): class test_model_sub(orm.Model):
_name = 'test_converter.test_model.sub' _name = 'test_converter.test_model.sub'
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import test_html from . import test_html, test_gbf
fast_suite = [ fast_suite = [
] ]
checks = [ checks = [
test_html test_html,
test_gbf,
] ]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from openerp.tests import common
class TestGBF(common.TransactionCase):
def test_group_by_full(self):
Subs = self.registry('test_converter.test_model.sub')
TM = self.registry('test_converter.test_model')
# remove all existing subs (no need to panic, it will be rollbacked...)
all_subs = Subs.search(self.cr, self.uid, [])
if all_subs:
Subs.unlink(self.cr, self.uid, all_subs)
subs_ids = [Subs.create(self.cr, self.uid, {'name': 'sub%d' % i}) for i in range(5)]
tm_ids = [TM.create(self.cr, self.uid, {'many2one': subs_ids[i]}) for i in range(3)]
domain = [('id', 'in', tuple(tm_ids))]
rg = TM.read_group(self.cr, self.uid, domain, fields=['many2one'], groupby=['many2one'])
self.assertEqual(len(rg), len(subs_ids))
rg_subs = sorted(g['many2one'][0] for g in rg)
self.assertListEqual(rg_subs, sorted(subs_ids))
+14 -5
View File
@@ -1854,7 +1854,8 @@ class BaseModel(object):
pass pass
def _read_group_fill_results(self, cr, uid, domain, groupby, remaining_groupbys, aggregated_fields, def _read_group_fill_results(self, cr, uid, domain, groupby, remaining_groupbys,
aggregated_fields, count_field,
read_group_result, read_group_order=None, context=None): read_group_result, read_group_order=None, context=None):
"""Helper method for filling in empty groups for all possible values of """Helper method for filling in empty groups for all possible values of
the field being grouped by""" the field being grouped by"""
@@ -1888,8 +1889,7 @@ class BaseModel(object):
result.append(left_side) result.append(left_side)
known_values[grouped_value] = left_side known_values[grouped_value] = left_side
else: else:
count_attr = groupby + '_count' known_values[grouped_value].update({count_field: left_side[count_field]})
known_values[grouped_value].update({count_attr: left_side[count_attr]})
def append_right(right_side): def append_right(right_side):
grouped_value = right_side[0] grouped_value = right_side[0]
if not grouped_value in known_values: if not grouped_value in known_values:
@@ -2134,12 +2134,13 @@ class BaseModel(object):
count_field = groupby_fields[0] if len(groupby_fields) >= 1 else '_' count_field = groupby_fields[0] if len(groupby_fields) >= 1 else '_'
else: else:
count_field = '_' count_field = '_'
count_field += '_count'
prefix_terms = lambda prefix, terms: (prefix + " " + ",".join(terms)) if terms else '' prefix_terms = lambda prefix, terms: (prefix + " " + ",".join(terms)) if terms else ''
prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else '' prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else ''
query = """ query = """
SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s_count %(extra_fields)s SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s %(extra_fields)s
FROM %(from)s FROM %(from)s
%(where)s %(where)s
%(groupby)s %(groupby)s
@@ -2179,7 +2180,7 @@ class BaseModel(object):
# method _read_group_fill_results need to be completely reimplemented # method _read_group_fill_results need to be completely reimplemented
# in a sane way # in a sane way
result = self._read_group_fill_results(cr, uid, domain, groupby_fields[0], groupby[len(annotated_groupbys):], result = self._read_group_fill_results(cr, uid, domain, groupby_fields[0], groupby[len(annotated_groupbys):],
aggregated_fields, result, read_group_order=order, aggregated_fields, count_field, result, read_group_order=order,
context=context) context=context)
return result return result
@@ -3540,6 +3541,7 @@ class BaseModel(object):
self.check_access_rule(cr, uid, ids, 'unlink', context=context) self.check_access_rule(cr, uid, ids, 'unlink', context=context)
pool_model_data = self.pool.get('ir.model.data') pool_model_data = self.pool.get('ir.model.data')
ir_values_obj = self.pool.get('ir.values') ir_values_obj = self.pool.get('ir.values')
ir_attachment_obj = self.pool.get('ir.attachment')
for sub_ids in cr.split_for_in_conditions(ids): for sub_ids in cr.split_for_in_conditions(ids):
cr.execute('delete from ' + self._table + ' ' \ cr.execute('delete from ' + self._table + ' ' \
'where id IN %s', (sub_ids,)) 'where id IN %s', (sub_ids,))
@@ -3561,6 +3563,13 @@ class BaseModel(object):
if ir_value_ids: if ir_value_ids:
ir_values_obj.unlink(cr, uid, ir_value_ids, context=context) ir_values_obj.unlink(cr, uid, ir_value_ids, context=context)
# For the same reason, removing the record relevant to ir_attachment
# The search is performed with sql as the search method of ir_attachment is overridden to hide attachments of deleted records
cr.execute('select id from ir_attachment where res_model = %s and res_id in %s', (self._name, sub_ids))
ir_attachment_ids = [ir_attachment[0] for ir_attachment in cr.fetchall()]
if ir_attachment_ids:
ir_attachment_obj.unlink(cr, uid, ir_attachment_ids, context=context)
# invalidate the *whole* cache, since the orm does not handle all # invalidate the *whole* cache, since the orm does not handle all
# changes made in the database, like cascading delete! # changes made in the database, like cascading delete!
recs.invalidate_cache() recs.invalidate_cache()
+1 -1
View File
@@ -607,7 +607,7 @@ command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
# Updated in 7.0 to match the model name as well # Updated in 7.0 to match the model name as well
# Typical form of references is <timestamp-openerp-record_id-model_name@domain> # Typical form of references is <timestamp-openerp-record_id-model_name@domain>
# group(1) = the record ID ; group(2) = the model (if any) ; group(3) = the domain # group(1) = the record ID ; group(2) = the model (if any) ; group(3) = the domain
reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?.*@(.*)>", re.UNICODE) reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?[^>]*@([^>]*)>", re.UNICODE)
def generate_tracking_message_id(res_id): def generate_tracking_message_id(res_id):