|
|
# Copyright 2013 Nicolas Bessi (Camptocamp SA) # Copyright 2014 Agile Business Group (<http://www.agilebg.com>) # Copyright 2015 Grupo ESOC (<http://www.grupoesoc.es>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging from odoo 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'
firstname = fields.Char( "First name", index=True, ) lastname = fields.Char( "Last name", index=True, ) name = fields.Char( compute="_compute_name", inverse="_inverse_name_after_cleaning_whitespace", required=False, store=True)
@api.model def create(self, vals): """Add inverted names at creation if unavailable.""" context = dict(self.env.context) name = vals.get("name", context.get("default_name"))
if name is not None: # Calculate the splitted fields inverted = self._get_inverse_name( self._get_whitespace_cleaned_name(name), vals.get("is_company", self.default_get(["is_company"])["is_company"])) for key, value in inverted.items(): if not vals.get(key) or context.get("copy"): vals[key] = value
# Remove the combined fields if "name" in vals: del vals["name"] if "default_name" in context: del context["default_name"]
return super(ResPartner, self.with_context(context)).create(vals)
@api.multi def copy(self, default=None): """Ensure partners are copied right.
Odoo adds ``(copy)`` to the end of :attr:`~.name`, but that would get ignored in :meth:`~.create` because it also copies explicitly firstname and lastname fields. """
return super(ResPartner, self.with_context(copy=True)).copy(default)
@api.model def default_get(self, fields_list): """Invert name when getting default values.""" result = super(ResPartner, self).default_get(fields_list)
inverted = self._get_inverse_name( self._get_whitespace_cleaned_name(result.get("name", "")), result.get("is_company", False))
for field in list(inverted.keys()): if field in fields_list: result[field] = inverted.get(field)
return result
@api.model def _names_order_default(self): return 'first_last'
@api.model def _get_names_order(self): """Get names order configuration from system parameters.
You can override this method to read configuration from language, country, company or other"""
return self.env['ir.config_parameter'].sudo().get_param( 'partner_names_order', self._names_order_default())
@api.model def _get_computed_name(self, lastname, firstname): """Compute the 'name' field according to splitted data.
You can override this method to change the order of lastname and firstname the computed name"""
order = self._get_names_order() if order == 'last_first_comma': return ", ".join((p for p in (lastname, firstname) if p)) elif order == 'first_last': return " ".join((p for p in (firstname, lastname) if p)) else: return " ".join((p for p in (lastname, firstname) if p))
@api.multi @api.depends("firstname", "lastname") def _compute_name(self): """Write the 'name' field according to splitted data.""" for record in self: record.name = record._get_computed_name( record.lastname, record.firstname, )
@api.multi def _inverse_name_after_cleaning_whitespace(self): """Clean whitespace in :attr:`~.name` and split it.
The splitting logic is stored separately in :meth:`~._inverse_name`, so submodules can extend that method and get whitespace cleaning for free. """
for record in self: # Remove unneeded whitespace clean = record._get_whitespace_cleaned_name(record.name)
# Clean name avoiding infinite recursion if record.name != clean: record.name = clean
# Save name in the real fields else: record._inverse_name()
@api.model def _get_whitespace_cleaned_name(self, name, comma=False): """Remove redundant whitespace from :param:`name`.
Removes leading, trailing and duplicated whitespace. """
try: name = " ".join(name.split()) if name else name except UnicodeDecodeError: # with users coming from LDAP, name can be a str encoded as utf-8 # this happens with ActiveDirectory for instance, and in that case # we get a UnicodeDecodeError during the automatic ASCII -> Unicode # conversion that Python does for us. # In that case we need to manually decode the string to get a # proper unicode string. name = ' '.join(name.decode('utf-8').split()) if name else name
if comma: name = name.replace(" ,", ",") name = name.replace(", ", ",") return name
@api.model def _get_inverse_name(self, name, is_company=False): """Compute the inverted name.
- If the partner is a company, save it in the lastname. - Otherwise, make a guess.
This method can be easily overriden by other submodules. You can also override this method to change the order of name's attributes
When this method is called, :attr:`~.name` already has unified and trimmed whitespace. """
# Company name goes to the lastname if is_company or not name: parts = [name or False, False] # Guess name splitting else: order = self._get_names_order() # Remove redundant spaces name = self._get_whitespace_cleaned_name( name, comma=(order == 'last_first_comma')) parts = name.split("," if order == 'last_first_comma' else " ", 1) if len(parts) > 1: if order == 'first_last': parts = [" ".join(parts[1:]), parts[0]] else: parts = [parts[0], " ".join(parts[1:])] else: while len(parts) < 2: parts.append(False) return {"lastname": parts[0], "firstname": parts[1]}
@api.multi def _inverse_name(self): """Try to revert the effect of :meth:`._compute_name`.""" for record in self: parts = record._get_inverse_name(record.name, record.is_company) record.lastname = parts['lastname'] record.firstname = parts['firstname']
@api.multi @api.constrains("firstname", "lastname") def _check_name(self): """Ensure at least one name is set.""" for record in self: if all(( record.type == 'contact' or record.is_company, not (record.firstname or record.lastname) )): raise exceptions.EmptyNamesError(record)
@api.onchange("firstname", "lastname") def _onchange_subnames(self): """Avoid recursion when the user changes one of these fields.
This forces to skip the :attr:`~.name` inversion when the user is setting it in a not-inverted way. """
# Modify self's context without creating a new Environment. # See https://github.com/odoo/odoo/issues/7472#issuecomment-119503916. self.env.context = self.with_context(skip_onchange=True).env.context
@api.onchange("name") def _onchange_name(self): """Ensure :attr:`~.name` is inverted in the UI.""" if self.env.context.get("skip_onchange"): # Do not skip next onchange self.env.context = ( self.with_context(skip_onchange=False).env.context) else: self._inverse_name_after_cleaning_whitespace()
@api.model def _install_partner_firstname(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._inverse_name() _logger.info("%d partners updated installing module.", len(records))
# Disabling SQL constraint givint a more explicit error using a Python # contstraint _sql_constraints = [( 'check_name', "CHECK( 1=1 )", 'Contacts require a name.' )]
|