diff --git a/partner_identification/__manifest__.py b/partner_identification/__manifest__.py index 5b6aacb6a..e69b444b3 100644 --- a/partner_identification/__manifest__.py +++ b/partner_identification/__manifest__.py @@ -7,26 +7,24 @@ # Copyright 2016 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - 'name': 'Partner Identification Numbers', - 'category': 'Customer Relationship Management', - 'version': '12.0.1.0.0', - 'license': 'AGPL-3', - 'depends': [ - 'contacts', + "name": "Partner Identification Numbers", + "category": "Customer Relationship Management", + "version": "12.0.1.0.0", + "license": "AGPL-3", + "depends": ["contacts",], + "data": [ + "security/ir.model.access.csv", + "views/res_partner_id_category_view.xml", + "views/res_partner_id_number_view.xml", + "views/res_partner_view.xml", ], - 'data': [ - 'security/ir.model.access.csv', - 'views/res_partner_id_category_view.xml', - 'views/res_partner_id_number_view.xml', - 'views/res_partner_view.xml', - ], - 'author': 'ChriCar Beteiligungs- und Beratungs- GmbH,' - 'Tecnativa,' - 'Camptocamp,' - 'ACSONE SA/NV,' - 'LasLabs,' - 'Onestein,' - 'Odoo Community Association (OCA)', - 'website': 'https://github.com/OCA/partner-contact', - 'development_status': 'Production/Stable', + "author": "ChriCar Beteiligungs- und Beratungs- GmbH," + "Tecnativa," + "Camptocamp," + "ACSONE SA/NV," + "LasLabs," + "Onestein," + "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/partner-contact", + "development_status": "Production/Stable", } diff --git a/partner_identification/models/res_partner.py b/partner_identification/models/res_partner.py index 06ae55741..c1fb4f8f1 100644 --- a/partner_identification/models/res_partner.py +++ b/partner_identification/models/res_partner.py @@ -11,16 +11,16 @@ from odoo.exceptions import ValidationError class ResPartner(models.Model): - _inherit = 'res.partner' + _inherit = "res.partner" id_numbers = fields.One2many( - comodel_name='res.partner.id_number', - inverse_name='partner_id', + comodel_name="res.partner.id_number", + inverse_name="partner_id", string="Identification Numbers", ) @api.multi - @api.depends('id_numbers') + @api.depends("id_numbers") def _compute_identification(self, field_name, category_code): """ Compute a field that indicates a certain ID type. @@ -103,19 +103,20 @@ class ResPartner(models.Model): if not name: # No value to set continue - category = self.env['res.partner.id_category'].search([ - ('code', '=', category_code), - ]) + category = self.env["res.partner.id_category"].search( + [("code", "=", category_code),] + ) if not category: - category = self.env['res.partner.id_category'].create({ - 'code': category_code, - 'name': category_code, - }) - self.env['res.partner.id_number'].create({ - 'partner_id': record.id, - 'category_id': category.id, - 'name': name, - }) + category = self.env["res.partner.id_category"].create( + {"code": category_code, "name": category_code,} + ) + self.env["res.partner.id_number"].create( + { + "partner_id": record.id, + "category_id": category.id, + "name": name, + } + ) # There was an identification record singleton found. elif record_len == 1: value = record[field_name] @@ -125,13 +126,14 @@ class ResPartner(models.Model): id_number.active = False # Guard against writing wrong records. else: - raise ValidationError(_( - 'This %s has multiple IDs of this type (%s), so a write ' - 'via the %s field is not possible. In order to fix this, ' - 'please use the IDs tab.', - ) % ( - record._name, category_code, field_name, - )) + raise ValidationError( + _( + "This %s has multiple IDs of this type (%s), so a write " + "via the %s field is not possible. In order to fix this, " + "please use the IDs tab.", + ) + % (record._name, category_code, field_name,) + ) @api.model def _search_identification(self, category_code, operator, value): @@ -161,10 +163,12 @@ class ResPartner(models.Model): Returns: list: Domain to search with. """ - id_numbers = self.env['res.partner.id_number'].search([ - ('name', operator, value), - ('category_id.code', '=', category_code), - ]) + id_numbers = self.env["res.partner.id_number"].search( + [ + ("name", operator, value), + ("category_id.code", "=", category_code), + ] + ) return [ - ('id_numbers.id', 'in', id_numbers.ids), + ("id_numbers.id", "in", id_numbers.ids), ] diff --git a/partner_identification/models/res_partner_id_category.py b/partner_identification/models/res_partner_id_category.py index 9ecbb3ecd..2a224c72e 100644 --- a/partner_identification/models/res_partner_id_category.py +++ b/partner_identification/models/res_partner_id_category.py @@ -19,32 +19,42 @@ class ResPartnerIdCategory(models.Model): _order = "name" code = fields.Char( - string="Code", size=16, required=True, + string="Code", + size=16, + required=True, help="Abbreviation or acronym of this ID type. For example, " - "'driver_license'") + "'driver_license'", + ) name = fields.Char( - string="ID name", required=True, translate=True, - help="Name of this ID type. For example, 'Driver License'") + string="ID name", + required=True, + translate=True, + help="Name of this ID type. For example, 'Driver License'", + ) active = fields.Boolean(string="Active", default=True) validation_code = fields.Text( - 'Python validation code', + "Python validation code", help="Python code called to validate an id number.", - default=lambda self: self._default_validation_code()) + default=lambda self: self._default_validation_code(), + ) def _default_validation_code(self): - return _("\n# Python code. Use failed = True to specify that the id " - "number is not valid.\n" - "# You can use the following variables :\n" - "# - self: browse_record of the current ID Category " - "browse_record\n" - "# - id_number: browse_record of ID number to validate") + return _( + "\n# Python code. Use failed = True to specify that the id " + "number is not valid.\n" + "# You can use the following variables :\n" + "# - self: browse_record of the current ID Category " + "browse_record\n" + "# - id_number: browse_record of ID number to validate" + ) @api.multi def _validation_eval_context(self, id_number): self.ensure_one() - return {'self': self, - 'id_number': id_number, - } + return { + "self": self, + "id_number": id_number, + } @api.multi def validate_id_number(self, id_number): @@ -53,19 +63,23 @@ class ResPartnerIdCategory(models.Model): python validation code fails """ self.ensure_one() - if self.env.context.get('id_no_validate'): + if self.env.context.get("id_no_validate"): return eval_context = self._validation_eval_context(id_number) try: - safe_eval(self.validation_code, - eval_context, - mode='exec', - nocopy=True) + safe_eval( + self.validation_code, eval_context, mode="exec", nocopy=True + ) except Exception as e: raise UserError( - _('Error when evaluating the id_category validation code:' - ':\n %s \n(%s)') % (self.name, e)) - if eval_context.get('failed', False): + _( + "Error when evaluating the id_category validation code:" + ":\n %s \n(%s)" + ) + % (self.name, e) + ) + if eval_context.get("failed", False): raise ValidationError( - _("%s is not a valid %s identifier") % ( - id_number.name, self.name)) + _("%s is not a valid %s identifier") + % (id_number.name, self.name) + ) diff --git a/partner_identification/models/res_partner_id_number.py b/partner_identification/models/res_partner_id_number.py index 86466fb72..82d534ddd 100644 --- a/partner_identification/models/res_partner_id_number.py +++ b/partner_identification/models/res_partner_id_number.py @@ -15,44 +15,59 @@ class ResPartnerIdNumber(models.Model): _description = "Partner ID Number" _order = "name" - @api.constrains('name', 'category_id') + @api.constrains("name", "category_id") def validate_id_number(self): self.category_id.validate_id_number(self) name = fields.Char( - string="ID Number", required=True, + string="ID Number", + required=True, help="The ID itself. For example, Driver License number of this " - "person") + "person", + ) category_id = fields.Many2one( - string="Category", required=True, - comodel_name='res.partner.id_category', - help="ID type defined in configuration. For example, Driver License") - partner_id = fields.Many2one(string="Partner", required=True, - comodel_name='res.partner', - ondelete='cascade') + string="Category", + required=True, + comodel_name="res.partner.id_category", + help="ID type defined in configuration. For example, Driver License", + ) + partner_id = fields.Many2one( + string="Partner", + required=True, + comodel_name="res.partner", + ondelete="cascade", + ) partner_issued_id = fields.Many2one( - string="Issued by", comodel_name='res.partner', + string="Issued by", + comodel_name="res.partner", help="Another partner, who issued this ID. For example, Traffic " - "National Institution") + "National Institution", + ) place_issuance = fields.Char( string="Place of Issuance", help="The place where the ID has been issued. For example the country " - "for passports and visa") + "for passports and visa", + ) date_issued = fields.Date( string="Issued on", help="Issued date. For example, date when person approved his driving " - "exam, 21/10/2009") + "exam, 21/10/2009", + ) valid_from = fields.Date( - string="Valid from", - help="Validation period stating date.") + string="Valid from", help="Validation period stating date." + ) valid_until = fields.Date( string="Valid until", help="Expiration date. For example, date when person needs to renew " - "his driver license, 21/10/2019") + "his driver license, 21/10/2019", + ) comment = fields.Text(string="Notes") status = fields.Selection( - [('draft', 'New'), - ('open', 'Running'), - ('pending', 'To Renew'), - ('close', 'Expired')]) + [ + ("draft", "New"), + ("open", "Running"), + ("pending", "To Renew"), + ("close", "Expired"), + ] + ) active = fields.Boolean(string="Active", default=True) diff --git a/partner_identification/tests/fake_models.py b/partner_identification/tests/fake_models.py index 8438dfa55..b5e30e326 100644 --- a/partner_identification/tests/fake_models.py +++ b/partner_identification/tests/fake_models.py @@ -14,8 +14,7 @@ def setup_test_model(env, model_cls): model_cls._build_model(env.registry, env.cr) env.registry.setup_models(env.cr) env.registry.init_models( - env.cr, [model_cls._name], - dict(env.context, update_custom_fields=True) + env.cr, [model_cls._name], dict(env.context, update_custom_fields=True) ) @@ -24,24 +23,18 @@ def teardown_test_model(env, model_cls): Courtesy of SBidoul from https://github.com/OCA/mis-builder :) """ - if not getattr(model_cls, '_teardown_no_delete', False): + if not getattr(model_cls, "_teardown_no_delete", False): del env.registry.models[model_cls._name] env.registry.setup_models(env.cr) class ResPartner(models.Model): - _name = 'res.partner' - _inherit = 'res.partner' + _name = "res.partner" + _inherit = "res.partner" _teardown_no_delete = True social_security = fields.Char( - compute=lambda s: s._compute_identification( - 'social_security', 'SSN', - ), - inverse=lambda s: s._inverse_identification( - 'social_security', 'SSN', - ), - search=lambda s, *a: s._search_identification( - 'SSN', *a - ), + compute=lambda s: s._compute_identification("social_security", "SSN",), + inverse=lambda s: s._inverse_identification("social_security", "SSN",), + search=lambda s, *a: s._search_identification("SSN", *a), ) diff --git a/partner_identification/tests/test_partner_identification.py b/partner_identification/tests/test_partner_identification.py index aedd4dd5e..c14097cd2 100644 --- a/partner_identification/tests/test_partner_identification.py +++ b/partner_identification/tests/test_partner_identification.py @@ -7,111 +7,164 @@ from odoo.tools import mute_logger class TestPartnerIdentificationBase(common.TransactionCase): - def test_create_id_category(self): - partner_id_category = self.env['res.partner.id_category'].create({ - 'code': 'id_code', - 'name': 'id_name', - }) - self.assertEqual(partner_id_category.name, 'id_name') - self.assertEqual(partner_id_category.code, 'id_code') + partner_id_category = self.env["res.partner.id_category"].create( + {"code": "id_code", "name": "id_name",} + ) + self.assertEqual(partner_id_category.name, "id_name") + self.assertEqual(partner_id_category.code, "id_code") - @mute_logger('odoo.sql_db') + @mute_logger("odoo.sql_db") def test_update_partner_with_no_category(self): - partner_1 = self.env.ref('base.res_partner_1') + partner_1 = self.env.ref("base.res_partner_1") self.assertEqual(len(partner_1.id_numbers), 0) # create without required category with self.assertRaises(IntegrityError): - partner_1.write({'id_numbers': [(0, 0, { - 'name': '1234', - })]}) + partner_1.write({"id_numbers": [(0, 0, {"name": "1234",})]}) def test_update_partner_with_category(self): - partner_1 = self.env.ref('base.res_partner_1') - partner_id_category = self.env['res.partner.id_category'].create({ - 'code': 'new_code', - 'name': 'new_name', - }) + partner_1 = self.env.ref("base.res_partner_1") + partner_id_category = self.env["res.partner.id_category"].create( + {"code": "new_code", "name": "new_name",} + ) # successful creation - partner_1.write({'id_numbers': [(0, 0, { - 'name': '1234', - 'category_id': partner_id_category.id - })]}) + partner_1.write( + { + "id_numbers": [ + ( + 0, + 0, + { + "name": "1234", + "category_id": partner_id_category.id, + }, + ) + ] + } + ) self.assertEqual(len(partner_1.id_numbers), 1) - self.assertEqual(partner_1.id_numbers.name, '1234') + self.assertEqual(partner_1.id_numbers.name, "1234") # delete - partner_1.write({'id_numbers': [(5, 0, 0)]}) + partner_1.write({"id_numbers": [(5, 0, 0)]}) self.assertEqual(len(partner_1.id_numbers), 0) class TestPartnerCategoryValidation(common.TransactionCase): - def test_partner_id_number_validation(self): - partner_id_category = self.env['res.partner.id_category'].create({ - 'code': 'id_code', - 'name': 'id_name', - 'validation_code': """ + partner_id_category = self.env["res.partner.id_category"].create( + { + "code": "id_code", + "name": "id_name", + "validation_code": """ if id_number.name != '1234': failed = True -""" - }) - partner_1 = self.env.ref('base.res_partner_1') +""", + } + ) + partner_1 = self.env.ref("base.res_partner_1") with self.assertRaises(ValidationError), self.cr.savepoint(): - partner_1.write({'id_numbers': [(0, 0, { - 'name': '01234', - 'category_id': partner_id_category.id - })]}) - partner_1.write({'id_numbers': [(0, 0, { - 'name': '1234', - 'category_id': partner_id_category.id - })]}) + partner_1.write( + { + "id_numbers": [ + ( + 0, + 0, + { + "name": "01234", + "category_id": partner_id_category.id, + }, + ) + ] + } + ) + partner_1.write( + { + "id_numbers": [ + ( + 0, + 0, + { + "name": "1234", + "category_id": partner_id_category.id, + }, + ) + ] + } + ) self.assertEqual(len(partner_1.id_numbers), 1) - self.assertEqual(partner_1.id_numbers.name, '1234') + self.assertEqual(partner_1.id_numbers.name, "1234") - partner_id_category2 = self.env['res.partner.id_category'].create({ - 'code': 'id_code2', - 'name': 'id_name2', - 'validation_code': """ + partner_id_category2 = self.env["res.partner.id_category"].create( + { + "code": "id_code2", + "name": "id_name2", + "validation_code": """ if id_number.name != '1235': failed = True -"""}) +""", + } + ) # check that the constrains is also checked when we change the # associated category with self.assertRaises(ValidationError), self.cr.savepoint(): - partner_1.id_numbers.write({ - 'category_id': partner_id_category2.id - }) + partner_1.id_numbers.write( + {"category_id": partner_id_category2.id} + ) def test_bad_validation_code(self): - partner_id_category = self.env['res.partner.id_category'].create({ - 'code': 'id_code', - 'name': 'id_name', - 'validation_code': """ + partner_id_category = self.env["res.partner.id_category"].create( + { + "code": "id_code", + "name": "id_name", + "validation_code": """ if id_number.name != '1234' # missing : failed = True -""" - }) - partner_1 = self.env.ref('base.res_partner_1') +""", + } + ) + partner_1 = self.env.ref("base.res_partner_1") with self.assertRaises(ValidationError): - partner_1.write({'id_numbers': [(0, 0, { - 'name': '1234', - 'category_id': partner_id_category.id - })]}) + partner_1.write( + { + "id_numbers": [ + ( + 0, + 0, + { + "name": "1234", + "category_id": partner_id_category.id, + }, + ) + ] + } + ) def test_bad_validation_code_override(self): """ It should allow a bad validation code if context overrides. """ - partner_id_category = self.env['res.partner.id_category'].create({ - 'code': 'id_code', - 'name': 'id_name', - 'validation_code': """ + partner_id_category = self.env["res.partner.id_category"].create( + { + "code": "id_code", + "name": "id_name", + "validation_code": """ if id_number.name != '1234' # missing : failed = True -""" - }) - partner_1 = self.env.ref('base.res_partner_1').with_context( +""", + } + ) + partner_1 = self.env.ref("base.res_partner_1").with_context( id_no_validate=True, ) - partner_1.write({'id_numbers': [(0, 0, { - 'name': '1234', - 'category_id': partner_id_category.id - })]}) + partner_1.write( + { + "id_numbers": [ + ( + 0, + 0, + { + "name": "1234", + "category_id": partner_id_category.id, + }, + ) + ] + } + ) diff --git a/partner_identification/tests/test_res_partner.py b/partner_identification/tests/test_res_partner.py index b215ab861..e502bb062 100644 --- a/partner_identification/tests/test_res_partner.py +++ b/partner_identification/tests/test_res_partner.py @@ -7,30 +7,31 @@ from .fake_models import ResPartner, setup_test_model, teardown_test_model class TestResPartner(common.SavepointCase): - @classmethod def setUpClass(cls): super().setUpClass() setup_test_model(cls.env, ResPartner) - bad_cat = cls.env['res.partner.id_category'].create({ - 'code': 'another_code', - 'name': 'another_name', - }) - cls.env['res.partner.id_number'].create({ - 'name': 'Bad ID', - 'category_id': bad_cat.id, - 'partner_id': cls.env.user.partner_id.id, - }) - cls.partner_id_category = cls.env['res.partner.id_category'].create({ - 'code': 'id_code', - 'name': 'id_name', - }) - cls.partner = cls.env.ref('base.main_partner') - cls.partner_id = cls.env['res.partner.id_number'].create({ - 'name': 'Good ID', - 'category_id': cls.partner_id_category.id, - 'partner_id': cls.partner.id, - }) + bad_cat = cls.env["res.partner.id_category"].create( + {"code": "another_code", "name": "another_name",} + ) + cls.env["res.partner.id_number"].create( + { + "name": "Bad ID", + "category_id": bad_cat.id, + "partner_id": cls.env.user.partner_id.id, + } + ) + cls.partner_id_category = cls.env["res.partner.id_category"].create( + {"code": "id_code", "name": "id_name",} + ) + cls.partner = cls.env.ref("base.main_partner") + cls.partner_id = cls.env["res.partner.id_number"].create( + { + "name": "Good ID", + "category_id": cls.partner_id_category.id, + "partner_id": cls.partner.id, + } + ) @classmethod def tearDownClass(cls): @@ -39,49 +40,52 @@ class TestResPartner(common.SavepointCase): def test_compute_identification(self): """ It should set the proper field to the proper ID name. """ - self.partner._compute_identification('name', 'id_code') + self.partner._compute_identification("name", "id_code") self.assertEqual(self.partner.name, self.partner_id.name) def test_inverse_identification_saves(self): """ It should set the ID name to the proper field value. """ - self.partner._inverse_identification('name', 'id_code') + self.partner._inverse_identification("name", "id_code") self.assertEqual(self.partner_id.name, self.partner.name) def test_inverse_identification_creates_new_category(self): """ It should create a new category of the type if non-existent. """ - self.partner._inverse_identification('name', 'new_code_type') - category = self.env['res.partner.id_category'].search([ - ('code', '=', 'new_code_type'), - ]) + self.partner._inverse_identification("name", "new_code_type") + category = self.env["res.partner.id_category"].search( + [("code", "=", "new_code_type"),] + ) self.assertTrue(category) def test_inverse_identification_creates_new_id(self): """ It should create a new ID of the type if non-existent. """ - category = self.env['res.partner.id_category'].create({ - 'code': 'new_code_type', - 'name': 'new_code_type', - }) - self.partner._inverse_identification('name', 'new_code_type') - identification = self.env['res.partner.id_number'].search([ - ('category_id', '=', category.id), - ('partner_id', '=', self.partner.id), - ]) + category = self.env["res.partner.id_category"].create( + {"code": "new_code_type", "name": "new_code_type",} + ) + self.partner._inverse_identification("name", "new_code_type") + identification = self.env["res.partner.id_number"].search( + [ + ("category_id", "=", category.id), + ("partner_id", "=", self.partner.id), + ] + ) self.assertEqual(identification.name, self.partner.name) def test_inverse_identification_multi_exception(self): """ It should not allow a write when multiple IDs of same type. """ - self.env['res.partner.id_number'].create({ - 'name': 'Another ID', - 'category_id': self.partner_id_category.id, - 'partner_id': self.partner.id, - }) + self.env["res.partner.id_number"].create( + { + "name": "Another ID", + "category_id": self.partner_id_category.id, + "partner_id": self.partner.id, + } + ) with self.assertRaises(ValidationError): - self.partner._inverse_identification('name', 'id_code') + self.partner._inverse_identification("name", "id_code") def test_search_identification(self): """ It should return the right record when searched by ID. """ - self.partner.social_security = 'Test' - partner = self.env['res.partner'].search([ - ('social_security', '=', 'Test'), - ]) + self.partner.social_security = "Test" + partner = self.env["res.partner"].search( + [("social_security", "=", "Test"),] + ) self.assertEqual(partner, self.partner)