Merge pull request #78 from akretion/12.0-mig-base_usability

12.0 mig base usability
This commit is contained in:
David Beal
2019-01-02 18:59:12 +01:00
committed by GitHub
16 changed files with 617 additions and 0 deletions

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,39 @@
# © 2014-2016 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Base Usability',
'version': '12.0.0.1.0',
'category': 'Partner',
'license': 'AGPL-3',
'summary': 'Better usability in base module',
'description': """
Base Usability
==============
This module adds *track_visibility='onchange'* on all the important fields of the Partner object.
By default, Odoo doesn't display the title field on all the partner form views. This module fixes it (it replaces the module base_title_on_partner).
It also adds a log message at INFO level when sending an email via SMTP.
It displays the local modules with installable filter.
A group by 'State' is added to module search view.
It provides a _display_report_header method on the res.company object and
_display_full_address on res.partner which are useful for reporting.
""",
'author': 'Akretion',
'website': 'http://www.akretion.com',
'depends': ['base'],
'data': [
'security/group.xml',
'views/partner_view.xml',
'views/partner_bank_view.xml',
'views/users_view.xml',
'views/country_view.xml',
'views/module_view.xml',
],
'installable': True,
}

View File

@@ -0,0 +1,19 @@
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index 083607f9..99ae8857 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -426,7 +426,13 @@ class Users(models.Model):
for user in users:
user.partner_id.active = user.active
if user.partner_id.company_id:
- user.partner_id.write({'company_id': user.company_id.id})
+ # AKRETION HACK: if you have a multi-company setup where
+ # partners are NOT shared between companies, having
+ # company_id=False on partners related to users
+ # avoids a lot of trouble (you should also disable 'read'
+ # on the ir.rule 'user rule' (XMLID base.res_users_rule)
+ # user.partner_id.write({'company_id': user.company_id.id})
+ user.partner_id.write({'company_id': False})
return users
@api.multi

View File

@@ -0,0 +1,5 @@
from . import users
from . import partner
from . import company
from . import mail
from . import misc

View File

@@ -0,0 +1,81 @@
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, api, _
class ResCompany(models.Model):
_inherit = 'res.company'
@api.model
def generate_line(self, fields, options, icon=True, separator=' - '):
assert fields
assert options
content = []
for field in fields:
value = False
if isinstance(field, tuple) and len(field) == 2:
value = field[0]
label = field[1]
uicon = False
elif isinstance(field, str) and field in options:
value = options[field]['value']
label = options[field].get('label')
uicon = options[field].get('icon')
if value:
prefix = icon and uicon or label
if prefix:
content.append('%s %s' % (prefix, value))
else:
content.append(value)
line = separator.join(content)
return line
@api.multi
def _prepare_header_options(self):
self.ensure_one()
options = {
'phone': {
'value': self.phone,
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm
'icon': '\U0001F4DE',
'label': _('Tel:')},
'email': {
'value': self.email,
# http://www.fileformat.info/info/unicode/char/2709/index.htm
'icon': '\u2709',
'label': _('E-mail:')},
'website': {
'value': self.website,
'icon': '\U0001f310',
'label': _('Website:')},
'vat': {
'value': self.vat,
'label': _('TVA :')}, # TODO translate
}
return options
def _report_company_legal_name(self):
'''Method inherited in the module base_company_extension'''
self.ensure_one()
return self.name
# for reports
@api.multi
def _display_report_header(
self, line_details=[['phone', 'website'], ['vat']],
icon=True, line_separator=' - '):
self.ensure_one()
res = ''
address = self.partner_id._display_address(without_company=True)
address = address.replace('\n', ' - ')
line1 = '%s - %s' % (self._report_company_legal_name(), address)
lines = [line1]
options = self._prepare_header_options()
for details in line_details:
line = self.generate_line(
details, options, icon=icon, separator=line_separator)
lines.append(line)
res = '\n'.join(lines)
return res

View File

