From d9a7291ef0f8043d891f8161f85f5160828525a9 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 15 May 2015 13:53:22 +0200 Subject: [PATCH 01/20] Better partner_firstname. Merged rebase. --- partner_firstname/README.rst | 55 +++++ partner_firstname/__init__.py | 11 +- partner_firstname/__openerp__.py | 32 +-- partner_firstname/data/res_partner.yml | 19 ++ partner_firstname/exceptions.py | 36 +++ partner_firstname/i18n/es.po | 52 ++-- partner_firstname/i18n/partner_firstname.pot | 33 ++- partner_firstname/models.py | 92 +++++++ partner_firstname/partner.py | 131 ---------- partner_firstname/partner_view.xml | 60 ----- partner_firstname/res_user.py | 49 ---- partner_firstname/res_user_view.xml | 35 --- .../tests/test_partner_firstname.py | 229 +++++++++--------- partner_firstname/views/res_partner.xml | 113 +++++++++ partner_firstname/views/res_user.xml | 49 ++++ 15 files changed, 536 insertions(+), 460 deletions(-) create mode 100644 partner_firstname/README.rst create mode 100644 partner_firstname/data/res_partner.yml create mode 100644 partner_firstname/exceptions.py create mode 100644 partner_firstname/models.py delete mode 100644 partner_firstname/partner.py delete mode 100644 partner_firstname/partner_view.xml delete mode 100644 partner_firstname/res_user.py delete mode 100644 partner_firstname/res_user_view.xml create mode 100644 partner_firstname/views/res_partner.xml create mode 100644 partner_firstname/views/res_user.xml diff --git a/partner_firstname/README.rst b/partner_firstname/README.rst new file mode 100644 index 000000000..774e735f0 --- /dev/null +++ b/partner_firstname/README.rst @@ -0,0 +1,55 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +Partner first name and last name +================================ + +This module was written to extend the functionality of contacts to support +having separate last name and first name. + +Usage +===== + +The field *name* becomes a stored function field concatenating the *last name* +and the *first name*. This avoids breaking compatibility with other modules. + +Users should fulfill manually the separate fields for *last name* and *first +name*, but in case you edit just the *name* field in some unexpected module, +there is an inverse function that tries to split that automatically. It assumes +that you write the *name* in format *"Lastname Firstname"*, but it could lead to +wrong splitting (because it's just blindly trying to guess what you meant), so +you better specify it manually. + +For the same reason, after installing, previous names for contacts will stay in +the *name* field, and the first time you edit any of them you will be asked to +supply the *last name* and *first name* (just once per contact). + +For further information, please visit: + +* https://www.odoo.com/forum/help-1 + +Credits +======= + +Contributors +------------ + +* Nicolas Bessi +* Jonathan Nemry +* Olivier Laurent +* Jairo Llopis + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/partner_firstname/__init__.py b/partner_firstname/__init__.py index bb6fb792a..4cdd79b3f 100644 --- a/partner_firstname/__init__.py +++ b/partner_firstname/__init__.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- -############################################################################## -# + # Author: Nicolas Bessi. Copyright Camptocamp SA +# Copyright (C) +# 2014: Agile Business Group () +# 2015: Grupo ESOC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -15,8 +17,5 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# -############################################################################## -from . import partner -from . import res_user +from . import models diff --git a/partner_firstname/__openerp__.py b/partner_firstname/__openerp__.py index c407ab7d3..265414cc9 100644 --- a/partner_firstname/__openerp__.py +++ b/partner_firstname/__openerp__.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- -############################################################################## -# + # Author: Nicolas Bessi. Copyright Camptocamp SA +# Copyright (C) +# 2014: Agile Business Group () +# 2015: Grupo ESOC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -15,35 +17,19 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -# -############################################################################## { - 'name': 'Partner first name, last name', - 'description': """ -This module splits first name and last name for non company partners -==================================================================== - -The field 'name' becomes a stored function field concatenating lastname and -firstname -Note: in version 7.0, installing this module invalidates a yaml test in the -'edi' module - -Contributors -============ -Jonathan Nemry -Olivier Laurent - -""", - 'version': '1.2', + 'name': 'Partner first name and last name', + 'summary': "Split first name and last name for non company partners", + 'version': '2.0', 'author': "Camptocamp,Odoo Community Association (OCA)", 'maintainer': 'Camptocamp, Acsone', 'category': 'Extra Tools', 'website': 'http://www.camptocamp.com, http://www.acsone.eu', 'depends': ['base'], 'data': [ - 'partner_view.xml', - 'res_user_view.xml', + 'views/res_partner.xml', + 'views/res_user.xml', ], 'demo': [], 'test': [], diff --git a/partner_firstname/data/res_partner.yml b/partner_firstname/data/res_partner.yml new file mode 100644 index 000000000..5b45f6643 --- /dev/null +++ b/partner_firstname/data/res_partner.yml @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) +# 2015: Grupo ESOC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +- !function {model: res.partner, name: _firstname_install} diff --git a/partner_firstname/exceptions.py b/partner_firstname/exceptions.py new file mode 100644 index 000000000..4f1dc6910 --- /dev/null +++ b/partner_firstname/exceptions.py @@ -0,0 +1,36 @@ +# -*- encoding: utf-8 -*- + +# Odoo, Open Source Management Solution +# Copyright (C) 2014-2015 Grupo ESOC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from openerp import _, exceptions + + +class PartnerNameError(exceptions.ValidationError): + def __init__(self, record, value=None): + self.record = record + self._value = value + self.name = _("Error(s) with partner %d's name.") % record.id + + @property + def value(self): + raise NotImplementedError() + + +class EmptyNames(PartnerNameError): + @property + def value(self): + return _("No name is set.") diff --git a/partner_firstname/i18n/es.po b/partner_firstname/i18n/es.po index 2a1b52af7..43411dd67 100644 --- a/partner_firstname/i18n/es.po +++ b/partner_firstname/i18n/es.po @@ -6,48 +6,54 @@ msgid "" msgstr "" "Project-Id-Version: OpenERP Server 7.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-12-23 14:09+0000\n" -"PO-Revision-Date: 2014-12-23 14:09+0000\n" -"Last-Translator: <>\n" +"POT-Creation-Date: 2015-03-30 07:53+0000\n" +"PO-Revision-Date: 2015-03-30 10:01+0100\n" +"Last-Translator: Jairo Llopis \n" "Language-Team: \n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: \n" +"X-Generator: Poedit 1.7.5\n" #. module: partner_firstname -#: code:addons/partner_firstname/partner.py:81 -#: code:addons/partner_firstname/res_user.py:35 -#: code:addons/partner_firstname/tests/test_partner_firstname.py:56 -#: code:addons/partner_firstname/tests/test_partner_firstname.py:57 -#: code:addons/partner_firstname/tests/test_partner_firstname.py:71 -#: code:addons/partner_firstname/tests/test_partner_firstname.py:72 +#: code:addons/partner_firstname/exceptions.py:26 #, python-format -msgid "%s (copy)" -msgstr "%s (copia)" - -#. module: partner_firstname -#: model:ir.model,name:partner_firstname.model_res_users -msgid "Users" -msgstr "Usuarios" +msgid "Error(s) with partner %d's name." +msgstr "Errores con el nombre de la empresa %d." #. module: partner_firstname #: field:res.partner,firstname:0 -msgid "Firstname" +msgid "First name" msgstr "Nombre" +#. module: partner_firstname +#: view:res.partner:partner_firstname.partner_form +msgid "Is a Company?" +msgstr "¿Es una empresa?" + #. module: partner_firstname #: field:res.partner,lastname:0 -msgid "Lastname" -msgstr "Apellidos" +msgid "Last name" +msgstr "Apellido" #. module: partner_firstname -#: view:res.partner:0 -msgid "Is a Company?" -msgstr "¿Es una empresa?" +#: code:addons/partner_firstname/exceptions.py:40 +#, python-format +msgid "No name is set." +msgstr "No se ha establecido ningún nombre." #. module: partner_firstname #: model:ir.model,name:partner_firstname.model_res_partner msgid "Partner" msgstr "Empresa" +#~ msgid "True" +#~ msgstr "Verdadero" + +#~ msgid "%s (copy)" +#~ msgstr "%s (copia)" + +#~ msgid "Users" +#~ msgstr "Usuarios" diff --git a/partner_firstname/i18n/partner_firstname.pot b/partner_firstname/i18n/partner_firstname.pot index ab75c787a..96d5a4925 100644 --- a/partner_firstname/i18n/partner_firstname.pot +++ b/partner_firstname/i18n/partner_firstname.pot @@ -1,13 +1,13 @@ -# Translation of OpenERP Server. +# Translation of Odoo Server. # This file contains the translation of the following modules: # * partner_firstname # msgid "" msgstr "" -"Project-Id-Version: OpenERP Server 7.0\n" +"Project-Id-Version: Odoo Server 8.0-20150327\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-01-22 14:09+0000\n" -"PO-Revision-Date: 2014-01-22 14:09+0000\n" +"POT-Creation-Date: 2015-03-30 07:53+0000\n" +"PO-Revision-Date: 2015-03-30 07:53+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -16,38 +16,33 @@ msgstr "" "Plural-Forms: \n" #. module: partner_firstname -#: code:addons/partner_firstname/partner.py:81 -#: code:addons/partner_firstname/res_user.py:35 -#: code:addons/partner_firstname/tests/test_partner_firstname.py:56 -#: code:addons/partner_firstname/tests/test_partner_firstname.py:57 -#: code:addons/partner_firstname/tests/test_partner_firstname.py:71 -#: code:addons/partner_firstname/tests/test_partner_firstname.py:72 +#: code:addons/partner_firstname/exceptions.py:26 #, python-format -msgid "%s (copy)" +msgid "Error(s) with partner %d's name." msgstr "" #. module: partner_firstname -#: model:ir.model,name:partner_firstname.model_res_users -msgid "Users" +#: field:res.partner,firstname:0 +msgid "First name" msgstr "" #. module: partner_firstname -#: field:res.partner,firstname:0 -msgid "Firstname" +#: view:res.partner:partner_firstname.partner_form +msgid "Is a Company?" msgstr "" #. module: partner_firstname #: field:res.partner,lastname:0 -msgid "Lastname" +msgid "Last name" msgstr "" #. module: partner_firstname -#: view:res.partner:0 -msgid "Is a Company?" +#: code:addons/partner_firstname/exceptions.py:40 +#, python-format +msgid "No name is set." msgstr "" #. module: partner_firstname #: model:ir.model,name:partner_firstname.model_res_partner msgid "Partner" msgstr "" - diff --git a/partner_firstname/models.py b/partner_firstname/models.py new file mode 100644 index 000000000..f6476772c --- /dev/null +++ b/partner_firstname/models.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +# Author: Nicolas Bessi. Copyright Camptocamp SA +# Copyright (C) +# 2014: Agile Business Group () +# 2015: Grupo ESOC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from openerp import api, fields, models +from . import exceptions + + +class ResPartner(models.Model): + """Adds last name and first name; name becomes a stored function field.""" + _inherit = 'res.partner' + + firstname = fields.Char("First name") + lastname = fields.Char("Last name") + name = fields.Char( + compute="_name_compute", + inverse="_name_inverse", + required=False, + store=True) + + @api.one + @api.depends("firstname", "lastname") + def _name_compute(self): + """Write the 'name' field according to splitted data.""" + self.name = " ".join((p for p in (self.lastname, + self.firstname) if p)) + + @api.one + def _name_inverse(self): + """Try to reverse the effect of _compute_name_custom. + + - If the partner is a company, save it in the first name. + - Otherwise, make a guess. + """ + # Remove unneeded whitespace + clean = " ".join(self.name.split(None)) + + # Clean name avoiding infinite recursion + if self.name != clean: + self.name = clean + + # Save name in the real fields + else: + # Company name goes to the lastname + if self.is_company: + parts = [clean, False] + + # Guess name splitting + else: + parts = clean.split(" ", 1) + while len(parts) < 2: + parts.append(False) + + self.lastname, self.firstname = parts + + @api.one + @api.constrains("firstname", "lastname") + def _check_name(self): + """Ensure at least one name is set.""" + if not (self.firstname or self.lastname): + raise exceptions.EmptyNames(self) + + @api.model + def _firstname_install(self): + """Save names correctly in the database. + + Before installing the module, field ``name`` contains all full names. + When installing it, this method parses those names and saves them + correctly into the database. This can be called later too if needed. + """ + # Find records with empty firstname and lastname + records = self.search([("firstname", "=", False), + ("lastname", "=", False)]) + + # Force calculations there + records._write_name() diff --git a/partner_firstname/partner.py b/partner_firstname/partner.py deleted file mode 100644 index 52dccd201..000000000 --- a/partner_firstname/partner.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Nicolas Bessi. Copyright Camptocamp SA -# Copyright (C) 2014 Agile Business Group () -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp.osv import orm, fields -from openerp.tools.translate import _ -import logging - -_logger = logging.getLogger(__name__) - - -class ResPartner(orm.Model): - """Adds lastname and firstname, name become a stored function field""" - - _inherit = 'res.partner' - - def _set_default_value_on_column(self, cr, column_name, context=None): - res = super(ResPartner, self)._set_default_value_on_column( - cr, column_name, context=context) - if column_name == 'lastname': - cr.execute('UPDATE res_partner SET lastname = name WHERE name ' - 'IS NOT NULL AND lastname IS NULL') - cr.execute('ALTER TABLE res_partner ALTER COLUMN lastname ' - 'SET NOT NULL') - _logger.info("NOT NULL constraint for " - "res_partner.lastname correctly set") - return res - - def _prepare_name_custom(self, cursor, uid, partner, context=None): - """ - This function is designed to be inherited in a custom module - """ - names = (partner.lastname, partner.firstname) - fullname = " ".join([s for s in names if s]) - return fullname - - def _compute_name_custom(self, cursor, uid, ids, fname, arg, context=None): - res = {} - for partner in self.browse(cursor, uid, ids, context=context): - res[partner.id] = self._prepare_name_custom( - cursor, uid, partner, context=context) - return res - - def _write_name( - self, cursor, uid, partner_id, field_name, field_value, arg, - context=None - ): - """ - Try to reverse the effect of _compute_name_custom: - * if the partner is not a company and the firstname does not change in - the new name then firstname remains untouched and lastname is updated - accordingly - * otherwise lastname=new name and firstname=False - In addition an heuristic avoids to keep a firstname without a non-blank - lastname - """ - field_value = ( - field_value and not field_value.isspace() and field_value or False) - vals = {'lastname': field_value, 'firstname': False} - if field_value: - flds = self.read( - cursor, uid, [partner_id], ['firstname', 'is_company'], - context=context)[0] - if not flds['is_company']: - to_check = ' %s' % flds['firstname'] - if field_value.endswith(to_check): - ln = field_value[:-len(to_check)].strip() - if ln: - vals['lastname'] = ln - del(vals['firstname']) - else: - # If the lastname is deleted from the new name - # then the firstname becomes the lastname - vals['lastname'] = flds['firstname'] - - return self.write(cursor, uid, partner_id, vals, context=context) - - def copy_data(self, cr, uid, id, default=None, context=None): - """ - Avoid to replicate the firstname into the name when duplicating a - partner - """ - default = default or {} - if not default.get('lastname'): - default = default.copy() - default['lastname'] = ( - _('%s (copy)') % self.read( - cr, uid, [id], ['lastname'], context=context - )[0]['lastname'] - ) - if default.get('name'): - del(default['name']) - return super(ResPartner, self).copy_data( - cr, uid, id, default, context=context) - - def create(self, cursor, uid, vals, context=None): - """ - To support data backward compatibility we have to keep this overwrite - even if we use fnct_inv: otherwise we can't create entry because - lastname is mandatory and module will not install if there is demo data - """ - corr_vals = vals.copy() - if corr_vals.get('name'): - corr_vals['lastname'] = corr_vals['name'] - del(corr_vals['name']) - return super(ResPartner, self).create( - cursor, uid, corr_vals, context=context) - - _columns = {'name': fields.function(_compute_name_custom, string="Name", - type="char", store=True, - select=True, readonly=True, - fnct_inv=_write_name), - - 'firstname': fields.char("Firstname"), - 'lastname': fields.char("Lastname", required=True)} diff --git a/partner_firstname/partner_view.xml b/partner_firstname/partner_view.xml deleted file mode 100644 index 87f41b805..000000000 --- a/partner_firstname/partner_view.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - res.partner.simplified.form.firstname - res.partner - - - - {'readonly': [('is_company', '=', False)], 'required': [('is_company', '=', True)]} - - - - - - - - - - - - res.partner.form.firstname - res.partner - - - - {'readonly': [('is_company', '=', False)], 'required': [('is_company', '=', True)]} - - - - - - - - - - - - - - - - - - -
- -
-
- - {'readonly': [('is_company', '=', False)], 'required': [('is_company', '=', True)]} - - -
-
- -
-
diff --git a/partner_firstname/res_user.py b/partner_firstname/res_user.py deleted file mode 100644 index 83ec8be7c..000000000 --- a/partner_firstname/res_user.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -"""Extend res.users to be compatible with split name in res.partner.""" -############################################################################## -# -# Author: Nicolas Bessi. Copyright Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp import api, models -from openerp.tools.translate import _ - - -class ResUsers(models.Model): - """Extend res.users to be compatible with split name in res.partner.""" - _inherit = 'res.users' - - @api.onchange('firstname', 'lastname') - def change_name(self): - names = [name for name in [self.firstname, self.lastname] if name] - self.name = ' '.join(names) - - def copy_data(self, cr, uid, _id, default=None, context=None): - """ - Avoid to replicate the firstname into the name when duplicating a user - """ - default = default or {} - if not default.get('lastname'): - default = default.copy() - default['lastname'] = ( - _('%s (copy)') % self.read( - cr, uid, [_id], ['lastname'], context=context - )[0]['lastname'] - ) - if default.get('name'): - del(default['name']) - return super(ResUsers, self).copy_data( - cr, uid, _id, default, context=context) diff --git a/partner_firstname/res_user_view.xml b/partner_firstname/res_user_view.xml deleted file mode 100644 index f6516468d..000000000 --- a/partner_firstname/res_user_view.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - res.users.form.firstname - res.users - - - - - - - - diff --git a/partner_firstname/tests/test_partner_firstname.py b/partner_firstname/tests/test_partner_firstname.py index a4bac1ee0..8415b129f 100644 --- a/partner_firstname/tests/test_partner_firstname.py +++ b/partner_firstname/tests/test_partner_firstname.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -# -# + # Authors: Nemry Jonathan # Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu) # All Rights Reserved @@ -25,129 +24,131 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -# -import openerp.tests.common as common -from openerp.tools.translate import _ +from openerp.tests.common import TransactionCase +from .. import exceptions as ex -class test_partner_firstname(common.TransactionCase): - +class PartnerFirstnameCase(TransactionCase): def setUp(self): - super(test_partner_firstname, self).setUp() + super(PartnerFirstnameCase, self).setUp() + + self.original = self.env["res.partner"].create({ + "lastname": "lastname", + "firstname": "firstname"}) - self.registry('ir.model').clear_caches() - self.registry('ir.model.data').clear_caches() + def test_copy_partner(self): + """Copy the partner and compare the result.""" + copy = self.original.with_context(lang="en_US").copy() + + self.assertEqual( + copy.name, + "lastname firstname (copy)", + "Copy of the partner failed with wrong name") + self.assertEqual( + copy.lastname, + "lastname", + "Copy of the partner failed with wrong lastname") + self.assertEqual( + copy.firstname, + "firstname (copy)", + "Copy of the partner failed with wrong firstname") - self.user_model = self.registry("res.users") - self.partner_model = self.registry("res.partner") - self.fields_partner = { - 'lastname': 'lastname', 'firstname': 'firstname'} - self.fields_user = { - 'name': 'lastname', 'login': 'v5Ue4Tql0Pm67KX05g25A'} + def test_update_user_lastname(self): + """Change lastname.""" + self.original.name = "changed firstname" - self.context = self.user_model.context_get(self.cr, self.uid) + self.assertEqual( + self.original.name, + "changed firstname", + "Update of the partner lastname failed with wrong name") + self.assertEqual( + self.original.lastname, + "changed", + "Update of the partner lastname failed with wrong lastname") + self.assertEqual( + self.original.firstname, + "firstname", + "Update of the partner lastname failed with wrong firstname") - def test_copy_partner(self): - cr, uid, context = self.cr, self.uid, self.context - res_id = self.partner_model.create( - cr, uid, self.fields_partner, context=context) - res_id = self.partner_model.copy( - cr, uid, res_id, default={}, context=context) - vals = self.partner_model.read(cr, uid, [res_id], [ - 'name', 'lastname', 'firstname'], context=context)[0] - - self.assertEqual( - vals['name'], - _('%s (copy)') % 'lastname' + " firstname", - 'Copy of the partner failed with wrong name' - ) - self.assertEqual( - vals['lastname'], - _('%s (copy)') % 'lastname', - 'Copy of the partner failed with wrong lastname' - ) - self.assertEqual(vals['firstname'], 'firstname', - 'Copy of the partner failed with wrong firstname') + def test_update_user_firstname(self): + """Change firstname.""" + self.original.name = "lastname changed" + + self.assertEqual( + self.original.name, + "lastname changed", + "Update of the partner lastname failed with wrong name") + self.assertEqual( + self.original.lastname, + "lastname", + "Update of the partner lastname failed with wrong lastname") + self.assertEqual( + self.original.firstname, + "changed", + "Update of the partner lastname failed with wrong firstname") + + def test_no_names(self): + """Test that you cannot set a partner without names.""" + with self.assertRaises(ex.EmptyNames): + self.original.firstname = self.original.lastname = False + + +class UserFirstnameCase(TransactionCase): + def setUp(self): + super(UserFirstnameCase, self).setUp() + + self.original = self.env["res.users"].create({ + "name": "lastname firstname", + "login": "firstnametest@example.com"}) def test_copy_user(self): - cr, uid, context = self.cr, self.uid, self.context - # create a user - res_id = self.user_model.create( - cr, uid, self.fields_user, context=context) - # get the related partner id and add it a firstname - flds = self.user_model.read( - cr, uid, [res_id], ['partner_id'], context=context)[0] - self.partner_model.write(cr, uid, flds['partner_id'][ - 0], {'firstname': 'firstname'}, context=context) - # copy the user and compare result - res_id = self.user_model.copy( - cr, uid, res_id, default={}, context=context) - vals = self.user_model.read( - cr, uid, [res_id], ['name', 'lastname', 'firstname'], - context=context)[0] - - self.assertEqual( - vals['name'], - _('%s (copy)') % 'lastname' + ' firstname', - 'Copy of the user failed with wrong name' - ) - self.assertEqual( - vals['lastname'], _('%s (copy)') % - 'lastname', 'Copy of the user failed with wrong lastname') - self.assertEqual(vals['firstname'], 'firstname', - 'Copy of the user failed with wrong firstname') + """Copy the user and compare result.""" + copy = self.original.with_context(lang="en_US").copy() + + self.assertEqual( + copy.name, + "lastname firstname (copy)", + "Copy of the partner failed with wrong name") + self.assertEqual( + copy.lastname, + "lastname", + "Copy of the partner failed with wrong lastname") + self.assertEqual( + copy.firstname, + "firstname (copy)", + "Copy of the partner failed with wrong firstname") def test_update_user_lastname(self): - cr, uid, context = self.cr, self.uid, self.context - # create a user - res_id = self.user_model.create( - cr, uid, self.fields_user, context=context) - # get the related partner id and add it a firstname - flds = self.user_model.read( - cr, uid, [res_id], ['partner_id'], context=context)[0] - self.partner_model.write( - cr, uid, flds['partner_id'][0], {'firstname': 'firstname'}, - context=context) - self.user_model.write( - cr, uid, res_id, {'name': 'change firstname'}, context=context) - vals = self.user_model.read( - cr, uid, [res_id], ['name', 'lastname', 'firstname'], - context=context)[0] - - self.assertEqual(vals['name'], 'change firstname', - 'Update of the user lastname failed with wrong name') - self.assertEqual( - vals['lastname'], 'change', - 'Update of the user lastname failed with wrong lastname') - self.assertEqual( - vals['firstname'], 'firstname', - 'Update of the user lastname failed with wrong firstname') + """Change lastname.""" + self.original.name = "changed firstname" + + self.assertEqual( + self.original.name, + "changed firstname", + "Update of the user lastname failed with wrong name") + self.assertEqual( + self.original.lastname, + "changed", + "Update of the user lastname failed with wrong lastname") + self.assertEqual( + self.original.firstname, + "firstname", + "Update of the user lastname failed with wrong firstname") def test_update_user_firstname(self): - cr, uid, context = self.cr, self.uid, self.context - # create a user - res_id = self.user_model.create( - cr, uid, self.fields_user, context=context) - # get the related partner id and add it a firstname - flds = self.user_model.read( - cr, uid, [res_id], ['partner_id'], context=context)[0] - self.partner_model.write( - cr, uid, flds['partner_id'][0], {'firstname': 'firstname'}, - context=context) - self.user_model.write( - cr, uid, res_id, {'name': 'lastname other'}, context=context) - vals = self.user_model.read( - cr, uid, [res_id], ['name', 'lastname', 'firstname'], - context=context)[0] - - self.assertEqual( - vals['name'], 'lastname other', - 'Update of the user firstname failed with wrong name') - self.assertEqual( - vals['lastname'], 'lastname other', - 'Update of the user firstname failed with wrong lastname') - self.assertFalse( - vals['firstname'], - 'Update of the user firstname failed with wrong firstname') + """Change firstname.""" + self.original.name = "lastname changed" + + self.assertEqual( + self.original.name, + "lastname changed", + "Update of the user lastname failed with wrong name") + self.assertEqual( + self.original.lastname, + "lastname", + "Update of the user lastname failed with wrong lastname") + self.assertEqual( + self.original.firstname, + "changed", + "Update of the user lastname failed with wrong firstname") diff --git a/partner_firstname/views/res_partner.xml b/partner_firstname/views/res_partner.xml new file mode 100644 index 000000000..e917560e5 --- /dev/null +++ b/partner_firstname/views/res_partner.xml @@ -0,0 +1,113 @@ + + + + + + + + Add firstname and lastname + res.partner + + + + + { + 'readonly': [('is_company', '=', False)], + 'required': [('is_company', '=', True)] + } + + + + + + + + + + + + + + Add firstname and surnames + res.partner + + + + + { + 'readonly': [('is_company', '=', False)], + 'required': [('is_company', '=', True)] + } + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + { + 'readonly': [('is_company', '=', False)], + 'required': [('is_company', '=', True)] + } + +
+
+
+ +
+
diff --git a/partner_firstname/views/res_user.xml b/partner_firstname/views/res_user.xml new file mode 100644 index 000000000..5213ed312 --- /dev/null +++ b/partner_firstname/views/res_user.xml @@ -0,0 +1,49 @@ + + + + + + + + Add firstname and surnames + res.users + + + + + True + + + + + + + + + + + + + + From 1a58c2fa7da76522f7a8c5e82eb428fc26833d3a Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 18 May 2015 09:34:15 +0200 Subject: [PATCH 02/20] Fix wrong naming. --- partner_firstname/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/partner_firstname/models.py b/partner_firstname/models.py index f6476772c..b8ccaaea5 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -43,7 +43,7 @@ class ResPartner(models.Model): @api.one def _name_inverse(self): - """Try to reverse the effect of _compute_name_custom. + """Try to reverse the effect of :meth:`._check_name`. - If the partner is a company, save it in the first name. - Otherwise, make a guess. @@ -89,4 +89,4 @@ class ResPartner(models.Model): ("lastname", "=", False)]) # Force calculations there - records._write_name() + records._name_inverse() From 1dbb1e0f8751f37271178665a727c4eefc49a88c Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 18 May 2015 09:37:24 +0200 Subject: [PATCH 03/20] Remove subclassing of exception, since there is only one. --- partner_firstname/exceptions.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/partner_firstname/exceptions.py b/partner_firstname/exceptions.py index 4f1dc6910..226ddb09d 100644 --- a/partner_firstname/exceptions.py +++ b/partner_firstname/exceptions.py @@ -19,18 +19,8 @@ from openerp import _, exceptions -class PartnerNameError(exceptions.ValidationError): - def __init__(self, record, value=None): +class EmptyNames(exceptions.ValidationError): + def __init__(self, record, value=_("No name is set.")): self.record = record self._value = value self.name = _("Error(s) with partner %d's name.") % record.id - - @property - def value(self): - raise NotImplementedError() - - -class EmptyNames(PartnerNameError): - @property - def value(self): - return _("No name is set.") From e4decaed7990c3fbca654a9adce8a406cf5f4b42 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 18 May 2015 09:37:53 +0200 Subject: [PATCH 04/20] Rename exception according to PEP8. --- partner_firstname/exceptions.py | 2 +- partner_firstname/models.py | 2 +- partner_firstname/tests/test_partner_firstname.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/partner_firstname/exceptions.py b/partner_firstname/exceptions.py index 226ddb09d..d13072422 100644 --- a/partner_firstname/exceptions.py +++ b/partner_firstname/exceptions.py @@ -19,7 +19,7 @@ from openerp import _, exceptions -class EmptyNames(exceptions.ValidationError): +class EmptyNamesError(exceptions.ValidationError): def __init__(self, record, value=_("No name is set.")): self.record = record self._value = value diff --git a/partner_firstname/models.py b/partner_firstname/models.py index b8ccaaea5..993f2d06d 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -74,7 +74,7 @@ class ResPartner(models.Model): def _check_name(self): """Ensure at least one name is set.""" if not (self.firstname or self.lastname): - raise exceptions.EmptyNames(self) + raise exceptions.EmptyNamesError(self) @api.model def _firstname_install(self): diff --git a/partner_firstname/tests/test_partner_firstname.py b/partner_firstname/tests/test_partner_firstname.py index 8415b129f..96092c1fc 100644 --- a/partner_firstname/tests/test_partner_firstname.py +++ b/partner_firstname/tests/test_partner_firstname.py @@ -90,7 +90,7 @@ class PartnerFirstnameCase(TransactionCase): def test_no_names(self): """Test that you cannot set a partner without names.""" - with self.assertRaises(ex.EmptyNames): + with self.assertRaises(ex.EmptyNamesError): self.original.firstname = self.original.lastname = False From 49626e192af8bc9a8c4c3221d4e9ec6f6fa4173d Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 18 May 2015 09:52:46 +0200 Subject: [PATCH 05/20] Reduce tests' redundancy. --- .../tests/test_partner_firstname.py | 136 +++++------------- 1 file changed, 35 insertions(+), 101 deletions(-) diff --git a/partner_firstname/tests/test_partner_firstname.py b/partner_firstname/tests/test_partner_firstname.py index 96092c1fc..feb54a98f 100644 --- a/partner_firstname/tests/test_partner_firstname.py +++ b/partner_firstname/tests/test_partner_firstname.py @@ -32,123 +32,57 @@ from .. import exceptions as ex class PartnerFirstnameCase(TransactionCase): def setUp(self): super(PartnerFirstnameCase, self).setUp() + self.check_fields = True + self.create_original() + def create_original(self): self.original = self.env["res.partner"].create({ "lastname": "lastname", "firstname": "firstname"}) - def test_copy_partner(self): - """Copy the partner and compare the result.""" - copy = self.original.with_context(lang="en_US").copy() + def expect(self, lastname, firstname, name=None): + """Define what is expected in each field when ending.""" + self.lastname = lastname + self.firstname = firstname + self.name = name or "%s %s" % (lastname, firstname) - self.assertEqual( - copy.name, - "lastname firstname (copy)", - "Copy of the partner failed with wrong name") - self.assertEqual( - copy.lastname, - "lastname", - "Copy of the partner failed with wrong lastname") - self.assertEqual( - copy.firstname, - "firstname (copy)", - "Copy of the partner failed with wrong firstname") + def tearDown(self): + if self.check_fields: + if not hasattr(self, "changed"): + self.changed = self.original - def test_update_user_lastname(self): - """Change lastname.""" - self.original.name = "changed firstname" + for field in ("name", "lastname", "firstname"): + self.assertEqual( + getattr(self.changed, field), + getattr(self, field), + "Test failed with wrong %s" % field) - self.assertEqual( - self.original.name, - "changed firstname", - "Update of the partner lastname failed with wrong name") - self.assertEqual( - self.original.lastname, - "changed", - "Update of the partner lastname failed with wrong lastname") - self.assertEqual( - self.original.firstname, - "firstname", - "Update of the partner lastname failed with wrong firstname") + super(PartnerFirstnameCase, self).tearDown() - def test_update_user_firstname(self): - """Change firstname.""" - self.original.name = "lastname changed" + def test_copy(self): + """Copy the partner and compare the result.""" + self.expect("lastname", "firstname (copy)") + self.changed = self.original.with_context(lang="en_US").copy() - self.assertEqual( - self.original.name, - "lastname changed", - "Update of the partner lastname failed with wrong name") - self.assertEqual( - self.original.lastname, - "lastname", - "Update of the partner lastname failed with wrong lastname") - self.assertEqual( - self.original.firstname, - "changed", - "Update of the partner lastname failed with wrong firstname") + def test_update_lastname(self): + """Change lastname.""" + self.expect("newlastname", "firstname") + self.original.name = self.name + + def test_update_firstname(self): + """Change firstname.""" + self.expect("lastname", "newfirstname") + self.original.name = self.name def test_no_names(self): - """Test that you cannot set a partner without names.""" + """Test that you cannot set a partner/user without names.""" + self.check_fields = False with self.assertRaises(ex.EmptyNamesError): self.original.firstname = self.original.lastname = False -class UserFirstnameCase(TransactionCase): - def setUp(self): - super(UserFirstnameCase, self).setUp() - +class UserFirstnameCase(PartnerFirstnameCase): + def create_original(self): self.original = self.env["res.users"].create({ "name": "lastname firstname", "login": "firstnametest@example.com"}) - - def test_copy_user(self): - """Copy the user and compare result.""" - copy = self.original.with_context(lang="en_US").copy() - - self.assertEqual( - copy.name, - "lastname firstname (copy)", - "Copy of the partner failed with wrong name") - self.assertEqual( - copy.lastname, - "lastname", - "Copy of the partner failed with wrong lastname") - self.assertEqual( - copy.firstname, - "firstname (copy)", - "Copy of the partner failed with wrong firstname") - - def test_update_user_lastname(self): - """Change lastname.""" - self.original.name = "changed firstname" - - self.assertEqual( - self.original.name, - "changed firstname", - "Update of the user lastname failed with wrong name") - self.assertEqual( - self.original.lastname, - "changed", - "Update of the user lastname failed with wrong lastname") - self.assertEqual( - self.original.firstname, - "firstname", - "Update of the user lastname failed with wrong firstname") - - def test_update_user_firstname(self): - """Change firstname.""" - self.original.name = "lastname changed" - - self.assertEqual( - self.original.name, - "lastname changed", - "Update of the user lastname failed with wrong name") - self.assertEqual( - self.original.lastname, - "lastname", - "Update of the user lastname failed with wrong lastname") - self.assertEqual( - self.original.firstname, - "changed", - "Update of the user lastname failed with wrong firstname") From 3c8be01571cd3c612bd820fb8bee8f92c34032e0 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 18 May 2015 10:25:59 +0200 Subject: [PATCH 06/20] Add test for whitespace trimming. --- partner_firstname/tests/test_partner_firstname.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/partner_firstname/tests/test_partner_firstname.py b/partner_firstname/tests/test_partner_firstname.py index feb54a98f..1fcfc38fc 100644 --- a/partner_firstname/tests/test_partner_firstname.py +++ b/partner_firstname/tests/test_partner_firstname.py @@ -74,6 +74,14 @@ class PartnerFirstnameCase(TransactionCase): self.expect("lastname", "newfirstname") self.original.name = self.name + def test_whitespace_cleanup(self): + """Check that whitespace in name gets cleared.""" + self.expect("newlastname", "newfirstname") + self.original.name = " newlastname newfirstname " + + # Need this to refresh the ``name`` field + self.original.invalidate_cache() + def test_no_names(self): """Test that you cannot set a partner/user without names.""" self.check_fields = False From 9a72a9efd0862877d597778fb26d439552f2a1f6 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Thu, 21 May 2015 12:07:34 +0200 Subject: [PATCH 07/20] Use unicode for code & tests. --- partner_firstname/models.py | 6 +++--- .../tests/test_partner_firstname.py | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/partner_firstname/models.py b/partner_firstname/models.py index 993f2d06d..98506aaeb 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -38,8 +38,8 @@ class ResPartner(models.Model): @api.depends("firstname", "lastname") def _name_compute(self): """Write the 'name' field according to splitted data.""" - self.name = " ".join((p for p in (self.lastname, - self.firstname) if p)) + self.name = u" ".join((p for p in (self.lastname, + self.firstname) if p)) @api.one def _name_inverse(self): @@ -49,7 +49,7 @@ class ResPartner(models.Model): - Otherwise, make a guess. """ # Remove unneeded whitespace - clean = " ".join(self.name.split(None)) + clean = u" ".join(self.name.split(None)) # Clean name avoiding infinite recursion if self.name != clean: diff --git a/partner_firstname/tests/test_partner_firstname.py b/partner_firstname/tests/test_partner_firstname.py index 1fcfc38fc..d880aef5d 100644 --- a/partner_firstname/tests/test_partner_firstname.py +++ b/partner_firstname/tests/test_partner_firstname.py @@ -33,18 +33,19 @@ class PartnerFirstnameCase(TransactionCase): def setUp(self): super(PartnerFirstnameCase, self).setUp() self.check_fields = True + self.expect(u"Núñez", u"Fernán") self.create_original() def create_original(self): self.original = self.env["res.partner"].create({ - "lastname": "lastname", - "firstname": "firstname"}) + "lastname": self.lastname, + "firstname": self.firstname}) def expect(self, lastname, firstname, name=None): """Define what is expected in each field when ending.""" self.lastname = lastname self.firstname = firstname - self.name = name or "%s %s" % (lastname, firstname) + self.name = name or u"%s %s" % (lastname, firstname) def tearDown(self): if self.check_fields: @@ -61,23 +62,23 @@ class PartnerFirstnameCase(TransactionCase): def test_copy(self): """Copy the partner and compare the result.""" - self.expect("lastname", "firstname (copy)") + self.expect(self.lastname, u"%s (copy)" % self.firstname) self.changed = self.original.with_context(lang="en_US").copy() def test_update_lastname(self): """Change lastname.""" - self.expect("newlastname", "firstname") + self.expect(u"newlästname", self.firstname) self.original.name = self.name def test_update_firstname(self): """Change firstname.""" - self.expect("lastname", "newfirstname") + self.expect(self.lastname, u"newfïrstname") self.original.name = self.name def test_whitespace_cleanup(self): """Check that whitespace in name gets cleared.""" - self.expect("newlastname", "newfirstname") - self.original.name = " newlastname newfirstname " + self.expect(u"newlästname", u"newfïrstname") + self.original.name = " newlästname newfïrstname " # Need this to refresh the ``name`` field self.original.invalidate_cache() @@ -92,5 +93,5 @@ class PartnerFirstnameCase(TransactionCase): class UserFirstnameCase(PartnerFirstnameCase): def create_original(self): self.original = self.env["res.users"].create({ - "name": "lastname firstname", + "name": u"%s %s" % (self.lastname, self.firstname), "login": "firstnametest@example.com"}) From 3ab7b801e6239c4a65bcfef2971267fc89c25db5 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 25 May 2015 12:38:00 +0200 Subject: [PATCH 08/20] Fix wrong comments. --- partner_firstname/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/partner_firstname/models.py b/partner_firstname/models.py index 98506aaeb..c760115c2 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -43,9 +43,9 @@ class ResPartner(models.Model): @api.one def _name_inverse(self): - """Try to reverse the effect of :meth:`._check_name`. + """Try to reverse the effect of :meth:`._name_compute`. - - If the partner is a company, save it in the first name. + - If the partner is a company, save it in the lastname. - Otherwise, make a guess. """ # Remove unneeded whitespace From 4b125173f7871d801b5736c250305637b411c5ae Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 25 May 2015 12:48:18 +0200 Subject: [PATCH 09/20] Split _name_inverse adding _name_clean_inverse. Conflicts: partner_firstname/models.py --- partner_firstname/models.py | 42 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/partner_firstname/models.py b/partner_firstname/models.py index c760115c2..f5ae2e6f7 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -30,7 +30,7 @@ class ResPartner(models.Model): lastname = fields.Char("Last name") name = fields.Char( compute="_name_compute", - inverse="_name_inverse", + inverse="_name_clean_inverse", required=False, store=True) @@ -42,12 +42,8 @@ class ResPartner(models.Model): self.firstname) if p)) @api.one - def _name_inverse(self): - """Try to reverse the effect of :meth:`._name_compute`. - - - If the partner is a company, save it in the lastname. - - Otherwise, make a guess. - """ + def _name_clean_inverse(self): + """Clean whitespace in ``name`` and call :meth:`._name_inverse`.""" # Remove unneeded whitespace clean = u" ".join(self.name.split(None)) @@ -57,17 +53,31 @@ class ResPartner(models.Model): # Save name in the real fields else: - # Company name goes to the lastname - if self.is_company: - parts = [clean, False] + self._name_inverse() + + @api.one + def _name_inverse(self): + """Try to revert the effect of :meth:`._name_compute`. - # Guess name splitting - else: - parts = clean.split(" ", 1) - while len(parts) < 2: - parts.append(False) + - If the partner is a company, save it in the lastname. + - Otherwise, make a guess. + + This method can be easily overriden by other submodules. + + When this method is called, ``self.name`` already has unified and + trimmed whitespace. + """ + # Company name goes to the lastname + if self.is_company: + parts = [self.name, False] + + # Guess name splitting + else: + parts = self.name.split(" ", 1) + while len(parts) < 2: + parts.append(False) - self.lastname, self.firstname = parts + self.lastname, self.firstname = parts @api.one @api.constrains("firstname", "lastname") From f371c6c428227f9af3eeceb8c4a9494f0f09dd97 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 25 May 2015 12:49:03 +0200 Subject: [PATCH 10/20] Remove license header in XML files. --- partner_firstname/views/res_partner.xml | 22 ---------------------- partner_firstname/views/res_user.xml | 22 ---------------------- 2 files changed, 44 deletions(-) diff --git a/partner_firstname/views/res_partner.xml b/partner_firstname/views/res_partner.xml index e917560e5..7867fff8c 100644 --- a/partner_firstname/views/res_partner.xml +++ b/partner_firstname/views/res_partner.xml @@ -1,26 +1,4 @@ - - - diff --git a/partner_firstname/views/res_user.xml b/partner_firstname/views/res_user.xml index 5213ed312..c9daee01d 100644 --- a/partner_firstname/views/res_user.xml +++ b/partner_firstname/views/res_user.xml @@ -1,26 +1,4 @@ - - - From 4d99869a3705dc47bfc0c3c592ec9e3316aeb923 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 25 May 2015 12:55:29 +0200 Subject: [PATCH 11/20] Preserve old view names. --- partner_firstname/views/res_partner.xml | 5 +++-- partner_firstname/views/res_user.xml | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/partner_firstname/views/res_partner.xml b/partner_firstname/views/res_partner.xml index 7867fff8c..51c913ecb 100644 --- a/partner_firstname/views/res_partner.xml +++ b/partner_firstname/views/res_partner.xml @@ -1,7 +1,8 @@ - + + Add firstname and lastname res.partner @@ -26,7 +27,7 @@ - + Add firstname and surnames res.partner diff --git a/partner_firstname/views/res_user.xml b/partner_firstname/views/res_user.xml index c9daee01d..41590a35a 100644 --- a/partner_firstname/views/res_user.xml +++ b/partner_firstname/views/res_user.xml @@ -1,7 +1,8 @@ - + + Add firstname and surnames res.users From 40af0327b0695b07d11b4739776fe3bd6603ad17 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 25 May 2015 13:21:54 +0200 Subject: [PATCH 12/20] Credits to last translator. --- partner_firstname/README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/partner_firstname/README.rst b/partner_firstname/README.rst index 774e735f0..19a14dc6b 100644 --- a/partner_firstname/README.rst +++ b/partner_firstname/README.rst @@ -37,6 +37,7 @@ Contributors * Nicolas Bessi * Jonathan Nemry * Olivier Laurent +* Hans Henrik Gabelgaard * Jairo Llopis Maintainer From ac719b66e8fe3f8c1f0f1bdc2fbc6c61d46e1801 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Thu, 28 May 2015 10:00:57 +0200 Subject: [PATCH 13/20] Increase coverage. --- partner_firstname/tests/base.py | 78 +++++++++++++++++++ .../tests/test_partner_firstname.py | 62 +++++---------- 2 files changed, 96 insertions(+), 44 deletions(-) create mode 100644 partner_firstname/tests/base.py diff --git a/partner_firstname/tests/base.py b/partner_firstname/tests/base.py new file mode 100644 index 000000000..9a48dbf3b --- /dev/null +++ b/partner_firstname/tests/base.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +# Authors: Nemry Jonathan +# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu) +# All Rights Reserved +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs. +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly advised to contact a Free Software +# Service Company. +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +from openerp.tests.common import TransactionCase +from .. import exceptions as ex + + +class BaseCase(TransactionCase): + def setUp(self): + super(BaseCase, self).setUp() + self.check_fields = True + self.expect(u"Núñez", u"Fernán") + self.create_original() + + def create_original(self): + self.original = self.env["res.partner"].create({ + "lastname": self.lastname, + "firstname": self.firstname}) + + def expect(self, lastname, firstname, name=None): + """Define what is expected in each field when ending.""" + self.lastname = lastname + self.firstname = firstname + self.name = name or u"%s %s" % (lastname, firstname) + + def tearDown(self): + if self.check_fields: + if not hasattr(self, "changed"): + self.changed = self.original + + for field in ("name", "lastname", "firstname"): + self.assertEqual( + getattr(self.changed, field), + getattr(self, field), + "Test failed with wrong %s" % field) + + super(BaseCase, self).tearDown() + + def test_copy(self): + """Copy the partner and compare the result.""" + self.expect(self.lastname, u"%s (copy)" % self.firstname) + self.changed = self.original.with_context(lang="en_US").copy() + + def test_one_name(self): + """Test what happens when only one name is given.""" + name = u"Mönty" + self.expect(name, False, name) + self.original.name = name + + def test_no_names(self): + """Test that you cannot set a partner/user without names.""" + self.check_fields = False + with self.assertRaises(ex.EmptyNamesError): + self.original.firstname = self.original.lastname = False diff --git a/partner_firstname/tests/test_partner_firstname.py b/partner_firstname/tests/test_partner_firstname.py index d880aef5d..73190caf8 100644 --- a/partner_firstname/tests/test_partner_firstname.py +++ b/partner_firstname/tests/test_partner_firstname.py @@ -25,46 +25,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -from openerp.tests.common import TransactionCase -from .. import exceptions as ex +from .base import BaseCase -class PartnerFirstnameCase(TransactionCase): - def setUp(self): - super(PartnerFirstnameCase, self).setUp() - self.check_fields = True - self.expect(u"Núñez", u"Fernán") - self.create_original() - - def create_original(self): - self.original = self.env["res.partner"].create({ - "lastname": self.lastname, - "firstname": self.firstname}) - - def expect(self, lastname, firstname, name=None): - """Define what is expected in each field when ending.""" - self.lastname = lastname - self.firstname = firstname - self.name = name or u"%s %s" % (lastname, firstname) - - def tearDown(self): - if self.check_fields: - if not hasattr(self, "changed"): - self.changed = self.original - - for field in ("name", "lastname", "firstname"): - self.assertEqual( - getattr(self.changed, field), - getattr(self, field), - "Test failed with wrong %s" % field) - - super(PartnerFirstnameCase, self).tearDown() - - def test_copy(self): - """Copy the partner and compare the result.""" - self.expect(self.lastname, u"%s (copy)" % self.firstname) - self.changed = self.original.with_context(lang="en_US").copy() - +class PartnerContactCase(BaseCase): def test_update_lastname(self): """Change lastname.""" self.expect(u"newlästname", self.firstname) @@ -83,14 +47,24 @@ class PartnerFirstnameCase(TransactionCase): # Need this to refresh the ``name`` field self.original.invalidate_cache() - def test_no_names(self): - """Test that you cannot set a partner/user without names.""" - self.check_fields = False - with self.assertRaises(ex.EmptyNamesError): - self.original.firstname = self.original.lastname = False + +class PartnerCompanyCase(BaseCase): + def create_original(self): + super(PartnerCompanyCase, self).create_original() + self.original.is_company = True + + def test_copy(self): + super(PartnerCompanyCase, self).test_copy() + self.expect(self.name, False, self.name) + + def test_company_inverse(self): + """Test the inverse method in a company record.""" + name = u"Thïs is a Companŷ" + self.expect(name, False, name) + self.original.name = name -class UserFirstnameCase(PartnerFirstnameCase): +class UserCase(PartnerContactCase): def create_original(self): self.original = self.env["res.users"].create({ "name": u"%s %s" % (self.lastname, self.firstname), From 2dcb159bdd826ceeb68658cc3760c97dae04289e Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Tue, 16 Jun 2015 10:54:45 +0200 Subject: [PATCH 14/20] Add args to exception to display the correct message in the UI. --- partner_firstname/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/partner_firstname/exceptions.py b/partner_firstname/exceptions.py index d13072422..99d5b630e 100644 --- a/partner_firstname/exceptions.py +++ b/partner_firstname/exceptions.py @@ -24,3 +24,4 @@ class EmptyNamesError(exceptions.ValidationError): self.record = record self._value = value self.name = _("Error(s) with partner %d's name.") % record.id + self.args = (self.name, value) From 4b9ab2dfaafd7496cce330e1f456fdb8bd6a224c Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 29 Jun 2015 13:06:29 +0200 Subject: [PATCH 15/20] Execute _firstname_install when installing, and log it. --- partner_firstname/__openerp__.py | 1 + partner_firstname/models.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/partner_firstname/__openerp__.py b/partner_firstname/__openerp__.py index 265414cc9..dd2c7d77c 100644 --- a/partner_firstname/__openerp__.py +++ b/partner_firstname/__openerp__.py @@ -30,6 +30,7 @@ 'data': [ 'views/res_partner.xml', 'views/res_user.xml', + 'data/res_partner.yml', ], 'demo': [], 'test': [], diff --git a/partner_firstname/models.py b/partner_firstname/models.py index f5ae2e6f7..4b9df8c95 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -18,10 +18,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging from openerp import api, fields, models from . import exceptions +_logger = logging.getLogger(__name__) + + class ResPartner(models.Model): """Adds last name and first name; name becomes a stored function field.""" _inherit = 'res.partner' @@ -100,3 +104,4 @@ class ResPartner(models.Model): # Force calculations there records._name_inverse() + _logger.info("%d partners updated installing module.", len(records)) From 299b4e2f5f4c7cbc408f6ed7f087983e73e40864 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Wed, 1 Jul 2015 10:47:47 +0200 Subject: [PATCH 16/20] Rename methods to follow guidelines. --- partner_firstname/data/res_partner.yml | 2 +- partner_firstname/models.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/partner_firstname/data/res_partner.yml b/partner_firstname/data/res_partner.yml index 5b45f6643..a88e01841 100644 --- a/partner_firstname/data/res_partner.yml +++ b/partner_firstname/data/res_partner.yml @@ -16,4 +16,4 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -- !function {model: res.partner, name: _firstname_install} +- !function {model: res.partner, name: _install_partner_firstname} diff --git a/partner_firstname/models.py b/partner_firstname/models.py index 4b9df8c95..c529fbfc6 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -33,21 +33,21 @@ class ResPartner(models.Model): firstname = fields.Char("First name") lastname = fields.Char("Last name") name = fields.Char( - compute="_name_compute", - inverse="_name_clean_inverse", + compute="_compute_name", + inverse="_inverse_name_after_cleaning_whitespace", required=False, store=True) @api.one @api.depends("firstname", "lastname") - def _name_compute(self): + def _compute_name(self): """Write the 'name' field according to splitted data.""" self.name = u" ".join((p for p in (self.lastname, self.firstname) if p)) @api.one - def _name_clean_inverse(self): - """Clean whitespace in ``name`` and call :meth:`._name_inverse`.""" + def _inverse_name_after_cleaning_whitespace(self): + """Clean whitespace in ``name`` and call :meth:`._inverse_name`.""" # Remove unneeded whitespace clean = u" ".join(self.name.split(None)) @@ -57,11 +57,11 @@ class ResPartner(models.Model): # Save name in the real fields else: - self._name_inverse() + self._inverse_name() @api.one - def _name_inverse(self): - """Try to revert the effect of :meth:`._name_compute`. + def _inverse_name(self): + """Try to revert the effect of :meth:`._compute_name`. - If the partner is a company, save it in the lastname. - Otherwise, make a guess. @@ -91,7 +91,7 @@ class ResPartner(models.Model): raise exceptions.EmptyNamesError(self) @api.model - def _firstname_install(self): + def _install_partner_firstname(self): """Save names correctly in the database. Before installing the module, field ``name`` contains all full names. @@ -103,5 +103,5 @@ class ResPartner(models.Model): ("lastname", "=", False)]) # Force calculations there - records._name_inverse() + records._inverse_name() _logger.info("%d partners updated installing module.", len(records)) From 79e37f85c3f74989f6e5e6faf9e14523667b2acc Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Wed, 1 Jul 2015 13:31:22 +0200 Subject: [PATCH 17/20] Better docstrings. --- partner_firstname/models.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/partner_firstname/models.py b/partner_firstname/models.py index c529fbfc6..86037a3eb 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -47,7 +47,13 @@ class ResPartner(models.Model): @api.one def _inverse_name_after_cleaning_whitespace(self): - """Clean whitespace in ``name`` and call :meth:`._inverse_name`.""" + """Clean whitespace in :attr:`~.name` and split it. + + Removes leading, trailing and duplicated whitespace. + + The splitting logic is stored separately in :meth:`~._inverse_name`, so + submodules can extend that method and get whitespace cleaning for free. + """ # Remove unneeded whitespace clean = u" ".join(self.name.split(None)) @@ -68,7 +74,7 @@ class ResPartner(models.Model): This method can be easily overriden by other submodules. - When this method is called, ``self.name`` already has unified and + When this method is called, :attr:`~.name` already has unified and trimmed whitespace. """ # Company name goes to the lastname From 87171bb6630c67a107a8191d0326a2a4eca89631 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Wed, 1 Jul 2015 13:31:41 +0200 Subject: [PATCH 18/20] Workaround https://github.com/odoo/odoo/issues/6324. --- partner_firstname/views/res_user.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/partner_firstname/views/res_user.xml b/partner_firstname/views/res_user.xml index 41590a35a..ddfe9c7ef 100644 --- a/partner_firstname/views/res_user.xml +++ b/partner_firstname/views/res_user.xml @@ -2,6 +2,10 @@ + + + Add firstname and surnames res.users From 24948156e329b5321507275200757a7b11c47cbf Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Wed, 1 Jul 2015 13:32:05 +0200 Subject: [PATCH 19/20] Fix users not being able to create companies. This happened because the invert method was not being called when using the UI, and because lastname & firstname fields were required while hidden. --- partner_firstname/models.py | 6 +++++ partner_firstname/views/res_partner.xml | 30 +++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/partner_firstname/models.py b/partner_firstname/models.py index 86037a3eb..83aa50a88 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models.py @@ -96,6 +96,12 @@ class ResPartner(models.Model): if not (self.firstname or self.lastname): raise exceptions.EmptyNamesError(self) + @api.one + @api.onchange("name") + def _onchange_name(self): + """Ensure :attr:`~.name` is inverted in the UI.""" + self._inverse_name_after_cleaning_whitespace() + @api.model def _install_partner_firstname(self): """Save names correctly in the database. diff --git a/partner_firstname/views/res_partner.xml b/partner_firstname/views/res_partner.xml index 51c913ecb..0838628eb 100644 --- a/partner_firstname/views/res_partner.xml +++ b/partner_firstname/views/res_partner.xml @@ -17,10 +17,12 @@ - - + - - + - - +