+
+
@@ -266,6 +267,7 @@
+
product.product.procurement
product.product
@@ -336,6 +338,7 @@
+
product.template_procurement
product.template
@@ -397,6 +400,22 @@
class="oe_stat_button" icon="fa-cogs"
groups="stock.group_adv_location"/>
+
+
+
+ {'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '>', 1)]}
+
+
+ {'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '>', 1)]}
+
+
+ {'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '>', 1)]}
+
+
+ {'invisible':['|', ('type', 'not in', ['product', 'consu']), ('product_variant_count', '>', 1)]}
+
+
diff --git a/addons/stock/views/stock_quant_views.xml b/addons/stock/views/stock_quant_views.xml
index a33039ce7bd..67997c74854 100644
--- a/addons/stock/views/stock_quant_views.xml
+++ b/addons/stock/views/stock_quant_views.xml
@@ -86,7 +86,7 @@
-
+
diff --git a/addons/stock_account/models/account_invoice.py b/addons/stock_account/models/account_invoice.py
index aacdd6c7177..6291da2922f 100644
--- a/addons/stock_account/models/account_invoice.py
+++ b/addons/stock_account/models/account_invoice.py
@@ -38,7 +38,7 @@ class AccountInvoice(models.Model):
cacc = accounts['expense'].id
if dacc and cacc:
price_unit = i_line._get_anglo_saxon_price_unit()
- if inv.currency_id.id != company_currency:
+ if inv.currency_id != company_currency:
currency_id = inv.currency_id.id
amount_currency = i_line._get_price(company_currency, price_unit)
else:
@@ -99,6 +99,6 @@ class AccountInvoiceLine(models.Model):
def get_invoice_line_account(self, type, product, fpos, company):
if company.anglo_saxon_accounting and type in ('in_invoice', 'in_refund') and product and product.type == 'product':
accounts = product.product_tmpl_id.get_product_accounts(fiscal_pos=fpos)
- if accounts['stock_input']:
+ if product.categ_id.property_valuation != 'manual_periodic' and accounts['stock_input']:
return accounts['stock_input']
return super(AccountInvoiceLine, self).get_invoice_line_account(type, product, fpos, company)
diff --git a/addons/web/static/src/js/views/list_view_editable.js b/addons/web/static/src/js/views/list_view_editable.js
index 6beb6a7a044..42987a64af1 100644
--- a/addons/web/static/src/js/views/list_view_editable.js
+++ b/addons/web/static/src/js/views/list_view_editable.js
@@ -634,6 +634,7 @@ ListView.include(/** @lends instance.web.ListView# */{
* @return {*}
*/
_next: function (next_record, options) {
+ options = options || {};
next_record = next_record || 'succ';
var self = this;
return this.save_edition().then(function (saveInfo) {
diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js
index 95e8d13e5ff..c40d17b41e2 100644
--- a/addons/web/static/src/js/views/pivot_view.js
+++ b/addons/web/static/src/js/views/pivot_view.js
@@ -201,8 +201,8 @@ var PivotView = View.extend({
this.initial_col_groupby = context.pivot_col_groupby || this.initial_col_groupby;
}
this.main_row.groupbys = group_by.length ? group_by : (context.pivot_row_groupby || this.initial_row_groupby.slice(0));
- this.main_col.groupbys = context.pivot_column_groupby || this.initial_col_groupby.slice(0);
- this.active_measures = context.pivot_measures || this.active_measures;
+ this.main_col.groupbys = context.col_group_by || context.pivot_column_groupby || this.initial_col_groupby.slice(0);
+ this.active_measures = context.measures || context.pivot_measures || this.active_measures;
this.domain = domain;
this.context = context;
diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js
index 60c096603d8..4ab1e7bed06 100644
--- a/addons/web_editor/static/src/js/rte.summernote.js
+++ b/addons/web_editor/static/src/js/rte.summernote.js
@@ -511,7 +511,12 @@ function prettify_html(html) {
level--;
}
- result += token.trim().replace(/\s+/, ' ');
+ // don't trim inline content (which could change appearance)
+ if (!inline) {
+ token = token.trim();
+ }
+
+ result += token.replace(/\s+/, ' ');
if (inline_level > level) {
result += '\n';
diff --git a/addons/web_editor/static/src/js/snippets.editor.js b/addons/web_editor/static/src/js/snippets.editor.js
index 20a96a91e84..42f2103f95f 100644
--- a/addons/web_editor/static/src/js/snippets.editor.js
+++ b/addons/web_editor/static/src/js/snippets.editor.js
@@ -107,7 +107,7 @@ data.Class = Widget.extend({
return $from.closest(selector, parentNode);
},
all: function ($from) {
- return $from ? $from.find(selector) : $(selector);
+ return $from ? cssFind($from, selector) : $(selector);
},
is: function ($from) {
return $from.is(selector);
@@ -130,15 +130,31 @@ data.Class = Widget.extend({
});
},
all: is_children ? function ($from) {
- return ($from || self.$editable).find(selector);
+ return cssFind($from || self.$editable, selector);
} : function ($from) {
- return $from ? $from.find(selector) : self.$editable.filter(selector).add(self.$editable.find(selector));
+ $from = $from || self.$editable;
+ return $from.filter(selector).add(cssFind($from, selector));
},
is: function ($from) {
return $from.is(selector);
}
};
}
+
+ /**
+ * jQuery find function behavior is:
+ * $('A').find('A B') <=> $('A A B')
+ * The searches behavior to find options' DOM needs to be
+ * $('A').find('A B') <=> $('A B')
+ * This is what this function does.
+ *
+ * @param {jQuery} $from - the jQuery element(s) from which to search
+ * @param {string} selector - the CSS selector to match
+ * @returns {jQuery}
+ */
+ function cssFind($from, selector) {
+ return $from.find('*').filter(selector);
+ }
},
fetch_snippet_templates: function () {
diff --git a/addons/web_editor/static/src/js/transcoder.js b/addons/web_editor/static/src/js/transcoder.js
index 05068972532..6b42e028b3a 100644
--- a/addons/web_editor/static/src/js/transcoder.js
+++ b/addons/web_editor/static/src/js/transcoder.js
@@ -121,6 +121,16 @@ var getMatchedCSSRules = function (a) {
}
});
+ // text-decoration rule is decomposed in -line, -color and -style. This is
+ // however not supported by many browser/mail clients and the editor does
+ // not allow to change -color and -style rule anyway
+ if (style['text-decoration-line']) {
+ style['text-decoration'] = style['text-decoration-line'];
+ delete style['text-decoration-line'];
+ delete style['text-decoration-color'];
+ delete style['text-decoration-style'];
+ }
+
return style;
};
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index c62486d34a0..1e65fd256a8 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -179,8 +179,8 @@ class Http(models.AbstractModel):
nearest_lang = not func and cls.get_nearest_lang(path[1])
url_lang = nearest_lang and path[1]
preferred_lang = ((cook_lang if cook_lang in langs else False)
- or (not is_a_bot and cls.get_nearest_lang(request.lang))
- or request.website.default_lang_code)
+ or request.website.default_lang_code
+ or (not is_a_bot and cls.get_nearest_lang(request.lang)))
request.lang = context['lang'] = nearest_lang or preferred_lang
# if lang in url but not the displayed or default language --> change or remove
diff --git a/addons/website/static/src/less/website.snippets.less b/addons/website/static/src/less/website.snippets.less
index 5f96bbac0e6..cde2501e605 100644
--- a/addons/website/static/src/less/website.snippets.less
+++ b/addons/website/static/src/less/website.snippets.less
@@ -391,6 +391,7 @@ div.carousel {
// Parallax Theme
.parallax {
position: relative;
+ z-index: 0;
.oe_structure {
> :first-child {
diff --git a/addons/website/static/src/less/website.ui.less b/addons/website/static/src/less/website.ui.less
index 8f4732024b4..b515ad40096 100644
--- a/addons/website/static/src/less/website.ui.less
+++ b/addons/website/static/src/less/website.ui.less
@@ -47,4 +47,10 @@ body.o_connected_user {
font-size: 20px;
}
}
+ @media only screen and (max-width: @screen-xs-max) {
+ #oe_applications {
+ position: inherit;
+ z-index: 1002;
+ }
+ }
}
diff --git a/addons/website_blog/static/src/js/contentshare.js b/addons/website_blog/static/src/js/contentshare.js
index 47cd88fc4a7..cfc02470f7f 100644
--- a/addons/website_blog/static/src/js/contentshare.js
+++ b/addons/website_blog/static/src/js/contentshare.js
@@ -22,9 +22,12 @@
popover_content += '';
}
if($('.blog_title, .blog_content').hasClass('js_tweet')){
- var current_url = window.location.href;
- var tweet_selected_text = this.getSelection('string').substring(0,option.maxLength-(current_url.length+option.author_name.length+7));
- var text = encodeURIComponent(_.str.sprintf('\"%s\" --@%s %s', tweet_selected_text, option.author_name, current_url));
+ var tweet = '"%s" - %s';
+ var baseLength = tweet.replace(/%s/g, '').length;
+ // Shorten the selected text to match the tweet max length
+ // Note: all (non-localhost) urls in a tweet have 23 characters https://support.twitter.com/articles/78124
+ var selectedText = this.getSelection('string').substring(0, option.maxLength - baseLength - 23);
+ var text = encodeURIComponent(_.str.sprintf(tweet, selectedText, window.location.href));
popover_content += '
';
}
return popover_content;
diff --git a/addons/website_blog/static/src/js/website_blog.js b/addons/website_blog/static/src/js/website_blog.js
index 5c2d82f7646..7985e0ebecc 100644
--- a/addons/website_blog/static/src/js/website_blog.js
+++ b/addons/website_blog/static/src/js/website_blog.js
@@ -58,7 +58,7 @@ function shareArticle(event){
$(document).ready(function() {
if ($('.website_blog').length) {
- $(".js_tweet, .js_comment").share({'author_name': $('#blog_author').text()});
+ $(".js_tweet, .js_comment").share({});
$('.cover_footer').on('click', page_transist);
$('a[href^="#blog_content"]').on('click', animate);
$('.o_twitter, .o_facebook, .o_linkedin, .o_google, .o_twitter_complete, .o_facebook_complete, .o_linkedin_complete, .o_google_complete').on('click', shareArticle);
diff --git a/addons/website_form/static/src/js/website_form.js b/addons/website_form/static/src/js/website_form.js
index f94371261d9..38f37a27340 100644
--- a/addons/website_form/static/src/js/website_form.js
+++ b/addons/website_form/static/src/js/website_form.js
@@ -167,13 +167,15 @@ odoo.define('website_form.animation', function (require) {
// Special cases for dates and datetimes
} else if ($(input).hasClass('o_website_form_date')) {
- return !self.is_datetime_valid(input.value, 'date');
+ if (!self.is_datetime_valid(input.value, 'date')) {
+ return true;
+ }
} else if ($(input).hasClass('o_website_form_datetime')) {
- return !self.is_datetime_valid(input.value, 'datetime');
-
- } else {
- return !input.checkValidity();
+ if (!self.is_datetime_valid(input.value, 'datetime')) {
+ return true;
+ }
}
+ return !input.checkValidity();
})
// Update field color if invalid or erroneous
diff --git a/addons/website_mail/controllers/main.py b/addons/website_mail/controllers/main.py
index c6fc592e3d8..546def6890a 100644
--- a/addons/website_mail/controllers/main.py
+++ b/addons/website_mail/controllers/main.py
@@ -34,6 +34,7 @@ def _message_post_helper(res_model='', res_id=None, message='', token='', token_
else:
if not author_id:
raise NotFound()
+ kw.pop('csrf_token', None)
return record.with_context(mail_create_nosubscribe=nosubscribe).message_post(body=message,
message_type=kw.pop('message_type', "comment"),
subtype=kw.pop('subtype', "mt_comment"),
diff --git a/addons/website_portal_sale/controllers/main.py b/addons/website_portal_sale/controllers/main.py
index a83a15ce6a4..79d5d424313 100644
--- a/addons/website_portal_sale/controllers/main.py
+++ b/addons/website_portal_sale/controllers/main.py
@@ -223,10 +223,10 @@ class website_account(website_account):
partner = request.env['res.users'].browse(request.uid).partner_id
invoices = request.env['account.invoice'].sudo().search_count([('partner_id', '=', partner.id), ('state', 'not in', ['draft', 'cancel'])])
if invoices:
- if (data.get('vat', partner.vat) or False) != partner.vat:
+ if 'vat' in partner and (data['vat'] or False) != (partner.vat or False):
error['vat'] = 'error'
error_message.append(_('Changing VAT number is not allowed once invoices have been issued for your account. Please contact us directly for this operation.'))
- if data.get('name', partner.name) != partner.name:
+ if 'name' in data and (data['name'] or False) != (partner.name or False):
error['name'] = 'error'
error_message.append(_('Changing your name is not allowed once invoices have been issued for your account. Please contact us directly for this operation.'))
return error, error_message
diff --git a/addons/website_quote/models/sale_order.py b/addons/website_quote/models/sale_order.py
index d84b6b6766b..d5f3f2483ab 100644
--- a/addons/website_quote/models/sale_order.py
+++ b/addons/website_quote/models/sale_order.py
@@ -76,6 +76,13 @@ class SaleOrder(models.Model):
(2, 'Immediate after website order validation and save a token'),
], 'Payment', help="Require immediate payment by the customer when validating the order from the website quote")
+ @api.multi
+ def copy(self, default=None):
+ if self.template_id and self.template_id.number_of_days > 0:
+ default = dict(default or {})
+ default['validity_date'] = fields.Date.to_string(datetime.now() + timedelta(self.template_id.number_of_days))
+ return super(SaleOrder, self).copy(default=default)
+
@api.one
def _compute_amount_undiscounted(self):
total = 0.0
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 407a2e7cd11..1208625bc9f 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -321,7 +321,8 @@ class WebsiteSale(http.Controller):
@http.route(['/shop/change_pricelist/
'], type='http', auth="public", website=True)
def pricelist_change(self, pl_id, **post):
- if request.website.is_pricelist_available(pl_id.id):
+ if (pl_id.selectable or pl_id == request.env.user.partner_id.property_product_pricelist) \
+ and request.website.is_pricelist_available(pl_id.id):
request.session['website_sale_current_pl'] = pl_id.id
request.website.sale_get_order(force_pricelist=pl_id.id)
return request.redirect(request.httprequest.referrer or '/shop')
diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py
index d5a8f77a72d..d3e1c6b893a 100644
--- a/addons/website_sale/models/product.py
+++ b/addons/website_sale/models/product.py
@@ -19,7 +19,7 @@ class ProductPricelist(models.Model):
return self.env['website'].search([], limit=1)
website_id = fields.Many2one('website', string="website", default=_default_website)
- code = fields.Char(string='E-commerce Promotional Code')
+ code = fields.Char(string='E-commerce Promotional Code', groups="base.group_user")
selectable = fields.Boolean(help="Allow the end user to choose this price list")
def clear_cache(self):
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 7e4e3c42bcf..15030fc510e 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -71,7 +71,7 @@ class SaleOrder(models.Model):
if order.pricelist_id and order.partner_id:
order_line = order._cart_find_product_line(product.id)
if order_line:
- pu = self.env['account.tax']._fix_tax_included_price(pu, product.taxes_id, order_line.tax_id)
+ pu = self.env['account.tax']._fix_tax_included_price(pu, product.taxes_id, order_line[0].tax_id)
return {
'product_id': product_id,
@@ -228,7 +228,7 @@ class Website(models.Model):
if not pricelists: # no pricelist for this country, or no GeoIP
pricelists |= all_pl.filtered(lambda pl: not show_visible or pl.selectable or pl.id in (current_pl, order_pl))
else:
- pricelists |= all_pl.filtered(lambda pl: not show_visible and pl.code)
+ pricelists |= all_pl.filtered(lambda pl: not show_visible and pl.sudo().code)
# This method is cached, must not return records! See also #8795
return pricelists.ids
@@ -397,6 +397,9 @@ class Website(models.Model):
partner.write({'last_website_so_id': sale_order.id})
if sale_order:
+ # case when user emptied the cart
+ if not request.session.get('sale_order_id'):
+ request.session['sale_order_id'] = sale_order.id
# check for change of pricelist with a coupon
pricelist_id = pricelist_id or partner.property_product_pricelist.id
@@ -435,7 +438,7 @@ class Website(models.Model):
update_pricelist = True
if code and code != sale_order.pricelist_id.code:
- code_pricelist = self.env['product.pricelist'].search([('code', '=', code)], limit=1)
+ code_pricelist = self.env['product.pricelist'].sudo().search([('code', '=', code)], limit=1)
if code_pricelist:
pricelist_id = code_pricelist.id
update_pricelist = True
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 1df1fcd0e97..6ac948b4fe1 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -250,6 +250,7 @@ odoo.define('website_sale.website_sale', function (require) {
if ($(".decimal_precision").length) {
precision = parseInt($(".decimal_precision").last().data('precision'));
+ if (!precision) { precision = 0; } //todo: remove me in master/saas-17
}
var formatted = _.str.sprintf('%.' + precision + 'f', price).split('.');
formatted[0] = utils.insert_thousand_seps(formatted[0]);
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 6dcff342f5a..0387badeaa8 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -600,7 +600,7 @@
-
+
trans.src) gives the original value back
value0 = field.translate(lambda term: None, record[fname])
value1 = field.translate({trans.src: trans.value}.get, value0)
+ # don't check the reverse if no translation happened
+ if value0 == value1:
+ continue
value2 = field.translate({trans.value: trans.src}.get, value1)
if value2 != value0:
raise ValidationError(_("Translation is not valid:\n%s") % trans.value)
diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py
index 8414d933f9b..4d890494aa8 100644
--- a/odoo/addons/base/ir/ir_ui_view.py
+++ b/odoo/addons/base/ir/ir_ui_view.py
@@ -457,7 +457,8 @@ actual arch.
# cannot currently use relationships that are
# not required. The root cause is the INNER JOIN
# used to implement it.
- views = self.search(conditions + [('model_ids.module', 'in', tuple(self.pool._init_modules))])
+ modules = tuple(self.pool._init_modules) + (self._context.get('install_mode_data', {}).get('module'),)
+ views = self.search(conditions + [('model_ids.module', 'in', modules)])
views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + map(int, views))])
else:
views = self.search(conditions)
diff --git a/odoo/addons/base/res/res.country.state.csv b/odoo/addons/base/res/res.country.state.csv
index 877834a77ca..c48ed49141d 100644
--- a/odoo/addons/base/res/res.country.state.csv
+++ b/odoo/addons/base/res/res.country.state.csv
@@ -436,7 +436,7 @@ state_es_cu,es,"Cuenca","CU"
state_es_gi,es,"Girona (Gerona)","GI"
state_es_gr,es,"Granada","GR"
state_es_gu,es,"Guadalajara","GU"
-state_es_ss,es,"Guipuzkoa (Guipúzcoa)","SS"
+state_es_ss,es,"Gipuzkoa (Guipúzcoa)","SS"
state_es_h,es,"Huelva","H"
state_es_hu,es,"Huesca","HU"
state_es_j,es,"Jaén","J"
diff --git a/odoo/addons/base/tests/test_translate.py b/odoo/addons/base/tests/test_translate.py
index 1cd833c9df6..de607798a82 100644
--- a/odoo/addons/base/tests/test_translate.py
+++ b/odoo/addons/base/tests/test_translate.py
@@ -205,10 +205,16 @@ class TranslationToolsTestCase(unittest.TestCase):
self.assertEquals(result, expect)
def test_translate_html(self):
+ """ Test html_translate(). """
+ source = """A B C """
+ result = html_translate(lambda term: term, source)
+ self.assertEquals(result, source)
+
+ def test_translate_html_i(self):
""" Test xml_translate() and html_translate() with elements. """
- source = """ """
+ source = """ A B
"""
result = xml_translate(lambda term: term, source)
- self.assertEquals(result, """ """)
+ self.assertEquals(result, """A B
""")
result = html_translate(lambda term: term, source)
self.assertEquals(result, source)
diff --git a/odoo/models.py b/odoo/models.py
index cb9f32c9c94..be607bb72e2 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -3818,7 +3818,7 @@ class BaseModel(object):
del vals['source'] # remove source to avoid triggering _set_src
del vals['module'] # duplicated vals is not linked to any module
vals['res_id'] = target_id
- if vals['lang'] == old.env.lang:
+ if vals['lang'] == old.env.lang and field.translate is True:
# 'source' to force the call to _set_src
# 'value' needed if value is changed in copy(), want to see the new_value
vals['source'] = old_wo_lang[name]
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index c947ae5e75d..fbef3443929 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -17,7 +17,7 @@ from datetime import datetime
from os.path import join
from babel.messages import extract
-from lxml import etree
+from lxml import etree, html
import odoo
from odoo.tools import config
@@ -153,8 +153,13 @@ avoid_pattern = re.compile(r"\s*]*>(.*)[^<]*>", re.DOTALL | re.MULTILINE | re.UNICODE)
-def translate_xml_node(node, callback, method, parser=None):
- """ Return the translation of the given XML/HTML node. """
+def translate_xml_node(node, callback, parse, serialize):
+ """ Return the translation of the given XML/HTML node.
+
+ :param callback: callback(text) returns translated text or None
+ :param parse: parse(text) returns a node (text is unicode)
+ :param serialize: serialize(node) returns unicode text
+ """
def nonspace(text):
return bool(text) and not text.isspace()
@@ -182,7 +187,7 @@ def translate_xml_node(node, callback, method, parser=None):
def translate_content(node):
""" Return ``node`` with its content translated inline. """
# serialize the node that contains the stuff to translate
- text = etree.tostring(node, method=method, encoding='utf8').decode('utf8')
+ text = serialize(node)
# retrieve the node's content and translate it
match = node_pattern.match(text)
trans = translate_text(match.group(1))
@@ -190,7 +195,7 @@ def translate_xml_node(node, callback, method, parser=None):
# replace the content, and convert it back to an XML node
text = text[:match.start(1)] + trans + text[match.end(1):]
try:
- node = etree.fromstring(encode(text), parser=parser)
+ node = parse(text)
except etree.ParseError:
# fallback: escape the translation as text
node = etree.Element(node.tag, node.attrib, node.nsmap)
@@ -275,6 +280,21 @@ def translate_xml_node(node, callback, method, parser=None):
return node
+def parse_xml(text):
+ return etree.fromstring(encode(text))
+
+def serialize_xml(node):
+ return etree.tostring(node, method='xml', encoding='utf8').decode('utf8')
+
+_HTML_PARSER = etree.HTMLParser(encoding='utf8')
+
+def parse_html(text):
+ return html.fragment_fromstring(encode(text), parser=_HTML_PARSER)
+
+def serialize_html(node):
+ return etree.tostring(node, method='html', encoding='utf8').decode('utf8')
+
+
def xml_translate(callback, value):
""" Translate an XML value (string), using `callback` for translating text
appearing in `value`.
@@ -283,17 +303,15 @@ def xml_translate(callback, value):
return value
try:
- root = etree.fromstring(encode(value))
- result = translate_xml_node(root, callback, 'xml')
- return etree.tostring(result, method='xml', encoding='utf8').decode('utf8')
+ root = parse_xml(value)
+ result = translate_xml_node(root, callback, parse_xml, serialize_xml)
+ return serialize_xml(result)
except etree.ParseError:
# fallback for translated terms: use an HTML parser and wrap the term
- wrapped = "%s
" % encode(value)
- root = etree.fromstring(wrapped, etree.HTMLParser(encoding='utf-8'))
- # root is html > body > div; translate the div only
- result = translate_xml_node(root[0][0], callback, 'xml')
+ root = parse_html("%s
" % value)
+ result = translate_xml_node(root, callback, parse_xml, serialize_xml)
# remove tags and
from result
- return etree.tostring(result, method='xml', encoding='utf8').decode('utf8')[5:-6]
+ return serialize_xml(result)[5:-6]
def html_translate(callback, value):
""" Translate an HTML value (string), using `callback` for translating text
@@ -303,14 +321,11 @@ def html_translate(callback, value):
return value
try:
- parser = etree.HTMLParser(encoding='utf-8')
# value may be some HTML fragment, wrap it into a div
- wrapped = "%s
" % encode(value)
- root = etree.fromstring(wrapped, parser)
- # root is html > body > div; translate the div only
- result = translate_xml_node(root[0][0], callback, 'html', parser)
+ root = parse_html("%s
" % value)
+ result = translate_xml_node(root, callback, parse_html, serialize_html)
# remove tags and
from result
- value = etree.tostring(result, method='html', encoding='utf8').decode('utf8')[5:-6]
+ value = serialize_html(result)[5:-6]
except ValueError:
_logger.exception("Cannot translate malformed HTML, using source value instead")
diff --git a/requirements.txt b/requirements.txt
index 51325155af2..9109a29a8c7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,7 +12,7 @@ Mako==1.0.4
MarkupSafe==0.23
mock==2.0.0
num2words==0.5.4
-ofxparse==0.15
+ofxparse==0.16
passlib==1.6.5
Pillow==3.4.1
psutil==4.3.1