@@ -0,0 +1,35 @@
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, api
from odoo.addons.base.models.ir_mail_server import extract_rfc2822_addresses
import logging
logger = logging.getLogger(__name__)
class IrMailServer(models.Model):
_inherit = "ir.mail_server"
@api.model
def send_email(
self, message, mail_server_id=None, smtp_server=None,
smtp_port=None, smtp_user=None, smtp_password=None,
smtp_encryption=None, smtp_debug=False, smtp_session=None):
# Start copy from native method
smtp_from = message['Return-Path'] or\
self._get_default_bounce_address() or message['From']
from_rfc2822 = extract_rfc2822_addresses(smtp_from)
smtp_from = from_rfc2822[-1]
# End copy from native method
logger.info(
"Sending email from '%s' to '%s' Cc '%s' Bcc '%s' "
"with subject '%s'",
smtp_from, message.get('To'), message.get('Cc'),
message.get('Bcc'), message.get('Subject'))
return super(IrMailServer, self).send_email(
message, mail_server_id=mail_server_id,
smtp_server=smtp_server, smtp_port=smtp_port,
smtp_user=smtp_user, smtp_password=smtp_password,
smtp_encryption=smtp_encryption, smtp_debug=smtp_debug,
smtp_session=smtp_session)

View File

@@ -0,0 +1,48 @@
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
from odoo.tools import misc
from odoo.tools import float_compare
class BaseLanguageExport(models.TransientModel):
_inherit = 'base.language.export'
# Default format for language files = format used by OpenERP modules
format = fields.Selection(default='po')
class BaseLanguageInstall(models.TransientModel):
_inherit = 'base.language.install'
overwrite = fields.Boolean(default=True)
class BaseUsabilityInstalled(models.AbstractModel):
_name = "base.usability.installed"
_description = "technical flag to see if base_usability module is installed"
formatLang_original = misc.formatLang
def formatLang(self, value, digits=None, grouping=True, monetary=False,
dp=False, currency_obj=False, int_no_digits=True):
with api.Environment.manage():
env = api.Environment(self.cr, self.uid, {})
if (
'base.usability.installed' in env and
int_no_digits and
not monetary and
isinstance(value, float) and
dp):
prec = env['decimal.precision'].precision_get(dp)
if not float_compare(value, int(value), precision_digits=prec):
digits = 0
dp = False
res = formatLang_original(
self, value, digits=digits, grouping=grouping, monetary=monetary,
dp=dp, currency_obj=currency_obj)
return res
misc.formatLang = formatLang

View File

