diff --git a/doc/setup/install.rst b/doc/setup/install.rst index c1f65abafd1..add4f22962b 100644 --- a/doc/setup/install.rst +++ b/doc/setup/install.rst @@ -217,10 +217,13 @@ command is available and configured properly, run the following commands : $ sudo systemctl start postgresql In order to print PDF reports, you must install wkhtmltopdf_ yourself: -the version of wkhtmltopdf_ available in debian repositories does not support -headers and footers so it can not be installed automatically. -The recommended version is 0.12.1 and is available on `the wkhtmltopdf download page`_, -in the archive section. +the version of wkhtmltopdf_ available in Debian repositories does +not support headers and footers so it is not used as a direct dependency. +The recommended version is 0.12.5 and is available on +`the wkhtmltopdf download page`_, in the archive section. Previously +recommended version 0.12.1 is a good alternative. +More details on the various versions and their respective quirks can be +found in our `wiki `_. Repository ^^^^^^^^^^ @@ -606,7 +609,7 @@ official Odoo `docker image `_ page. http://www.enterprisedb.com/products-services-training/pgdownload .. _Quilt: http://en.wikipedia.org/wiki/Quilt_(software) .. _saas: https://www.odoo.com/page/start -.. _the wkhtmltopdf download page: https://github.com/wkhtmltopdf/wkhtmltopdf/releases/tag/0.12.1 +.. _the wkhtmltopdf download page: https://github.com/wkhtmltopdf/wkhtmltopdf/releases/tag/0.12.5 .. _UAC: http://en.wikipedia.org/wiki/User_Account_Control .. _wkhtmltopdf: http://wkhtmltopdf.org .. _pip: https://pip.pypa.io diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py index 41c4e336f39..cca836bde7e 100644 --- a/odoo/addons/base/ir/ir_translation.py +++ b/odoo/addons/base/ir/ir_translation.py @@ -445,15 +445,15 @@ class IrTranslation(models.Model): if not callable(field.translate): return - trans = self.env['ir.translation'] - outdated = trans - discarded = trans + Translation = self.env['ir.translation'] + outdated = Translation + discarded = Translation for record in records: # get field value and terms to translate value = record[field.name] terms = set(field.get_trans_terms(value)) - record_trans = trans.search([ + translations = Translation.search([ ('type', '=', 'model'), ('name', '=', "%s,%s" % (field.model_name, field.name)), ('res_id', '=', record.id), @@ -461,25 +461,32 @@ class IrTranslation(models.Model): if not terms: # discard all translations for that field - discarded += record_trans + discarded += translations continue - # remap existing translations on terms when possible - trans_src = record_trans.mapped('src') - for trans in record_trans: - if trans.src == trans.value: - discarded += trans - elif trans.src not in terms: - matches = get_close_matches(trans.src, terms, 1, 0.9) - if matches: - if matches[0] in trans_src: - # there is already a translation for this term; discard this one - discarded += trans - else: - trans.write({'src': matches[0], 'state': trans.state}) - trans_src.append(matches[0]) # avoid reuse of term - else: - outdated += trans + # remap existing translations on terms when possible; each term + # should be translated at most once per language + done = set() # {(src, lang), ...} + translations_to_match = [] + + for translation in translations: + if translation.src == translation.value: + discarded += translation + elif translation.src in terms: + done.add((translation.src, translation.lang)) + else: + translations_to_match.append(translation) + + for translation in translations_to_match: + matches = get_close_matches(translation.src, terms, 1, 0.9) + src = matches[0] if matches else None + if not src: + outdated += translation + elif (src, translation.lang) in done: + discarded += translation + else: + translation.write({'src': src, 'state': translation.state}) + done.add((src, translation.lang)) # process outdated and discarded translations outdated.write({'state': 'to_translate'}) diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py index b474be1a0d0..dff4075670f 100644 --- a/odoo/addons/base/res/res_users.py +++ b/odoo/addons/base/res/res_users.py @@ -449,6 +449,8 @@ class Users(models.Model): @api.model def check_credentials(self, password): """ Override this method to plug additional authentication methods""" + if not password: + raise AccessDenied() user = self.sudo().search([('id', '=', self._uid), ('password', '=', password)]) if not user: raise AccessDenied() diff --git a/odoo/addons/base/tests/test_translate.py b/odoo/addons/base/tests/test_translate.py index dd70c9111ca..76ff73aeebe 100644 --- a/odoo/addons/base/tests/test_translate.py +++ b/odoo/addons/base/tests/test_translate.py @@ -278,30 +278,36 @@ class TestTranslation(TransactionCase): class TestXMLTranslation(TransactionCase): def setUp(self): super(TestXMLTranslation, self).setUp() - self.env['ir.translation'].load_module_terms(['base'], ['fr_FR']) + self.env['ir.translation'].load_module_terms(['base'], ['fr_FR', 'nl_NL']) + + def create_view(self, archf, terms, **kwargs): + view = self.env['ir.ui.view'].create({ + 'name': 'test', + 'model': 'res.partner', + 'arch': archf % terms, + }) + for lang, trans_terms in kwargs.items(): + for src, val in pycompat.izip(terms, trans_terms): + self.env['ir.translation'].create({ + 'type': 'model', + 'name': 'ir.ui.view,arch_db', + 'lang': lang, + 'res_id': view.id, + 'src': src, + 'value': val, + 'state': 'translated', + }) + return view def test_copy(self): """ Create a simple view, fill in translations, and copy it. """ - env_en = self.env(context={}) - env_fr = self.env(context={'lang': 'fr_FR'}) - archf = '
%s
%s
' terms_en = ('Knife', 'Fork', 'Spoon') terms_fr = ('Couteau', 'Fourchette', 'Cuiller') - view0 = self.env['ir.ui.view'].create({ - 'name': 'test', - 'model': 'res.partner', - 'arch': archf % terms_en, - }) - for src, value in list(pycompat.izip(terms_en, terms_fr)): - self.env['ir.translation'].create({ - 'type': 'model', - 'name': 'ir.ui.view,arch_db', - 'lang': 'fr_FR', - 'res_id': view0.id, - 'src': src, - 'value': value, - }) + view0 = self.create_view(archf, terms_en, fr_FR=terms_fr) + + env_en = self.env(context={}) + env_fr = self.env(context={'lang': 'fr_FR'}) # check translated field self.assertEqual(view0.with_env(env_en).arch_db, archf % terms_en) @@ -328,17 +334,29 @@ class TestXMLTranslation(TransactionCase): archf = '
%s
%s
' terms_en = ('Knife', 'Fork', 'Spoon') terms_fr = (' Couteau', 'Fourchette ', ' Cuiller ') - view0 = self.env['ir.ui.view'].create({ - 'name': 'test', - 'model': 'res.partner', - 'arch': archf % terms_en, - }) - for src, value in list(pycompat.izip(terms_en, terms_fr)): - self.env['ir.translation'].create({ - 'type': 'model', - 'name': 'ir.ui.view,arch_db', - 'lang': 'fr_FR', - 'res_id': view0.id, - 'src': src, - 'value': value, - }) + self.create_view(archf, terms_en, fr_FR=terms_fr) + + def test_sync(self): + """ Check translations after minor change in source terms. """ + archf = '
%s
' + terms_en = ('Bread and cheeze',) + terms_fr = ('Pain et fromage',) + terms_nl = ('Brood and kaas',) + view = self.create_view(archf, terms_en, fr_FR=terms_fr, nl_NL=terms_nl) + + env_en = self.env(context={}) + env_fr = self.env(context={'lang': 'fr_FR'}) + env_nl = self.env(context={'lang': 'nl_NL'}) + + self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr) + self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl) + + # modify source term in view (fixed type in 'cheeze') + terms_en = ('Bread and cheese',) + view.write({'arch_db': archf % terms_en}) + + # check whether translations have been synchronized + self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr) + self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)