[MERGE] forward port of branch saas-5 up to 7eab880
This commit is contained in:
@@ -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')))
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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([
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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
@@ -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()
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user