@@ -0,0 +1,139 @@
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
class ResPartner(models.Model):
_inherit = 'res.partner'
# track_visibility is handled in the 'mail' module, and base_usability
# doesn't depend on 'mail', but that doesn't hurt, it will just be
# ignored if mail is not installed
name = fields.Char(track_visibility='onchange')
parent_id = fields.Many2one(track_visibility='onchange')
ref = fields.Char(track_visibility='onchange', copy=False)
lang = fields.Selection(track_visibility='onchange')
user_id = fields.Many2one(track_visibility='onchange')
vat = fields.Char(track_visibility='onchange')
customer = fields.Boolean(track_visibility='onchange')
supplier = fields.Boolean(track_visibility='onchange')
type = fields.Selection(track_visibility='onchange')
street = fields.Char(track_visibility='onchange')
street2 = fields.Char(track_visibility='onchange')
zip = fields.Char(track_visibility='onchange')
city = fields.Char(track_visibility='onchange')
state_id = fields.Many2one(track_visibility='onchange')
country_id = fields.Many2one(track_visibility='onchange')
email = fields.Char(track_visibility='onchange')
is_company = fields.Boolean(track_visibility='onchange')
active = fields.Boolean(track_visibility='onchange')
company_id = fields.Many2one(track_visibility='onchange')
# For reports
name_title = fields.Char(
compute='_compute_name_title', string='Name with Title')
@api.multi
@api.depends('name', 'title')
def _compute_name_title(self):
for partner in self:
name_title = partner.name
if partner.title and not partner.is_company:
partner_lg = partner
# If prefer to read the lang of the partner than the lang
# of the context. That way, an English man will be displayed
# with his title in English whatever the environment
if partner.lang:
partner_lg = partner.with_context(lang=partner.lang)
title = partner_lg.title.shortcut or partner_lg.title.name
name_title = ' '.join([title, name_title])
partner.name_title = name_title
@api.multi
def _display_address(self, without_company=False):
'''Remove empty lines'''
res = super(ResPartner, self)._display_address(
without_company=without_company)
while "\n\n" in res:
res = res.replace('\n\n', '\n')
return res
# for reports
@api.multi
def _display_full_address(
self, details=[
'company', 'name', 'address', 'phone',
'mobile', 'email'],
icon=True):
self.ensure_one()
# To make the icons work with py3o with PDF export, on the py3o server:
# 1) sudo apt-get install fonts-symbola
# 2) start libreoffice in xvfb (don't use --headless) (To confirm)
if self.is_company:
company = self.name
name = False
else:
name = self.name_title
company = self.parent_id and self.parent_id.is_company and\
self.parent_id.name or False
options = {
'name': {
'value': name,
},
'company': {
'value': company,
},
'phone': {
'value': self.phone,
# http://www.fileformat.info/info/unicode/char/1f4de/index.htm
'icon': '\U0001F4DE',
'label': _('Tel:'),
},
'mobile': {
'value': self.mobile,
# http://www.fileformat.info/info/unicode/char/1f4f1/index.htm
'icon': '\U0001F4F1',
'label': _('Mobile:'),
},
'email': {
'value': self.email,
# http://www.fileformat.info/info/unicode/char/2709/index.htm
'icon': '\u2709',
'label': _('E-mail:'),
},
'website': {
'value': self.website,
# http://www.fileformat.info/info/unicode/char/1f310/index.htm
'icon': '\U0001f310',
'label': _('Website:'),
},
'address': {
'value': self._display_address(without_company=True),
}
}
res = []
for detail in details:
if options.get(detail) and options[detail]['value']:
entry = options[detail]
prefix = icon and entry.get('icon') or entry.get('label')
if prefix:
res.append('%s %s' % (prefix, entry['value']))
else:
res.append('%s' % entry['value'])
res = '\n'.join(res)
return res
class ResPartnerCategory(models.Model):
_inherit = 'res.partner.category'
name = fields.Char(translate=False)
class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank'
# In the 'base' module, they didn't put any string, so the bank name is
# displayed as 'Name', which the string of the related field it
# points to
bank_name = fields.Char(string='Bank Name')

View File

@@ -0,0 +1,40 @@
# Copyright 2018 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, api, SUPERUSER_ID, _
from odoo.exceptions import UserError
import logging
logger = logging.getLogger(__name__)
class ResUsers(models.Model):
_inherit = 'res.users'
@api.model
def default_get(self, fields_list):
res = super(ResUsers, self).default_get(fields_list)
# For a new partner auto-created when you create a new user, we prefer
# customer=False and supplier=True by default
res.update({
'customer': False,
'supplier': True,
})
return res
@api.model
def _script_partners_linked_to_users_no_company(self):
if self.env.user.id != SUPERUSER_ID:
raise UserError(_('You must run this script as admin user'))
logger.info(
'START to set company_id=False on partners related to users')
users = self.search(
['|', ('active', '=', True), ('active', '=', False)])
for user in users:
if user.partner_id.company_id:
user.partner_id.company_id = False
logger.info(
'Wrote company_id=False on user %s ID %d',
user.login, user.id)
logger.info(
'END setting company_id=False on partners related to users')
return True

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<!-- This group is used to hide menu entries to everybody,
so you should not put any user in this group. It is used
by the module account_hide_analytic_line, but it will certainly
be used by other modules in the future, that's why I declare
this group in the base_usability module
I don't want to use the base.group_no_one for this, because a lot
of interesing menu entries are attached to this group, so it's
common to have several users that belong to this group -->
<record id="group_nobody" model="res.groups">
<field name="name">Nobody (used to hide native menus)</field>
</record>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2015-2016 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="res_country_state_search" model="ir.ui.view">
<field name="name">base_usability.res.country.state.search</field>
<field name="model">res.country.state</field>
<field name="arch" type="xml">
<search string="Search States">
<field name="name" filter_domain="['|', ('name', 'ilike', self), ('code', '=', self)]" string="Name or Code"/>
<field name="code"/>
<field name="country_id"/>
<group string="Group By" name="groupby">
<filter name="country_groupby" string="Country" context="{'group_by': 'country_id'}"/>
</group>
</search>
</field>
</record>
<record id="res_country_search" model="ir.ui.view">
<field name="name">base_usability.res.country.search</field>
<field name="model">res.country</field>
<field name="arch" type="xml">
<search string="Search Countries">
<field name="name" filter_domain="['|', ('name', 'ilike', self), ('code', '=', self)]" string="Name or Code"/>
<field name="code"/>
<field name="currency_id"/>
<group string="Group By" name="groupby">
<filter name="currency_groupby" string="Currency" context="{'group_by': 'currency_id'}"/>
</group>
</search>
</field>
</record>
<record id="view_country_form" model="ir.ui.view">
<field name="name">base_usability.res.country.form</field>
<field name="model">res.country</field>
<field name="inherit_id" ref="base.view_country_form"/>
<field name="arch" type="xml">
<field name="code" position="after">
<field name="country_group_ids" widget="many2many_tags"/>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2015-2016 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_module_filter" model="ir.ui.view">
<field name="model">ir.module.module</field>
<field name="inherit_id" ref="base.view_module_filter"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='extra']" position="after">
<filter name="installable" string="Installable" domain="[('state', '!=', 'uninstallable')]"/>
</xpath>
<group expand="0" position="inside">
<filter name="state_groupby" string="State" context="{'group_by': 'state'}"/>
</group>
</field>
</record>
<record id="base.open_module_tree" model="ir.actions.act_window">
<field name="context">{'search_default_installable': 1}</field>
</record>
</odoo>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_partner_bank_tree" model="ir.ui.view">
<field name="name">base_usability.res.partner.bank.tree</field>
<field name="model">res.partner.bank</field>
<field name="inherit_id" ref="base.view_partner_bank_tree"/>
<field name="arch" type="xml">
<field name="sequence" position="attributes">
<attribute name="invisible">0</attribute>
<attribute name="widget">handle</attribute>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2014-2016 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_partner_form" model="ir.ui.view">
<field name="name">base_usability.title.on.partner.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<!-- Wider 'name' field -->
<xpath expr="//sheet/div[hasclass('oe_title')]" position="attributes">
<attribute name="style">width: 650px;</attribute>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='email']" position="attributes">
<attribute name="widget">email</attribute>
</xpath>
<!-- Show title not only on Contacts -->
<xpath expr="//field[@name='child_ids']/form//field[@name='title']" position="attributes">
<attribute name="attrs"></attribute>
</xpath>
</field>
</record>
<record id="view_partner_simple_form" model="ir.ui.view">
<field name="name">base_usability.title.on.partner.simplified.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_simple_form"/>
<field name="arch" type="xml">
<field name="function" position="before">
<field name="title"/>
</field>
</field>
</record>
<record id="view_partner_tree" model="ir.ui.view">
<field name="name">base_usability.res.partner.tree</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<field name="country_id" position="attributes">
<attribute name="invisible">0</attribute>
</field>
<field name="country_id" position="before">
<field name="city"/>
</field>
</field>
</record>
<record id="view_res_partner_filter" model="ir.ui.view">
<field name="name">base_usability.partner.search.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<field name="name" position="attributes">
<attribute name="string">Name or Email or Reference</attribute>
<!-- for 'ref', change '=' to 'start with' -->
<attribute name="filter_domain">['|','|',('display_name','ilike',self),('ref','=ilike',self + '%'),('email','ilike',self)]</attribute>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_users_tree" model="ir.ui.view">
<field name="name">base_usability.res.users.tree</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_tree"/>
<field name="arch" type="xml">
<field name="login_date" position="after">
<field name="company_id" groups="base.group_multi_company"/>
</field>
</field>
</record>
</odoo>